This commit is contained in:
sunbeam 2025-04-30 17:01:25 +08:00
parent 2ddf1bbad5
commit 3bde6954a1
7 changed files with 326 additions and 18 deletions

View File

@ -26,10 +26,10 @@ flutter build windows
## 二、功能 ## 二、功能
1. 展示 1. 展示
1.1 座椅调节 1. 座椅调节
1.2 座椅按摩 1. 座椅按摩
1.3 座椅加热 1. 座椅加热
1.4 座椅通风 1. 座椅通风
2. 报文 2. 报文
3. 日志 3. 日志
4. 设置 4. 设置

View File

@ -1,3 +1,5 @@
import 'can_fd_msg.dart';
//CAN设备状态抽象类 //CAN设备状态抽象类
class CANDev { class CANDev {
//CAN设备名称 //CAN设备名称
@ -15,6 +17,8 @@ class CANDev {
// //
int devhandle=0; int devhandle=0;
int errCounter=0;
CANDev({ CANDev({
required this.devname, required this.devname,
required this.devchannel, required this.devchannel,
@ -43,14 +47,14 @@ abstract class AbstractCANDevice {
List<CANDev> getDeviceList(); List<CANDev> getDeviceList();
// CAN设备 // CAN设备
bool start(); bool start({required int canIndex,required int canChannel , required int baudrate});
// CAN设备 // CAN设备
bool stop(); bool stop({required int canIndex});
// CAN消息 // CAN消息
Future<bool> sendMessage(List<int> data); Future<bool> sendMessage({required int canIndex, required int canChannel, required int canid,required List<int> data});
// CAN消息 // CAN消息
Stream<List<int>> receiveMessage(); Stream<CanFdMsg> receiveMessage({required int canIndex, required int canChannel});
} }

View File

@ -0,0 +1,24 @@
import 'dart:ffi';
final class CanFdMsg extends Struct {
@Uint32()
external int id; // ID,bit[30]-RTR,bit[31]-IDE,bit[28..0]-ID
@Uint8()
external int dlc; // -0,1,2,3,4,5,6,7,8,12,16,20,24,32,48,64
@Uint8()
external int flags; // bit[0]-BRS,bit[1]-ESI,bit[2]-FDF,bit[6..5]-Channel,bit[7]-RXD
@Uint8()
external int res0; //
@Uint8()
external int res1; //
@Uint32()
external int timeStamp; // 10us
@Array<Uint8>(64)
external Array<Uint8> data;
}

View File

@ -0,0 +1,49 @@
import 'dart:ffi';
final class CanFdInitConfig extends Struct {
/// 0-1-
@Uint8()
external int mode;
/// 0-ISO CRC,1-使ISO CRC
@Uint8()
external int isocrcEnable;
/// 0-1-
@Uint8()
external int retrySend;
/// 0-1201-120
@Uint8()
external int resEnable;
/// , =40M/NBT_BRP*(1+NBT_SEG1+NBT_SEG2)
@Uint8()
external int nbtBRP;
@Uint8()
external int nbtSEG1;
@Uint8()
external int nbtSEG2;
@Uint8()
external int nbtSJW;
/// , =40M/DBT_BRP*(1+DBT_SEG1+DBT_SEG2)
@Uint8()
external int dbtBRP;
@Uint8()
external int dbtSEG1;
@Uint8()
external int dbtSEG2;
@Uint8()
external int dbtSJW;
///
@Array<Uint8>(8)
external Array<Uint8> res0;
}

View File

@ -1,5 +1,7 @@
import 'abstract_can_device.dart'; import 'abstract_can_device.dart';
import 'device_info.dart'; import 'device_info.dart';
import 'canfd_init_config.dart';
import 'can_fd_msg.dart';
import 'dart:ffi'; import 'dart:ffi';
import 'package:ffi/ffi.dart'; import 'package:ffi/ffi.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
@ -96,27 +98,147 @@ bool initialize() {
} }
@override @override
bool start() { bool start({required int canIndex,required int canChannel , required int baudrate}) {
// //
try {
final canfdInit = _dll.lookupFunction<Int32 Function(Int32, Uint8, Pointer<CanFdInitConfig>), int Function(int, int, Pointer<CanFdInitConfig>)>('CANFD_Init');
// 使
if (_deviceList.isNotEmpty) {
var canConfig = calloc<CanFdInitConfig>(); // CANFD_INIT_CONFIG
canConfig.ref.mode = 0; //
canConfig.ref.isocrcEnable = 0; // ISO CRC
canConfig.ref.retrySend = 0; //
canConfig.ref.resEnable = 1; // 120
canConfig.ref.nbtBRP = 1; //
canConfig.ref.nbtSEG1 = 59; //
canConfig.ref.nbtSEG2 = 20; //
canConfig.ref.nbtSJW = 2; //
canConfig.ref.dbtBRP = 2; //
canConfig.ref.dbtSEG1 = 29; //
canConfig.ref.dbtSEG2 = 10; //
canConfig.ref.dbtSJW = 2; //
final result = canfdInit(_deviceList[canIndex].devhandle, canChannel, canConfig);
calloc.free(canConfig); //
if (result != 1) {
_logger.severe('CANFD_Init 初始化失败,返回值: $result');
return false;
}
//
_logger.info('CANFD_Init 初始化成功,返回值: $result');
_deviceList[canIndex].channelstatus[canChannel] = true;
_deviceList[canIndex].runstatus = true;
} else {
_logger.severe('无可用设备,无法初始化 CANFD_Init');
return false;
}
} catch (e) {
_logger.severe('调用 CANFD_Init 时发生异常: $e');
return false;
}
return true; return true;
} }
@override @override
bool stop() { bool stop({required int canIndex}) {
// try {
return true; if (_loadDll && _deviceList.isNotEmpty) {
final usbCloseDevice = _dll.lookupFunction<Bool Function(Int32), bool Function(int)>('USB_CloseDevice');
final result = usbCloseDevice(_deviceList[canIndex].devhandle);
if (!result) {
_logger.severe('USB_CloseDevice 调用失败,设备句柄: ${_deviceList[canIndex].devhandle.toRadixString(16).toUpperCase()}');
}
return result;
} else {
_logger.severe('无法调用 USB_CloseDevice可能 DLL 未加载或无可用设备。');
return false;
}
} catch (e) {
_logger.severe('调用 USB_CloseDevice 时发生异常: $e');
return false;
}
} }
@override @override
Future<bool> sendMessage(List<int> data) { Future<bool> sendMessage({required int canIndex, required int canChannel, required int canid,required List<int> data}) {
// if(_deviceList[canIndex].runstatus == false || _deviceList[canIndex].channelstatus[canChannel] == false) {
return Future.value(true); return Future.value(false);///
}
try {
final canfdSendMsg = _dll.lookupFunction<Int32 Function(Int32, Uint8, Pointer<CanFdMsg>, Uint32), int Function(int, int, Pointer<CanFdMsg>, int)>('CANFD_SendMsg');
if (_deviceList.isNotEmpty) {
final devHandle = _deviceList[canIndex].devhandle;
var canMsg = calloc<CanFdMsg>();
canMsg.ref.id = canid;
canMsg.ref.dlc = data.length.toUnsigned(8);
for (int i = 0; i < data.length; i++) {
canMsg.ref.data[i] = data[i].toUnsigned(8);
}
final result = canfdSendMsg(devHandle, canChannel, canMsg, 1);
calloc.free(canMsg);
if (result >= 0) {
//_logger.info('CANFD_SendMsg 发送成功,返回值: $result');
_deviceList[canIndex].errCounter = 0;
return Future.value(true);
} else {
/*
//
#define CANFD_SUCCESS (0) //
#define CANFD_ERR_NOT_SUPPORT (-1) //
#define CANFD_ERR_USB_WRITE_FAIL (-2) //USB写数据失败
#define CANFD_ERR_USB_READ_FAIL (-3) //USB读数据失败
#define CANFD_ERR_CMD_FAIL (-4) //
*/
_logger.severe('CANFD_SendMsg 发送失败,返回值: $result');
_deviceList[canIndex].errCounter++;
if(_deviceList[canIndex].errCounter > 5) {
_deviceList[canIndex].channelstatus[canChannel] = false;
_deviceList[canIndex].runstatus = false;
}
return Future.value(false);
}
} else {
_logger.severe('无可用设备,无法调用 CANFD_SendMsg');
return Future.value(false);
}
} catch (e) {
_logger.severe('调用 CANFD_SendMsg 时发生异常: $e');
_deviceList[canIndex].channelstatus[canChannel] = false;
_deviceList[canIndex].runstatus = false;
return Future.value(false);
}
} }
@override @override
Stream<List<int>> receiveMessage() { Stream<CanFdMsg> receiveMessage({required int canIndex, required int canChannel}) {
// return Stream.periodic(Duration(milliseconds: 10), (_) {
return Stream.empty(); if(_deviceList[canIndex].runstatus == false || _deviceList[canIndex].channelstatus[canChannel] == false) {
return null;
}
try {
final canfdGetMsg = _dll.lookupFunction<Int32 Function(Int32, Uint8, Pointer<CanFdMsg>, Int32), int Function(int, int, Pointer<CanFdMsg>, int)>('CANFD_GetMsg');
final bufferSize = 100;
final canMsgs = calloc<CanFdMsg>(bufferSize);
final result = canfdGetMsg(_deviceList[canIndex].devhandle, canChannel, canMsgs, bufferSize);
if (result > 0) {
List<CanFdMsg> messages = [];
for (int i = 0; i < result; i++) {
// 访
final msgCopy = calloc<CanFdMsg>();
msgCopy.ref = canMsgs[i];
messages.add(msgCopy.ref);
}
calloc.free(canMsgs);
return messages;
} else {
calloc.free(canMsgs);
return null;
}
} catch (e) {
_logger.severe('调用 CANFD_GetMsg 时发生异常: $e');
return null;
}
}).where((messages) => messages != null).expand((messages) => messages!);
} }

95
lib/device_managge.dart Normal file
View File

@ -0,0 +1,95 @@
import 'package:flutter/material.dart';
class DeviceManagementDialog extends StatelessWidget {
const DeviceManagementDialog({super.key});
@override
Widget build(BuildContext context) {
// CAN设备IDCAN通道和波特率的选项列表
final List<String> canDeviceIds = ['ID1', 'ID2', 'ID3'];
final List<String> canChannels = ['通道1', '通道2'];
final List<String> baudRates = ['500K', '115200', '250000'];
//
String selectedCanDeviceId = canDeviceIds[0];
String selectedCanChannel = canChannels[0];
String selectedBaudRate = baudRates[0];
return AlertDialog(
title: const Text('设备管理'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
// CAN设备ID下拉框
DropdownButton<String>(
value: selectedCanDeviceId,
onChanged: (newValue) {
if (newValue != null) {
selectedCanDeviceId = newValue;
}
},
items: canDeviceIds.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
),
// CAN通道下拉框
DropdownButton<String>(
value: selectedCanChannel,
onChanged: (newValue) {
if (newValue != null) {
selectedCanChannel = newValue;
}
},
items: canChannels.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
),
//
DropdownButton<String>(
value: selectedBaudRate,
onChanged: (newValue) {
if (newValue != null) {
selectedBaudRate = newValue;
}
},
items: baudRates.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
),
],
),
actions: <Widget>[
//
TextButton(
child: const Text('刷新'),
onPressed: () {
//
},
),
//
TextButton(
child: const Text('连接'),
onPressed: () {
//
},
),
//
TextButton(
child: const Text('关闭'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
}
}

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'Page/display_page.dart'; import 'Page/display_page.dart';
import 'Page/message_page.dart'; import 'Page/message_page.dart';
import 'Page/log_page.dart'; import 'Page/log_page.dart';
import 'device_managge.dart';
void main() { void main() {
runApp(const MyApp()); runApp(const MyApp());
@ -107,6 +108,19 @@ class _MyHomePageState extends State<MyHomePage> {
_scaffoldKey.currentState?.openDrawer(); _scaffoldKey.currentState?.openDrawer();
}, },
), ),
actions: [
IconButton(
icon: const Icon(Icons.settings),
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return const DeviceManagementDialog();
},
);
},
),
],
backgroundColor: Theme.of(context).colorScheme.inversePrimary, backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title), title: Text(widget.title),
), ),