diff --git a/packages/linux_can/example/lib/main.dart b/packages/linux_can/example/lib/main.dart index 510c3cf..7d46bb6 100644 --- a/packages/linux_can/example/lib/main.dart +++ b/packages/linux_can/example/lib/main.dart @@ -23,8 +23,15 @@ Future main() async { // Actually open the device, so we can send/receive frames. final socket = device.open(); - // Send some example CAN frame. - socket.send(CanFrame.standard(id: 0x123, data: [0x01, 0x02, 0x03, 0x04])); + if (socket.isFlexibleDataRate) { + socket.send(CanFrame.standardFd( + id: 0x123, + data: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12], + switchBitRate: true)); + } else { + // Send some example CAN frame. + socket.send(CanFrame.standard(id: 0x123, data: [0x01, 0x02, 0x03, 0x04])); + } // Read a CAN frame. This is blocking, i.e. it will wait for a frame to arrive. // diff --git a/packages/linux_can/lib/src/can_device.dart b/packages/linux_can/lib/src/can_device.dart index 7ee350c..0f5885d 100644 --- a/packages/linux_can/lib/src/can_device.dart +++ b/packages/linux_can/lib/src/can_device.dart @@ -67,9 +67,12 @@ class CanDevice { return _queryAttribute(CanInterfaceAttribute.operState).operState!; } - /// True if the network interface is up, i.e. [operationalState] is [NetInterfaceOperState.up]. - bool get isUp => operationalState == NetInterfaceOperState.up; - + /// True if the network interface is up and running. + bool get isUp => switch (operationalState) { + NetInterfaceOperState.up => true, + NetInterfaceOperState.unknown => interfaceFlags.containsAll({NetInterfaceFlag.up, NetInterfaceFlag.running}), + _ => false, + }; /// Some general statistics for this network interface. /// /// Not yet implemented. @@ -226,6 +229,7 @@ class CanDevice { /// Creates a new CanSocket for sending/receiving frames on this CAN device. CanSocket open() { + bool isFlexibleDataRate = _platformInterface.isFlexibleDataRateCapable(networkInterface.name); final fd = _platformInterface.createCanSocket(); try { _platformInterface.bind(fd, networkInterface.index); @@ -242,6 +246,7 @@ class CanDevice { platformInterface: _platformInterface, fd: fd, networkInterface: networkInterface, + isFlexibleDataRate: isFlexibleDataRate, ); } on Object { _platformInterface.close(fd); @@ -259,17 +264,19 @@ class CanSocket implements Sink { required PlatformInterface platformInterface, required int fd, required this.networkInterface, + required this.isFlexibleDataRate, }) : _fd = fd, _platformInterface = platformInterface; final PlatformInterface _platformInterface; final int _fd; final NetworkInterface networkInterface; + final bool isFlexibleDataRate; var _open = true; var _listening = false; FdHandler? _fdListener; - ffi.Pointer? _fdHandlerBuffer; + ffi.Pointer? _fdHandlerBuffer; void _checkOpen() { if (!_open) { @@ -311,6 +318,9 @@ class CanSocket implements Sink { /// happen when sending lots of frames in a short time period. If [block] is false, this will throw a [LinuxError] /// with errno [EWOULDBLOCK] (value 22) in this case. Future send(CanFrame frame, {bool block = true}) async { + if (!isFlexibleDataRate && frame is CanFdFrame) { + throw ArgumentError.value(frame, 'frame', 'CAN controller does not support CAN FD.'); + } _checkOpen(); // TODO: Do the blocking in the kernel or in a worker isolate @@ -366,7 +376,7 @@ class CanSocket implements Sink { final frames = []; while (true) { - final frame = PlatformInterface.readStatic(libc, fd, buffer, ffi.sizeOf()); + final frame = PlatformInterface.readStatic(libc, fd, buffer, ffi.sizeOf()); if (frame != null) { frames.add(frame); } else { @@ -390,7 +400,7 @@ class CanSocket implements Sink { assert(_fdListener == null); assert(_fdHandlerBuffer == null); - _fdHandlerBuffer = ffi.calloc(); + _fdHandlerBuffer = ffi.calloc(); _fdListener = await _platformInterface.eventListener.add( fd: _fd, diff --git a/packages/linux_can/lib/src/data_classes.dart b/packages/linux_can/lib/src/data_classes.dart index 73fecef..7ccc68f 100644 --- a/packages/linux_can/lib/src/data_classes.dart +++ b/packages/linux_can/lib/src/data_classes.dart @@ -438,6 +438,22 @@ sealed class CanFrame { return CanExtendedRemoteFrame(id: id); } + /// CAN FD Data frame, + /// Standard Frame Format with Flexible Data-rate (CAN FD) + factory CanFrame.standardFd( + {required int id, required List data, bool switchBitRate = false, bool errorStateIndicator = false}) { + return CanFdBaseFrame( + id: id, data: data, flags: CANFD_FDF | (switchBitRate ? CANFD_BRS : 0) | (errorStateIndicator ? CANFD_ESI : 0)); + } + + /// CAN FD Data frame, + /// Extended Frame Format with Flexible Data-rate (CAN FD) + factory CanFrame.extendedFd( + {required int id, required List data, bool switchBitRate = false, bool errorStateIndicator = false}) { + return CanFdFrameExtended( + id: id, data: data, flags: CANFD_FDF | (switchBitRate ? CANFD_BRS : 0) | (errorStateIndicator ? CANFD_ESI : 0)); + } + @override int get hashCode; @@ -458,13 +474,19 @@ sealed class CanExtendedFrame extends CanFrame { } sealed class CanDataFrame extends CanFrame { - /// 0 to 8 byte CAN frame payload. + /// 0 to 8 byte CAN frame payload, or 0 to 64 bytes CAN FD frame payload. List get data; } +sealed class CanLegacyFrame extends CanFrame {} + +sealed class CanFdFrame extends CanFrame { + int get flags; +} + sealed class CanRemoteFrame extends CanFrame {} -class CanStandardDataFrame extends CanFrame implements CanBaseFrame, CanDataFrame { +class CanStandardDataFrame extends CanFrame implements CanLegacyFrame, CanBaseFrame, CanDataFrame { const CanStandardDataFrame({required this.id, required this.data}) : assert(0 <= data.length && data.length <= 8); @override @@ -491,7 +513,7 @@ class CanStandardDataFrame extends CanFrame implements CanBaseFrame, CanDataFram String toString() => 'CanStandardDataFrame(id: $id, data: $data)'; } -class CanExtendedDataFrame extends CanFrame implements CanExtendedFrame, CanDataFrame { +class CanExtendedDataFrame extends CanFrame implements CanLegacyFrame, CanExtendedFrame, CanDataFrame { const CanExtendedDataFrame({required this.id, required this.data}) : assert(id & ~CAN_EFF_MASK == 0); @override @@ -518,7 +540,7 @@ class CanExtendedDataFrame extends CanFrame implements CanExtendedFrame, CanData String toString() => 'CanExtendedDataFrame(id: $id, data: $data)'; } -class CanStandardRemoteFrame extends CanFrame implements CanBaseFrame, CanRemoteFrame { +class CanStandardRemoteFrame extends CanFrame implements CanLegacyFrame, CanBaseFrame, CanRemoteFrame { const CanStandardRemoteFrame({required this.id}) : assert(id & ~CAN_SFF_MASK == 0); @override @@ -542,7 +564,7 @@ class CanStandardRemoteFrame extends CanFrame implements CanBaseFrame, CanRemote String toString() => 'CanStandardRemoteFrame(id: $id)'; } -class CanExtendedRemoteFrame extends CanFrame implements CanExtendedFrame, CanRemoteFrame { +class CanExtendedRemoteFrame extends CanFrame implements CanLegacyFrame, CanExtendedFrame, CanRemoteFrame { const CanExtendedRemoteFrame({required this.id}) : assert(id & ~CAN_EFF_MASK == 0); @override @@ -566,6 +588,87 @@ class CanExtendedRemoteFrame extends CanFrame implements CanExtendedFrame, CanRe String toString() => 'CanExtendedRemoteFrame(id: $id)'; } +class CanFdBaseFrame extends CanFrame implements CanBaseFrame, CanDataFrame, CanFdFrame { + const CanFdBaseFrame({required this.id, required this.data, required this.flags}) + : assert((id & ~CAN_SFF_MASK == 0) && 0 <= data.length && + data.length <= 64 && + (data.length <= 8 || + data.length == 12 || + data.length == 16 || + data.length == 20 || + data.length == 24 || + data.length == 32 || + data.length == 48 || + data.length == 64)); + + @override + final int id; + + @override + final List data; + + @override + final int flags; + + @override + final CanFrameFormat format = CanFrameFormat.base; + + @override + final CanFrameType type = CanFrameType.data; + + @override + int get hashCode => Object.hash(id, flags, data); + + @override + bool operator ==(Object other) { + return other is CanFdBaseFrame && id == other.id && flags == other.flags && listsEqual(data, other.data); + } + + @override + String toString() => 'CanFdBaseFrame(id: $id, flags: $flags, data: $data)'; +} + +class CanFdFrameExtended extends CanFrame implements CanExtendedFrame, CanDataFrame, CanFdFrame { + const CanFdFrameExtended({required this.id, required this.data, required this.flags}) + : assert(id & ~CAN_EFF_MASK == 0 && + 0 <= data.length && + data.length <= 64 && + (data.length <= 8 || + data.length == 12 || + data.length == 16 || + data.length == 20 || + data.length == 24 || + data.length == 32 || + data.length == 48 || + data.length == 64)); + + @override + final int id; + + @override + final List data; + + @override + final int flags; + + @override + final CanFrameFormat format = CanFrameFormat.extended; + + @override + final CanFrameType type = CanFrameType.data; + + @override + int get hashCode => Object.hash(id, flags, data); + + @override + bool operator ==(Object other) { + return other is CanFdFrameExtended && id == other.id && flags == other.flags && listsEqual(data, other.data); + } + + @override + String toString() => 'CanFdFrameExtended(id: $id, flags: $flags, data: $data)'; +} + /// The RFC2863 state of the network interface. /// /// See: https://docs.kernel.org/networking/operstates.html diff --git a/packages/linux_can/lib/src/platform_interface.dart b/packages/linux_can/lib/src/platform_interface.dart index ce96fca..75e2f7f 100644 --- a/packages/linux_can/lib/src/platform_interface.dart +++ b/packages/linux_can/lib/src/platform_interface.dart @@ -7,8 +7,8 @@ import 'dart:typed_data'; import 'package:_ardera_common_libc_bindings/_ardera_common_libc_bindings.dart'; import 'package:_ardera_common_libc_bindings/epoll_event_loop.dart'; import 'package:_ardera_common_libc_bindings/linux_error.dart'; -import 'package:ffi/ffi.dart' as ffi; import 'package:either_dart/either.dart'; +import 'package:ffi/ffi.dart' as ffi; import 'package:linux_can/src/can_device.dart'; import 'package:linux_can/src/data_classes.dart'; @@ -391,6 +391,24 @@ class PlatformInterface { throw LinuxError('Could not create CAN socket.', 'socket', libc.errno); } + _withBuffer((buffer, size) { + assert(size >= ffi.sizeOf()); + + final native = buffer.cast(); + native.value = 1; + + final ok = libc.setsockopt( + socket, + SOL_CAN_RAW, + CAN_RAW_FD_FRAMES, + native.cast(), + ffi.sizeOf(), + ); + if (ok < 0) { + throw LinuxError('Could not set CAN FD socket option.', 'setsockopt', libc.errno); + } + }); + _debugOpenFds.add(socket); return socket; @@ -424,7 +442,7 @@ class PlatformInterface { }); } - bool isFlexibleDatarateCapable(String interfaceName) { + bool isFlexibleDataRateCapable(String interfaceName) { return getInterfaceMTU(interfaceName) == CANFD_MTU; } @@ -477,12 +495,21 @@ class PlatformInterface { CanRemoteFrame _ => [], }; - return _withBuffer((buffer, size) { - final native = buffer.cast(); + final flags = switch (frame) { + CanFdFrame _ => frame.flags, + CanLegacyFrame _ => 0, + }; + + final mtu = switch (frame) { + CanFdFrame _ => CANFD_MTU, + CanLegacyFrame _ => CAN_MTU, + }; - // encode the native can_frame. + return _withBuffer((buffer, size) { + final native = buffer.cast(); native.ref.can_id = id; native.ref.len = data.length; + native.ref.flags = flags; for (final (index, byte) in data.indexed) { native.ref.data[index] = byte; } @@ -492,31 +519,45 @@ class PlatformInterface { // send the CAN frame. final ok = _retry( - () => libc.write(fd, native.cast(), ffi.sizeOf()), + () => libc.write(fd, native.cast(), mtu), retryErrorCodes: {EINTR, if (block) EAGAIN}, ); if (ok < 0) { throw LinuxError('Couldn\'t write CAN frame to socket.', 'write', libc.errno); - } else if (ok != CAN_MTU) { + } else if (ok != mtu) { throw LinuxError('Incomplete write.', 'write'); } }); } - static CanFrameOrError canFrameFromNative(can_frame native) { - final eff = native.can_id & CAN_EFF_FLAG != 0; - final rtr = native.can_id & CAN_RTR_FLAG != 0; - final err = native.can_id & CAN_ERR_FLAG != 0; + static CanFrameOrError canFrameFromNative(ffi.Pointer native, {required bool isCanFd}) { + final frame = native.ref; + + final canId = frame.can_id; + final eff = canId & CAN_EFF_FLAG != 0; + final rtr = canId & CAN_RTR_FLAG != 0; + final err = canId & CAN_ERR_FLAG != 0; + + final canFd = isCanFd && frame.flags & CANFD_FDF != 0; + + if (!canFd && frame.len > CAN_MAX_DLEN) { + throw LinuxError( + 'Malformed CAN frame. Expected CAN frame length to be at most $CAN_MAX_DLEN, but was: ${frame.len}', 'read'); + } else if (frame.len > CANFD_MAX_DLEN) { + throw LinuxError( + 'Malformed CAN FD frame. Expected CAN FD frame length to be at most $CANFD_MAX_DLEN, but was: ${frame.len}', + 'read'); + } final data = List.generate( - native.len, - (index) => native.data[index], + frame.len, + (index) => frame.data[index], growable: false, ); Iterable canControllerErrors() { return [ - if (native.can_id & CAN_ERR_CRTL != 0) + if (canId & CAN_ERR_CRTL != 0) for (final controllerError in CanControllerError.values) if (data[1] & controllerError.controllerErrorNative != 0) controllerError, ]; @@ -524,7 +565,7 @@ class PlatformInterface { Iterable protocolErrors() { return [ - if (native.can_id & CAN_ERR_PROT != 0) + if (canId & CAN_ERR_PROT != 0) for (final protocolError in CanProtocolError.values) if (data[2] & protocolError.protocolErrorNative != 0) protocolError, ]; @@ -558,59 +599,52 @@ class PlatformInterface { if (err) { final errors = [ - if (native.can_id & CAN_ERR_TX_TIMEOUT != 0) CanError.txTimeout, - if (native.can_id & CAN_ERR_LOSTARB != 0) CanArbitrationLostError(data[0] == 0 ? null : data[0]), + if (canId & CAN_ERR_TX_TIMEOUT != 0) CanError.txTimeout, + if (canId & CAN_ERR_LOSTARB != 0) CanArbitrationLostError(data[0] == 0 ? null : data[0]), ...canControllerErrors(), ...protocolErrors(), ...wiringErrors(), - if (native.can_id & CAN_ERR_ACK != 0) CanError.noAck, - if (native.can_id & CAN_ERR_BUSOFF != 0) CanError.busOff, - if (native.can_id & CAN_ERR_BUSERROR != 0) CanError.busError, - if (native.can_id & CAN_ERR_RESTARTED != 0) CanError.restarted, + if (canId & CAN_ERR_ACK != 0) CanError.noAck, + if (canId & CAN_ERR_BUSOFF != 0) CanError.busOff, + if (canId & CAN_ERR_BUSERROR != 0) CanError.busError, + if (canId & CAN_ERR_RESTARTED != 0) CanError.restarted, ]; return Left(errors); } else if (eff) { - final id = native.can_id & CAN_EFF_MASK; - + final id = canId & CAN_EFF_MASK; if (rtr) { return Right(CanFrame.extendedRemote(id: id)); } else { - return Right(CanFrame.extended( - id: id, - data: List.generate( - native.len, - (index) => native.data[index], - growable: false, - ), - )); + if (canFd) { + return Right(CanFdFrameExtended(id: id, data: data, flags: frame.flags)); + } else { + return Right(CanFrame.extended(id: id, data: data)); + } } } else { - final id = native.can_id & CAN_SFF_MASK; - + final id = canId & CAN_SFF_MASK; if (rtr) { return Right(CanFrame.standardRemote(id: id)); } else { - return Right(CanFrame.standard( - id: id, - data: List.generate( - native.len, - (index) => native.data[index], - growable: false, - ), - )); + if (canFd) { + return Right(CanFdBaseFrame(id: id, data: data, flags: frame.flags)); + } else { + return Right(CanFrame.standard(id: id, data: data)); + } } } } static CanFrameOrError? readStatic(LibC libc, int fd, ffi.Pointer buffer, int bufferSize) { - assert(bufferSize >= ffi.sizeOf()); + assert(bufferSize >= ffi.sizeOf()); assert(CAN_MTU == ffi.sizeOf()); + assert(CANFD_MTU == ffi.sizeOf()); - final native = buffer.cast(); - _memset(native, 0, ffi.sizeOf()); + final native = buffer.cast(); + _memset(native, 0, bufferSize); - final ok = libc.read(fd, buffer, ffi.sizeOf()); + final ok = libc.read(fd, buffer, ffi.sizeOf()); if (ok < 0 && libc.errno == EAGAIN) { // no frame available right now. return null; @@ -618,11 +652,12 @@ class PlatformInterface { throw LinuxError('Could not read CAN frame.', 'read', libc.errno); } - if (ok != CAN_MTU) { - throw LinuxError('Malformed CAN frame. Expected received frame to be $CAN_MTU bytes large, was: $ok.', 'read'); + if (ok != CANFD_MTU && ok != CAN_MTU) { + throw LinuxError( + 'Malformed CAN frame. Expected received frame to be $CANFD_MTU or $CAN_MTU bytes large, was: $ok.', 'read'); } - return canFrameFromNative(native.ref); + return canFrameFromNative(native, isCanFd: ok == CANFD_MTU); } CanFrameOrError? read(int fd) {