import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../models/device_state.dart'; import '../services/device_log.dart'; import '../services/device_message.dart'; import '../services/device_message_service.dart'; import 'serial_provider.dart'; /// 设备信息通知器 /// /// 订阅 [DeviceMessageService] 中的 `device_info` 上行消息, /// 维护门状态 / 灯状态 / 任务状态,供 UI 直接读取。 class DeviceInfoNotifier extends StateNotifier { final DeviceMessageService _msgService; void Function()? _cancelSub; DeviceInfoNotifier(this._msgService) : super(const DeviceState()) { _cancelSub = _msgService.subscribe( DeviceMessageType.deviceInfo, _onDeviceInfo, ); // 串口断开时无需主动清空 lastInfoAt —— infoStale 会根据 // 当前时间与 lastInfoAt 的差值自动判断为「通讯异常」。 } void _onDeviceInfo(DeviceMessage msg) { final data = msg.data; final light = _parseLight(data['light_status']); final door = _parseDoor(data['door_status']); final task = _parseTask(data['task_status']); final prev = state; final changed = prev.lightStatus != light || prev.doorStatus != door || prev.taskStatus != task; state = state.copyWith( lightStatus: light, doorStatus: door, taskStatus: task, lastInfoAt: DateTime.now(), ); if (changed) { DeviceLog.info( 'device_info updated: light=${light.name} door=${door.name} task=${task.name}', ); } } /// 主动查询设备信息(发送 need_ack=true 的 create_task 之外的 device_info 请求, /// 具体取决于下位机协议约定;目前仅作占位)。 Future queryDeviceInfo() async { if (!_msgService.canSend) { DeviceLog.warn('queryDeviceInfo skipped: serial disconnected'); return; } final id = _msgService.nextId(); await _msgService.send(DeviceMessage.request( messageId: id, type: DeviceMessageType.deviceInfo, data: const {}, needAck: true, )); } /// 发送灯光控制消息 /// /// 主动切换后,下位机通常会通过 device_info 上报新的 light_status /// 覆盖本地状态。 Future toggleLight() async { if (!_msgService.canSend) { DeviceLog.warn('toggleLight skipped: serial disconnected'); return false; } final next = state.lightStatus == DeviceLightStatus.on ? 'off' : 'on'; final id = _msgService.nextId(); final ok = await _msgService.send(DeviceMessage.request( messageId: id, type: DeviceMessageType.lightControl, data: {'status': next}, needAck: true, )); if (ok) { // 乐观更新;下位机的 device_info 上报会作为最终一致来源 state = state.copyWith( lightStatus: next == 'on' ? DeviceLightStatus.on : DeviceLightStatus.off, ); DeviceLog.info('toggleLight optimistic: -> $next'); } return ok; } /// 显式设定灯光 Future setLight(bool on) async { if (!_msgService.canSend) { DeviceLog.warn('setLight skipped: serial disconnected'); return false; } if (on && state.lightStatus == DeviceLightStatus.on) return true; if (!on && state.lightStatus == DeviceLightStatus.off) return true; final id = _msgService.nextId(); final ok = await _msgService.send(DeviceMessage.request( messageId: id, type: DeviceMessageType.lightControl, data: {'status': on ? 'on' : 'off'}, needAck: true, )); if (ok) { state = state.copyWith( lightStatus: on ? DeviceLightStatus.on : DeviceLightStatus.off, ); DeviceLog.info('setLight optimistic: -> ${on ? "on" : "off"}'); } return ok; } @override void dispose() { _cancelSub?.call(); _cancelSub = null; super.dispose(); } // -- 解析 ------------------------------------------------------------- static DeviceLightStatus _parseLight(dynamic v) => v == 'on' ? DeviceLightStatus.on : DeviceLightStatus.off; static DeviceDoorStatus _parseDoor(dynamic v) => v == 'open' ? DeviceDoorStatus.open : DeviceDoorStatus.close; static DeviceTaskStatus _parseTask(dynamic v) { return switch (v) { 'running' => DeviceTaskStatus.running, 'pause' => DeviceTaskStatus.pause, _ => DeviceTaskStatus.idle, }; } } /// 设备信息 Provider final deviceInfoProvider = StateNotifierProvider((ref) { final service = ref.watch(deviceMessageServiceProvider); return DeviceInfoNotifier(service); });