using System.Diagnostics; using System.Net.WebSockets; using System.Text; using HeartRateMonitorAndroid.Services; using Newtonsoft.Json; using Plugin.BLE; using Plugin.BLE.Abstractions.Contracts; using Plugin.BLE.Abstractions.EventArgs; using Plugin.BLE.Abstractions; namespace HeartRateMonitorAndroid; // 心率数据点类 public class HeartRateDataPoint { public DateTime Timestamp { get; set; } public int HeartRate { get; set; } } // 心率图表绘制类 public class HeartRateGraphDrawable : IDrawable { private List _dataPoints = []; private int _maxPoints = 100; // 最多显示100个数据点 private int _minHeartRate = 40; private int _maxHeartRate = 180; // 图表配色方案 private readonly Color _backgroundColor = Color.FromArgb("#F8F9FA"); // 浅灰背景色 private readonly Color _gridLineColor = Color.FromArgb("#E9ECEF"); // 网格线颜色 private readonly Color _axisColor = Color.FromArgb("#CED4DA"); // 坐标轴颜色 private readonly Color _textColor = Color.FromArgb("#6C757D"); // 文本颜色 private readonly Color _heartRateLineColor = Color.FromArgb("#FF4757"); // 心率线颜色 private readonly Color _heartRateAreaColor = Color.FromRgba(255, 71, 87, 0.2); // 心率区域填充颜色 private readonly Color _heartRatePointColor = Color.FromArgb("#FF4757"); // 数据点颜色 private readonly Color _accentColor = Color.FromArgb("#2E86DE"); // 强调色 public void UpdateData(List dataPoints) { _dataPoints = dataPoints.ToList(); // 如果有数据,动态调整Y轴范围 if (_dataPoints.Count > 0) { _minHeartRate = Math.Max(40, _dataPoints.Min(p => p.HeartRate) - 10); _maxHeartRate = Math.Min(200, _dataPoints.Max(p => p.HeartRate) + 10); // 确保Y轴范围合理 int range = _maxHeartRate - _minHeartRate; if (range < 30) // 如果范围太小,扩大它 { _minHeartRate = Math.Max(40, _minHeartRate - (30 - range) / 2); _maxHeartRate = Math.Min(200, _maxHeartRate + (30 - range) / 2); } // 圆整到最接近的10 _minHeartRate = (_minHeartRate / 10) * 10; _maxHeartRate = ((_maxHeartRate + 9) / 10) * 10; } } public void Draw(ICanvas canvas, RectF dirtyRect) { // 设置背景色 canvas.FillColor = _backgroundColor; canvas.FillRectangle(dirtyRect); if (_dataPoints.Count < 2) return; // 至少需要两个点才能绘制线条 // 计算绘图区域,增加左侧留白以放置y轴标签 float leftPadding = 45; float rightPadding = 20; float topPadding = 30; float bottomPadding = 40; float graphWidth = dirtyRect.Width - leftPadding - rightPadding; float graphHeight = dirtyRect.Height - topPadding - bottomPadding; float graphBottom = dirtyRect.Height - bottomPadding; float graphTop = topPadding; float graphLeft = leftPadding; float graphRight = dirtyRect.Width - rightPadding; // 绘制背景和边框 canvas.FillColor = Colors.White; canvas.FillRoundedRectangle(graphLeft - 5, graphTop - 5, graphWidth + 10, graphHeight + 10, 8); canvas.StrokeColor = _gridLineColor; canvas.StrokeSize = 1; canvas.DrawRoundedRectangle(graphLeft - 5, graphTop - 5, graphWidth + 10, graphHeight + 10, 8); // 绘制网格线 canvas.StrokeColor = _gridLineColor; canvas.StrokeSize = 1; canvas.StrokeDashPattern = new float[] { 4, 4 }; // 虚线网格 // 水平网格线和心率刻度 int yStep = (_maxHeartRate - _minHeartRate) > 100 ? 40 : 20; // 根据范围动态调整步长 for (int hr = _minHeartRate; hr <= _maxHeartRate; hr += yStep) { float y = graphBottom - ((hr - _minHeartRate) * graphHeight / (_maxHeartRate - _minHeartRate)); // 绘制网格线 canvas.DrawLine(graphLeft, y, graphRight, y); // 绘制心率刻度 canvas.FontSize = 12; canvas.FontColor = _textColor; canvas.DrawString(hr.ToString(), graphLeft - 25, y, HorizontalAlignment.Center); } // 重置虚线模式 canvas.StrokeDashPattern = null; // 时间刻度线和标签 if (_dataPoints.Count > 0) { int pointCount = _dataPoints.Count; int xStep = Math.Max(1, pointCount / 5); // 大约显示5个时间点 for (int i = 0; i < pointCount; i += xStep) { if (i >= pointCount) break; float x = graphLeft + (i * graphWidth / (pointCount - 1)); // 绘制垂直网格线 canvas.StrokeColor = _gridLineColor; canvas.StrokeDashPattern = [4, 4]; canvas.DrawLine(x, graphTop, x, graphBottom); canvas.StrokeDashPattern = null; // 绘制时间刻度(分钟:秒) canvas.FontSize = 12; canvas.FontColor = _textColor; string timeLabel = _dataPoints[i].Timestamp.ToString("mm:ss"); canvas.DrawString(timeLabel, x, graphBottom + 15, HorizontalAlignment.Center); } } // 绘制坐标轴 canvas.StrokeColor = _axisColor; canvas.StrokeSize = 2; canvas.DrawLine(graphLeft, graphBottom, graphRight, graphBottom); // X轴 canvas.DrawLine(graphLeft, graphTop, graphLeft, graphBottom); // Y轴 // 添加标题 canvas.FontColor = _accentColor; canvas.FontSize = 14; canvas.DrawString("心率监测图表", dirtyRect.Width / 2, graphTop - 15, HorizontalAlignment.Center); // 创建心率曲线路径 PathF linePath = new PathF(); PathF areaPath = new PathF(); bool isFirst = true; // 添加区域填充起始点 areaPath.MoveTo(graphLeft, graphBottom); for (int i = 0; i < _dataPoints.Count; i++) { float x = graphLeft + (i * graphWidth / (_dataPoints.Count - 1)); float y = graphBottom - ((_dataPoints[i].HeartRate - _minHeartRate) * graphHeight / (_maxHeartRate - _minHeartRate)); if (isFirst) { linePath.MoveTo(x, y); areaPath.LineTo(x, y); isFirst = false; } else { // 使用曲线而不是直线,使图表更平滑 if (i > 0 && i < _dataPoints.Count - 1) { float prevX = graphLeft + ((i - 1) * graphWidth / (_dataPoints.Count - 1)); float prevY = graphBottom - ((_dataPoints[i - 1].HeartRate - _minHeartRate) * graphHeight / (_maxHeartRate - _minHeartRate)); float nextX = graphLeft + ((i + 1) * graphWidth / (_dataPoints.Count - 1)); float nextY = graphBottom - ((_dataPoints[i + 1].HeartRate - _minHeartRate) * graphHeight / (_maxHeartRate - _minHeartRate)); float cpx1 = prevX + (x - prevX) * 0.5f; float cpy1 = prevY; float cpx2 = x - (x - prevX) * 0.5f; float cpy2 = y; linePath.CurveTo(cpx1, cpy1, cpx2, cpy2, x, y); areaPath.CurveTo(cpx1, cpy1, cpx2, cpy2, x, y); } else { linePath.LineTo(x, y); areaPath.LineTo(x, y); } } } // 完成区域填充路径 areaPath.LineTo(graphLeft + graphWidth, graphBottom); areaPath.LineTo(graphLeft, graphBottom); areaPath.Close(); // 绘制区域填充 canvas.FillColor = _heartRateAreaColor; canvas.FillPath(areaPath); // 绘制曲线 canvas.StrokeColor = _heartRateLineColor; canvas.StrokeSize = 3; canvas.DrawPath(linePath); // 只绘制最新数据点 if (_dataPoints.Count > 0) { // 获取最新数据点的位置 int lastIndex = _dataPoints.Count - 1; float x = graphLeft + (lastIndex * graphWidth / (_dataPoints.Count - 1)); float y = graphBottom - ((_dataPoints[lastIndex].HeartRate - _minHeartRate) * graphHeight / (_maxHeartRate - _minHeartRate)); // 绘制最新点的标记 canvas.FillColor = _heartRatePointColor; canvas.FillCircle(x, y, 6); canvas.StrokeSize = 2; canvas.StrokeColor = Colors.White; canvas.DrawCircle(x, y, 6); // 显示最新心率值 canvas.FontSize = 12; canvas.FontColor = _heartRateLineColor; //canvas.Font = FontAttributes.Bold; canvas.DrawString(_dataPoints[lastIndex].HeartRate + " bpm", x, y - 15, HorizontalAlignment.Center); } } } public partial class MainPage : ContentPage { const string TAG = "HeartRateMonitor"; // 心率服务和特征的UUID常量 private static readonly Guid HEART_RATE_SERVICE_UUID = Guid.Parse("0000180D-0000-1000-8000-00805F9B34FB"); private static readonly Guid HEART_RATE_MEASUREMENT_CHARACTERISTIC_UUID = Guid.Parse("00002A37-0000-1000-8000-00805F9B34FB"); // 通知相关常量 private const int NOTIFICATION_ID = 100; private const string CHANNEL_ID = "HeartRateMonitorChannel"; // 图表更新定时器相关 private IDispatcherTimer _graphUpdateTimer; private const int GRAPH_UPDATE_INTERVAL_MS = 1000; // 每秒更新一次图表 private int _latestHeartRate = 0; // 保存最新心率值 private bool _hasNewHeartRateData = false; // 标记是否有新数据 // WebSocket相关 private Services.WebSocketService.HeartRateWebSocketClient _webSocketClient; private bool _isWebSocketEnabled = false; // 是否启用WebSocket上报 private const string DEFAULT_WEBSOCKET_URL = "wss://ws.nuanr-mxi.com/ws"; // 默认WebSocket服务器地址 private string _webSocketUrl = DEFAULT_WEBSOCKET_URL; IAdapter _adapter; IBluetoothLE _ble; private bool _isConnecting = false; // 添加连接状态标志 private bool _isRunningInBackground = false; // 心率数据相关 private List _heartRateData = new List(); private HeartRateGraphDrawable _heartRateGraph = new HeartRateGraphDrawable(); private int _minHeartRate = 0; private int _maxHeartRate = 0; private double _avgHeartRate = 0; private DateTime _sessionStartTime; private IDevice _connectedDevice = null; private object _heartRateDataLock = new object(); // 添加锁对象,用于线程安全操作 public MainPage() { InitializeComponent(); _ble = CrossBluetoothLE.Current; _adapter = CrossBluetoothLE.Current.Adapter; _adapter.DeviceDiscovered += OnDeviceDiscovered; // 初始化图表 heartRateGraphicsView.Drawable = _heartRateGraph; // 初始化定时器,用于固定频率更新图表 InitializeGraphUpdateTimer(); // 初始化通知服务 NotificationService.Initialize(); CheckBluetoothState(); } // 初始化图表更新定时器 private void InitializeGraphUpdateTimer() { _graphUpdateTimer = Dispatcher.CreateTimer(); _graphUpdateTimer.Interval = TimeSpan.FromMilliseconds(GRAPH_UPDATE_INTERVAL_MS); _graphUpdateTimer.Tick += async (s, e) => await UpdateGraph(); _graphUpdateTimer.Start(); } // 定时更新图表 private async Task UpdateGraph() { await SendHeartRateToServerAsync(_latestHeartRate); lock (_heartRateDataLock) { // 无论是否有新数据,都更新图表 _heartRateGraph.UpdateData(_heartRateData); heartRateGraphicsView.Invalidate(); // 重置新数据标记 _hasNewHeartRateData = false; // 如果在后台运行且有数据,更新通知 if (_isRunningInBackground && _heartRateData.Count > 0) { TimeSpan duration = DateTime.Now - _sessionStartTime; NotificationService.ShowHeartRateNotification( _latestHeartRate, _avgHeartRate, _minHeartRate, _maxHeartRate, duration); } } } async void CheckBluetoothState() { Debug.WriteLine($"{TAG}: 检查 BLE 状态..."); if (!_ble.IsAvailable) { Debug.WriteLine($"{TAG}: 设备不支持 BLE"); statusLabel.Text = "设备不支持 BLE"; return; } if (!_ble.IsOn) { Debug.WriteLine($"{TAG}: 蓝牙未开启"); statusLabel.Text = "请开启蓝牙后再试"; return; } Debug.WriteLine($"{TAG}: BLE 可用且已开启"); statusLabel.Text = "准备就绪,点击开始扫描"; } async void OnScanClicked(object sender, EventArgs e) { Debug.WriteLine($"{TAG}: 开始扫描附近设备..."); statusLabel.Text = "正在扫描..."; try { // 先停止之前的扫描 if (_adapter.IsScanning) { Debug.WriteLine($"{TAG}: 停止之前的扫描"); await _adapter.StopScanningForDevicesAsync(); // 短暂延迟确保扫描完全停止 await Task.Delay(200); } // 设置扫描参数 _adapter.ScanMode = ScanMode.LowLatency; // 使用低延迟模式提高响应速度 // 先尝试不带服务UUID过滤来扫描,这样可以捕获更多设备 Debug.WriteLine($"{TAG}: 开始全扫描模式"); await _adapter.StartScanningForDevicesAsync(); Debug.WriteLine($"{TAG}: 扫描已启动,将自动超时或在发现心率设备时停止"); } catch (Exception ex) { Debug.WriteLine($"{TAG}: 扫描出错: {ex.Message}"); statusLabel.Text = $"扫描出错: {ex.Message}"; } } void OnDeviceDiscovered(object sender, DeviceEventArgs args) { var device = args.Device; Debug.WriteLine($"{TAG}: 发现设备: {device.Name ?? "未知设备"} ({device.Id})"); foreach (var adv in device.AdvertisementRecords) { Debug.WriteLine($"{TAG}: Adv Type: {adv.Type}, Data: {BitConverter.ToString(adv.Data)}"); } // 检查广播数据是否包含心率服务 UUID (0x180D) bool hasHeartRateService = false; // 检查16位UUID服务列表 var serviceUuids16Bit = device.AdvertisementRecords.FirstOrDefault(r => r.Type == AdvertisementRecordType.UuidsComplete16Bit || r.Type == AdvertisementRecordType.UuidsIncomple16Bit); if (serviceUuids16Bit != null) { // 心率服务UUID是0x180D,根据日志,数据存储顺序为18-0D string dataString = BitConverter.ToString(serviceUuids16Bit.Data); Debug.WriteLine($"{TAG}: 16位UUID数据: {dataString}"); hasHeartRateService = dataString.Contains("18-0D"); if (hasHeartRateService) { Debug.WriteLine($"{TAG}: 在16位UUID中找到心率服务(0x180D)"); } } // 如果16位列表中未找到,则检查128位UUID列表 if (!hasHeartRateService) { var serviceUuids128Bit = device.AdvertisementRecords.FirstOrDefault(r => r.Type == AdvertisementRecordType.UuidsComplete128Bit || r.Type == AdvertisementRecordType.UuidsIncomplete128Bit); if (serviceUuids128Bit != null) { // 心率服务在128位UUID中的格式通常是0000180D-0000-1000-8000-00805F9B34FB // 检查两种可能的排列方式 string dataString = BitConverter.ToString(serviceUuids128Bit.Data); Debug.WriteLine($"{TAG}: 128位UUID数据: {dataString}"); hasHeartRateService = dataString.Contains("18-0D") || dataString.Contains("0D-18"); if (hasHeartRateService) { Debug.WriteLine($"{TAG}: 在128位UUID中找到心率服务(0x180D)"); } } } // 检查设备名称,有些心率设备名称中包含相关信息 if (!hasHeartRateService && !string.IsNullOrEmpty(device.Name)) { string name = device.Name.ToLower(); if (name.Contains("heart") || name.Contains("hr") || name.Contains("pulse") || name.Contains("cardiac") || name.Contains("心率")) { hasHeartRateService = true; } } if (hasHeartRateService) { Debug.WriteLine($"{TAG}: 检测到心率设备: {device.Name ?? "未知设备"}"); // 立即停止扫描 - 这是重要的,必须先停止扫描再连接 if (_adapter.IsScanning) { try { Debug.WriteLine($"{TAG}: 停止扫描以准备连接设备"); _adapter.StopScanningForDevicesAsync().ContinueWith(t => { if (t.IsCompleted && !t.IsFaulted) { Debug.WriteLine($"{TAG}: 扫描已停止,准备连接设备"); MainThread.BeginInvokeOnMainThread(async () => { statusLabel.Text = $"检测到心率设备: {device.Name ?? "未知设备"}"; // 连接设备 await ConnectToHeartRateDeviceAsync(device); }); } else if (t.IsFaulted && t.Exception != null) { Debug.WriteLine($"{TAG}: 停止扫描失败: {t.Exception.Message}"); } }); } catch (Exception ex) { Debug.WriteLine($"{TAG}: 停止扫描时出错: {ex.Message}"); } } else { // 如果没有扫描,直接连接 MainThread.BeginInvokeOnMainThread(async () => { statusLabel.Text = $"检测到心率设备: {device.Name ?? "未知设备"}"; await ConnectToHeartRateDeviceAsync(device); }); } // 尝试从广播数据中解析心率值(有些设备可能在广播中包含数据) var manufacturerData = device.AdvertisementRecords.FirstOrDefault(r => r.Type == AdvertisementRecordType.ManufacturerSpecificData); if (manufacturerData != null && manufacturerData.Data.Length > 1) { int heartRate = manufacturerData.Data[1]; Debug.WriteLine($"{TAG}: 广播心率值: {heartRate} bpm"); MainThread.BeginInvokeOnMainThread(() => { heartRateLabel.Text = $"心率: {heartRate} bpm"; }); } else { Debug.WriteLine($"{TAG}: 未在广播中找到心率值,将尝试连接设备读取"); } // 返回true表示找到心率设备,不再继续处理其他设备 return; } } // 更新通知 private void UpdateNotification(int heartRate) { if (DeviceInfo.Platform != DevicePlatform.Android) return; #if ANDROID var context = Android.App.Application.Context; // 创建PendingIntent用于点击通知时打开应用 var intent = context.PackageManager.GetLaunchIntentForPackage(context.PackageName); var pendingIntent = Android.App.PendingIntent.GetActivity(context, 0, intent, Android.App.PendingIntentFlags.Immutable); // 创建通知内容 var notificationBuilder = new AndroidX.Core.App.NotificationCompat.Builder(context, CHANNEL_ID) .SetContentTitle("心率监测") .SetContentText($"当前心率: {heartRate} bpm 平均: {_avgHeartRate:0} bpm") .SetSmallIcon(Resource.Drawable.notification_icon_background) // 使用Android自带图标,实际应用中应替换为自定义图标 .SetOngoing(true) .SetContentIntent(pendingIntent) .SetPriority(AndroidX.Core.App.NotificationCompat.PriorityHigh); // 如果有统计数据,添加更多信息 if (_heartRateData.Count > 1) { TimeSpan duration = DateTime.Now - _sessionStartTime; string timeInfo = $"监测时长: {duration.Hours:00}:{duration.Minutes:00}:{duration.Seconds:00}"; string statsInfo = $"最低: {_minHeartRate} bpm | 最高: {_maxHeartRate} bpm"; notificationBuilder.SetStyle(new AndroidX.Core.App.NotificationCompat.BigTextStyle() .BigText($"当前心率: {heartRate} bpm\n{timeInfo}\n{statsInfo}")); } // 显示通知 var notificationManager = AndroidX.Core.App.NotificationManagerCompat.From(context); notificationManager.Notify(NOTIFICATION_ID, notificationBuilder.Build()); #endif } // 切换后台模式 async void OnBackgroundClicked(object sender, EventArgs e) { if (_connectedDevice == null || _connectedDevice.State != DeviceState.Connected) { await DisplayAlert("提示", "请先连接心率设备", "确定"); return; } _isRunningInBackground = !_isRunningInBackground; if (_isRunningInBackground) { backgroundButton.Text = "停止后台运行"; // 显示通知 if (_heartRateData.Count > 0) { TimeSpan duration = DateTime.Now - _sessionStartTime; Services.NotificationService.ShowHeartRateNotification( _latestHeartRate, _avgHeartRate, _minHeartRate, _maxHeartRate, duration); } else { Services.NotificationService.ShowHeartRateNotification(0, 0, 0, 0, TimeSpan.Zero); } // 通知用户应用将在后台运行 await DisplayAlert("后台运行", "应用将在后台继续监测心率。可以通过通知栏查看实时数据。", "确定"); } else { backgroundButton.Text = "后台运行"; // 退出后台模式,如果应用在前台则恢复图表更新 // 取消通知 Services.NotificationService.CancelNotification(); } } // 处理WebSocket设置按钮点击 async void OnWebSocketSettingsClicked(object sender, EventArgs e) { string result = await DisplayPromptAsync( "配置数据上传", "请输入WebSocket服务器地址:\n格式:wss://example.com/ws", "确定", "取消", _webSocketUrl, maxLength: 100, keyboard: Keyboard.Url); //if (string.IsNullOrWhiteSpace(result)) return; if (!result.StartsWith("ws://") && !result.StartsWith("wss://")) { // fallback到默认websocket服务器 result = _webSocketUrl; } try { // 更新按钮状态,显示正在连接 webSocketButton.Text = "正在连接..."; webSocketButton.IsEnabled = false; // 初始化WebSocket客户端 await InitializeWebSocketClientAsync(result); if (_isWebSocketEnabled) { webSocketButton.Text = "数据上传已启用"; webSocketButton.BackgroundColor = Color.FromArgb("#28A745"); // 绿色 await DisplayAlert("连接成功", "心率数据将会实时上传到服务器", "确定"); // 禁用按钮 webSocketButton.IsEnabled = false; } else { webSocketButton.Text = "配置数据上传"; webSocketButton.BackgroundColor = Color.FromArgb("#6C757D"); // 灰色 await DisplayAlert("连接失败", "无法连接到指定的WebSocket服务器,请检查地址或网络连接", "确定"); } } catch (Exception ex) { webSocketButton.Text = "配置数据上传"; webSocketButton.BackgroundColor = Color.FromArgb("#6C757D"); // 灰色 await DisplayAlert("错误", $"配置WebSocket时出错: {ex.Message}", "确定"); } finally { webSocketButton.IsEnabled = true; } } async Task ConnectToHeartRateDeviceAsync(IDevice device) { // 防止重复连接 if (_isConnecting) { Debug.WriteLine($"{TAG}: 已有连接请求正在进行中,忽略此次连接"); return; } _isConnecting = true; try { // 确保扫描已停止 - 这一步非常重要 if (_adapter.IsScanning) { Debug.WriteLine($"{TAG}: 连接前确保扫描已停止"); await _adapter.StopScanningForDevicesAsync(); // 短暂延迟确保扫描完全停止 await Task.Delay(300); } statusLabel.Text = $"正在连接到 {device.Name ?? "未知设备"}..."; Debug.WriteLine($"{TAG}: 正在连接到设备: {device.Name ?? "未知设备"}..."); // 使用CancellationToken添加超时控制 var cancelSource = new CancellationTokenSource(); cancelSource.CancelAfter(TimeSpan.FromSeconds(15)); // 15秒连接超时 // 连接到设备 try { await _adapter.ConnectToDeviceAsync(device, new ConnectParameters(autoConnect: false, forceBleTransport: true), cancelSource.Token); Debug.WriteLine($"{TAG}: 连接命令已发送,等待连接完成"); } catch (OperationCanceledException) { Debug.WriteLine($"{TAG}: 连接操作超时"); MainThread.BeginInvokeOnMainThread(() => { statusLabel.Text = "连接超时,请重试"; }); return; } if (device.State == DeviceState.Connected) { statusLabel.Text = $"已连接到 {device.Name ?? "未知设备"}"; Debug.WriteLine($"{TAG}: 已连接到设备: {device.Name ?? "未知设备"}"); // 保存连接的设备引用 _connectedDevice = device; // 重置心率数据 _heartRateData.Clear(); _minHeartRate = 0; _maxHeartRate = 0; _avgHeartRate = 0; minHeartRateLabel.Text = "--"; maxHeartRateLabel.Text = "--"; avgHeartRateLabel.Text = "--"; noDataLabel.IsVisible = true; heartRateGraphicsView.Invalidate(); // 获取心率服务 Debug.WriteLine($"{TAG}: 尝试获取心率服务 {HEART_RATE_SERVICE_UUID}"); var heartRateService = await device.GetServiceAsync(HEART_RATE_SERVICE_UUID); if (heartRateService == null) { Debug.WriteLine($"{TAG}: 未找到心率服务"); statusLabel.Text = "未找到心率服务"; return; } Debug.WriteLine($"{TAG}: 已获取心率服务,尝试获取心率特征"); // 获取心率特征 var heartRateCharacteristic = await heartRateService.GetCharacteristicAsync(HEART_RATE_MEASUREMENT_CHARACTERISTIC_UUID); if (heartRateCharacteristic == null) { Debug.WriteLine($"{TAG}: 未找到心率特征"); statusLabel.Text = "未找到心率特征"; return; } // 订阅心率通知 heartRateCharacteristic.ValueUpdated += (s, e) => { // 解析心率数据 var data = e.Characteristic.Value; if (data == null || data.Length == 0) return; byte flags = data[0]; bool isHeartRateValueFormat16Bit = ((flags & 0x01) != 0); int heartRate; if (isHeartRateValueFormat16Bit && data.Length >= 3) { heartRate = BitConverter.ToUInt16(data, 1); } else if (data.Length >= 2) { heartRate = data[1]; } else { return; // 数据不完整 } //Debug.WriteLine($"{TAG}: 收到心率值: {heartRate} bpm"); MainThread.BeginInvokeOnMainThread(() => { UpdateHeartRateData(heartRate); }); }; // 开始接收通知 await heartRateCharacteristic.StartUpdatesAsync(); statusLabel.Text = "正在监测心率..."; } else { Debug.WriteLine($"{TAG}: 连接失败"); statusLabel.Text = "连接失败,请重试"; } } catch (Exception ex) { Debug.WriteLine($"{TAG}: 连接错误: {ex.Message}"); statusLabel.Text = $"连接错误: {ex.Message}"; } finally { // 重置连接状态标志 _isConnecting = false; } } // 释放资源 ~MainPage() { if (_webSocketClient != null) { _webSocketClient.Dispose(); _webSocketClient = null; } } // 更新心率数据和图表 private void UpdateHeartRateData(int heartRate) { // 更新UI上的心率显示 heartRateLabel.Text = $"心率: {heartRate} bpm"; // 保存最新心率值,用于通知 _latestHeartRate = heartRate; lock (_heartRateDataLock) // 使用锁确保线程安全 { // 添加新的数据点 var dataPoint = new HeartRateDataPoint { Timestamp = DateTime.Now, HeartRate = heartRate }; // 如果是第一个数据点,记录会话开始时间 if (_heartRateData.Count == 0) { _sessionStartTime = DateTime.Now; MainThread.BeginInvokeOnMainThread(() => { noDataLabel.IsVisible = false; }); } _heartRateData.Add(dataPoint); // 限制数据点数量 if (_heartRateData.Count > 100) { _heartRateData.RemoveAt(0); } // 更新统计信息 if (_heartRateData.Count > 0) { _minHeartRate = _heartRateData.Min(p => p.HeartRate); _maxHeartRate = _heartRateData.Max(p => p.HeartRate); _avgHeartRate = _heartRateData.Average(p => p.HeartRate); MainThread.BeginInvokeOnMainThread(() => { minHeartRateLabel.Text = _minHeartRate.ToString(); maxHeartRateLabel.Text = _maxHeartRate.ToString(); avgHeartRateLabel.Text = _avgHeartRate.ToString("0"); }); } // 标记有新数据,等待定时器更新图表 _hasNewHeartRateData = true; } } // 创建通知渠道(Android特有) private void CreateNotificationChannel() { #if ANDROID if (OperatingSystem.IsAndroidVersionAtLeast(26)) { var context = Android.App.Application.Context; var channel = new Android.App.NotificationChannel(CHANNEL_ID, "心率监测", Android.App.NotificationImportance.High) { Description = "显示实时心率数据" }; var notificationManager = context.GetSystemService(Android.Content.Context .NotificationService) as Android.App.NotificationManager; notificationManager?.CreateNotificationChannel(channel); } #endif } // 初始化WebSocket客户端 private async Task InitializeWebSocketClientAsync(string url = null) { // 释放现有的WebSocket客户端 if (_webSocketClient != null) { _webSocketClient.Dispose(); _webSocketClient = null; } // 使用提供的URL或默认URL _webSocketUrl = string.IsNullOrEmpty(url) ? DEFAULT_WEBSOCKET_URL : url; try { _webSocketClient = new Services.WebSocketService.HeartRateWebSocketClient(_webSocketUrl); await _webSocketClient.ConnectAsync(); _isWebSocketEnabled = true; Debug.WriteLine($"{TAG}: WebSocket客户端已初始化,连接到 {_webSocketUrl}"); } catch (Exception ex) { Debug.WriteLine($"{TAG}: 初始化WebSocket客户端失败: {ex.Message}"); _isWebSocketEnabled = false; } } // 发送心率数据到WebSocket服务器 private async Task SendHeartRateToServerAsync(int heartRate) { if (!_isWebSocketEnabled || _webSocketClient == null) return; try { var data = new WebSocketService.HeartRateData { HeartRate = heartRate, Timestamp = DateTime.Now, DeviceName = _connectedDevice?.Name ?? "未知设备" }; await _webSocketClient.SendHeartRateDataAsync(data); //Debug.WriteLine($"已发送心率数据 {heartRate} bpm 到服务器"); } catch (Exception ex) { Debug.WriteLine($"发送心率数据失败: {ex.Message}"); } } }