C

Qt Quick Ultralite Automotive Cluster Demo

/****************************************************************************** ** ** Copyright (C) 2019 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Quick Ultralite module. ** ** $QT_BEGIN_LICENSE:COMM$ ** ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see http://www.qt.io/terms-conditions. For further ** information use the contact form at http://www.qt.io/contact-us. ** ** $QT_END_LICENSE$ ** ******************************************************************************/
#include <canbusdevice.h> #include <etl/unordered_multimap.h> #include <QCanBus> #include <QCanBusDevice> #include <QCanBusFrame> #include <QVariant> #include <QQueue> #include <cstdlib> #undef emit namespace { const QString DEFAULT_CAN_PLUGIN = "virtualcan"; const QString DEFAULT_CAN_INTERFACE = "can0"; struct RawHandlerCallback { CanBus::CanBusDevice::RawCanMessageHandler callback; void *userData; }; QQueue<QCanBusFrame> messageQueue; QList<QCanBusDevice::Filter> filterList; etl::unordered_multimap<quint32, RawHandlerCallback, CANBUS_MAX_RAW_HANDLERS> rawHandlers; RawHandlerCallback globalHandler = {NULL, NULL}; QCanBusDevice *canBusDevice = NULL; void handleCanFrame(const QCanBusFrame &frame) { const QByteArray framePayload = frame.payload(); const quint8 *payload = reinterpret_cast<const quint8 *>(framePayload.data()); auto its = rawHandlers.equal_range(frame.frameId()); for (auto it = its.first; it != its.second; ++it) { const RawHandlerCallback &callback = it->second; callback.callback(frame.frameId(), static_cast<CanBus::CanBusDevice::CanFrameType>(frame.frameType()), payload, framePayload.length(), callback.userData); } if (globalHandler.callback) { globalHandler.callback(frame.frameId(), static_cast<CanBus::CanBusDevice::CanFrameType>(frame.frameType()), payload, framePayload.length(), globalHandler.userData); } } } // namespace namespace CanBus { /*! \class CanBus::CanBusDevice \preliminary \brief The CanBusDevice class is the interface class for CAN bus communication. This class is singleton and the instance can be obtained by call to \c CanBus::CanBusDevice::instance() \code #include <canbusdevice.h> ... CanBus::CanBusDevice &canBusDevice = CanBus::CanBusDevice::instance(); ... \endcode \note On desktop platforms the implementation is using QCanBusDevice class as the CAN backend, therefore it is required to configure can plugin/interface that shall be used. This can be done by setting two environment variables \c QT_CANBUS_PLUGIN and \c QT_CANBUS_INTERFACE. The default values are as follows: \c virtualcan and \c can0. */ /*! \variable CanBus::CanBusDevice::status \brief Property holding the current status of the device. \sa connect() disconnect() CanBusDevice::CanBusDeviceState */ /*! \variable CanBus::CanBusDevice::bitrate \brief Property used to configure device CAN bitrate in bits per second With CAN FD the payload can be transmitted at a higher data bitrate, if \l CanBusDevice::sendRawMessage is invoked with \c bitrateSwitch parameter set to \c true. In this case, \c CanBusDevice::bitrate is only used for the CAN ID arbitration phase. This property will be only applied if set before call to \l CanBusDevice::connect \sa CanBusDevice::dataBitrate CanBusDevice::isCanFD */ /*! \variable CanBus::CanBusDevice::isCanFD \brief Property used to configure if device can handle CAN FD frames This property will be only applied if set before call to \l CanBusDevice::connect \sa CanBusDevice::bitrate CanBusDevice::dataBitrate */ /*! \variable CanBus::CanBusDevice::dataBitrate \brief Property used to configure the CAN FD payload bitrate in bits per second CAN FD allows to transmit the payload of frames at a higher data bitrate if \l CanBusDevice::sendRawMessage is invoked with \c bitrateSwitch parameter set to \c true This property will be only applied if set before call to \l CanBusDevice::connect \sa CanBusDevice::bitrate CanBusDevice::isCanFD */ /*! \enum CanBusDevice::CanBusDeviceError This enum describes all the possible error conditions. \value NoError No errors have occurred. \value ReadError An error occurred during a read operation. \value WriteError An error occurred during a write operation. \value ConnectionError An error occurred when attempting to connect to CAN Bus. \value ConfigurationError An error occurred when attempting to set a configuration parameter. \value UnknownError An unknown error occurred. */ /*! \enum CanBusDevice::CanBusDeviceState This enum describes all possible device states. \value UnconnectedState The device is disconnected. \value ConnectingState The device is being connected. \value ConnectedState The device is connected to the CAN bus. \value DisconnectingState The device is being closed. */ /*! \enum CanBusDevice::CanFrameType This enum describes the type of the CAN frame. \value UnknownFrame The frame type is unknown. \value DataFrame This value represents a data frame. \value ErrorFrame This value represents an error frame. \value RemoteRequestFrame This value represents a remote request. \value InvalidFrame This value represents an invalid frame. This type is used for error reporting. */ /*! \enum CanBusDevice::FormatFilter This enum describes the format pattern, which is used to filter incoming CAN bus frames. \value MatchBaseFormat The CAN bus frame must use the base frame format (11 bit identifier). \value MatchExtendedFormat The CAN bus frame must use the extended frame format (29 bit identifier). \value MatchBaseAndExtendedFormat The CAN bus frame can have a base or an extended frame format. */ /*! \typedef CanBusDevice::RawCanMessageHandler \brief Callback function of type RawCanMessageHandler \sa registerGlobalMessageHandler() registerRawMessageHandler() */ CanBusDevice::CanBusDevice() { isCanFD.setValue(false); } /*! Connects the device to the CAN bus. This asynchronous function modifies \l CanBusDevice::status property setting it to \l CanBusDevice::ConnectingState. Upon success the instance's \l CanBusDevice::status property is set to \l CanBusDevice::ConnectedState; otherwise \l CanBusDevice::UnconnectedState. Before connection attempt the configuration phase takes place based on settings for \l CanBusDevice::bitrate \l CanBusDevice::dataBitrate \l CanBusDevice::isCanFD properties and apply all filters added by calls to \l CanBusDevice::appendMessageFilter In case if some of those settings are not supported by unedrlying physical device \l CanBusDevice::error signal will be emited with value of \l CanBusDevice::ConfigurationError */ void CanBusDevice::connect() { if (!canBusDevice) { QString errorString; QString canBusPlugin; QString canBusInterface; QUL_GETENV_OR_DEFAULT("QT_CANBUS_PLUGIN", canBusPlugin, DEFAULT_CAN_PLUGIN); QUL_GETENV_OR_DEFAULT("QT_CANBUS_INTERFACE", canBusInterface, DEFAULT_CAN_INTERFACE); canBusDevice = QCanBus::instance()->createDevice(canBusPlugin, canBusInterface, &errorString); if (!canBusDevice) { error(ConfigurationError); std::printf("[CANBus] Error creating device: %s\n", errorString.toLatin1().data()); return; } else { std::printf("[CANBus] Device created! %s - %s\n", canBusPlugin.toLatin1().data(), canBusInterface.toLatin1().data()); } QObject::connect(canBusDevice, &QCanBusDevice::stateChanged, [this](QCanBusDevice::CanBusDeviceState state) { status.setValue(static_cast<CanBusDeviceState>(state)); }); QObject::connect(canBusDevice, &QCanBusDevice::errorOccurred, [this](QCanBusDevice::CanBusError err) { std::printf("[CANBus] Device error: %s\n", canBusDevice->errorString().toLatin1().data()); error(static_cast<CanBusDeviceError>(err)); }); QObject::connect(canBusDevice, &QCanBusDevice::framesReceived, [this]() { if (!canBusDevice) return; while (canBusDevice->framesAvailable()) { const QCanBusFrame frame = canBusDevice->readFrame(); messageQueue.enqueue(frame); } }); if (filterList.size()) { // Seems it this functionality is not supported by neither peakusb nor virtualcan plugins // it will result in rising configuration error - it is however supported on socketcan plugin canBusDevice->setConfigurationParameter(QCanBusDevice::RawFilterKey, QVariant::fromValue(filterList)); } if (isCanFD.value()) { canBusDevice->setConfigurationParameter(QCanBusDevice::CanFdKey, true); if (dataBitrate.wasExplicitlySet()) { canBusDevice->setConfigurationParameter(QCanBusDevice::DataBitRateKey, dataBitrate.value()); } } if (bitrate.wasExplicitlySet()) { canBusDevice->setConfigurationParameter(QCanBusDevice::BitRateKey, bitrate.value()); } } if (canBusDevice && canBusDevice->state() == QCanBusDevice::UnconnectedState) { if (!canBusDevice->connectDevice()) { std::printf("[CANBus] Unable to connect!\n"); } } } /*! Disconnects the device from the CAN bus. This asynchronous function modifies \l CanBusDevice::status property by setting it to \l CanBusDevice::UnconnectedState on connection termination. If supported by underlying device before connection will be terminated \l CanBusDevice::status can be set to \l CanBusDevice::DisconnectingState */ void CanBusDevice::disconnect() { if (canBusDevice) { canBusDevice->disconnectDevice(); delete canBusDevice; canBusDevice = nullptr; } } /*! Writes frame with frameId set to \a msgId of \a type type to the CAN bus. The frame \a payload needs to point to valid memory buffer of length \a payloadLength. If the CAN frame shall use a 29bit identifier \a isExtended shall be set to \c true otherwise \c false, implying an 11bit identifier. Setting \a isCanFD to \c true will make this CAN frame to use Flexible Data-Rate which allows up to 64 data bytes, otherwise \c false will imply at most 8 byte of payload. If \a isCanFD is set \c true, seting \a bitrateSwitch flag to \c true will result in transfering this frame at a higher data bitrate as configured by \l CanBusDevice::dataBitrate As per CAN bus specification, frames of type \l {CanBusDevice::RemoteRequestFrame} {remote transfer request (RTR)} do not have a payload, but a length from 0 to 8 (including). This length indicates the expected response payload length from the remote party. Therefore when sending a RTR frame using this function it may still be required to set an arbitrary payload on frame. The length of the arbitrary payload is what is set as size expectation for the RTR frame. The code shows sample usage. \code #include <canbusdevice.h> #include <qul/private/global.h> using namespace CanBus; ... void DataSender::send() { quint32 frameId = 0x123; quint8 payload[2] = {0}; payload[0] = 0x18; payload[1] = 0xA | 0xD << 8; CanBusDevice &canBusDevice = CanBusDevice::instance(); canBusDevice.sendRawMessage(frameId, CanBusDevice::DataFrame, payload, sizeof(payload)); } ... \endcode */ void CanBusDevice::sendRawMessage(quint32 msgId, CanFrameType type, const quint8 *payload, quint8 payloadLength, bool isExtended, bool isCanFD, bool bitrateSwitch) { if (!canBusDevice) return; const QByteArray msgPayload = QByteArray(reinterpret_cast<const char *>(payload), payloadLength); QCanBusFrame frame = QCanBusFrame(msgId, msgPayload); frame.setExtendedFrameFormat(isExtended); frame.setFlexibleDataRateFormat(isCanFD); frame.setBitrateSwitch(bitrateSwitch); frame.setFrameType(static_cast<QCanBusFrame::FrameType>(type)); canBusDevice->writeFrame(frame); } /*! This method allows to limit the type of frames that the current device accepts. If a received CAN frame matches at least one of the filters in the list, the CanBusDevice will accept it. \a frameId is used to filter incomming frames in conjuction with \a frameIdMask. The matching is successful if the following evaluates to \c true: \code (receivedFrameId & frameIdMask) == (frameId & frameIdMask) \endcode By default \a frameId and \a frameIdMask are set to \c 0x0. Any CAN bus frame type can be matched by setting \a type to \l CanBusDevice::InvalidFrame. The filter object is invalid if type is equal to \l CanBusDevice::UnknownFrame. By default \a type is set to \l CanBusDevice::InvalidFrame. Another way of matching is possible by setting \a format. By default it is set to \l CanBusDevice::MatchBaseAndExtendedFormat. */ void CanBusDevice::appendMessageFilter(quint32 frameId, quint32 frameIdMask, FormatFilter format, CanFrameType type) { QCanBusDevice::Filter filter; filter.frameId = frameId; filter.frameIdMask = frameIdMask; filter.format = static_cast<QCanBusDevice::Filter::FormatFilter>(format); filter.type = static_cast<QCanBusFrame::FrameType>(type); filterList.append(filter); } /*! This method is used to register message hanldler for concrete CAN Frames Whenever frame with \a msgId is found in incoming message queue during \l processPendingData() \a handler will be invoked with retrieved frame payload and \a userData passed during registration Returns \c true if handler was succesfully added, otherwise \c false if there is no more space for handlers \code #include <canbusdevice.h> #include <qul/private/global.h> using namespace Qul; using namespace CanBus; void msgHandler(Qul::quint32 msgId, CanBusDevice::CanFrameType type, const quint8* payload, quint8 payloadLength, void* userData) { DataListener* handler = reinterpret_cast<DataListener*>(userData); if (handler != NULL && type == CanBusDevice::DataFrame && payloadLength == 2) { static const quint8 VAL2_MASK = 0x0F; static const quint8 VAL3_MASK = 0xF0; quint8 val1 = payload[0]; quint8 val2 = payload[1] & VAL2_MASK; quint8 val3 = (payload[1] & VAL3_MASK) >> 8; } } DataListener::DataListener() { ... CanBusDevice canBusDevice = CanBusDevice::instance(); canBusDevice.registerRawMessageHandler(0x123, msgHandler, this); } \endcode \sa CanBusDevice::RawCanMessageHandler registerGlobalMessageHandler() */ bool CanBusDevice::registerRawMessageHandler(quint32 msgId, RawCanMessageHandler handler, void *userData) { if (!rawHandlers.full()) { rawHandlers.insert(std::pair<quint32, RawHandlerCallback>(msgId, {handler, userData})); return true; } return false; } /*! This method is used to register global message hanldler that will be invoked for every retrieved CAN Frame Whenever frame is found in incoming message queue during \l processPendingData() \a handler will be invoked with retrieved frame payload and \a userData passed during registration Returns \c true if handler was succesfully added, otherwise \c false if other global handler is already registered \note There can be only one global message handler \code #include <qul/canbusdevice.h> #include <qul/private/global.h> using namespace Qul; using namespace CanBus; void globalMsgHandler(Qul::quint32 msgId, CanBusDevice::CanFrameType type, const quint8* payload, quint8 payloadLength, void* userData) { DataListener* handler = reinterpret_cast<DataListener*>(userData); if (handler != NULL && msgId = 0x123 && type == CanBusDevice::DataFrame && payloadLength == 2) { static const quint8 VAL2_MASK = 0x0F; static const quint8 VAL3_MASK = 0xF0; quint8 val1 = payload[0]; quint8 val2 = payload[1] & VAL2_MASK; quint8 val3 = (payload[1] & VAL3_MASK) >> 8; } else if(handler != NULL && msgId = 0x124 && type == CanBusDevice::RemoteRequestFrame ) { ... } } DataListener::DataListener() { ... CanBusDevice &canBusDevice = CanBusDevice::instance(); canBusDevice.registerGlobalMessageHandler(globalMsgHandler, this); } \endcode \sa CanBusDevice::RawCanMessageHandler registerGlobalMessageHandler() */ bool CanBusDevice::registerGlobalMessageHandler(RawCanMessageHandler handler, void *userData) { // register only first global handler if (globalHandler.callback == NULL) { globalHandler.callback = handler; globalHandler.userData = userData; return true; } return false; } /*! This method is used by application to invoke handling of queued incoming CAN frames If any handler was registered with \l registerRawMessageHandler() for queued frame it will be invoked. If any global handler was registered with \l registerGlobalMessageHandler() it will be invoked for every frame in the queue. */ void CanBusDevice::processPendingData() { while (!messageQueue.isEmpty()) { QCanBusFrame frame = messageQueue.dequeue(); handleCanFrame(frame); } } } // namespace CanBus