Эх сурвалжийг харах

Add 添加win平台USB测试工具页面,读取标签

Yue 3 долоо хоног өмнө
parent
commit
a750fc22de

+ 180 - 0
UI/CF.APP/chicken_farm/lib/core/services/win/win_reader_channel.dart

@@ -0,0 +1,180 @@
+import 'package:chicken_farm/core/utils/logger.dart';
+import 'package:flutter/services.dart';
+
+class WinReaderChannel {
+  static const MethodChannel _channel = MethodChannel(
+    'com.vber.chicken_farm/win_reader',
+  );
+
+  // 用于监听数据回调的EventChannel
+  static const EventChannel _eventChannel = EventChannel(
+    'com.vber.chicken_farm/win_reader_events',
+  );
+
+  static Future<List<String>?> scanUsb() async {
+    try {
+      final result = await _channel.invokeMethod('scanUsb');
+
+      // 检查返回结果的类型,防止数据解析错误
+      if (result == null) {
+        return null;
+      } else if (result is List) {
+        // 将返回的结果转换为字符串列表
+        return result.map((e) => e.toString()).toList().cast<String>();
+      } else if (result is List<dynamic>) {
+        // 处理动态列表类型
+        return result.map((e) => e?.toString() ?? '').toList().cast<String>();
+      } else {
+        logger.e('scanUsb返回的数据类型不正确: ${result.runtimeType}');
+        return null;
+      }
+    } on PlatformException catch (e) {
+      logger.e('调用scanUsb方法时发生平台异常: ${e.message}');
+      return null;
+    } on MissingPluginException catch (e) {
+      logger.e('插件缺失异常: ${e.message}');
+      return null;
+    } catch (e) {
+      logger.e('扫描USB设备时发生未知错误: $e');
+      return null;
+    }
+  }
+
+  // 连接到指定索引的USB设备
+  static Future<bool> connect(int deviceIndex) async {
+    try {
+      final result = await _channel.invokeMethod('connect', {
+        'deviceIndex': deviceIndex,
+      });
+      return result ?? false;
+    } on PlatformException catch (e) {
+      logger.e('调用connect方法时发生平台异常: ${e.message}');
+      return false;
+    } on MissingPluginException catch (e) {
+      logger.e('插件缺失异常: ${e.message}');
+      return false;
+    } catch (e) {
+      logger.e('连接USB设备时发生未知错误: $e');
+      return false;
+    }
+  }
+
+  // 断开连接
+  static Future<void> disconnect() async {
+    try {
+      await _channel.invokeMethod('disconnect');
+    } on PlatformException catch (e) {
+      logger.e('调用disconnect方法时发生平台异常: ${e.message}');
+    } on MissingPluginException catch (e) {
+      logger.e('插件缺失异常: ${e.message}');
+    } catch (e) {
+      logger.e('断开USB设备连接时发生未知错误: $e');
+    }
+  }
+
+  // 开始读取
+  static Future<bool> startRead() async {
+    try {
+      final result = await _channel.invokeMethod('startRead');
+      return result ?? false;
+    } on PlatformException catch (e) {
+      logger.e('调用startRead方法时发生平台异常: ${e.message}');
+      return false;
+    } on MissingPluginException catch (e) {
+      logger.e('插件缺失异常: ${e.message}');
+      return false;
+    } catch (e) {
+      logger.e('开始读取时发生未知错误: $e');
+      return false;
+    }
+  }
+
+  // 停止读取
+  static Future<void> stopRead() async {
+    try {
+      await _channel.invokeMethod('stopRead');
+    } on PlatformException catch (e) {
+      logger.e('调用stopRead方法时发生平台异常: ${e.message}');
+    } on MissingPluginException catch (e) {
+      logger.e('插件缺失异常: ${e.message}');
+    } catch (e) {
+      logger.e('停止读取时发生未知错误: $e');
+    }
+  }
+
+  // 获取设备信息
+  static Future<String?> getDeviceInfo() async {
+    try {
+      final result = await _channel.invokeMethod('getDeviceInfo');
+      return result?.toString();
+    } on PlatformException catch (e) {
+      logger.e('调用getDeviceInfo方法时发生平台异常: ${e.message}');
+      return null;
+    } on MissingPluginException catch (e) {
+      logger.e('插件缺失异常: ${e.message}');
+      return null;
+    } catch (e) {
+      logger.e('获取设备信息时发生未知错误: $e');
+      return null;
+    }
+  }
+
+  // 查询功率
+  static Future<int?> getPower() async {
+    try {
+      final result = await _channel.invokeMethod('getPower');
+      if (result == null) {
+        return null;
+      }
+      // 将结果转换为整数类型
+      if (result is int) {
+        return result;
+      } else if (result is double) {
+        return result.toInt();
+      } else {
+        return int.tryParse(result.toString());
+      }
+    } on PlatformException catch (e) {
+      logger.e('调用getPower方法时发生平台异常: ${e.message}');
+      return null;
+    } on MissingPluginException catch (e) {
+      logger.e('插件缺失异常: ${e.message}');
+      return null;
+    } catch (e) {
+      logger.e('查询功率时发生未知错误: $e');
+      return null;
+    }
+  }
+
+  // 设置功率
+  static Future<bool> setPower(int power) async {
+    try {
+      final result = await _channel.invokeMethod('setPower', {
+        'power': power,
+      });
+      return result ?? false;
+    } on PlatformException catch (e) {
+      logger.e('调用setPower方法时发生平台异常: ${e.message}');
+      return false;
+    } on MissingPluginException catch (e) {
+      logger.e('插件缺失异常: ${e.message}');
+      return false;
+    } catch (e) {
+      logger.e('设置功率时发生未知错误: $e');
+      return false;
+    }
+  }
+
+  // 监听设备数据流
+  static Stream<String> get onDataReceived {
+    return _eventChannel.receiveBroadcastStream().map((dynamic data) {
+      if (data is String) {
+        // 去除所有空格(包括首尾空格和中间空格)
+        return data.replaceAll(RegExp(r'\s+'), '').trim();
+      }
+      return '';
+    });
+  }
+
+  // 监听连接状态事件
+}

+ 299 - 0
UI/CF.APP/chicken_farm/lib/core/services/win/win_reader_manager.dart

