Commit 12e86f2f authored by akari's avatar akari

feat: 蓝牙数据同步到主页

parent e5f6d0a9
...@@ -28,8 +28,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> { ...@@ -28,8 +28,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
try { try {
// 新接口在登录响应中直接返回用户信息 // 新接口在登录响应中直接返回用户信息
final loginModel = final loginModel = await _authService.login(event.phone, event.smsCode);
await _authService.login(event.phone, event.smsCode);
if (loginModel.user != null) { if (loginModel.user != null) {
emit(AuthSuccess(loginModel.user!)); emit(AuthSuccess(loginModel.user!));
......
import 'dart:async';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:laki_icu_app/models/bo/bluetooth_read_model.dart';
import 'package:laki_icu_app/utils/event_bus.dart';
import 'package:laki_icu_app/utils/storage/storage_service.dart';
import 'bluetooth_read_event.dart';
import 'bluetooth_read_state.dart';
class BluetoothReadBloc extends Bloc<BluetoothReadEvent, BluetoothReadState> {
final StorageService _storageService;
late final StreamSubscription<BluetoothReadInfoChangedEvent> _infoChangedSub;
late final StreamSubscription<BluetoothReadInfoClearedEvent> _infoClearedSub;
BluetoothReadBloc({
required StorageService storageService,
}) : _storageService = storageService,
super(const BluetoothReadState()) {
on<BluetoothReadLoadRequested>(_onLoadRequested);
on<BluetoothReadUpdated>(_onUpdated);
on<BluetoothReadCleared>(_onCleared);
_infoChangedSub = eventBus.on<BluetoothReadInfoChangedEvent>().listen(
(event) => add(BluetoothReadUpdated(event.info)),
);
_infoClearedSub = eventBus.on<BluetoothReadInfoClearedEvent>().listen(
(_) => add(const BluetoothReadCleared()),
);
add(const BluetoothReadLoadRequested());
}
Future<void> _onLoadRequested(
BluetoothReadLoadRequested event,
Emitter<BluetoothReadState> emit,
) async {
emit(state.copyWith(isLoading: true, clearError: true));
try {
final info = await _storageService.getBluetoothReadInfo();
emit(state.copyWith(info: info, isLoading: false, clearError: true));
} catch (e) {
emit(state.copyWith(isLoading: false, error: e.toString()));
}
}
void _onUpdated(
BluetoothReadUpdated event,
Emitter<BluetoothReadState> emit,
) {
emit(state.copyWith(info: event.info, clearError: true));
}
void _onCleared(
BluetoothReadCleared event,
Emitter<BluetoothReadState> emit,
) {
emit(state.copyWith(info: BluetoothReadModel.empty, clearError: true));
}
@override
Future<void> close() async {
await _infoChangedSub.cancel();
await _infoClearedSub.cancel();
return super.close();
}
}
import 'package:equatable/equatable.dart';
import 'package:laki_icu_app/models/bo/bluetooth_read_model.dart';
abstract class BluetoothReadEvent extends Equatable {
const BluetoothReadEvent();
@override
List<Object?> get props => [];
}
class BluetoothReadLoadRequested extends BluetoothReadEvent {
const BluetoothReadLoadRequested();
}
class BluetoothReadUpdated extends BluetoothReadEvent {
final BluetoothReadModel info;
const BluetoothReadUpdated(this.info);
@override
List<Object?> get props => [info];
}
class BluetoothReadCleared extends BluetoothReadEvent {
const BluetoothReadCleared();
}
import 'package:equatable/equatable.dart';
import 'package:laki_icu_app/models/bo/bluetooth_read_model.dart';
class BluetoothReadState extends Equatable {
final BluetoothReadModel info;
final bool isLoading;
final String? error;
const BluetoothReadState({
this.info = BluetoothReadModel.empty,
this.isLoading = false,
this.error,
});
BluetoothReadState copyWith({
BluetoothReadModel? info,
bool? isLoading,
String? error,
bool clearError = false,
}) {
return BluetoothReadState(
info: info ?? this.info,
isLoading: isLoading ?? this.isLoading,
error: clearError ? null : error ?? this.error,
);
}
@override
List<Object?> get props => [info, isLoading, error];
}
...@@ -6,6 +6,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; ...@@ -6,6 +6,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:laki_icu_app/blocs/auth/auth_bloc.dart'; import 'package:laki_icu_app/blocs/auth/auth_bloc.dart';
import 'package:laki_icu_app/blocs/auth/auth_event.dart'; import 'package:laki_icu_app/blocs/auth/auth_event.dart';
import 'package:laki_icu_app/blocs/auth/auth_state.dart'; import 'package:laki_icu_app/blocs/auth/auth_state.dart';
import 'package:laki_icu_app/blocs/bluetooth_read/bluetooth_read_bloc.dart';
import 'package:laki_icu_app/routes/app_router.dart'; import 'package:laki_icu_app/routes/app_router.dart';
import 'package:laki_icu_app/repositories/auth_repository.dart'; import 'package:laki_icu_app/repositories/auth_repository.dart';
import 'package:laki_icu_app/services/auth_service.dart'; import 'package:laki_icu_app/services/auth_service.dart';
...@@ -32,14 +33,14 @@ void main(List<String> args) { ...@@ -32,14 +33,14 @@ void main(List<String> args) {
// WidgetsFlutterBinding.ensureInitialized(); // WidgetsFlutterBinding.ensureInitialized();
// UME 调试工具(仅 Debug 模式启用) // UME 调试工具(仅 Debug 模式启用)
if (kDebugMode) { if (kDebugMode) {
MarionetteBinding.ensureInitialized(); MarionetteBinding.ensureInitialized();
PluginManager.instance PluginManager.instance
..register(WidgetInfoInspector()) ..register(WidgetInfoInspector())
..register(WidgetDetailInspector()) ..register(WidgetDetailInspector())
..register(Console()); ..register(Console());
// ..register(DioInspector(dio: ApiClient.instance().dio)); // ..register(DioInspector(dio: ApiClient.instance().dio));
runApp(UMEWidget(enable: true, child: const MyApp())); runApp(UMEWidget(enable: true, child: const MyApp()));
} else { } else {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
...@@ -61,9 +62,15 @@ class _MyAppState extends State<MyApp> { ...@@ -61,9 +62,15 @@ class _MyAppState extends State<MyApp> {
late final AuthService _authService; late final AuthService _authService;
late final AppRouter _appRouter; late final AppRouter _appRouter;
late final AuthBloc _authBloc; late final AuthBloc _authBloc;
BluetoothReadBloc? _bluetoothReadBloc;
StreamSubscription? _tokenExpiredSubscription; StreamSubscription? _tokenExpiredSubscription;
bool _isInitializing = true; bool _isInitializing = true;
BluetoothReadBloc get _bluetoothReadBlocInstance =>
_bluetoothReadBloc ??= BluetoothReadBloc(
storageService: _storageService,
);
@override @override
void initState() { void initState() {
super.initState(); super.initState();
...@@ -146,6 +153,7 @@ class _MyAppState extends State<MyApp> { ...@@ -146,6 +153,7 @@ class _MyAppState extends State<MyApp> {
void dispose() { void dispose() {
_tokenExpiredSubscription?.cancel(); _tokenExpiredSubscription?.cancel();
_authBloc.close(); _authBloc.close();
_bluetoothReadBloc?.close();
super.dispose(); super.dispose();
} }
...@@ -157,6 +165,9 @@ class _MyAppState extends State<MyApp> { ...@@ -157,6 +165,9 @@ class _MyAppState extends State<MyApp> {
return MultiBlocProvider( return MultiBlocProvider(
providers: [ providers: [
BlocProvider<AuthBloc>.value(value: _authBloc), BlocProvider<AuthBloc>.value(value: _authBloc),
BlocProvider<BluetoothReadBloc>.value(
value: _bluetoothReadBlocInstance,
),
// BlocProvider<CounterBloc>(create: (_) => CounterBloc()), // BlocProvider<CounterBloc>(create: (_) => CounterBloc()),
], ],
child: MultiBlocListener( child: MultiBlocListener(
......
import 'package:equatable/equatable.dart';
import 'package:laki_icu_app/utils/bluetooth/mcu_report.dart';
class BluetoothReadModel extends Equatable {
static const empty = BluetoothReadModel();
final String? sn;
final double? mainTemperature;
final int? humidity;
final int? oxygenConcentration;
final int? co2Measurement;
final DateTime? updatedAt;
const BluetoothReadModel({
this.sn,
this.mainTemperature,
this.humidity,
this.oxygenConcentration,
this.co2Measurement,
this.updatedAt,
});
factory BluetoothReadModel.fromMcuReport(McuReport report) {
return BluetoothReadModel(
sn: report.sn.trim().isEmpty ? null : report.sn.trim(),
mainTemperature: report.mainTemperature,
humidity: report.humidity,
oxygenConcentration: report.oxygenConcentration,
co2Measurement: report.co2Measurement,
updatedAt: DateTime.now(),
);
}
bool get hasSn => sn != null && sn!.isNotEmpty;
BluetoothReadModel copyWith({
String? sn,
double? mainTemperature,
int? humidity,
int? oxygenConcentration,
int? co2Measurement,
DateTime? updatedAt,
bool clearSn = false,
}) {
return BluetoothReadModel(
sn: clearSn ? null : sn ?? this.sn,
mainTemperature: mainTemperature ?? this.mainTemperature,
humidity: humidity ?? this.humidity,
oxygenConcentration: oxygenConcentration ?? this.oxygenConcentration,
co2Measurement: co2Measurement ?? this.co2Measurement,
updatedAt: updatedAt ?? this.updatedAt,
);
}
@override
List<Object?> get props => [
sn,
mainTemperature,
humidity,
oxygenConcentration,
co2Measurement,
updatedAt,
];
}
...@@ -11,9 +11,6 @@ class UserModel extends Equatable { ...@@ -11,9 +11,6 @@ class UserModel extends Equatable {
final dynamic hospitalId; final dynamic hospitalId;
final String? hospitalName; final String? hospitalName;
// ignore: non_constant_identifier_names
static String? SN;
const UserModel({ const UserModel({
required this.id, required this.id,
this.nickname, this.nickname,
......
...@@ -248,6 +248,10 @@ class BleBluetoothManager<T> { ...@@ -248,6 +248,10 @@ class BleBluetoothManager<T> {
return; return;
} }
if (!await ensureBluetoothReady()) {
return;
}
await disconnect(); await disconnect();
_connectedDeviceId = deviceId; _connectedDeviceId = deviceId;
_notifyCharacteristic = QualifiedCharacteristic( _notifyCharacteristic = QualifiedCharacteristic(
......
import 'dart:async'; import 'dart:async';
import 'package:laki_icu_app/models/bo/bluetooth_read_model.dart';
class EventBus { class EventBus {
final StreamController<dynamic> _controller = StreamController.broadcast(); final StreamController<dynamic> _controller = StreamController.broadcast();
...@@ -27,3 +29,15 @@ class LogoutEvent {} ...@@ -27,3 +29,15 @@ class LogoutEvent {}
// 打开蓝牙扫描/绑定页面事件 // 打开蓝牙扫描/绑定页面事件
class OpenBluetoothScanEvent {} class OpenBluetoothScanEvent {}
// 蓝牙读取信息更新事件
class BluetoothReadInfoChangedEvent {
final BluetoothReadModel info;
const BluetoothReadInfoChangedEvent(this.info);
}
// 蓝牙读取信息清空事件
class BluetoothReadInfoClearedEvent {
const BluetoothReadInfoClearedEvent();
}
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:laki_icu_app/models/bo/bluetooth_read_model.dart';
import 'package:laki_icu_app/models/bo/user_model.dart'; import 'package:laki_icu_app/models/bo/user_model.dart';
class StorageService { class StorageService {
...@@ -9,7 +10,8 @@ class StorageService { ...@@ -9,7 +10,8 @@ class StorageService {
static const String _keyRememberSmsCode = 'remember_sms_code'; static const String _keyRememberSmsCode = 'remember_sms_code';
static const String _keyRememberEnabled = 'remember_enabled'; static const String _keyRememberEnabled = 'remember_enabled';
static const String _keyUserInfo = 'user_info'; static const String _keyUserInfo = 'user_info';
static const String _keyUserSn = 'user_sn'; static const String _keyBluetoothReadSn = 'bluetooth_read_sn';
static const String _legacyKeyUserSn = 'user_sn';
static const String _keyClientId = 'client_id'; static const String _keyClientId = 'client_id';
static const String _keyBoundBluetoothDeviceId = 'bound_bluetooth_device_id'; static const String _keyBoundBluetoothDeviceId = 'bound_bluetooth_device_id';
static const String _keyBoundBluetoothDeviceName = static const String _keyBoundBluetoothDeviceName =
...@@ -76,12 +78,10 @@ class StorageService { ...@@ -76,12 +78,10 @@ class StorageService {
final jsonStr = final jsonStr =
'${userInfo.id}|${userInfo.nickname ?? ''}|${userInfo.phone ?? ''}|${userInfo.avatar ?? ''}|${userInfo.type ?? ''}|${userInfo.canControlDevice ?? ''}|${userInfo.canViewOperation ?? ''}|${userInfo.hospitalId ?? ''}|${userInfo.hospitalName ?? ''}'; '${userInfo.id}|${userInfo.nickname ?? ''}|${userInfo.phone ?? ''}|${userInfo.avatar ?? ''}|${userInfo.type ?? ''}|${userInfo.canControlDevice ?? ''}|${userInfo.canViewOperation ?? ''}|${userInfo.hospitalId ?? ''}|${userInfo.hospitalName ?? ''}';
await _storage.write(key: _keyUserInfo, value: jsonStr); await _storage.write(key: _keyUserInfo, value: jsonStr);
await getUserSn();
} }
Future<UserModel?> getUserInfo() async { Future<UserModel?> getUserInfo() async {
final value = await _storage.read(key: _keyUserInfo); final value = await _storage.read(key: _keyUserInfo);
UserModel.SN = await getUserSn();
if (value == null || value.isEmpty) return null; if (value == null || value.isEmpty) return null;
final parts = value.split('|'); final parts = value.split('|');
if (parts.length < 9) return null; if (parts.length < 9) return null;
...@@ -102,20 +102,22 @@ class StorageService { ...@@ -102,20 +102,22 @@ class StorageService {
await _storage.delete(key: _keyUserInfo); await _storage.delete(key: _keyUserInfo);
} }
Future<void> saveUserSn(String sn) async { // ==================== 蓝牙读取信息相关 ====================
UserModel.SN = sn;
await _storage.write(key: _keyUserSn, value: sn); Future<void> saveBluetoothReadSn(String sn) async {
await _storage.write(key: _keyBluetoothReadSn, value: sn);
await _storage.delete(key: _legacyKeyUserSn);
} }
Future<String?> getUserSn() async { Future<BluetoothReadModel> getBluetoothReadInfo() async {
final sn = await _storage.read(key: _keyUserSn); final sn = await _storage.read(key: _keyBluetoothReadSn) ??
UserModel.SN = sn; await _storage.read(key: _legacyKeyUserSn);
return sn; return BluetoothReadModel(sn: sn);
} }
Future<void> deleteUserSn() async { Future<void> deleteBluetoothReadInfo() async {
UserModel.SN = null; await _storage.delete(key: _keyBluetoothReadSn);
await _storage.delete(key: _keyUserSn); await _storage.delete(key: _legacyKeyUserSn);
} }
// ==================== ClientId 相关 ==================== // ==================== ClientId 相关 ====================
......
...@@ -3,10 +3,13 @@ import 'dart:async'; ...@@ -3,10 +3,13 @@ import 'dart:async';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:laki_icu_app/enums/video_stream_mode_enum.dart'; import 'package:laki_icu_app/enums/video_stream_mode_enum.dart';
import 'package:laki_icu_app/models/bo/monitoring_bo.dart';
import 'package:laki_icu_app/services/monitoring_service.dart'; import 'package:laki_icu_app/services/monitoring_service.dart';
import 'package:laki_icu_app/services/p2p_video_service.dart'; import 'package:laki_icu_app/services/p2p_video_service.dart';
import 'package:laki_icu_app/services/webrtc_service.dart'; import 'package:laki_icu_app/services/webrtc_service.dart';
import 'package:laki_icu_app/models/bo/bluetooth_read_model.dart';
import 'package:laki_icu_app/utils/bluetooth/index.dart'; import 'package:laki_icu_app/utils/bluetooth/index.dart';
import 'package:laki_icu_app/utils/event_bus.dart';
import 'package:laki_icu_app/utils/storage/storage_service.dart'; import 'package:laki_icu_app/utils/storage/storage_service.dart';
import 'monitoring_index_state.dart'; import 'monitoring_index_state.dart';
...@@ -28,6 +31,10 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> { ...@@ -28,6 +31,10 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
StreamSubscription<BluetoothConnectionStatus>? _bluetoothStateSub; StreamSubscription<BluetoothConnectionStatus>? _bluetoothStateSub;
StreamSubscription<String>? _bluetoothMessageSub; StreamSubscription<String>? _bluetoothMessageSub;
StreamSubscription<BluetoothDataPacket>? _bluetoothDataSub; StreamSubscription<BluetoothDataPacket>? _bluetoothDataSub;
Timer? _bluetoothReconnectTimer;
bool _isAutoReconnectEnabled = false;
bool _isAutoConnectingBluetooth = false;
bool _isManualUnbindingBluetooth = false;
/// 连接代际计数器 —— 每次切换模式时 +1, /// 连接代际计数器 —— 每次切换模式时 +1,
/// 防止旧连接的异步结果污染当前模式的状态。 /// 防止旧连接的异步结果污染当前模式的状态。
...@@ -76,6 +83,9 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> { ...@@ -76,6 +83,9 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
_bluetoothStateSub = _bluetoothManager.stateStream.listen((status) { _bluetoothStateSub = _bluetoothManager.stateStream.listen((status) {
if (isClosed) return; if (isClosed) return;
emit(state.copyWith(bluetoothConnectionStatus: status)); emit(state.copyWith(bluetoothConnectionStatus: status));
if (status == BluetoothConnectionStatus.disconnected) {
_scheduleBoundBluetoothReconnect();
}
}); });
_bluetoothMessageSub = _bluetoothManager.messageStream.listen((message) { _bluetoothMessageSub = _bluetoothManager.messageStream.listen((message) {
...@@ -88,14 +98,23 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> { ...@@ -88,14 +98,23 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
try { try {
final reports = _mcuReportDecoder.addBytes(packet.bytes); final reports = _mcuReportDecoder.addBytes(packet.bytes);
final latestReport = reports.isEmpty ? null : reports.last; final latestReport = reports.isEmpty ? null : reports.last;
final latestSn = latestReport?.sn.trim(); if (latestReport != null) {
if (latestSn != null && latestSn.isNotEmpty) { final bluetoothReadInfo =
await _storageService.saveUserSn(latestSn); BluetoothReadModel.fromMcuReport(latestReport);
if (bluetoothReadInfo.hasSn) {
await _storageService.saveBluetoothReadSn(bluetoothReadInfo.sn!);
}
eventBus.emit(
BluetoothReadInfoChangedEvent(bluetoothReadInfo),
);
if (isClosed) return; if (isClosed) return;
} }
emit(state.copyWith( emit(state.copyWith(
latestBluetoothRawHex: packet.rawHex, latestBluetoothRawHex: packet.rawHex,
latestMcuReport: latestReport, latestMcuReport: latestReport,
metrics: latestReport == null
? state.metrics
: _metricsFromBluetoothReport(latestReport),
bluetoothMessage: latestReport == null bluetoothMessage: latestReport == null
? state.bluetoothMessage ? state.bluetoothMessage
: '蓝牙数据解析成功: ${latestReport.sn.isEmpty ? latestReport.head : latestReport.sn}', : '蓝牙数据解析成功: ${latestReport.sn.isEmpty ? latestReport.head : latestReport.sn}',
...@@ -113,13 +132,17 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> { ...@@ -113,13 +132,17 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
final boundDevice = await _storageService.getBoundBluetoothDevice(); final boundDevice = await _storageService.getBoundBluetoothDevice();
if (isClosed) return; if (isClosed) return;
final deviceId = boundDevice['deviceId']; final deviceId = boundDevice['deviceId'];
await _storageService.getUserSn(); await _storageService.getBluetoothReadInfo();
if (isClosed) return; if (isClosed) return;
emit(state.copyWith( emit(state.copyWith(
boundBluetoothDeviceId: boundBluetoothDeviceId:
deviceId == null || deviceId.isEmpty ? null : deviceId, deviceId == null || deviceId.isEmpty ? null : deviceId,
boundBluetoothDeviceName: boundDevice['deviceName'], boundBluetoothDeviceName: boundDevice['deviceName'],
)); ));
if (deviceId != null && deviceId.isNotEmpty) {
_isAutoReconnectEnabled = true;
_connectBoundBluetoothDevice();
}
} }
Future<void> loadData() async { Future<void> loadData() async {
...@@ -310,6 +333,7 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> { ...@@ -310,6 +333,7 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
deviceId: device.remoteId, deviceId: device.remoteId,
deviceName: device.name, deviceName: device.name,
); );
_isAutoReconnectEnabled = true;
if (isClosed) return; if (isClosed) return;
emit(state.copyWith( emit(state.copyWith(
isBluetoothBinding: false, isBluetoothBinding: false,
...@@ -329,9 +353,13 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> { ...@@ -329,9 +353,13 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
} }
Future<void> unbindBluetoothDevice() async { Future<void> unbindBluetoothDevice() async {
_isManualUnbindingBluetooth = true;
_isAutoReconnectEnabled = false;
_cancelBluetoothReconnect();
await _bluetoothManager.disconnect(); await _bluetoothManager.disconnect();
await _storageService.deleteBoundBluetoothDevice(); await _storageService.deleteBoundBluetoothDevice();
await _storageService.deleteUserSn(); await _storageService.deleteBluetoothReadInfo();
eventBus.emit(const BluetoothReadInfoClearedEvent());
_mcuReportDecoder.clear(); _mcuReportDecoder.clear();
if (isClosed) return; if (isClosed) return;
emit(state.copyWith( emit(state.copyWith(
...@@ -340,6 +368,126 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> { ...@@ -340,6 +368,126 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
bluetoothMessage: '蓝牙设备已解绑', bluetoothMessage: '蓝牙设备已解绑',
latestBluetoothRawHex: '', latestBluetoothRawHex: '',
)); ));
_isManualUnbindingBluetooth = false;
}
void _scheduleBoundBluetoothReconnect() {
if (!_isAutoReconnectEnabled ||
_isManualUnbindingBluetooth ||
_isAutoConnectingBluetooth ||
state.boundBluetoothDeviceId == null ||
state.boundBluetoothDeviceId!.isEmpty ||
state.isBluetoothBinding) {
return;
}
_bluetoothReconnectTimer ??= Timer(
const Duration(seconds: 5),
() {
_bluetoothReconnectTimer = null;
_connectBoundBluetoothDevice();
},
);
}
Future<void> _connectBoundBluetoothDevice() async {
final deviceId = state.boundBluetoothDeviceId;
if (!_isAutoReconnectEnabled ||
_isManualUnbindingBluetooth ||
_isAutoConnectingBluetooth ||
deviceId == null ||
deviceId.isEmpty ||
_bluetoothManager.isConnected) {
return;
}
_cancelBluetoothReconnect();
_isAutoConnectingBluetooth = true;
if (!isClosed) {
emit(state.copyWith(bluetoothMessage: '正在自动连接蓝牙设备'));
}
try {
await _bluetoothManager.connectToDeviceId(deviceId);
if (!isClosed) {
emit(state.copyWith(bluetoothMessage: '蓝牙设备已自动连接'));
}
} catch (e) {
if (!isClosed) {
emit(state.copyWith(bluetoothMessage: '蓝牙自动连接失败: $e'));
}
} finally {
_isAutoConnectingBluetooth = false;
if (!isClosed && !_bluetoothManager.isConnected) {
_scheduleBoundBluetoothReconnect();
}
}
}
void _cancelBluetoothReconnect() {
_bluetoothReconnectTimer?.cancel();
_bluetoothReconnectTimer = null;
}
List<MonitoringMetricBO> _metricsFromBluetoothReport(McuReport report) {
final replacements = <String, MonitoringMetricBO>{
'舱内温度': _replaceMetricValue(
'舱内温度',
report.mainTemperature.toStringAsFixed(1),
fallbackUnit: '℃',
),
'舱内湿度': _replaceMetricValue(
'舱内湿度',
'${report.humidity}',
fallbackUnit: 'RH',
),
'氧气浓度': _replaceMetricValue(
'氧气浓度',
'${report.oxygenConcentration}',
fallbackUnit: '%',
),
'二氧化碳': _replaceMetricValue(
'二氧化碳',
'${report.co2Measurement}',
fallbackUnit: 'ppm',
),
};
if (state.metrics.isEmpty) {
return replacements.values.toList();
}
return [
for (final metric in state.metrics) replacements[metric.label] ?? metric,
];
}
MonitoringMetricBO _replaceMetricValue(
String label,
String value, {
required String fallbackUnit,
}) {
MonitoringMetricBO? existing;
for (final metric in state.metrics) {
if (metric.label == label) {
existing = metric;
break;
}
}
if (existing == null) {
return MonitoringMetricBO(
value: value,
unit: fallbackUnit,
label: label,
);
}
return MonitoringMetricBO(
value: value,
unit: existing.unit.isEmpty ? fallbackUnit : existing.unit,
label: existing.label,
borderColorValue: existing.borderColorValue,
backgroundColorValue: existing.backgroundColorValue,
);
} }
/// 获取 WebRTC 渲染器供 UI 层使用 /// 获取 WebRTC 渲染器供 UI 层使用
...@@ -350,6 +498,7 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> { ...@@ -350,6 +498,7 @@ class MonitoringIndexCubit extends Cubit<MonitoringIndexState> {
@override @override
Future<void> close() { Future<void> close() {
_cancelBluetoothReconnect();
_webrtcStateSub?.cancel(); _webrtcStateSub?.cancel();
_p2pStateSub?.cancel(); _p2pStateSub?.cancel();
_bluetoothScanSub?.cancel(); _bluetoothScanSub?.cancel();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment