serial_setting_page.dart 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  1. // ignore_for_file: unused_import, unused_field, prefer_final_fields
  2. import 'dart:async';
  3. import 'dart:typed_data';
  4. import 'package:chicken_farm/components/vb_app_bar.dart';
  5. import 'package:chicken_farm/core/services/win/win_reader_channel.dart';
  6. import 'package:chicken_farm/core/services/win/win_reader_manager.dart';
  7. import 'package:chicken_farm/core/utils/logger.dart';
  8. import 'package:chicken_farm/core/utils/toast.dart';
  9. import 'package:flutter/material.dart';
  10. class SerialSettingPage extends StatefulWidget {
  11. const SerialSettingPage({super.key});
  12. @override
  13. State<SerialSettingPage> createState() => _SerialSettingPageState();
  14. }
  15. class _SerialSettingPageState extends State<SerialSettingPage> {
  16. final WinReaderManager _readerManager = WinReaderManager();
  17. List<String> _availablePorts = [];
  18. String? _selectedPort;
  19. final TextEditingController _dataController = TextEditingController();
  20. final List<String> _receivedData = [];
  21. bool _isConnected = false;
  22. bool _isReading = false;
  23. // 设备信息
  24. String _deviceSN = '';
  25. String _softVersion = '';
  26. String _hardVersion = '';
  27. // 功率相关状态
  28. int? _currentPower;
  29. int _selectedPower = 20; // 默认值
  30. @override
  31. void initState() {
  32. super.initState();
  33. // 注册回调函数
  34. _readerManager.registerCallbacks(
  35. onConnect: _onConnect,
  36. onDataReceived: _onDataReceived,
  37. onError: _onError,
  38. );
  39. _loadUsbPorts();
  40. // 监听数据流 - 使用WinReaderManager内部处理,无需重复监听
  41. }
  42. /// 加载可用Usb列表
  43. void _loadUsbPorts() async {
  44. try {
  45. final ports = await _readerManager.scanUsb();
  46. if (mounted) {
  47. // 检查组件是否仍然挂载
  48. setState(() {
  49. _availablePorts = ports ?? [];
  50. if (_availablePorts.isNotEmpty) {
  51. _selectedPort = _availablePorts.first;
  52. }
  53. });
  54. }
  55. } catch (e) {
  56. if (mounted) {
  57. // 检查组件是否仍然挂载
  58. logger.e(e);
  59. ToastUtil.error("扫描USB设备失败: $e");
  60. }
  61. }
  62. }
  63. /// 连接状态回调
  64. void _onConnect(bool connected) {
  65. if (mounted) {
  66. setState(() {
  67. _isConnected = connected;
  68. if (!connected) {
  69. _isReading = false;
  70. _deviceSN = '';
  71. _softVersion = '';
  72. _hardVersion = '';
  73. _currentPower = null;
  74. }
  75. });
  76. if (connected) {
  77. _addLog("成功连接到设备: $_selectedPort");
  78. // 连接成功后自动查询设备信息
  79. _autoQueryDeviceInfo();
  80. // 连接成功后自动查询功率
  81. _autoQueryPower();
  82. } else {
  83. _addLog("已断开设备连接");
  84. }
  85. }
  86. }
  87. /// 自动查询设备信息
  88. void _autoQueryDeviceInfo() async {
  89. String? deviceInfo = await _readerManager.getDeviceInfo();
  90. if (deviceInfo != null && mounted) {
  91. // 解析设备信息
  92. _parseDeviceInfo(deviceInfo);
  93. _addLog("设备信息查询成功: $deviceInfo");
  94. } else {
  95. _addLog("设备信息查询失败");
  96. }
  97. }
  98. /// 自动查询功率
  99. void _autoQueryPower() async {
  100. int? power = await _readerManager.getPower();
  101. if (power != null && mounted) {
  102. setState(() {
  103. _currentPower = power;
  104. _selectedPower = power; // 设置下拉框的默认值为当前功率
  105. });
  106. _addLog("功率查询成功: ${power}dBm");
  107. } else {
  108. _addLog("功率查询失败");
  109. }
  110. }
  111. /// 解析设备信息字符串
  112. void _parseDeviceInfo(String deviceInfo) {
  113. if (mounted) {
  114. // deviceInfo 格式: "SN:1234567890ABCDEF,SoftVer:1.2,HardVer:3.4"
  115. List<String> parts = deviceInfo.split(',');
  116. for (String part in parts) {
  117. if (part.startsWith('SN:')) {
  118. setState(() {
  119. _deviceSN = part.substring(3); // 去掉'SN:'前缀
  120. });
  121. } else if (part.startsWith('SoftVer:')) {
  122. setState(() {
  123. _softVersion = part.substring(8); // 去掉'SoftVer:'前缀
  124. });
  125. } else if (part.startsWith('HardVer:')) {
  126. setState(() {
  127. _hardVersion = part.substring(8); // 去掉'HardVer:'前缀
  128. });
  129. }
  130. }
  131. }
  132. }
  133. /// 数据接收回调
  134. void _onDataReceived(String data) {
  135. if (mounted) {
  136. _addLog("读取标签: $data");
  137. }
  138. }
  139. /// 错误回调
  140. void _onError(String error) {
  141. if (mounted) {
  142. _addLog("错误: $error");
  143. ToastUtil.error("操作失败: $error");
  144. }
  145. }
  146. /// 连接USB
  147. void _connectUsbPort() async {
  148. if (_selectedPort == null) {
  149. ToastUtil.error("请先选择一个USB设备");
  150. return;
  151. }
  152. if (!_isConnected) {
  153. // 获取当前选中设备的索引
  154. int deviceIndex = _availablePorts.indexOf(_selectedPort!);
  155. bool success = await _readerManager.connect(deviceIndex);
  156. if (!success) {
  157. ToastUtil.error("连接设备失败");
  158. }
  159. } else {
  160. await _readerManager.disconnect();
  161. }
  162. }
  163. /// 开始读取
  164. void _startRead() async {
  165. bool success = await _readerManager.startRead();
  166. if (success && mounted) {
  167. setState(() {
  168. _isReading = true;
  169. });
  170. _addLog("开始读取数据");
  171. }
  172. }
  173. /// 停止读取
  174. void _stopRead() async {
  175. await _readerManager.stopRead();
  176. if (mounted) {
  177. setState(() {
  178. _isReading = false;
  179. });
  180. _addLog("已停止读取数据");
  181. }
  182. }
  183. /// 查询功率
  184. void _queryPower() async {
  185. if (!_isConnected) {
  186. ToastUtil.error("请先连接设备");
  187. return;
  188. }
  189. int? power = await _readerManager.getPower();
  190. if (power != null && mounted) {
  191. setState(() {
  192. _currentPower = power;
  193. _selectedPower = power;
  194. });
  195. _addLog("功率查询成功: ${power}dBm");
  196. } else {
  197. _addLog("功率查询失败");
  198. }
  199. }
  200. /// 设置功率
  201. void _setPower() async {
  202. if (!_isConnected) {
  203. ToastUtil.error("请先连接设备");
  204. return;
  205. }
  206. bool success = await _readerManager.setPower(_selectedPower);
  207. if (success) {
  208. ToastUtil.success("功率设置成功");
  209. if (mounted) {
  210. _addLog("功率设置成功: ${_selectedPower}dBm");
  211. }
  212. Future.delayed(const Duration(milliseconds: 500), () {
  213. _queryPower();
  214. });
  215. } else {
  216. _addLog("功率设置失败");
  217. }
  218. }
  219. /// 添加日志
  220. void _addLog(String log) {
  221. if (mounted) {
  222. setState(() {
  223. _receivedData.add("${DateTime.now()}: $log");
  224. });
  225. }
  226. }
  227. /// 清空日志
  228. void _clearLogs() {
  229. if (mounted) {
  230. setState(() {
  231. _receivedData.clear();
  232. });
  233. }
  234. }
  235. @override
  236. Widget build(BuildContext context) {
  237. return Scaffold(
  238. appBar: VberAppBar(title: 'USB测试工具'),
  239. body: Padding(
  240. padding: const EdgeInsets.all(16.0),
  241. child: SingleChildScrollView(
  242. physics: const BouncingScrollPhysics(),
  243. child: Column(
  244. crossAxisAlignment: CrossAxisAlignment.start,
  245. children: [
  246. // 设备信息卡片
  247. Card(
  248. child: Padding(
  249. padding: const EdgeInsets.all(16.0),
  250. child: Column(
  251. crossAxisAlignment: CrossAxisAlignment.start,
  252. children: [
  253. Row(
  254. children: [
  255. const Text('USB设备:'),
  256. const SizedBox(width: 10),
  257. Expanded(
  258. child: DropdownButton<String>(
  259. value: _selectedPort,
  260. items: _availablePorts.map((port) {
  261. return DropdownMenuItem(
  262. value: port,
  263. child: Text(
  264. port,
  265. overflow: TextOverflow.ellipsis,
  266. ),
  267. );
  268. }).toList(),
  269. onChanged: (value) {
  270. if (!_isConnected) {
  271. setState(() {
  272. _selectedPort = value;
  273. });
  274. }
  275. },
  276. hint: const Text('选择USB'),
  277. isExpanded: true,
  278. ),
  279. ),
  280. const SizedBox(width: 20),
  281. SizedBox(
  282. height: 30,
  283. child: ElevatedButton(
  284. onPressed: _loadUsbPorts,
  285. child: const Text('刷新'),
  286. ),
  287. ),
  288. const SizedBox(width: 20),
  289. if (_selectedPort != null) ...[
  290. SizedBox(
  291. height: 30,
  292. child: ElevatedButton(
  293. onPressed: _connectUsbPort,
  294. child: Text(_isConnected ? '关闭连接' : '打开连接'),
  295. ),
  296. ),
  297. ],
  298. const SizedBox(width: 20),
  299. if (_isConnected) ...[
  300. SizedBox(
  301. height: 30,
  302. child: ElevatedButton(
  303. onPressed: _isReading ? _stopRead : _startRead,
  304. child: Text(_isReading ? '停止读取' : '开始读取'),
  305. ),
  306. ),
  307. ],
  308. ],
  309. ),
  310. if (_isConnected) ...[
  311. const SizedBox(height: 10),
  312. Row(
  313. children: [
  314. const Text(
  315. '设备信息: ',
  316. style: TextStyle(
  317. fontSize: 16,
  318. fontWeight: FontWeight.bold,
  319. ),
  320. ),
  321. Expanded(
  322. child: _deviceSN.isNotEmpty
  323. ? Row(
  324. children: [
  325. const Text(
  326. 'SN: ',
  327. style: TextStyle(
  328. fontWeight: FontWeight.bold,
  329. ),
  330. ),
  331. Text(
  332. _deviceSN,
  333. style: const TextStyle(
  334. fontWeight: FontWeight.w500,
  335. ),
  336. ),
  337. const SizedBox(width: 16),
  338. const Text(
  339. 'SoftVer: ',
  340. style: TextStyle(
  341. fontWeight: FontWeight.bold,
  342. ),
  343. ),
  344. Text(
  345. _softVersion,
  346. style: const TextStyle(
  347. fontWeight: FontWeight.w500,
  348. ),
  349. ),
  350. const SizedBox(width: 16),
  351. const Text(
  352. 'HardVer: ',
  353. style: TextStyle(
  354. fontWeight: FontWeight.bold,
  355. ),
  356. ),
  357. Text(
  358. _hardVersion,
  359. style: const TextStyle(
  360. fontWeight: FontWeight.w500,
  361. ),
  362. ),
  363. ],
  364. )
  365. : const Text(
  366. '未连接',
  367. style: TextStyle(
  368. fontWeight: FontWeight.w500,
  369. ),
  370. ),
  371. ),
  372. const SizedBox(width: 20),
  373. SizedBox(
  374. height: 30,
  375. child: ElevatedButton(
  376. onPressed: _autoQueryDeviceInfo,
  377. child: const Text('刷新设备信息'),
  378. ),
  379. ),
  380. ],
  381. ),
  382. const SizedBox(height: 10),
  383. // 功率设置卡片
  384. Row(
  385. children: [
  386. const Text(
  387. '当前功率: ',
  388. style: TextStyle(
  389. fontSize: 14,
  390. fontWeight: FontWeight.bold,
  391. ),
  392. ),
  393. const SizedBox(width: 10),
  394. Text(
  395. _currentPower != null
  396. ? '${_currentPower}dBm'
  397. : '未查询',
  398. style: const TextStyle(
  399. fontSize: 14,
  400. fontWeight: FontWeight.w500,
  401. ),
  402. ),
  403. const SizedBox(width: 30),
  404. SizedBox(
  405. height: 30,
  406. child: ElevatedButton(
  407. onPressed: _queryPower,
  408. child: const Text('查询功率'),
  409. ),
  410. ),
  411. const SizedBox(width: 50),
  412. const Text(
  413. '功率设置: ',
  414. style: TextStyle(
  415. fontSize: 16,
  416. fontWeight: FontWeight.bold,
  417. ),
  418. ),
  419. const SizedBox(width: 10),
  420. Expanded(
  421. child: DropdownButton<int>(
  422. value: _selectedPower,
  423. items:
  424. List.generate(
  425. 26,
  426. (index) => index + 5,
  427. ) // 5-30的值
  428. .map((value) {
  429. return DropdownMenuItem(
  430. value: value,
  431. child: Text('${value}dBm'),
  432. );
  433. })
  434. .toList(),
  435. onChanged: (value) {
  436. if (value != null && _isConnected) {
  437. setState(() {
  438. _selectedPower = value;
  439. });
  440. }
  441. },
  442. isExpanded: true,
  443. ),
  444. ),
  445. const SizedBox(width: 20),
  446. SizedBox(
  447. height: 30,
  448. child: ElevatedButton(
  449. onPressed: _setPower,
  450. child: const Text('设置功率'),
  451. ),
  452. ),
  453. ],
  454. ),
  455. const SizedBox(height: 5),
  456. ],
  457. ],
  458. ),
  459. ),
  460. ),
  461. const SizedBox(height: 20),
  462. Card(
  463. child: Padding(
  464. padding: const EdgeInsets.all(16.0),
  465. child: Column(
  466. crossAxisAlignment: CrossAxisAlignment.start,
  467. children: [
  468. const Text(
  469. '数据日志',
  470. style: TextStyle(
  471. fontSize: 16,
  472. fontWeight: FontWeight.bold,
  473. ),
  474. ),
  475. const SizedBox(height: 10),
  476. Row(
  477. children: [
  478. ElevatedButton(
  479. onPressed: _clearLogs,
  480. child: const Text('清空日志'),
  481. ),
  482. const SizedBox(width: 10),
  483. Text(
  484. '日志数量: ${_receivedData.length}',
  485. style: const TextStyle(fontWeight: FontWeight.bold),
  486. ),
  487. ],
  488. ),
  489. const SizedBox(height: 10),
  490. Container(
  491. height: 250,
  492. decoration: BoxDecoration(
  493. border: Border.all(color: Colors.grey),
  494. borderRadius: BorderRadius.circular(4),
  495. ),
  496. child: _receivedData.isEmpty
  497. ? const Center(child: Text('暂无日志数据'))
  498. : ListView.builder(
  499. itemCount: _receivedData.length,
  500. itemBuilder: (context, index) {
  501. return Padding(
  502. padding: const EdgeInsets.all(8.0),
  503. child: Text(
  504. _receivedData[_receivedData.length -
  505. 1 -
  506. index],
  507. style: const TextStyle(fontSize: 12),
  508. ),
  509. );
  510. },
  511. ),
  512. ),
  513. ],
  514. ),
  515. ),
  516. ),
  517. ],
  518. ),
  519. ),
  520. ),
  521. );
  522. }
  523. @override
  524. void dispose() {
  525. // 在页面销毁时断开设备连接
  526. if (_isConnected) {
  527. _readerManager.disconnect();
  528. }
  529. _readerManager.clearCallbacks();
  530. _readerManager.dispose();
  531. _dataController.dispose();
  532. super.dispose();
  533. }
  534. }