@@ -0,0 +1,299 @@
+import 'dart:async';
+import 'package:chicken_farm/core/utils/logger.dart';
+import 'package:chicken_farm/core/utils/toast.dart';
+import 'win_reader_channel.dart';
+
+typedef OnConnectCallback = void Function(bool success);
+typedef OnDataReceivedCallback = void Function(String data);
+typedef OnErrorCallback = void Function(String error);
+
+/// Windows读卡器管理器 (单例模式)
+class WinReaderManager {
+  // 单例实例
+  static final WinReaderManager _instance = WinReaderManager._internal();
+
+  factory WinReaderManager() {
+    // 确保初始化
+    _instance.init();
+    return _instance;
+  }
+
+  // 存储回调函数
+  OnConnectCallback? _onConnect;
+  OnDataReceivedCallback? _onDataReceived;
+  OnErrorCallback? _onError;
+
+  // 连接状态
+  bool _isConnected = false;
+  bool _isReading = false;
+  int? _currentDeviceIndex;
+
+  // 数据流订阅
+  StreamSubscription? _dataSubscription;
+
+  // 初始化状态标志
+  bool _isInitialized = false;
+
+  // 私有构造函数
+  WinReaderManager._internal();
+
+  // 获取单例实例
+  static WinReaderManager get instance => WinReaderManager();
+
+  /// 初始化管理器
+  Future<void> init() async {
+    if (_isInitialized) return;
+    _isInitialized = true;
+
+    // 监听数据流 - 确保在任何读取操作前已经订阅
+    _dataSubscription = WinReaderChannel.onDataReceived.listen(
+      _handleDataReceived,
+      onError: (error) {
+        logger.e('读卡器数据接收错误: $error');
+        _onError?.call(error.toString());
+      },
+    );
+    
+    logger.i("WinReaderManager 初始化完成,已订阅数据流");
+  }
+
+  /// 注册回调函数
+  void registerCallbacks({
+    OnConnectCallback? onConnect,
+    OnDataReceivedCallback? onDataReceived,
+    OnErrorCallback? onError,
+  }) {
+    logger.i("注册Win读卡器回调函数");
+    _onConnect = onConnect;
+    _onDataReceived = onDataReceived;
+    _onError = onError;
+  }
+
+  /// 清除回调函数
+  void clearCallbacks() {
+    _onConnect = null;
+    _onDataReceived = null;
+    _onError = null;
+  }
+
+  /// 扫描USB设备
+  Future<List<String>?> scanUsb() async {
+    try {
+      final result = await WinReaderChannel.scanUsb();
+      return result;
+    } catch (e) {
+      logger.e('扫描USB设备错误: $e');
+      _onError?.call(e.toString());
+      return null;
+    }
+  }
+
+  /// 连接到指定索引的设备
+  Future<bool> connect(int deviceIndex) async {
+    if (_isConnected) {
+      logger.w("设备已连接,无需重复连接");
+      return true;
+    }
+
+    try {
+      bool success = await WinReaderChannel.connect(deviceIndex);
+      if (success) {
+        _isConnected = true;
+        _currentDeviceIndex = deviceIndex;
+        logger.i("成功连接到设备,索引: $deviceIndex");
+        _onConnect?.call(true);
+        return true;
+      } else {
+        logger.e("连接设备失败,索引: $deviceIndex");
+        _onConnect?.call(false);
+        return false;
+      }
+    } catch (e) {
+      logger.e('连接设备错误: $e');
+      _onError?.call(e.toString());
+      _onConnect?.call(false);
+      return false;
+    }
+  }
+
+  /// 断开连接
+  Future<void> disconnect() async {
+    if (!_isConnected) {
+      logger.w("设备未连接,无需断开");
+      return;
+    }
+
+    try {
+      await WinReaderChannel.disconnect();
+      _isConnected = false;
+      _currentDeviceIndex = null;
+      if (_isReading) {
+        _isReading = false;
+      }
+      logger.i("成功断开设备连接");
+      _onConnect?.call(false);
+    } catch (e) {
+      logger.e('断开设备错误: $e');
+      _onError?.call(e.toString());
+    }
+  }
+
+  /// 开始读取
+  Future<bool> startRead() async {
+    if (!_isConnected) {
+      logger.e("设备未连接,无法开始读取");
+      ToastUtil.error("请先连接设备");
+      return false;
+    }
+
+    if (_isReading) {
+      logger.w("已经在读取状态,无需重复开始");
+      return true;
+    }
+
+    // 确保数据流已订阅
+    if (_dataSubscription == null) {
+      logger.e("数据流未正确订阅,请检查初始化状态");
+      ToastUtil.error("读卡器服务未正确初始化");
+      return false;
+    }
+
+    try {
+      bool success = await WinReaderChannel.startRead();
+      if (success) {
+        _isReading = true;
+        logger.i("开始读取数据");
+        return true;
+      } else {
+        logger.e("开始读取失败");
+        _onError?.call("开始读取失败");
+        return false;
+      }
+    } catch (e) {
+      logger.e('开始读取错误: $e');
+      _onError?.call(e.toString());
+      return false;
+    }
+  }
+
+  /// 停止读取
+  Future<void> stopRead() async {
+    if (!_isConnected) {
+      logger.w("设备未连接,无需停止读取");
+      return;
+    }
+
+    if (!_isReading) {
+      logger.w("当前未在读取状态");
+      return;
+    }
+
+    try {
+      await WinReaderChannel.stopRead();
+      _isReading = false;
+      logger.i("已停止读取数据");
+    } catch (e) {
+      logger.e('停止读取错误: $e');
+      _onError?.call(e.toString());
+    }
+  }
+
+  /// 获取设备信息
+  Future<String?> getDeviceInfo() async {
+    if (!_isConnected) {
+      logger.e("设备未连接,无法获取设备信息");
+      ToastUtil.error("请先连接设备");
+      return null;
+    }
+
+    try {
+      String? deviceInfo = await WinReaderChannel.getDeviceInfo();
+      if (deviceInfo != null && deviceInfo.isNotEmpty) {
+        logger.i("成功获取设备信息: $deviceInfo");
+        return deviceInfo;
+      } else {
+        logger.e("获取设备信息失败或返回为空");
+        _onError?.call("获取设备信息失败");
+        return null;
+      }
+    } catch (e) {
+      logger.e('获取设备信息错误: $e');
+      _onError?.call(e.toString());
+      return null;
+    }
+  }
+
+  /// 查询功率
+  Future<int?> getPower() async {
+    if (!_isConnected) {
+      logger.e("设备未连接,无法查询功率");
+      ToastUtil.error("请先连接设备");
+      return null;
+    }
+
+    try {
+      int? power = await WinReaderChannel.getPower();
+      if (power != null) {
+        logger.i("成功获取功率: $power");
+        return power;
+      } else {
+        logger.e("查询功率失败或返回为空");
+        _onError?.call("查询功率失败");
+        return null;
+      }
+    } catch (e) {
+      logger.e('查询功率错误: $e');
+      _onError?.call(e.toString());
+      return null;
+    }
+  }
+
+  /// 设置功率
+  Future<bool> setPower(int power) async {
+    if (!_isConnected) {
+      logger.e("设备未连接,无法设置功率");
+      ToastUtil.error("请先连接设备");
+      return false;
+    }
+
+    try {
+      bool success = await WinReaderChannel.setPower(power);
+      if (success) {
+        logger.i("成功设置功率: $power");
+        return true;
+      } else {
+        logger.e("设置功率失败: $power");
+        _onError?.call("设置功率失败");
+        return false;
+      }
+    } catch (e) {
+      logger.e('设置功率错误: $e');
+      _onError?.call(e.toString());
+      return false;
+    }
+  }
+
+  /// 处理数据接收
+  void _handleDataReceived(dynamic data) {
+    if (data is String) {
+      logger.i("接收到读卡器数据: $data");
+      _onDataReceived?.call(data);
+    }
+  }
+
+  /// 检查是否已连接
+  bool get isConnected => _isConnected;
+
+  /// 检查是否正在读取
+  bool get isReading => _isReading;
+
+  /// 获取当前设备索引
+  int? get currentDeviceIndex => _currentDeviceIndex;
+
+  /// 释放资源
+  void dispose() {
+    _dataSubscription?.cancel();
+    _dataSubscription = null;
+    logger.i("WinReaderManager 资源已释放");
+  }
+}

+ 519 - 278
UI/CF.APP/chicken_farm/lib/pages/serial/serial_setting_page.dart

@@ -1,7 +1,11 @@
 // ignore_for_file: unused_import, unused_field, prefer_final_fields
 
+import 'dart:async';
 import 'dart:typed_data';
 import 'package:chicken_farm/components/vb_app_bar.dart';
+import 'package:chicken_farm/core/services/win/win_reader_channel.dart';
+import 'package:chicken_farm/core/services/win/win_reader_manager.dart';
+import 'package:chicken_farm/core/utils/logger.dart';
 import 'package:chicken_farm/core/utils/toast.dart';
 import 'package:flutter/material.dart';
 
