Reference App
The reference application project provides developers with example source code of how to program with Qt and display live data from the CAN bus on the screen. Contained in the MRS Virtual Machine is a CAN simulation application that allows you to adjust sliders which cause the reference app gauges to update to the simulated CAN messages.
Reference App Code
Section titled “Reference App Code”#include <QGuiApplication>#include <QQmlApplicationEngine>#include <QFontDatabase>#include <QStyleHints>#include <QQmlContext>#include <QSharedPointer>#include "CanBus.hpp"#include "Misc.hpp"#include "Vehicle.hpp"#include "ColorPalette.hpp"#include "Application.hpp"#include "MaterialIcons.h"
int main(int argc, char *argv[]){ QCoreApplication::setOrganizationName("MRS Electronic, Inc."); QCoreApplication::setOrganizationDomain("mrs-electronics.com"); QCoreApplication::setApplicationName("neuralplex-demo"); QCoreApplication::setApplicationVersion(VERSION);
QGuiApplication app(argc, argv); ColorPalette* appColors = new ColorPalette();
qmlRegisterUncreatableMetaObject(MiscEnums::staticMetaObject, "com.mrs.MiscEnums", 1, 0, "MiscEnums", "Cannot create C++ class \"MiscEnums::staticMetaObject\" in QML");
// Set the default drag distance QGuiApplication::styleHints()->setStartDragDistance(24);
// Setup the application fonts QString fontFamily; int id = QFontDatabase::addApplicationFont(":/TypeFace/Montserrat-Regular.ttf"); QFontDatabase::addApplicationFont(":/TypeFace/DejaVuSans.ttf"); QFontDatabase::addApplicationFont(":/TypeFace/DSEG7Modern-BoldItalic.ttf"); QFontDatabase::addApplicationFont(":/TypeFace/materialdesignicons-webfont.ttf"); QFontDatabase::addApplicationFont(":/TypeFace/HelveticaNeue-Light.ttf"); fontFamily = QFontDatabase::applicationFontFamilies(id).at(0); QFont font(fontFamily); QGuiApplication::setFont(font);
// Create instance of main classes QSharedPointer<CanBus> canBusPointer = QSharedPointer<CanBus>::create(appSettings.value("canbus0", "CAN0")); Application* application = new Application(); Vehicle* vehicle = new Vehicle(canBusPointer);
// Setup the context properties so the front end can access the back end QQmlApplicationEngine engine; engine.rootContext()->setContextProperty("MaterialIcons", new MaterialIcons()); engine.rootContext()->setContextObject(application); engine.rootContext()->setContextProperty("colors", appColors); engine.rootContext()->setContextProperty("vehicle", vehicle);
const QUrl url(QStringLiteral("qrc:/qml/Main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, [url](QObject *obj, const QUrl &objUrl) { if (!obj && url == objUrl) QCoreApplication::exit(-1); }, Qt::QueuedConnection);
engine.load(url);
return app.exec();}
#pragma once
#include <QObject>#include <QtMath>#include <QSharedPointer>#include <QDebug>#include "CanBus.hpp"#include "CanEncodeDecode.h"
class Vehicle : public QObject{ Q_OBJECT
Q_PROPERTY(float speedometer READ speedometer WRITE setSpeedometer NOTIFY speedometerChanged) Q_PROPERTY(float fuelLevel READ fuelLevel WRITE setFuelLevel NOTIFY fuelLevelChanged) Q_PROPERTY(int ignition READ ignition WRITE setIgnition NOTIFY ignitionChanged) Q_PROPERTY(float sensor1 READ sensor1 WRITE setSensor1 NOTIFY sensor1Changed) Q_PROPERTY(float sensor2 READ sensor2 WRITE setSensor2 NOTIFY sensor2Changed) Q_PROPERTY(float sensor3 READ sensor3 WRITE setSensor3 NOTIFY sensor3Changed) Q_PROPERTY(float sensor4 READ sensor4 WRITE setSensor4 NOTIFY sensor4Changed)
public: explicit Vehicle(QSharedPointer<CanBus> canBus, QObject *parent = nullptr);
// Returns a shared pointer to the can bus object. QSharedPointer<CanBus> canBus() const;
float speedometer() const; float fuelLevel() const; float sensor1() const; float sensor2() const; float sensor3() const; float sensor4() const; int ignition() const;
public slots: void processMessage(const QCanBusFrame &frame);
void setSpeedometer(float speedometer); void setFuelLevel(float fuelLevel); void setSensor1(float sensor1); void setSensor2(float sensor2); void setSensor3(float sensor3); void setSensor4(float sensor4); void setIgnition(int ignition);
signals: void speedometerChanged(float speedometer); void fuelLevelChanged(float fuelLevel); void sensor1Changed(float sensor1); void sensor2Changed(float sensor2); void sensor3Changed(float sensor3); void sensor4Changed(float sensor4); void ignitionChanged(int ignition);
private: QSharedPointer<CanBus> m_canBus; float m_speedometer; float m_fuelLevel; float m_sensor1; float m_sensor2; float m_sensor3; float m_sensor4; int m_ignition;};
// Define the Vehicle CAN IDs#define DD1 0x18FEFC00 // Dash Display 1#define CCVS1 0x18FEF100 // Vehicle speed 1#define SENSOR 0x18FF0000 // Sensor Deviation
#include "Vehicle.hpp"
Vehicle::Vehicle(QSharedPointer<CanBus> canBus, QObject *parent) : QObject(parent) , m_canBus(canBus) , m_speedometer(0) , m_fuelLevel(0) , m_sensor1(0) , m_sensor2(0) , m_sensor3(0) , m_sensor4(0){ m_canBus->setVehicleIds({ DD1, CCVS1, SENSOR }); connect(m_canBus.data(), &CanBus::vehicleFrameArrived, this, &Vehicle::processMessage);}
void Vehicle::processMessage(const QCanBusFrame &frame){ const auto frameId = frame.frameId(); const auto *payload = (uint8_t*)(frame.payload().constData());
// Begin the switch statement for processing each CAN frame switch (frameId) { case DD1: { setFuelLevel(decode(payload, 8, 8, false, false, 0.4, 0)); break; } case CCVS1: { setSpeedometer(decode(payload, 8, 16, false, false, 0.00390625, 0)); break; } case SENSOR: { setSensor1(decode(payload, 0, 8, false, true, 0.4, 0)); setSensor2(decode(payload, 8, 8, false, true, 0.4, 0)); setSensor3(decode(payload, 16, 8, false, true, 0.4, 0)); setSensor4(decode(payload, 24, 8, false, true, 0.4, 0)); break; } default: break; }}
float Vehicle::speedometer() const{ return m_speedometer;}
float Vehicle::fuelLevel() const{ return m_fuelLevel;}
float Vehicle::sensor1() const{ return m_sensor1;}
float Vehicle::sensor2() const{ return m_sensor2;}
float Vehicle::sensor3() const{ return m_sensor3;}
float Vehicle::sensor4() const{ return m_sensor4;}
int Vehicle::ignition() const{ return m_ignition;}
void Vehicle::setSpeedometer(float speedometer){ if (m_speedometer == speedometer) return;
m_speedometer = speedometer; emit speedometerChanged(m_speedometer);}
void Vehicle::setFuelLevel(float fuelLevel){ if (m_fuelLevel == fuelLevel) return;
m_fuelLevel = fuelLevel; emit fuelLevelChanged(m_fuelLevel);}
void Vehicle::setSensor1(float sensor1){ if (m_sensor1 == sensor1) return;
m_sensor1 = sensor1; emit sensor1Changed(m_sensor1);}
void Vehicle::setSensor2(float sensor2){ if (m_sensor2 == sensor2) return;
m_sensor2 = sensor2; emit sensor2Changed(m_sensor2);}
void Vehicle::setSensor3(float sensor3){ if (m_sensor3 == sensor3) return;
m_sensor3 = sensor3; emit sensor3Changed(m_sensor3);}
void Vehicle::setSensor4(float sensor4){ if (m_sensor4 == sensor4) return;
m_sensor4 = sensor4; emit sensor4Changed(m_sensor4);}
void Vehicle::setIgnition(int ignition){ if (m_ignition == ignition) return;
m_ignition = ignition; emit ignitionChanged(m_ignition);}
#pragma once
#include <QObject>#include <QCanBus>#include <QTimer>
// This class establishes a connection to a CAN bus & is used to send/receive// CAN frames.// To watch for CAN frames, connect to the frameArrived signal.// To send CAN frames, connect a signal to the writeFrame() slot.class CanBus : public QObject{ Q_OBJECT
public: explicit CanBus(QString busInterface, QObject *parent = nullptr); ~CanBus();
// Connect to the given bus, return true if the connection is successful. bool connectBus();
// Write a CAN frame. bool writeFrame(quint32 id, char payload[], int payloadLengthBytes, bool extFrame=true, QCanBusFrame::FrameType type=QCanBusFrame::FrameType::DataFrame); bool writeFrame(QCanBusFrame frame);
// Update the list of CAN ids that are used for notifying the vehicle. void setVehicleIds(QVector<int> vehicleIds);
signals: // Sent out when a CAN frame has arrived on the canbus. void frameArrived(const QCanBusFrame& frame);
// Send out when a CAN frame has arrived on the canbus, that should be sent // to the vehicle void vehicleFrameArrived(const QCanBusFrame& frame);
// Sent out when the CAN bus activity stops or starts. void isActiveChanged(bool);
private: // Dequeues the latest CAN frames from the canbus device and sends them out through signals. void onFramesReceived();
// Check for activity on the CAN bus. void checkCanActivity();
// CAN Bus interface QString m_busInterface;
// Pointer to the CAN bus device to notify when frames arrive. QCanBusDevice* m_canbus;
// 1 second timer that checks if the CAN has had activity in the timeout // period. QTimer m_activityTimer;
// Timeout period (in seconds) in which the CAN bus is considered dead. int m_activityTimeout;
// Indicates if the last write to the bus was successful. int m_hadWriteSuccess;
// The CAN ids to send to vehicle. QVector<int> m_vehicleIds;
// Counts up every second that there is no CAN activity. int m_timeoutCounter;
// Track if the CAN bus is active. bool m_isActive;};
#include "CanBus.hpp"#include <QDebug>
CanBus::CanBus(QString busInterface, QObject *parent) : QObject(parent) , m_busInterface(busInterface) , m_canbus(nullptr) , m_activityTimeout(120) , m_hadWriteSuccess(true) , m_timeoutCounter(0) , m_isActive(true){ connectBus();}
CanBus::~CanBus(){ if (m_canbus) { m_canbus->disconnectDevice(); }}
bool CanBus::connectBus(){ if (m_canbus != nullptr) { return false; }
// Connect to the CAN interfaces. m_canbus = QCanBus::instance()->createDevice("socketcan", m_busInterface);
// If we can access the given interface, try to connect. if (m_canbus != nullptr) { // Listen for new CAN frames. connect(m_canbus, &QCanBusDevice::framesReceived, this, &CanBus::onFramesReceived);
// Connect the device. bool success = m_canbus->connectDevice(); if (success) { // Start the timer to monitor CAN messages if (m_activityTimeout > 0) { // Check for CAN activity connect(&m_activityTimer, &QTimer::timeout, this, &CanBus::checkCanActivity); m_activityTimer.start(1000); } } qDebug() << "Connect to CAN interface" << m_busInterface << "-" << (success ? "success" : "error"); return success; } else { qDebug() << "Could not connect to CAN interface" << m_busInterface; return false; }}
bool CanBus::writeFrame(quint32 id, char payload[], int payloadLengthBytes, bool extFrame, QCanBusFrame::FrameType type){ QByteArray data(payload, payloadLengthBytes);
// Setup CAN frame. QCanBusFrame frame(id, data); frame.setExtendedFrameFormat(extFrame); frame.setFrameType(type);
return writeFrame(frame);}
bool CanBus::writeFrame(QCanBusFrame frame){ bool success = false;
// If device is valid, write the frame to the canbus. if (m_canbus != nullptr) { success = m_canbus->writeFrame(frame); }
// If there was an error writing the frame this time, and there was no // error before, log error message. if (!success && m_hadWriteSuccess) { qDebug() << "CAN write frame failed :" << m_canbus->errorString() << frame.toString(); } m_hadWriteSuccess = success;
return success;}
void CanBus::setVehicleIds(QVector<int> vehicleIds){ m_vehicleIds.clear(); m_vehicleIds = vehicleIds;}
void CanBus::onFramesReceived(){ // Reset the counter. m_timeoutCounter = 0;
// Read the CAN frames. quint64 numFrames = m_canbus->framesAvailable(); while (numFrames-- > 0) { QCanBusFrame frame = m_canbus->readFrame();
// Notify others about the new frame. emit frameArrived(frame);
if (m_vehicleIds.contains(frame.frameId())) { emit vehicleFrameArrived(frame); } }}
void CanBus::checkCanActivity(){ m_timeoutCounter++;
if (!m_isActive && m_timeoutCounter < m_activityTimeout) { m_isActive = true; emit isActiveChanged(m_isActive); } else if (m_isActive && m_timeoutCounter >= m_activityTimeout) { m_isActive = false; emit isActiveChanged(m_isActive); }}
import QtQuickimport QtQuick.Layouts
Window { id: window width: 1920 height: 720 visible: true title: Qt.application.name flags: isDesktop ? Qt.Window : Qt.FramelessWindowHint color: colors.get("Background") property alias nav: nav
Image { id: background source: "qrc:/AppImages/background.png" anchors.centerIn: parent }
Image { id: navbar source: "qrc:/AppImages/navbar.png" anchors.bottom: background.bottom anchors.left: background.left }
ListView { id: rootList anchors.fill: parent model: list orientation: ListView.Horizontal flickableDirection: Flickable.HorizontalFlick snapMode: ListView.SnapOneItem highlightRangeMode: ListView.StrictlyEnforceRange focus: true onCurrentIndexChanged: nav.index = currentIndex highlightMoveDuration: 500 highlightMoveVelocity: -1 }
NavMenu { id: nav height: 100 anchors.bottom: parent.bottom width: parent.width index: 0 onNavigationChanged: (itemState) => { switch (itemState) { case "g+": rootList.currentIndex = 0 break; case "gsi": rootList.currentIndex = 1 break; case "vibs": rootList.currentIndex = 2 break; case "acc": rootList.currentIndex = 3 break; case "test": rootList.currentIndex = 4 break; } } }
ObjectModel { id: list Sensors {} GSI {} Vibs {} ACC {} Test {} }
FuelGauge { id: fuelGauge anchors { top: parent.top topMargin: 20 left: parent.left leftMargin: 20 }
gaugeHeight: 30 gaugeWidth: 260 fuelMin: 0 fuelMax: 100 fuelLevel: vehicle.fuelLevel gaugeName: MaterialIcons.mdiGasStation fillColor: colors.get("InsideRange") }
SpeedGauge { id: speedGauge anchors { top: parent.top topMargin: 20 right: parent.right rightMargin: 20 }
speed: vehicle.speedometer.toFixed(1) unit: "ft/min" pointSize: 32 gaugeHeight: 30 gaugeWidth: 260 }
Image { anchors { horizontalCenter: parent.horizontalCenter top: parent.top topMargin: 20 }
source: "qrc:/AppImages/mrs-logo.png" height: 50 fillMode: Image.PreserveAspectFit }}