@@ -13,311 +17,548 @@ class SerialSettingPage extends StatefulWidget {
 }
 
 class _SerialSettingPageState extends State<SerialSettingPage> {
-  // final SerialPortService _serialService = SerialPortService();
+  final WinReaderManager _readerManager = WinReaderManager();
   List<String> _availablePorts = [];
   String? _selectedPort;
-  final TextEditingController _baudRateController = TextEditingController(
-    text: '9600',
-  );
   final TextEditingController _dataController = TextEditingController();
   final List<String> _receivedData = [];
   bool _isConnected = false;
+  bool _isReading = false;
+
+  // 设备信息
+  String _deviceSN = '';
+  String _softVersion = '';
+  String _hardVersion = '';
+
+  // 功率相关状态
+  int? _currentPower;
+  int _selectedPower = 20; // 默认值
 
   @override
   void initState() {
     super.initState();
-    // _loadAvailablePorts();
-    // _listenToSerialEvents();
+    // 注册回调函数
+    _readerManager.registerCallbacks(
+      onConnect: _onConnect,
+      onDataReceived: _onDataReceived,
+      onError: _onError,
+    );
+    _loadUsbPorts();
+    // 监听数据流 - 使用WinReaderManager内部处理,无需重复监听
   }
 
-  // /// 加载可用串口列表
-  // void _loadAvailablePorts() {
-  //   setState(() {
-  //     _availablePorts = SerialPortService.getAvailablePorts();
-  //     if (_availablePorts.isNotEmpty) {
-  //       _selectedPort = _availablePorts.first;
-  //     }
-  //   });
-  // }
+  /// 加载可用Usb列表
+  void _loadUsbPorts() async {
+    try {
+      final ports = await _readerManager.scanUsb();
+      if (mounted) {
+        // 检查组件是否仍然挂载
+        setState(() {
+          _availablePorts = ports ?? [];
+          if (_availablePorts.isNotEmpty) {
+            _selectedPort = _availablePorts.first;
+          }
+        });
+      }
+    } catch (e) {
+      if (mounted) {
+        // 检查组件是否仍然挂载
+        logger.e(e);
+        ToastUtil.error("扫描USB设备失败: $e");
+      }
+    }
+  }
 
-  // /// 监听串口事件
-  // void _listenToSerialEvents() {
-  //   _serialService.events.listen((event) {
-  //     switch (event.type) {
-  //       case VbSerialPortEventType.statusChanged:
-  //         final status = event.data as VbSerialPortStatus;
-  //         setState(() {
-  //           _isConnected = status == VbSerialPortStatus.opened;
-  //         });
-  //         break;
-  //       case VbSerialPortEventType.dataReceived:
-  //         final data = event.data as Uint8List;
-  //         final text = String.fromCharCodes(data);
-  //         setState(() {
-  //           _receivedData.add('RX: $text');
-  //           if (_receivedData.length > 100) {
-  //             _receivedData.removeAt(0);
-  //           }
-  //         });
-  //         break;
-  //       case VbSerialPortEventType.errorOccurred:
-  //         final errorMsg = event.errorMessage ?? '未知错误';
-  //         setState(() {
-  //           _receivedData.add('ERR: $errorMsg');
-  //           if (_receivedData.length > 100) {
-  //             _receivedData.removeAt(0);
-  //           }
-  //         });
-  //         break;
-  //     }
-  //   });
-  // }
+  /// 连接状态回调
+  void _onConnect(bool connected) {
+    if (mounted) {
+      setState(() {
+        _isConnected = connected;
+        if (!connected) {
+          _isReading = false;
+          _deviceSN = '';
+          _softVersion = '';
+          _hardVersion = '';
+          _currentPower = null;
+        }
+      });
 
-  // /// 连接/断开串口
-  // void _toggleConnection() async {
-  //   if (!_isConnected) {
-  //     // 连接串口
-  //     if (_selectedPort == null) {
-  //       ToastUtil.warning("请选择串口");
-  //       return;
-  //     }
+      if (connected) {
+        _addLog("成功连接到设备: $_selectedPort");
+        // 连接成功后自动查询设备信息
+        _autoQueryDeviceInfo();
+        // 连接成功后自动查询功率
+        _autoQueryPower();
+      } else {
+        _addLog("已断开设备连接");
+      }
+    }
+  }
 
-  //     final baudRate = int.tryParse(_baudRateController.text) ?? 9600;
-  //     final config = VbSerialPortConfig(baudRate: baudRate);
+  /// 自动查询设备信息
+  void _autoQueryDeviceInfo() async {
+    String? deviceInfo = await _readerManager.getDeviceInfo();
+    if (deviceInfo != null && mounted) {
+      // 解析设备信息
+      _parseDeviceInfo(deviceInfo);
+      _addLog("设备信息查询成功: $deviceInfo");
+    } else {
+      _addLog("设备信息查询失败");
+    }
+  }
 
-  //     final result = await _serialService.openPort(_selectedPort!, config);
-  //     if (!result) {
-  //       ToastUtil.errorB("连接串口失败");
-  //     }
-  //   } else {
-  //     // 断开串口
-  //     await _serialService.closePort();
-  //   }
-  // }
+  /// 自动查询功率
+  void _autoQueryPower() async {
+    int? power = await _readerManager.getPower();
+    if (power != null && mounted) {
+      setState(() {
+        _currentPower = power;
+        _selectedPower = power; // 设置下拉框的默认值为当前功率
+      });
+      _addLog("功率查询成功: ${power}dBm");
+    } else {
+      _addLog("功率查询失败");
+    }
+  }
 
-  // /// 发送数据
-  // void _sendData() async {
-  //   if (!_isConnected) {
-  //     ToastUtil.errorB("请先连接串口");
-  //     return;
-  //   }
+  /// 解析设备信息字符串
+  void _parseDeviceInfo(String deviceInfo) {
+    if (mounted) {
+      // deviceInfo 格式: "SN:1234567890ABCDEF,SoftVer:1.2,HardVer:3.4"
+      List<String> parts = deviceInfo.split(',');
 
-  //   final data = _dataController.text;
-  //   if (data.isEmpty) {
-  //     ToastUtil.warning("请输入要发送的数据");
-  //     return;
-  //   }
+      for (String part in parts) {
+        if (part.startsWith('SN:')) {
+          setState(() {
+            _deviceSN = part.substring(3); // 去掉'SN:'前缀
+          });
+        } else if (part.startsWith('SoftVer:')) {
+          setState(() {
+            _softVersion = part.substring(8); // 去掉'SoftVer:'前缀
+          });
+        } else if (part.startsWith('HardVer:')) {
+          setState(() {
+            _hardVersion = part.substring(8); // 去掉'HardVer:'前缀
+          });
+        }
+      }
+    }
+  }
 
-  //   final result = await _serialService.sendString(data);
-  //   if (result) {
-  //     setState(() {
-  //       _receivedData.add('TX: $data');
-  //       if (_receivedData.length > 100) {
-  //         _receivedData.removeAt(0);
-  //       }
-  //     });
-  //     _dataController.clear();
-  //   } else {
-  //     ToastUtil.errorB("发送数据失败");
-  //   }
-  // }
+  /// 数据接收回调
+  void _onDataReceived(String data) {
+    if (mounted) {
+      _addLog("读取标签: $data");
+    }
+  }
 
-  // /// 清空接收区
-  // void _clearReceivedData() {
-  //   setState(() {
-  //     _receivedData.clear();
-  //   });
-  // }
+  /// 错误回调
+  void _onError(String error) {
+    if (mounted) {
+      _addLog("错误: $error");
+      ToastUtil.error("操作失败: $error");
+    }
+  }
 
-  @override
-  Widget build(BuildContext context) {
-    return Scaffold(
-      appBar: VberAppBar(title: '串口调试工具'),
-      body: Padding(
-        padding: const EdgeInsets.all(16.0),
-        //   child: SingleChildScrollView(
-        //     physics: const BouncingScrollPhysics(),
-        //     child: Column(
-        //       crossAxisAlignment: CrossAxisAlignment.start,
-        //       children: [
-        //         // 串口配置区域
-        //         Card(
-        //           child: Padding(
-        //             padding: const EdgeInsets.all(16.0),
-        //             child: Column(
-        //               crossAxisAlignment: CrossAxisAlignment.start,
-        //               children: [
-        //                 const Text(
-        //                   '串口配置',
-        //                   style: TextStyle(
-        //                     fontSize: 18,
-        //                     fontWeight: FontWeight.bold,
-        //                   ),
-        //                 ),
-        //                 const SizedBox(height: 10),
-        //                 Row(
-        //                   children: [
-        //                     const Text('串口:'),
-        //                     const SizedBox(width: 10),
-        //                     DropdownButton<String>(
-        //                       value: _selectedPort,
-        //                       items: _availablePorts.map((port) {
-        //                         return DropdownMenuItem(
-        //                           value: port,
-        //                           child: Text(port),
-        //                         );
-        //                       }).toList(),
-        //                       onChanged: (value) {
-        //                         setState(() {
-        //                           _selectedPort = value;
-        //                         });
-        //                       },
-        //                       hint: const Text('选择串口'),
-        //                     ),
-        //                     const SizedBox(width: 20),
-        //                     SizedBox(
-        //                       height: 30,
-        //                       child: ElevatedButton(
-        //                         onPressed: _loadAvailablePorts,
-        //                         child: const Text('刷新'),
-        //                       ),
-        //                     ),
+  /// 连接USB
+  void _connectUsbPort() async {
+    if (_selectedPort == null) {
+      ToastUtil.error("请先选择一个USB设备");
+      return;
+    }
 
-        //                     const SizedBox(width: 20),
+    if (!_isConnected) {
+      // 获取当前选中设备的索引
+      int deviceIndex = _availablePorts.indexOf(_selectedPort!);
+      bool success = await _readerManager.connect(deviceIndex);
+      if (!success) {
+        ToastUtil.error("连接设备失败");
+      }
+    } else {
+      await _readerManager.disconnect();
+    }
+  }
 
-        //                     const Text('波特率:'),
-        //                     const SizedBox(width: 10),
-        //                     SizedBox(
-        //                       width: 100,
-        //                       height: 40,
-        //                       child: TextField(
-        //                         controller: _baudRateController,
-        //                         keyboardType: TextInputType.number,
-        //                         decoration: const InputDecoration(
-        //                           border: OutlineInputBorder(),
-        //                         ),
-        //                       ),
-        //                     ),
-        //                     const SizedBox(width: 20),
-        //                     SizedBox(
-        //                       height: 30,
-        //                       child: ElevatedButton(
-        //                         onPressed: _toggleConnection,
-        //                         child: Text(
-        //                           _isConnected ? '断开' : '连接',
-        //                           style: TextStyle(
-        //                             color: _isConnected
-        //                                 ? Colors.red
-        //                                 : Colors.green,
-        //                           ),
-        //                         ),
-        //                       ),
-        //                     ),
-        //                   ],
-        //                 ),
-        //               ],
-        //             ),
-        //           ),
-        //         ),
+  /// 开始读取
+  void _startRead() async {
+    bool success = await _readerManager.startRead();
+    if (success && mounted) {
+      setState(() {
+        _isReading = true;
+      });
+      _addLog("开始读取数据");
+    }
+  }
 
-        //         const SizedBox(height: 20),
+  /// 停止读取
+  void _stopRead() async {
+    await _readerManager.stopRead();
+    if (mounted) {
+      setState(() {
+        _isReading = false;
+      });
+      _addLog("已停止读取数据");
+    }
+  }
 
-        //         // 数据发送区域
-        //         Card(
-        //           child: Padding(
-        //             padding: const EdgeInsets.all(16.0),
-        //             child: Column(
-        //               crossAxisAlignment: CrossAxisAlignment.start,
-        //               children: [
-        //                 const Text(
-        //                   '数据发送',
-        //                   style: TextStyle(
-        //                     fontSize: 18,
-        //                     fontWeight: FontWeight.bold,
-        //                   ),
-        //                 ),
-        //                 const SizedBox(height: 10),
-        //                 Row(
-        //                   children: [
-        //                     Expanded(
-        //                       child: SizedBox(
-        //                         height: 50,
-        //                         child: TextField(
-        //                           controller: _dataController,
-        //                           decoration: const InputDecoration(
-        //                             hintText: '输入要发送的数据',
-        //                             border: OutlineInputBorder(),
-        //                           ),
-        //                         ),
-        //                       ),
-        //                     ),
-        //                     const SizedBox(width: 10),
-        //                     SizedBox(
-        //                       height: 50,
-        //                       child: ElevatedButton(
-        //                         onPressed: _sendData,
-        //                         child: const Text('发送'),
-        //                       ),
-        //                     ),
-        //                   ],
-        //                 ),
-        //               ],
-        //             ),
-        //           ),
-        //         ),
+  /// 查询功率
+  void _queryPower() async {
+    if (!_isConnected) {
+      ToastUtil.error("请先连接设备");
+      return;
+    }
 
-        //         const SizedBox(height: 20),
+    int? power = await _readerManager.getPower();
+    if (power != null && mounted) {
+      setState(() {
+        _currentPower = power;
+        _selectedPower = power;
+      });
+      _addLog("功率查询成功: ${power}dBm");
+    } else {
+      _addLog("功率查询失败");
+    }
+  }
+
+  /// 设置功率
+  void _setPower() async {
+    if (!_isConnected) {
+      ToastUtil.error("请先连接设备");
+      return;
+    }
 
-        //         // 数据接收区域
-        //         Card(
-        //           child: Padding(
-        //             padding: const EdgeInsets.all(16.0),
-        //             child: Column(
-        //               crossAxisAlignment: CrossAxisAlignment.start,
-        //               children: [
-        //                 Row(
-        //                   mainAxisAlignment: MainAxisAlignment.spaceBetween,
-        //                   children: [
-        //                     const Text(
-        //                       '数据接收',
-        //                       style: TextStyle(
-        //                         fontSize: 18,
-        //                         fontWeight: FontWeight.bold,
-        //                       ),
-        //                     ),
-        //                     ElevatedButton(
-        //                       onPressed: _clearReceivedData,
-        //                       child: const Text('清空'),
-        //                     ),
-        //                   ],
-        //                 ),
-        //                 const SizedBox(height: 10),
-        //                 Container(
-        //                   height: 200,
-        //                   decoration: BoxDecoration(
-        //                     border: Border.all(color: Colors.grey),
-        //                   ),
-        //                   child: ListView.builder(
-        //                     itemCount: _receivedData.length,
-        //                     itemBuilder: (context, index) {
-        //                       return Padding(
-        //                         padding: const EdgeInsets.symmetric(
-        //                           horizontal: 8.0,
-        //                           vertical: 4.0,
-        //                         ),
-        //                         child: Text(_receivedData[index]),
-        //                       );
-        //                     },
-        //                   ),
-        //                 ),
-        //               ],
-        //             ),
-        //           ),
-        //         ),
-        //       ],
-        //     ),
-        //   ),
+    bool success = await _readerManager.setPower(_selectedPower);
+    if (success) {
+      ToastUtil.success("功率设置成功");
+      if (mounted) {
+        _addLog("功率设置成功: ${_selectedPower}dBm");
+      }
+      Future.delayed(const Duration(milliseconds: 500), () {
+        _queryPower();
+      });
+    } else {
+      _addLog("功率设置失败");
+    }
+  }
+
+  /// 添加日志
+  void _addLog(String log) {
+    if (mounted) {
+      setState(() {
+        _receivedData.add("${DateTime.now()}: $log");
+      });
+    }
+  }
+
+  /// 清空日志
+  void _clearLogs() {
+    if (mounted) {
+      setState(() {
+        _receivedData.clear();
+      });
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: VberAppBar(title: 'USB测试工具'),
+      body: Padding(
+        padding: const EdgeInsets.all(16.0),
+        child: SingleChildScrollView(
+          physics: const BouncingScrollPhysics(),
+          child: Column(
+            crossAxisAlignment: CrossAxisAlignment.start,
+            children: [
+              // 设备信息卡片
+              Card(
+                child: Padding(
+                  padding: const EdgeInsets.all(16.0),
+                  child: Column(
+                    crossAxisAlignment: CrossAxisAlignment.start,
+                    children: [
+                      Row(
+                        children: [
+                          const Text('USB设备:'),
+                          const SizedBox(width: 10),
+                          Expanded(
+                            child: DropdownButton<String>(
+                              value: _selectedPort,
+                              items: _availablePorts.map((port) {
+                                return DropdownMenuItem(
+                                  value: port,
+                                  child: Text(
+                                    port,
+                                    overflow: TextOverflow.ellipsis,
+                                  ),
+                                );
+                              }).toList(),
+                              onChanged: (value) {
+                                if (!_isConnected) {
+                                  setState(() {
+                                    _selectedPort = value;
+                                  });
+                                }
+                              },
+                              hint: const Text('选择USB'),
+                              isExpanded: true,
+                            ),
+                          ),
+                          const SizedBox(width: 20),
+                          SizedBox(
+                            height: 30,
+                            child: ElevatedButton(
+                              onPressed: _loadUsbPorts,
+                              child: const Text('刷新'),
+                            ),
+                          ),
+                          const SizedBox(width: 20),
+                          if (_selectedPort != null) ...[
+                            SizedBox(
+                              height: 30,
+                              child: ElevatedButton(
+                                onPressed: _connectUsbPort,
+                                child: Text(_isConnected ? '关闭连接' : '打开连接'),
+                              ),
+                            ),
+                          ],
+                          const SizedBox(width: 20),
+                          if (_isConnected) ...[
+                            SizedBox(
+                              height: 30,
+                              child: ElevatedButton(
+                                onPressed: _isReading ? _stopRead : _startRead,
+                                child: Text(_isReading ? '停止读取' : '开始读取'),
+                              ),
+                            ),
+                          ],
+                        ],
+                      ),
+                      if (_isConnected) ...[
+                        const SizedBox(height: 10),
+                        Row(
+                          children: [
+                            const Text(
+                              '设备信息: ',
+                              style: TextStyle(
+                                fontSize: 16,
+                                fontWeight: FontWeight.bold,
+                              ),
+                            ),
+                            Expanded(
+                              child: _deviceSN.isNotEmpty
+                                  ? Row(
+                                      children: [
+                                        const Text(
+                                          'SN: ',
+                                          style: TextStyle(
+                                            fontWeight: FontWeight.bold,
+                                          ),
+                                        ),
+                                        Text(
+                                          _deviceSN,
+                                          style: const TextStyle(
+                                            fontWeight: FontWeight.w500,
+                                          ),
+                                        ),
+                                        const SizedBox(width: 16),
+                                        const Text(
+                                          'SoftVer: ',
+                                          style: TextStyle(
+                                            fontWeight: FontWeight.bold,
+                                          ),
+                                        ),
+                                        Text(
+                                          _softVersion,
+                                          style: const TextStyle(
+                                            fontWeight: FontWeight.w500,
+                                          ),
+                                        ),
+                                        const SizedBox(width: 16),
+                                        const Text(
+                                          'HardVer: ',
+                                          style: TextStyle(
+                                            fontWeight: FontWeight.bold,
+                                          ),
+                                        ),
+                                        Text(
+                                          _hardVersion,
+                                          style: const TextStyle(
+                                            fontWeight: FontWeight.w500,
+                                          ),
+                                        ),
+                                      ],
+                                    )
+                                  : const Text(
+                                      '未连接',
+                                      style: TextStyle(
+                                        fontWeight: FontWeight.w500,
+                                      ),
+                                    ),
+                            ),
+                            const SizedBox(width: 20),
+                            SizedBox(
+                              height: 30,
+                              child: ElevatedButton(
+                                onPressed: _autoQueryDeviceInfo,
+                                child: const Text('刷新设备信息'),
+                              ),
+                            ),
+                          ],
+                        ),
+                        const SizedBox(height: 10),
+                        // 功率设置卡片
+                        Row(
+                          children: [
+                            const Text(
+                              '当前功率: ',
+                              style: TextStyle(
+                                fontSize: 14,
+                                fontWeight: FontWeight.bold,
+                              ),
+                            ),
+                            const SizedBox(width: 10),
+                            Text(
+                              _currentPower != null
+                                  ? '${_currentPower}dBm'
+                                  : '未查询',
+                              style: const TextStyle(
+                                fontSize: 14,
+                                fontWeight: FontWeight.w500,
+                              ),
+                            ),
+                            const SizedBox(width: 30),
+                            SizedBox(
+                              height: 30,
+                              child: ElevatedButton(
+                                onPressed: _queryPower,
+                                child: const Text('查询功率'),
+                              ),
+                            ),
+                            const SizedBox(width: 50),
+                            const Text(
+                              '功率设置: ',
+                              style: TextStyle(
+                                fontSize: 16,
+                                fontWeight: FontWeight.bold,
+                              ),
+                            ),
+                            const SizedBox(width: 10),
+                            Expanded(
+                              child: DropdownButton<int>(
+                                value: _selectedPower,
+                                items:
+                                    List.generate(
+                                          26,
+                                          (index) => index + 5,
+                                        ) // 5-30的值
+                                        .map((value) {
+                                          return DropdownMenuItem(
+                                            value: value,
+                                            child: Text('${value}dBm'),
+                                          );
+                                        })
+                                        .toList(),
+                                onChanged: (value) {
+                                  if (value != null && _isConnected) {
+                                    setState(() {
+                                      _selectedPower = value;
+                                    });
+                                  }
+                                },
+                                isExpanded: true,
+                              ),
+                            ),
+                            const SizedBox(width: 20),
+
+                            SizedBox(
+                              height: 30,
+                              child: ElevatedButton(
+                                onPressed: _setPower,
+                                child: const Text('设置功率'),
+                              ),
+                            ),
+                          ],
+                        ),
+                        const SizedBox(height: 5),
+                      ],
+                    ],
+                  ),
+                ),
+              ),
+              const SizedBox(height: 20),
+              Card(
+                child: Padding(
+                  padding: const EdgeInsets.all(16.0),
+                  child: Column(
+                    crossAxisAlignment: CrossAxisAlignment.start,
+                    children: [
+                      const Text(
+                        '数据日志',
+                        style: TextStyle(
+                          fontSize: 16,
+                          fontWeight: FontWeight.bold,
+                        ),
+                      ),
+                      const SizedBox(height: 10),
+                      Row(
+                        children: [
+                          ElevatedButton(
+                            onPressed: _clearLogs,
+                            child: const Text('清空日志'),
+                          ),
+                          const SizedBox(width: 10),
+                          Text(
+                            '日志数量: ${_receivedData.length}',
+                            style: const TextStyle(fontWeight: FontWeight.bold),
+                          ),
+                        ],
+                      ),
+                      const SizedBox(height: 10),
+                      Container(
+                        height: 250,
+                        decoration: BoxDecoration(
+                          border: Border.all(color: Colors.grey),
+                          borderRadius: BorderRadius.circular(4),
+                        ),
+                        child: _receivedData.isEmpty
+                            ? const Center(child: Text('暂无日志数据'))
+                            : ListView.builder(
+                                itemCount: _receivedData.length,
+                                itemBuilder: (context, index) {
+                                  return Padding(
+                                    padding: const EdgeInsets.all(8.0),
+                                    child: Text(
+                                      _receivedData[_receivedData.length -
+                                          1 -
+                                          index],
+                                      style: const TextStyle(fontSize: 12),
+                                    ),
+                                  );
+                                },
+                              ),
+                      ),
+                    ],
+                  ),
+                ),
+              ),
+            ],
+          ),
+        ),
       ),
     );
   }
+
+  @override
+  void dispose() {
+    // 在页面销毁时断开设备连接
+    if (_isConnected) {
+      _readerManager.disconnect();
+    }
+    
+    _readerManager.clearCallbacks();
+    _readerManager.dispose();
+    _dataController.dispose();
+    super.dispose();
+  }
 }

+ 8 - 1
UI/CF.APP/chicken_farm/windows/CMakeLists.txt

@@ -1,4 +1,4 @@
-# Project-level configuration.
+# Project-level configuration.
 cmake_minimum_required(VERSION 3.14)
 project(chicken_farm LANGUAGES CXX)
 
@@ -87,6 +87,13 @@ if(PLUGIN_BUNDLED_LIBRARIES)
     COMPONENT Runtime)
 endif()
 
+# 复制SWHidApi.dll到输出目录
+set(SWHIDAPI_DLL "${CMAKE_CURRENT_SOURCE_DIR}/runner/reader/SWHidApi.dll")
+if(EXISTS "${SWHIDAPI_DLL}")
+  install(FILES "${SWHIDAPI_DLL}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+    COMPONENT Runtime)
+endif()
+
 # Copy the native assets provided by the build.dart from all packages.
 set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/")
 install(DIRECTORY "${NATIVE_ASSETS_DIR}"

+ 10 - 1
UI/CF.APP/chicken_farm/windows/runner/CMakeLists.txt

@@ -11,6 +11,8 @@ add_executable(${BINARY_NAME} WIN32
   "main.cpp"
   "utils.cpp"
   "win32_window.cpp"
+  "reader/reader_service.cpp"
+  "reader/reader_method_call.cpp"
   "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
   "Runner.rc"
   "runner.exe.manifest"
@@ -34,7 +36,14 @@ target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
 # dependencies here.
 target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
 target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib")
+
+# 链接SWHidApi库 - 指定完整路径
+get_filename_component(SWHIDAPI_LIB_DIR "${CMAKE_CURRENT_SOURCE_DIR}/reader" ABSOLUTE)
+target_link_libraries(${BINARY_NAME} PRIVATE "${SWHIDAPI_LIB_DIR}/SWHidApi.lib")
+
+# 添加包含目录
+target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}/runner/reader")
 target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
 
 # Run the Flutter tool portions of the build. This must not be removed.
-add_dependencies(${BINARY_NAME} flutter_assemble)
+add_dependencies(${BINARY_NAME} flutter_assemble)

+ 26 - 16
UI/CF.APP/chicken_farm/windows/runner/flutter_window.cpp

@@ -3,14 +3,17 @@
 #include <optional>
 
 #include "flutter/generated_plugin_registrant.h"
+#include "reader/reader_method_call.h"
 
-FlutterWindow::FlutterWindow(const flutter::DartProject& project)
+FlutterWindow::FlutterWindow(const flutter::DartProject &project)
     : project_(project) {}
 
 FlutterWindow::~FlutterWindow() {}
 
-bool FlutterWindow::OnCreate() {
-  if (!Win32Window::OnCreate()) {
+bool FlutterWindow::OnCreate()
+{
+  if (!Win32Window::OnCreate())
+  {
     return false;
   }
 
@@ -21,15 +24,16 @@ bool FlutterWindow::OnCreate() {
   flutter_controller_ = std::make_unique<flutter::FlutterViewController>(
       frame.right - frame.left, frame.bottom - frame.top, project_);
   // Ensure that basic setup of the controller was successful.
-  if (!flutter_controller_->engine() || !flutter_controller_->view()) {
+  if (!flutter_controller_->engine() || !flutter_controller_->view())
+  {
     return false;
   }
   RegisterPlugins(flutter_controller_->engine());
   SetChildContent(flutter_controller_->view()->GetNativeWindow());
 
-  flutter_controller_->engine()->SetNextFrameCallback([&]() {
-    this->Show();
-  });
+  ReaderMethodCall::RegisterWindowsAPI(flutter_controller_->engine());
+  flutter_controller_->engine()->SetNextFrameCallback([&]()
+                                                      { this->Show(); });
 
   // Flutter can complete the first frame before the "show window" callback is
   // registered. The following call ensures a frame is pending to ensure the
@@ -39,8 +43,10 @@ bool FlutterWindow::OnCreate() {
   return true;
 }
 
-void FlutterWindow::OnDestroy() {
-  if (flutter_controller_) {
+void FlutterWindow::OnDestroy()
+{
+  if (flutter_controller_)
+  {
     flutter_controller_ = nullptr;
   }
 
@@ -50,21 +56,25 @@ void FlutterWindow::OnDestroy() {
 LRESULT
 FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
                               WPARAM const wparam,
-                              LPARAM const lparam) noexcept {
+                              LPARAM const lparam) noexcept
+{
   // Give Flutter, including plugins, an opportunity to handle window messages.
-  if (flutter_controller_) {
+  if (flutter_controller_)
+  {
     std::optional<LRESULT> result =
         flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
                                                       lparam);
-    if (result) {
+    if (result)
+    {
       return *result;
     }
   }
 
-  switch (message) {
-    case WM_FONTCHANGE:
-      flutter_controller_->engine()->ReloadSystemFonts();
-      break;
+  switch (message)
+  {
+  case WM_FONTCHANGE:
+    flutter_controller_->engine()->ReloadSystemFonts();
+    break;
   }
 
   return Win32Window::MessageHandler(hwnd, message, wparam, lparam);

BIN
UI/CF.APP/chicken_farm/windows/runner/reader/SWHidApi.dll


+ 182 - 0
UI/CF.APP/chicken_farm/windows/runner/reader/SWHidApi.h

@@ -0,0 +1,182 @@
+#ifndef _SWHidAPI_H_
+#define _SWHidAPI_H_
+
+#include <windows.h>
+
+#define SWSDK_API __declspec(dllimport)
+
+/******** Func: Get Usb Device Number**********/
+//  Param: None
+//  Return: Success return number, failed return 0
+/*********************************************************/
+extern "C" int SWSDK_API SWHid_GetUsbCount(void);
+
+/******** Func: Get USB HID value**********/
+//  Param: iIndex: HID Index 0,1,2....
+//         pucDeviceInfo: HID value
+//  Return: Success return number, failed return 0
+/*********************************************************/
+extern "C" BOOL SWSDK_API SWHid_GetUsbInfo(unsigned short iIndex, char *pucDeviceInfo);
+
+/******** Func: Open Device  *******************************/
+//  Param: iIndex: HID Index 0,1,2....
+//  Return: Success return 1, failed return 0
+/*********************************************************/
+extern "C" BOOL SWSDK_API SWHid_OpenDevice(unsigned short iIndex);
+
+/******** Func: Close Device *******************************/
+//  Param: None
+//  Return: Success return 1, failed return 0
+/*********************************************************/
+extern "C" BOOL SWSDK_API SWHid_CloseDevice(void);
+
+/******** Func: GetDeviceInfo. 9Bytes**********/
+//  Param: bDevAdr: 0xFF
+//         pucSystemInfo: SysInfo  9Bytes 1:SoftVer 2:HardVer 3 - 9:DeviceSN
+//  Return: Success return 1, failed return 0
+/*********************************************************/
+extern "C" BOOL SWSDK_API SWHid_GetDeviceSystemInfo(unsigned char bDevAdr, unsigned char *pucSystemInfo);
+
+/******** Func: Get Device One Param**********/
+//  Param: bDevAdr: 0xFF
+//         pucDevParamAddr: Param Addr
+//         pValue: Return Param Value
+//         ucParamLength: Return Param Length
+//  Return: Success return 1, failed return 0
+/*********************************************************/
+extern "C" BOOL SWSDK_API SWHid_ReadDeviceOneParam(unsigned char bDevAdr, unsigned char pucDevParamAddr, unsigned char *pValue);
+
+/******** Func: Set Device One Param**********/
+//  Param: bDevAdr: 0xFF
+//         pucDevParamAddr: Param Addr
+//         bValue: Param
+//  Return: Success return 1, failed return 0
+/*********************************************************/
+extern "C" BOOL SWSDK_API SWHid_SetDeviceOneParam(unsigned char bDevAdr, unsigned char pucDevParamAddr, unsigned char bValue);
+
+/******** Func: Stop all RF reading**********/
+//  Param: bDevAdr: 0xFF
+//  Return: Success return 1, failed return 0
+/*********************************************************/
+extern "C" BOOL SWSDK_API SWHid_StopRead(unsigned char bDevAdr);
+
+/******** Func: Start all RF reading**********/
+//  Param: bDevAdr: 0xFF
+//  Return: Success return 1, failed return 0
+/*********************************************************/
+extern "C" BOOL SWSDK_API SWHid_StartRead(unsigned char bDevAdr);
+
+/*
+ * Callback function prototype
+ * msg == 0: Device Insert
+ * msg == 1: Device Out
+ * msg == 2: param1 means tag number, param2 means tagdata, param3 means tagdata length,param4 means DevSN
+ */
+typedef void (*FUNPTR_ACTIVE_CALLBACK)(int msg, int param1, unsigned char *param2, int param3, unsigned char *param4);
+extern "C" int SWSDK_API SWHid_SetCallback(FUNPTR_ACTIVE_CALLBACK pfAddr);
+
+/******** Func: Inventory EPC**********/
+//  Param: bDevAdr: 0xFF
+//         pBuffer: Get Buffer
+//         Totallen: Get Buffer Length
+//         CardNum: Tag Number
+//  Return: Success return 1, failed return 0
+/*********************************************************/
+extern "C" BOOL SWSDK_API SWHid_InventoryG2(unsigned char bDevAdr, unsigned char *pBuffer, unsigned short *Totallen, unsigned short *CardNum);
+
+/******** Func: Write EPC**********/
+//  Param: bDevAdr: 0xFF
+//         Password: Password (4 bytes)
+//         WriteEPC: Write Data
+//         WriteEPClen: Write Length
+//  Return: Success return 1, failed return 0
+/*********************************************************/
+extern "C" BOOL SWSDK_API SWHid_WriteEPCG2(unsigned char bDevAdr, unsigned char *Password, unsigned char *WriteEPC, unsigned char WriteEPClen);
+
+/******** Func: Read Card**********/
+//  Param: bDevAdr: 0xFF
+//         Password: Password (4 bytes)
+//         Mem:      0:Reserved 1:EPC 2:TID 3:USER
+//         WordPtr:  Start Address
+//         ReadEPClen: Read Length
+//         Data: Read Data
+//  Return: Success return 1, failed return 0
+/*********************************************************/
+extern "C" BOOL SWSDK_API SWHid_ReadCardG2(unsigned char bDevAdr, unsigned char *Password, unsigned char Mem, unsigned char WordPtr, unsigned char ReadEPClen, unsigned char *Data);
+
+/******** Func: Write Card**********/
+//  Param: bDevAdr:  0xFF
+//         Password: Password (4 bytes)
+//         Mem:      0:Reserved 1:EPC 2:TID 3:USER
+//         WordPtr:  Start Address
+//         WriteEPC: Write Data
+//         WriteEPClen: Write Length
+//  Return: Success return 1, failed return 0
+/*********************************************************/
+extern "C" BOOL SWSDK_API SWHid_WriteCardG2(unsigned char bDevAdr, unsigned char *Password, unsigned char Mem, unsigned char WordPtr, unsigned char Writelen, unsigned char *Writedata);
+
+/******** Func: RelayOn**********/
+//  Param: bDevAdr: 0xFF
+//  Return: Success return 1, failed return 0
+/*********************************************************/
+extern "C" BOOL SWSDK_API SWHid_RelayOn(unsigned char bDevAdr);
+
+/******** Func: RelayOff**********/
+//  Param: bDevAdr: 0xFF
+//  Return: Success return 1, failed return 0
+/*********************************************************/
+extern "C" BOOL SWSDK_API SWHid_RelayOff(unsigned char bDevAdr);
+
+/******** Func: SetFreq**********/
+//  Param: bDevAdr: 0xFF
+//         pFreq:Set 2bytes Freq Value
+// pFreq[0]   pFreq[1]
+// 0x31        0x80     //US Freq
+// 0x4E        0x00     //Europe
+// 0x2C        0xA3     //China
+// 0x29        0x9D     //Korea
+// 0x2E        0x9F     //Australia
+// 0x4E        0x00     //New Zealand
+// 0x4E        0x00     //India
+// 0x2C        0x81     //Singapore
+// 0x2C        0xA3     //Hongkong
+// 0x31        0xA7     //Taiwan
+// 0x31        0x80     //Canada
+// 0x31        0x80     //Mexico
+// 0x31        0x99     //Brazil
+// 0x1C        0x99     //Israel
+// 0x24        0x9D     //South Africa
+// 0x2C        0xA3     //Thailand
+// 0x28        0xA1     //Malaysia
+// 0x29        0x9D     //Japan
+//  Return: Success return 1, failed return 0
+/*********************************************************/
+extern "C" BOOL SWSDK_API SWHid_SetFreq(unsigned char bDevAdr, unsigned char *pFreq);
+
+/******** Func: ReadFreq**********/
+//  Param: bDevAdr: 0xFF
+//         pFreq: Return 2bytes Freq Value
+// pFreq[0]   pFreq[1]
+// 0x31        0x80     //US Freq
+// 0x4E        0x00     //Europe
+// 0x2C        0xA3     //China
+// 0x29        0x9D     //Korea
+// 0x2E        0x9F     //Australia
+// 0x4E        0x00     //New Zealand
+// 0x4E        0x00     //India
+// 0x2C        0x81     //Singapore
+// 0x2C        0xA3     //Hongkong
+// 0x31        0xA7     //Taiwan
+// 0x31        0x80     //Canada
+// 0x31        0x80     //Mexico
+// 0x31        0x99     //Brazil
+// 0x1C        0x99     //Israel
+// 0x24        0x9D     //South Africa
+// 0x2C        0xA3     //Thailand
+// 0x28        0xA1     //Malaysia
+// 0x29        0x9D     //Japan
+//  Return: Success return 1, failed return 0
+/*********************************************************/
+extern "C" BOOL SWSDK_API SWHid_ReadFreq(unsigned char bDevAdr, unsigned char *pFreq);
+
+#endif

BIN
UI/CF.APP/chicken_farm/windows/runner/reader/SWHidApi.lib


+ 150 - 0
UI/CF.APP/chicken_farm/windows/runner/reader/reader_method_call.cpp

@@ -0,0 +1,150 @@
+#include "reader_method_call.h"
+#include "reader_service.h"
+
+#include <flutter/flutter_engine.h>
+#include <flutter/method_channel.h>
+#include <flutter/standard_method_codec.h>
+#include <flutter/event_channel.h>
+#include <flutter/event_stream_handler_functions.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+#include <functional>
+#include <map>
+
+static std::unique_ptr<flutter::EventChannel<flutter::EncodableValue>> g_event_channel = nullptr;
+static std::unique_ptr<flutter::StreamHandler<flutter::EncodableValue>> g_stream_handler = nullptr;
+
+void ReaderMethodCall::RegisterWindowsAPI(flutter::FlutterEngine *engine)
+{
+  auto channel = std::make_unique<flutter::MethodChannel<flutter::EncodableValue>>(
+      engine->messenger(), "com.vber.chicken_farm/win_reader",
+      &flutter::StandardMethodCodec::GetInstance());
+  channel->SetMethodCallHandler([](const auto &call, auto result)
+                                { HandleMethodCall(call, std::move(result)); });
+
+  g_event_channel = std::make_unique<flutter::EventChannel<flutter::EncodableValue>>(
+      engine->messenger(), "com.vber.chicken_farm/win_reader_events",
+      &flutter::StandardMethodCodec::GetInstance());
+
+  g_stream_handler =
+      std::make_unique<flutter::StreamHandlerFunctions<flutter::EncodableValue>>(
+          [](const flutter::EncodableValue *arguments,
+             std::unique_ptr<flutter::EventSink<flutter::EncodableValue>> &&events)
+              -> std::unique_ptr<flutter::StreamHandlerError<flutter::EncodableValue>>
+          {
+            // 使用shared_ptr包装,使其可拷贝
+            auto events_ptr = std::shared_ptr<flutter::EventSink<flutter::EncodableValue>>(std::move(events));
+            ReaderService::GetInstance()->SetDataCallback([events_ptr](const std::string &data)
+                                                          { events_ptr->Success(flutter::EncodableValue(data)); });
+            return nullptr;
+          },
+          [](const flutter::EncodableValue *arguments)
+              -> std::unique_ptr<flutter::StreamHandlerError<flutter::EncodableValue>>
+          {
+            ReaderService::GetInstance()->SetDataCallback(nullptr);
+            return nullptr;
+          });
+
+  g_event_channel->SetStreamHandler(std::move(g_stream_handler));
+}
+
+void ReaderMethodCall::HandleMethodCall(
+    const flutter::MethodCall<flutter::EncodableValue> &method_call,
+    std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result)
+{
+  // Use a map to handle different method calls
+  static std::map<std::string, std::function<void(const flutter::MethodCall<flutter::EncodableValue> &method_call, std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result)>> handlers = {
+      {"scanUsb", [](const flutter::MethodCall<flutter::EncodableValue> &method_call, std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result)
+       {
+         // Call the scan USB method of the reader service
+         std::vector<std::string> usb = ReaderService::GetInstance()->ScanUsb();
+
+         // Create a list to store USB device names
+         flutter::EncodableList device_list;
+         for (const auto &device : usb)
+         {
+           device_list.push_back(flutter::EncodableValue(device));
+         }
+
+         // Return the list of USB devices to Flutter (empty if no devices found)
+         result->Success(flutter::EncodableValue(device_list));
+       }},
+      {"connect", [](const flutter::MethodCall<flutter::EncodableValue> &method_call, std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result)
+       {
+         const auto *args = std::get_if<flutter::EncodableMap>(method_call.arguments());
+         if (args)
+         {
+           auto deviceIndexIt = args->find(flutter::EncodableValue("deviceIndex"));
+           if (deviceIndexIt != args->end())
+           {
+             int deviceIndex = std::get<int>(deviceIndexIt->second);
+             bool success = ReaderService::GetInstance()->Connect(deviceIndex);
+             result->Success(flutter::EncodableValue(success));
+             return;
+           }
+         }
+         result->Success(flutter::EncodableValue(false));
+       }},
+      {"disconnect", [](const flutter::MethodCall<flutter::EncodableValue> &method_call, std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result)
+       {
+         ReaderService::GetInstance()->Disconnect();
+         result->Success();
+       }},
+      {"startRead", [](const flutter::MethodCall<flutter::EncodableValue> &method_call, std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result)
+       {
+         bool success = ReaderService::GetInstance()->StartRead();
+         result->Success(flutter::EncodableValue(success));
+       }},
+      {"stopRead", [](const flutter::MethodCall<flutter::EncodableValue> &method_call, std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result)
+       {
+         ReaderService::GetInstance()->StopRead();
+         result->Success();
+       }},
+      {"getDeviceInfo", [](const flutter::MethodCall<flutter::EncodableValue> &method_call, std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result)
+       {
+         std::string deviceInfo = ReaderService::GetInstance()->GetDeviceInfo();
+         result->Success(flutter::EncodableValue(deviceInfo));
+       }},
+      {"getPower", [](const flutter::MethodCall<flutter::EncodableValue> &method_call, std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result)
+       {
+         unsigned char power = 0;
+         bool success = ReaderService::GetInstance()->GetPower(power);
+         if (success) {
+           result->Success(flutter::EncodableValue(static_cast<int>(power)));
+         } else {
+           result->Success(flutter::EncodableValue());
+         }
+       }},
+      {"setPower", [](const flutter::MethodCall<flutter::EncodableValue> &method_call, std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result)
+       {
+         const auto *args = std::get_if<flutter::EncodableMap>(method_call.arguments());
+         if (args)
+         {
+           auto powerIt = args->find(flutter::EncodableValue("power"));
+           if (powerIt != args->end())
+           {
+             int power = std::get<int>(powerIt->second);
+             bool success = ReaderService::GetInstance()->SetPower(static_cast<unsigned char>(power));
+             result->Success(flutter::EncodableValue(success));
+             return;
+           }
+         }
+         result->Success(flutter::EncodableValue(false));
+       }}
+      // More method handlers can be added here
+      // {"methodName", [](auto result) { /* Handle method */ }}
+  };
+
+  // Find and execute the corresponding method handler
+  auto handler = handlers.find(method_call.method_name());
+  if (handler != handlers.end())
+  {
+    handler->second(method_call, std::move(result));
+  }
+  else
+  {
+    result->NotImplemented();
+  }
+}

+ 32 - 0
UI/CF.APP/chicken_farm/windows/runner/reader/reader_method_call.h

@@ -0,0 +1,32 @@
+#ifndef RUNNER_READER_METHOD_CALL_H_
+#define RUNNER_READER_METHOD_CALL_H_
+
+#include <flutter/flutter_engine.h>
+#include <flutter/method_channel.h>
+#include <flutter/plugin_registrar_windows.h>
+#include <flutter/standard_method_codec.h>
+#include <flutter/event_channel.h>
+#include <flutter/event_stream_handler_functions.h>
+
+#include <memory>
+#include <string>
+
+// Handle reader functionality method calls
+class ReaderMethodCall
+{
+public:
+    // Register method channel with the given registrar
+    static void RegisterWindowsAPI(flutter::FlutterEngine *engine);
+
+private:
+    // Keep constructor private since this is a utility class
+    ReaderMethodCall() = delete;
+    ~ReaderMethodCall() = delete;
+
+    // Called when a method is invoked on the channel
+    static void HandleMethodCall(
+        const flutter::MethodCall<flutter::EncodableValue> &method_call,
+        std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result);
+};
+
+#endif // RUNNER_READER_METHOD_CALL_H_

+ 413 - 0
UI/CF.APP/chicken_farm/windows/runner/reader/reader_service.cpp

@@ -0,0 +1,413 @@
+#include "reader_service.h"
+#include "../utils.h"
+
+#include <iostream>
+#include <thread>
+#include <chrono>
+
+void PrintLogMessage(const std::string &message)
+{
+  CreateAndAttachConsole();
+  std::cout << message << std::endl;
+}
+
+ReaderService *ReaderService::GetInstance()
+{
+  static ReaderService instance;
+  return &instance;
+}
+
+std::vector<std::string> ReaderService::ScanUsb()
+{
+  PrintLogMessage("Scanning USB...");
+
+  std::vector<std::string> usb_devices;
+  int iHidNum = SWHid_GetUsbCount();
+  for (int i = 0; i < iHidNum; i++)
+  {
+    char arrHidName[256];
+    SWHid_GetUsbInfo(static_cast<unsigned short>(i), arrHidName);
+    usb_devices.push_back(arrHidName);
+  }
+  return usb_devices;
+}
+
+bool ReaderService::Connect(int deviceIndex)
+{
+  unsigned char arrBuffer[256] = {0};
+
+  // Close previous connection (if exists)
+  if (connected_)
+  {
+    SWHid_SetCallback(NULL);
+    SWHid_CloseDevice();
+  }
+
+  if (SWHid_OpenDevice(static_cast<unsigned short>(deviceIndex)) == FALSE)
+  {
+    PrintLogMessage("Failed to connect to device!");
+    return false;
+  }
+
+  // Get device information
+  if (SWHid_GetDeviceSystemInfo(0xFF, arrBuffer) == false) // failed
+  {
+    Sleep(10);
+    if (SWHid_GetDeviceSystemInfo(0xFF, arrBuffer) == false) // failed
+    {
+      SWHid_CloseDevice();
+      PrintLogMessage("Failed to get device info!");
+      return false;
+    }
+  }
+
+  connected_ = true;
+
+  // Set callback function
+  // SWHid_SetCallback(ReaderService::DeviceCallback);
+
+  // Extract device info
+  int softVer = static_cast<int>(arrBuffer[0]);
+  int hardVer = static_cast<int>(arrBuffer[1]);
+  std::string softVersion = std::to_string(softVer >> 4) + "." + std::to_string(softVer & 0x0F);
+  std::string hardVersion = std::to_string(hardVer >> 4) + "." + std::to_string(hardVer & 0x0F);
+
+  // Extract device SN from buffer[2] to buffer[9] (8 bytes)
+  std::string deviceSN = "";
+  for (int i = 2; i < 10; i++)
+  {
+    char hex[3];
+    sprintf_s(hex, sizeof(hex), "%.2X", arrBuffer[i]);
+    deviceSN += hex;
+  }
+
+  std::string log_message = "Connected to device successfully! SN: " + deviceSN +
+                            ", SoftVer: " + softVersion +
+                            ", HardVer: " + hardVersion;
+  PrintLogMessage(log_message);
+
+  return true;
+}
+
+void ReaderService::Disconnect()
+{
+  if (connected_)
+  {
+    StopRead(); // Stop reading
+    SWHid_SetCallback(NULL);
+    SWHid_CloseDevice();
+    connected_ = false;
+
+    PrintLogMessage("Disconnected from device.");
+  }
+}
+
+bool ReaderService::StartRead()
+{
+  if (!connected_)
+  {
+    PrintLogMessage("Not connected to any device!");
+    return false;
+  }
+  // SWHid_SetDeviceOneParam(0xFF, 0x02, 0x01); // Set Workmode as Activemode
+  // Set working mode to answer mode
+  if (SWHid_SetDeviceOneParam(0xFF, 0x02, 0x00) == false) // Set Workmode as Answermode
+  {
+    PrintLogMessage("Failed to set device to answer mode!");
+    return false;
+  }
+
+  reading_ = true;
+  timer_running_ = true;
+
+  // Start timer thread to periodically call AnswerMode
+  timer_thread_ = std::make_unique<std::thread>(&ReaderService::RunTimer, this);
+
+  PrintLogMessage("Started reading in answer mode.");
+
+  return true;
+}
+
+void ReaderService::StopRead()
+{
+  if (reading_)
+  {
+    // Stop the timer thread
+    if (timer_running_)
+    {
+      timer_running_ = false;
+      if (timer_thread_ && timer_thread_->joinable())
+      {
+        timer_thread_->join();
+      }
+    }
+
+    reading_ = false;
+
+    PrintLogMessage("Stopped reading.");
+  }
+}
+
+void ReaderService::RunTimer()
+{
+  while (timer_running_)
+  {
+    AnswerMode();
+    std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Sleep for 100ms, similar to the demo
+  }
+}
+
+std::string ReaderService::GetDeviceInfo()
+{
+  if (!connected_)
+  {
+    return "";
+  }
+
+  unsigned char arrBuffer[256] = {0};
+  if (SWHid_GetDeviceSystemInfo(0xFF, arrBuffer) == false)
+  {
+    Sleep(10);
+    if (SWHid_GetDeviceSystemInfo(0xFF, arrBuffer) == false)
+    {
+      return "";
+    }
+  }
+
+  // Extract device info
+  int softVer = static_cast<int>(arrBuffer[0]);
+  int hardVer = static_cast<int>(arrBuffer[1]);
+  std::string softVersion = std::to_string(softVer >> 4) + "." + std::to_string(softVer & 0x0F);
+  std::string hardVersion = std::to_string(hardVer >> 4) + "." + std::to_string(hardVer & 0x0F);
+
+  // Extract device SN from buffer[2] to buffer[9] (8 bytes)
+  std::string deviceSN = "";
+  for (int i = 2; i < 10; i++)
+  {
+    char hex[3];
+    sprintf_s(hex, sizeof(hex), "%.2X", arrBuffer[i]);
+    deviceSN += hex;
+  }
+
+  // Create a formatted string with all device info
+  std::string deviceInfo = "SN:" + deviceSN +
+                           ",SoftVer:" + softVersion +
+                           ",HardVer:" + hardVersion;
+
+  std::string log_message = "Device Info - " + deviceInfo;
+  PrintLogMessage(log_message);
+
+  return deviceInfo;
+}
+
+// 新增功率查询功能
+bool ReaderService::GetPower(unsigned char &power)
+{
+  if (!connected_)
+  {
+    PrintLogMessage("Not connected to any device!");
+    return false;
+  }
+
+  unsigned char bValue = 0;
+  unsigned char bParamAddr = 0x05; // RFPower parameter address
+
+  if (SWHid_ReadDeviceOneParam(0xFF, bParamAddr, &bValue) == false)
+  {
+    PrintLogMessage("Failed to read power!");
+    return false;
+  }
+
+  power = bValue;
+  std::string log_message = "Power read successfully: " + std::to_string(power);
+  PrintLogMessage(log_message);
+
+  return true;
+}
+
+// 新增功率设置功能
+bool ReaderService::SetPower(unsigned char power)
+{
+  if (!connected_)
+  {
+    PrintLogMessage("Not connected to any device!");
+    return false;
+  }
+
+  // Check if power is within valid range (based on demo: 5-30)
+  if (power < 5 || power > 30)
+  {
+    std::string log_message = "Invalid power value: " + std::to_string(power) + " (must be between 5-30)";
+    PrintLogMessage(log_message);
+    return false;
+  }
+
+  unsigned char bParamAddr = 0x05; // RFPower parameter address
+
+  if (SWHid_SetDeviceOneParam(0xFF, bParamAddr, power) == false)
+  {
+    PrintLogMessage("Failed to set power!");
+    return false;
+  }
+
+  std::string log_message = "Power set successfully to: " + std::to_string(power);
+  PrintLogMessage(log_message);
+
+  return true;
+}
+
+void ReaderService::DeviceCallback(int msg, int param1, unsigned char *param2, int param3, unsigned char *param4)
+{
+  ReaderService *instance = ReaderService::GetInstance();
+
+  if (msg == 2) // Data
+  {
+    PrintLogMessage("Data received.");
+    unsigned short iTagLength = 0;
+    unsigned short iTagNumber = 0;
+    iTagLength = static_cast<unsigned short>(param3);
+    iTagNumber = static_cast<unsigned short>(param1);
+    unsigned char *pBuffer = NULL;
+    pBuffer = (unsigned char *)param2;
+
+    if (iTagNumber == 0)
+      return;
+
+    int iIndex = 0;
+    int iLength = 0;
+    unsigned char *pID;
+    unsigned char bPackLength = 0;
+    int iIDLen = 0;
+
+    for (iIndex = 0; iIndex < iTagNumber; iIndex++)
+    {
+      bPackLength = pBuffer[iLength];
+      pID = (unsigned char *)&pBuffer[1 + iLength];
+
+      std::string str2 = "";
+      char buffer[256];
+
+      sprintf_s(buffer, sizeof(buffer), "Type:%.2X ", pID[0]);
+      str2 += buffer;
+
+      if ((pID[0] & 0x80) == 0x80) // with TimeStamp
+      {
+        iIDLen = bPackLength - 7;
+      }
+      else
+        iIDLen = bPackLength - 1;
+
+      sprintf_s(buffer, sizeof(buffer), "Ant:%.2X Tag:", pID[1]);
+      str2 += buffer;
+
+      for (int i = 2; i < iIDLen; i++)
+      {
+        sprintf_s(buffer, sizeof(buffer), "%.2X ", pID[i]);
+        str2 += buffer;
+      }
+
+      sprintf_s(buffer, sizeof(buffer), "RSSI:%.2X", pID[iIDLen]);
+      str2 += buffer;
+
+      // Notify UI of new data
+      if (instance->data_callback_)
+      {
+        instance->data_callback_(str2);
+      }
+
+      iLength = iLength + bPackLength + 1;
+    }
+  }
+  else if (msg == 1) // Device Out
+  {
+    PrintLogMessage("Device removed.");
+
+    if (instance->data_callback_)
+    {
+      instance->data_callback_("Device Out");
+    }
+  }
+  else if (msg == 0) // Device Insert
+  {
+    PrintLogMessage("Device inserted.");
+
+    if (instance->data_callback_)
+    {
+      instance->data_callback_("Device Insert");
+    }
+  }
+}
+
+void ReaderService::AnswerMode()
+{
+  PrintLogMessage("reading...");
+  unsigned char arrBuffer[2048] = {0};
+  unsigned short iTagLength = 0;
+  unsigned short iTagNumber = 0;
+  if (SWHid_InventoryG2(0xFF, arrBuffer, &iTagLength, &iTagNumber) == false)
+  {
+    PrintLogMessage("Failed to inventory!");
+    return;
+  }
+  if (iTagNumber == 0)
+  {
+    // PrintLogMessage("No data!");
+    return;
+  }
+  ReaderService *instance = ReaderService::GetInstance();
+  int iIndex = 0;
+  int iLength = 0;
+  unsigned char *pID;
+  unsigned char bPackLength = 0;
+  int iIDLen = 0;
+
+  for (iIndex = 0; iIndex < iTagNumber; iIndex++)
+  {
+    bPackLength = arrBuffer[iLength];
+    pID = (unsigned char *)&arrBuffer[1 + iLength];
+    std::string str2 = "", tag = "";
+    char buffer[256];
+
+    sprintf_s(buffer, sizeof(buffer), "Type:%.2X ", pID[0]);
+    str2 += buffer;
+
+    if ((pID[0] & 0x80) == 0x80) // with TimeStamp
+    {
+      iIDLen = bPackLength - 7;
+    }
+    else
+      iIDLen = bPackLength - 1;
+
+    sprintf_s(buffer, sizeof(buffer), "Ant:%.2X Tag:", pID[1]);
+    str2 += buffer;
+
+    for (int i = 2; i < iIDLen; i++)
+    {
+      sprintf_s(buffer, sizeof(buffer), "%.2X ", pID[i]);
+      str2 += buffer;
+      tag += buffer;
+    }
+
+    sprintf_s(buffer, sizeof(buffer), "RSSI:%.2X", pID[iIDLen]);
+    str2 += buffer;
+    PrintLogMessage(str2);
+    // Notify UI of new data
+    if (instance->data_callback_)
+    {
+      PrintLogMessage("Tag: " + tag);
+      instance->data_callback_(tag);
+    }
+    else
+    {
+      PrintLogMessage("Tag2: " + tag);
+    }
+
+    iLength = iLength + bPackLength + 1;
+  }
+}
+
+ReaderService::~ReaderService()
+{
+  // Disconnect during destruction
+  Disconnect();
+}

+ 77 - 0
UI/CF.APP/chicken_farm/windows/runner/reader/reader_service.h

@@ -0,0 +1,77 @@
+#ifndef RUNNER_READER_SERVICE_H_
+#define RUNNER_READER_SERVICE_H_
+
+#include <iostream>
+#include <vector>
+#include <string>
+#include <functional>
+#include <thread>
+#include <atomic>
+#include <chrono>
+extern "C"
+{
+#include "SWHidApi.h"
+}
+
+// Define data callback type to notify UI of received tag data
+typedef std::function<void(const std::string &)> DataCallback;
+
+// Service for handling USB scanning functionality
+class ReaderService
+{
+public:
+  // Get singleton instance
+  static ReaderService *GetInstance();
+
+  // Scan USB devices and return list of device names
+  std::vector<std::string> ScanUsb();
+
+  // Connect to specified USB device by index
+  bool Connect(int deviceIndex);
+
+  // Disconnect
+  void Disconnect();
+
+  // Start reading in answer mode
+  bool StartRead();
+
+  // Stop reading in answer mode
+  void StopRead();
+
+  void AnswerMode();
+
+  // Set data callback function to notify UI of received data
+  void SetDataCallback(DataCallback callback) { data_callback_ = callback; }
+
+  // Get device system information and return it as a string
+  std::string GetDeviceInfo();
+
+  // Get current power level (returns true on success)
+  bool GetPower(unsigned char& power);
+
+  // Set power level (returns true on success)
+  bool SetPower(unsigned char power);
+
+private:
+  ReaderService() = default;
+  ~ReaderService();
+
+  // Disable copy constructor and assignment operator
+  ReaderService(const ReaderService &) = delete;
+  ReaderService &operator=(const ReaderService &) = delete;
+
+  // Static callback function for receiving device data
+  static void DeviceCallback(int msg, int param1, unsigned char *param2, int param3, unsigned char *param4);
+
+  // Function to run the timer loop for periodically calling AnswerMode
+  void RunTimer();
+
+  // Member variables
+  bool connected_ = false;
+  bool reading_ = false;
+  std::atomic<bool> timer_running_{false};
+  std::unique_ptr<std::thread> timer_thread_;
+  DataCallback data_callback_;
+};
+
+#endif // RUNNER_READER_SERVICE_H_