Merge branch 'WIP' of github.com:collin80/SavvyCAN into WIP
@@ -24,7 +24,7 @@ matrix:
|
||||
- wget -c -nv "https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage"
|
||||
- chmod a+x linuxdeployqt-continuous-x86_64.AppImage
|
||||
# export VERSION=... # linuxdeployqt uses this for naming the file
|
||||
- ./linuxdeployqt-continuous-x86_64.AppImage appdir/usr/local/share/applications/SavvyCAN.desktop -appimage -extra-plugins=iconengines,platformthemes.libqgtk3.so
|
||||
- ./linuxdeployqt-continuous-x86_64.AppImage appdir/usr/local/share/applications/SavvyCAN.desktop -appimage -extra-plugins=iconengines,platformthemes/libqgtk3.so,canbus
|
||||
|
||||
after_success:
|
||||
# find appdir -executable -type f -exec ldd {} \; | grep " => /usr" | cut -d " " -f 2-3 | sort | uniq # for debugging
|
||||
@@ -50,7 +50,8 @@ matrix:
|
||||
script:
|
||||
- qmake CONFIG+=release -spec macx-xcode SavvyCAN.pro
|
||||
- xcodebuild
|
||||
- cp *.q* Release/SavvyCAN.app/Contents/MacOS/
|
||||
- mkdir Release/SavvyCAN.app/Contents/MacOS/help
|
||||
- cp -R help/*.* Release/SavvyCAN.app/Contents/MacOS/help
|
||||
- cd Release
|
||||
- ls SavvyCAN.app/Contents/MacOS
|
||||
- macdeployqt SavvyCAN.app -dmg
|
||||
@@ -78,7 +79,8 @@ matrix:
|
||||
- rm *.cpp
|
||||
- rm *.h
|
||||
- rm *.obj
|
||||
- cp ../*.q* .
|
||||
- mkdir help
|
||||
- cp -R ../help/*.* ./help
|
||||
- cp -r ../examples .
|
||||
- 7z a -tzip SavvyCAN32.zip -r *
|
||||
|
||||
@@ -92,6 +94,6 @@ matrix:
|
||||
draft: false
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- WIP
|
||||
except:
|
||||
- # Do not build tags that we create when we upload to GitHub Releases
|
||||
- /^(?i:continuous)/
|
||||
|
||||
@@ -68,7 +68,8 @@ SOURCES += main.cpp\
|
||||
blfhandler.cpp \
|
||||
re/sniffer/SnifferDelegate.cpp \
|
||||
connections/newconnectiondialog.cpp \
|
||||
re/temporalgraphwindow.cpp
|
||||
re/temporalgraphwindow.cpp \
|
||||
filterutility.cpp
|
||||
|
||||
HEADERS += mainwindow.h \
|
||||
can_structs.h \
|
||||
@@ -127,7 +128,8 @@ HEADERS += mainwindow.h \
|
||||
blfhandler.h \
|
||||
re/sniffer/SnifferDelegate.h \
|
||||
connections/newconnectiondialog.h \
|
||||
re/temporalgraphwindow.h
|
||||
re/temporalgraphwindow.h \
|
||||
filterutility.h
|
||||
|
||||
FORMS += ui/candatagrid.ui \
|
||||
ui/connectionwindow.ui \
|
||||
@@ -186,8 +188,8 @@ iconfiles.files=icons
|
||||
iconfiles.path = $$PREFIX/share/icons
|
||||
INSTALLS += iconfiles
|
||||
|
||||
helpfiles.files=SavvyCAN.q*
|
||||
helpfiles.path = $$PREFIX/bin
|
||||
helpfiles.files=help/*
|
||||
helpfiles.path = $$PREFIX/bin/help
|
||||
INSTALLS += helpfiles
|
||||
|
||||
INSTALLS += target
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<QHelpCollectionProject version="1.0">
|
||||
<assistant>
|
||||
<title>SavvyCAN 189 documentation</title>
|
||||
<homePage>qthelp://org.sphinx.savvycan.189/doc/index.html</homePage>
|
||||
<startPage>qthelp://org.sphinx.savvycan.189/doc/index.html</startPage>
|
||||
</assistant>
|
||||
<docFiles>
|
||||
<generate>
|
||||
<file>
|
||||
<input>SavvyCAN.qhp</input>
|
||||
<output>SavvyCAN.qch</output>
|
||||
</file>
|
||||
</generate>
|
||||
<register>
|
||||
<file>SavvyCAN.qch</file>
|
||||
</register>
|
||||
</docFiles>
|
||||
</QHelpCollectionProject>
|
||||
@@ -1,170 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<QtHelpProject version="1.0">
|
||||
<namespace>org.sphinx.savvycan.189</namespace>
|
||||
<virtualFolder>doc</virtualFolder>
|
||||
<customFilter name="SavvyCAN 189">
|
||||
<filterAttribute>SavvyCAN</filterAttribute>
|
||||
<filterAttribute>189</filterAttribute>
|
||||
</customFilter>
|
||||
<filterSection>
|
||||
<filterAttribute>SavvyCAN</filterAttribute>
|
||||
<filterAttribute>189</filterAttribute>
|
||||
<toc>
|
||||
<section title="SavvyCAN 189 documentation" ref="index.html">
|
||||
<section title="Main / Start Up Screen" ref="mainscreen.html"/>
|
||||
<section title="The Main Frame List" ref="mainscreen.html#the-main-frame-list"/>
|
||||
<section title="The Bottom Statusbar" ref="mainscreen.html#the-bottom-statusbar"/>
|
||||
<section title="The Rest of the Main Window" ref="mainscreen.html#the-rest-of-the-main-window"/>
|
||||
<section title="Loading And Saving Frames" ref="mainscreen.html#loading-and-saving-frames"/>
|
||||
<section title="Filters" ref="mainscreen.html#filters"/>
|
||||
<section title="What is DBC and why would I care?!" ref="mainscreen.html#what-is-dbc-and-why-would-i-care"/>
|
||||
<section title="How DBC interacts with the main screen:" ref="mainscreen.html#how-dbc-interacts-with-the-main-screen"/>
|
||||
<section title="Connection Window" ref="connectionwindow.html"/>
|
||||
<section title="Connecting To A Dongle" ref="connectionwindow.html#connecting-to-a-dongle"/>
|
||||
<section title="Debugging Connection Problems" ref="connectionwindow.html#debugging-connection-problems"/>
|
||||
<section title="Preference Window" ref="preferences.html"/>
|
||||
<section title="Setting Preferences" ref="preferences.html#setting-preferences"/>
|
||||
<section title="DBC File Manager" ref="dbc_manager.html"/>
|
||||
<section title="Working with DBC Files" ref="dbc_manager.html#working-with-dbc-files"/>
|
||||
<section title="DBC File Ordering" ref="dbc_manager.html#dbc-file-ordering"/>
|
||||
<section title="DBC Message Editor" ref="dbc_editor.html"/>
|
||||
<section title="Working with Nodes" ref="dbc_editor.html#working-with-nodes"/>
|
||||
<section title="Working with Messages" ref="dbc_editor.html#working-with-messages"/>
|
||||
<section title="DBC Signal Editor" ref="signaleditor.html"/>
|
||||
<section title="Defining and Editing Signals" ref="signaleditor.html#defining-and-editing-signals"/>
|
||||
<section title="Graphing Window" ref="graphwindow.html"/>
|
||||
<section title="Creating a new Graph" ref="graphwindow.html#creating-a-new-graph"/>
|
||||
<section title="Selecting a Graph" ref="graphwindow.html#selecting-a-graph"/>
|
||||
<section title="Editing a Graph" ref="graphwindow.html#editing-a-graph"/>
|
||||
<section title="Deleting Graphs" ref="graphwindow.html#deleting-graphs"/>
|
||||
<section title="Moving Around" ref="graphwindow.html#moving-around"/>
|
||||
<section title="Loading and Saving Graphs" ref="graphwindow.html#loading-and-saving-graphs"/>
|
||||
<section title="Real Time Graphing" ref="graphwindow.html#real-time-graphing"/>
|
||||
<section title="Hidden Tricks" ref="graphwindow.html#hidden-tricks"/>
|
||||
<section title="Graph Setup" ref="graphsetup.html"/>
|
||||
<section title="Setting up a Graph" ref="graphsetup.html#setting-up-a-graph"/>
|
||||
<section title="Graphing a DBC Signal" ref="graphsetup.html#graphing-a-dbc-signal"/>
|
||||
<section title="Manual Signal Graphing (Or Editing)" ref="graphsetup.html#manual-signal-graphing-or-editing"/>
|
||||
<section title="Flow View Window" ref="flowview.html"/>
|
||||
<section title="The Purpose of Flow View" ref="flowview.html#the-purpose-of-flow-view"/>
|
||||
<section title="Selecting an ID to Flow" ref="flowview.html#selecting-an-id-to-flow"/>
|
||||
<section title="Viewing the Flow" ref="flowview.html#viewing-the-flow"/>
|
||||
<section title="Controlling the Flow" ref="flowview.html#controlling-the-flow"/>
|
||||
<section title="Additional control options" ref="flowview.html#additional-control-options"/>
|
||||
<section title="Reference Values" ref="flowview.html#reference-values"/>
|
||||
<section title="Seeking to Specific Values" ref="flowview.html#seeking-to-specific-values"/>
|
||||
<section title="Bisector Window" ref="bisector.html"/>
|
||||
<section title="Using the Bisector Window" ref="bisector.html#using-the-bisector-window"/>
|
||||
<section title="Sniffer Window" ref="sniffer.html"/>
|
||||
<section title="Using the Sniffer Window" ref="sniffer.html#using-the-sniffer-window"/>
|
||||
<section title="Notching and Unnotching" ref="sniffer.html#notching-and-unnotching"/>
|
||||
<section title="Advanced Options" ref="sniffer.html#advanced-options"/>
|
||||
<section title="Never Expire IDs" ref="sniffer.html#never-expire-ids"/>
|
||||
<section title="Mute notched bits" ref="sniffer.html#mute-notched-bits"/>
|
||||
<section title="Fade inactive bytes" ref="sniffer.html#fade-inactive-bytes"/>
|
||||
<section title="View Bits" ref="sniffer.html#view-bits"/>
|
||||
<section title="Frame Details Window" ref="framedetails.html"/>
|
||||
<section title="The Purpose of Frame Details Window" ref="framedetails.html#the-purpose-of-frame-details-window"/>
|
||||
<section title="File Comparison Window" ref="filecomparison.html"/>
|
||||
<section title="The Purpose of the File Comparator" ref="filecomparison.html#the-purpose-of-the-file-comparator"/>
|
||||
<section title="The layout of the differences list" ref="filecomparison.html#the-layout-of-the-differences-list"/>
|
||||
<section title="Fuzzing Window" ref="fuzzingwindow.html"/>
|
||||
<section title="The Purpose of Fuzzing" ref="fuzzingwindow.html#the-purpose-of-fuzzing"/>
|
||||
<section title="Fuzzing is Dangerous" ref="fuzzingwindow.html#fuzzing-is-dangerous"/>
|
||||
<section title="Controlling the Fuzzy Beast" ref="fuzzingwindow.html#controlling-the-fuzzy-beast"/>
|
||||
<section title="Pulling the Trigger" ref="fuzzingwindow.html#pulling-the-trigger"/>
|
||||
<section title="ISO-TP Decoder" ref="isotp_decoder.html"/>
|
||||
<section title="Using the ISO-TP Decoder" ref="isotp_decoder.html#using-the-iso-tp-decoder"/>
|
||||
<section title="Range State Window" ref="rangestate.html"/>
|
||||
<section title="Using the Range State Window" ref="rangestate.html#using-the-range-state-window"/>
|
||||
<section title="UDS Scan Window" ref="uds_scanner.html"/>
|
||||
<section title="Purpose of the UDS Scan Window" ref="uds_scanner.html#purpose-of-the-uds-scan-window"/>
|
||||
<section title="Using the UDS Scan Window" ref="uds_scanner.html#using-the-uds-scan-window"/>
|
||||
<section title="Playback Window" ref="playbackwindow.html"/>
|
||||
<section title="Preparing Frames for Playback" ref="playbackwindow.html#preparing-frames-for-playback"/>
|
||||
<section title="Playing Back Frames" ref="playbackwindow.html#playing-back-frames"/>
|
||||
<section title="Playback Status" ref="playbackwindow.html#playback-status"/>
|
||||
<section title="Custom Sender Window" ref="customsender.html"/>
|
||||
<section title="General Overview" ref="customsender.html#general-overview"/>
|
||||
<section title="Layout of the View" ref="customsender.html#layout-of-the-view"/>
|
||||
<section title="Writing Trigger Rules" ref="customsender.html#writing-trigger-rules"/>
|
||||
<section title="Writing Modifications" ref="customsender.html#writing-modifications"/>
|
||||
<section title="Scripting Interface" ref="scriptingwindow.html"/>
|
||||
<section title="Purpose of the Scripting Interface" ref="scriptingwindow.html#purpose-of-the-scripting-interface"/>
|
||||
<section title="Managing Scripts" ref="scriptingwindow.html#managing-scripts"/>
|
||||
<section title="Getting Script Status" ref="scriptingwindow.html#getting-script-status"/>
|
||||
<section title="Writing Scripts" ref="scriptingwindow.html#writing-scripts"/>
|
||||
<section title="Callback Functions" ref="scriptingwindow.html#callback-functions"/>
|
||||
<section title="The host Object" ref="scriptingwindow.html#the-host-object"/>
|
||||
<section title="The can Object" ref="scriptingwindow.html#the-can-object"/>
|
||||
<section title="The isotp Object" ref="scriptingwindow.html#the-isotp-object"/>
|
||||
<section title="The uds Object" ref="scriptingwindow.html#the-uds-object"/>
|
||||
<section title="A full example script" ref="scriptingwindow.html#a-full-example-script"/>
|
||||
</section>
|
||||
</toc>
|
||||
<keywords>
|
||||
|
||||
</keywords>
|
||||
<files>
|
||||
<file>bisector.html</file>
|
||||
<file>connectionwindow.html</file>
|
||||
<file>customsender.html</file>
|
||||
<file>dbc_editor.html</file>
|
||||
<file>dbc_manager.html</file>
|
||||
<file>filecomparison.html</file>
|
||||
<file>firmwareuploader.html</file>
|
||||
<file>flowview.html</file>
|
||||
<file>framedetails.html</file>
|
||||
<file>fuzzingwindow.html</file>
|
||||
<file>genindex.html</file>
|
||||
<file>graphsetup.html</file>
|
||||
<file>graphwindow.html</file>
|
||||
<file>index.html</file>
|
||||
<file>isotp_decoder.html</file>
|
||||
<file>mainscreen.html</file>
|
||||
<file>playbackwindow.html</file>
|
||||
<file>preferences.html</file>
|
||||
<file>rangestate.html</file>
|
||||
<file>scriptingwindow.html</file>
|
||||
<file>search.html</file>
|
||||
<file>signaleditor.html</file>
|
||||
<file>sniffer.html</file>
|
||||
<file>uds_scanner.html</file>
|
||||
<file>_static/ajax-loader.gif</file>
|
||||
<file>_static/alabaster.css</file>
|
||||
<file>_static/basic.css</file>
|
||||
<file>_static/comment-bright.png</file>
|
||||
<file>_static/comment-close.png</file>
|
||||
<file>_static/comment.png</file>
|
||||
<file>_static/down-pressed.png</file>
|
||||
<file>_static/down.png</file>
|
||||
<file>_static/file.png</file>
|
||||
<file>_static/minus.png</file>
|
||||
<file>_static/nonav.css</file>
|
||||
<file>_static/plus.png</file>
|
||||
<file>_static/pygments.css</file>
|
||||
<file>_static/up-pressed.png</file>
|
||||
<file>_static/up.png</file>
|
||||
<file>_images/Bisector.png</file>
|
||||
<file>_images/ConnectionWindow.png</file>
|
||||
<file>_images/CustomSender.png</file>
|
||||
<file>_images/DBCEditor.png</file>
|
||||
<file>_images/DBCManager.png</file>
|
||||
<file>_images/FileComparator.png</file>
|
||||
<file>_images/FlowView.png</file>
|
||||
<file>_images/FrameInfoWindow.png</file>
|
||||
<file>_images/FuzzingWindow.png</file>
|
||||
<file>_images/GraphSetup.png</file>
|
||||
<file>_images/GraphingView.png</file>
|
||||
<file>_images/ISOTPDecoder.png</file>
|
||||
<file>_images/MainScreen.png</file>
|
||||
<file>_images/Playback.png</file>
|
||||
<file>_images/Preferences.png</file>
|
||||
<file>_images/RangeState.png</file>
|
||||
<file>_images/ScriptingWindow.png</file>
|
||||
<file>_images/SignalEditor.png</file>
|
||||
<file>_images/Sniffer.png</file>
|
||||
<file>_images/UDS_Scanner.png</file>
|
||||
</files>
|
||||
</filterSection>
|
||||
</QtHelpProject>
|
||||
@@ -66,7 +66,7 @@ void BisectWindow::refreshIDList()
|
||||
int id;
|
||||
for (int i = 0; i < modelFrames->count(); i++)
|
||||
{
|
||||
id = modelFrames->at(i).ID;
|
||||
id = modelFrames->at(i).frameId();
|
||||
if (!foundID.contains(id))
|
||||
{
|
||||
foundID.append(id);
|
||||
@@ -129,7 +129,7 @@ void BisectWindow::handleCalculateButton()
|
||||
uint32_t upperID = Utility::ParseStringToNum2(ui->cbIDUpper->currentText());
|
||||
for (int i = 0; i < modelFrames->count(); i++)
|
||||
{
|
||||
if (modelFrames->at(i).ID >= lowerID && modelFrames->at(i).ID <= upperID) splitFrames.append(modelFrames->at(i));
|
||||
if (modelFrames->at(i).frameId() >= lowerID && modelFrames->at(i).frameId() <= upperID) splitFrames.append(modelFrames->at(i));
|
||||
}
|
||||
}
|
||||
refreshFrameNumbers();
|
||||
|
||||
@@ -78,7 +78,7 @@ bool BLFHandler::loadBLF(QString filename, QVector<CANFrame>* frames)
|
||||
}
|
||||
qDebug() << "Uncompressed size: " << uncompressedData.count();
|
||||
pos = 0;
|
||||
bool foundHeader = false;
|
||||
//bool foundHeader = false;
|
||||
//first skip forward to find a header signature - usually not necessary
|
||||
while ( (int)(pos + sizeof(BLF_OBJ_HEADER)) < uncompressedData.count())
|
||||
{
|
||||
@@ -100,19 +100,20 @@ bool BLFHandler::loadBLF(QString filename, QVector<CANFrame>* frames)
|
||||
memcpy(&canObject, fileData.constData(), sizeof(BLF_CAN_OBJ));
|
||||
CANFrame frame;
|
||||
frame.bus = canObject.channel;
|
||||
frame.extended = (canObject.id & 0x80000000ull)?true:false;
|
||||
frame.ID = canObject.id & 0x1FFFFFFFull;
|
||||
frame.setExtendedFrameFormat((canObject.id & 0x80000000ull)?true:false);
|
||||
frame.setFrameId(canObject.id & 0x1FFFFFFFull);
|
||||
frame.isReceived = true;
|
||||
frame.len = canObject.dlc;
|
||||
QByteArray bytes(canObject.dlc, 0);
|
||||
|
||||
if (canObject.flags & BLF_REMOTE_FLAG) {
|
||||
frame.remote = true;
|
||||
frame.setFrameType(QCanBusFrame::RemoteRequestFrame);
|
||||
} else {
|
||||
frame.remote = false;
|
||||
for (int i = 0; i < 8; i++) frame.data[i] = canObject.data[i];
|
||||
frame.setFrameType(QCanBusFrame::DataFrame);
|
||||
for (int i = 0; i < 8; i++) bytes[i] = canObject.data[i];
|
||||
}
|
||||
frame.setPayload(bytes);
|
||||
//Should we divide by a thousand or a million? Unsure here. It appears some logs are stamped in microseconds and some in milliseconds?
|
||||
frame.timestamp = obj.header.v1Obj.uncompSize / 1000.0; //uncompsize field also used for timestamp oddly enough
|
||||
frame.setTimeStamp(QCanBusFrame::TimeStamp(0, obj.header.v1Obj.uncompSize / 1000.0)); //uncompsize field also used for timestamp oddly enough
|
||||
frames->append(frame);
|
||||
}
|
||||
else if (obj.header.base.objType == BLF_CAN_MSG2)
|
||||
@@ -120,19 +121,19 @@ bool BLFHandler::loadBLF(QString filename, QVector<CANFrame>* frames)
|
||||
memcpy(&canObject2, fileData.constData(), sizeof(BLF_CAN_OBJ2));
|
||||
CANFrame frame;
|
||||
frame.bus = canObject2.channel;
|
||||
frame.extended = (canObject2.id & 0x80000000ull)?true:false;
|
||||
frame.ID = canObject2.id & 0x1FFFFFFFull;
|
||||
frame.setExtendedFrameFormat((canObject2.id & 0x80000000ull)?true:false);
|
||||
frame.setFrameId(canObject2.id & 0x1FFFFFFFull);
|
||||
frame.isReceived = true;
|
||||
frame.len = canObject2.dlc;
|
||||
QByteArray bytes(canObject2.dlc, 0);
|
||||
|
||||
if (canObject2.flags & BLF_REMOTE_FLAG) {
|
||||
frame.remote = true;
|
||||
frame.setFrameType(QCanBusFrame::RemoteRequestFrame);
|
||||
} else {
|
||||
frame.remote = false;
|
||||
for (int i = 0; i < 8; i++) frame.data[i] = canObject2.data[i];
|
||||
frame.setFrameType(QCanBusFrame::DataFrame);
|
||||
for (int i = 0; i < 8; i++) bytes[i] = canObject2.data[i];
|
||||
}
|
||||
//Should we divide by a thousand or a million? Unsure here. It appears some logs are stamped in microseconds and some in milliseconds?
|
||||
frame.timestamp = obj.header.v1Obj.uncompSize / 1000.0; //uncompsize field also used for timestamp oddly enough
|
||||
frame.setTimeStamp(QCanBusFrame::TimeStamp(0, obj.header.v1Obj.uncompSize / 1000.0)); //uncompsize field also used for timestamp oddly enough
|
||||
frames->append(frame);
|
||||
}
|
||||
else
|
||||
@@ -162,5 +163,7 @@ bool BLFHandler::loadBLF(QString filename, QVector<CANFrame>* frames)
|
||||
|
||||
bool BLFHandler::saveBLF(QString filename, QVector<CANFrame> *frames)
|
||||
{
|
||||
Q_UNUSED(filename)
|
||||
Q_UNUSED(frames)
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ ISOTP_HANDLER::ISOTP_HANDLER()
|
||||
processAll = false;
|
||||
lastSenderBus = 0;
|
||||
lastSenderID = 0;
|
||||
issuedMultiFrame = false;
|
||||
|
||||
modelFrames = MainWindow::getReference()->getCANFrameModel()->getListReference();
|
||||
|
||||
@@ -48,10 +47,10 @@ void ISOTP_HANDLER::setReception(bool mode)
|
||||
}
|
||||
}
|
||||
|
||||
void ISOTP_HANDLER::sendISOTPFrame(int bus, int ID, QVector<unsigned char> data)
|
||||
void ISOTP_HANDLER::sendISOTPFrame(int bus, int ID, QByteArray data)
|
||||
{
|
||||
CANFrame frame;
|
||||
frame.remote = false;
|
||||
frame.setFrameType(QCanBusFrame::DataFrame);
|
||||
int currByte = 0;
|
||||
int index = 0;
|
||||
if (bus < 0) return;
|
||||
@@ -62,29 +61,28 @@ void ISOTP_HANDLER::sendISOTPFrame(int bus, int ID, QVector<unsigned char> data)
|
||||
|
||||
if (data.length() < 8)
|
||||
{
|
||||
issuedMultiFrame = false;
|
||||
frame.bus = bus;
|
||||
frame.ID = ID;
|
||||
if (ID > 0x7FF) frame.extended = true;
|
||||
else frame.extended = false;
|
||||
frame.len = 8;
|
||||
for (int b = 0; b < 8; b++) frame.data[b] = 0x00;
|
||||
frame.data[0] = data.length();
|
||||
for (int i = 0; i < frame.data[0]; i++) frame.data[i + 1] = data[i];
|
||||
frame.bus = bus;
|
||||
frame.setFrameId(ID);
|
||||
if (ID > 0x7FF) frame.setExtendedFrameFormat(true);
|
||||
else frame.setExtendedFrameFormat(false);
|
||||
QByteArray bytes(8,0);
|
||||
bytes.resize(8);
|
||||
bytes[0] = data.length();
|
||||
for (int i = 0; i < data.length(); i++) bytes[i + 1] = data[i];
|
||||
frame.setPayload(bytes);
|
||||
CANConManager::getInstance()->sendFrame(frame);
|
||||
}
|
||||
else //need to send a multi-part ISO_TP message - Respects timing and frame number based flow control
|
||||
{
|
||||
issuedMultiFrame = true;
|
||||
frame.bus = bus;
|
||||
frame.ID = ID;
|
||||
if (ID > 0x7FF) frame.extended = true;
|
||||
else frame.extended = false;
|
||||
frame.len = 8;
|
||||
for (int b = 0; b < 8; b++) frame.data[b] = 0x00;
|
||||
frame.data[0] = 0x10 + (data.length() / 256);
|
||||
frame.data[1] = data.length() & 0xFF;
|
||||
for (int i = 0; i < 6; i++) frame.data[2 + i] = data[currByte++];
|
||||
frame.setFrameId(ID);
|
||||
if (ID > 0x7FF) frame.setExtendedFrameFormat(true);
|
||||
else frame.setExtendedFrameFormat(false);
|
||||
QByteArray bytes(8, 0);
|
||||
bytes[0] = 0x10 + (data.length() / 256);
|
||||
bytes[1] = data.length() & 0xFF;
|
||||
for (int i = 0; i < 6; i++) bytes[2 + i] = data[currByte++];
|
||||
frame.setPayload(bytes);
|
||||
CANConManager::getInstance()->sendFrame(frame);
|
||||
//Queue up the rest of the frames
|
||||
waitingForFlow = true;
|
||||
@@ -93,13 +91,13 @@ void ISOTP_HANDLER::sendISOTPFrame(int bus, int ID, QVector<unsigned char> data)
|
||||
frameTimer.start();
|
||||
while (currByte < data.length())
|
||||
{
|
||||
for (int b = 0; b < 8; b++) frame.data[b] = 0x00;
|
||||
frame.data[0] = 0x20 + index;
|
||||
for (int b = 0; b < 8; b++) bytes[b] = 0x00;
|
||||
bytes[0] = 0x20 + index;
|
||||
index = (index + 1) & 0xF;
|
||||
int bytesToGo = data.length() - currByte;
|
||||
if (bytesToGo > 7) bytesToGo = 7;
|
||||
for (int i = 0; i < bytesToGo; i++) frame.data[1 + i] = data[currByte++];
|
||||
frame.len = 8;
|
||||
for (int i = 0; i < bytesToGo; i++) bytes[1 + i] = data[currByte++];
|
||||
frame.setPayload(bytes);
|
||||
sendingFrames.append(frame);
|
||||
//CANConManager::getInstance()->sendFrame(frame);
|
||||
}
|
||||
@@ -141,7 +139,7 @@ void ISOTP_HANDLER::rapidFrames(const CANConnection* conn, const QVector<CANFram
|
||||
{
|
||||
for (int i = 0; i < filters.count(); i++)
|
||||
{
|
||||
if ((thisFrame.bus == filters[i].bus) && ((thisFrame.ID & filters[i].mask) == filters[i].ID))
|
||||
if ((thisFrame.bus == filters[i].bus) && ((thisFrame.frameId() & filters[i].mask) == filters[i].ID))
|
||||
{
|
||||
processFrame(thisFrame);
|
||||
break;
|
||||
@@ -154,13 +152,15 @@ void ISOTP_HANDLER::rapidFrames(const CANConnection* conn, const QVector<CANFram
|
||||
|
||||
void ISOTP_HANDLER::processFrame(const CANFrame &frame)
|
||||
{
|
||||
uint64_t ID = frame.ID;
|
||||
uint64_t ID = frame.frameId();
|
||||
int frameType;
|
||||
int frameLen;
|
||||
int ln;
|
||||
//int offset;
|
||||
ISOTP_MESSAGE msg;
|
||||
ISOTP_MESSAGE *pMsg;
|
||||
unsigned char *data = reinterpret_cast<unsigned char *>(frame.payload().data());
|
||||
//int dataLen = frame.payload().count();
|
||||
|
||||
frameType = 0;
|
||||
frameLen = 0;
|
||||
@@ -168,14 +168,14 @@ void ISOTP_HANDLER::processFrame(const CANFrame &frame)
|
||||
if (useExtendedAddressing)
|
||||
{
|
||||
ID = ID << 8;
|
||||
ID += frame.data[0];
|
||||
frameType = frame.data[1] >> 4;
|
||||
frameLen = frame.data[1] & 0xF;
|
||||
ID += data[0];
|
||||
frameType = data[1] >> 4;
|
||||
frameLen = data[1] & 0xF;
|
||||
}
|
||||
else
|
||||
{
|
||||
frameType = frame.data[0] >> 4;
|
||||
frameLen = frame.data[0] & 0xF;
|
||||
frameType = data[0] >> 4;
|
||||
frameLen = data[0] & 0xF;
|
||||
}
|
||||
|
||||
switch(frameType)
|
||||
@@ -188,87 +188,89 @@ void ISOTP_HANDLER::processFrame(const CANFrame &frame)
|
||||
if (frameLen > 7) return;
|
||||
|
||||
msg.bus = frame.bus;
|
||||
msg.extended = frame.extended;
|
||||
msg.ID = ID;
|
||||
msg.setExtendedFrameFormat( frame.hasExtendedFrameFormat() );
|
||||
msg.setFrameId(ID);
|
||||
msg.isReceived = frame.isReceived;
|
||||
msg.len = frameLen;
|
||||
msg.data.reserve(frameLen);
|
||||
msg.timestamp = frame.timestamp;
|
||||
if (useExtendedAddressing) for (int j = 0; j < frameLen; j++) msg.data.append(frame.data[j+2]);
|
||||
else for (int j = 0; j < frameLen; j++) msg.data.append(frame.data[j+1]);
|
||||
msg.payload().reserve(frameLen);
|
||||
msg.reportedLength = frameLen;
|
||||
msg.setTimeStamp(frame.timeStamp());
|
||||
msg.isMultiframe = false;
|
||||
if (useExtendedAddressing) for (int j = 0; j < frameLen; j++) msg.payload().append(frame.payload()[j+2]);
|
||||
else for (int j = 0; j < frameLen; j++) msg.payload().append(frame.payload()[j+1]);
|
||||
//qDebug() << "Emitting single frame ISOTP message";
|
||||
emit newISOMessage(msg);
|
||||
break;
|
||||
case 1: //first frame of a multi-frame message
|
||||
checkNeedFlush(ID);
|
||||
msg.bus = frame.bus;
|
||||
msg.extended = frame.extended;
|
||||
msg.ID = ID;
|
||||
msg.timestamp = frame.timestamp;
|
||||
msg.setExtendedFrameFormat( frame.hasExtendedFrameFormat() );
|
||||
msg.setFrameId(ID);
|
||||
msg.setTimeStamp(frame.timeStamp());
|
||||
msg.isReceived = frame.isReceived;
|
||||
issuedMultiFrame = true;
|
||||
msg.isMultiframe = true;
|
||||
frameLen = frameLen << 8;
|
||||
if (useExtendedAddressing)
|
||||
{
|
||||
frameLen += frame.data[2];
|
||||
frameLen += data[2];
|
||||
frameLen = frameLen & 0xFFF;
|
||||
msg.len = frameLen;
|
||||
msg.data.reserve(frameLen);
|
||||
for (int j = 0; j < 5; j++) msg.data.append(frame.data[3 + j]);
|
||||
msg.payload().reserve(frameLen);
|
||||
msg.reportedLength = frameLen;
|
||||
for (int j = 0; j < 5; j++) msg.payload().append(frame.payload()[3 + j]);
|
||||
}
|
||||
else
|
||||
{
|
||||
frameLen += frame.data[1];
|
||||
frameLen += data[1];
|
||||
frameLen = frameLen & 0xFFF;
|
||||
msg.len = frameLen;
|
||||
msg.data.reserve(frameLen);
|
||||
for (int j = 0; j < 6; j++) msg.data.append(frame.data[2 + j]);
|
||||
msg.payload().reserve(frameLen);
|
||||
msg.reportedLength = frameLen;
|
||||
for (int j = 0; j < 6; j++) msg.payload().append(frame.payload()[2 + j]);
|
||||
}
|
||||
msg.lastSequence = -1;
|
||||
messageBuffer.append(msg);
|
||||
//The sending ID is set to the last ID we used to send from this class which is
|
||||
//very likely to be correct. But, caution, there is a chance that it isn't. Beware.
|
||||
if (issueFlowMsgs && lastSenderID > 0 && issuedMultiFrame)
|
||||
if (issueFlowMsgs && lastSenderID > 0)
|
||||
{
|
||||
CANFrame outFrame;
|
||||
outFrame.bus = lastSenderBus;
|
||||
outFrame.extended = false;
|
||||
outFrame.ID = lastSenderID;
|
||||
outFrame.len = 8;
|
||||
for (int b = 0; b < 8; b++) outFrame.data[b] = 0x00;
|
||||
outFrame.data[0] = 0x30; //flow control, go ahead and send
|
||||
outFrame.data[1] = 0; //dont ask again about flow control
|
||||
outFrame.data[2] = 3; //separation time in milliseconds between messages.
|
||||
outFrame.setExtendedFrameFormat(false);
|
||||
outFrame.setFrameId(lastSenderID);
|
||||
QByteArray bytes(8, 0);
|
||||
bytes[0] = 0x30; //flow control, go ahead and send
|
||||
bytes[1] = 0; //dont ask again about flow control
|
||||
bytes[2] = 3; //separation time in milliseconds between messages.
|
||||
outFrame.setPayload(bytes);
|
||||
CANConManager::getInstance()->sendFrame(outFrame);
|
||||
}
|
||||
break;
|
||||
case 2: //subsequent frames for multi-frame messages
|
||||
pMsg = NULL;
|
||||
pMsg = nullptr;
|
||||
for (int i = 0; i < messageBuffer.length(); i++)
|
||||
{
|
||||
if (messageBuffer[i].ID == ID)
|
||||
if (messageBuffer[i].frameId() == ID)
|
||||
{
|
||||
pMsg = &messageBuffer[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!pMsg) return;
|
||||
ln = pMsg->len - pMsg->data.count();
|
||||
if (!pMsg->isMultiframe) return; //if we didn't get a frame type 1 (start of multiframe) first then ignore this frame.
|
||||
ln = pMsg->payload().length() - pMsg->payload().count();
|
||||
//offset = pMsg->data.count();
|
||||
if (useExtendedAddressing)
|
||||
{
|
||||
if (ln > 6) ln = 6;
|
||||
for (int j = 0; j < ln; j++) pMsg->data.append(frame.data[j+2]);
|
||||
for (int j = 0; j < ln; j++) pMsg->payload().append(frame.payload()[j+2]);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ln > 7) ln = 7;
|
||||
for (int j = 0; j < ln; j++) pMsg->data.append(frame.data[j+1]);
|
||||
for (int j = 0; j < ln; j++) pMsg->payload().append(frame.payload()[j+1]);
|
||||
}
|
||||
if (pMsg->len <= pMsg->data.count())
|
||||
if (pMsg->reportedLength <= pMsg->payload().count())
|
||||
{
|
||||
//qDebug() << "Emitting multiframe ISOTP message";
|
||||
issuedMultiFrame = false;
|
||||
emit newISOMessage(*pMsg);
|
||||
checkNeedFlush(pMsg->frameId());
|
||||
}
|
||||
break;
|
||||
case 3: //flow control messages
|
||||
@@ -277,10 +279,10 @@ void ISOTP_HANDLER::processFrame(const CANFrame &frame)
|
||||
case 0: //continue to send frames but maybe change inter-frame delay
|
||||
waitingForFlow = false;
|
||||
//data[1] contains number of frames to send before waiting for next flow control
|
||||
framesUntilFlow = frame.data[1];
|
||||
framesUntilFlow = data[1];
|
||||
if (framesUntilFlow == 0) framesUntilFlow = -1; //-1 means don't count frames and just keep going
|
||||
//data[2] contains the interframe delay to use (0xF1 through 0xF9 are special through)
|
||||
if (frame.data[2] < 0xF1) frameTimer.start(frame.data[2]); //set proper delay between frames
|
||||
if (data[2] < 0xF1) frameTimer.start(data[2]); //set proper delay between frames
|
||||
else frameTimer.start(1); //can't do sub-millisecond sending with this code so just use 1ms timing
|
||||
break;
|
||||
case 1: //wait - do not send any more frames until other side says so
|
||||
@@ -303,12 +305,21 @@ void ISOTP_HANDLER::checkNeedFlush(uint64_t ID)
|
||||
{
|
||||
for (int i = 0; i < messageBuffer.length(); i++)
|
||||
{
|
||||
if (messageBuffer[i].ID == ID)
|
||||
if (messageBuffer[i].frameId() == ID)
|
||||
{
|
||||
//used to pass by reference but now newISOMessage should pass by value which makes it easier to use cross thread
|
||||
//qDebug() << "Flushing a partial frame";
|
||||
if (messageBuffer[i].frameId() > 0x600 && messageBuffer[i].frameId() < 0x630)
|
||||
{
|
||||
if (messageBuffer[i].reportedLength <= messageBuffer[i].payload().count())
|
||||
{
|
||||
qDebug() << "Flushing full frame" << QString::number(messageBuffer[i].frameId(), 16) << " " << messageBuffer[i].reportedLength << " " << messageBuffer[i].payload().count();
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "Flushing a partial frame " << QString::number(messageBuffer[i].frameId(), 16) << " " << messageBuffer[i].reportedLength << " " << messageBuffer[i].payload().count();
|
||||
}
|
||||
}
|
||||
emit newISOMessage(messageBuffer[i]);
|
||||
issuedMultiFrame = false;
|
||||
messageBuffer.removeAt(i);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#ifndef ISOTP_HANDLER_H
|
||||
#define ISOTP_HANDLER_H
|
||||
#pragma once
|
||||
|
||||
#include <Qt>
|
||||
#include <QObject>
|
||||
@@ -20,7 +19,7 @@ public:
|
||||
~ISOTP_HANDLER();
|
||||
void setExtendedAddressing(bool mode);
|
||||
void setReception(bool mode); //set whether to accept and forward frames or not
|
||||
void sendISOTPFrame(int bus, int ID, QVector<unsigned char> data);
|
||||
void sendISOTPFrame(int bus, int ID, QByteArray data);
|
||||
void setProcessAll(bool state);
|
||||
void setFlowCtrl(bool state);
|
||||
void addFilter(uint32_t pBusId, uint32_t ID, uint32_t mask);
|
||||
@@ -46,7 +45,6 @@ private:
|
||||
int framesUntilFlow;
|
||||
bool processAll;
|
||||
bool issueFlowMsgs;
|
||||
bool issuedMultiFrame;
|
||||
QTimer frameTimer;
|
||||
uint32_t lastSenderID;
|
||||
uint32_t lastSenderBus;
|
||||
@@ -54,5 +52,3 @@ private:
|
||||
void processFrame(const CANFrame &frame);
|
||||
void checkNeedFlush(uint64_t ID);
|
||||
};
|
||||
|
||||
#endif // ISOTP_HANDLER_H
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
#ifndef ISOTP_MESSAGE_H
|
||||
#define ISOTP_MESSAGE_H
|
||||
|
||||
//the same as the CANFrame struct but with arbitrary data size.
|
||||
class ISOTP_MESSAGE
|
||||
#include <Qt>
|
||||
#include <QVector>
|
||||
#include <can_structs.h>
|
||||
|
||||
//Now a child class of CANFrame. We just add the ability to track how long it was supposed to be and other
|
||||
//ISOTP related details. But, mostly just CANFrame.
|
||||
class ISOTP_MESSAGE : public CANFrame
|
||||
{
|
||||
public:
|
||||
uint32_t ID;
|
||||
int bus;
|
||||
bool extended;
|
||||
bool isReceived;
|
||||
uint32_t len; //# of bytes this message should have (as reported)
|
||||
uint32_t actualSize; //# we actually got
|
||||
QVector<unsigned char> data;
|
||||
uint64_t timestamp;
|
||||
int reportedLength;
|
||||
int lastSequence;
|
||||
bool isMultiframe;
|
||||
};
|
||||
|
||||
#endif // ISOTP_MESSAGE_H
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
#include "isotp_handler.h"
|
||||
#include <QDebug>
|
||||
|
||||
QVector<CODE_STRUCT> UDS_DIAG_CTRL_SUB = {
|
||||
static QVector<CODE_STRUCT> UDS_DIAG_CTRL_SUB = {
|
||||
{1,"DFLT_SESS", "Default session"},
|
||||
{2,"PROG_SESS", "Programming Session"},
|
||||
{3,"EXT_SESS", "Extended Diagnostics Session"},
|
||||
{4,"SAFETY_SESS", "Safety System Diagnostics Session"},
|
||||
};
|
||||
|
||||
QVector<CODE_STRUCT> UDS_ECU_RESET_SUB = {
|
||||
static QVector<CODE_STRUCT> UDS_ECU_RESET_SUB = {
|
||||
{1,"HARD_RESET", "Hard reset of ECU"},
|
||||
{2,"KEYOFFON_RESET", "Simulated key off then on reset"},
|
||||
{3,"SOFT_RESET", "Soft reset - leaving RAM intact"},
|
||||
@@ -19,7 +19,7 @@ QVector<CODE_STRUCT> UDS_ECU_RESET_SUB = {
|
||||
{5,"DIS_POWERDOWN_RESET", "Disable sleep mode"},
|
||||
};
|
||||
|
||||
QVector<CODE_STRUCT> UDS_COMM_CTRL_SUB = {
|
||||
static QVector<CODE_STRUCT> UDS_COMM_CTRL_SUB = {
|
||||
{0,"COMM_NORMAL", "Enable both Rx and Tx of normal messages"},
|
||||
{1,"COMM_DIS_TX", "Enable reception of normal messages but don't Tx them"},
|
||||
{3,"COMM_DIS_ALL", "Disable both Rx and Tx of non-diagnostics messages"},
|
||||
@@ -27,13 +27,13 @@ QVector<CODE_STRUCT> UDS_COMM_CTRL_SUB = {
|
||||
{5,"COMM_ENHANC", "Addressed bus master should set related sub-bus to app scheduling mode"},
|
||||
};
|
||||
|
||||
QVector<CODE_STRUCT> UDS_ROUTINE_SUB = {
|
||||
static QVector<CODE_STRUCT> UDS_ROUTINE_SUB = {
|
||||
{1,"START_ROUTINE", "Start routine by given ID"},
|
||||
{2,"STOP_ROUTINE", "Stop routine by given ID"},
|
||||
{3,"GET_ROUTINE_RESULTS", "Get results from routine specified by ID"},
|
||||
};
|
||||
|
||||
QVector<CODE_STRUCT> UDS_SERVICE_DESC = {
|
||||
static QVector<CODE_STRUCT> UDS_SERVICE_DESC = {
|
||||
{1, "OBDII_SHOW_CURRENT", "OBDII - Show current data"},
|
||||
{2, "OBDII_SHOW_FREEZE", "OBDII - Show freeze data"},
|
||||
{3, "OBDII_SHOW_STORED_DTC", "OBDII - Show stored DTC codes"},
|
||||
@@ -50,7 +50,8 @@ QVector<CODE_STRUCT> UDS_SERVICE_DESC = {
|
||||
{0x14, "CLEAR_DIAG", "Clear diagnostic trouble codes"},
|
||||
{0x19, "READ_DTC", "Read diagnostic trouble codes"},
|
||||
{0x1A, "GMLAN_READ_DIAGNOSTIC_ID", "GMLAN - Read diagnostics ID"},
|
||||
{0x20, "RETURN_TO_NORMAL", "Return to normal mode"},
|
||||
{0x20, "RETURN_TO_NORMAL", "Return to normal mode"},
|
||||
{0x21, "READ_BY_LOCALID", "Read data by Local ID"},
|
||||
{0x22, "READ_BY_ID", "Read data by ID"},
|
||||
{0x23, "READ_BY_ADDR", "Read data by address"},
|
||||
{0x24, "READ_SCALING_ID", "Read scaling data by ID"},
|
||||
@@ -84,7 +85,7 @@ QVector<CODE_STRUCT> UDS_SERVICE_DESC = {
|
||||
{0xFF, "UNKNOWN_CODE", "Unknown, likely proprietary UDS function code"}
|
||||
};
|
||||
|
||||
QVector<CODE_STRUCT> UDS_NEG_RESPONSE =
|
||||
static QVector<CODE_STRUCT> UDS_NEG_RESPONSE =
|
||||
{
|
||||
{0x10, "GENERAL_REJECT", "General rejection (no other codes matched)"},
|
||||
{0x11, "SERVICE_NOTSUPP", "ECU does not support this service code"},
|
||||
@@ -157,7 +158,6 @@ UDS_MESSAGE::UDS_MESSAGE()
|
||||
subFunc = 0;
|
||||
service = 0;
|
||||
subFuncLen = 1;
|
||||
extended = false;
|
||||
isErrorReply = false;
|
||||
}
|
||||
|
||||
@@ -177,35 +177,39 @@ UDS_HANDLER::~UDS_HANDLER()
|
||||
void UDS_HANDLER::gotISOTPFrame(ISOTP_MESSAGE msg)
|
||||
{
|
||||
qDebug() << "UDS handler got ISOTP frame";
|
||||
unsigned char *data = reinterpret_cast<unsigned char *>(msg.payload().data());
|
||||
int dataLen = msg.payload().count();
|
||||
UDS_MESSAGE udsMsg;
|
||||
udsMsg.bus = msg.bus;
|
||||
udsMsg.extended = msg.extended;
|
||||
udsMsg.ID = msg.ID;
|
||||
udsMsg.setExtendedFrameFormat(msg.hasExtendedFrameFormat());
|
||||
udsMsg.setFrameId(msg.frameId());
|
||||
udsMsg.isReceived = msg.isReceived;
|
||||
udsMsg.timestamp = msg.timestamp;
|
||||
udsMsg.actualSize = msg.actualSize;
|
||||
udsMsg.len = msg.len;
|
||||
if (msg.data.length() > 0) {
|
||||
udsMsg.service = msg.data.at(0);
|
||||
udsMsg.setTimeStamp(msg.timeStamp());
|
||||
udsMsg.reportedLength = msg.reportedLength;
|
||||
udsMsg.service = 0;
|
||||
udsMsg.subFunc = 0;
|
||||
udsMsg.subFuncLen = 0;
|
||||
udsMsg.isErrorReply = false;
|
||||
udsMsg.setPayload(msg.payload());
|
||||
if (dataLen > 0) {
|
||||
udsMsg.service = data[0];
|
||||
if (udsMsg.service == 0x7F)
|
||||
{
|
||||
udsMsg.isErrorReply = true;
|
||||
if (msg.data.length() > 1)
|
||||
if (dataLen > 1)
|
||||
{
|
||||
udsMsg.service = msg.data.at(1);
|
||||
if (msg.data.length() > 2) udsMsg.subFunc = msg.data.at(2);
|
||||
udsMsg.service = data[1];
|
||||
if (dataLen > 2) udsMsg.subFunc = data[2];
|
||||
else return;
|
||||
}
|
||||
else return;
|
||||
udsMsg.data = msg.data.mid(2, -1); //don't copy error byte nor service byte
|
||||
udsMsg.len -= 2; //and remove those bytes from the length reported too.
|
||||
udsMsg.payload().remove(0, 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
udsMsg.isErrorReply = false;
|
||||
if (msg.data.length() > 1) udsMsg.subFunc = msg.data.at(1);
|
||||
udsMsg.data = msg.data.mid(1, -1); //don't copy service byte
|
||||
udsMsg.len -= 1; //and remove service byte from length too.
|
||||
if (dataLen > 1) udsMsg.subFunc = data[1];
|
||||
udsMsg.payload().remove(0, 1);
|
||||
}
|
||||
}
|
||||
else return;
|
||||
@@ -240,7 +244,7 @@ void UDS_HANDLER::setReception(bool mode)
|
||||
|
||||
void UDS_HANDLER::sendUDSFrame(const UDS_MESSAGE &msg)
|
||||
{
|
||||
QVector<unsigned char> data;
|
||||
QByteArray data;
|
||||
if (msg.bus < 0) return;
|
||||
if (msg.bus >= CANConManager::getInstance()->getNumBuses()) return;
|
||||
if (msg.service > 0xFF) return;
|
||||
@@ -251,8 +255,8 @@ void UDS_HANDLER::sendUDSFrame(const UDS_MESSAGE &msg)
|
||||
data.append((msg.subFunc >> (8 * b)) & 0xFF);
|
||||
}
|
||||
|
||||
data.append(msg.data);
|
||||
isoHandler->sendISOTPFrame(msg.bus, msg.ID, data);
|
||||
data.append(msg.payload());
|
||||
isoHandler->sendISOTPFrame(msg.bus, msg.frameId(), data);
|
||||
|
||||
qDebug() << "Sent UDS service: " << getServiceShortDesc(msg.service) << " on bus " << msg.bus;
|
||||
}
|
||||
@@ -323,6 +327,8 @@ QString UDS_HANDLER::getDetailedMessageAnalysis(const UDS_MESSAGE &msg)
|
||||
bool isResponse = true;
|
||||
int dataSize;
|
||||
int addrSize;
|
||||
unsigned char *data = reinterpret_cast<unsigned char *>(msg.payload().data());
|
||||
int dataLen = msg.payload().length();
|
||||
|
||||
if (msg.isErrorReply)
|
||||
{
|
||||
@@ -358,15 +364,15 @@ QString UDS_HANDLER::getDetailedMessageAnalysis(const UDS_MESSAGE &msg)
|
||||
case UDS_SERVICES::DIAG_CONTROL + 0x40: //positive response
|
||||
buildString.append("Session Request: " + getLongDesc(UDS_DIAG_CTRL_SUB, msg.subFunc));
|
||||
//there should be four extra bytes now
|
||||
if (msg.data.length() < 5) //5 because subfunc codes are left in data so it starts with one subfunc byte
|
||||
if (dataLen < 5) //5 because subfunc codes are left in data so it starts with one subfunc byte
|
||||
{
|
||||
//buildString.append("\nReturned data payload wasn't at least \n4 bytes like it should have been");
|
||||
}
|
||||
else
|
||||
{
|
||||
int p2 = msg.data[1] * 256 + msg.data[2];
|
||||
int p2 = data[1] * 256 + data[2];
|
||||
buildString.append("\nP2MAX (Max Wait / Resp Time): " + QString::number(p2) + "ms");
|
||||
p2 = (msg.data[3] * 256 + msg.data[4]) * 10;
|
||||
p2 = (data[3] * 256 + data[4]) * 10;
|
||||
buildString.append("\nP2 Ext MAX: " + QString::number(p2) + "ms");
|
||||
}
|
||||
break;
|
||||
@@ -377,11 +383,11 @@ QString UDS_HANDLER::getDetailedMessageAnalysis(const UDS_MESSAGE &msg)
|
||||
case UDS_SERVICES::ECU_RESET + 0x40:
|
||||
buildString.append("Reset Type: " + getLongDesc(UDS_ECU_RESET_SUB, msg.subFunc));
|
||||
//There should be one additional byte which encodes power down time
|
||||
if (msg.data.length() > 1)
|
||||
if (dataLen > 1)
|
||||
{
|
||||
if (msg.data[1] < 0xFF)
|
||||
if (data[1] < 0xFF)
|
||||
{
|
||||
buildString.append("\nMinimum powered down time: " + QString::number(msg.data[1]));
|
||||
buildString.append("\nMinimum powered down time: " + QString::number(data[1]));
|
||||
}
|
||||
else buildString.append("\nPowerdown time not available");
|
||||
}
|
||||
@@ -390,11 +396,11 @@ QString UDS_HANDLER::getDetailedMessageAnalysis(const UDS_MESSAGE &msg)
|
||||
case UDS_SERVICES::COMM_CTRL:
|
||||
//Comm control has potentially a lot of parameters. control type, comm type, nodeID
|
||||
buildString.append("Control type: " + getLongDesc(UDS_COMM_CTRL_SUB, msg.subFunc));
|
||||
if (msg.data.length() > 1)
|
||||
buildString.append("\nComm Type: " + QString::number(msg.data[1])); //TODO: no attempt to interpret yet
|
||||
if (msg.data.length() > 3)
|
||||
if (dataLen > 1)
|
||||
buildString.append("\nComm Type: " + QString::number(data[1])); //TODO: no attempt to interpret yet
|
||||
if (dataLen > 3)
|
||||
{
|
||||
int nodeID = (msg.data[2] * 256 + msg.data[3]);
|
||||
int nodeID = (data[2] * 256 + data[3]);
|
||||
buildString.append("\nNode ID: " + Utility::formatHexNum(nodeID));
|
||||
}
|
||||
break;
|
||||
@@ -402,19 +408,19 @@ QString UDS_HANDLER::getDetailedMessageAnalysis(const UDS_MESSAGE &msg)
|
||||
if ((msg.subFunc % 2) == 1)
|
||||
{
|
||||
buildString.append("Seed request for security level: " + QString::number(msg.subFunc) + "\n");
|
||||
if (msg.data.length()> 1)
|
||||
if (dataLen > 1)
|
||||
{
|
||||
buildString.append("Data payload: ");
|
||||
for (int j = 1; j < msg.data.length(); j++) buildString.append(Utility::formatHexNum(msg.data[j]) + " ");
|
||||
for (int j = 1; j < dataLen; j++) buildString.append(Utility::formatHexNum(data[j]) + " ");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
buildString.append("Key sending for security level: " + QString::number(msg.subFunc - 1));
|
||||
if (msg.data.length()> 1) //and it sure as hell should be!
|
||||
if (dataLen > 1) //and it sure as hell should be!
|
||||
{
|
||||
buildString.append("KEY: ");
|
||||
for (int j = 1; j < msg.data.length(); j++) buildString.append(Utility::formatHexNum(msg.data[j]) + " ");
|
||||
for (int j = 1; j < dataLen; j++) buildString.append(Utility::formatHexNum(data[j]) + " ");
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -422,10 +428,10 @@ QString UDS_HANDLER::getDetailedMessageAnalysis(const UDS_MESSAGE &msg)
|
||||
if ((msg.subFunc % 2) == 1)
|
||||
{
|
||||
buildString.append("Seed response for security level: " + QString::number(msg.subFunc) + "\n");
|
||||
if (msg.data.length()> 1) //be kinda pointless if it weren't
|
||||
if (dataLen > 1) //be kinda pointless if it weren't
|
||||
{
|
||||
buildString.append("SEED: ");
|
||||
for (int j = 1; j < msg.data.length(); j++) buildString.append(Utility::formatHexNum(msg.data[j]) + " ");
|
||||
for (int j = 1; j < dataLen; j++) buildString.append(Utility::formatHexNum(data[j]) + " ");
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -436,33 +442,33 @@ QString UDS_HANDLER::getDetailedMessageAnalysis(const UDS_MESSAGE &msg)
|
||||
break;
|
||||
case UDS_SERVICES::READ_BY_ID:
|
||||
//parameter is groups of two bytes, each of which specify an ID to read
|
||||
if (msg.data.length() > 2)
|
||||
if (dataLen > 2)
|
||||
{
|
||||
uint32_t id;
|
||||
for (int i = 1; i < msg.data.length(); i = i + 2)
|
||||
for (int i = 1; i < dataLen; i = i + 2)
|
||||
{
|
||||
id = (msg.data[i] * 256) + msg.data[i+1];
|
||||
id = (data[i] * 256) + data[i+1];
|
||||
buildString.append("\nID to read: " + Utility::formatHexNum(id));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case UDS_SERVICES::READ_BY_ID + 0x40: //reply
|
||||
buildString.append("Reply is non-standard and so no decoding is done. The format is (ID) followed by how ever much data that ID returns, followed by more ID/data pairs if applicable.\nPayload: ");
|
||||
for (int i = 1; i < msg.data.length(); i++)
|
||||
for (int i = 1; i < dataLen; i++)
|
||||
{
|
||||
buildString.append(Utility::formatHexNum(msg.data[i]) + " ");
|
||||
buildString.append(Utility::formatHexNum(data[i]) + " ");
|
||||
}
|
||||
break;
|
||||
case UDS_SERVICES::READ_BY_ADDR:
|
||||
//subfunc byte specifies address and length format, then address, then size
|
||||
dataSize = msg.subFunc >> 4;
|
||||
addrSize = msg.subFunc & 0xF;
|
||||
if (msg.data.length() > (dataSize + addrSize))
|
||||
if (dataLen > (dataSize + addrSize))
|
||||
{
|
||||
buildString.append("Address: 0x");
|
||||
for (int i = 0; i < addrSize; i++) buildString.append(QString::number(msg.data[1+i], 16).toUpper().rightJustified(2,'0'));
|
||||
for (int i = 0; i < addrSize; i++) buildString.append(QString::number(data[1+i], 16).toUpper().rightJustified(2,'0'));
|
||||
buildString.append("\nSize: 0x");
|
||||
for (int i = 0; i < dataSize; i++) buildString.append(QString::number(msg.data[1+i+addrSize], 16).toUpper().rightJustified(2,'0'));
|
||||
for (int i = 0; i < dataSize; i++) buildString.append(QString::number(data[1+i+addrSize], 16).toUpper().rightJustified(2,'0'));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -471,44 +477,44 @@ QString UDS_HANDLER::getDetailedMessageAnalysis(const UDS_MESSAGE &msg)
|
||||
break;
|
||||
case UDS_SERVICES::READ_BY_ADDR + 0x40:
|
||||
buildString.append("Reply is a raw packet of data of the size requested.\nPayload: ");
|
||||
for (int i = 1; i < msg.data.length(); i++)
|
||||
for (int i = 1; i < dataLen; i++)
|
||||
{
|
||||
buildString.append(Utility::formatHexNum(msg.data[i]) + " ");
|
||||
buildString.append(Utility::formatHexNum(data[i]) + " ");
|
||||
}
|
||||
break;
|
||||
case UDS_SERVICES::WRITE_BY_ID:
|
||||
break;
|
||||
case UDS_SERVICES::ROUTINE_CTRL:
|
||||
buildString.append("Routine Control: " + getLongDesc(UDS_ROUTINE_SUB, msg.subFunc));
|
||||
if (msg.data.length() > 2)
|
||||
if (dataLen > 2)
|
||||
{
|
||||
int routineID;
|
||||
routineID = (msg.data[1] * 256 + msg.data[2]);
|
||||
routineID = (data[1] * 256 + data[2]);
|
||||
buildString.append("\nRoutine ID: " + Utility::formatHexNum(routineID));
|
||||
}
|
||||
if (msg.data.length() > 3)
|
||||
if (dataLen > 3)
|
||||
{
|
||||
buildString.append("\nParameter bytes to routine: ");
|
||||
for (int i = 4; i < msg.data.length(); i++)
|
||||
for (int i = 4; i < dataLen; i++)
|
||||
{
|
||||
buildString.append(Utility::formatHexNum(msg.data[i]) + " ");
|
||||
buildString.append(Utility::formatHexNum(data[i]) + " ");
|
||||
}
|
||||
}
|
||||
break;
|
||||
case UDS_SERVICES::ROUTINE_CTRL + 0x40:
|
||||
buildString.append("Routine Control: " + getLongDesc(UDS_ROUTINE_SUB, msg.subFunc));
|
||||
if (msg.data.length() > 2)
|
||||
if (dataLen > 2)
|
||||
{
|
||||
int routineID;
|
||||
routineID = (msg.data[1] * 256 + msg.data[2]);
|
||||
routineID = (data[1] * 256 + data[2]);
|
||||
buildString.append("\nRoutine ID: " + Utility::formatHexNum(routineID));
|
||||
}
|
||||
if (msg.data.length() > 3)
|
||||
if (dataLen > 3)
|
||||
{
|
||||
buildString.append("\nBytes returned by routine: ");
|
||||
for (int i = 4; i < msg.data.length(); i++)
|
||||
for (int i = 4; i < dataLen; i++)
|
||||
{
|
||||
buildString.append(Utility::formatHexNum(msg.data[i]) + " ");
|
||||
buildString.append(Utility::formatHexNum(data[i]) + " ");
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -65,7 +65,7 @@ namespace UDS_SERVICES
|
||||
|
||||
struct CODE_STRUCT
|
||||
{
|
||||
unsigned int code;
|
||||
int code;
|
||||
QString shortDesc;
|
||||
QString longDesc;
|
||||
};
|
||||
@@ -73,9 +73,9 @@ struct CODE_STRUCT
|
||||
class UDS_MESSAGE: public ISOTP_MESSAGE
|
||||
{
|
||||
public:
|
||||
unsigned int service;
|
||||
unsigned int subFunc;
|
||||
unsigned int subFuncLen;
|
||||
int service;
|
||||
int subFunc;
|
||||
int subFuncLen;
|
||||
bool isErrorReply;
|
||||
|
||||
UDS_MESSAGE();
|
||||
|
||||
@@ -4,35 +4,32 @@
|
||||
#include <QObject>
|
||||
#include <QVector>
|
||||
#include <stdint.h>
|
||||
#include <QCanBusFrame>
|
||||
|
||||
struct CANFrame
|
||||
//Now inherits from the built-in CAN frame class from Qt. This should be more future proof and easier to integrate with other code
|
||||
|
||||
struct CANFrame : public QCanBusFrame
|
||||
{
|
||||
public:
|
||||
uint32_t ID;
|
||||
uint32_t bus;
|
||||
bool extended;
|
||||
bool remote;
|
||||
int bus;
|
||||
bool isReceived; //did we receive this or send it?
|
||||
uint32_t len;
|
||||
unsigned char data[8];
|
||||
uint64_t timestamp;
|
||||
uint64_t timedelta;
|
||||
uint32_t frameCount; //used in overwrite mode
|
||||
|
||||
friend bool operator<(const CANFrame& l, const CANFrame& r)
|
||||
{
|
||||
return l.timestamp < r.timestamp;
|
||||
qint64 lStamp = l.timeStamp().seconds() * 1000000 + l.timeStamp().microSeconds();
|
||||
qint64 rStamp = r.timeStamp().seconds() * 1000000 + r.timeStamp().microSeconds();
|
||||
return lStamp < rStamp;
|
||||
}
|
||||
|
||||
CANFrame()
|
||||
{
|
||||
ID = 0;
|
||||
setFrameId(0);
|
||||
bus = 0;
|
||||
extended = false;
|
||||
remote = false;
|
||||
setExtendedFrameFormat(false);
|
||||
setFrameType(QCanBusFrame::DataFrame);
|
||||
isReceived = true;
|
||||
len = 0;
|
||||
timestamp = 0;
|
||||
timedelta = 0;
|
||||
frameCount = 1;
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ void CANFrameModel::setSysTimeMode(bool mode)
|
||||
}
|
||||
}
|
||||
|
||||
void CANFrameModel::setInterpetMode(bool mode)
|
||||
void CANFrameModel::setInterpretMode(bool mode)
|
||||
{
|
||||
//if the state of interpretFrames changes then we need to reset the model
|
||||
//so that QT will refresh the view properly
|
||||
@@ -114,6 +114,11 @@ void CANFrameModel::setInterpetMode(bool mode)
|
||||
}
|
||||
}
|
||||
|
||||
bool CANFrameModel::getInterpretMode()
|
||||
{
|
||||
return interpretFrames;
|
||||
}
|
||||
|
||||
void CANFrameModel::setTimeFormat(QString format)
|
||||
{
|
||||
Utility::timeFormat = format;
|
||||
@@ -128,23 +133,23 @@ void CANFrameModel::normalizeTiming()
|
||||
{
|
||||
mutex.lock();
|
||||
if (frames.count() == 0) return;
|
||||
timeOffset = frames[0].timestamp;
|
||||
timeOffset = frames[0].timeStamp().microSeconds();
|
||||
|
||||
//find the absolute lowest timestamp in the whole time. Needed because maybe timestamp was reset in the middle.
|
||||
for (int j = 0; j < frames.count(); j++)
|
||||
{
|
||||
if (frames[j].timestamp < timeOffset) timeOffset = frames[j].timestamp;
|
||||
if (frames[j].timeStamp().microSeconds() < timeOffset) timeOffset = frames[j].timeStamp().microSeconds();
|
||||
}
|
||||
|
||||
for (int i = 0; i < frames.count(); i++)
|
||||
{
|
||||
frames[i].timestamp -= timeOffset;
|
||||
frames[i].setTimeStamp(QCanBusFrame::TimeStamp(0, frames[i].timeStamp().microSeconds() - timeOffset));
|
||||
}
|
||||
|
||||
this->beginResetModel();
|
||||
for (int i = 0; i < filteredFrames.count(); i++)
|
||||
{
|
||||
filteredFrames[i].timestamp -= timeOffset;
|
||||
filteredFrames[i].setTimeStamp(QCanBusFrame::TimeStamp(0, filteredFrames[i].timeStamp().microSeconds() - timeOffset));
|
||||
}
|
||||
this->endResetModel();
|
||||
|
||||
@@ -190,29 +195,32 @@ uint64_t CANFrameModel::getCANFrameVal(int row, Column col)
|
||||
{
|
||||
case Column::TimeStamp:
|
||||
if (overwriteDups) return frame.timedelta;
|
||||
return frame.timestamp;
|
||||
return frame.timeStamp().microSeconds();
|
||||
case Column::FrameId:
|
||||
return frame.ID;
|
||||
return frame.frameId();
|
||||
case Column::Extended:
|
||||
if (frame.extended) return 1;
|
||||
if (frame.hasExtendedFrameFormat()) return 1;
|
||||
return 0;
|
||||
case Column::Remote:
|
||||
if (overwriteDups) return frame.frameCount;
|
||||
if (frame.remote) return 1;
|
||||
if (frame.frameType() == QCanBusFrame::RemoteRequestFrame) return 1;
|
||||
return 0;
|
||||
case Column::Direction:
|
||||
if (frame.isReceived) return 1;
|
||||
return 0;
|
||||
case Column::Bus:
|
||||
return frame.bus;
|
||||
return static_cast<uint64_t>(frame.bus);
|
||||
case Column::Length:
|
||||
return frame.len;
|
||||
return static_cast<uint64_t>(frame.payload().length());
|
||||
case Column::ASCII: //sort both the same for now
|
||||
case Column::Data:
|
||||
for (int i = 0; i < frame.len; i++) temp += ((uint64_t)frame.data[i] << (56 - (8 * i)));
|
||||
for (int i = 0; i < frame.payload().length(); i++) temp += (static_cast<uint64_t>(frame.payload()[i]) << (56 - (8 * i)));
|
||||
//qDebug() << temp;
|
||||
return temp;
|
||||
case Column::NUM_COLUMN:
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void CANFrameModel::qSortCANFrameAsc(QVector<CANFrame> *frames, Column column, int lowerBound, int upperBound)
|
||||
@@ -295,9 +303,6 @@ void CANFrameModel::recalcOverwrite()
|
||||
|
||||
qDebug() << "recalcOverwrite called in model";
|
||||
|
||||
int lastUnique = 0;
|
||||
bool found;
|
||||
|
||||
mutex.lock();
|
||||
beginResetModel();
|
||||
|
||||
@@ -306,7 +311,7 @@ void CANFrameModel::recalcOverwrite()
|
||||
uint64_t idAugmented; //id in lower 29 bits, bus number shifted up 29 bits
|
||||
foreach(CANFrame frame, frames)
|
||||
{
|
||||
idAugmented = frame.ID;
|
||||
idAugmented = frame.frameId();
|
||||
idAugmented = idAugmented + (frame.bus << 29ull);
|
||||
if (!overWriteFrames.contains(idAugmented))
|
||||
{
|
||||
@@ -316,7 +321,7 @@ void CANFrameModel::recalcOverwrite()
|
||||
}
|
||||
else
|
||||
{
|
||||
frame.timedelta = frame.timestamp - overWriteFrames[idAugmented].timestamp;
|
||||
frame.timedelta = frame.timeStamp().microSeconds() - overWriteFrames[idAugmented].timeStamp().microSeconds();
|
||||
frame.frameCount = overWriteFrames[idAugmented].frameCount + 1;
|
||||
overWriteFrames[idAugmented] = frame;
|
||||
}
|
||||
@@ -330,7 +335,7 @@ void CANFrameModel::recalcOverwrite()
|
||||
|
||||
for (int i = 0; i < frames.count(); i++)
|
||||
{
|
||||
if (filters[frames[i].ID])
|
||||
if (filters[frames[i].frameId()])
|
||||
{
|
||||
filteredFrames.append(frames[i]);
|
||||
}
|
||||
@@ -342,7 +347,6 @@ void CANFrameModel::recalcOverwrite()
|
||||
|
||||
QVariant CANFrameModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
int dLen;
|
||||
QString tempString;
|
||||
CANFrame thisFrame;
|
||||
static bool rowFlip = false;
|
||||
@@ -356,12 +360,15 @@ QVariant CANFrameModel::data(const QModelIndex &index, int role) const
|
||||
|
||||
thisFrame = filteredFrames.at(index.row());
|
||||
|
||||
unsigned char *data = reinterpret_cast<unsigned char *>(thisFrame.payload().data());
|
||||
int dataLen = thisFrame.payload().count();
|
||||
|
||||
if (role == Qt::BackgroundColorRole)
|
||||
{
|
||||
if (dbcHandler != NULL && interpretFrames)
|
||||
if (dbcHandler != nullptr && interpretFrames)
|
||||
{
|
||||
DBC_MESSAGE *msg = dbcHandler->findMessage(thisFrame);
|
||||
if (msg != NULL)
|
||||
if (msg != nullptr)
|
||||
{
|
||||
return msg->bgColor;
|
||||
}
|
||||
@@ -378,10 +385,10 @@ QVariant CANFrameModel::data(const QModelIndex &index, int role) const
|
||||
|
||||
if (role == Qt::TextColorRole)
|
||||
{
|
||||
if (dbcHandler != NULL && interpretFrames)
|
||||
if (dbcHandler != nullptr && interpretFrames)
|
||||
{
|
||||
DBC_MESSAGE *msg = dbcHandler->findMessage(thisFrame);
|
||||
if (msg != NULL)
|
||||
if (msg != nullptr)
|
||||
{
|
||||
return msg->fgColor;
|
||||
}
|
||||
@@ -399,17 +406,17 @@ QVariant CANFrameModel::data(const QModelIndex &index, int role) const
|
||||
if (timeSeconds) return QString::number(thisFrame.timedelta / 1000000.0, 'f', 5);
|
||||
return QString::number(thisFrame.timedelta);
|
||||
}
|
||||
else ts = Utility::formatTimestamp(thisFrame.timestamp);
|
||||
else ts = Utility::formatTimestamp(thisFrame.timeStamp().microSeconds());
|
||||
if (ts.type() == QVariant::Double) return QString::number(ts.toDouble(), 'f', 5); //never scientific notation, 5 decimal places
|
||||
if (ts.type() == QVariant::LongLong) return QString::number(ts.toLongLong()); //never scientific notion, all digits shown
|
||||
if (ts.type() == QVariant::DateTime) return ts.toDateTime().toString(timeFormat); //custom set format for dates and times
|
||||
return Utility::formatTimestamp(thisFrame.timestamp);
|
||||
return Utility::formatTimestamp(thisFrame.timeStamp().microSeconds());
|
||||
case Column::FrameId:
|
||||
return Utility::formatCANID(thisFrame.ID, thisFrame.extended);
|
||||
return Utility::formatCANID(thisFrame.frameId(), thisFrame.hasExtendedFrameFormat());
|
||||
case Column::Extended:
|
||||
return QString::number(thisFrame.extended);
|
||||
return QString::number(thisFrame.hasExtendedFrameFormat());
|
||||
case Column::Remote:
|
||||
if (!overwriteDups) return QString::number(thisFrame.remote);
|
||||
if (!overwriteDups) return QString::number(thisFrame.frameType() == QCanBusFrame::RemoteRequestFrame);
|
||||
return QString::number(thisFrame.frameCount);
|
||||
case Column::Direction:
|
||||
if (thisFrame.isReceived) return QString(tr("Rx"));
|
||||
@@ -417,47 +424,44 @@ QVariant CANFrameModel::data(const QModelIndex &index, int role) const
|
||||
case Column::Bus:
|
||||
return QString::number(thisFrame.bus);
|
||||
case Column::Length:
|
||||
return QString::number(thisFrame.len);
|
||||
return QString::number(dataLen);
|
||||
case Column::ASCII:
|
||||
if (thisFrame.ID >= 0x7FFFFFF0ull)
|
||||
if (thisFrame.frameId() >= 0x7FFFFFF0ull)
|
||||
{
|
||||
tempString.append("MARK ");
|
||||
tempString.append(QString::number(thisFrame.ID & 0x7));
|
||||
tempString.append(QString::number(thisFrame.frameId() & 0x7));
|
||||
return tempString;
|
||||
}
|
||||
dLen = thisFrame.len;
|
||||
if (!thisFrame.remote) {
|
||||
if (dLen < 0) dLen = 0;
|
||||
if (dLen > 8) dLen = 8;
|
||||
for (int i = 0; i < dLen; i++)
|
||||
if (thisFrame.frameType() != QCanBusFrame::RemoteRequestFrame) {
|
||||
if (dataLen < 0) dataLen = 0;
|
||||
//if (dLen > 8) dLen = 8;
|
||||
for (int i = 0; i < dataLen; i++)
|
||||
{
|
||||
quint8 byt = thisFrame.data[i];
|
||||
char byt = thisFrame.payload()[i];
|
||||
//0x20 through 0x7E are printable characters. Outside of that range they aren't. So use dots instead
|
||||
if (byt < 0x20) byt = 0x2E; //dot character
|
||||
if (byt > 0x7E) byt = 0x2E;
|
||||
tempString.append(QString::fromUtf8((char *)&byt, 1));
|
||||
tempString.append(QString::fromUtf8(&byt, 1));
|
||||
}
|
||||
}
|
||||
return tempString;
|
||||
case Column::Data:
|
||||
dLen = thisFrame.len;
|
||||
if (dLen < 0) dLen = 0;
|
||||
if (dLen > 8) dLen = 8;
|
||||
if (dataLen < 0) dataLen = 0;
|
||||
//if (useHexMode) tempString.append("0x ");
|
||||
if (thisFrame.remote) {
|
||||
if (thisFrame.frameType() == QCanBusFrame::RemoteRequestFrame) {
|
||||
return tempString;
|
||||
}
|
||||
for (int i = 0; i < dLen; i++)
|
||||
for (int i = 0; i < dataLen; i++)
|
||||
{
|
||||
if (useHexMode) tempString.append( QString::number(thisFrame.data[i], 16).toUpper().rightJustified(2, '0'));
|
||||
else tempString.append(QString::number(thisFrame.data[i], 10));
|
||||
if (useHexMode) tempString.append( QString::number(data[i], 16).toUpper().rightJustified(2, '0'));
|
||||
else tempString.append(QString::number(data[i], 10));
|
||||
tempString.append(" ");
|
||||
}
|
||||
//now, if we're supposed to interpret the data and the DBC handler is loaded then use it
|
||||
if (dbcHandler != NULL && interpretFrames)
|
||||
if (dbcHandler != nullptr && interpretFrames)
|
||||
{
|
||||
DBC_MESSAGE *msg = dbcHandler->findMessage(thisFrame);
|
||||
if (msg != NULL)
|
||||
if (msg != nullptr)
|
||||
{
|
||||
tempString.append(" <Interpreted>\n");
|
||||
tempString.append(msg->name + "\n" + msg->comment + "\n");
|
||||
@@ -547,25 +551,25 @@ void CANFrameModel::addFrame(const CANFrame& frame, bool autoRefresh = false)
|
||||
mutex.lock();
|
||||
CANFrame tempFrame;
|
||||
tempFrame = frame;
|
||||
tempFrame.timestamp -= timeOffset;
|
||||
tempFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, tempFrame.timeStamp().microSeconds() - timeOffset));
|
||||
|
||||
lastUpdateNumFrames++;
|
||||
|
||||
//if this ID isn't found in the filters list then add it and show it by default
|
||||
if (!filters.contains(tempFrame.ID))
|
||||
if (!filters.contains(tempFrame.frameId()))
|
||||
{
|
||||
// if there are any filters already configured, leave the new filter disabled
|
||||
if (any_filters_are_configured())
|
||||
filters.insert(tempFrame.ID, false);
|
||||
filters.insert(tempFrame.frameId(), false);
|
||||
else
|
||||
filters.insert(tempFrame.ID, true);
|
||||
filters.insert(tempFrame.frameId(), true);
|
||||
needFilterRefresh = true;
|
||||
}
|
||||
|
||||
if (!overwriteDups)
|
||||
{
|
||||
frames.append(tempFrame);
|
||||
if (filters[tempFrame.ID])
|
||||
if (filters[tempFrame.frameId()])
|
||||
{
|
||||
if (autoRefresh) beginInsertRows(QModelIndex(), filteredFrames.count(), filteredFrames.count());
|
||||
tempFrame.frameCount = 1;
|
||||
@@ -578,10 +582,10 @@ void CANFrameModel::addFrame(const CANFrame& frame, bool autoRefresh = false)
|
||||
bool found = false;
|
||||
for (int i = 0; i < frames.count(); i++)
|
||||
{
|
||||
if ( (frames[i].ID == tempFrame.ID) && (frames[i].bus == tempFrame.bus) )
|
||||
if ( (frames[i].frameId() == tempFrame.frameId()) && (frames[i].bus == tempFrame.bus) )
|
||||
{
|
||||
tempFrame.frameCount = frames[i].frameCount + 1;
|
||||
tempFrame.timedelta = tempFrame.timestamp - frames[i].timestamp;
|
||||
tempFrame.timedelta = tempFrame.timeStamp().microSeconds() - frames[i].timeStamp().microSeconds();
|
||||
frames.replace(i, tempFrame);
|
||||
found = true;
|
||||
break;
|
||||
@@ -590,7 +594,7 @@ void CANFrameModel::addFrame(const CANFrame& frame, bool autoRefresh = false)
|
||||
if (!found)
|
||||
{
|
||||
frames.append(tempFrame);
|
||||
if (filters[tempFrame.ID])
|
||||
if (filters[tempFrame.frameId()])
|
||||
{
|
||||
if (autoRefresh) beginInsertRows(QModelIndex(), filteredFrames.count(), filteredFrames.count());
|
||||
tempFrame.frameCount = 1;
|
||||
@@ -603,7 +607,7 @@ void CANFrameModel::addFrame(const CANFrame& frame, bool autoRefresh = false)
|
||||
{
|
||||
for (int j = 0; j < filteredFrames.count(); j++)
|
||||
{
|
||||
if ( (filteredFrames[j].ID == tempFrame.ID) && (filteredFrames[j].bus == tempFrame.bus) )
|
||||
if ( (filteredFrames[j].frameId() == tempFrame.frameId()) && (filteredFrames[j].bus == tempFrame.bus) )
|
||||
{
|
||||
if (autoRefresh) beginResetModel();
|
||||
filteredFrames.replace(j, tempFrame);
|
||||
@@ -637,7 +641,7 @@ void CANFrameModel::sendRefresh()
|
||||
int count = frames.count();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (filters[frames[i].ID])
|
||||
if (filters[frames[i].frameId()])
|
||||
{
|
||||
tempContainer.append(frames[i]);
|
||||
}
|
||||
@@ -713,12 +717,12 @@ void CANFrameModel::insertFrames(const QVector<CANFrame> &newFrames)
|
||||
for (int i = 0; i < newFrames.count(); i++)
|
||||
{
|
||||
frames.append(newFrames[i]);
|
||||
if (!filters.contains(newFrames[i].ID))
|
||||
if (!filters.contains(newFrames[i].frameId()))
|
||||
{
|
||||
filters.insert(newFrames[i].ID, true);
|
||||
filters.insert(newFrames[i].frameId(), true);
|
||||
needFilterRefresh = true;
|
||||
}
|
||||
if (filters[newFrames[i].ID])
|
||||
if (filters[newFrames[i].frameId()])
|
||||
{
|
||||
insertedFiltered++;
|
||||
filteredFrames.append(newFrames[i]);
|
||||
@@ -735,12 +739,12 @@ void CANFrameModel::insertFrames(const QVector<CANFrame> &newFrames)
|
||||
int CANFrameModel::getIndexFromTimeID(unsigned int ID, double timestamp)
|
||||
{
|
||||
int bestIndex = -1;
|
||||
uint64_t intTimeStamp = timestamp * 1000000l;
|
||||
int64_t intTimeStamp = static_cast<int64_t> (timestamp * 1000000l);
|
||||
for (int i = 0; i < frames.count(); i++)
|
||||
{
|
||||
if ((frames[i].ID == ID))
|
||||
if ((frames[i].frameId() == ID))
|
||||
{
|
||||
if (frames[i].timestamp <= intTimeStamp) bestIndex = i;
|
||||
if (frames[i].timeStamp().microSeconds() <= intTimeStamp) bestIndex = i;
|
||||
else break; //drop out of loop as soon as we pass the proper timestamp
|
||||
}
|
||||
}
|
||||
@@ -763,7 +767,7 @@ void CANFrameModel::loadFilterFile(QString filename)
|
||||
if (line.length() > 2)
|
||||
{
|
||||
QList<QByteArray> tokens = line.split(',');
|
||||
ID = tokens[0].toInt(NULL, 16);
|
||||
ID = tokens[0].toInt(nullptr, 16);
|
||||
if (tokens[1].toUpper() == "T") filters.insert(ID, true);
|
||||
else filters.insert(ID, false);
|
||||
}
|
||||
|
||||
@@ -42,7 +42,8 @@ public:
|
||||
void sendRefresh(int);
|
||||
int sendBulkRefresh();
|
||||
void clearFrames();
|
||||
void setInterpetMode(bool);
|
||||
void setInterpretMode(bool);
|
||||
bool getInterpretMode();
|
||||
void setOverwriteMode(bool);
|
||||
void setHexMode(bool);
|
||||
void setSysTimeMode(bool);
|
||||
@@ -87,7 +88,7 @@ private:
|
||||
bool timeSeconds;
|
||||
bool useSystemTime;
|
||||
bool needFilterRefresh;
|
||||
uint64_t timeOffset;
|
||||
int64_t timeOffset;
|
||||
int lastUpdateNumFrames;
|
||||
uint32_t preallocSize;
|
||||
bool sortDirAsc;
|
||||
|
||||
@@ -17,5 +17,5 @@ CANConnection* CanConFactory::create(type pType, QString pPortName, QString pDri
|
||||
default: {}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include "canconmanager.h"
|
||||
#include "canconfactory.h"
|
||||
|
||||
CANConManager* CANConManager::mInstance = NULL;
|
||||
CANConManager* CANConManager::mInstance = nullptr;
|
||||
|
||||
CANConManager* CANConManager::getInstance()
|
||||
{
|
||||
@@ -43,7 +43,7 @@ void CANConManager::resetTimeBasis()
|
||||
CANConManager::~CANConManager()
|
||||
{
|
||||
mTimer.stop();
|
||||
mInstance = NULL;
|
||||
mInstance = nullptr;
|
||||
}
|
||||
|
||||
void CANConManager::stopAllConnections()
|
||||
@@ -142,7 +142,7 @@ CANConnection* CANConManager::getByName(const QString& pName) const
|
||||
return conn_p;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -159,9 +159,9 @@ void CANConManager::refreshConnection(CANConnection* pConn_p)
|
||||
emit connectionStatusUpdated(buses);
|
||||
}
|
||||
|
||||
if (pConn_p->getQueue().peek() == NULL) return;
|
||||
if (pConn_p->getQueue().peek() == nullptr) return;
|
||||
|
||||
CANFrame* frame_p = NULL;
|
||||
CANFrame* frame_p = nullptr;
|
||||
QVector<CANFrame> frames;
|
||||
|
||||
//Each connection only knows about its own bus numbers
|
||||
@@ -220,11 +220,11 @@ bool CANConManager::sendFrame(const CANFrame& pFrame)
|
||||
workingFrame.isReceived = false;
|
||||
if (useSystemTime)
|
||||
{
|
||||
workingFrame.timestamp = (QDateTime::currentMSecsSinceEpoch() * 1000);
|
||||
workingFrame.setTimeStamp(QCanBusFrame::TimeStamp(0,QDateTime::currentMSecsSinceEpoch() * 1000));
|
||||
}
|
||||
else
|
||||
{
|
||||
workingFrame.timestamp = mElapsedTimer.nsecsElapsed() / 1000;
|
||||
workingFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, mElapsedTimer.nsecsElapsed() / 1000));
|
||||
//workingFrame.timestamp -= mTimestampBasis;
|
||||
}
|
||||
txFrame = conn->getQueue().get();
|
||||
@@ -253,13 +253,13 @@ bool CANConManager::sendFrames(const QList<CANFrame>& pFrames)
|
||||
//the bus numbers if bus wasn't -1 so that they're local to the device
|
||||
bool CANConManager::addTargettedFrame(int pBusId, uint32_t ID, uint32_t mask, QObject *receiver)
|
||||
{
|
||||
int tempBusVal;
|
||||
//int tempBusVal;
|
||||
int busBase = 0;
|
||||
|
||||
foreach (CANConnection* conn, mConns)
|
||||
{
|
||||
if (pBusId == -1) conn->addTargettedFrame(pBusId, ID, mask, receiver);
|
||||
else if (pBusId < (uint32_t)(busBase + conn->getNumBuses()))
|
||||
else if (pBusId < (busBase + conn->getNumBuses()))
|
||||
{
|
||||
qDebug() << "Forwarding targetted frame setting to a connection object";
|
||||
conn->addTargettedFrame(pBusId - busBase, ID, mask, receiver);
|
||||
@@ -272,13 +272,13 @@ bool CANConManager::addTargettedFrame(int pBusId, uint32_t ID, uint32_t mask, QO
|
||||
|
||||
bool CANConManager::removeTargettedFrame(int pBusId, uint32_t ID, uint32_t mask, QObject *receiver)
|
||||
{
|
||||
int tempBusVal;
|
||||
//int tempBusVal;
|
||||
int busBase = 0;
|
||||
|
||||
foreach (CANConnection* conn, mConns)
|
||||
{
|
||||
if (pBusId == -1) conn->removeTargettedFrame(pBusId, ID, mask, receiver);
|
||||
else if (pBusId < (uint32_t)(busBase + conn->getNumBuses()))
|
||||
else if (pBusId < (busBase + conn->getNumBuses()))
|
||||
{
|
||||
qDebug() << "Forwarding targetted frame setting to a connection object";
|
||||
conn->removeTargettedFrame(pBusId - busBase, ID, mask, receiver);
|
||||
|
||||
@@ -16,7 +16,7 @@ CANConnection::CANConnection(QString pPort,
|
||||
mIsCapSuspended(false),
|
||||
mStatus(CANCon::NOT_CONNECTED),
|
||||
mStarted(false),
|
||||
mThread_p(NULL)
|
||||
mThread_p(nullptr)
|
||||
{
|
||||
/* register types */
|
||||
qRegisterMetaType<CANBus>("CANBus");
|
||||
@@ -48,7 +48,7 @@ CANConnection::~CANConnection()
|
||||
mThread_p->quit();
|
||||
mThread_p->wait();
|
||||
delete mThread_p;
|
||||
mThread_p = NULL;
|
||||
mThread_p = nullptr;
|
||||
}
|
||||
|
||||
mBusData.clear();
|
||||
@@ -343,7 +343,7 @@ void CANConnection::checkTargettedFrame(CANFrame &frame)
|
||||
foreach (const CANFltObserver filt, mBusData[frame.bus].mTargettedFrames)
|
||||
{
|
||||
//qDebug() << "Checking filter with id " << filt.id << " mask " << filt.mask;
|
||||
maskedID = frame.ID & filt.mask;
|
||||
maskedID = frame.frameId() & filt.mask;
|
||||
if (maskedID == filt.id) {
|
||||
qDebug() << "In connection object I got a targetted frame. Forwarding it.";
|
||||
QMetaObject::invokeMethod(filt.observer, "gotTargettedFrame",Qt::QueuedConnection, Q_ARG(CANFrame, frame));
|
||||
|
||||
@@ -70,10 +70,10 @@ QVariant CANConnectionModel::data(const QModelIndex &index, int role) const
|
||||
//qDebug() << "Row: " << index.row();
|
||||
|
||||
CANConnection *conn_p = getAtIdx(index.row());
|
||||
bool ret;
|
||||
|
||||
if (!conn_p) return QVariant();
|
||||
|
||||
bool isSocketCAN = (conn_p->getType() == CANCon::SERIALBUS) ? true: false;
|
||||
//bool isSocketCAN = (conn_p->getType() == CANCon::SERIALBUS) ? true: false;
|
||||
|
||||
if (role == Qt::DisplayRole) {
|
||||
|
||||
@@ -87,11 +87,11 @@ QVariant CANConnectionModel::data(const QModelIndex &index, int role) const
|
||||
case CANCon::GVRET_SERIAL: return "GVRET";
|
||||
default: {}
|
||||
}
|
||||
else qDebug() << "Tried to show connection type but connection was NULL";
|
||||
else qDebug() << "Tried to show connection type but connection was nullptr";
|
||||
break;
|
||||
case Column::Port:
|
||||
if (conn_p) return conn_p->getPort();
|
||||
else qDebug() << "Tried to show connection port but connection was NULL";
|
||||
else qDebug() << "Tried to show connection port but connection was nullptr";
|
||||
break;
|
||||
case Column::Subtype:
|
||||
return conn_p->getDriver();
|
||||
@@ -138,7 +138,7 @@ void CANConnectionModel::replace(int idx , CANConnection* pConn_p)
|
||||
CANConnection* CANConnectionModel::getAtIdx(int pIdx) const
|
||||
{
|
||||
if (pIdx < 0)
|
||||
return NULL;
|
||||
return nullptr;
|
||||
|
||||
QList<CANConnection*>& conns = CANConManager::getInstance()->getConnections();
|
||||
|
||||
@@ -147,6 +147,8 @@ CANConnection* CANConnectionModel::getAtIdx(int pIdx) const
|
||||
|
||||
void CANConnectionModel::refresh(int pIndex)
|
||||
{
|
||||
Q_UNUSED(pIndex)
|
||||
|
||||
beginResetModel();
|
||||
endResetModel();
|
||||
/*
|
||||
|
||||
@@ -312,8 +312,8 @@ void ConnectionWindow::populateBusDetails(int offset)
|
||||
}
|
||||
else
|
||||
{
|
||||
bool ret;
|
||||
int numBuses;
|
||||
//bool ret;
|
||||
//int numBuses;
|
||||
|
||||
CANConnection* conn_p = connModel->getAtIdx(selIdx);
|
||||
CANBus bus;
|
||||
@@ -365,7 +365,7 @@ void ConnectionWindow::currentRowChanged(const QModelIndex ¤t, const QMode
|
||||
}
|
||||
else
|
||||
{
|
||||
bool ret;
|
||||
//bool ret;
|
||||
ui->groupBus->setEnabled(true);
|
||||
int numBuses;
|
||||
|
||||
|
||||
@@ -15,9 +15,9 @@ GVRetSerial::GVRetSerial(QString portName, bool useTcp) :
|
||||
{
|
||||
sendDebug("GVRetSerial()");
|
||||
|
||||
serial = NULL;
|
||||
tcpClient = NULL;
|
||||
udpClient = NULL;
|
||||
serial = nullptr;
|
||||
tcpClient = nullptr;
|
||||
udpClient = nullptr;
|
||||
rx_state = IDLE;
|
||||
rx_step = 0;
|
||||
validationCounter = 10; //how many times we can miss validation before we die
|
||||
@@ -46,7 +46,7 @@ void GVRetSerial::sendDebug(const QString debugText)
|
||||
|
||||
void GVRetSerial::sendToSerial(const QByteArray &bytes)
|
||||
{
|
||||
if (serial == NULL && tcpClient == NULL && udpClient == NULL)
|
||||
if (serial == nullptr && tcpClient == nullptr && udpClient == nullptr)
|
||||
{
|
||||
sendDebug("Attempt to write to serial port when it has not been initialized!");
|
||||
return;
|
||||
@@ -192,14 +192,14 @@ void GVRetSerial::piSetBusSettings(int pBusIdx, CANBus bus)
|
||||
sendDebug("Got signal to update bauds. 1: " + QString::number((can0Baud & 0xFFFFFFF)) + " 2: " + QString::number((can1Baud & 0xFFFFFFF)));
|
||||
buffer[0] = (char)0xF1; //start of a command over serial
|
||||
buffer[1] = 5; //setup canbus
|
||||
buffer[2] = (unsigned char)(can0Baud & 0xFF); //four bytes of ID LSB first
|
||||
buffer[3] = (unsigned char)(can0Baud >> 8);
|
||||
buffer[4] = (unsigned char)(can0Baud >> 16);
|
||||
buffer[5] = (unsigned char)(can0Baud >> 24);
|
||||
buffer[6] = (unsigned char)(can1Baud & 0xFF); //four bytes of ID LSB first
|
||||
buffer[7] = (unsigned char)(can1Baud >> 8);
|
||||
buffer[8] = (unsigned char)(can1Baud >> 16);
|
||||
buffer[9] = (unsigned char)(can1Baud >> 24);
|
||||
buffer[2] = (char)(can0Baud & 0xFF); //four bytes of ID LSB first
|
||||
buffer[3] = (char)(can0Baud >> 8);
|
||||
buffer[4] = (char)(can0Baud >> 16);
|
||||
buffer[5] = (char)(can0Baud >> 24);
|
||||
buffer[6] = (char)(can1Baud & 0xFF); //four bytes of ID LSB first
|
||||
buffer[7] = (char)(can1Baud >> 8);
|
||||
buffer[8] = (char)(can1Baud >> 16);
|
||||
buffer[9] = (char)(can1Baud >> 24);
|
||||
buffer[10] = 0;
|
||||
sendToSerial(buffer);
|
||||
}
|
||||
@@ -210,18 +210,18 @@ void GVRetSerial::piSetBusSettings(int pBusIdx, CANBus bus)
|
||||
sendDebug("Got signal to update extended bus speeds SWCAN: " + QString::number(swcanBaud) + " LIN1: " + QString::number(lin1Baud) + " LIN2: " + QString::number(lin2Baud));
|
||||
buffer[0] = (char)0xF1; //start of a command over serial
|
||||
buffer[1] = 14; //setup extended buses
|
||||
buffer[2] = (unsigned char)(swcanBaud & 0xFF); //four bytes of ID LSB first
|
||||
buffer[3] = (unsigned char)(swcanBaud >> 8);
|
||||
buffer[4] = (unsigned char)(swcanBaud >> 16);
|
||||
buffer[5] = (unsigned char)(swcanBaud >> 24);
|
||||
buffer[6] = (unsigned char)(lin1Baud & 0xFF); //four bytes of ID LSB first
|
||||
buffer[7] = (unsigned char)(lin1Baud >> 8);
|
||||
buffer[8] = (unsigned char)(lin1Baud >> 16);
|
||||
buffer[9] = (unsigned char)(lin1Baud >> 24);
|
||||
buffer[10] = (unsigned char)(lin2Baud & 0xFF); //four bytes of ID LSB first
|
||||
buffer[11] = (unsigned char)(lin2Baud >> 8);
|
||||
buffer[12] = (unsigned char)(lin2Baud >> 16);
|
||||
buffer[13] = (unsigned char)(lin2Baud >> 24);
|
||||
buffer[2] = (char)(swcanBaud & 0xFF); //four bytes of ID LSB first
|
||||
buffer[3] = (char)(swcanBaud >> 8);
|
||||
buffer[4] = (char)(swcanBaud >> 16);
|
||||
buffer[5] = (char)(swcanBaud >> 24);
|
||||
buffer[6] = (char)(lin1Baud & 0xFF); //four bytes of ID LSB first
|
||||
buffer[7] = (char)(lin1Baud >> 8);
|
||||
buffer[8] = (char)(lin1Baud >> 16);
|
||||
buffer[9] = (char)(lin1Baud >> 24);
|
||||
buffer[10] = (char)(lin2Baud & 0xFF); //four bytes of ID LSB first
|
||||
buffer[11] = (char)(lin2Baud >> 8);
|
||||
buffer[12] = (char)(lin2Baud >> 16);
|
||||
buffer[13] = (char)(lin2Baud >> 24);
|
||||
buffer[14] = 0;
|
||||
sendToSerial(buffer);
|
||||
}
|
||||
@@ -231,14 +231,14 @@ void GVRetSerial::piSetBusSettings(int pBusIdx, CANBus bus)
|
||||
bool GVRetSerial::piSendFrame(const CANFrame& frame)
|
||||
{
|
||||
QByteArray buffer;
|
||||
unsigned int c;
|
||||
int ID;
|
||||
int c;
|
||||
quint32 ID;
|
||||
|
||||
//qDebug() << "Sending out GVRET frame with id " << frame.ID << " on bus " << frame.bus;
|
||||
|
||||
framesRapid++;
|
||||
|
||||
if (serial == NULL && tcpClient == NULL && udpClient == NULL) return false;
|
||||
if (serial == nullptr && tcpClient == nullptr && udpClient == nullptr) return false;
|
||||
if (serial && !serial->isOpen()) return false;
|
||||
if (tcpClient && !tcpClient->isOpen()) return false;
|
||||
if (udpClient && !udpClient->isOpen()) return false;
|
||||
@@ -246,25 +246,25 @@ bool GVRetSerial::piSendFrame(const CANFrame& frame)
|
||||
|
||||
// Doesn't make sense to send an error frame
|
||||
// to an adapter
|
||||
if (frame.ID & 0x20000000) {
|
||||
if (frame.frameId() & 0x20000000) {
|
||||
return true;
|
||||
}
|
||||
ID = frame.ID;
|
||||
if (frame.extended) ID |= 1 << 31;
|
||||
ID = frame.frameId();
|
||||
if (frame.hasExtendedFrameFormat()) ID |= 1u << 31;
|
||||
|
||||
buffer[0] = (unsigned char)0xF1; //start of a command over serial
|
||||
buffer[0] = (char)0xF1; //start of a command over serial
|
||||
buffer[1] = 0; //command ID for sending a CANBUS frame
|
||||
buffer[2] = (unsigned char)(ID & 0xFF); //four bytes of ID LSB first
|
||||
buffer[3] = (unsigned char)(ID >> 8);
|
||||
buffer[4] = (unsigned char)(ID >> 16);
|
||||
buffer[5] = (unsigned char)(ID >> 24);
|
||||
buffer[6] = (unsigned char)((frame.bus) & 3);
|
||||
buffer[7] = (unsigned char)frame.len;
|
||||
for (c = 0; c < frame.len; c++)
|
||||
buffer[2] = (char)(ID & 0xFF); //four bytes of ID LSB first
|
||||
buffer[3] = (char)(ID >> 8);
|
||||
buffer[4] = (char)(ID >> 16);
|
||||
buffer[5] = (char)(ID >> 24);
|
||||
buffer[6] = (char)((frame.bus) & 3);
|
||||
buffer[7] = (char)frame.payload().length();
|
||||
for (c = 0; c < frame.payload().length(); c++)
|
||||
{
|
||||
buffer[8 + c] = frame.data[c];
|
||||
buffer[8 + c] = frame.payload()[c];
|
||||
}
|
||||
buffer[8 + frame.len] = 0;
|
||||
buffer[8 + frame.payload().length()] = 0;
|
||||
|
||||
sendToSerial(buffer);
|
||||
|
||||
@@ -375,18 +375,18 @@ void GVRetSerial::deviceConnected()
|
||||
{
|
||||
sendDebug("Connecting to GVRET Device!");
|
||||
QByteArray output;
|
||||
output.append((unsigned char)0xE7); //this puts the device into binary comm mode
|
||||
output.append((unsigned char)0xE7);
|
||||
output.append((char)0xE7); //this puts the device into binary comm mode
|
||||
output.append((char)0xE7);
|
||||
|
||||
output.append((unsigned char)0xF1);
|
||||
output.append((unsigned char)0x0C); //get number of actually implemented buses. Not implemented except on M2RET
|
||||
output.append((char)0xF1);
|
||||
output.append((char)0x0C); //get number of actually implemented buses. Not implemented except on M2RET
|
||||
mNumBuses = 2; //the proper number if C/12 is not implemented
|
||||
|
||||
output.append((unsigned char)0xF1); //signal we want to issue a command
|
||||
output.append((unsigned char)0x06); //request canbus stats from the board
|
||||
output.append((char)0xF1); //signal we want to issue a command
|
||||
output.append((char)0x06); //request canbus stats from the board
|
||||
|
||||
output.append((unsigned char)0xF1); //another command to the GVRET
|
||||
output.append((unsigned char)0x07); //request device information
|
||||
output.append((char)0xF1); //another command to the GVRET
|
||||
output.append((char)0x07); //request device information
|
||||
|
||||
/*output.append((char)0xF1);
|
||||
output.append((char)0x08); //setting singlewire mode
|
||||
@@ -399,11 +399,11 @@ void GVRetSerial::deviceConnected()
|
||||
output.append((char)0xFF); //signal we don't want single wire mode
|
||||
}*/
|
||||
|
||||
output.append((unsigned char)0xF1); //and another command
|
||||
output.append((unsigned char)0x01); //Time Sync - Not implemented until 333 but we can try
|
||||
output.append((char)0xF1); //and another command
|
||||
output.append((char)0x01); //Time Sync - Not implemented until 333 but we can try
|
||||
|
||||
output.append((unsigned char)0xF1); //yet another command
|
||||
output.append((unsigned char)0x09); //comm validation command
|
||||
output.append((char)0xF1); //yet another command
|
||||
output.append((char)0x09); //comm validation command
|
||||
|
||||
continuousTimeSync = true;
|
||||
|
||||
@@ -422,7 +422,7 @@ void GVRetSerial::deviceConnected()
|
||||
}
|
||||
|
||||
void GVRetSerial::disconnectDevice() {
|
||||
if (serial != NULL)
|
||||
if (serial != nullptr)
|
||||
{
|
||||
if (serial->isOpen())
|
||||
{
|
||||
@@ -432,9 +432,9 @@ void GVRetSerial::disconnectDevice() {
|
||||
}
|
||||
serial->disconnect(); //disconnect all signals
|
||||
delete serial;
|
||||
serial = NULL;
|
||||
serial = nullptr;
|
||||
}
|
||||
if (tcpClient != NULL)
|
||||
if (tcpClient != nullptr)
|
||||
{
|
||||
if (tcpClient->isOpen())
|
||||
{
|
||||
@@ -442,9 +442,9 @@ void GVRetSerial::disconnectDevice() {
|
||||
}
|
||||
tcpClient->disconnect();
|
||||
delete tcpClient;
|
||||
tcpClient = NULL;
|
||||
tcpClient = nullptr;
|
||||
}
|
||||
if (udpClient != NULL)
|
||||
if (udpClient != nullptr)
|
||||
{
|
||||
if (udpClient->isOpen())
|
||||
{
|
||||
@@ -452,7 +452,7 @@ void GVRetSerial::disconnectDevice() {
|
||||
}
|
||||
udpClient->disconnect();
|
||||
delete udpClient;
|
||||
udpClient = NULL;
|
||||
udpClient = nullptr;
|
||||
}
|
||||
|
||||
setStatus(CANCon::NOT_CONNECTED);
|
||||
@@ -659,57 +659,59 @@ void GVRetSerial::procRXChar(unsigned char c)
|
||||
switch (rx_step)
|
||||
{
|
||||
case 0:
|
||||
buildFrame.timestamp = c;
|
||||
buildTimestamp = c;
|
||||
break;
|
||||
case 1:
|
||||
buildFrame.timestamp |= (uint)(c << 8);
|
||||
buildTimestamp |= (uint)(c << 8);
|
||||
break;
|
||||
case 2:
|
||||
buildFrame.timestamp |= (uint)c << 16;
|
||||
buildTimestamp |= (uint)c << 16;
|
||||
break;
|
||||
case 3:
|
||||
buildFrame.timestamp |= (uint)c << 24;
|
||||
buildTimestamp |= (uint)c << 24;
|
||||
|
||||
buildFrame.timestamp += timeBasis;
|
||||
buildTimestamp += timeBasis;
|
||||
if (useSystemTime)
|
||||
{
|
||||
buildFrame.timestamp = QDateTime::currentMSecsSinceEpoch() * 1000l;
|
||||
buildTimestamp = QDateTime::currentMSecsSinceEpoch() * 1000l;
|
||||
}
|
||||
buildFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, buildTimestamp));
|
||||
break;
|
||||
case 4:
|
||||
buildFrame.ID = c;
|
||||
buildId = c;
|
||||
break;
|
||||
case 5:
|
||||
buildFrame.ID |= c << 8;
|
||||
buildId |= c << 8;
|
||||
break;
|
||||
case 6:
|
||||
buildFrame.ID |= c << 16;
|
||||
buildId |= c << 16;
|
||||
break;
|
||||
case 7:
|
||||
buildFrame.ID |= c << 24;
|
||||
if ((buildFrame.ID & 1 << 31) == 1u << 31)
|
||||
buildId |= c << 24;
|
||||
if ((buildId & 1 << 31) == 1u << 31)
|
||||
{
|
||||
buildFrame.ID &= 0x7FFFFFFF;
|
||||
buildFrame.extended = true;
|
||||
buildId &= 0x7FFFFFFF;
|
||||
buildFrame.setExtendedFrameFormat(true);
|
||||
}
|
||||
else buildFrame.extended = false;
|
||||
else buildFrame.setExtendedFrameFormat(false);
|
||||
buildFrame.setFrameId(buildId);
|
||||
break;
|
||||
case 8:
|
||||
buildFrame.len = c & 0xF;
|
||||
if (buildFrame.len > 8) buildFrame.len = 8;
|
||||
buildData.resize(c & 0xF);
|
||||
buildFrame.bus = (c & 0xF0) >> 4;
|
||||
break;
|
||||
default:
|
||||
if (rx_step < buildFrame.len + 9)
|
||||
if (rx_step < buildData.length() + 9)
|
||||
{
|
||||
buildFrame.data[rx_step - 9] = c;
|
||||
buildData[rx_step - 9] = c;
|
||||
}
|
||||
else
|
||||
{
|
||||
rx_state = IDLE;
|
||||
rx_step = 0;
|
||||
buildFrame.isReceived = true;
|
||||
|
||||
buildFrame.setPayload(buildData);
|
||||
buildFrame.setFrameType(QCanBusFrame::FrameType::DataFrame);
|
||||
if (!isCapSuspended())
|
||||
{
|
||||
/* get frame from queue */
|
||||
@@ -717,8 +719,7 @@ void GVRetSerial::procRXChar(unsigned char c)
|
||||
if(frame_p) {
|
||||
//qDebug() << "GVRET got frame on bus " << frame_p->bus;
|
||||
/* copy frame */
|
||||
*frame_p = buildFrame;
|
||||
frame_p->remote = false;
|
||||
*frame_p = buildFrame;
|
||||
checkTargettedFrame(buildFrame);
|
||||
/* enqueue frame */
|
||||
getQueue().queue();
|
||||
@@ -941,6 +942,7 @@ void GVRetSerial::procRXChar(unsigned char c)
|
||||
break;
|
||||
case 9:
|
||||
lin1Baud |= c << 24;
|
||||
break;
|
||||
case 10:
|
||||
lin2Enabled = (c & 0xF);
|
||||
break;
|
||||
@@ -1004,7 +1006,7 @@ void GVRetSerial::handleTick()
|
||||
//qDebug() << validationCounter;
|
||||
if (validationCounter == 0 && doValidation)
|
||||
{
|
||||
if (serial == NULL && tcpClient == NULL) return;
|
||||
if (serial == nullptr && tcpClient == nullptr) return;
|
||||
if ( (serial && serial->isOpen()) || (tcpClient && tcpClient->isOpen()) || (udpClient && udpClient->isOpen())) //if it's still false we have a problem...
|
||||
{
|
||||
sendDebug("Comm validation failed.");
|
||||
@@ -1018,7 +1020,10 @@ void GVRetSerial::handleTick()
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (doValidation); //qDebug() << "Comm connection validated";
|
||||
else if (doValidation)
|
||||
{
|
||||
//qDebug() << "Comm connection validated";
|
||||
}
|
||||
}
|
||||
if (doValidation && serial && serial->isOpen()) sendCommValidation();
|
||||
if (doValidation && tcpClient && tcpClient->isOpen()) sendCommValidation();
|
||||
|
||||
@@ -93,6 +93,9 @@ protected:
|
||||
STATE rx_state;
|
||||
uint32_t rx_step;
|
||||
CANFrame buildFrame;
|
||||
qint64 buildTimestamp;
|
||||
quint32 buildId;
|
||||
QByteArray buildData;
|
||||
int can0Baud, can1Baud, swcanBaud, lin1Baud, lin2Baud;
|
||||
bool can0Enabled, can1Enabled, swcanEnabled, lin1Enabled, lin2Enabled;
|
||||
bool can0ListenOnly, can1ListenOnly, swcanListenOnly;
|
||||
|
||||
@@ -77,7 +77,7 @@ bool SerialBusConnection::piGetBusSettings(int pBusIdx, CANBus& pBus)
|
||||
|
||||
void SerialBusConnection::piSetBusSettings(int pBusIdx, CANBus bus)
|
||||
{
|
||||
CANConStatus stats;
|
||||
//CANConStatus stats;
|
||||
/* sanity checks */
|
||||
if(0 != pBusIdx)
|
||||
return;
|
||||
@@ -115,22 +115,11 @@ void SerialBusConnection::piSetBusSettings(int pBusIdx, CANBus bus)
|
||||
bool SerialBusConnection::piSendFrame(const CANFrame& pFrame)
|
||||
{
|
||||
/* sanity checks */
|
||||
if(0 != pFrame.bus || pFrame.len>8)
|
||||
if(0 != pFrame.bus /*|| pFrame.len>8*/)
|
||||
return false;
|
||||
if (!mDev_p) return false;
|
||||
|
||||
/* fill frame */
|
||||
QCanBusFrame frame;
|
||||
frame.setExtendedFrameFormat(pFrame.extended);
|
||||
frame.setFrameId(pFrame.ID);
|
||||
if (pFrame.remote) {
|
||||
frame.setFrameType(QCanBusFrame::FrameType::RemoteRequestFrame);
|
||||
} else {
|
||||
frame.setFrameType(QCanBusFrame::FrameType::DataFrame);
|
||||
}
|
||||
frame.setPayload(QByteArray(reinterpret_cast<const char *>(pFrame.data),
|
||||
static_cast<int>(pFrame.len)));
|
||||
return mDev_p->writeFrame(frame);
|
||||
return mDev_p->writeFrame(pFrame);
|
||||
}
|
||||
|
||||
|
||||
@@ -193,14 +182,16 @@ void SerialBusConnection::framesReceived()
|
||||
if (recFrame.payload().length() <= 8) {
|
||||
CANFrame* frame_p = getQueue().get();
|
||||
if(frame_p) {
|
||||
frame_p->len = static_cast<uint32_t>(recFrame.payload().length());
|
||||
frame_p->bus = 0;
|
||||
frame_p->setPayload(recFrame.payload());
|
||||
frame_p->bus = 0;
|
||||
|
||||
if (recFrame.frameType() == QCanBusFrame::ErrorFrame) {
|
||||
// Constants defined in include/uapi/linux/can/error.h
|
||||
//since the whole program uses QCanBusFrame now we can just grab errors directly out of the proper place from now on
|
||||
/*
|
||||
switch (recFrame.error()) {
|
||||
case QCanBusFrame::TransmissionTimeoutError:
|
||||
frame_p->ID = 0x20000001;
|
||||
frame_p->setFrameId(0x20000001);
|
||||
break;
|
||||
case QCanBusFrame::LostArbitrationError:
|
||||
frame_p->ID = 0x20000002;
|
||||
@@ -230,19 +221,21 @@ void SerialBusConnection::framesReceived()
|
||||
break;
|
||||
}
|
||||
frame_p->extended = true;
|
||||
*/
|
||||
} else {
|
||||
frame_p->extended = recFrame.hasExtendedFrameFormat();
|
||||
frame_p->ID = recFrame.frameId();
|
||||
frame_p->remote = (recFrame.frameType() == recFrame.RemoteRequestFrame);
|
||||
memcpy(frame_p->data, recFrame.payload().data(), frame_p->len);
|
||||
frame_p->setExtendedFrameFormat(recFrame.hasExtendedFrameFormat());
|
||||
frame_p->setFrameId(recFrame.frameId());
|
||||
frame_p->setTimeStamp(recFrame.timeStamp());
|
||||
frame_p->setFrameType(recFrame.frameType());
|
||||
frame_p->setError(recFrame.error());
|
||||
frame_p->isReceived = true;
|
||||
}
|
||||
|
||||
frame_p->isReceived = true;
|
||||
if (useSystemTime) {
|
||||
frame_p->timestamp = QDateTime::currentMSecsSinceEpoch() * 1000ul;
|
||||
frame_p->setTimeStamp(QCanBusFrame::TimeStamp(0, QDateTime::currentMSecsSinceEpoch() * 1000ul));
|
||||
}
|
||||
else frame_p->timestamp = (recFrame.timeStamp().seconds() * 1000000ul + recFrame.timeStamp().microSeconds()) - timeBasis;
|
||||
else frame_p->setTimeStamp(QCanBusFrame::TimeStamp(0, (recFrame.timeStamp().seconds() * 1000000ul + recFrame.timeStamp().microSeconds()) - timeBasis));
|
||||
|
||||
checkTargettedFrame(*frame_p);
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ bool DBC_SIGNAL::processAsText(const CANFrame &frame, QString &outString, bool o
|
||||
QString buildString;
|
||||
int startByte = startBit / 8;
|
||||
int bytes = signalSize / 8;
|
||||
for (int x = 0; x < bytes; x++) buildString.append(frame.data[startByte + x]);
|
||||
for (int x = 0; x < bytes; x++) buildString.append(frame.payload().data()[startByte + x]);
|
||||
outString = buildString;
|
||||
cachedValue = outString;
|
||||
return true;
|
||||
@@ -56,7 +56,7 @@ bool DBC_SIGNAL::processAsText(const CANFrame &frame, QString &outString, bool o
|
||||
//if this is a multiplexed signal then we have to see if it is even found in the current message
|
||||
if (isMultiplexed)
|
||||
{
|
||||
if (parentMessage->multiplexorSignal != NULL)
|
||||
if (parentMessage->multiplexorSignal != nullptr)
|
||||
{
|
||||
int val;
|
||||
if (!parentMessage->multiplexorSignal->processAsInt(frame, val)) return false;
|
||||
@@ -68,7 +68,7 @@ bool DBC_SIGNAL::processAsText(const CANFrame &frame, QString &outString, bool o
|
||||
if (valType == SIGNED_INT) isSigned = true;
|
||||
if (valType == SIGNED_INT || valType == UNSIGNED_INT)
|
||||
{
|
||||
result = Utility::processIntegerSignal(frame.data, startBit, signalSize, intelByteOrder, isSigned);
|
||||
result = Utility::processIntegerSignal(frame.payload(), startBit, signalSize, intelByteOrder, isSigned);
|
||||
endResult = ((double)result * factor) + bias;
|
||||
result = (int64_t)endResult;
|
||||
}
|
||||
@@ -79,19 +79,19 @@ bool DBC_SIGNAL::processAsText(const CANFrame &frame, QString &outString, bool o
|
||||
//that the bytes that make up the integer are instead treated as having made up
|
||||
//a 32 bit single precision float. That's evil incarnate but it is very fast and small
|
||||
//in terms of new code.
|
||||
result = Utility::processIntegerSignal(frame.data, startBit, 32, false, false);
|
||||
result = Utility::processIntegerSignal(frame.payload(), startBit, 32, intelByteOrder, false);
|
||||
endResult = (*((float *)(&result)) * factor) + bias;
|
||||
}
|
||||
else //double precision float
|
||||
{
|
||||
if ( frame.len < 8 )
|
||||
if ( frame.payload().length() < 8 )
|
||||
{
|
||||
result = 0;
|
||||
return false;
|
||||
}
|
||||
//like the above, this is rotten and evil and wrong in so many ways. Force
|
||||
//calculation of a 64 bit integer and then cast it into a double.
|
||||
result = Utility::processIntegerSignal(frame.data, 0, 64, false, false);
|
||||
result = Utility::processIntegerSignal(frame.payload(), 0, 64, intelByteOrder, false);
|
||||
endResult = (*((double *)(&result)) * factor) + bias;
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ bool DBC_SIGNAL::processAsInt(const CANFrame &frame, int32_t &outValue)
|
||||
//if this is a multiplexed signal then we have to see if it is even found in the current message
|
||||
if (isMultiplexed)
|
||||
{
|
||||
if (parentMessage->multiplexorSignal != NULL)
|
||||
if (parentMessage->multiplexorSignal != nullptr)
|
||||
{
|
||||
int val;
|
||||
if (!parentMessage->multiplexorSignal->processAsInt(frame, val)) return false;
|
||||
@@ -155,15 +155,16 @@ bool DBC_SIGNAL::processAsInt(const CANFrame &frame, int32_t &outValue)
|
||||
}
|
||||
|
||||
if (valType == SIGNED_INT) isSigned = true;
|
||||
if ( frame.len*8 < (startBit+signalSize) )
|
||||
if ( static_cast<int>(frame.payload().length() * 8) < (startBit + signalSize) )
|
||||
{
|
||||
result = 0;
|
||||
return false;
|
||||
}
|
||||
result = Utility::processIntegerSignal(frame.data, startBit, signalSize, intelByteOrder, isSigned);
|
||||
|
||||
double endResult = ((double)result * factor) + bias;
|
||||
result = (int32_t)endResult;
|
||||
result = static_cast<int32_t>(Utility::processIntegerSignal(frame.payload(), startBit, signalSize, intelByteOrder, isSigned));
|
||||
|
||||
double endResult = (result * factor) + bias;
|
||||
result = static_cast<int32_t>(endResult);
|
||||
cachedValue = result;
|
||||
outValue = result;
|
||||
return true;
|
||||
@@ -187,7 +188,7 @@ bool DBC_SIGNAL::processAsDouble(const CANFrame &frame, double &outValue)
|
||||
//if this is a multiplexed signal then we have to see if it is even found in the current message
|
||||
if (isMultiplexed)
|
||||
{
|
||||
if (parentMessage->multiplexorSignal != NULL)
|
||||
if (parentMessage->multiplexorSignal != nullptr)
|
||||
{
|
||||
int val;
|
||||
if (!parentMessage->multiplexorSignal->processAsInt(frame, val)) return false;
|
||||
@@ -199,19 +200,19 @@ bool DBC_SIGNAL::processAsDouble(const CANFrame &frame, double &outValue)
|
||||
if (valType == SIGNED_INT) isSigned = true;
|
||||
if (valType == SIGNED_INT || valType == UNSIGNED_INT)
|
||||
{
|
||||
if ( frame.len*8 < (startBit+signalSize) )
|
||||
if ( frame.payload().length() * 8 < (startBit+signalSize) )
|
||||
{
|
||||
result = 0;
|
||||
return false;
|
||||
}
|
||||
result = Utility::processIntegerSignal(frame.data, startBit, signalSize, intelByteOrder, isSigned);
|
||||
result = Utility::processIntegerSignal(frame.payload(), startBit, signalSize, intelByteOrder, isSigned);
|
||||
endResult = ((double)result * factor) + bias;
|
||||
result = (int64_t)endResult;
|
||||
}
|
||||
/*TODO: It should be noted that the below floating point has not even been tested. For shame! Test it!*/
|
||||
else if (valType == SP_FLOAT)
|
||||
{
|
||||
if ( frame.len*8 < (startBit+32) )
|
||||
if ( frame.payload().length() * 8 < (startBit + 32) )
|
||||
{
|
||||
result = 0;
|
||||
return false;
|
||||
@@ -221,19 +222,19 @@ bool DBC_SIGNAL::processAsDouble(const CANFrame &frame, double &outValue)
|
||||
//that the bytes that make up the integer are instead treated as having made up
|
||||
//a 32 bit single precision float. That's evil incarnate but it is very fast and small
|
||||
//in terms of new code.
|
||||
result = Utility::processIntegerSignal(frame.data, startBit, 32, false, false);
|
||||
result = Utility::processIntegerSignal(frame.payload(), startBit, 32, false, false);
|
||||
endResult = (*((float *)(&result)) * factor) + bias;
|
||||
}
|
||||
else //double precision float
|
||||
{
|
||||
if ( frame.len < 8 )
|
||||
if ( frame.payload().length() < 8 )
|
||||
{
|
||||
result = 0;
|
||||
return false;
|
||||
}
|
||||
//like the above, this is rotten and evil and wrong in so many ways. Force
|
||||
//calculation of a 64 bit integer and then cast it into a double.
|
||||
result = Utility::processIntegerSignal(frame.data, 0, 64, false, false);
|
||||
result = Utility::processIntegerSignal(frame.payload(), 0, 64, false, false);
|
||||
endResult = (*((double *)(&result)) * factor) + bias;
|
||||
}
|
||||
cachedValue = endResult;
|
||||
@@ -243,7 +244,7 @@ bool DBC_SIGNAL::processAsDouble(const CANFrame &frame, double &outValue)
|
||||
|
||||
DBC_ATTRIBUTE_VALUE *DBC_SIGNAL::findAttrValByName(QString name)
|
||||
{
|
||||
if (attributes.length() == 0) return NULL;
|
||||
if (attributes.length() == 0) return nullptr;
|
||||
for (int i = 0; i < attributes.length(); i++)
|
||||
{
|
||||
if (attributes[i].attrName.compare(name, Qt::CaseInsensitive) == 0)
|
||||
@@ -251,19 +252,19 @@ DBC_ATTRIBUTE_VALUE *DBC_SIGNAL::findAttrValByName(QString name)
|
||||
return &attributes[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
DBC_ATTRIBUTE_VALUE *DBC_SIGNAL::findAttrValByIdx(int idx)
|
||||
{
|
||||
if (idx < 0) return NULL;
|
||||
if (idx >= attributes.count()) return NULL;
|
||||
if (idx < 0) return nullptr;
|
||||
if (idx >= attributes.count()) return nullptr;
|
||||
return &attributes[idx];
|
||||
}
|
||||
|
||||
DBC_ATTRIBUTE_VALUE *DBC_MESSAGE::findAttrValByName(QString name)
|
||||
{
|
||||
if (attributes.length() == 0) return NULL;
|
||||
if (attributes.length() == 0) return nullptr;
|
||||
for (int i = 0; i < attributes.length(); i++)
|
||||
{
|
||||
if (attributes[i].attrName.compare(name, Qt::CaseInsensitive) == 0)
|
||||
@@ -271,19 +272,19 @@ DBC_ATTRIBUTE_VALUE *DBC_MESSAGE::findAttrValByName(QString name)
|
||||
return &attributes[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
DBC_ATTRIBUTE_VALUE *DBC_MESSAGE::findAttrValByIdx(int idx)
|
||||
{
|
||||
if (idx < 0) return NULL;
|
||||
if (idx >= attributes.count()) return NULL;
|
||||
if (idx < 0) return nullptr;
|
||||
if (idx >= attributes.count()) return nullptr;
|
||||
return &attributes[idx];
|
||||
}
|
||||
|
||||
DBC_ATTRIBUTE_VALUE *DBC_NODE::findAttrValByName(QString name)
|
||||
{
|
||||
if (attributes.length() == 0) return NULL;
|
||||
if (attributes.length() == 0) return nullptr;
|
||||
for (int i = 0; i < attributes.length(); i++)
|
||||
{
|
||||
if (attributes[i].attrName.compare(name, Qt::CaseInsensitive) == 0)
|
||||
@@ -291,12 +292,12 @@ DBC_ATTRIBUTE_VALUE *DBC_NODE::findAttrValByName(QString name)
|
||||
return &attributes[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
DBC_ATTRIBUTE_VALUE *DBC_NODE::findAttrValByIdx(int idx)
|
||||
{
|
||||
if (idx < 0) return NULL;
|
||||
if (idx >= attributes.count()) return NULL;
|
||||
if (idx < 0) return nullptr;
|
||||
if (idx >= attributes.count()) return nullptr;
|
||||
return &attributes[idx];
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ DBC_MESSAGE* DBCMessageHandler::findMsgByID(uint32_t id)
|
||||
if (messages.count() == 0) return nullptr;
|
||||
for (int i = 0; i < messages.count(); i++)
|
||||
{
|
||||
if (isJ1939Handler)
|
||||
if (matchingCriteria == J1939)
|
||||
{
|
||||
// include data page and extended data page in the pgn
|
||||
uint32_t pgn = (id & 0x3FFFF00) >> 8;
|
||||
@@ -112,6 +112,13 @@ DBC_MESSAGE* DBCMessageHandler::findMsgByID(uint32_t id)
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (matchingCriteria == GMLAN)
|
||||
{
|
||||
// Match the bits 14-26 (Arbitration Id) of GMLAN 29bit header
|
||||
uint32_t arbId = id &0x3FFE000;
|
||||
if ( (arbId != 0) && (messages[i].ID & 0x3FFE000) == arbId )
|
||||
return &messages[i];
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( messages[i].ID == id )
|
||||
@@ -206,29 +213,41 @@ int DBCMessageHandler::getCount()
|
||||
return messages.count();
|
||||
}
|
||||
|
||||
bool DBCMessageHandler::isJ1939()
|
||||
bool DBCMessageHandler::filterLabeling()
|
||||
{
|
||||
return isJ1939Handler;
|
||||
return filterLabelingEnabled;
|
||||
}
|
||||
|
||||
void DBCMessageHandler::setJ1939(bool j1939)
|
||||
void DBCMessageHandler::setFilterLabeling(bool filterLabeling)
|
||||
{
|
||||
isJ1939Handler = j1939;
|
||||
filterLabelingEnabled = filterLabeling;
|
||||
}
|
||||
|
||||
MatchingCriteria_t DBCMessageHandler::getMatchingCriteria()
|
||||
{
|
||||
return matchingCriteria;
|
||||
}
|
||||
|
||||
void DBCMessageHandler::setMatchingCriteria(MatchingCriteria_t _matchingCriteria)
|
||||
{
|
||||
matchingCriteria = _matchingCriteria;
|
||||
}
|
||||
|
||||
DBCFile::DBCFile()
|
||||
{
|
||||
messageHandler = new DBCMessageHandler;
|
||||
messageHandler->setJ1939(false);
|
||||
messageHandler->setMatchingCriteria(EXACT);
|
||||
messageHandler->setFilterLabeling(false);
|
||||
}
|
||||
|
||||
DBCFile::DBCFile(const DBCFile& cpy)
|
||||
DBCFile::DBCFile(const DBCFile& cpy) : QObject()
|
||||
{
|
||||
messageHandler = new DBCMessageHandler;
|
||||
for (int i = 0 ; i < cpy.messageHandler->getCount() ; i++)
|
||||
messageHandler->addMessage(*cpy.messageHandler->findMsgByIdx(i));
|
||||
|
||||
messageHandler->setJ1939(cpy.messageHandler->isJ1939());
|
||||
messageHandler->setMatchingCriteria(cpy.messageHandler->getMatchingCriteria());
|
||||
messageHandler->setFilterLabeling(cpy.messageHandler->filterLabeling());
|
||||
fileName = cpy.fileName;
|
||||
filePath = cpy.filePath;
|
||||
assocBuses = cpy.assocBuses;
|
||||
@@ -296,9 +315,10 @@ int DBCFile::getAssocBus()
|
||||
|
||||
void DBCFile::setAssocBus(int bus)
|
||||
{
|
||||
int numBuses = CANConManager::getInstance()->getNumBuses();
|
||||
if (bus < -1) return;
|
||||
if (bus >= numBuses) return;
|
||||
// To allow setting bus numbers even before connection is configured, do not enforce "valid" bus numbers
|
||||
//int numBuses = CANConManager::getInstance()->getNumBuses();
|
||||
//if (bus >= numBuses) return;
|
||||
assocBuses = bus;
|
||||
}
|
||||
|
||||
@@ -448,6 +468,14 @@ DBC_SIGNAL* DBCFile::parseSignalLine(QString line, DBC_MESSAGE *msg)
|
||||
case 4:
|
||||
sig.valType = STRING;
|
||||
break;
|
||||
case 5: //single point float in little endian
|
||||
sig.valType = SP_FLOAT;
|
||||
sig.intelByteOrder = true;
|
||||
break;
|
||||
case 6: //double point float in little endian
|
||||
sig.valType = DP_FLOAT;
|
||||
sig.intelByteOrder = true;
|
||||
break;
|
||||
}
|
||||
sig.factor = match.captured(6 + offset).toDouble();
|
||||
sig.bias = match.captured(7 + offset).toDouble();
|
||||
@@ -693,7 +721,8 @@ void DBCFile::loadFile(QString fileName)
|
||||
qDebug() << "Starting DBC load";
|
||||
dbc_nodes.clear();
|
||||
messageHandler->removeAllMessages();
|
||||
messageHandler->setJ1939(false);
|
||||
messageHandler->setMatchingCriteria(EXACT);
|
||||
messageHandler->setFilterLabeling(false);
|
||||
|
||||
DBC_NODE falseNode;
|
||||
falseNode.name = "Vector__XXX";
|
||||
@@ -887,14 +916,24 @@ void DBCFile::loadFile(QString fileName)
|
||||
fgAttr = findAttributeByName("GenMsgForegroundColor");
|
||||
}
|
||||
|
||||
DBC_ATTRIBUTE *j1939attr = findAttributeByName("isj1939dbc");
|
||||
if (j1939attr)
|
||||
DBC_ATTRIBUTE *mc_attr = findAttributeByName("matchingcriteria");
|
||||
if (mc_attr)
|
||||
{
|
||||
messageHandler->setJ1939(j1939attr->defaultValue > 0);
|
||||
messageHandler->setMatchingCriteria((MatchingCriteria_t)mc_attr->defaultValue.toInt());
|
||||
}
|
||||
else
|
||||
{
|
||||
messageHandler->setJ1939(false);
|
||||
messageHandler->setMatchingCriteria(EXACT);
|
||||
}
|
||||
|
||||
DBC_ATTRIBUTE *fl_attr = findAttributeByName("filterlabeling");
|
||||
if (fl_attr)
|
||||
{
|
||||
messageHandler->setFilterLabeling(fl_attr->defaultValue.toInt());
|
||||
}
|
||||
else
|
||||
{
|
||||
messageHandler->setFilterLabeling(false);
|
||||
}
|
||||
|
||||
QColor DefaultBG = QColor(bgAttr->defaultValue.toString());
|
||||
@@ -1202,10 +1241,12 @@ void DBCFile::saveFile(QString fileName)
|
||||
else msgOutput.append("0-");
|
||||
break;
|
||||
case SP_FLOAT:
|
||||
msgOutput.append("2-");
|
||||
if (sig->intelByteOrder) msgOutput.append("5-");
|
||||
else msgOutput.append("2-");
|
||||
break;
|
||||
case DP_FLOAT:
|
||||
msgOutput.append("3-");
|
||||
if (sig->intelByteOrder) msgOutput.append("6-");
|
||||
else msgOutput.append("3-");
|
||||
break;
|
||||
case STRING:
|
||||
msgOutput.append("4-");
|
||||
@@ -1402,7 +1443,16 @@ int DBCHandler::createBlankFile()
|
||||
attr.enumVals.clear();
|
||||
attr.lower = 0;
|
||||
attr.upper = 0;
|
||||
attr.name = "isj1939dbc";
|
||||
attr.name = "matchingcriteria";
|
||||
attr.valType = QINT;
|
||||
newFile.dbc_attributes.append(attr);
|
||||
|
||||
attr.attrType = MESSAGE;
|
||||
attr.defaultValue = 0;
|
||||
attr.enumVals.clear();
|
||||
attr.lower = 0;
|
||||
attr.upper = 0;
|
||||
attr.name = "filterlabeling";
|
||||
attr.valType = QINT;
|
||||
newFile.dbc_attributes.append(attr);
|
||||
|
||||
@@ -1415,6 +1465,14 @@ int DBCHandler::createBlankFile()
|
||||
return loadedFiles.count();
|
||||
}
|
||||
|
||||
DBCFile* DBCHandler::loadDBCFile(QString filename)
|
||||
{
|
||||
DBCFile newFile;
|
||||
newFile.loadFile(filename);
|
||||
loadedFiles.append(newFile);
|
||||
return &loadedFiles.last();
|
||||
}
|
||||
|
||||
//the only reason to even bother sending the index is to see if
|
||||
//the user wants to replace an already loaded DBC.
|
||||
//Otherwise add a new one. Well, always add a new one.
|
||||
@@ -1440,11 +1498,8 @@ DBCFile* DBCHandler::loadDBCFile(int idx)
|
||||
{
|
||||
filename = dialog.selectedFiles()[0];
|
||||
//right now there is only one file type that can be loaded here so just do it.
|
||||
DBCFile newFile;
|
||||
newFile.loadFile(filename);
|
||||
loadedFiles.append(newFile);
|
||||
settings.setValue("DBC/LoadSaveDirectory", dialog.directory().path());
|
||||
return &loadedFiles.last();
|
||||
return loadDBCFile(filename);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
@@ -1650,7 +1705,7 @@ void DBCHandler::swapFiles(int pos1, int pos2)
|
||||
* Convenience function that encapsulates a whole lot of the details.
|
||||
* You give it a canbus frame and it'll tell you whether there is a loaded DBC file that can
|
||||
* interpret that frame for you.
|
||||
* Returns NULL if there is no message definition that matches.
|
||||
* Returns nullptr if there is no message definition that matches.
|
||||
*/
|
||||
DBC_MESSAGE* DBCHandler::findMessage(const CANFrame &frame)
|
||||
{
|
||||
@@ -1658,13 +1713,36 @@ DBC_MESSAGE* DBCHandler::findMessage(const CANFrame &frame)
|
||||
{
|
||||
if (loadedFiles[i].getAssocBus() == -1 || frame.bus == (unsigned int)loadedFiles[i].getAssocBus())
|
||||
{
|
||||
DBC_MESSAGE* msg = loadedFiles[i].messageHandler->findMsgByID(frame.ID);
|
||||
DBC_MESSAGE* msg = loadedFiles[i].messageHandler->findMsgByID(frame.frameId());
|
||||
if (msg != nullptr) return msg;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
// This function won't care which bus the DBC file is associated, but will return any message as long as ID matches and the file
|
||||
// has filter labeling enabled.
|
||||
// Returns the found message as well as the matching criteria (exact/J1939/GMLAN)
|
||||
// Used for quickly populating the Frame Filtering section with interpreted values
|
||||
DBC_MESSAGE* DBCHandler::findMessageForFilter(uint32_t id, MatchingCriteria_t * matchingCriteria)
|
||||
{
|
||||
for(int i = 0; i < loadedFiles.count(); i++)
|
||||
{
|
||||
if (loadedFiles[i].messageHandler->filterLabeling())
|
||||
{
|
||||
DBC_MESSAGE* msg = loadedFiles[i].messageHandler->findMsgByID(id);
|
||||
if (msg != nullptr)
|
||||
{
|
||||
*matchingCriteria = loadedFiles[i].messageHandler->getMatchingCriteria();
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* As above, a real shortcut function that searches all files in order to try to find a message with the given name
|
||||
*/
|
||||
@@ -1708,7 +1786,44 @@ DBCFile* DBCHandler::getFileByName(QString name)
|
||||
|
||||
DBCHandler::DBCHandler()
|
||||
{
|
||||
// Load previously saved DBC file settings
|
||||
QSettings settings;
|
||||
int filecount = settings.value("DBC/FileCount", 0).toInt();
|
||||
for (int i=0; i<filecount; i++)
|
||||
{
|
||||
QString filename = settings.value("DBC/Filename_" + QString(i),"").toString();
|
||||
DBCFile * file = loadDBCFile(filename);
|
||||
int bus = settings.value("DBC/AssocBus_" + QString(i),0).toInt();
|
||||
file->setAssocBus(bus);
|
||||
|
||||
MatchingCriteria_t matchingCriteria = (MatchingCriteria_t)settings.value("DBC/MatchingCriteria_" + QString(i),0).toInt();
|
||||
|
||||
DBC_ATTRIBUTE attr;
|
||||
|
||||
attr.attrType = MESSAGE;
|
||||
attr.defaultValue = matchingCriteria;
|
||||
attr.enumVals.clear();
|
||||
attr.lower = 0;
|
||||
attr.upper = 0;
|
||||
attr.name = "matchingcriteria";
|
||||
attr.valType = QINT;
|
||||
file->dbc_attributes.append(attr);
|
||||
file->messageHandler->setMatchingCriteria(matchingCriteria);
|
||||
|
||||
bool filterLabeling = settings.value("DBC/FilterLabeling_" + QString(i),0).toBool();
|
||||
attr.attrType = MESSAGE;
|
||||
attr.defaultValue = filterLabeling;
|
||||
attr.enumVals.clear();
|
||||
attr.lower = 0;
|
||||
attr.upper = 0;
|
||||
attr.name = "filterlabeling";
|
||||
attr.valType = QINT;
|
||||
file->dbc_attributes.append(attr);
|
||||
file->messageHandler->setFilterLabeling(filterLabeling);
|
||||
|
||||
qInfo() << "Loaded DBC file" << filename << " (bus:" << bus
|
||||
<< ", Matching Criteria:" << (int)matchingCriteria << "Filter labeling: " << (filterLabeling?"enabled":"disabled") << ")";
|
||||
}
|
||||
}
|
||||
|
||||
DBCHandler* DBCHandler::getReference()
|
||||
|
||||
@@ -5,6 +5,13 @@
|
||||
#include "dbc_classes.h"
|
||||
#include "can_structs.h"
|
||||
|
||||
typedef enum
|
||||
{
|
||||
EXACT,
|
||||
J1939,
|
||||
GMLAN
|
||||
} MatchingCriteria_t;
|
||||
|
||||
/*
|
||||
* TODO:
|
||||
* Finish coding up the decoupled design
|
||||
@@ -40,11 +47,14 @@ public:
|
||||
bool removeMessage(QString name);
|
||||
void removeAllMessages();
|
||||
int getCount();
|
||||
bool isJ1939();
|
||||
void setJ1939(bool j1939);
|
||||
MatchingCriteria_t getMatchingCriteria();
|
||||
void setMatchingCriteria(MatchingCriteria_t mc);
|
||||
void setFilterLabeling( bool labelFiltering );
|
||||
bool filterLabeling();
|
||||
private:
|
||||
QList<DBC_MESSAGE> messages;
|
||||
bool isJ1939Handler;
|
||||
MatchingCriteria_t matchingCriteria;
|
||||
bool filterLabelingEnabled;
|
||||
};
|
||||
|
||||
//technically there should be a node handler too but I'm sort of treating nodes as second class
|
||||
@@ -90,6 +100,7 @@ class DBCHandler: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
DBCFile* loadDBCFile(QString filename);
|
||||
DBCFile* loadDBCFile(int);
|
||||
void saveDBCFile(int);
|
||||
void removeDBCFile(int);
|
||||
@@ -97,6 +108,7 @@ public:
|
||||
void swapFiles(int pos1, int pos2);
|
||||
DBC_MESSAGE* findMessage(const CANFrame &frame);
|
||||
DBC_MESSAGE* findMessage(const QString msgName);
|
||||
DBC_MESSAGE* findMessageForFilter(uint32_t id, MatchingCriteria_t * matchingCriteria);
|
||||
int getFileCount();
|
||||
DBCFile* getFileByIdx(int idx);
|
||||
DBCFile* getFileByName(QString name);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "dbcloadsavewindow.h"
|
||||
#include "ui_dbcloadsavewindow.h"
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QMessageBox>
|
||||
#include <qevent.h>
|
||||
#include "helpwindow.h"
|
||||
#include "connections/canconmanager.h"
|
||||
|
||||
@@ -15,17 +17,47 @@ DBCLoadSaveWindow::DBCLoadSaveWindow(const QVector<CANFrame> *frames, QWidget *p
|
||||
|
||||
ui->setupUi(this);
|
||||
|
||||
inhibitCellProcessing = false;
|
||||
inhibitCellProcessing = true;
|
||||
|
||||
QStringList header;
|
||||
header << "Filename" << "Associated Bus" << "J1939";
|
||||
ui->tableFiles->setColumnCount(3);
|
||||
header << "Filename" << "Associated Bus" << "Matching criteria" << "Label filters";
|
||||
ui->tableFiles->setColumnCount(4);
|
||||
ui->tableFiles->setHorizontalHeaderLabels(header);
|
||||
ui->tableFiles->setColumnWidth(0, 265);
|
||||
ui->tableFiles->setColumnWidth(1, 125);
|
||||
ui->tableFiles->setColumnWidth(2, 80);
|
||||
ui->tableFiles->setColumnWidth(2, 120);
|
||||
ui->tableFiles->setColumnWidth(3, 90);
|
||||
ui->tableFiles->horizontalHeader()->setStretchLastSection(true);
|
||||
|
||||
// Populate table
|
||||
for (int idx=0; idx<dbcHandler->getFileCount(); idx++)
|
||||
{
|
||||
DBCFile * file = dbcHandler->getFileByIdx(idx);
|
||||
ui->tableFiles->insertRow(ui->tableFiles->rowCount());
|
||||
ui->tableFiles->setItem(idx, 0, new QTableWidgetItem(file->getFullFilename()));
|
||||
QString bus = QString::number(file->getAssocBus() );
|
||||
ui->tableFiles->setItem(idx, 1, new QTableWidgetItem(bus));
|
||||
|
||||
QComboBox * mc_item = addMatchingCriteriaCombobox(idx);
|
||||
int mc = (int)file->messageHandler->getMatchingCriteria();
|
||||
mc_item->setCurrentIndex(mc);
|
||||
|
||||
QTableWidgetItem *item = new QTableWidgetItem("");
|
||||
ui->tableFiles->setItem(idx, 3, item);
|
||||
bool filterLabeling = file->messageHandler->filterLabeling();
|
||||
if (filterLabeling)
|
||||
{
|
||||
item->setCheckState(Qt::Checked);
|
||||
}
|
||||
else
|
||||
{
|
||||
item->setCheckState(Qt::Unchecked);
|
||||
}
|
||||
|
||||
qDebug() << "Populate DBC table:" << file->getFullFilename() << " (bus:" << bus << " - Matching Criteria:" << mc
|
||||
<< "Filter labeling: " << (filterLabeling?"enabled":"disabled") << ")";
|
||||
}
|
||||
|
||||
connect(ui->btnEdit, &QAbstractButton::clicked, this, &DBCLoadSaveWindow::editFile);
|
||||
connect(ui->btnLoad, &QAbstractButton::clicked, this, &DBCLoadSaveWindow::loadFile);
|
||||
connect(ui->btnMoveDown, &QAbstractButton::clicked, this, &DBCLoadSaveWindow::moveDown);
|
||||
@@ -40,15 +72,51 @@ DBCLoadSaveWindow::DBCLoadSaveWindow(const QVector<CANFrame> *frames, QWidget *p
|
||||
editorWindow = new DBCMainEditor(frames, this);
|
||||
currentlyEditingFile = nullptr;
|
||||
|
||||
inhibitCellProcessing = false;
|
||||
|
||||
installEventFilter(this);
|
||||
}
|
||||
|
||||
QComboBox * DBCLoadSaveWindow::addMatchingCriteriaCombobox(int row)
|
||||
{
|
||||
QComboBox *item = new QComboBox();
|
||||
item->addItem("Exact");
|
||||
item->addItem("J1939");
|
||||
item->addItem("GMLAN");
|
||||
ui->tableFiles->setCellWidget(row, 2, item);
|
||||
connect(item, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
|
||||
[this](int box_idx) { matchingCriteriaChanged(box_idx); } );
|
||||
return item;
|
||||
}
|
||||
|
||||
DBCLoadSaveWindow::~DBCLoadSaveWindow()
|
||||
{
|
||||
removeEventFilter(this);
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void DBCLoadSaveWindow::updateSettings()
|
||||
{
|
||||
QSettings settings;
|
||||
int filecount = ui->tableFiles->rowCount();
|
||||
settings.setValue("DBC/FileCount", filecount);
|
||||
for (int i=0; i<filecount; i++)
|
||||
{
|
||||
DBCFile * file = dbcHandler->getFileByIdx(i);
|
||||
if (file)
|
||||
{
|
||||
qDebug() << "Save DBC settings #" << i << " File: " << file->getFullFilename()
|
||||
<< "Bus: " << file->getAssocBus() << "MC: " << file->messageHandler->getMatchingCriteria()
|
||||
<< "Filter Labeling: " << (file->messageHandler->filterLabeling() ? "enabled" : "disabled");
|
||||
settings.setValue("DBC/Filename_" + QString(i), file->getFullFilename());
|
||||
settings.setValue("DBC/AssocBus_" + QString(i), file->getAssocBus());
|
||||
settings.setValue("DBC/MatchingCriteria_" + QString(i), file->messageHandler->getMatchingCriteria());
|
||||
settings.setValue("DBC/FilterLabeling_" + QString(i), file->messageHandler->filterLabeling());
|
||||
}
|
||||
}
|
||||
emit updatedDBCSettings();
|
||||
}
|
||||
|
||||
bool DBCLoadSaveWindow::eventFilter(QObject *obj, QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::KeyRelease) {
|
||||
@@ -75,22 +143,34 @@ void DBCLoadSaveWindow::newFile()
|
||||
ui->tableFiles->setItem(idx, 0, new QTableWidgetItem("UNNAMEDFILE"));
|
||||
ui->tableFiles->setItem(idx, 1, new QTableWidgetItem("-1"));
|
||||
|
||||
QComboBox * mc_item = addMatchingCriteriaCombobox(idx);
|
||||
mc_item->setCurrentIndex(EXACT);
|
||||
|
||||
QTableWidgetItem *item = new QTableWidgetItem("");
|
||||
item->setCheckState(Qt::Unchecked);
|
||||
ui->tableFiles->setItem(idx, 2, item);
|
||||
item->setCheckState(Qt::Checked);
|
||||
ui->tableFiles->setItem(idx, 3, item);
|
||||
}
|
||||
|
||||
void DBCLoadSaveWindow::loadFile()
|
||||
{
|
||||
DBCFile *file = dbcHandler->loadDBCFile(-1);
|
||||
if(file) {
|
||||
inhibitCellProcessing=true;
|
||||
int idx = ui->tableFiles->rowCount();
|
||||
ui->tableFiles->insertRow(ui->tableFiles->rowCount());
|
||||
ui->tableFiles->setItem(idx, 0, new QTableWidgetItem(file->getFullFilename()));
|
||||
ui->tableFiles->setItem(idx, 1, new QTableWidgetItem("-1"));
|
||||
DBC_ATTRIBUTE *attr = file->findAttributeByName("isj1939dbc");
|
||||
|
||||
DBC_ATTRIBUTE *attr = file->findAttributeByName("matchingcriteria");
|
||||
QComboBox * mc_item = addMatchingCriteriaCombobox(idx);
|
||||
if (attr && attr->defaultValue > 0)
|
||||
{
|
||||
mc_item->setCurrentIndex(attr->defaultValue.toInt());
|
||||
}
|
||||
|
||||
attr = file->findAttributeByName("filterlabeling");
|
||||
QTableWidgetItem *item = new QTableWidgetItem("");
|
||||
ui->tableFiles->setItem(idx, 2, item);
|
||||
ui->tableFiles->setItem(idx, 3, item);
|
||||
if (attr && attr->defaultValue > 0)
|
||||
{
|
||||
item->setCheckState(Qt::Checked);
|
||||
@@ -99,6 +179,9 @@ void DBCLoadSaveWindow::loadFile()
|
||||
{
|
||||
item->setCheckState(Qt::Unchecked);
|
||||
}
|
||||
inhibitCellProcessing=false;
|
||||
|
||||
updateSettings();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,6 +237,7 @@ void DBCLoadSaveWindow::removeFile()
|
||||
dbcHandler->removeDBCFile(idx);
|
||||
ui->tableFiles->removeRow(idx);
|
||||
}
|
||||
updateSettings();
|
||||
}
|
||||
|
||||
void DBCLoadSaveWindow::moveUp()
|
||||
@@ -162,6 +246,7 @@ void DBCLoadSaveWindow::moveUp()
|
||||
if (idx < 1) return;
|
||||
dbcHandler->swapFiles(idx - 1, idx);
|
||||
swapTableRows(true);
|
||||
updateSettings();
|
||||
}
|
||||
|
||||
void DBCLoadSaveWindow::moveDown()
|
||||
@@ -171,6 +256,7 @@ void DBCLoadSaveWindow::moveDown()
|
||||
if (idx > (dbcHandler->getFileCount() - 2)) return;
|
||||
dbcHandler->swapFiles(idx, idx + 1);
|
||||
swapTableRows(false);
|
||||
updateSettings();
|
||||
}
|
||||
|
||||
void DBCLoadSaveWindow::editFile()
|
||||
@@ -182,6 +268,43 @@ void DBCLoadSaveWindow::editFile()
|
||||
editorWindow->show();
|
||||
}
|
||||
|
||||
void DBCLoadSaveWindow::matchingCriteriaChanged(int index)
|
||||
{
|
||||
if (inhibitCellProcessing) return;
|
||||
// We don't know which combobox changed, so we just update all of them
|
||||
for (int row=0; row<ui->tableFiles->rowCount(); row++)
|
||||
{
|
||||
DBCFile *file = dbcHandler->getFileByIdx(row);
|
||||
if (file)
|
||||
{
|
||||
QComboBox *item = (QComboBox*)ui->tableFiles->cellWidget(row, 2);
|
||||
MatchingCriteria_t matchingCriteria = (MatchingCriteria_t) item->currentIndex();
|
||||
DBC_ATTRIBUTE *attr = file->findAttributeByName("matchingcriteria");
|
||||
if (attr)
|
||||
{
|
||||
attr->defaultValue = matchingCriteria;
|
||||
file->messageHandler->setMatchingCriteria(matchingCriteria);
|
||||
}
|
||||
else
|
||||
{
|
||||
DBC_ATTRIBUTE attr;
|
||||
|
||||
attr.attrType = MESSAGE;
|
||||
attr.defaultValue = matchingCriteria;
|
||||
attr.enumVals.clear();
|
||||
attr.lower = 0;
|
||||
attr.upper = 0;
|
||||
attr.name = "matchingcriteria";
|
||||
attr.valType = QINT;
|
||||
file->dbc_attributes.append(attr);
|
||||
file->messageHandler->setMatchingCriteria(matchingCriteria);
|
||||
}
|
||||
}
|
||||
}
|
||||
updateSettings();
|
||||
}
|
||||
|
||||
|
||||
void DBCLoadSaveWindow::cellChanged(int row, int col)
|
||||
{
|
||||
if (inhibitCellProcessing) return;
|
||||
@@ -190,39 +313,40 @@ void DBCLoadSaveWindow::cellChanged(int row, int col)
|
||||
DBCFile *file = dbcHandler->getFileByIdx(row);
|
||||
int bus = ui->tableFiles->item(row, col)->text().toInt();
|
||||
int numBuses = CANConManager::getInstance()->getNumBuses();
|
||||
if (bus > -2 && bus < numBuses)
|
||||
if (bus > -2)
|
||||
{
|
||||
file->setAssocBus(bus);
|
||||
}
|
||||
}
|
||||
else if (col == 2)
|
||||
updateSettings();
|
||||
}
|
||||
else if (col == 3) // labelfilters
|
||||
{
|
||||
DBCFile *file = dbcHandler->getFileByIdx(row);
|
||||
if (file)
|
||||
{
|
||||
//int isj1939dbc = ui->tableFiles->item(row, col)->text().toInt();
|
||||
bool isj1939dbc = ui->tableFiles->item(row, col)->checkState() == Qt::Checked;
|
||||
DBC_ATTRIBUTE *attr = file->findAttributeByName("isj1939dbc");
|
||||
bool labelFilters = ui->tableFiles->item(row, col)->checkState() == Qt::Checked;
|
||||
DBC_ATTRIBUTE *attr = file->findAttributeByName("filterlabeling");
|
||||
if (attr)
|
||||
{
|
||||
attr->defaultValue = isj1939dbc ? 1 : 0;
|
||||
file->messageHandler->setJ1939(isj1939dbc);
|
||||
attr->defaultValue = labelFilters ? 1 : 0;
|
||||
file->messageHandler->setFilterLabeling(labelFilters);
|
||||
}
|
||||
else
|
||||
{
|
||||
DBC_ATTRIBUTE attr;
|
||||
|
||||
attr.attrType = MESSAGE;
|
||||
attr.defaultValue = isj1939dbc ? 1 : 0;
|
||||
attr.defaultValue = labelFilters ? 1 : 0;
|
||||
attr.enumVals.clear();
|
||||
attr.lower = 0;
|
||||
attr.upper = 0;
|
||||
attr.name = "isj1939dbc";
|
||||
attr.name = "labelfilters";
|
||||
attr.valType = QINT;
|
||||
file->dbc_attributes.append(attr);
|
||||
file->messageHandler->setJ1939(isj1939dbc);
|
||||
file->messageHandler->setFilterLabeling(labelFilters);
|
||||
}
|
||||
}
|
||||
updateSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,10 +369,17 @@ void DBCLoadSaveWindow::swapTableRows(bool up)
|
||||
QList<QTableWidgetItem*> sourceItems = takeRow(idx);
|
||||
QList<QTableWidgetItem*> destItems = takeRow(destIdx);
|
||||
|
||||
// QCombobox needs separate handling
|
||||
int sourceMC = ((QComboBox*)ui->tableFiles->cellWidget(idx,2))->currentIndex();
|
||||
int destMC = ((QComboBox*)ui->tableFiles->cellWidget(destIdx,2))->currentIndex();
|
||||
|
||||
// set back in reverse order
|
||||
setRow(idx, destItems);
|
||||
setRow(destIdx, sourceItems);
|
||||
|
||||
((QComboBox*)ui->tableFiles->cellWidget(idx,2))->setCurrentIndex(destMC);
|
||||
((QComboBox*)ui->tableFiles->cellWidget(destIdx,2))->setCurrentIndex(sourceMC);
|
||||
|
||||
inhibitCellProcessing = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <QDialog>
|
||||
#include <QTableWidget>
|
||||
#include <QComboBox>
|
||||
#include "dbchandler.h"
|
||||
#include "dbcmaineditor.h"
|
||||
|
||||
@@ -28,8 +29,12 @@ private slots:
|
||||
void editFile();
|
||||
void cellChanged(int row, int col);
|
||||
void cellDoubleClicked(int row, int col);
|
||||
void matchingCriteriaChanged(int index);
|
||||
void newFile();
|
||||
|
||||
signals:
|
||||
void updatedDBCSettings();
|
||||
|
||||
private:
|
||||
Ui::DBCLoadSaveWindow *ui;
|
||||
DBCHandler *dbcHandler;
|
||||
@@ -42,6 +47,8 @@ private:
|
||||
QList<QTableWidgetItem*> takeRow(int row);
|
||||
void setRow(int row, const QList<QTableWidgetItem*>& rowItems);
|
||||
bool eventFilter(QObject *obj, QEvent *event);
|
||||
void updateSettings();
|
||||
QComboBox * addMatchingCriteriaCombobox(int row);
|
||||
};
|
||||
|
||||
#endif // DBCLOADSAVEWINDOW_H
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <QMessageBox>
|
||||
#include <QSettings>
|
||||
#include <QColorDialog>
|
||||
#include <qevent.h>
|
||||
#include "helpwindow.h"
|
||||
|
||||
DBCMainEditor::DBCMainEditor( const QVector<CANFrame> *frames, QWidget *parent) :
|
||||
@@ -193,7 +194,7 @@ void DBCMainEditor::onCellChangedNode(int row,int col)
|
||||
QString newName = ui->NodesTable->item(row, col)->text().simplified().replace(' ', '_');
|
||||
qDebug() << "new name: " << newName;
|
||||
if (newName.length() == 0) return;
|
||||
if (dbcFile->findNodeByName(newName) != NULL) //duplicates an existing node!
|
||||
if (dbcFile->findNodeByName(newName) != nullptr) //duplicates an existing node!
|
||||
{
|
||||
QMessageBox msg;
|
||||
msg.setParent(0);
|
||||
@@ -217,7 +218,7 @@ void DBCMainEditor::onCellChangedNode(int row,int col)
|
||||
{
|
||||
DBC_NODE *oldNode = dbcFile->findNodeByIdx(row);
|
||||
QString nodeName = ui->NodesTable->item(row, col)->text().simplified().replace(' ', '_');
|
||||
if (oldNode == NULL) return;
|
||||
if (oldNode == nullptr) return;
|
||||
if (row != 0) oldNode->name = nodeName;
|
||||
else nodeName = oldNode->name;
|
||||
inhibitCellChanged = true;
|
||||
@@ -230,7 +231,7 @@ void DBCMainEditor::onCellChangedNode(int row,int col)
|
||||
QString nodeName = ui->NodesTable->item(row, 0)->text().simplified().replace(' ', '_');
|
||||
qDebug() << "searching for node " << nodeName;
|
||||
DBC_NODE *thisNode = dbcFile->findNodeByName(nodeName);
|
||||
if (thisNode == NULL) return;
|
||||
if (thisNode == nullptr) return;
|
||||
thisNode->comment = ui->NodesTable->item(row, col)->text().simplified();
|
||||
qDebug() << "New comment: " << thisNode->comment;
|
||||
}
|
||||
@@ -240,15 +241,15 @@ void DBCMainEditor::onCellChangedNode(int row,int col)
|
||||
|
||||
void DBCMainEditor::onCellChangedMessage(int row,int col)
|
||||
{
|
||||
QTableWidgetItem* item = NULL;
|
||||
QTableWidgetItem* item = nullptr;
|
||||
bool ret = false;
|
||||
DBC_MESSAGE *msg = NULL;
|
||||
DBC_MESSAGE *msg = nullptr;
|
||||
uint msgID;
|
||||
|
||||
if (inhibitCellChanged) return;
|
||||
|
||||
DBC_NODE *node = dbcFile->findNodeByIdx(ui->NodesTable->currentRow());
|
||||
if (node == NULL)
|
||||
if (node == nullptr)
|
||||
{
|
||||
qDebug() << "No node set?!? This is bad!";
|
||||
return;
|
||||
@@ -271,7 +272,7 @@ void DBCMainEditor::onCellChangedMessage(int row,int col)
|
||||
ui->MessagesTable->item(row, 0)->setText("");
|
||||
return;
|
||||
}
|
||||
if (msg != NULL)
|
||||
if (msg != nullptr)
|
||||
{
|
||||
QMessageBox msg;
|
||||
msg.setParent(0);
|
||||
@@ -293,9 +294,9 @@ void DBCMainEditor::onCellChangedMessage(int row,int col)
|
||||
|
||||
for (int i = 0; i < referenceFrames->length(); i++)
|
||||
{
|
||||
if ((uint) referenceFrames->at(i).ID == msgID)
|
||||
if ((uint) referenceFrames->at(i).frameId() == msgID)
|
||||
{
|
||||
newMsg.len = referenceFrames->at(i).len;
|
||||
newMsg.len = referenceFrames->at(i).payload().length();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -328,7 +329,7 @@ void DBCMainEditor::onCellChangedMessage(int row,int col)
|
||||
{
|
||||
QString msgName = ui->MessagesTable->item(row, 1)->text().simplified().replace(' ', '_');
|
||||
if (msgName.length() == 0) return;
|
||||
if( ret && (msg!=NULL) )
|
||||
if( ret && (msg!=nullptr) )
|
||||
msg->name = msgName;
|
||||
break;
|
||||
}
|
||||
@@ -349,7 +350,7 @@ void DBCMainEditor::onCellChangedMessage(int row,int col)
|
||||
ui->MessagesTable->item(row, col)->setText(QString::number(msgLen));
|
||||
}
|
||||
|
||||
if( ret && (msg!=NULL) )
|
||||
if( ret && (msg!=nullptr) )
|
||||
msg->len = msgLen;
|
||||
break;
|
||||
}
|
||||
@@ -358,7 +359,7 @@ void DBCMainEditor::onCellChangedMessage(int row,int col)
|
||||
case 6: //comment
|
||||
{
|
||||
QString msgComment = ui->MessagesTable->item(row, col)->text().simplified();
|
||||
if( ret && (msgComment!=NULL) )
|
||||
if( ret && (msgComment!=nullptr) )
|
||||
msg->comment = msgComment;
|
||||
break;
|
||||
}
|
||||
@@ -376,7 +377,7 @@ void DBCMainEditor::onCellClickedNode(int row, int col)
|
||||
currRow = row;
|
||||
QTableWidgetItem *item = ui->NodesTable->item(currRow, 0);
|
||||
QString nodeName;
|
||||
if (item == NULL) return;
|
||||
if (item == nullptr) return;
|
||||
|
||||
nodeName = item->text();
|
||||
qDebug() << "Trying to find node with name " << nodeName;
|
||||
@@ -476,7 +477,7 @@ void DBCMainEditor::refreshNodesTable()
|
||||
|
||||
int rowIdx;
|
||||
|
||||
if (dbcFile->findNodeByName("Vector__XXX") == NULL)
|
||||
if (dbcFile->findNodeByName("Vector__XXX") == nullptr)
|
||||
{
|
||||
DBC_NODE newNode;
|
||||
newNode.name = "Vector__XXX";
|
||||
@@ -509,7 +510,7 @@ void DBCMainEditor::refreshMessagesTable(const DBC_NODE *node)
|
||||
|
||||
int rowIdx;
|
||||
|
||||
if (node != NULL)
|
||||
if (node != nullptr)
|
||||
{
|
||||
for (int x = 0; x < dbcFile->messageHandler->getCount(); x++)
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <QDebug>
|
||||
#include <QMenu>
|
||||
#include <QSettings>
|
||||
#include <qevent.h>
|
||||
#include "helpwindow.h"
|
||||
|
||||
DBCSignalEditor::DBCSignalEditor(QWidget *parent) :
|
||||
@@ -17,8 +18,8 @@ DBCSignalEditor::DBCSignalEditor(QWidget *parent) :
|
||||
qsrand(QDateTime::currentMSecsSinceEpoch());
|
||||
|
||||
dbcHandler = DBCHandler::getReference();
|
||||
dbcMessage = NULL;
|
||||
currentSignal = NULL;
|
||||
dbcMessage = nullptr;
|
||||
currentSignal = nullptr;
|
||||
|
||||
QStringList headers2;
|
||||
headers2 << "Value" << "Text";
|
||||
@@ -46,23 +47,21 @@ DBCSignalEditor::DBCSignalEditor(QWidget *parent) :
|
||||
connect(ui->cbIntelFormat, &QCheckBox::toggled,
|
||||
[=]()
|
||||
{
|
||||
if (currentSignal == NULL) return;
|
||||
if (currentSignal == nullptr) return;
|
||||
currentSignal->intelByteOrder = ui->cbIntelFormat->isChecked();
|
||||
if (currentSignal->valType == SP_FLOAT || currentSignal->valType == DP_FLOAT)
|
||||
currentSignal->intelByteOrder = false;
|
||||
fillSignalForm(currentSignal);
|
||||
});
|
||||
|
||||
connect(ui->comboReceiver, &QComboBox::currentTextChanged,
|
||||
[=]()
|
||||
{
|
||||
if (currentSignal == NULL) return;
|
||||
if (currentSignal == nullptr) return;
|
||||
currentSignal->receiver = dbcFile->findNodeByName(ui->comboReceiver->currentText());
|
||||
});
|
||||
connect(ui->comboType, &QComboBox::currentTextChanged,
|
||||
[=]()
|
||||
{
|
||||
if (currentSignal == NULL) return;
|
||||
if (currentSignal == nullptr) return;
|
||||
switch (ui->comboType->currentIndex())
|
||||
{
|
||||
case 0:
|
||||
@@ -73,13 +72,11 @@ DBCSignalEditor::DBCSignalEditor(QWidget *parent) :
|
||||
break;
|
||||
case 2:
|
||||
currentSignal->valType = SP_FLOAT;
|
||||
currentSignal->intelByteOrder = false;
|
||||
if (currentSignal->startBit > 39) currentSignal->startBit = 39;
|
||||
currentSignal->signalSize = 32;
|
||||
break;
|
||||
case 3:
|
||||
currentSignal->valType = DP_FLOAT;
|
||||
currentSignal->intelByteOrder = false;
|
||||
currentSignal->startBit = 7; //has to be!
|
||||
currentSignal->signalSize = 64;
|
||||
break;
|
||||
@@ -92,7 +89,7 @@ DBCSignalEditor::DBCSignalEditor(QWidget *parent) :
|
||||
connect(ui->txtBias, &QLineEdit::editingFinished,
|
||||
[=]()
|
||||
{
|
||||
if (currentSignal == NULL) return;
|
||||
if (currentSignal == nullptr) return;
|
||||
double temp;
|
||||
bool result;
|
||||
temp = ui->txtBias->text().toDouble(&result);
|
||||
@@ -102,7 +99,7 @@ DBCSignalEditor::DBCSignalEditor(QWidget *parent) :
|
||||
connect(ui->txtMaxVal, &QLineEdit::editingFinished,
|
||||
[=]()
|
||||
{
|
||||
if (currentSignal == NULL) return;
|
||||
if (currentSignal == nullptr) return;
|
||||
double temp;
|
||||
bool result;
|
||||
temp = ui->txtMaxVal->text().toDouble(&result);
|
||||
@@ -112,7 +109,7 @@ DBCSignalEditor::DBCSignalEditor(QWidget *parent) :
|
||||
connect(ui->txtMinVal, &QLineEdit::editingFinished,
|
||||
[=]()
|
||||
{
|
||||
if (currentSignal == NULL) return;
|
||||
if (currentSignal == nullptr) return;
|
||||
double temp;
|
||||
bool result;
|
||||
temp = ui->txtMinVal->text().toDouble(&result);
|
||||
@@ -121,7 +118,7 @@ DBCSignalEditor::DBCSignalEditor(QWidget *parent) :
|
||||
connect(ui->txtScale, &QLineEdit::editingFinished,
|
||||
[=]()
|
||||
{
|
||||
if (currentSignal == NULL) return;
|
||||
if (currentSignal == nullptr) return;
|
||||
double temp;
|
||||
bool result;
|
||||
temp = ui->txtScale->text().toDouble(&result);
|
||||
@@ -130,24 +127,24 @@ DBCSignalEditor::DBCSignalEditor(QWidget *parent) :
|
||||
connect(ui->txtComment, &QLineEdit::editingFinished,
|
||||
[=]()
|
||||
{
|
||||
if (currentSignal == NULL) return;
|
||||
if (currentSignal == nullptr) return;
|
||||
currentSignal->comment = ui->txtComment->text().simplified().replace(' ', '_');
|
||||
});
|
||||
|
||||
connect(ui->txtUnitName, &QLineEdit::editingFinished,
|
||||
[=]()
|
||||
{
|
||||
if (currentSignal == NULL) return;
|
||||
if (currentSignal == nullptr) return;
|
||||
currentSignal->unitName = ui->txtUnitName->text().simplified().replace(' ', '_');
|
||||
});
|
||||
connect(ui->txtBitLength, &QLineEdit::textChanged,
|
||||
[=]()
|
||||
{
|
||||
if (currentSignal == NULL) return;
|
||||
if (currentSignal == nullptr) return;
|
||||
int temp;
|
||||
temp = Utility::ParseStringToNum(ui->txtBitLength->text());
|
||||
if (temp < 0) return;
|
||||
if (temp > 63) return;
|
||||
if (temp < 1) return;
|
||||
if (temp > 64) return;
|
||||
if (currentSignal->valType != SP_FLOAT && currentSignal->valType != DP_FLOAT)
|
||||
currentSignal->signalSize = temp;
|
||||
fillSignalForm(currentSignal);
|
||||
@@ -155,7 +152,7 @@ DBCSignalEditor::DBCSignalEditor(QWidget *parent) :
|
||||
connect(ui->txtName, &QLineEdit::editingFinished,
|
||||
[=]()
|
||||
{
|
||||
if (currentSignal == NULL) return;
|
||||
if (currentSignal == nullptr) return;
|
||||
QString tempNameStr = ui->txtName->text().simplified().replace(' ', '_');
|
||||
if (tempNameStr.length() > 0) currentSignal->name = tempNameStr;
|
||||
//need to update the list too.
|
||||
@@ -165,7 +162,7 @@ DBCSignalEditor::DBCSignalEditor(QWidget *parent) :
|
||||
connect(ui->txtMultiplexValue, &QLineEdit::editingFinished,
|
||||
[=]()
|
||||
{
|
||||
if (currentSignal == NULL) return;
|
||||
if (currentSignal == nullptr) return;
|
||||
int temp;
|
||||
temp = Utility::ParseStringToNum(ui->txtMultiplexValue->text());
|
||||
//TODO: could look up the multiplexor and ensure that the value is within a range that the multiplexor could return
|
||||
@@ -179,7 +176,7 @@ DBCSignalEditor::DBCSignalEditor(QWidget *parent) :
|
||||
currentSignal->isMultiplexed = true;
|
||||
currentSignal->isMultiplexor = false;
|
||||
//if the set multiplexor for the message was this signal then clear it
|
||||
if (dbcMessage->multiplexorSignal == currentSignal) dbcMessage->multiplexorSignal = NULL;
|
||||
if (dbcMessage->multiplexorSignal == currentSignal) dbcMessage->multiplexorSignal = nullptr;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -189,7 +186,7 @@ DBCSignalEditor::DBCSignalEditor(QWidget *parent) :
|
||||
if (state && currentSignal) //signal is now set as a multiplexed signal
|
||||
{
|
||||
//don't allow this signal to be a multiplexor if there is already one for this message.
|
||||
//if (dbcMessage->multiplexorSignal != currentSignal && dbcMessage->multiplexorSignal != NULL) return; //I spoke too soon above...
|
||||
//if (dbcMessage->multiplexorSignal != currentSignal && dbcMessage->multiplexorSignal != nullptr) return; //I spoke too soon above...
|
||||
currentSignal->isMultiplexed = false;
|
||||
currentSignal->isMultiplexor = true;
|
||||
//we just set that this is the multiplexor so update the message to show that as well.
|
||||
@@ -204,7 +201,7 @@ DBCSignalEditor::DBCSignalEditor(QWidget *parent) :
|
||||
{
|
||||
currentSignal->isMultiplexed = false;
|
||||
currentSignal->isMultiplexor = false;
|
||||
if (dbcMessage->multiplexorSignal == currentSignal) dbcMessage->multiplexorSignal = NULL;
|
||||
if (dbcMessage->multiplexorSignal == currentSignal) dbcMessage->multiplexorSignal = nullptr;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -286,7 +283,7 @@ void DBCSignalEditor::showEvent(QShowEvent* event)
|
||||
{
|
||||
QDialog::showEvent(event);
|
||||
|
||||
currentSignal = NULL;
|
||||
currentSignal = nullptr;
|
||||
refreshSignalsList();
|
||||
}
|
||||
|
||||
@@ -422,7 +419,7 @@ void DBCSignalEditor::deleteCurrentSignal()
|
||||
{
|
||||
delete(ui->signalsList->item(currIdx));
|
||||
dbcMessage->sigHandler->removeSignal(currIdx);
|
||||
currentSignal = NULL;
|
||||
currentSignal = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -461,7 +458,7 @@ void DBCSignalEditor::fillSignalForm(DBC_SIGNAL *sig)
|
||||
{
|
||||
unsigned char bitpattern[8];
|
||||
|
||||
if (sig == NULL) {
|
||||
if (sig == nullptr) {
|
||||
ui->groupBox->setEnabled(false);
|
||||
ui->txtName->setText("");
|
||||
ui->txtBias->setText("");
|
||||
@@ -578,7 +575,7 @@ void DBCSignalEditor::fillValueTable(DBC_SIGNAL *sig)
|
||||
ui->valuesTable->clearContents();
|
||||
ui->valuesTable->setRowCount(0);
|
||||
|
||||
if (sig == NULL) {
|
||||
if (sig == nullptr) {
|
||||
ui->valuesTable->setEnabled(false);
|
||||
inhibitCellChanged = false;
|
||||
return;
|
||||
@@ -588,7 +585,7 @@ void DBCSignalEditor::fillValueTable(DBC_SIGNAL *sig)
|
||||
|
||||
for (int i = 0; i < sig->valList.count(); i++)
|
||||
{
|
||||
QTableWidgetItem *val = new QTableWidgetItem(Utility::formatNumber(sig->valList[i].value));
|
||||
QTableWidgetItem *val = new QTableWidgetItem(Utility::formatNumber((uint64_t)sig->valList[i].value));
|
||||
QTableWidgetItem *desc = new QTableWidgetItem(sig->valList[i].descript);
|
||||
rowIdx = ui->valuesTable->rowCount();
|
||||
ui->valuesTable->insertRow(rowIdx);
|
||||
@@ -606,7 +603,7 @@ void DBCSignalEditor::clickSignalList(int row)
|
||||
{
|
||||
//qDebug() << ui->signalsList->item(row)->text();
|
||||
|
||||
DBC_SIGNAL *thisSig = (row<0) ? NULL : dbcMessage->sigHandler->findSignalByName(ui->signalsList->item(row)->text());
|
||||
DBC_SIGNAL *thisSig = (row<0) ? nullptr : dbcMessage->sigHandler->findSignalByName(ui->signalsList->item(row)->text());
|
||||
currentSignal = thisSig;
|
||||
fillSignalForm(thisSig);
|
||||
fillValueTable(thisSig);
|
||||
@@ -615,7 +612,7 @@ void DBCSignalEditor::clickSignalList(int row)
|
||||
void DBCSignalEditor::bitfieldClicked(int x, int y)
|
||||
{
|
||||
int bit = (7 - x) + (y * 8);
|
||||
if (currentSignal == NULL) return;
|
||||
if (currentSignal == nullptr) return;
|
||||
currentSignal->startBit = bit;
|
||||
if (currentSignal->valType == SP_FLOAT)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
make qthelp
|
||||
~/Qt/5.12.1/gcc_64/bin/qcollectiongenerator build/qthelp/SavvyCAN.qhcp
|
||||
~/Qt/5.12.1/gcc_64/bin/assistant -collectionFile build/qthelp/SavvyCAN.qhc
|
||||
cp -R build/qthelp/* ../help
|
||||
rm ../help/SavvyCAN.q*
|
||||
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
#include "utility.h"
|
||||
#include "filterutility.h"
|
||||
#include "dbc/dbchandler.h"
|
||||
#include <QSettings>
|
||||
|
||||
uint32_t FilterUtility::getIdAsInt( QListWidgetItem * item )
|
||||
{
|
||||
return Utility::ParseStringToNum(getId(item));
|
||||
}
|
||||
|
||||
QString FilterUtility::getId( QString itemText )
|
||||
{
|
||||
if (itemText.contains(" "))
|
||||
// Strip away the filter label
|
||||
return itemText.left(itemText.indexOf(" "));
|
||||
else
|
||||
return itemText;
|
||||
}
|
||||
|
||||
QString FilterUtility::getId( QListWidgetItem * item )
|
||||
{
|
||||
return getId(item->text());
|
||||
}
|
||||
|
||||
uint32_t FilterUtility::getGMLanArbitrationId(int32_t id)
|
||||
{
|
||||
return (id >> 13) & 0x1FFF;
|
||||
}
|
||||
|
||||
uint32_t FilterUtility::getGMLanPriorityBits(int32_t id)
|
||||
{
|
||||
return (id >> 26) & 0x7;
|
||||
}
|
||||
|
||||
uint32_t FilterUtility::getGMLanSenderId(int32_t id)
|
||||
{
|
||||
return id & 0x1FFF;
|
||||
}
|
||||
|
||||
QListWidgetItem * FilterUtility::createCheckableFilterItem(int32_t id, bool checked, QListWidget* parent)
|
||||
{
|
||||
QListWidgetItem * thisItem = createFilterItem(id,parent);
|
||||
thisItem->setFlags(thisItem->flags() | Qt::ItemIsUserCheckable);
|
||||
if (checked)
|
||||
thisItem->setCheckState(Qt::Checked);
|
||||
else
|
||||
thisItem->setCheckState(Qt::Unchecked);
|
||||
return thisItem;
|
||||
}
|
||||
|
||||
QListWidgetItem * FilterUtility::createFilterItem(int32_t id, QListWidget* parent)
|
||||
{
|
||||
QSettings settings;
|
||||
DBCHandler * dbcHandler = DBCHandler::getReference();
|
||||
QListWidgetItem *thisItem = new QListWidgetItem(parent);
|
||||
QString filterItemName = Utility::formatCANID(id);
|
||||
|
||||
if (settings.value("Main/FilterLabeling", false).toBool())
|
||||
{
|
||||
// Filter labeling (show interpreted frame names next to the CAN addr ID)
|
||||
MatchingCriteria_t matchingCriteria;
|
||||
DBC_MESSAGE *msg = dbcHandler->findMessageForFilter(id,&matchingCriteria);
|
||||
if (msg != NULL)
|
||||
{
|
||||
filterItemName.append(" ");
|
||||
filterItemName.append(msg->name);
|
||||
|
||||
// Create tooltip to show the whole name just in case it's too long to fit in the filter window.
|
||||
// Also if the matching criteria is set to GMLAN, show the Arbitration ID as well
|
||||
QString tooltip;
|
||||
if (matchingCriteria == GMLAN)
|
||||
tooltip.append("0x" + QString::number(FilterUtility::getGMLanArbitrationId(id), 16).toUpper().rightJustified(4,'0') + ": ");
|
||||
tooltip.append(msg->name);
|
||||
thisItem->setToolTip(tooltip);
|
||||
}
|
||||
}
|
||||
|
||||
thisItem->setText(filterItemName);
|
||||
return thisItem;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#ifndef FILTERUTILITY_H
|
||||
#define FILTERUTILITY_H
|
||||
|
||||
#include <QListWidget>
|
||||
|
||||
|
||||
class FilterUtility
|
||||
{
|
||||
|
||||
public:
|
||||
static QListWidgetItem * createFilterItem(int32_t id, QListWidget* parent=NULL); // if parent is given, add item automatically to listwidget
|
||||
static QListWidgetItem * createCheckableFilterItem(int32_t id, bool checked, QListWidget* parent=NULL);
|
||||
|
||||
static uint32_t getIdAsInt( QListWidgetItem * item );
|
||||
static QString getId( QListWidgetItem * item );
|
||||
static QString getId( QString itemText );
|
||||
|
||||
static uint32_t getGMLanArbitrationId(int32_t id);
|
||||
static uint32_t getGMLanSenderId(int32_t id);
|
||||
static uint32_t getGMLanPriorityBits(int32_t id);
|
||||
};
|
||||
|
||||
#endif // DBCLOADSAVEWINDOW_H
|
||||
@@ -4,6 +4,13 @@
|
||||
|
||||
#include <QFile>
|
||||
|
||||
//You might wonder: Collin, what in the hell is this for? Firmware uploader? For what? I'm interested! Well, it's a custom
|
||||
//firmware uploader for a motor controller I built. Why would that be in this project. Cuz. It's not really relevant
|
||||
//to anyone else but might serve as a decent reference for a few things: How to make an uploader interface that runs over CAN,
|
||||
//how to lay out a screen like this, how to make a comm protocol for firmware updating over CAN. But, most things use UDS
|
||||
//for firmware updates and wouldn't need this specific code. But, it might be able to be turned into a UDS firmware uploader or downloader.
|
||||
//Note that this screen is specifically hidden by default because of it's oddball status. You have to re-enable it in mainwindow.cpp to see it.
|
||||
|
||||
FirmwareUploaderWindow::FirmwareUploaderWindow(const QVector<CANFrame> *frames, QWidget *parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::FirmwareUploaderWindow)
|
||||
@@ -70,17 +77,20 @@ void FirmwareUploaderWindow::updatedFrames(int numFrames)
|
||||
|
||||
void FirmwareUploaderWindow::gotTargettedFrame(CANFrame frame)
|
||||
{
|
||||
qDebug() << "FUW: Got targetted frame with id " << frame.ID;
|
||||
if (frame.ID == (uint32_t)(baseAddress + 0x10)) {
|
||||
unsigned char *data = reinterpret_cast<unsigned char *>(frame.payload().data());
|
||||
int dataLen = frame.payload().count();
|
||||
|
||||
qDebug() << "FUW: Got targetted frame with id " << frame.frameId();
|
||||
if (frame.frameId() == (uint32_t)(baseAddress + 0x10) && (dataLen == 8) ) {
|
||||
qDebug() << "Start firmware reply";
|
||||
if ((frame.data[0] == 0xAD) && (frame.data[1] == 0xDE))
|
||||
if ((data[0] == 0xAD) && (data[1] == 0xDE))
|
||||
{
|
||||
if ((frame.data[2] == 0xAF) && (frame.data[3] == 0xDE))
|
||||
if ((data[2] == 0xAF) && (data[3] == 0xDE))
|
||||
{
|
||||
qDebug() << "THere's dead beef here";
|
||||
if ((frame.data[4] == (token & 0xFF)) && (frame.data[5] == ((token >> 8) & 0xFF)))
|
||||
qDebug() << "There's dead beef here";
|
||||
if ( (data[4] == (token & 0xFF)) && (data[5] == ((token >> 8) & 0xFF) ) )
|
||||
{
|
||||
if ((frame.data[6] == ((token >> 16) & 0xFF)) && (frame.data[7] == ((token >> 24) & 0xFF)))
|
||||
if ((data[6] == ((token >> 16) & 0xFF)) && (data[7] == ((token >> 24) & 0xFF)))
|
||||
{
|
||||
qDebug() << "starting firmware process";
|
||||
//MainWindow::getReference()->setTargettedID(baseAddress + 0x20);
|
||||
@@ -92,9 +102,9 @@ void FirmwareUploaderWindow::gotTargettedFrame(CANFrame frame)
|
||||
}
|
||||
}
|
||||
|
||||
if (frame.ID == (uint32_t)(baseAddress + 0x20)) {
|
||||
if (frame.frameId() == (uint32_t)(baseAddress + 0x20)) {
|
||||
qDebug() << "Firmware reception success reply";
|
||||
int seq = frame.data[0] + (256 * frame.data[1]);
|
||||
int seq = data[0] + (256 * data[1]);
|
||||
if (seq == currentSendingPosition)
|
||||
{
|
||||
currentSendingPosition++;
|
||||
@@ -126,18 +136,19 @@ void FirmwareUploaderWindow::sendFirmwareChunk()
|
||||
CANFrame *output = new CANFrame;
|
||||
int firmwareLocation = currentSendingPosition * 4;
|
||||
int xorByte = 0;
|
||||
output->extended = false;
|
||||
output->len = 7;
|
||||
output->setExtendedFrameFormat(false);
|
||||
QByteArray bytes(7,0);
|
||||
output->bus = bus;
|
||||
output->ID = baseAddress + 0x16;
|
||||
output->data[0] = currentSendingPosition & 0xFF;
|
||||
output->data[1] = (currentSendingPosition >> 8) & 0xFF;
|
||||
output->data[2] = firmwareData[firmwareLocation++];
|
||||
output->data[3] = firmwareData[firmwareLocation++];
|
||||
output->data[4] = firmwareData[firmwareLocation++];
|
||||
output->data[5] = firmwareData[firmwareLocation++];
|
||||
for (int i = 0; i < 6; i++) xorByte = xorByte ^ output->data[i];
|
||||
output->data[6] = xorByte;
|
||||
output->setFrameId(baseAddress + 0x16);
|
||||
output->payload()[0] = currentSendingPosition & 0xFF;
|
||||
output->payload()[1] = (currentSendingPosition >> 8) & 0xFF;
|
||||
output->payload()[2] = firmwareData[firmwareLocation++];
|
||||
output->payload()[3] = firmwareData[firmwareLocation++];
|
||||
output->payload()[4] = firmwareData[firmwareLocation++];
|
||||
output->payload()[5] = firmwareData[firmwareLocation++];
|
||||
for (int i = 0; i < 6; i++) xorByte = xorByte ^ static_cast<unsigned char>(output->payload()[i]);
|
||||
output->payload()[6] = xorByte;
|
||||
output->setPayload(bytes);
|
||||
sendCANFrame(output);
|
||||
timer->start();
|
||||
}
|
||||
@@ -145,14 +156,15 @@ void FirmwareUploaderWindow::sendFirmwareChunk()
|
||||
void FirmwareUploaderWindow::sendFirmwareEnding()
|
||||
{
|
||||
CANFrame *output = new CANFrame;
|
||||
output->extended = false;
|
||||
output->setExtendedFrameFormat(false);
|
||||
output->bus = bus;
|
||||
output->len = 4;
|
||||
output->ID = baseAddress + 0x30;
|
||||
output->data[3] = 0xC0;
|
||||
output->data[2] = 0xDE;
|
||||
output->data[1] = 0xFA;
|
||||
output->data[0] = 0xDE;
|
||||
QByteArray bytes(4,0);
|
||||
output->setFrameId(baseAddress + 0x30);
|
||||
output->payload()[3] = 0xC0;
|
||||
output->payload()[2] = 0xDE;
|
||||
output->payload()[1] = 0xFA;
|
||||
output->payload()[0] = 0xDE;
|
||||
output->setPayload(bytes);
|
||||
//sendCANFrame(output, bus);
|
||||
}
|
||||
|
||||
@@ -171,19 +183,19 @@ void FirmwareUploaderWindow::handleStartStopTransfer()
|
||||
CANConManager::getInstance()->addTargettedFrame(bus, baseAddress + 0x10, 0x7FF, this);
|
||||
CANConManager::getInstance()->addTargettedFrame(bus, baseAddress + 0x20, 0x7FF, this);
|
||||
CANFrame *output = new CANFrame;
|
||||
output->extended = false;
|
||||
output->len = 8;
|
||||
output->setExtendedFrameFormat(false);
|
||||
QByteArray bytes(8,0);
|
||||
output->bus = bus;
|
||||
output->ID = baseAddress;
|
||||
output->setFrameId(baseAddress);
|
||||
|
||||
output->data[0] = 0xEF;
|
||||
output->data[1] = 0xBE;
|
||||
output->data[2] = 0xAD;
|
||||
output->data[3] = 0xDE;
|
||||
output->data[4] = token & 0xFF;
|
||||
output->data[5] = (token >> 8) & 0xFF;
|
||||
output->data[6] = (token >> 16) & 0xFF;
|
||||
output->data[7] = (token >> 24) & 0xFF;
|
||||
output->payload()[0] = 0xEF;
|
||||
output->payload()[1] = 0xBE;
|
||||
output->payload()[2] = 0xAD;
|
||||
output->payload()[3] = 0xDE;
|
||||
output->payload()[4] = token & 0xFF;
|
||||
output->payload()[5] = (token >> 8) & 0xFF;
|
||||
output->payload()[6] = (token >> 16) & 0xFF;
|
||||
output->payload()[7] = (token >> 24) & 0xFF;
|
||||
sendCANFrame(output);
|
||||
}
|
||||
else //stop anything in process
|
||||
|
||||
@@ -41,6 +41,7 @@ public:
|
||||
static bool loadCANDOFile(QString, QVector<CANFrame>*);
|
||||
static bool loadVehicleSpyFile(QString, QVector<CANFrame>*);
|
||||
static bool loadCanDumpFile(QString, QVector<CANFrame>*);
|
||||
static bool loadLawicelFile(QString, QVector<CANFrame>*);
|
||||
static bool loadPCANFile(QString, QVector<CANFrame>*);
|
||||
static bool loadKvaserFile(QString, QVector<CANFrame>*, bool);
|
||||
static bool loadCanalyzerASC(QString, QVector<CANFrame>*);
|
||||
@@ -61,6 +62,7 @@ public:
|
||||
static bool isCANDOFile(QString);
|
||||
static bool isVehicleSpyFile(QString);
|
||||
static bool isCanDumpFile(QString);
|
||||
static bool isLawicelFile(QString);
|
||||
static bool isPCANFile(QString);
|
||||
static bool isKvaserFile(QString);
|
||||
static bool isCanalyzerASC(QString);
|
||||
|
||||
@@ -68,7 +68,7 @@ quint64 FramePlaybackObject::updatePosition(bool forward)
|
||||
//only send frame out if its ID is checked in the list. Otherwise discard it.
|
||||
CANFrame *thisFrame = ¤tSeqItem->data[currentPosition];
|
||||
uint32_t originalBus = thisFrame->bus;
|
||||
if (currentSeqItem->idFilters.find(thisFrame->ID).value())
|
||||
if (currentSeqItem->idFilters.find(thisFrame->frameId()).value())
|
||||
{
|
||||
if (whichBusSend > -1)
|
||||
{
|
||||
@@ -90,7 +90,7 @@ quint64 FramePlaybackObject::updatePosition(bool forward)
|
||||
|
||||
thisFrame->bus = originalBus;
|
||||
}
|
||||
return thisFrame->timestamp;
|
||||
return thisFrame->timeStamp().microSeconds();
|
||||
}
|
||||
|
||||
quint64 FramePlaybackObject::peekPosition(bool forward)
|
||||
@@ -113,7 +113,7 @@ quint64 FramePlaybackObject::peekPosition(bool forward)
|
||||
}
|
||||
}
|
||||
CANFrame *thisFrame = ¤tSeqItem->data[peekCurrentPosition];
|
||||
return thisFrame->timestamp;
|
||||
return thisFrame->timeStamp().microSeconds();
|
||||
}
|
||||
|
||||
void FramePlaybackObject::piStart()
|
||||
@@ -197,8 +197,8 @@ void FramePlaybackObject::startPlaybackForward()
|
||||
{
|
||||
playbackTimer->setInterval(1);
|
||||
playbackElapsed.start();
|
||||
if (currentSeqItem->data[currentPosition].timestamp > 2000)
|
||||
playbackLastTimeStamp = currentSeqItem->data[currentPosition].timestamp - 2000;
|
||||
if (currentSeqItem->data[currentPosition].timeStamp().microSeconds() > 2000)
|
||||
playbackLastTimeStamp = currentSeqItem->data[currentPosition].timeStamp().microSeconds() - 2000;
|
||||
else playbackLastTimeStamp = 0;
|
||||
}
|
||||
playbackTimer->start();
|
||||
@@ -219,7 +219,7 @@ void FramePlaybackObject::startPlaybackBackward()
|
||||
{
|
||||
playbackElapsed.start();
|
||||
playbackTimer->setInterval(1);
|
||||
playbackLastTimeStamp = currentSeqItem->data[currentPosition].timestamp + 2000;
|
||||
playbackLastTimeStamp = currentSeqItem->data[currentPosition].timeStamp().microSeconds() + 2000;
|
||||
}
|
||||
playbackTimer->start();
|
||||
}
|
||||
@@ -350,8 +350,8 @@ void FramePlaybackObject::timerTriggered()
|
||||
if (peekPosition(true) == 0xFFFFFFFFFFFFFFFFull)
|
||||
{
|
||||
updatePosition(true); //this'll go to the next log (if there is one)
|
||||
if (currentSeqItem->data[currentPosition].timestamp > 1000)
|
||||
playbackLastTimeStamp = currentSeqItem->data[currentPosition].timestamp - 1000;
|
||||
if (currentSeqItem->data[currentPosition].timeStamp().microSeconds() > 1000)
|
||||
playbackLastTimeStamp = currentSeqItem->data[currentPosition].timeStamp().microSeconds() - 1000;
|
||||
else playbackLastTimeStamp = 0;
|
||||
}
|
||||
}
|
||||
@@ -367,7 +367,7 @@ void FramePlaybackObject::timerTriggered()
|
||||
if (peekPosition(false) == 0xFFFFFFFFFFFFFFFFull)
|
||||
{
|
||||
updatePosition(false); //this'll go to the next log (if there is one)
|
||||
playbackLastTimeStamp = currentSeqItem->data[currentPosition].timestamp + 1000;
|
||||
playbackLastTimeStamp = currentSeqItem->data[currentPosition].timeStamp().microSeconds() + 1000;
|
||||
}
|
||||
}
|
||||
statusCounter++;
|
||||
|
||||
@@ -4,8 +4,11 @@
|
||||
#include <QFileDialog>
|
||||
#include <QMenu>
|
||||
#include <QSettings>
|
||||
#include <qevent.h>
|
||||
#include <QScrollBar>
|
||||
#include "connections/canconmanager.h"
|
||||
#include "helpwindow.h"
|
||||
#include "filterutility.h"
|
||||
|
||||
/*
|
||||
* Notes about new functionality:
|
||||
@@ -40,7 +43,7 @@ FramePlaybackWindow::FramePlaybackWindow(const QVector<CANFrame> *frames, QWidge
|
||||
|
||||
modelFrames = frames;
|
||||
|
||||
currentSeqItem = NULL;
|
||||
currentSeqItem = nullptr;
|
||||
currentSeqNum = -1;
|
||||
currentPosition = 0;
|
||||
forward = true;
|
||||
@@ -78,6 +81,9 @@ FramePlaybackWindow::FramePlaybackWindow(const QVector<CANFrame> *frames, QWidge
|
||||
|
||||
playbackObject.setPlaybackInterval(ui->spinPlaySpeed->value());
|
||||
|
||||
// Prevent annoying accidental horizontal scrolling when filter list is populated with long interpreted message names
|
||||
ui->listID->horizontalScrollBar()->setEnabled(false);
|
||||
|
||||
QStringList headers;
|
||||
headers << "Source" << "Loops";
|
||||
ui->tblSequence->setColumnCount(2);
|
||||
@@ -197,7 +203,7 @@ void FramePlaybackWindow::saveFilters()
|
||||
|
||||
for (int c = 0; c < ui->listID->count(); c++)
|
||||
{
|
||||
outFile->write(QString::number(ui->listID->item(c)->text().toInt(NULL, 16), 16).toUtf8());
|
||||
outFile->write(QString::number(FilterUtility::getId(ui->listID->item(c)).toInt(nullptr, 16), 16).toUtf8());
|
||||
outFile->putChar(',');
|
||||
if (ui->listID->item(c)->checkState() == Qt::Checked) outFile->putChar('T');
|
||||
else outFile->putChar('F');
|
||||
@@ -244,7 +250,7 @@ void FramePlaybackWindow::loadFilters()
|
||||
if (line.length() > 2)
|
||||
{
|
||||
QList<QByteArray> tokens = line.split(',');
|
||||
ID = tokens[0].toInt(NULL, 16);
|
||||
ID = tokens[0].toInt(nullptr, 16);
|
||||
if (tokens[1].toUpper() == "T") checked = true;
|
||||
else checked = false;
|
||||
if (checked)
|
||||
@@ -260,7 +266,7 @@ void FramePlaybackWindow::loadFilters()
|
||||
for (int c = 0; c < ui->listID->count(); c++)
|
||||
{
|
||||
QListWidgetItem *item = ui->listID->item(c);
|
||||
if (item->text().toInt(NULL, 16) == ID)
|
||||
if (FilterUtility::getId(item).toInt(nullptr, 16) == ID)
|
||||
{
|
||||
item->setCheckState(Qt::Checked);
|
||||
}
|
||||
@@ -275,7 +281,7 @@ void FramePlaybackWindow::loadFilters()
|
||||
|
||||
void FramePlaybackWindow::refreshIDList()
|
||||
{
|
||||
if (currentSeqNum < 0 || currentSeqItem == NULL)
|
||||
if (currentSeqNum < 0 || currentSeqItem == nullptr)
|
||||
{
|
||||
ui->listID->clear();
|
||||
return;
|
||||
@@ -291,10 +297,7 @@ void FramePlaybackWindow::refreshIDList()
|
||||
QHash<int, bool>::Iterator filterIter;
|
||||
for (filterIter = currentSeqItem->idFilters.begin(); filterIter != currentSeqItem->idFilters.end(); ++filterIter)
|
||||
{
|
||||
QListWidgetItem* listItem = new QListWidgetItem(Utility::formatCANID(filterIter.key()), ui->listID);
|
||||
listItem->setFlags(listItem->flags() | Qt::ItemIsUserCheckable); // set checkable flag
|
||||
if (filterIter.value()) listItem->setCheckState(Qt::Checked);
|
||||
else listItem->setCheckState(Qt::Unchecked);
|
||||
/*QListWidgetItem* listItem = */ FilterUtility::createCheckableFilterItem(filterIter.key(), filterIter.value(), ui->listID);
|
||||
}
|
||||
//default is to sort in ascending order
|
||||
ui->listID->sortItems();
|
||||
@@ -423,7 +426,7 @@ void FramePlaybackWindow::fillIDHash(SequenceItem &item)
|
||||
|
||||
for (int i = 0; i < item.data.count(); i++)
|
||||
{
|
||||
id = item.data[i].ID;
|
||||
id = item.data[i].frameId();
|
||||
if (!item.idFilters.contains(id))
|
||||
{
|
||||
item.idFilters.insert(id, true);
|
||||
@@ -463,7 +466,7 @@ void FramePlaybackWindow::btnDeleteCurrSeq()
|
||||
else
|
||||
{
|
||||
currentSeqNum = -1;
|
||||
currentSeqItem = NULL;
|
||||
currentSeqItem = nullptr;
|
||||
}
|
||||
refreshIDList();
|
||||
updateFrameLabel();
|
||||
@@ -476,7 +479,7 @@ void FramePlaybackWindow::btnLoadFile()
|
||||
|
||||
if (FrameFileIO::loadFrameFile(filename, &item.data))
|
||||
{
|
||||
qSort(item.data); //sort by timestamp to be sure it's in order
|
||||
std::sort(item.data.begin(), item.data.end()); //sort by timestamp to be sure it's in order
|
||||
QStringList fileList = filename.split('/');
|
||||
item.filename = fileList[fileList.length() - 1];
|
||||
item.currentLoopCount = 0;
|
||||
@@ -510,7 +513,7 @@ void FramePlaybackWindow::btnLoadLive()
|
||||
item.currentLoopCount = 0;
|
||||
item.maxLoops = 1;
|
||||
item.data = QVector<CANFrame>(*modelFrames); //create a copy of the current frames from the main view
|
||||
qSort(item.data); //be sure it's all in time based order
|
||||
std::sort(item.data.begin(), item.data.end()); //be sure it's all in time based order
|
||||
fillIDHash(item);
|
||||
if (ui->tblSequence->currentRow() == -1)
|
||||
{
|
||||
@@ -564,7 +567,7 @@ void FramePlaybackWindow::btnStopClick()
|
||||
}
|
||||
else {
|
||||
currentSeqNum = -1;
|
||||
currentSeqItem = NULL;
|
||||
currentSeqItem = nullptr;
|
||||
}
|
||||
if (ui->tblSequence->rowCount() > 0)
|
||||
{
|
||||
@@ -604,13 +607,14 @@ void FramePlaybackWindow::changeLooping(bool check)
|
||||
|
||||
void FramePlaybackWindow::changeSendingBus(int newIdx)
|
||||
{
|
||||
Q_UNUSED(newIdx);
|
||||
calculateWhichBus();
|
||||
}
|
||||
|
||||
void FramePlaybackWindow::changeIDFiltering(QListWidgetItem *item)
|
||||
{
|
||||
qDebug() << "Changed ID filter " << item->text() << " : " << item->checkState();
|
||||
int ID = Utility::ParseStringToNum(item->text());
|
||||
int ID = FilterUtility::getIdAsInt(item);
|
||||
currentSeqItem->idFilters[ID] = (item->checkState() == Qt::Checked) ? true : false;
|
||||
}
|
||||
|
||||
|
||||
@@ -130,13 +130,13 @@ void FrameSenderWindow::buildFrameCache()
|
||||
for (int i = 0; i < modelFrames->count(); i++)
|
||||
{
|
||||
thisFrame = modelFrames->at(i);
|
||||
if (!frameCache.contains(thisFrame.ID))
|
||||
if (!frameCache.contains(thisFrame.frameId()))
|
||||
{
|
||||
frameCache.insert(thisFrame.ID, thisFrame);
|
||||
frameCache.insert(thisFrame.frameId(), thisFrame);
|
||||
}
|
||||
else
|
||||
{
|
||||
frameCache[thisFrame.ID] = thisFrame;
|
||||
frameCache[thisFrame.frameId()] = thisFrame;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -160,13 +160,13 @@ void FrameSenderWindow::updatedFrames(int numFrames)
|
||||
for (int i = modelFrames->count() - numFrames; i < modelFrames->count(); i++)
|
||||
{
|
||||
thisFrame = modelFrames->at(i);
|
||||
if (!frameCache.contains(thisFrame.ID))
|
||||
if (!frameCache.contains(thisFrame.frameId()))
|
||||
{
|
||||
frameCache.insert(thisFrame.ID, thisFrame);
|
||||
frameCache.insert(thisFrame.frameId(), thisFrame);
|
||||
}
|
||||
else
|
||||
{
|
||||
frameCache[thisFrame.ID] = thisFrame;
|
||||
frameCache[thisFrame.frameId()] = thisFrame;
|
||||
}
|
||||
processIncomingFrame(&thisFrame);
|
||||
}
|
||||
@@ -182,10 +182,10 @@ void FrameSenderWindow::processIncomingFrame(CANFrame *frame)
|
||||
{
|
||||
Trigger *thisTrigger = &sendingData[sd].triggers[trig];
|
||||
qDebug() << "Trigger ID: " << thisTrigger->ID;
|
||||
qDebug() << "Frame ID: " << frame->ID;
|
||||
if (thisTrigger->ID > 0 && (uint32_t)thisTrigger->ID == frame->ID)
|
||||
qDebug() << "Frame ID: " << frame->frameId();
|
||||
if (thisTrigger->ID > 0 && (uint32_t)thisTrigger->ID == frame->frameId())
|
||||
{
|
||||
if ((uint32_t)thisTrigger->bus == frame->bus || thisTrigger->bus == -1)
|
||||
if (thisTrigger->bus == frame->bus || thisTrigger->bus == -1)
|
||||
{
|
||||
if (thisTrigger->currCount < thisTrigger->maxCount)
|
||||
{
|
||||
@@ -511,13 +511,13 @@ void FrameSenderWindow::doModifiers(int idx)
|
||||
}
|
||||
}
|
||||
//Finally, drop the result into the proper data byte
|
||||
sendData->data[mod->destByte] = (unsigned char) shadowReg;
|
||||
sendData->payload().data()[mod->destByte] = (char) shadowReg;
|
||||
}
|
||||
}
|
||||
|
||||
int FrameSenderWindow::fetchOperand(int idx, ModifierOperand op)
|
||||
{
|
||||
CANFrame *tempFrame = NULL;
|
||||
CANFrame *tempFrame = nullptr;
|
||||
if (op.ID == 0) //numeric constant
|
||||
{
|
||||
if (op.notOper) return ~op.databyte;
|
||||
@@ -525,16 +525,16 @@ int FrameSenderWindow::fetchOperand(int idx, ModifierOperand op)
|
||||
}
|
||||
else if (op.ID == -2) //fetch data from a data byte within the output frame
|
||||
{
|
||||
if (op.notOper) return ~sendingData.at(idx).data[op.databyte];
|
||||
else return sendingData.at(idx).data[op.databyte];
|
||||
if (op.notOper) return ~((unsigned char)sendingData.at(idx).payload()[op.databyte]);
|
||||
else return (unsigned char)sendingData.at(idx).payload()[op.databyte];
|
||||
}
|
||||
else //look up external data byte
|
||||
{
|
||||
tempFrame = lookupFrame(op.ID, op.bus);
|
||||
if (tempFrame != NULL)
|
||||
if (tempFrame != nullptr)
|
||||
{
|
||||
if (op.notOper) return ~tempFrame->data[op.databyte];
|
||||
else return tempFrame->data[op.databyte];
|
||||
if (op.notOper) return ~((unsigned char)tempFrame->payload()[op.databyte]);
|
||||
else return (unsigned char)tempFrame->payload()[op.databyte];
|
||||
}
|
||||
else return 0;
|
||||
}
|
||||
@@ -548,11 +548,11 @@ int FrameSenderWindow::fetchOperand(int idx, ModifierOperand op)
|
||||
/// <returns></returns>
|
||||
CANFrame* FrameSenderWindow::lookupFrame(int ID, int bus)
|
||||
{
|
||||
if (!frameCache.contains(ID)) return NULL;
|
||||
if (!frameCache.contains(ID)) return nullptr;
|
||||
|
||||
if (bus == -1 || frameCache[ID].bus == (unsigned int)bus) return &frameCache[ID];
|
||||
if (bus == -1 || frameCache[ID].bus == bus) return &frameCache[ID];
|
||||
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -643,7 +643,7 @@ void FrameSenderWindow::processModifierText(int line)
|
||||
}
|
||||
else thisOp.second.notOper = false;
|
||||
thisOp.second.bus = sendingData[line].bus;
|
||||
thisOp.second.ID = sendingData[line].ID;
|
||||
thisOp.second.ID = sendingData[line].frameId();
|
||||
parseOperandString(secondOp.split(":"), thisOp.second);
|
||||
thisMod.operations.append(thisOp);
|
||||
}
|
||||
@@ -786,17 +786,21 @@ ModifierOperationType FrameSenderWindow::parseOperation(QString op)
|
||||
void FrameSenderWindow::updateGridRow(int idx)
|
||||
{
|
||||
qDebug() << "updateGridRow";
|
||||
|
||||
inhibitChanged = true;
|
||||
FrameSendData *temp = &sendingData[idx];
|
||||
int gridLine = idx;
|
||||
QString dataString;
|
||||
QTableWidgetItem *item = ui->tableSender->item(gridLine, 9);
|
||||
if (item == NULL) item = new QTableWidgetItem();
|
||||
unsigned char *data = reinterpret_cast<unsigned char *>(temp->payload().data());
|
||||
int dataLen = temp->payload().length();
|
||||
|
||||
if (item == nullptr) item = new QTableWidgetItem();
|
||||
item->setText(QString::number(temp->count));
|
||||
if (!temp->remote) {
|
||||
for (unsigned int i = 0; i < temp->len; i++)
|
||||
if (temp->frameType() != QCanBusFrame::RemoteRequestFrame) {
|
||||
for (int i = 0; i < dataLen; i++)
|
||||
{
|
||||
dataString.append(Utility::formatNumber(temp->data[i]));
|
||||
dataString.append(Utility::formatNumber(data[i]));
|
||||
dataString.append(" ");
|
||||
}
|
||||
ui->tableSender->item(gridLine, 6)->setText(dataString);
|
||||
@@ -818,8 +822,8 @@ void FrameSenderWindow::processCellChange(int line, int col)
|
||||
{
|
||||
FrameSendData tempData;
|
||||
tempData.enabled = false;
|
||||
tempData.remote = false;
|
||||
tempData.extended = false;
|
||||
tempData.setFrameType(QCanBusFrame::DataFrame);
|
||||
tempData.setExtendedFrameFormat(false);
|
||||
sendingData.append(tempData);
|
||||
}
|
||||
|
||||
@@ -848,9 +852,9 @@ void FrameSenderWindow::processCellChange(int line, int col)
|
||||
tempVal = Utility::ParseStringToNum(ui->tableSender->item(line, 2)->text());
|
||||
if (tempVal < 0) tempVal = 0;
|
||||
if (tempVal > 0x7FFFFFFF) tempVal = 0x7FFFFFFF;
|
||||
sendingData[line].ID = tempVal;
|
||||
if (sendingData[line].ID > 0x7FF) {
|
||||
sendingData[line].extended = true;
|
||||
sendingData[line].setFrameId(tempVal);
|
||||
if (sendingData[line].frameId() > 0x7FF) {
|
||||
sendingData[line].setExtendedFrameFormat(true);
|
||||
ui->tableSender->blockSignals(true);
|
||||
ui->tableSender->item(line, 4)->setCheckState(Qt::Checked);
|
||||
ui->tableSender->blockSignals(false);
|
||||
@@ -861,29 +865,29 @@ void FrameSenderWindow::processCellChange(int line, int col)
|
||||
tempVal = Utility::ParseStringToNum(ui->tableSender->item(line, 3)->text());
|
||||
if (tempVal < 0) tempVal = 0;
|
||||
if (tempVal > 8) tempVal = 8;
|
||||
sendingData[line].len = tempVal;
|
||||
sendingData[line].payload().resize(tempVal);
|
||||
break;
|
||||
case 4: // Ext
|
||||
if (ui->tableSender->item(line, 4)->checkState() == Qt::Checked) {
|
||||
sendingData[line].extended = true;
|
||||
sendingData[line].setExtendedFrameFormat(true);
|
||||
} else {
|
||||
sendingData[line].extended = false;
|
||||
sendingData[line].setExtendedFrameFormat(false);
|
||||
}
|
||||
break;
|
||||
case 5: // Rem
|
||||
if (ui->tableSender->item(line, 5)->checkState() == Qt::Checked) {
|
||||
sendingData[line].remote = true;
|
||||
sendingData[line].setFrameType(QCanBusFrame::RemoteRequestFrame);
|
||||
} else {
|
||||
sendingData[line].remote = false;
|
||||
sendingData[line].setFrameType(QCanBusFrame::DataFrame);
|
||||
}
|
||||
break;
|
||||
case 6: //Data bytes
|
||||
for (int i = 0; i < 8; i++) sendingData[line].data[i] = 0;
|
||||
for (int i = 0; i < 8; i++) sendingData[line].payload().data()[i] = 0;
|
||||
|
||||
tokens = ui->tableSender->item(line, 6)->text().split(" ");
|
||||
for (int j = 0; j < tokens.count(); j++)
|
||||
{
|
||||
sendingData[line].data[j] = (uint8_t)Utility::ParseStringToNum(tokens[j]);
|
||||
sendingData[line].payload().data()[j] = (uint8_t)Utility::ParseStringToNum(tokens[j]);
|
||||
}
|
||||
break;
|
||||
case 7: //triggers
|
||||
|
||||
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 134 KiB |
|
After Width: | Height: | Size: 156 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 81 KiB |
|
After Width: | Height: | Size: 159 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 136 KiB |
|
After Width: | Height: | Size: 106 KiB |
|
After Width: | Height: | Size: 135 KiB |
|
After Width: | Height: | Size: 146 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 673 B |
@@ -0,0 +1,676 @@
|
||||
/*
|
||||
* basic.css
|
||||
* ~~~~~~~~~
|
||||
*
|
||||
* Sphinx stylesheet -- basic theme.
|
||||
*
|
||||
* :copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS.
|
||||
* :license: BSD, see LICENSE for details.
|
||||
*
|
||||
*/
|
||||
|
||||
/* -- main layout ----------------------------------------------------------- */
|
||||
|
||||
div.clearer {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
/* -- relbar ---------------------------------------------------------------- */
|
||||
|
||||
div.related {
|
||||
width: 100%;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
div.related h3 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.related ul {
|
||||
margin: 0;
|
||||
padding: 0 0 0 10px;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
div.related li {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
div.related li.right {
|
||||
float: right;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
/* -- sidebar --------------------------------------------------------------- */
|
||||
|
||||
div.sphinxsidebarwrapper {
|
||||
padding: 10px 5px 0 10px;
|
||||
}
|
||||
|
||||
div.sphinxsidebar {
|
||||
float: left;
|
||||
width: 230px;
|
||||
margin-left: -100%;
|
||||
font-size: 90%;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap : break-word;
|
||||
}
|
||||
|
||||
div.sphinxsidebar ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
div.sphinxsidebar ul ul,
|
||||
div.sphinxsidebar ul.want-points {
|
||||
margin-left: 20px;
|
||||
list-style: square;
|
||||
}
|
||||
|
||||
div.sphinxsidebar ul ul {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
div.sphinxsidebar form {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
div.sphinxsidebar input {
|
||||
border: 1px solid #98dbcc;
|
||||
font-family: sans-serif;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
div.sphinxsidebar #searchbox form.search {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
div.sphinxsidebar #searchbox input[type="text"] {
|
||||
float: left;
|
||||
width: 80%;
|
||||
padding: 0.25em;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
div.sphinxsidebar #searchbox input[type="submit"] {
|
||||
float: left;
|
||||
width: 20%;
|
||||
border-left: none;
|
||||
padding: 0.25em;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
||||
img {
|
||||
border: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* -- search page ----------------------------------------------------------- */
|
||||
|
||||
ul.search {
|
||||
margin: 10px 0 0 20px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul.search li {
|
||||
padding: 5px 0 5px 20px;
|
||||
background-image: url(file.png);
|
||||
background-repeat: no-repeat;
|
||||
background-position: 0 7px;
|
||||
}
|
||||
|
||||
ul.search li a {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
ul.search li div.context {
|
||||
color: #888;
|
||||
margin: 2px 0 0 30px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
ul.keywordmatches li.goodmatch a {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* -- index page ------------------------------------------------------------ */
|
||||
|
||||
table.contentstable {
|
||||
width: 90%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
table.contentstable p.biglink {
|
||||
line-height: 150%;
|
||||
}
|
||||
|
||||
a.biglink {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
span.linkdescr {
|
||||
font-style: italic;
|
||||
padding-top: 5px;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
/* -- general index --------------------------------------------------------- */
|
||||
|
||||
table.indextable {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table.indextable td {
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
table.indextable ul {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
table.indextable > tbody > tr > td > ul {
|
||||
padding-left: 0em;
|
||||
}
|
||||
|
||||
table.indextable tr.pcap {
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
table.indextable tr.cap {
|
||||
margin-top: 10px;
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
img.toggler {
|
||||
margin-right: 3px;
|
||||
margin-top: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
div.modindex-jumpbox {
|
||||
border-top: 1px solid #ddd;
|
||||
border-bottom: 1px solid #ddd;
|
||||
margin: 1em 0 1em 0;
|
||||
padding: 0.4em;
|
||||
}
|
||||
|
||||
div.genindex-jumpbox {
|
||||
border-top: 1px solid #ddd;
|
||||
border-bottom: 1px solid #ddd;
|
||||
margin: 1em 0 1em 0;
|
||||
padding: 0.4em;
|
||||
}
|
||||
|
||||
/* -- domain module index --------------------------------------------------- */
|
||||
|
||||
table.modindextable td {
|
||||
padding: 2px;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
/* -- general body styles --------------------------------------------------- */
|
||||
|
||||
div.body {
|
||||
min-width: 450px;
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
div.body p, div.body dd, div.body li, div.body blockquote {
|
||||
-moz-hyphens: auto;
|
||||
-ms-hyphens: auto;
|
||||
-webkit-hyphens: auto;
|
||||
hyphens: auto;
|
||||
}
|
||||
|
||||
a.headerlink {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
h1:hover > a.headerlink,
|
||||
h2:hover > a.headerlink,
|
||||
h3:hover > a.headerlink,
|
||||
h4:hover > a.headerlink,
|
||||
h5:hover > a.headerlink,
|
||||
h6:hover > a.headerlink,
|
||||
dt:hover > a.headerlink,
|
||||
caption:hover > a.headerlink,
|
||||
p.caption:hover > a.headerlink,
|
||||
div.code-block-caption:hover > a.headerlink {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
div.body p.caption {
|
||||
text-align: inherit;
|
||||
}
|
||||
|
||||
div.body td {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.first {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
p.rubric {
|
||||
margin-top: 30px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
img.align-left, .figure.align-left, object.align-left {
|
||||
clear: left;
|
||||
float: left;
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
img.align-right, .figure.align-right, object.align-right {
|
||||
clear: right;
|
||||
float: right;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
img.align-center, .figure.align-center, object.align-center {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.align-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.align-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.align-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* -- sidebars -------------------------------------------------------------- */
|
||||
|
||||
div.sidebar {
|
||||
margin: 0 0 0.5em 1em;
|
||||
border: 1px solid #ddb;
|
||||
padding: 7px 7px 0 7px;
|
||||
background-color: #ffe;
|
||||
width: 40%;
|
||||
float: right;
|
||||
}
|
||||
|
||||
p.sidebar-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* -- topics ---------------------------------------------------------------- */
|
||||
|
||||
div.topic {
|
||||
border: 1px solid #ccc;
|
||||
padding: 7px 7px 0 7px;
|
||||
margin: 10px 0 10px 0;
|
||||
}
|
||||
|
||||
p.topic-title {
|
||||
font-size: 1.1em;
|
||||
font-weight: bold;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
/* -- admonitions ----------------------------------------------------------- */
|
||||
|
||||
div.admonition {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
padding: 7px;
|
||||
}
|
||||
|
||||
div.admonition dt {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
div.admonition dl {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
p.admonition-title {
|
||||
margin: 0px 10px 5px 0px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
div.body p.centered {
|
||||
text-align: center;
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
/* -- tables ---------------------------------------------------------------- */
|
||||
|
||||
table.docutils {
|
||||
border: 0;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
table.align-center {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
table caption span.caption-number {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
table caption span.caption-text {
|
||||
}
|
||||
|
||||
table.docutils td, table.docutils th {
|
||||
padding: 1px 8px 1px 5px;
|
||||
border-top: 0;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
border-bottom: 1px solid #aaa;
|
||||
}
|
||||
|
||||
table.footnote td, table.footnote th {
|
||||
border: 0 !important;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: left;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
table.citation {
|
||||
border-left: solid 1px gray;
|
||||
margin-left: 1px;
|
||||
}
|
||||
|
||||
table.citation td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* -- figures --------------------------------------------------------------- */
|
||||
|
||||
div.figure {
|
||||
margin: 0.5em;
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
div.figure p.caption {
|
||||
padding: 0.3em;
|
||||
}
|
||||
|
||||
div.figure p.caption span.caption-number {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
div.figure p.caption span.caption-text {
|
||||
}
|
||||
|
||||
/* -- field list styles ----------------------------------------------------- */
|
||||
|
||||
table.field-list td, table.field-list th {
|
||||
border: 0 !important;
|
||||
}
|
||||
|
||||
.field-list ul {
|
||||
margin: 0;
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
.field-list p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.field-name {
|
||||
-moz-hyphens: manual;
|
||||
-ms-hyphens: manual;
|
||||
-webkit-hyphens: manual;
|
||||
hyphens: manual;
|
||||
}
|
||||
|
||||
/* -- hlist styles ---------------------------------------------------------- */
|
||||
|
||||
table.hlist td {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
|
||||
/* -- other body styles ----------------------------------------------------- */
|
||||
|
||||
ol.arabic {
|
||||
list-style: decimal;
|
||||
}
|
||||
|
||||
ol.loweralpha {
|
||||
list-style: lower-alpha;
|
||||
}
|
||||
|
||||
ol.upperalpha {
|
||||
list-style: upper-alpha;
|
||||
}
|
||||
|
||||
ol.lowerroman {
|
||||
list-style: lower-roman;
|
||||
}
|
||||
|
||||
ol.upperroman {
|
||||
list-style: upper-roman;
|
||||
}
|
||||
|
||||
dl {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
dd p {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
dd ul, dd table {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin-top: 3px;
|
||||
margin-bottom: 10px;
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
dt:target, span.highlighted {
|
||||
background-color: #fbe54e;
|
||||
}
|
||||
|
||||
rect.highlighted {
|
||||
fill: #fbe54e;
|
||||
}
|
||||
|
||||
dl.glossary dt {
|
||||
font-weight: bold;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.optional {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
.sig-paren {
|
||||
font-size: larger;
|
||||
}
|
||||
|
||||
.versionmodified {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.system-message {
|
||||
background-color: #fda;
|
||||
padding: 5px;
|
||||
border: 3px solid red;
|
||||
}
|
||||
|
||||
.footnote:target {
|
||||
background-color: #ffa;
|
||||
}
|
||||
|
||||
.line-block {
|
||||
display: block;
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.line-block .line-block {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
margin-left: 1.5em;
|
||||
}
|
||||
|
||||
.guilabel, .menuselection {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
.accelerator {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.classifier {
|
||||
font-style: oblique;
|
||||
}
|
||||
|
||||
abbr, acronym {
|
||||
border-bottom: dotted 1px;
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
/* -- code displays --------------------------------------------------------- */
|
||||
|
||||
pre {
|
||||
overflow: auto;
|
||||
overflow-y: hidden; /* fixes display issues on Chrome browsers */
|
||||
}
|
||||
|
||||
span.pre {
|
||||
-moz-hyphens: none;
|
||||
-ms-hyphens: none;
|
||||
-webkit-hyphens: none;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
td.linenos pre {
|
||||
padding: 5px 0px;
|
||||
border: 0;
|
||||
background-color: transparent;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
table.highlighttable {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
table.highlighttable td {
|
||||
padding: 0 0.5em 0 0.5em;
|
||||
}
|
||||
|
||||
div.code-block-caption {
|
||||
padding: 2px 5px;
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
div.code-block-caption code {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
div.code-block-caption + div > div.highlight > pre {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
div.code-block-caption span.caption-number {
|
||||
padding: 0.1em 0.3em;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
div.code-block-caption span.caption-text {
|
||||
}
|
||||
|
||||
div.literal-block-wrapper {
|
||||
padding: 1em 1em 0;
|
||||
}
|
||||
|
||||
div.literal-block-wrapper div.highlight {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
code.descname {
|
||||
background-color: transparent;
|
||||
font-weight: bold;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
code.descclassname {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
code.xref, a code {
|
||||
background-color: transparent;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
h1 code, h2 code, h3 code, h4 code, h5 code, h6 code {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.viewcode-link {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.viewcode-back {
|
||||
float: right;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
div.viewcode-block:target {
|
||||
margin: -1px -10px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
/* -- math display ---------------------------------------------------------- */
|
||||
|
||||
img.math {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
div.body div.math p {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
span.eqno {
|
||||
float: right;
|
||||
}
|
||||
|
||||
span.eqno a.headerlink {
|
||||
position: relative;
|
||||
left: 0px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
div.math:hover a.headerlink {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
/* -- printout stylesheet --------------------------------------------------- */
|
||||
|
||||
@media print {
|
||||
div.document,
|
||||
div.documentwrapper,
|
||||
div.bodywrapper {
|
||||
margin: 0 !important;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div.sphinxsidebar,
|
||||
div.related,
|
||||
div.footer,
|
||||
#top-link {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 756 B |
|
After Width: | Height: | Size: 829 B |
|
After Width: | Height: | Size: 641 B |
@@ -0,0 +1,315 @@
|
||||
/*
|
||||
* doctools.js
|
||||
* ~~~~~~~~~~~
|
||||
*
|
||||
* Sphinx JavaScript utilities for all documentation.
|
||||
*
|
||||
* :copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS.
|
||||
* :license: BSD, see LICENSE for details.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* select a different prefix for underscore
|
||||
*/
|
||||
$u = _.noConflict();
|
||||
|
||||
/**
|
||||
* make the code below compatible with browsers without
|
||||
* an installed firebug like debugger
|
||||
if (!window.console || !console.firebug) {
|
||||
var names = ["log", "debug", "info", "warn", "error", "assert", "dir",
|
||||
"dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace",
|
||||
"profile", "profileEnd"];
|
||||
window.console = {};
|
||||
for (var i = 0; i < names.length; ++i)
|
||||
window.console[names[i]] = function() {};
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* small helper function to urldecode strings
|
||||
*/
|
||||
jQuery.urldecode = function(x) {
|
||||
return decodeURIComponent(x).replace(/\+/g, ' ');
|
||||
};
|
||||
|
||||
/**
|
||||
* small helper function to urlencode strings
|
||||
*/
|
||||
jQuery.urlencode = encodeURIComponent;
|
||||
|
||||
/**
|
||||
* This function returns the parsed url parameters of the
|
||||
* current request. Multiple values per key are supported,
|
||||
* it will always return arrays of strings for the value parts.
|
||||
*/
|
||||
jQuery.getQueryParameters = function(s) {
|
||||
if (typeof s === 'undefined')
|
||||
s = document.location.search;
|
||||
var parts = s.substr(s.indexOf('?') + 1).split('&');
|
||||
var result = {};
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
var tmp = parts[i].split('=', 2);
|
||||
var key = jQuery.urldecode(tmp[0]);
|
||||
var value = jQuery.urldecode(tmp[1]);
|
||||
if (key in result)
|
||||
result[key].push(value);
|
||||
else
|
||||
result[key] = [value];
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* highlight a given string on a jquery object by wrapping it in
|
||||
* span elements with the given class name.
|
||||
*/
|
||||
jQuery.fn.highlightText = function(text, className) {
|
||||
function highlight(node, addItems) {
|
||||
if (node.nodeType === 3) {
|
||||
var val = node.nodeValue;
|
||||
var pos = val.toLowerCase().indexOf(text);
|
||||
if (pos >= 0 &&
|
||||
!jQuery(node.parentNode).hasClass(className) &&
|
||||
!jQuery(node.parentNode).hasClass("nohighlight")) {
|
||||
var span;
|
||||
var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg");
|
||||
if (isInSVG) {
|
||||
span = document.createElementNS("http://www.w3.org/2000/svg", "tspan");
|
||||
} else {
|
||||
span = document.createElement("span");
|
||||
span.className = className;
|
||||
}
|
||||
span.appendChild(document.createTextNode(val.substr(pos, text.length)));
|
||||
node.parentNode.insertBefore(span, node.parentNode.insertBefore(
|
||||
document.createTextNode(val.substr(pos + text.length)),
|
||||
node.nextSibling));
|
||||
node.nodeValue = val.substr(0, pos);
|
||||
if (isInSVG) {
|
||||
var bbox = span.getBBox();
|
||||
var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
|
||||
rect.x.baseVal.value = bbox.x;
|
||||
rect.y.baseVal.value = bbox.y;
|
||||
rect.width.baseVal.value = bbox.width;
|
||||
rect.height.baseVal.value = bbox.height;
|
||||
rect.setAttribute('class', className);
|
||||
var parentOfText = node.parentNode.parentNode;
|
||||
addItems.push({
|
||||
"parent": node.parentNode,
|
||||
"target": rect});
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!jQuery(node).is("button, select, textarea")) {
|
||||
jQuery.each(node.childNodes, function() {
|
||||
highlight(this, addItems);
|
||||
});
|
||||
}
|
||||
}
|
||||
var addItems = [];
|
||||
var result = this.each(function() {
|
||||
highlight(this, addItems);
|
||||
});
|
||||
for (var i = 0; i < addItems.length; ++i) {
|
||||
jQuery(addItems[i].parent).before(addItems[i].target);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/*
|
||||
* backward compatibility for jQuery.browser
|
||||
* This will be supported until firefox bug is fixed.
|
||||
*/
|
||||
if (!jQuery.browser) {
|
||||
jQuery.uaMatch = function(ua) {
|
||||
ua = ua.toLowerCase();
|
||||
|
||||
var match = /(chrome)[ \/]([\w.]+)/.exec(ua) ||
|
||||
/(webkit)[ \/]([\w.]+)/.exec(ua) ||
|
||||
/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) ||
|
||||
/(msie) ([\w.]+)/.exec(ua) ||
|
||||
ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) ||
|
||||
[];
|
||||
|
||||
return {
|
||||
browser: match[ 1 ] || "",
|
||||
version: match[ 2 ] || "0"
|
||||
};
|
||||
};
|
||||
jQuery.browser = {};
|
||||
jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Small JavaScript module for the documentation.
|
||||
*/
|
||||
var Documentation = {
|
||||
|
||||
init : function() {
|
||||
this.fixFirefoxAnchorBug();
|
||||
this.highlightSearchWords();
|
||||
this.initIndexTable();
|
||||
if (DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) {
|
||||
this.initOnKeyListeners();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* i18n support
|
||||
*/
|
||||
TRANSLATIONS : {},
|
||||
PLURAL_EXPR : function(n) { return n === 1 ? 0 : 1; },
|
||||
LOCALE : 'unknown',
|
||||
|
||||
// gettext and ngettext don't access this so that the functions
|
||||
// can safely bound to a different name (_ = Documentation.gettext)
|
||||
gettext : function(string) {
|
||||
var translated = Documentation.TRANSLATIONS[string];
|
||||
if (typeof translated === 'undefined')
|
||||
return string;
|
||||
return (typeof translated === 'string') ? translated : translated[0];
|
||||
},
|
||||
|
||||
ngettext : function(singular, plural, n) {
|
||||
var translated = Documentation.TRANSLATIONS[singular];
|
||||
if (typeof translated === 'undefined')
|
||||
return (n == 1) ? singular : plural;
|
||||
return translated[Documentation.PLURALEXPR(n)];
|
||||
},
|
||||
|
||||
addTranslations : function(catalog) {
|
||||
for (var key in catalog.messages)
|
||||
this.TRANSLATIONS[key] = catalog.messages[key];
|
||||
this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')');
|
||||
this.LOCALE = catalog.locale;
|
||||
},
|
||||
|
||||
/**
|
||||
* add context elements like header anchor links
|
||||
*/
|
||||
addContextElements : function() {
|
||||
$('div[id] > :header:first').each(function() {
|
||||
$('<a class="headerlink">\u00B6</a>').
|
||||
attr('href', '#' + this.id).
|
||||
attr('title', _('Permalink to this headline')).
|
||||
appendTo(this);
|
||||
});
|
||||
$('dt[id]').each(function() {
|
||||
$('<a class="headerlink">\u00B6</a>').
|
||||
attr('href', '#' + this.id).
|
||||
attr('title', _('Permalink to this definition')).
|
||||
appendTo(this);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* workaround a firefox stupidity
|
||||
* see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075
|
||||
*/
|
||||
fixFirefoxAnchorBug : function() {
|
||||
if (document.location.hash && $.browser.mozilla)
|
||||
window.setTimeout(function() {
|
||||
document.location.href += '';
|
||||
}, 10);
|
||||
},
|
||||
|
||||
/**
|
||||
* highlight the search words provided in the url in the text
|
||||
*/
|
||||
highlightSearchWords : function() {
|
||||
var params = $.getQueryParameters();
|
||||
var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : [];
|
||||
if (terms.length) {
|
||||
var body = $('div.body');
|
||||
if (!body.length) {
|
||||
body = $('body');
|
||||
}
|
||||
window.setTimeout(function() {
|
||||
$.each(terms, function() {
|
||||
body.highlightText(this.toLowerCase(), 'highlighted');
|
||||
});
|
||||
}, 10);
|
||||
$('<p class="highlight-link"><a href="javascript:Documentation.' +
|
||||
'hideSearchWords()">' + _('Hide Search Matches') + '</a></p>')
|
||||
.appendTo($('#searchbox'));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* init the domain index toggle buttons
|
||||
*/
|
||||
initIndexTable : function() {
|
||||
var togglers = $('img.toggler').click(function() {
|
||||
var src = $(this).attr('src');
|
||||
var idnum = $(this).attr('id').substr(7);
|
||||
$('tr.cg-' + idnum).toggle();
|
||||
if (src.substr(-9) === 'minus.png')
|
||||
$(this).attr('src', src.substr(0, src.length-9) + 'plus.png');
|
||||
else
|
||||
$(this).attr('src', src.substr(0, src.length-8) + 'minus.png');
|
||||
}).css('display', '');
|
||||
if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) {
|
||||
togglers.click();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* helper function to hide the search marks again
|
||||
*/
|
||||
hideSearchWords : function() {
|
||||
$('#searchbox .highlight-link').fadeOut(300);
|
||||
$('span.highlighted').removeClass('highlighted');
|
||||
},
|
||||
|
||||
/**
|
||||
* make the url absolute
|
||||
*/
|
||||
makeURL : function(relativeURL) {
|
||||
return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL;
|
||||
},
|
||||
|
||||
/**
|
||||
* get the current relative url
|
||||
*/
|
||||
getCurrentURL : function() {
|
||||
var path = document.location.pathname;
|
||||
var parts = path.split(/\//);
|
||||
$.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() {
|
||||
if (this === '..')
|
||||
parts.pop();
|
||||
});
|
||||
var url = parts.join('/');
|
||||
return path.substring(url.lastIndexOf('/') + 1, path.length - 1);
|
||||
},
|
||||
|
||||
initOnKeyListeners: function() {
|
||||
$(document).keyup(function(event) {
|
||||
var activeElementType = document.activeElement.tagName;
|
||||
// don't navigate when in search box or textarea
|
||||
if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT') {
|
||||
switch (event.keyCode) {
|
||||
case 37: // left
|
||||
var prevHref = $('link[rel="prev"]').prop('href');
|
||||
if (prevHref) {
|
||||
window.location.href = prevHref;
|
||||
return false;
|
||||
}
|
||||
case 39: // right
|
||||
var nextHref = $('link[rel="next"]').prop('href');
|
||||
if (nextHref) {
|
||||
window.location.href = nextHref;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// quick alias for translations
|
||||
_ = Documentation.gettext;
|
||||
|
||||
$(document).ready(function() {
|
||||
Documentation.init();
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
var DOCUMENTATION_OPTIONS = {
|
||||
URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
|
||||
VERSION: '189',
|
||||
LANGUAGE: 'None',
|
||||
COLLAPSE_INDEX: false,
|
||||
FILE_SUFFIX: '.html',
|
||||
HAS_SOURCE: true,
|
||||
SOURCELINK_SUFFIX: '.txt',
|
||||
NAVIGATION_WITH_KEYS: false,
|
||||
};
|
||||
|
After Width: | Height: | Size: 222 B |
|
After Width: | Height: | Size: 202 B |
|
After Width: | Height: | Size: 286 B |
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* language_data.js
|
||||
* ~~~~~~~~~~~~~~~~
|
||||
*
|
||||
* This script contains the language-specific data used by searchtools.js,
|
||||
* namely the list of stopwords, stemmer, scorer and splitter.
|
||||
*
|
||||
* :copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS.
|
||||
* :license: BSD, see LICENSE for details.
|
||||
*
|
||||
*/
|
||||
|
||||
var stopwords = ;
|
||||
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 90 B |
@@ -0,0 +1,580 @@
|
||||
/*
|
||||
* nonav.css
|
||||
* ~~~~~~~~~~
|
||||
*
|
||||
* Sphinx stylesheet -- nonav theme.
|
||||
*
|
||||
* :copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS.
|
||||
* :license: BSD, see LICENSE for details.
|
||||
*
|
||||
*/
|
||||
|
||||
/* -- main layout ----------------------------------------------------------- */
|
||||
|
||||
div.clearer {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
a:link, a:visited {
|
||||
color: #3333ff;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
img {
|
||||
border: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* -- relbar ---------------------------------------------------------------- */
|
||||
|
||||
div.related {
|
||||
width: 100%;
|
||||
font-family: sans-serif;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
div.related h3 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.related ul {
|
||||
margin: 0;
|
||||
padding: 0 0 0 10px;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
div.related li {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
div.related li.right {
|
||||
float: right;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
/* -- sidebar --------------------------------------------------------------- */
|
||||
|
||||
div.sphinxsidebarwrapper {
|
||||
padding: 10px 5px 0 10px;
|
||||
}
|
||||
|
||||
div.sphinxsidebar {
|
||||
float: left;
|
||||
width: 230px;
|
||||
margin-left: -100%;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
div.sphinxsidebar ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
div.sphinxsidebar ul ul,
|
||||
div.sphinxsidebar ul.want-points {
|
||||
margin-left: 20px;
|
||||
list-style: square;
|
||||
}
|
||||
|
||||
div.sphinxsidebar ul ul {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
div.sphinxsidebar form {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
div.sphinxsidebar input {
|
||||
border: 1px solid #98dbcc;
|
||||
font-family: sans-serif;
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
img {
|
||||
border: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* -- search page ----------------------------------------------------------- */
|
||||
|
||||
ul.search {
|
||||
margin: 10px 0 0 20px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul.search li {
|
||||
padding: 5px 0 5px 20px;
|
||||
background-image: url(file.png);
|
||||
background-repeat: no-repeat;
|
||||
background-position: 0 7px;
|
||||
}
|
||||
|
||||
ul.search li a {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
ul.search li div.context {
|
||||
color: #888;
|
||||
margin: 2px 0 0 30px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
ul.keywordmatches li.goodmatch a {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* -- index page ------------------------------------------------------------ */
|
||||
|
||||
table.contentstable {
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
table.contentstable p.biglink {
|
||||
line-height: 150%;
|
||||
}
|
||||
|
||||
a.biglink {
|
||||
font-size: 130%;
|
||||
}
|
||||
|
||||
span.linkdescr {
|
||||
font-style: italic;
|
||||
padding-top: 5px;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
/* -- general index --------------------------------------------------------- */
|
||||
|
||||
table.indextable td {
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
table.indextable ul {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
table.indextable > tbody > tr > td > ul {
|
||||
padding-left: 0em;
|
||||
}
|
||||
|
||||
table.indextable tr.pcap {
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
table.indextable tr.cap {
|
||||
margin-top: 10px;
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
img.toggler {
|
||||
margin-right: 3px;
|
||||
margin-top: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* -- domain module index --------------------------------------------------- */
|
||||
|
||||
table.modindextable td {
|
||||
padding: 2px;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
/* -- general body styles --------------------------------------------------- */
|
||||
|
||||
a.headerlink {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
div.body p.caption {
|
||||
text-align: inherit;
|
||||
}
|
||||
|
||||
div.body td {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.first {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
p.rubric {
|
||||
margin-top: 30px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.align-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.align-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.align-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* -- sidebars -------------------------------------------------------------- */
|
||||
|
||||
div.sidebar {
|
||||
margin: 0 0 0.5em 1em;
|
||||
border: 1px solid #ddb;
|
||||
padding: 7px 7px 0 7px;
|
||||
background-color: #ffe;
|
||||
width: 40%;
|
||||
float: right;
|
||||
}
|
||||
|
||||
p.sidebar-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* -- topics ---------------------------------------------------------------- */
|
||||
|
||||
div.topic {
|
||||
border: 1px solid #ccc;
|
||||
padding: 7px 7px 0 7px;
|
||||
margin: 10px 0 10px 0;
|
||||
}
|
||||
|
||||
p.topic-title {
|
||||
font-size: 110%;
|
||||
font-weight: bold;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
/* -- admonitions ----------------------------------------------------------- */
|
||||
|
||||
div.admonition {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
padding: 7px;
|
||||
}
|
||||
|
||||
div.admonition dt {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
div.admonition dl {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
p.admonition-title {
|
||||
margin: 0px 10px 5px 0px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
div.body p.centered {
|
||||
text-align: center;
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
/* -- tables ---------------------------------------------------------------- */
|
||||
|
||||
table.docutils {
|
||||
border: 0;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
table caption span.caption-number {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
table caption span.caption-text {
|
||||
}
|
||||
|
||||
table.docutils td, table.docutils th {
|
||||
padding: 1px 8px 1px 5px;
|
||||
border-top: 0;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
border-bottom: 1px solid #aaa;
|
||||
}
|
||||
|
||||
table.footnote td, table.footnote th {
|
||||
border: 0 !important;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: left;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
table.citation {
|
||||
border-left: solid 1px gray;
|
||||
margin-left: 1px;
|
||||
}
|
||||
|
||||
table.citation td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* -- figures --------------------------------------------------------------- */
|
||||
|
||||
div.figure p.caption span.caption-number {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
div.figure p.caption span.caption-text {
|
||||
}
|
||||
|
||||
/* -- field list styles ----------------------------------------------------- */
|
||||
|
||||
table.field-list td, table.field-list th {
|
||||
border: 0 !important;
|
||||
}
|
||||
|
||||
.field-list ul {
|
||||
margin: 0;
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
.field-list p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* -- other body styles ----------------------------------------------------- */
|
||||
|
||||
ol.arabic {
|
||||
list-style: decimal;
|
||||
}
|
||||
|
||||
ol.loweralpha {
|
||||
list-style: lower-alpha;
|
||||
}
|
||||
|
||||
ol.upperalpha {
|
||||
list-style: upper-alpha;
|
||||
}
|
||||
|
||||
ol.lowerroman {
|
||||
list-style: lower-roman;
|
||||
}
|
||||
|
||||
ol.upperroman {
|
||||
list-style: upper-roman;
|
||||
}
|
||||
|
||||
dl {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
dd p {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
dd ul, dd table {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin-top: 3px;
|
||||
margin-bottom: 10px;
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
dt:target, .highlighted {
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
dl.glossary dt {
|
||||
font-weight: bold;
|
||||
font-size: 110%;
|
||||
}
|
||||
|
||||
.optional {
|
||||
font-size: 130%;
|
||||
}
|
||||
|
||||
.sig-paren {
|
||||
font-size: larger;
|
||||
}
|
||||
|
||||
.versionmodified {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.system-message {
|
||||
background-color: #fda;
|
||||
padding: 5px;
|
||||
border: 3px solid red;
|
||||
}
|
||||
|
||||
.footnote:target {
|
||||
background-color: #dddddd;
|
||||
}
|
||||
|
||||
.line-block {
|
||||
display: block;
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.line-block .line-block {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
margin-left: 1.5em;
|
||||
}
|
||||
|
||||
.guilabel, .menuselection {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.accelerator {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.classifier {
|
||||
font-style: oblique;
|
||||
}
|
||||
|
||||
abbr, acronym {
|
||||
border-bottom: dotted 1px;
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
/* -- code displays --------------------------------------------------------- */
|
||||
|
||||
pre {
|
||||
font-family: monospace;
|
||||
overflow: auto;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
td.linenos pre {
|
||||
padding: 5px 0px;
|
||||
border: 0;
|
||||
background-color: transparent;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
table.highlighttable {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
table.highlighttable td {
|
||||
padding: 0 0.5em 0 0.5em;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
div.code-block-caption span.caption-number {
|
||||
padding: 0.1em 0.3em;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
div.code-block-caption span.caption-text {
|
||||
}
|
||||
|
||||
div.literal-block-wrapper {
|
||||
padding: 1em 1em 0;
|
||||
}
|
||||
|
||||
div.literal-block-wrapper div.highlight {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
code.descname {
|
||||
background-color: transparent;
|
||||
font-weight: bold;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
code.descclassname {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
code.xref, a code {
|
||||
background-color: transparent;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
h1 code, h2 code, h3 code, h4 code, h5 code, h6 code {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/* -- math display ---------------------------------------------------------- */
|
||||
|
||||
img.math {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
div.body div.math p {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
span.eqno {
|
||||
float: right;
|
||||
}
|
||||
|
||||
/* -- special divs --------------------------------------------------------- */
|
||||
|
||||
div.quotebar {
|
||||
background-color: #e3eff1;
|
||||
max-width: 250px;
|
||||
float: right;
|
||||
font-family: sans-serif;
|
||||
padding: 7px 7px;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
div.footer {
|
||||
background-color: #e3eff1;
|
||||
padding: 3px 8px 3px 0;
|
||||
clear: both;
|
||||
font-family: sans-serif;
|
||||
font-size: 80%;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
div.footer a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* -- link-target ----------------------------------------------------------- */
|
||||
|
||||
.link-target {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
table .link-target {
|
||||
/* Do not show links in tables, there is not enough space */
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* -- font-face ------------------------------------------------------------- */
|
||||
|
||||
/*
|
||||
@font-face {
|
||||
font-family: "LiberationNarrow";
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
src: url("res:///Data/fonts/LiberationNarrow-Regular.otf")
|
||||
format("opentype");
|
||||
}
|
||||
@font-face {
|
||||
font-family: "LiberationNarrow";
|
||||
font-style: oblique, italic;
|
||||
font-weight: normal;
|
||||
src: url("res:///Data/fonts/LiberationNarrow-Italic.otf")
|
||||
format("opentype");
|
||||
}
|
||||
@font-face {
|
||||
font-family: "LiberationNarrow";
|
||||
font-style: normal;
|
||||
font-weight: bold;
|
||||
src: url("res:///Data/fonts/LiberationNarrow-Bold.otf")
|
||||
format("opentype");
|
||||
}
|
||||
@font-face {
|
||||
font-family: "LiberationNarrow";
|
||||
font-style: oblique, italic;
|
||||
font-weight: bold;
|
||||
src: url("res:///Data/fonts/LiberationNarrow-BoldItalic.otf")
|
||||
format("opentype");
|
||||
}
|
||||
*/
|
||||
|
After Width: | Height: | Size: 90 B |
@@ -0,0 +1,69 @@
|
||||
.highlight .hll { background-color: #ffffcc }
|
||||
.highlight { background: #eeffcc; }
|
||||
.highlight .c { color: #408090; font-style: italic } /* Comment */
|
||||
.highlight .err { border: 1px solid #FF0000 } /* Error */
|
||||
.highlight .k { color: #007020; font-weight: bold } /* Keyword */
|
||||
.highlight .o { color: #666666 } /* Operator */
|
||||
.highlight .ch { color: #408090; font-style: italic } /* Comment.Hashbang */
|
||||
.highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */
|
||||
.highlight .cp { color: #007020 } /* Comment.Preproc */
|
||||
.highlight .cpf { color: #408090; font-style: italic } /* Comment.PreprocFile */
|
||||
.highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */
|
||||
.highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */
|
||||
.highlight .gd { color: #A00000 } /* Generic.Deleted */
|
||||
.highlight .ge { font-style: italic } /* Generic.Emph */
|
||||
.highlight .gr { color: #FF0000 } /* Generic.Error */
|
||||
.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */
|
||||
.highlight .gi { color: #00A000 } /* Generic.Inserted */
|
||||
.highlight .go { color: #333333 } /* Generic.Output */
|
||||
.highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */
|
||||
.highlight .gs { font-weight: bold } /* Generic.Strong */
|
||||
.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
|
||||
.highlight .gt { color: #0044DD } /* Generic.Traceback */
|
||||
.highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */
|
||||
.highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */
|
||||
.highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */
|
||||
.highlight .kp { color: #007020 } /* Keyword.Pseudo */
|
||||
.highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */
|
||||
.highlight .kt { color: #902000 } /* Keyword.Type */
|
||||
.highlight .m { color: #208050 } /* Literal.Number */
|
||||
.highlight .s { color: #4070a0 } /* Literal.String */
|
||||
.highlight .na { color: #4070a0 } /* Name.Attribute */
|
||||
.highlight .nb { color: #007020 } /* Name.Builtin */
|
||||
.highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */
|
||||
.highlight .no { color: #60add5 } /* Name.Constant */
|
||||
.highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */
|
||||
.highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */
|
||||
.highlight .ne { color: #007020 } /* Name.Exception */
|
||||
.highlight .nf { color: #06287e } /* Name.Function */
|
||||
.highlight .nl { color: #002070; font-weight: bold } /* Name.Label */
|
||||
.highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */
|
||||
.highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */
|
||||
.highlight .nv { color: #bb60d5 } /* Name.Variable */
|
||||
.highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */
|
||||
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
|
||||
.highlight .mb { color: #208050 } /* Literal.Number.Bin */
|
||||
.highlight .mf { color: #208050 } /* Literal.Number.Float */
|
||||
.highlight .mh { color: #208050 } /* Literal.Number.Hex */
|
||||
.highlight .mi { color: #208050 } /* Literal.Number.Integer */
|
||||
.highlight .mo { color: #208050 } /* Literal.Number.Oct */
|
||||
.highlight .sa { color: #4070a0 } /* Literal.String.Affix */
|
||||
.highlight .sb { color: #4070a0 } /* Literal.String.Backtick */
|
||||
.highlight .sc { color: #4070a0 } /* Literal.String.Char */
|
||||
.highlight .dl { color: #4070a0 } /* Literal.String.Delimiter */
|
||||
.highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */
|
||||
.highlight .s2 { color: #4070a0 } /* Literal.String.Double */
|
||||
.highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */
|
||||
.highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */
|
||||
.highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */
|
||||
.highlight .sx { color: #c65d09 } /* Literal.String.Other */
|
||||
.highlight .sr { color: #235388 } /* Literal.String.Regex */
|
||||
.highlight .s1 { color: #4070a0 } /* Literal.String.Single */
|
||||
.highlight .ss { color: #517918 } /* Literal.String.Symbol */
|
||||
.highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */
|
||||
.highlight .fm { color: #06287e } /* Name.Function.Magic */
|
||||
.highlight .vc { color: #bb60d5 } /* Name.Variable.Class */
|
||||
.highlight .vg { color: #bb60d5 } /* Name.Variable.Global */
|
||||
.highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */
|
||||
.highlight .vm { color: #bb60d5 } /* Name.Variable.Magic */
|
||||
.highlight .il { color: #208050 } /* Literal.Number.Integer.Long */
|
||||
@@ -0,0 +1,481 @@
|
||||
/*
|
||||
* searchtools.js
|
||||
* ~~~~~~~~~~~~~~~~
|
||||
*
|
||||
* Sphinx JavaScript utilities for the full-text search.
|
||||
*
|
||||
* :copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS.
|
||||
* :license: BSD, see LICENSE for details.
|
||||
*
|
||||
*/
|
||||
|
||||
if (!Scorer) {
|
||||
/**
|
||||
* Simple result scoring code.
|
||||
*/
|
||||
var Scorer = {
|
||||
// Implement the following function to further tweak the score for each result
|
||||
// The function takes a result array [filename, title, anchor, descr, score]
|
||||
// and returns the new score.
|
||||
/*
|
||||
score: function(result) {
|
||||
return result[4];
|
||||
},
|
||||
*/
|
||||
|
||||
// query matches the full name of an object
|
||||
objNameMatch: 11,
|
||||
// or matches in the last dotted part of the object name
|
||||
objPartialMatch: 6,
|
||||
// Additive scores depending on the priority of the object
|
||||
objPrio: {0: 15, // used to be importantResults
|
||||
1: 5, // used to be objectResults
|
||||
2: -5}, // used to be unimportantResults
|
||||
// Used when the priority is not in the mapping.
|
||||
objPrioDefault: 0,
|
||||
|
||||
// query found in title
|
||||
title: 15,
|
||||
// query found in terms
|
||||
term: 5
|
||||
};
|
||||
}
|
||||
|
||||
if (!splitQuery) {
|
||||
function splitQuery(query) {
|
||||
return query.split(/\s+/);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search Module
|
||||
*/
|
||||
var Search = {
|
||||
|
||||
_index : null,
|
||||
_queued_query : null,
|
||||
_pulse_status : -1,
|
||||
|
||||
init : function() {
|
||||
var params = $.getQueryParameters();
|
||||
if (params.q) {
|
||||
var query = params.q[0];
|
||||
$('input[name="q"]')[0].value = query;
|
||||
this.performSearch(query);
|
||||
}
|
||||
},
|
||||
|
||||
loadIndex : function(url) {
|
||||
$.ajax({type: "GET", url: url, data: null,
|
||||
dataType: "script", cache: true,
|
||||
complete: function(jqxhr, textstatus) {
|
||||
if (textstatus != "success") {
|
||||
document.getElementById("searchindexloader").src = url;
|
||||
}
|
||||
}});
|
||||
},
|
||||
|
||||
setIndex : function(index) {
|
||||
var q;
|
||||
this._index = index;
|
||||
if ((q = this._queued_query) !== null) {
|
||||
this._queued_query = null;
|
||||
Search.query(q);
|
||||
}
|
||||
},
|
||||
|
||||
hasIndex : function() {
|
||||
return this._index !== null;
|
||||
},
|
||||
|
||||
deferQuery : function(query) {
|
||||
this._queued_query = query;
|
||||
},
|
||||
|
||||
stopPulse : function() {
|
||||
this._pulse_status = 0;
|
||||
},
|
||||
|
||||
startPulse : function() {
|
||||
if (this._pulse_status >= 0)
|
||||
return;
|
||||
function pulse() {
|
||||
var i;
|
||||
Search._pulse_status = (Search._pulse_status + 1) % 4;
|
||||
var dotString = '';
|
||||
for (i = 0; i < Search._pulse_status; i++)
|
||||
dotString += '.';
|
||||
Search.dots.text(dotString);
|
||||
if (Search._pulse_status > -1)
|
||||
window.setTimeout(pulse, 500);
|
||||
}
|
||||
pulse();
|
||||
},
|
||||
|
||||
/**
|
||||
* perform a search for something (or wait until index is loaded)
|
||||
*/
|
||||
performSearch : function(query) {
|
||||
// create the required interface elements
|
||||
this.out = $('#search-results');
|
||||
this.title = $('<h2>' + _('Searching') + '</h2>').appendTo(this.out);
|
||||
this.dots = $('<span></span>').appendTo(this.title);
|
||||
this.status = $('<p style="display: none"></p>').appendTo(this.out);
|
||||
this.output = $('<ul class="search"/>').appendTo(this.out);
|
||||
|
||||
$('#search-progress').text(_('Preparing search...'));
|
||||
this.startPulse();
|
||||
|
||||
// index already loaded, the browser was quick!
|
||||
if (this.hasIndex())
|
||||
this.query(query);
|
||||
else
|
||||
this.deferQuery(query);
|
||||
},
|
||||
|
||||
/**
|
||||
* execute search (requires search index to be loaded)
|
||||
*/
|
||||
query : function(query) {
|
||||
var i;
|
||||
|
||||
// stem the searchterms and add them to the correct list
|
||||
var stemmer = new Stemmer();
|
||||
var searchterms = [];
|
||||
var excluded = [];
|
||||
var hlterms = [];
|
||||
var tmp = splitQuery(query);
|
||||
var objectterms = [];
|
||||
for (i = 0; i < tmp.length; i++) {
|
||||
if (tmp[i] !== "") {
|
||||
objectterms.push(tmp[i].toLowerCase());
|
||||
}
|
||||
|
||||
if ($u.indexOf(stopwords, tmp[i].toLowerCase()) != -1 || tmp[i].match(/^\d+$/) ||
|
||||
tmp[i] === "") {
|
||||
// skip this "word"
|
||||
continue;
|
||||
}
|
||||
// stem the word
|
||||
var word = stemmer.stemWord(tmp[i].toLowerCase());
|
||||
// prevent stemmer from cutting word smaller than two chars
|
||||
if(word.length < 3 && tmp[i].length >= 3) {
|
||||
word = tmp[i];
|
||||
}
|
||||
var toAppend;
|
||||
// select the correct list
|
||||
if (word[0] == '-') {
|
||||
toAppend = excluded;
|
||||
word = word.substr(1);
|
||||
}
|
||||
else {
|
||||
toAppend = searchterms;
|
||||
hlterms.push(tmp[i].toLowerCase());
|
||||
}
|
||||
// only add if not already in the list
|
||||
if (!$u.contains(toAppend, word))
|
||||
toAppend.push(word);
|
||||
}
|
||||
var highlightstring = '?highlight=' + $.urlencode(hlterms.join(" "));
|
||||
|
||||
// console.debug('SEARCH: searching for:');
|
||||
// console.info('required: ', searchterms);
|
||||
// console.info('excluded: ', excluded);
|
||||
|
||||
// prepare search
|
||||
var terms = this._index.terms;
|
||||
var titleterms = this._index.titleterms;
|
||||
|
||||
// array of [filename, title, anchor, descr, score]
|
||||
var results = [];
|
||||
$('#search-progress').empty();
|
||||
|
||||
// lookup as object
|
||||
for (i = 0; i < objectterms.length; i++) {
|
||||
var others = [].concat(objectterms.slice(0, i),
|
||||
objectterms.slice(i+1, objectterms.length));
|
||||
results = results.concat(this.performObjectSearch(objectterms[i], others));
|
||||
}
|
||||
|
||||
// lookup as search terms in fulltext
|
||||
results = results.concat(this.performTermsSearch(searchterms, excluded, terms, titleterms));
|
||||
|
||||
// let the scorer override scores with a custom scoring function
|
||||
if (Scorer.score) {
|
||||
for (i = 0; i < results.length; i++)
|
||||
results[i][4] = Scorer.score(results[i]);
|
||||
}
|
||||
|
||||
// now sort the results by score (in opposite order of appearance, since the
|
||||
// display function below uses pop() to retrieve items) and then
|
||||
// alphabetically
|
||||
results.sort(function(a, b) {
|
||||
var left = a[4];
|
||||
var right = b[4];
|
||||
if (left > right) {
|
||||
return 1;
|
||||
} else if (left < right) {
|
||||
return -1;
|
||||
} else {
|
||||
// same score: sort alphabetically
|
||||
left = a[1].toLowerCase();
|
||||
right = b[1].toLowerCase();
|
||||
return (left > right) ? -1 : ((left < right) ? 1 : 0);
|
||||
}
|
||||
});
|
||||
|
||||
// for debugging
|
||||
//Search.lastresults = results.slice(); // a copy
|
||||
//console.info('search results:', Search.lastresults);
|
||||
|
||||
// print the results
|
||||
var resultCount = results.length;
|
||||
function displayNextItem() {
|
||||
// results left, load the summary and display it
|
||||
if (results.length) {
|
||||
var item = results.pop();
|
||||
var listItem = $('<li style="display:none"></li>');
|
||||
if (DOCUMENTATION_OPTIONS.FILE_SUFFIX === '') {
|
||||
// dirhtml builder
|
||||
var dirname = item[0] + '/';
|
||||
if (dirname.match(/\/index\/$/)) {
|
||||
dirname = dirname.substring(0, dirname.length-6);
|
||||
} else if (dirname == 'index/') {
|
||||
dirname = '';
|
||||
}
|
||||
listItem.append($('<a/>').attr('href',
|
||||
DOCUMENTATION_OPTIONS.URL_ROOT + dirname +
|
||||
highlightstring + item[2]).html(item[1]));
|
||||
} else {
|
||||
// normal html builders
|
||||
listItem.append($('<a/>').attr('href',
|
||||
item[0] + DOCUMENTATION_OPTIONS.FILE_SUFFIX +
|
||||
highlightstring + item[2]).html(item[1]));
|
||||
}
|
||||
if (item[3]) {
|
||||
listItem.append($('<span> (' + item[3] + ')</span>'));
|
||||
Search.output.append(listItem);
|
||||
listItem.slideDown(5, function() {
|
||||
displayNextItem();
|
||||
});
|
||||
} else if (DOCUMENTATION_OPTIONS.HAS_SOURCE) {
|
||||
var suffix = DOCUMENTATION_OPTIONS.SOURCELINK_SUFFIX;
|
||||
if (suffix === undefined) {
|
||||
suffix = '.txt';
|
||||
}
|
||||
$.ajax({url: DOCUMENTATION_OPTIONS.URL_ROOT + '_sources/' + item[5] + (item[5].slice(-suffix.length) === suffix ? '' : suffix),
|
||||
dataType: "text",
|
||||
complete: function(jqxhr, textstatus) {
|
||||
var data = jqxhr.responseText;
|
||||
if (data !== '' && data !== undefined) {
|
||||
listItem.append(Search.makeSearchSummary(data, searchterms, hlterms));
|
||||
}
|
||||
Search.output.append(listItem);
|
||||
listItem.slideDown(5, function() {
|
||||
displayNextItem();
|
||||
});
|
||||
}});
|
||||
} else {
|
||||
// no source available, just display title
|
||||
Search.output.append(listItem);
|
||||
listItem.slideDown(5, function() {
|
||||
displayNextItem();
|
||||
});
|
||||
}
|
||||
}
|
||||
// search finished, update title and status message
|
||||
else {
|
||||
Search.stopPulse();
|
||||
Search.title.text(_('Search Results'));
|
||||
if (!resultCount)
|
||||
Search.status.text(_('Your search did not match any documents. Please make sure that all words are spelled correctly and that you\'ve selected enough categories.'));
|
||||
else
|
||||
Search.status.text(_('Search finished, found %s page(s) matching the search query.').replace('%s', resultCount));
|
||||
Search.status.fadeIn(500);
|
||||
}
|
||||
}
|
||||
displayNextItem();
|
||||
},
|
||||
|
||||
/**
|
||||
* search for object names
|
||||
*/
|
||||
performObjectSearch : function(object, otherterms) {
|
||||
var filenames = this._index.filenames;
|
||||
var docnames = this._index.docnames;
|
||||
var objects = this._index.objects;
|
||||
var objnames = this._index.objnames;
|
||||
var titles = this._index.titles;
|
||||
|
||||
var i;
|
||||
var results = [];
|
||||
|
||||
for (var prefix in objects) {
|
||||
for (var name in objects[prefix]) {
|
||||
var fullname = (prefix ? prefix + '.' : '') + name;
|
||||
if (fullname.toLowerCase().indexOf(object) > -1) {
|
||||
var score = 0;
|
||||
var parts = fullname.split('.');
|
||||
// check for different match types: exact matches of full name or
|
||||
// "last name" (i.e. last dotted part)
|
||||
if (fullname == object || parts[parts.length - 1] == object) {
|
||||
score += Scorer.objNameMatch;
|
||||
// matches in last name
|
||||
} else if (parts[parts.length - 1].indexOf(object) > -1) {
|
||||
score += Scorer.objPartialMatch;
|
||||
}
|
||||
var match = objects[prefix][name];
|
||||
var objname = objnames[match[1]][2];
|
||||
var title = titles[match[0]];
|
||||
// If more than one term searched for, we require other words to be
|
||||
// found in the name/title/description
|
||||
if (otherterms.length > 0) {
|
||||
var haystack = (prefix + ' ' + name + ' ' +
|
||||
objname + ' ' + title).toLowerCase();
|
||||
var allfound = true;
|
||||
for (i = 0; i < otherterms.length; i++) {
|
||||
if (haystack.indexOf(otherterms[i]) == -1) {
|
||||
allfound = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!allfound) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
var descr = objname + _(', in ') + title;
|
||||
|
||||
var anchor = match[3];
|
||||
if (anchor === '')
|
||||
anchor = fullname;
|
||||
else if (anchor == '-')
|
||||
anchor = objnames[match[1]][1] + '-' + fullname;
|
||||
// add custom score for some objects according to scorer
|
||||
if (Scorer.objPrio.hasOwnProperty(match[2])) {
|
||||
score += Scorer.objPrio[match[2]];
|
||||
} else {
|
||||
score += Scorer.objPrioDefault;
|
||||
}
|
||||
results.push([docnames[match[0]], fullname, '#'+anchor, descr, score, filenames[match[0]]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
},
|
||||
|
||||
/**
|
||||
* search for full-text terms in the index
|
||||
*/
|
||||
performTermsSearch : function(searchterms, excluded, terms, titleterms) {
|
||||
var docnames = this._index.docnames;
|
||||
var filenames = this._index.filenames;
|
||||
var titles = this._index.titles;
|
||||
|
||||
var i, j, file;
|
||||
var fileMap = {};
|
||||
var scoreMap = {};
|
||||
var results = [];
|
||||
|
||||
// perform the search on the required terms
|
||||
for (i = 0; i < searchterms.length; i++) {
|
||||
var word = searchterms[i];
|
||||
var files = [];
|
||||
var _o = [
|
||||
{files: terms[word], score: Scorer.term},
|
||||
{files: titleterms[word], score: Scorer.title}
|
||||
];
|
||||
|
||||
// no match but word was a required one
|
||||
if ($u.every(_o, function(o){return o.files === undefined;})) {
|
||||
break;
|
||||
}
|
||||
// found search word in contents
|
||||
$u.each(_o, function(o) {
|
||||
var _files = o.files;
|
||||
if (_files === undefined)
|
||||
return
|
||||
|
||||
if (_files.length === undefined)
|
||||
_files = [_files];
|
||||
files = files.concat(_files);
|
||||
|
||||
// set score for the word in each file to Scorer.term
|
||||
for (j = 0; j < _files.length; j++) {
|
||||
file = _files[j];
|
||||
if (!(file in scoreMap))
|
||||
scoreMap[file] = {}
|
||||
scoreMap[file][word] = o.score;
|
||||
}
|
||||
});
|
||||
|
||||
// create the mapping
|
||||
for (j = 0; j < files.length; j++) {
|
||||
file = files[j];
|
||||
if (file in fileMap)
|
||||
fileMap[file].push(word);
|
||||
else
|
||||
fileMap[file] = [word];
|
||||
}
|
||||
}
|
||||
|
||||
// now check if the files don't contain excluded terms
|
||||
for (file in fileMap) {
|
||||
var valid = true;
|
||||
|
||||
// check if all requirements are matched
|
||||
if (fileMap[file].length != searchterms.length)
|
||||
continue;
|
||||
|
||||
// ensure that none of the excluded terms is in the search result
|
||||
for (i = 0; i < excluded.length; i++) {
|
||||
if (terms[excluded[i]] == file ||
|
||||
titleterms[excluded[i]] == file ||
|
||||
$u.contains(terms[excluded[i]] || [], file) ||
|
||||
$u.contains(titleterms[excluded[i]] || [], file)) {
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// if we have still a valid result we can add it to the result list
|
||||
if (valid) {
|
||||
// select one (max) score for the file.
|
||||
// for better ranking, we should calculate ranking by using words statistics like basic tf-idf...
|
||||
var score = $u.max($u.map(fileMap[file], function(w){return scoreMap[file][w]}));
|
||||
results.push([docnames[file], titles[file], '', null, score, filenames[file]]);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
},
|
||||
|
||||
/**
|
||||
* helper function to return a node containing the
|
||||
* search summary for a given text. keywords is a list
|
||||
* of stemmed words, hlwords is the list of normal, unstemmed
|
||||
* words. the first one is used to find the occurrence, the
|
||||
* latter for highlighting it.
|
||||
*/
|
||||
makeSearchSummary : function(text, keywords, hlwords) {
|
||||
var textLower = text.toLowerCase();
|
||||
var start = 0;
|
||||
$.each(keywords, function() {
|
||||
var i = textLower.indexOf(this.toLowerCase());
|
||||
if (i > -1)
|
||||
start = i;
|
||||
});
|
||||
start = Math.max(start - 120, 0);
|
||||
var excerpt = ((start > 0) ? '...' : '') +
|
||||
$.trim(text.substr(start, 240)) +
|
||||
((start + 240 - text.length) ? '...' : '');
|
||||
var rv = $('<div class="context"></div>').text(excerpt);
|
||||
$.each(hlwords, function() {
|
||||
rv = rv.highlightText(this, 'highlighted');
|
||||
});
|
||||
return rv;
|
||||
}
|
||||
};
|
||||
|
||||
$(document).ready(function() {
|
||||
Search.init();
|
||||
});
|
||||
|
After Width: | Height: | Size: 214 B |
|
After Width: | Height: | Size: 203 B |
@@ -0,0 +1,808 @@
|
||||
/*
|
||||
* websupport.js
|
||||
* ~~~~~~~~~~~~~
|
||||
*
|
||||
* sphinx.websupport utilities for all documentation.
|
||||
*
|
||||
* :copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS.
|
||||
* :license: BSD, see LICENSE for details.
|
||||
*
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
$.fn.autogrow = function() {
|
||||
return this.each(function() {
|
||||
var textarea = this;
|
||||
|
||||
$.fn.autogrow.resize(textarea);
|
||||
|
||||
$(textarea)
|
||||
.focus(function() {
|
||||
textarea.interval = setInterval(function() {
|
||||
$.fn.autogrow.resize(textarea);
|
||||
}, 500);
|
||||
})
|
||||
.blur(function() {
|
||||
clearInterval(textarea.interval);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$.fn.autogrow.resize = function(textarea) {
|
||||
var lineHeight = parseInt($(textarea).css('line-height'), 10);
|
||||
var lines = textarea.value.split('\n');
|
||||
var columns = textarea.cols;
|
||||
var lineCount = 0;
|
||||
$.each(lines, function() {
|
||||
lineCount += Math.ceil(this.length / columns) || 1;
|
||||
});
|
||||
var height = lineHeight * (lineCount + 1);
|
||||
$(textarea).css('height', height);
|
||||
};
|
||||
})(jQuery);
|
||||
|
||||
(function($) {
|
||||
var comp, by;
|
||||
|
||||
function init() {
|
||||
initEvents();
|
||||
initComparator();
|
||||
}
|
||||
|
||||
function initEvents() {
|
||||
$(document).on("click", 'a.comment-close', function(event) {
|
||||
event.preventDefault();
|
||||
hide($(this).attr('id').substring(2));
|
||||
});
|
||||
$(document).on("click", 'a.vote', function(event) {
|
||||
event.preventDefault();
|
||||
handleVote($(this));
|
||||
});
|
||||
$(document).on("click", 'a.reply', function(event) {
|
||||
event.preventDefault();
|
||||
openReply($(this).attr('id').substring(2));
|
||||
});
|
||||
$(document).on("click", 'a.close-reply', function(event) {
|
||||
event.preventDefault();
|
||||
closeReply($(this).attr('id').substring(2));
|
||||
});
|
||||
$(document).on("click", 'a.sort-option', function(event) {
|
||||
event.preventDefault();
|
||||
handleReSort($(this));
|
||||
});
|
||||
$(document).on("click", 'a.show-proposal', function(event) {
|
||||
event.preventDefault();
|
||||
showProposal($(this).attr('id').substring(2));
|
||||
});
|
||||
$(document).on("click", 'a.hide-proposal', function(event) {
|
||||
event.preventDefault();
|
||||
hideProposal($(this).attr('id').substring(2));
|
||||
});
|
||||
$(document).on("click", 'a.show-propose-change', function(event) {
|
||||
event.preventDefault();
|
||||
showProposeChange($(this).attr('id').substring(2));
|
||||
});
|
||||
$(document).on("click", 'a.hide-propose-change', function(event) {
|
||||
event.preventDefault();
|
||||
hideProposeChange($(this).attr('id').substring(2));
|
||||
});
|
||||
$(document).on("click", 'a.accept-comment', function(event) {
|
||||
event.preventDefault();
|
||||
acceptComment($(this).attr('id').substring(2));
|
||||
});
|
||||
$(document).on("click", 'a.delete-comment', function(event) {
|
||||
event.preventDefault();
|
||||
deleteComment($(this).attr('id').substring(2));
|
||||
});
|
||||
$(document).on("click", 'a.comment-markup', function(event) {
|
||||
event.preventDefault();
|
||||
toggleCommentMarkupBox($(this).attr('id').substring(2));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set comp, which is a comparator function used for sorting and
|
||||
* inserting comments into the list.
|
||||
*/
|
||||
function setComparator() {
|
||||
// If the first three letters are "asc", sort in ascending order
|
||||
// and remove the prefix.
|
||||
if (by.substring(0,3) == 'asc') {
|
||||
var i = by.substring(3);
|
||||
comp = function(a, b) { return a[i] - b[i]; };
|
||||
} else {
|
||||
// Otherwise sort in descending order.
|
||||
comp = function(a, b) { return b[by] - a[by]; };
|
||||
}
|
||||
|
||||
// Reset link styles and format the selected sort option.
|
||||
$('a.sel').attr('href', '#').removeClass('sel');
|
||||
$('a.by' + by).removeAttr('href').addClass('sel');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a comp function. If the user has preferences stored in
|
||||
* the sortBy cookie, use those, otherwise use the default.
|
||||
*/
|
||||
function initComparator() {
|
||||
by = 'rating'; // Default to sort by rating.
|
||||
// If the sortBy cookie is set, use that instead.
|
||||
if (document.cookie.length > 0) {
|
||||
var start = document.cookie.indexOf('sortBy=');
|
||||
if (start != -1) {
|
||||
start = start + 7;
|
||||
var end = document.cookie.indexOf(";", start);
|
||||
if (end == -1) {
|
||||
end = document.cookie.length;
|
||||
by = unescape(document.cookie.substring(start, end));
|
||||
}
|
||||
}
|
||||
}
|
||||
setComparator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a comment div.
|
||||
*/
|
||||
function show(id) {
|
||||
$('#ao' + id).hide();
|
||||
$('#ah' + id).show();
|
||||
var context = $.extend({id: id}, opts);
|
||||
var popup = $(renderTemplate(popupTemplate, context)).hide();
|
||||
popup.find('textarea[name="proposal"]').hide();
|
||||
popup.find('a.by' + by).addClass('sel');
|
||||
var form = popup.find('#cf' + id);
|
||||
form.submit(function(event) {
|
||||
event.preventDefault();
|
||||
addComment(form);
|
||||
});
|
||||
$('#s' + id).after(popup);
|
||||
popup.slideDown('fast', function() {
|
||||
getComments(id);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide a comment div.
|
||||
*/
|
||||
function hide(id) {
|
||||
$('#ah' + id).hide();
|
||||
$('#ao' + id).show();
|
||||
var div = $('#sc' + id);
|
||||
div.slideUp('fast', function() {
|
||||
div.remove();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform an ajax request to get comments for a node
|
||||
* and insert the comments into the comments tree.
|
||||
*/
|
||||
function getComments(id) {
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: opts.getCommentsURL,
|
||||
data: {node: id},
|
||||
success: function(data, textStatus, request) {
|
||||
var ul = $('#cl' + id);
|
||||
var speed = 100;
|
||||
$('#cf' + id)
|
||||
.find('textarea[name="proposal"]')
|
||||
.data('source', data.source);
|
||||
|
||||
if (data.comments.length === 0) {
|
||||
ul.html('<li>No comments yet.</li>');
|
||||
ul.data('empty', true);
|
||||
} else {
|
||||
// If there are comments, sort them and put them in the list.
|
||||
var comments = sortComments(data.comments);
|
||||
speed = data.comments.length * 100;
|
||||
appendComments(comments, ul);
|
||||
ul.data('empty', false);
|
||||
}
|
||||
$('#cn' + id).slideUp(speed + 200);
|
||||
ul.slideDown(speed);
|
||||
},
|
||||
error: function(request, textStatus, error) {
|
||||
showError('Oops, there was a problem retrieving the comments.');
|
||||
},
|
||||
dataType: 'json'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a comment via ajax and insert the comment into the comment tree.
|
||||
*/
|
||||
function addComment(form) {
|
||||
var node_id = form.find('input[name="node"]').val();
|
||||
var parent_id = form.find('input[name="parent"]').val();
|
||||
var text = form.find('textarea[name="comment"]').val();
|
||||
var proposal = form.find('textarea[name="proposal"]').val();
|
||||
|
||||
if (text == '') {
|
||||
showError('Please enter a comment.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable the form that is being submitted.
|
||||
form.find('textarea,input').attr('disabled', 'disabled');
|
||||
|
||||
// Send the comment to the server.
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: opts.addCommentURL,
|
||||
dataType: 'json',
|
||||
data: {
|
||||
node: node_id,
|
||||
parent: parent_id,
|
||||
text: text,
|
||||
proposal: proposal
|
||||
},
|
||||
success: function(data, textStatus, error) {
|
||||
// Reset the form.
|
||||
if (node_id) {
|
||||
hideProposeChange(node_id);
|
||||
}
|
||||
form.find('textarea')
|
||||
.val('')
|
||||
.add(form.find('input'))
|
||||
.removeAttr('disabled');
|
||||
var ul = $('#cl' + (node_id || parent_id));
|
||||
if (ul.data('empty')) {
|
||||
$(ul).empty();
|
||||
ul.data('empty', false);
|
||||
}
|
||||
insertComment(data.comment);
|
||||
var ao = $('#ao' + node_id);
|
||||
ao.find('img').attr({'src': opts.commentBrightImage});
|
||||
if (node_id) {
|
||||
// if this was a "root" comment, remove the commenting box
|
||||
// (the user can get it back by reopening the comment popup)
|
||||
$('#ca' + node_id).slideUp();
|
||||
}
|
||||
},
|
||||
error: function(request, textStatus, error) {
|
||||
form.find('textarea,input').removeAttr('disabled');
|
||||
showError('Oops, there was a problem adding the comment.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively append comments to the main comment list and children
|
||||
* lists, creating the comment tree.
|
||||
*/
|
||||
function appendComments(comments, ul) {
|
||||
$.each(comments, function() {
|
||||
var div = createCommentDiv(this);
|
||||
ul.append($(document.createElement('li')).html(div));
|
||||
appendComments(this.children, div.find('ul.comment-children'));
|
||||
// To avoid stagnating data, don't store the comments children in data.
|
||||
this.children = null;
|
||||
div.data('comment', this);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* After adding a new comment, it must be inserted in the correct
|
||||
* location in the comment tree.
|
||||
*/
|
||||
function insertComment(comment) {
|
||||
var div = createCommentDiv(comment);
|
||||
|
||||
// To avoid stagnating data, don't store the comments children in data.
|
||||
comment.children = null;
|
||||
div.data('comment', comment);
|
||||
|
||||
var ul = $('#cl' + (comment.node || comment.parent));
|
||||
var siblings = getChildren(ul);
|
||||
|
||||
var li = $(document.createElement('li'));
|
||||
li.hide();
|
||||
|
||||
// Determine where in the parents children list to insert this comment.
|
||||
for(var i=0; i < siblings.length; i++) {
|
||||
if (comp(comment, siblings[i]) <= 0) {
|
||||
$('#cd' + siblings[i].id)
|
||||
.parent()
|
||||
.before(li.html(div));
|
||||
li.slideDown('fast');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If we get here, this comment rates lower than all the others,
|
||||
// or it is the only comment in the list.
|
||||
ul.append(li.html(div));
|
||||
li.slideDown('fast');
|
||||
}
|
||||
|
||||
function acceptComment(id) {
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: opts.acceptCommentURL,
|
||||
data: {id: id},
|
||||
success: function(data, textStatus, request) {
|
||||
$('#cm' + id).fadeOut('fast');
|
||||
$('#cd' + id).removeClass('moderate');
|
||||
},
|
||||
error: function(request, textStatus, error) {
|
||||
showError('Oops, there was a problem accepting the comment.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function deleteComment(id) {
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: opts.deleteCommentURL,
|
||||
data: {id: id},
|
||||
success: function(data, textStatus, request) {
|
||||
var div = $('#cd' + id);
|
||||
if (data == 'delete') {
|
||||
// Moderator mode: remove the comment and all children immediately
|
||||
div.slideUp('fast', function() {
|
||||
div.remove();
|
||||
});
|
||||
return;
|
||||
}
|
||||
// User mode: only mark the comment as deleted
|
||||
div
|
||||
.find('span.user-id:first')
|
||||
.text('[deleted]').end()
|
||||
.find('div.comment-text:first')
|
||||
.text('[deleted]').end()
|
||||
.find('#cm' + id + ', #dc' + id + ', #ac' + id + ', #rc' + id +
|
||||
', #sp' + id + ', #hp' + id + ', #cr' + id + ', #rl' + id)
|
||||
.remove();
|
||||
var comment = div.data('comment');
|
||||
comment.username = '[deleted]';
|
||||
comment.text = '[deleted]';
|
||||
div.data('comment', comment);
|
||||
},
|
||||
error: function(request, textStatus, error) {
|
||||
showError('Oops, there was a problem deleting the comment.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function showProposal(id) {
|
||||
$('#sp' + id).hide();
|
||||
$('#hp' + id).show();
|
||||
$('#pr' + id).slideDown('fast');
|
||||
}
|
||||
|
||||
function hideProposal(id) {
|
||||
$('#hp' + id).hide();
|
||||
$('#sp' + id).show();
|
||||
$('#pr' + id).slideUp('fast');
|
||||
}
|
||||
|
||||
function showProposeChange(id) {
|
||||
$('#pc' + id).hide();
|
||||
$('#hc' + id).show();
|
||||
var textarea = $('#pt' + id);
|
||||
textarea.val(textarea.data('source'));
|
||||
$.fn.autogrow.resize(textarea[0]);
|
||||
textarea.slideDown('fast');
|
||||
}
|
||||
|
||||
function hideProposeChange(id) {
|
||||
$('#hc' + id).hide();
|
||||
$('#pc' + id).show();
|
||||
var textarea = $('#pt' + id);
|
||||
textarea.val('').removeAttr('disabled');
|
||||
textarea.slideUp('fast');
|
||||
}
|
||||
|
||||
function toggleCommentMarkupBox(id) {
|
||||
$('#mb' + id).toggle();
|
||||
}
|
||||
|
||||
/** Handle when the user clicks on a sort by link. */
|
||||
function handleReSort(link) {
|
||||
var classes = link.attr('class').split(/\s+/);
|
||||
for (var i=0; i<classes.length; i++) {
|
||||
if (classes[i] != 'sort-option') {
|
||||
by = classes[i].substring(2);
|
||||
}
|
||||
}
|
||||
setComparator();
|
||||
// Save/update the sortBy cookie.
|
||||
var expiration = new Date();
|
||||
expiration.setDate(expiration.getDate() + 365);
|
||||
document.cookie= 'sortBy=' + escape(by) +
|
||||
';expires=' + expiration.toUTCString();
|
||||
$('ul.comment-ul').each(function(index, ul) {
|
||||
var comments = getChildren($(ul), true);
|
||||
comments = sortComments(comments);
|
||||
appendComments(comments, $(ul).empty());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to process a vote when a user clicks an arrow.
|
||||
*/
|
||||
function handleVote(link) {
|
||||
if (!opts.voting) {
|
||||
showError("You'll need to login to vote.");
|
||||
return;
|
||||
}
|
||||
|
||||
var id = link.attr('id');
|
||||
if (!id) {
|
||||
// Didn't click on one of the voting arrows.
|
||||
return;
|
||||
}
|
||||
// If it is an unvote, the new vote value is 0,
|
||||
// Otherwise it's 1 for an upvote, or -1 for a downvote.
|
||||
var value = 0;
|
||||
if (id.charAt(1) != 'u') {
|
||||
value = id.charAt(0) == 'u' ? 1 : -1;
|
||||
}
|
||||
// The data to be sent to the server.
|
||||
var d = {
|
||||
comment_id: id.substring(2),
|
||||
value: value
|
||||
};
|
||||
|
||||
// Swap the vote and unvote links.
|
||||
link.hide();
|
||||
$('#' + id.charAt(0) + (id.charAt(1) == 'u' ? 'v' : 'u') + d.comment_id)
|
||||
.show();
|
||||
|
||||
// The div the comment is displayed in.
|
||||
var div = $('div#cd' + d.comment_id);
|
||||
var data = div.data('comment');
|
||||
|
||||
// If this is not an unvote, and the other vote arrow has
|
||||
// already been pressed, unpress it.
|
||||
if ((d.value !== 0) && (data.vote === d.value * -1)) {
|
||||
$('#' + (d.value == 1 ? 'd' : 'u') + 'u' + d.comment_id).hide();
|
||||
$('#' + (d.value == 1 ? 'd' : 'u') + 'v' + d.comment_id).show();
|
||||
}
|
||||
|
||||
// Update the comments rating in the local data.
|
||||
data.rating += (data.vote === 0) ? d.value : (d.value - data.vote);
|
||||
data.vote = d.value;
|
||||
div.data('comment', data);
|
||||
|
||||
// Change the rating text.
|
||||
div.find('.rating:first')
|
||||
.text(data.rating + ' point' + (data.rating == 1 ? '' : 's'));
|
||||
|
||||
// Send the vote information to the server.
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: opts.processVoteURL,
|
||||
data: d,
|
||||
error: function(request, textStatus, error) {
|
||||
showError('Oops, there was a problem casting that vote.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a reply form used to reply to an existing comment.
|
||||
*/
|
||||
function openReply(id) {
|
||||
// Swap out the reply link for the hide link
|
||||
$('#rl' + id).hide();
|
||||
$('#cr' + id).show();
|
||||
|
||||
// Add the reply li to the children ul.
|
||||
var div = $(renderTemplate(replyTemplate, {id: id})).hide();
|
||||
$('#cl' + id)
|
||||
.prepend(div)
|
||||
// Setup the submit handler for the reply form.
|
||||
.find('#rf' + id)
|
||||
.submit(function(event) {
|
||||
event.preventDefault();
|
||||
addComment($('#rf' + id));
|
||||
closeReply(id);
|
||||
})
|
||||
.find('input[type=button]')
|
||||
.click(function() {
|
||||
closeReply(id);
|
||||
});
|
||||
div.slideDown('fast', function() {
|
||||
$('#rf' + id).find('textarea').focus();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the reply form opened with openReply.
|
||||
*/
|
||||
function closeReply(id) {
|
||||
// Remove the reply div from the DOM.
|
||||
$('#rd' + id).slideUp('fast', function() {
|
||||
$(this).remove();
|
||||
});
|
||||
|
||||
// Swap out the hide link for the reply link
|
||||
$('#cr' + id).hide();
|
||||
$('#rl' + id).show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively sort a tree of comments using the comp comparator.
|
||||
*/
|
||||
function sortComments(comments) {
|
||||
comments.sort(comp);
|
||||
$.each(comments, function() {
|
||||
this.children = sortComments(this.children);
|
||||
});
|
||||
return comments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the children comments from a ul. If recursive is true,
|
||||
* recursively include childrens' children.
|
||||
*/
|
||||
function getChildren(ul, recursive) {
|
||||
var children = [];
|
||||
ul.children().children("[id^='cd']")
|
||||
.each(function() {
|
||||
var comment = $(this).data('comment');
|
||||
if (recursive)
|
||||
comment.children = getChildren($(this).find('#cl' + comment.id), true);
|
||||
children.push(comment);
|
||||
});
|
||||
return children;
|
||||
}
|
||||
|
||||
/** Create a div to display a comment in. */
|
||||
function createCommentDiv(comment) {
|
||||
if (!comment.displayed && !opts.moderator) {
|
||||
return $('<div class="moderate">Thank you! Your comment will show up '
|
||||
+ 'once it is has been approved by a moderator.</div>');
|
||||
}
|
||||
// Prettify the comment rating.
|
||||
comment.pretty_rating = comment.rating + ' point' +
|
||||
(comment.rating == 1 ? '' : 's');
|
||||
// Make a class (for displaying not yet moderated comments differently)
|
||||
comment.css_class = comment.displayed ? '' : ' moderate';
|
||||
// Create a div for this comment.
|
||||
var context = $.extend({}, opts, comment);
|
||||
var div = $(renderTemplate(commentTemplate, context));
|
||||
|
||||
// If the user has voted on this comment, highlight the correct arrow.
|
||||
if (comment.vote) {
|
||||
var direction = (comment.vote == 1) ? 'u' : 'd';
|
||||
div.find('#' + direction + 'v' + comment.id).hide();
|
||||
div.find('#' + direction + 'u' + comment.id).show();
|
||||
}
|
||||
|
||||
if (opts.moderator || comment.text != '[deleted]') {
|
||||
div.find('a.reply').show();
|
||||
if (comment.proposal_diff)
|
||||
div.find('#sp' + comment.id).show();
|
||||
if (opts.moderator && !comment.displayed)
|
||||
div.find('#cm' + comment.id).show();
|
||||
if (opts.moderator || (opts.username == comment.username))
|
||||
div.find('#dc' + comment.id).show();
|
||||
}
|
||||
return div;
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple template renderer. Placeholders such as <%id%> are replaced
|
||||
* by context['id'] with items being escaped. Placeholders such as <#id#>
|
||||
* are not escaped.
|
||||
*/
|
||||
function renderTemplate(template, context) {
|
||||
var esc = $(document.createElement('div'));
|
||||
|
||||
function handle(ph, escape) {
|
||||
var cur = context;
|
||||
$.each(ph.split('.'), function() {
|
||||
cur = cur[this];
|
||||
});
|
||||
return escape ? esc.text(cur || "").html() : cur;
|
||||
}
|
||||
|
||||
return template.replace(/<([%#])([\w\.]*)\1>/g, function() {
|
||||
return handle(arguments[2], arguments[1] == '%' ? true : false);
|
||||
});
|
||||
}
|
||||
|
||||
/** Flash an error message briefly. */
|
||||
function showError(message) {
|
||||
$(document.createElement('div')).attr({'class': 'popup-error'})
|
||||
.append($(document.createElement('div'))
|
||||
.attr({'class': 'error-message'}).text(message))
|
||||
.appendTo('body')
|
||||
.fadeIn("slow")
|
||||
.delay(2000)
|
||||
.fadeOut("slow");
|
||||
}
|
||||
|
||||
/** Add a link the user uses to open the comments popup. */
|
||||
$.fn.comment = function() {
|
||||
return this.each(function() {
|
||||
var id = $(this).attr('id').substring(1);
|
||||
var count = COMMENT_METADATA[id];
|
||||
var title = count + ' comment' + (count == 1 ? '' : 's');
|
||||
var image = count > 0 ? opts.commentBrightImage : opts.commentImage;
|
||||
var addcls = count == 0 ? ' nocomment' : '';
|
||||
$(this)
|
||||
.append(
|
||||
$(document.createElement('a')).attr({
|
||||
href: '#',
|
||||
'class': 'sphinx-comment-open' + addcls,
|
||||
id: 'ao' + id
|
||||
})
|
||||
.append($(document.createElement('img')).attr({
|
||||
src: image,
|
||||
alt: 'comment',
|
||||
title: title
|
||||
}))
|
||||
.click(function(event) {
|
||||
event.preventDefault();
|
||||
show($(this).attr('id').substring(2));
|
||||
})
|
||||
)
|
||||
.append(
|
||||
$(document.createElement('a')).attr({
|
||||
href: '#',
|
||||
'class': 'sphinx-comment-close hidden',
|
||||
id: 'ah' + id
|
||||
})
|
||||
.append($(document.createElement('img')).attr({
|
||||
src: opts.closeCommentImage,
|
||||
alt: 'close',
|
||||
title: 'close'
|
||||
}))
|
||||
.click(function(event) {
|
||||
event.preventDefault();
|
||||
hide($(this).attr('id').substring(2));
|
||||
})
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
var opts = {
|
||||
processVoteURL: '/_process_vote',
|
||||
addCommentURL: '/_add_comment',
|
||||
getCommentsURL: '/_get_comments',
|
||||
acceptCommentURL: '/_accept_comment',
|
||||
deleteCommentURL: '/_delete_comment',
|
||||
commentImage: '/static/_static/comment.png',
|
||||
closeCommentImage: '/static/_static/comment-close.png',
|
||||
loadingImage: '/static/_static/ajax-loader.gif',
|
||||
commentBrightImage: '/static/_static/comment-bright.png',
|
||||
upArrow: '/static/_static/up.png',
|
||||
downArrow: '/static/_static/down.png',
|
||||
upArrowPressed: '/static/_static/up-pressed.png',
|
||||
downArrowPressed: '/static/_static/down-pressed.png',
|
||||
voting: false,
|
||||
moderator: false
|
||||
};
|
||||
|
||||
if (typeof COMMENT_OPTIONS != "undefined") {
|
||||
opts = jQuery.extend(opts, COMMENT_OPTIONS);
|
||||
}
|
||||
|
||||
var popupTemplate = '\
|
||||
<div class="sphinx-comments" id="sc<%id%>">\
|
||||
<p class="sort-options">\
|
||||
Sort by:\
|
||||
<a href="#" class="sort-option byrating">best rated</a>\
|
||||
<a href="#" class="sort-option byascage">newest</a>\
|
||||
<a href="#" class="sort-option byage">oldest</a>\
|
||||
</p>\
|
||||
<div class="comment-header">Comments</div>\
|
||||
<div class="comment-loading" id="cn<%id%>">\
|
||||
loading comments... <img src="<%loadingImage%>" alt="" /></div>\
|
||||
<ul id="cl<%id%>" class="comment-ul"></ul>\
|
||||
<div id="ca<%id%>">\
|
||||
<p class="add-a-comment">Add a comment\
|
||||
(<a href="#" class="comment-markup" id="ab<%id%>">markup</a>):</p>\
|
||||
<div class="comment-markup-box" id="mb<%id%>">\
|
||||
reStructured text markup: <i>*emph*</i>, <b>**strong**</b>, \
|
||||
<code>``code``</code>, \
|
||||
code blocks: <code>::</code> and an indented block after blank line</div>\
|
||||
<form method="post" id="cf<%id%>" class="comment-form" action="">\
|
||||
<textarea name="comment" cols="80"></textarea>\
|
||||
<p class="propose-button">\
|
||||
<a href="#" id="pc<%id%>" class="show-propose-change">\
|
||||
Propose a change ▹\
|
||||
</a>\
|
||||
<a href="#" id="hc<%id%>" class="hide-propose-change">\
|
||||
Propose a change ▿\
|
||||
</a>\
|
||||
</p>\
|
||||
<textarea name="proposal" id="pt<%id%>" cols="80"\
|
||||
spellcheck="false"></textarea>\
|
||||
<input type="submit" value="Add comment" />\
|
||||
<input type="hidden" name="node" value="<%id%>" />\
|
||||
<input type="hidden" name="parent" value="" />\
|
||||
</form>\
|
||||
</div>\
|
||||
</div>';
|
||||
|
||||
var commentTemplate = '\
|
||||
<div id="cd<%id%>" class="sphinx-comment<%css_class%>">\
|
||||
<div class="vote">\
|
||||
<div class="arrow">\
|
||||
<a href="#" id="uv<%id%>" class="vote" title="vote up">\
|
||||
<img src="<%upArrow%>" />\
|
||||
</a>\
|
||||
<a href="#" id="uu<%id%>" class="un vote" title="vote up">\
|
||||
<img src="<%upArrowPressed%>" />\
|
||||
</a>\
|
||||
</div>\
|
||||
<div class="arrow">\
|
||||
<a href="#" id="dv<%id%>" class="vote" title="vote down">\
|
||||
<img src="<%downArrow%>" id="da<%id%>" />\
|
||||
</a>\
|
||||
<a href="#" id="du<%id%>" class="un vote" title="vote down">\
|
||||
<img src="<%downArrowPressed%>" />\
|
||||
</a>\
|
||||
</div>\
|
||||
</div>\
|
||||
<div class="comment-content">\
|
||||
<p class="tagline comment">\
|
||||
<span class="user-id"><%username%></span>\
|
||||
<span class="rating"><%pretty_rating%></span>\
|
||||
<span class="delta"><%time.delta%></span>\
|
||||
</p>\
|
||||
<div class="comment-text comment"><#text#></div>\
|
||||
<p class="comment-opts comment">\
|
||||
<a href="#" class="reply hidden" id="rl<%id%>">reply ▹</a>\
|
||||
<a href="#" class="close-reply" id="cr<%id%>">reply ▿</a>\
|
||||
<a href="#" id="sp<%id%>" class="show-proposal">proposal ▹</a>\
|
||||
<a href="#" id="hp<%id%>" class="hide-proposal">proposal ▿</a>\
|
||||
<a href="#" id="dc<%id%>" class="delete-comment hidden">delete</a>\
|
||||
<span id="cm<%id%>" class="moderation hidden">\
|
||||
<a href="#" id="ac<%id%>" class="accept-comment">accept</a>\
|
||||
</span>\
|
||||
</p>\
|
||||
<pre class="proposal" id="pr<%id%>">\
|
||||
<#proposal_diff#>\
|
||||
</pre>\
|
||||
<ul class="comment-children" id="cl<%id%>"></ul>\
|
||||
</div>\
|
||||
<div class="clearleft"></div>\
|
||||
</div>\
|
||||
</div>';
|
||||
|
||||
var replyTemplate = '\
|
||||
<li>\
|
||||
<div class="reply-div" id="rd<%id%>">\
|
||||
<form id="rf<%id%>">\
|
||||
<textarea name="comment" cols="80"></textarea>\
|
||||
<input type="submit" value="Add reply" />\
|
||||
<input type="button" value="Cancel" />\
|
||||
<input type="hidden" name="parent" value="<%id%>" />\
|
||||
<input type="hidden" name="node" value="" />\
|
||||
</form>\
|
||||
</div>\
|
||||
</li>';
|
||||
|
||||
$(document).ready(function() {
|
||||
init();
|
||||
});
|
||||
})(jQuery);
|
||||
|
||||
$(document).ready(function() {
|
||||
// add comment anchors for all paragraphs that are commentable
|
||||
$('.sphinx-has-comment').comment();
|
||||
|
||||
// highlight search words in search results
|
||||
$("div.context").each(function() {
|
||||
var params = $.getQueryParameters();
|
||||
var terms = (params.q) ? params.q[0].split(/\s+/) : [];
|
||||
var result = $(this);
|
||||
$.each(terms, function() {
|
||||
result.highlightText(this.toLowerCase(), 'highlighted');
|
||||
});
|
||||
});
|
||||
|
||||
// directly open comment window if requested
|
||||
var anchor = document.location.hash;
|
||||
if (anchor.substring(0, 9) == '#comment-') {
|
||||
$('#ao' + anchor.substring(9)).click();
|
||||
document.location.hash = '#s' + anchor.substring(9);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
|
||||
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title>43. Bisector Window</title>
|
||||
<link rel="stylesheet" href="_static/nonav.css" type="text/css" />
|
||||
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
|
||||
</head><body>
|
||||
|
||||
<div class="document">
|
||||
<div class="documentwrapper">
|
||||
<div class="body" role="main">
|
||||
|
||||
<div class="section" id="bisector-window">
|
||||
<h1>43. Bisector Window</h1>
|
||||
<p>.</p>
|
||||
<img alt="_images/Bisector.png" src="_images/Bisector.png" />
|
||||
</div>
|
||||
<div class="section" id="using-the-bisector-window">
|
||||
<h1>44. Using the Bisector Window</h1>
|
||||
<p>The purpose of this window is obviously to split the list of frames into two pieces. You have many options for how to do this. You can split by:</p>
|
||||
<p>ID Range - Grab a set of frames between two values. Note that you cannot pick and choose frame IDs here but rather just use a list. If you want to pick and choose then use the filter interface on the main form and then save the filtered list from the File menu.</p>
|
||||
<p>Frame Number - You can split right at a given frame number. All frames up to that number will be on one side with all the rest on the other.</p>
|
||||
<p>Percentage - Pretty much just like the Frame Number option but in percentage instead in case that is more convenient.</p>
|
||||
<p>In all cases, you have the option of which side of the split you want to save. Click “Calculate Split” to process the split. You will see above the buttons a reference of how many frames there were in total and how many you would be saving after the split. From here you <em>should</em> be able to do one of two things:</p>
|
||||
<p>“Save split frames to a new file” - Save the new list of frames (after the split) to a file. You can save to any file format that SavvyCAN supports elsewhere.</p>
|
||||
<p>“Replace main list with split frames” - Were this to work it would replace the frames loaded in the rest of the application with the split list. I’m sure this would be a really nice thing to be able to do. Well, you can’t right now. It’s not implemented yet. So, just imagine you can do this. In the mean time you can save to a file then load that file. Sorry…</p>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,81 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
|
||||
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title>9. Connection Window</title>
|
||||
<link rel="stylesheet" href="_static/nonav.css" type="text/css" />
|
||||
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
|
||||
</head><body>
|
||||
|
||||
<div class="document">
|
||||
<div class="documentwrapper">
|
||||
<div class="body" role="main">
|
||||
|
||||
<div class="section" id="connection-window">
|
||||
<h1>9. Connection Window</h1>
|
||||
<p>.</p>
|
||||
<img alt="_images/ConnectionWindow.png" src="_images/ConnectionWindow.png" />
|
||||
<p>The connection window is used to add, remove, and modify connections. At the moment it is possible
|
||||
to use any QT SerialBus compatible device and any GVRET compatible device in any of the supported
|
||||
operating systems. SerialBus supports socketcan on linux, passthrough on Linux and Windows 32
|
||||
bit, and Vector, PeakCAN, and TinyCAN on supported OS’s.</p>
|
||||
<p>At this time GVRET compatible devices are: EVTVDue, EVTV CANDue (1.3/2/2.1/2.2),
|
||||
Teensy 3.1-3.6, Macchina M2, EVTV ESP32 Due.</p>
|
||||
</div>
|
||||
<div class="section" id="connecting-to-a-dongle">
|
||||
<h1>10. Connecting To A Dongle</h1>
|
||||
<p>SavvyCAN is able to connect to GVRET compatible devices to capture new traffic. These
|
||||
devices will present as serial ports on the connected PC.
|
||||
To connect to a dongle select the proper serial port and click “Create New Connection”.
|
||||
If a valid device is found on that serial port the first statusbar section will update
|
||||
and the currently set canbus speeds will show in the table at the left of the window.
|
||||
These speeds can then be changed by clicking on the speed (or otherwise selecting the cell
|
||||
in the table) and typing in a new value. Leaving the cell will update the speed to the new
|
||||
value. GVRET devices also support setting “listen only” on each bus. This mode causes the
|
||||
device to not acknowledge any traffic or try to modify the bus at all. It is as it says,
|
||||
a mode where you can only listen to whatever traffic is found on the bus. Some older GVRET
|
||||
devices supported a mode where you could change the second bus between single wire CAN
|
||||
and normal CAN. This is deprecated. However, newer GVRET devices have dedicated single
|
||||
wire CAN buses and the relevant bus will show the checkbox.</p>
|
||||
<p>SavvyCAN can also connect to a wide variety of CAN hardware through the built-in QT
|
||||
SerialBus drivers. These drivers vary by operating system but support socketcan on LINUX
|
||||
and Vector tools on both LINUX and Windows. When you select “QT SerialBus Devices” you will
|
||||
get a list of device types supported. Select a device type and for most devices you should see
|
||||
the Port list fill out with all registered and valid ports for that driver. Socketcan devices, for
|
||||
instance, are automatically detected now. Then push “Create New Connection” and you should
|
||||
see the new connection in the table on the left of the window. Note that SocketCAN devices
|
||||
don’t support changing the baud rate within a program. You must do this when you set up
|
||||
the connection via console commands. This is outside the scope of this documentation.
|
||||
Consult the SocketCAN documentation for details on configuring such devices.</p>
|
||||
<p>The last connection option is “Remote Host.” If you select this option then Port will change
|
||||
to a textbox. Enter the IP address of the remote (but still local to your LAN) IP address. Currently
|
||||
this works with EVTV ESP32 boards and M2 boards.</p>
|
||||
</div>
|
||||
<div class="section" id="debugging-connection-problems">
|
||||
<h1>11. Debugging Connection Problems</h1>
|
||||
<p>GVRET devices present as serial ports and have significant configuration options.
|
||||
However, the ability to configure so many things and the ability to compile the firmware
|
||||
yourself both come as a double edged sword. They present many opportunities for things
|
||||
to go wrong. Because of this there is a debugging console present on the connection window.
|
||||
Click a bus in the table then click “Enable Console” to cause it to start logging serial
|
||||
traffic. From this console you can see what is going on. It shows what SavvyCAN is sending
|
||||
and what it is getting back. It has extended status messages that might help to narrow down
|
||||
what is going wrong. Additionally, if you’re feeling adventurous you can send traffic to
|
||||
the serial device from the Send line. “Send Hex” accepts a set of hex values separated
|
||||
by spaces. “Send Text” will send the raw text you type on the line. GVRET traffic is
|
||||
ordinarily binary so “Send Text” won’t work very well for that. But, there is also a
|
||||
text console possible on GVRET devices. If you connect to them with a serial program you can
|
||||
configure things via a text console. Type ? and follow it up with some form of line
|
||||
ending (Cr, Lf, CrLf, any will work).</p>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,126 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
|
||||
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title>74. Custom Sender Window</title>
|
||||
<link rel="stylesheet" href="_static/nonav.css" type="text/css" />
|
||||
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
|
||||
</head><body>
|
||||
|
||||
<div class="document">
|
||||
<div class="documentwrapper">
|
||||
<div class="body" role="main">
|
||||
|
||||
<div class="section" id="custom-sender-window">
|
||||
<h1>74. Custom Sender Window</h1>
|
||||
<p>.</p>
|
||||
<img alt="_images/CustomSender.png" src="_images/CustomSender.png" />
|
||||
</div>
|
||||
<div class="section" id="general-overview">
|
||||
<h1>75. General Overview</h1>
|
||||
<p>This window allows you to create custom frames that will be sent out on one of the can buses. The uses are endless.
|
||||
It can be used to generate valid traffic to control connected hardware. It can be used to test out various ideas
|
||||
you might have for now to interact with other devices. It can replay frames from one bus into the other bus but with
|
||||
modifications.</p>
|
||||
</div>
|
||||
<div class="section" id="layout-of-the-view">
|
||||
<h1>76. Layout of the View</h1>
|
||||
<p>This screen is laid out as a data grid.</p>
|
||||
<ul class="simple">
|
||||
<li>The first field is “En” which stands for Enabled. If the checkbox is checked
|
||||
then this line will be active.</li>
|
||||
<li>The second field is “Bus” and sets which bus to use for sending.</li>
|
||||
<li>The third field is “ID” and is the ID to use for sending. It can be specified in hex or decimal formats.</li>
|
||||
<li>The fourth field is “Len” and sets the number of data bytes for this frame.</li>
|
||||
<li>The fifth field is “Data” and specifies what values will be sent for the next frame. These values can be
|
||||
automatically updated by the modifiers which will be covered later on.</li>
|
||||
<li>The sixth field is “Trigger” which specifies when this frame will be sent. Proper syntax for triggers is
|
||||
covered later.</li>
|
||||
<li>The seventh field is “Modifications” which specifies how the data bytes for this frame will be changed for
|
||||
the next frame sent. Full syntax for this is covered later on.</li>
|
||||
<li>The final field is “Count” and is automatically filled out with the number of frames that have been sent as
|
||||
a result of this line.</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="writing-trigger-rules">
|
||||
<h1>77. Writing Trigger Rules</h1>
|
||||
<p>It should first be noted that each line can contain multiple triggers. Each trigger is separated by a comma ‘,’.</p>
|
||||
<p>Triggers are allowed to have one or more conditions. Each condition contained within a single trigger is separated
|
||||
by a space ‘ ‘.</p>
|
||||
<p>The program implments the following conditions:</p>
|
||||
<ul class="simple">
|
||||
<li>id - Set the trigger to activate when a frame with the given ID is received. The syntax is ‘id’
|
||||
followed by the numeric ID you want to match. Example: ‘id0x200’</li>
|
||||
<li>ms - Set the number of milliseconds to wait. This condition acts differently depending on whether
|
||||
you have set ID matching as well. If so then ms will cause a delay of the requested number of milliseconds
|
||||
after receiving a frame with the ID. If not, the trigger will constantly fire every time the requested number
|
||||
of milliseconds have elapsed. The syntax is the number of desired milliseconds followed by ‘ms’. For example:
|
||||
‘40ms’.</li>
|
||||
<li>x - Only allow this trigger to fire a set number of times. The syntax is the number of times you want the trigger
|
||||
to fire followed by ‘x’. For example: ‘100x’.</li>
|
||||
<li>bus - Only trigger when a frame with the given ID comes in on the specified bus. Otherwise the triggering frame
|
||||
could come from either bus. The syntax is ‘bus’ followed by either ‘0’ or ‘1’. For example: ‘bus0’</li>
|
||||
</ul>
|
||||
<p>Now, a full example of a trigger line: “id0x200 5ms 10x bus0,1000ms” This trigger means: Trigger when a frame with ID 0x200 comes
|
||||
in on bus 0. Wait 5 milliseconds before sending and only allow this to happen at most 10 times. Also, always trigger every
|
||||
1 second and never stop doing this.</p>
|
||||
</div>
|
||||
<div class="section" id="writing-modifications">
|
||||
<h1>78. Writing Modifications</h1>
|
||||
<p>As with the triggers, you can have multiple modifications per line; each of which is separated by a comma ‘,’.</p>
|
||||
<p>Modifications always start with a data byte to modify The syntax is ‘D’ or ‘d’ followed by a number 0 through 7.
|
||||
For example: ‘D4’. This is then always followed by an equal sign ‘=’. Thereafter there is a string of operands and
|
||||
operations.</p>
|
||||
<p>Operands have a special syntax. Each operand can have multiple sections separated by colons ‘:’.</p>
|
||||
<ul class="simple">
|
||||
<li>D - A data byte. Specifies which data byte 0 - 7 from the given frame to use for this operation. If specified
|
||||
on its own it will reference the data in the “Data” section of this line. The syntax is ‘d’ or ‘D’ followed
|
||||
by a number 0 - 7. Example: ‘D3’.</li>
|
||||
<li>ID - Instead of grabbing data from this line’s data bytes grab it from the last frame received with the given ID.
|
||||
Syntax: ‘ID’ followed by a colon ‘:’ followed by the ID to match against. Example: ‘ID:0x200’</li>
|
||||
<li>BUS - Restrict which bus the frame used for grabbing the data bytes can come in on. Syntax: ‘BUS’ followed by
|
||||
a colon ‘:’ followed by 0 or 1 to specify which bus to use. Example: ‘BUS:0’</li>
|
||||
<li><NUMBER> - Instead of using a data byte from somewhere you can instead use a numeric literal. The syntax is
|
||||
the same as any number - either a series of numbers or 0x followed by a series of numbers and A - F to specify
|
||||
a hexadecimal number. Example: 0x10.</li>
|
||||
</ul>
|
||||
<p>A few full examples of operands:</p>
|
||||
<ul class="simple">
|
||||
<li>“bus:0:id:0x120:D3” = Grab byte 3 from the last frame with ID 0x120 that came in on bus 0.</li>
|
||||
<li>“0x200” = Use the numeric value 0x200 directly.</li>
|
||||
<li>“id:0x200:D7” = Grab byte 7 from the last frame received with ID 0x200.</li>
|
||||
</ul>
|
||||
<p>Each operand is usually followed by an operation and then a second operand. If no operation or second operand is found that
|
||||
is also OK. Thus a modifier can be as simple as “D0 = D1” if you choose. Operands can also be directly chained such that the
|
||||
output of the last operation is used as the left hand operand for the next operation. This will be shown later on.</p>
|
||||
<p>Modifiers can use the following operations:</p>
|
||||
<ul class="simple">
|
||||
<li>+ - Add the two operands together. Example: “D1 + D2”</li>
|
||||
<li>- - Subtract the second operand from the first operand. Example: “D1 - D2”</li>
|
||||
<li>* - Multiply the two operands together. Example: “D1 * D2”</li>
|
||||
<li>/ - Divide the first operand by the second operand. Example: “D1 / D2”</li>
|
||||
<li>& - Do the bitfield operation AND on the two operands. Example: D1 & 0x20</li>
|
||||
<li>| - Do the bitfield operation OR on the two operands. Example: D1 | 0x10</li>
|
||||
<li>^ - Do the bitfield operation XOR on the two operands. Example: D1 ^ 0xD2</li>
|
||||
</ul>
|
||||
<p>Putting all of that together yields complete modifiers. For simplicity all operations
|
||||
are done left to right. There is no special order of operations like in normal mathematics.</p>
|
||||
<p>Here are a few examples:</p>
|
||||
<ul class="simple">
|
||||
<li>“D0=D0+1” = Take the value in D0 within this line’s data bytes, add 1, and store it back in D0.</li>
|
||||
<li>“D1=ID:0x200:D3+ID:0x200:D4&0xF0” = Grab byte 3 from the most recently received frame with ID 0x200 and add it
|
||||
to byte 4 from the same received frame. AND this new value with 0xF0 and finally store it in D1 of the data bytes for this line.</li>
|
||||
<li>“D2=D4 * 10 + D3 & 0x3F” = Multiply byte 4 by 10, add the new value to byte 3, AND the resulting value by 0x3F and store it in byte 2.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,40 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
|
||||
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title>17. DBC Message Editor</title>
|
||||
<link rel="stylesheet" href="_static/nonav.css" type="text/css" />
|
||||
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
|
||||
</head><body>
|
||||
|
||||
<div class="document">
|
||||
<div class="documentwrapper">
|
||||
<div class="body" role="main">
|
||||
|
||||
<div class="section" id="dbc-message-editor">
|
||||
<h1>17. DBC Message Editor</h1>
|
||||
<p>.</p>
|
||||
<img alt="_images/DBCEditor.png" src="_images/DBCEditor.png" />
|
||||
</div>
|
||||
<div class="section" id="working-with-nodes">
|
||||
<h1>18. Working with Nodes</h1>
|
||||
<p>In DBC files a node is a device on the CAN bus. For instance, the engine control unit (ECU) would be a node as would a motor controller, a battery charger, or any other device that is connected to the CAN bus. DBC files let you define nodes that are
|
||||
set as either the sender or receiver of a message. This allows messages to be organized for more easy retrieval. To add
|
||||
a new node click on the empty row beneath the last defined node and type a new name and optionally a comment. The comment is not used by SavvyCAN but can be filled out for your own reference. When a node is selected in the top list you will then see
|
||||
in the bottom a listing of every message it sends.</p>
|
||||
</div>
|
||||
<div class="section" id="working-with-messages">
|
||||
<h1>19. Working with Messages</h1>
|
||||
<p>The bottom list is all of the messages that are sent by the selected node. A message is defined based on its message ID. The message ID is the CAN id used for this message. For normal DBC files this creates a one to one correspondance of ID to a given CAN ID. For J1939 messages special masking is done and so more than one actual CAN id will map to the given message ID but still only one specific J1939 PGN will come through. Once a message ID is entered in for a new message it will attempt to auto populate the Data Len column with the number of data bytes that message has. This is only for your information, it is not used by SavvyCAN. You can give the DBC message a meaningful name. The “Fg” and “Bg” columns can be clicked on to set a color. This will set the foreground and background color to use for this message. These colors will be used in the main frame view on the main screen when you click “Interpret Frames.” Clicking on the “Signals” column will bring up the signals editor for that message so that you can edit, add, or remove signals from the message. The “Comments” column is once again not used by SavvyCAN and only for your viewing reference.</p>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,37 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
|
||||
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title>14. DBC File Manager</title>
|
||||
<link rel="stylesheet" href="_static/nonav.css" type="text/css" />
|
||||
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
|
||||
</head><body>
|
||||
|
||||
<div class="document">
|
||||
<div class="documentwrapper">
|
||||
<div class="body" role="main">
|
||||
|
||||
<div class="section" id="dbc-file-manager">
|
||||
<h1>14. DBC File Manager</h1>
|
||||
<p>.</p>
|
||||
<img alt="_images/DBCManager.png" src="_images/DBCManager.png" />
|
||||
</div>
|
||||
<div class="section" id="working-with-dbc-files">
|
||||
<h1>15. Working with DBC Files</h1>
|
||||
<p>This screen allows you to load and save DBC files. SavvyCAN supports loading more than one DBC file at a time. It can even use more than one DBC file per bus. But, “Associated Bus” can be used to associate a given DBC file to only one bus. If you don’t need to associate to any specific bus then set this value to -1 which means “any bus.” The J1939 button causes SavvyCAN to mask out J1939 message IDs to conform to J1939 signalling. You can create a brand new DBC file by clicking “Create new DBC” button. It will be automatically named a unique name for you. You probably don’t want that name though. Any time you save a DBC file its name will automatically update in the list. The “Load”, “Save”, “Remove”, “Edit” buttons are all straight forward. You can also edit a DBC file by double clicking it in the list.</p>
|
||||
</div>
|
||||
<div class="section" id="dbc-file-ordering">
|
||||
<h1>16. DBC File Ordering</h1>
|
||||
<p>The “Move Up” and “Move Down” buttons can be used to change the order of DBC files. Why would you care? DBC files are accessed in the order they are in the list. When a frame is interpreted the system goes through the DBC files in order. It selects the first DBC file that is associated to the bus the message came in on and that imprements the correct message ID. So, if you have multiple DBC files it is possible that the order might matter.</p>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,60 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
|
||||
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title>55. File Comparison Window</title>
|
||||
<link rel="stylesheet" href="_static/nonav.css" type="text/css" />
|
||||
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
|
||||
</head><body>
|
||||
|
||||
<div class="document">
|
||||
<div class="documentwrapper">
|
||||
<div class="body" role="main">
|
||||
|
||||
<div class="section" id="file-comparison-window">
|
||||
<h1>55. File Comparison Window</h1>
|
||||
<p>.</p>
|
||||
<img alt="_images/FileComparator.png" class="align-left" src="_images/FileComparator.png" />
|
||||
</div>
|
||||
<div class="section" id="the-purpose-of-the-file-comparator">
|
||||
<h1>56. The Purpose of the File Comparator</h1>
|
||||
<p>This screen can be used to figure out what is different between a set of files. On one side you have a single file. This is called the “File of interest”. On the other side you have any list of files. They’re not listed any longer as actual files. The program can load frames from any number of files and dump them all into the same “bucket” of frames. You can thus load up a batch of files and compare them against the one “File of interest.” The purpose of this is to figure out what is different. Are there IDs found only on one side? For IDs found on both sides are there bits set only on one side and not the other? This can be used to find stubborn data that you are having trouble locating. One use is to capture a large amount of traffic to use as “background noise” of sorts. Perhaps drive around for a long time or let the vehicle idle for some time but never do the thing you need to find. Then do another capture and do the thing you’re missing a few times. Perhaps you’re looking for a gear shift signal. You could capture a large batch of frames while idling. Then, in a second capture shift several times. Now, compare the two. Somewhere in the differences should be the gear selection you couldn’t find. The list ought to be much more narrow than just “shooting in the dark” so to speak.</p>
|
||||
</div>
|
||||
<div class="section" id="the-layout-of-the-differences-list">
|
||||
<h1>57. The layout of the differences list</h1>
|
||||
<p>In the differences list you’ll find three main tree nodes:</p>
|
||||
<ol class="arabic">
|
||||
<li><p class="first">IDs found only in <file of interest></p>
|
||||
<blockquote>
|
||||
<div><ul class="simple">
|
||||
<li>In sub nodes you’ll find every ID found only in the file of interest and not in any of the reference files.</li>
|
||||
</ul>
|
||||
</div></blockquote>
|
||||
</li>
|
||||
<li><p class="first">IDs found only in reference frames</p>
|
||||
<blockquote>
|
||||
<div><ul class="simple">
|
||||
<li>In theory this should be a fairly small list. Here are any IDs never seen in the file of interest</li>
|
||||
</ul>
|
||||
</div></blockquote>
|
||||
</li>
|
||||
<li><p class="first">IDs found in both</p>
|
||||
<blockquote>
|
||||
<div><ul class="simple">
|
||||
<li>Here is where the interesting information lies. The sub nodes here are found in both places. A list of all differences will be shown sub nodes of each ID node. Here you can see bits set only in one side or the other. You can also find values only found on one side or the other. These might be candidates for your mystery signal.</li>
|
||||
</ul>
|
||||
</div></blockquote>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,60 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
|
||||
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title>35. Flow View Window</title>
|
||||
<link rel="stylesheet" href="_static/nonav.css" type="text/css" />
|
||||
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
|
||||
</head><body>
|
||||
|
||||
<div class="document">
|
||||
<div class="documentwrapper">
|
||||
<div class="body" role="main">
|
||||
|
||||
<div class="section" id="flow-view-window">
|
||||
<h1>35. Flow View Window</h1>
|
||||
<p>.</p>
|
||||
<img alt="_images/FlowView.png" src="_images/FlowView.png" />
|
||||
</div>
|
||||
<div class="section" id="the-purpose-of-flow-view">
|
||||
<h1>36. The Purpose of Flow View</h1>
|
||||
<p>Flow view is meant to show a view of what “happens to” the data for a given ID as time goes on. The flow is a flow of time or messages going by. The goal is to visualize what is going on as time passes. The screen is very busy but everything has the same goal - to show the data bytes across the list of all frames with that ID.</p>
|
||||
</div>
|
||||
<div class="section" id="selecting-an-id-to-flow">
|
||||
<h1>37. Selecting an ID to Flow</h1>
|
||||
<p>Along the left most side of the window is a list of every frame ID captured so far. You can select any of these IDs. Once you select an ID you’ll see the number of frames with that ID just to the right of the IDs list (in the bottom of the Playback Control box).</p>
|
||||
</div>
|
||||
<div class="section" id="viewing-the-flow">
|
||||
<h1>38. Viewing the Flow</h1>
|
||||
<p>There are a variety of ways that the data bytes for the current frame are displayed. Toward the middle of the window there are 8 text boxes that show the current value of each data byte. At the right of the window are two visualizations. On the top there is an 8x8 grid that visualizes every possible bit in a CAN message. Bits which are white are currently 0 or off. Bits which are black are 1 or on. Bits that are green are newly on in this frame. They were 0/off in the reference. Bits which are red are newly off in this frame. Those bits were 1/on in the reference.</p>
|
||||
<p>Below the 8x8 grid view is a graph based view. The “+” in the center marks the “current” time. You can thus see both into the future and the past (relative to the current frame) in this view. All 8 data bytes are graphed according to their value. You can thus see in the graph what the value is doing as time ticks by.</p>
|
||||
<p>Between these three views you should have a very good idea of how the data bytes change over time.</p>
|
||||
</div>
|
||||
<div class="section" id="controlling-the-flow">
|
||||
<h1>39. Controlling the Flow</h1>
|
||||
<p>The screen has many ways to control the flow. The “Playback Control” box has 6 icons in a 3x2 grid. The upper icons mean (from left to right): Go back one frame, Stop, Go forward one frame. The bottom icons mean: Play backward, pause, Play forward. Playback speed is used to control how quickly the flow happens. You can raise and lower this value even as the flow is playing back and it will change the flow speed in realtime. You can select “Loop Playback” to keep looping over and over. It is possible to select “0” as the frame interval. This will make the program progress as quickly as it can. This probably will not be much faster than a frame interval of 1 but it will be a bit faster.</p>
|
||||
</div>
|
||||
<div class="section" id="additional-control-options">
|
||||
<h1>40. Additional control options</h1>
|
||||
<p>Below the “Playback Control” box is a set of three check boxes. “Live Mode” will cause the flow view to keep the current frame at the latest received frame from the capture hardware. This is thus a “real time” option where the flow will happen in real time as frames come in. “Synchronize windows” causes the flowing in this window to update the position in the main window so that the main window is always at the current position in flow playback. “Graph by timestamp” causes the “Time Axis” at the bottom of the graph view to be in seconds or microseconds. Deselecting this will cause that axis to instead be in frame number.</p>
|
||||
</div>
|
||||
<div class="section" id="reference-values">
|
||||
<h1>41. Reference Values</h1>
|
||||
<p>Previously it was mentioned that bits in the 8x8 grid are colored based on whether they are
|
||||
newly on/off or steady state compared to the reference. What is the reference? Well, look to the left of the 8x8 grid. The reference values here are used for reference with the 8x8 grid. You can set these values to something static and then see how bits compare to that static reference. Or, above the reference values is a checkbox called “Auto Reference” if this is clicked then the last frame is used as a reference for the current one. There are advantages to both options. A static reference can be used to see how things change compared to, say, the very first frame. Auto reference shows how the data changed from frame to frame. Either might yield interesting findings. The default values for static reference are taken from the values that the first frame has. You are free to change them to any value 0 through 0xFF.</p>
|
||||
</div>
|
||||
<div class="section" id="seeking-to-specific-values">
|
||||
<h1>42. Seeking to Specific Values</h1>
|
||||
<p>Lastly, it is possible to seek to specific values. “Data Seek Values” normally default to -1. But, if a value other than -1 is found in one of these boxes then it will be used as a seek value. Any time the flow is playing back (either forward or backward) it will compare to the seek values. If there is a match then playback will automatically stop. This can be used to quickly play things back until you get to a specific value. One use would be to seek to a known value that precedes an important section you want to analyze in more depth. Also, in this same vein, if you click in the 8x8 grid of bits you will see that the cell you clicked on has the text turn blue and become bold. Any bit that you’ve selected this way will also act as a way to stop playback. If that bit changes, either being set or unset, then playback will stop. Click the cell again to deselect it.</p>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,37 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
|
||||
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title>53. Frame Details Window</title>
|
||||
<link rel="stylesheet" href="_static/nonav.css" type="text/css" />
|
||||
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
|
||||
</head><body>
|
||||
|
||||
<div class="document">
|
||||
<div class="documentwrapper">
|
||||
<div class="body" role="main">
|
||||
|
||||
<div class="section" id="frame-details-window">
|
||||
<h1>53. Frame Details Window</h1>
|
||||
<p>.</p>
|
||||
<img alt="_images/FrameInfoWindow.png" src="_images/FrameInfoWindow.png" />
|
||||
</div>
|
||||
<div class="section" id="the-purpose-of-frame-details-window">
|
||||
<h1>54. The Purpose of Frame Details Window</h1>
|
||||
<p>This window is used to get detailed statistics about frames.</p>
|
||||
<p>It provides information about a given frame ID across all frames with that ID. You can get such information as the number of frames, the number of data bytes that frame ID has, the average interval between frames with that ID, and the minimum and maximum interval.</p>
|
||||
<p>Also listed are detailed statistics for each data byte in that frame. Each byte has listed which bits changed, the range of values found, and a histogram both graphically (at the righthand side of the window) and textually. The textual representation shows the number of times a specific value occurred.</p>
|
||||
<p>The top right graph is a histogram of all the bits and the number of times each bit was set. This can be used to quickly visually see where data has changed.</p>
|
||||
<p>The bottom right graph is of each individual byte as its value varies over time. All textual information can be saved to a text file for later analysis.</p>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,62 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
|
||||
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title>58. Fuzzing Window</title>
|
||||
<link rel="stylesheet" href="_static/nonav.css" type="text/css" />
|
||||
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
|
||||
</head><body>
|
||||
|
||||
<div class="document">
|
||||
<div class="documentwrapper">
|
||||
<div class="body" role="main">
|
||||
|
||||
<div class="section" id="fuzzing-window">
|
||||
<h1>58. Fuzzing Window</h1>
|
||||
<p>.</p>
|
||||
<img alt="_images/FuzzingWindow.png" src="_images/FuzzingWindow.png" />
|
||||
</div>
|
||||
<div class="section" id="the-purpose-of-fuzzing">
|
||||
<h1>59. The Purpose of Fuzzing</h1>
|
||||
<p>Some people are big fans of fuzzing, some people have no use for it. There usually isn’t much in between. So, what is fuzzing and why would you want to do it? Fuzzing is intentionally sending random information to see what happens. It’s pretty much the “shotgun” solution - you fire birdshot into the air and see if it hits anything. Does that sound a bit dangerous? It kind of is. So, why do it? It might help you to find frame IDs that control things. It might help you to find a data byte that controls something. Used carefully it can be used to figure out how different values affect things.</p>
|
||||
</div>
|
||||
<div class="section" id="fuzzing-is-dangerous">
|
||||
<h1>60. Fuzzing is Dangerous</h1>
|
||||
<p>You have now been warned. Sending random garbage over the CAN bus to see what happens could mess something up. It just might put your vehicle into gear and cause you to drive over a box full of kittens. Be careful! Never fuzz a car unless you’re right there and you can stop it. Even better, don’t do it unless your car is up on a hoist or something and can’t drive over anyone. Even still, there is a small chance you could cause an adverse effect to your car.</p>
|
||||
</div>
|
||||
<div class="section" id="controlling-the-fuzzy-beast">
|
||||
<h1>61. Controlling the Fuzzy Beast</h1>
|
||||
<p>So, you want to give it a try? Let’s do it! First of all, you can set the delay between frames and the burst rate. The burst rate can cause the program to send one than one frame each interval. This is useful as a CAN bus could potentially support 2000 to 8000 frames per second. Then you can set the number of bytes to send. Ordinarily this would be the full 8 but you can experiment with smaller frames. You can set to send on a specific bus. That’s all the simple settings. It gets a bit more complicated now.</p>
|
||||
<p>The “ID Scanning” box has two radio buttons:</p>
|
||||
<ol class="arabic simple">
|
||||
<li>Sequential will go from “Start ID” to “End ID” then reset back to Start over and over.</li>
|
||||
<li>Random will pick IDs at random in the range between Start and End</li>
|
||||
</ol>
|
||||
<p>But, perhaps you don’t want to fuzz IDs like that? The next box is “ID Selection” and the choices are:</p>
|
||||
<ol class="arabic simple">
|
||||
<li>Range of IDs - this uses the aforementioned Start and End IDs</li>
|
||||
<li>Filter list - This causes it to pick frame IDs from the list below either sequentially or randomly according to the radio boxes under “ID Scanning”</li>
|
||||
</ol>
|
||||
<p>The last box is “Bit Scanning”</p>
|
||||
<ol class="arabic simple">
|
||||
<li>Sequential causes it to scan bits in logically sequential order. That is, the first available fuzzing bit is set then the just the second, then the first two, etc. This causes all of the fuzzed bits to sequentially set in order.</li>
|
||||
<li>Sweep causes the system to set the first one, then unset that one and set the second bit, then unset that, etc. Thus the fuzzed bit sweeps and only one fuzzed bit is set at once.</li>
|
||||
<li>Random will randomly pick whether each fuzzed bit is set or not.</li>
|
||||
</ol>
|
||||
<p>In order to fuzz bits you need to set which bits to fuzz and which not to. As listed at the bottom of the window, there is a color code to the 8x8 grid. Clicking cells in the grid will toggle them between their various values. White bits are never set, black bits are always set no matter what, green bits follow the fuzzing pattern you specified in “Bit Scanning”</p>
|
||||
</div>
|
||||
<div class="section" id="pulling-the-trigger">
|
||||
<h1>62. Pulling the Trigger</h1>
|
||||
<p>Once you’ve configured everything click “Start Fuzzing” to give it a shot. You will see the number of frames sent so far listed below the button. You can stop the fuzzing by pushing the button again.</p>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,31 @@
|
||||
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
|
||||
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title>Index</title>
|
||||
<link rel="stylesheet" href="_static/nonav.css" type="text/css" />
|
||||
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
|
||||
</head><body>
|
||||
|
||||
<div class="document">
|
||||
<div class="documentwrapper">
|
||||
<div class="body" role="main">
|
||||
|
||||
|
||||
<h1 id="index">Index</h1>
|
||||
|
||||
<div class="genindex-jumpbox">
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,52 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
|
||||
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title>31. Graph Setup</title>
|
||||
<link rel="stylesheet" href="_static/nonav.css" type="text/css" />
|
||||
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
|
||||
</head><body>
|
||||
|
||||
<div class="document">
|
||||
<div class="documentwrapper">
|
||||
<div class="body" role="main">
|
||||
|
||||
<div class="section" id="graph-setup">
|
||||
<h1>31. Graph Setup</h1>
|
||||
<p>.</p>
|
||||
<img alt="_images/GraphSetup.png" src="_images/GraphSetup.png" />
|
||||
</div>
|
||||
<div class="section" id="setting-up-a-graph">
|
||||
<h1>32. Setting up a Graph</h1>
|
||||
<p>This same screen is used whether you are creating a new graph or editing an existing graph. If you are creating a new graph then you have two options.</p>
|
||||
</div>
|
||||
<div class="section" id="graphing-a-dbc-signal">
|
||||
<h1>33. Graphing a DBC Signal</h1>
|
||||
<p>If you want to graph a signal from a DBC file that you have loaded then pick the Message from the combo box. Picking a message will then allow you to pick a signal within that message. After selecting both the message and the signal then click the “Copy Signal Parameters” button. This will fill out the left hand side with the proper values for you automatically.</p>
|
||||
</div>
|
||||
<div class="section" id="manual-signal-graphing-or-editing">
|
||||
<h1>34. Manual Signal Graphing (Or Editing)</h1>
|
||||
<p>If you want to manually create a signal or edit an existing signal then you will find the relevant fields on the left hand size of the window.</p>
|
||||
<p>“Name” is for your reference and will be shown on the Graphing Window.</p>
|
||||
<p>“ID” is the frame ID to use for this graph. You can enter in either decimal or hex. Hex values are preceded by 0x</p>
|
||||
<p>“Data Len” is used to specify how many bits are in the signal to graph. You can graph odd bit lengths such as 9, 11, or 15 bits. These are not as uncommon as you might think.</p>
|
||||
<p>“Little Endian” switches between little and big endian mode. This will change how the signal grabs bits from the frame.</p>
|
||||
<p>Once you’ve set both Data Len and Little Endian you can set the bits to use. The 8x8 grid above is used for this. Click on the start bit and the rest of the bits will automatically be selected for you based on Data Len and Little Endian.</p>
|
||||
<p>“Signed” can be used to select between signed and unsigned mode.</p>
|
||||
<p>“Mask” will apply a mask to the value before using it.</p>
|
||||
<p>“Bias” allows for setting an offset to the values to bring them to a different resting point</p>
|
||||
<p>“Scale” is used as a multiplier to change the scaling of the values</p>
|
||||
<p>“Stride” is not often used but will cause only every “x” values to actually be graphed. This can be used to graph a very dense set of data with less points to speed things up.</p>
|
||||
<p>“Color” changes the color of the graphed line. It is automatically randomly set for new graphs but if you don’t like the random color you can click the color and select a better one.</p>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,61 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
|
||||
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title>22. Graphing Window</title>
|
||||
<link rel="stylesheet" href="_static/nonav.css" type="text/css" />
|
||||
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
|
||||
</head><body>
|
||||
|
||||
<div class="document">
|
||||
<div class="documentwrapper">
|
||||
<div class="body" role="main">
|
||||
|
||||
<div class="section" id="graphing-window">
|
||||
<h1>22. Graphing Window</h1>
|
||||
<p>.</p>
|
||||
<img alt="_images/GraphingView.png" src="_images/GraphingView.png" />
|
||||
</div>
|
||||
<div class="section" id="creating-a-new-graph">
|
||||
<h1>23. Creating a new Graph</h1>
|
||||
<p>Right click in the graph window to bring up a popup menu. The last entry in the first section is “Add new graph”. This brings up the Graph Setup Window. From there you can create a new graph.</p>
|
||||
</div>
|
||||
<div class="section" id="selecting-a-graph">
|
||||
<h1>24. Selecting a Graph</h1>
|
||||
<p>You can select a graph either by clicking on it or by clicking its name in the list of graphs up that the top right.</p>
|
||||
</div>
|
||||
<div class="section" id="editing-a-graph">
|
||||
<h1>25. Editing a Graph</h1>
|
||||
<p>You can either double click its name in the list of graphs at the top right or select the graph, right click, and select “Edit Selected Graph”</p>
|
||||
</div>
|
||||
<div class="section" id="deleting-graphs">
|
||||
<h1>26. Deleting Graphs</h1>
|
||||
<p>There are two options. You can select a graph, right click, and select “Remove selected graph” or you can right click and select “Remove All Graphs”</p>
|
||||
</div>
|
||||
<div class="section" id="moving-around">
|
||||
<h1>27. Moving Around</h1>
|
||||
<p>Left clicking and dragging in the graphing area will allow you to pan around. The mouse wheel (if you have one) will let you zoom in and out. If you select the numbers on either the X or Y axis then you’ll be able to pan and zoom on just that axis leaving the other alone. This is useful in order to expand or shrink the time axis or to rescale the vertical axis to better fit the data in view. The “+” key will zoom in, the “-” key zooms out. You can also zoom in and out from the pop up window that appears when you right click. If you’ve messed up your view and can’t figure out how to fix it then right click and select “Reset View” to get a nice view of all the data again.</p>
|
||||
</div>
|
||||
<div class="section" id="loading-and-saving-graphs">
|
||||
<h1>28. Loading and Saving Graphs</h1>
|
||||
<p>It can be beneficial to create a set of graphs that can be used over and over. You can save the currently setup graphs to a file and then load it later. Right click on the graphing window and use “Save graph defintions to file” and “Load graph definitions from file” to do this. You can also save a picture of the graphing window. PDF, PNG, and JPG are supported. Lastly, you can save a spreadsheet of all the graphed points.</p>
|
||||
</div>
|
||||
<div class="section" id="real-time-graphing">
|
||||
<h1>29. Real Time Graphing</h1>
|
||||
<p>Sometimes it is useful to graph while data are still being captured. When this is done it is additionally handy if the view follows the new data. In the right click pop up menu you will find “Follow end of graph”. It is a checkbox. When it is on the current view will follow the end of the data captured. The “window” you’ve set up will scroll. That is, the zoom and vertical calibration will stay the same and only the “Time Axis” will scroll such that the end of the capture is always at the right hand side of the graphing window.</p>
|
||||
</div>
|
||||
<div class="section" id="hidden-tricks">
|
||||
<h1>30. Hidden Tricks</h1>
|
||||
<p>If you double click on a graphed line the main window will scroll and the frame that produced the nearest data point to where you clicked will be selected. Additionally, if you selected “Synchronize Windows” in Flow view then the graphing view will synchronize to Flow view as well.</p>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,121 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
|
||||
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title>Welcome to SavvyCAN V189 documentation!</title>
|
||||
<link rel="stylesheet" href="_static/nonav.css" type="text/css" />
|
||||
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
|
||||
</head><body>
|
||||
|
||||
<div class="document">
|
||||
<div class="documentwrapper">
|
||||
<div class="body" role="main">
|
||||
|
||||
<div class="section" id="welcome-to-savvycan-v189-documentation">
|
||||
<h1>Welcome to SavvyCAN V189 documentation!</h1>
|
||||
<p>Contents:</p>
|
||||
<div class="toctree-wrapper compound">
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="reference internal" href="mainscreen.html">1. Main / Start Up Screen</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="mainscreen.html#the-main-frame-list">2. The Main Frame List</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="mainscreen.html#the-bottom-statusbar">3. The Bottom Statusbar</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="mainscreen.html#the-rest-of-the-main-window">4. The Rest of the Main Window</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="mainscreen.html#loading-and-saving-frames">5. Loading And Saving Frames</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="mainscreen.html#filters">6. Filters</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="mainscreen.html#what-is-dbc-and-why-would-i-care">7. What is DBC and why would I care?!</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="mainscreen.html#how-dbc-interacts-with-the-main-screen">8. How DBC interacts with the main screen:</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="connectionwindow.html">9. Connection Window</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="connectionwindow.html#connecting-to-a-dongle">10. Connecting To A Dongle</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="connectionwindow.html#debugging-connection-problems">11. Debugging Connection Problems</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="preferences.html">12. Preference Window</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="preferences.html#setting-preferences">13. Setting Preferences</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="dbc_manager.html">14. DBC File Manager</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="dbc_manager.html#working-with-dbc-files">15. Working with DBC Files</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="dbc_manager.html#dbc-file-ordering">16. DBC File Ordering</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="dbc_editor.html">17. DBC Message Editor</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="dbc_editor.html#working-with-nodes">18. Working with Nodes</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="dbc_editor.html#working-with-messages">19. Working with Messages</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="signaleditor.html">20. DBC Signal Editor</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="signaleditor.html#defining-and-editing-signals">21. Defining and Editing Signals</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="graphwindow.html">22. Graphing Window</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="graphwindow.html#creating-a-new-graph">23. Creating a new Graph</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="graphwindow.html#selecting-a-graph">24. Selecting a Graph</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="graphwindow.html#editing-a-graph">25. Editing a Graph</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="graphwindow.html#deleting-graphs">26. Deleting Graphs</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="graphwindow.html#moving-around">27. Moving Around</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="graphwindow.html#loading-and-saving-graphs">28. Loading and Saving Graphs</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="graphwindow.html#real-time-graphing">29. Real Time Graphing</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="graphwindow.html#hidden-tricks">30. Hidden Tricks</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="graphsetup.html">31. Graph Setup</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="graphsetup.html#setting-up-a-graph">32. Setting up a Graph</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="graphsetup.html#graphing-a-dbc-signal">33. Graphing a DBC Signal</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="graphsetup.html#manual-signal-graphing-or-editing">34. Manual Signal Graphing (Or Editing)</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="flowview.html">35. Flow View Window</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="flowview.html#the-purpose-of-flow-view">36. The Purpose of Flow View</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="flowview.html#selecting-an-id-to-flow">37. Selecting an ID to Flow</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="flowview.html#viewing-the-flow">38. Viewing the Flow</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="flowview.html#controlling-the-flow">39. Controlling the Flow</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="flowview.html#additional-control-options">40. Additional control options</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="flowview.html#reference-values">41. Reference Values</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="flowview.html#seeking-to-specific-values">42. Seeking to Specific Values</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="bisector.html">43. Bisector Window</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="bisector.html#using-the-bisector-window">44. Using the Bisector Window</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="sniffer.html">45. Sniffer Window</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="sniffer.html#using-the-sniffer-window">46. Using the Sniffer Window</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="sniffer.html#notching-and-unnotching">47. Notching and Unnotching</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="sniffer.html#advanced-options">48. Advanced Options</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="sniffer.html#never-expire-ids">49. Never Expire IDs</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="sniffer.html#mute-notched-bits">50. Mute notched bits</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="sniffer.html#fade-inactive-bytes">51. Fade inactive bytes</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="sniffer.html#view-bits">52. View Bits</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="framedetails.html">53. Frame Details Window</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="framedetails.html#the-purpose-of-frame-details-window">54. The Purpose of Frame Details Window</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="filecomparison.html">55. File Comparison Window</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="filecomparison.html#the-purpose-of-the-file-comparator">56. The Purpose of the File Comparator</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="filecomparison.html#the-layout-of-the-differences-list">57. The layout of the differences list</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="fuzzingwindow.html">58. Fuzzing Window</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="fuzzingwindow.html#the-purpose-of-fuzzing">59. The Purpose of Fuzzing</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="fuzzingwindow.html#fuzzing-is-dangerous">60. Fuzzing is Dangerous</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="fuzzingwindow.html#controlling-the-fuzzy-beast">61. Controlling the Fuzzy Beast</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="fuzzingwindow.html#pulling-the-trigger">62. Pulling the Trigger</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="isotp_decoder.html">63. ISO-TP Decoder</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="isotp_decoder.html#using-the-iso-tp-decoder">64. Using the ISO-TP Decoder</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="rangestate.html">65. Range State Window</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="rangestate.html#using-the-range-state-window">66. Using the Range State Window</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="uds_scanner.html">67. UDS Scan Window</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="uds_scanner.html#purpose-of-the-uds-scan-window">68. Purpose of the UDS Scan Window</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="uds_scanner.html#using-the-uds-scan-window">69. Using the UDS Scan Window</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="playbackwindow.html">70. Playback Window</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="playbackwindow.html#preparing-frames-for-playback">71. Preparing Frames for Playback</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="playbackwindow.html#playing-back-frames">72. Playing Back Frames</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="playbackwindow.html#playback-status">73. Playback Status</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="customsender.html">74. Custom Sender Window</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="customsender.html#general-overview">75. General Overview</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="customsender.html#layout-of-the-view">76. Layout of the View</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="customsender.html#writing-trigger-rules">77. Writing Trigger Rules</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="customsender.html#writing-modifications">78. Writing Modifications</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="scriptingwindow.html">79. Scripting Interface</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="scriptingwindow.html#purpose-of-the-scripting-interface">80. Purpose of the Scripting Interface</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="scriptingwindow.html#managing-scripts">81. Managing Scripts</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="scriptingwindow.html#getting-script-status">82. Getting Script Status</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="scriptingwindow.html#writing-scripts">83. Writing Scripts</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="scriptingwindow.html#callback-functions">84. Callback Functions</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="scriptingwindow.html#the-host-object">85. The host Object</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="scriptingwindow.html#the-can-object">86. The can Object</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="scriptingwindow.html#the-isotp-object">87. The isotp Object</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="scriptingwindow.html#the-uds-object">88. The uds Object</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="scriptingwindow.html#a-full-example-script">89. A full example script</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,37 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
|
||||
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title>63. ISO-TP Decoder</title>
|
||||
<link rel="stylesheet" href="_static/nonav.css" type="text/css" />
|
||||
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
|
||||
</head><body>
|
||||
|
||||
<div class="document">
|
||||
<div class="documentwrapper">
|
||||
<div class="body" role="main">
|
||||
|
||||
<div class="section" id="iso-tp-decoder">
|
||||
<h1>63. ISO-TP Decoder</h1>
|
||||
<p>.</p>
|
||||
<img alt="_images/ISOTPDecoder.png" src="_images/ISOTPDecoder.png" />
|
||||
</div>
|
||||
<div class="section" id="using-the-iso-tp-decoder">
|
||||
<h1>64. Using the ISO-TP Decoder</h1>
|
||||
<p>This window scans the existing captured frames and newly captured frames to see if it can find CAN traffic that seems to conform to the ISO-TP standard. ISO-TP is used to send multi-frame messages and as an encoding standard that forms the base for other protocols such as UDS and ODBII (which is essentially itself a subset of UDS).</p>
|
||||
<p>The main list at the top shows any messages that seem to conform to ISO-TP. There will very likely be messages here which aren’t really ISO-TP. You can deselect IDs that seem to generate false positives so that they quit showing up in this list. As you can see in the picture only the ids 0x7E0 through 0x7EA were selected. These IDs are standard for UDS communication. If you want to immediately recalculate the results to exclude the deselected IDs then push “Interpret Previously Captured Frames” to regenerate the whole list. Otherwise the effect of changing the ID selections will only happen for newly captured frames.</p>
|
||||
<p>The “Show incomplete and/or corrupted messages” checkbox will cause a lot of false positives and should only be used as a last resort if you suspect that you might have some dropped traffic.</p>
|
||||
<p>“Use extended addressing” will cause the decoder to assume that extended addressing is being used on this CAN bus. Extended addressing adds an additional byte of addressing that is found in the data bytes of the frame. This isn’t that commonly used but is used on some vehicles and ISO-TP decoding won’t work properly unless this setting is correct. If you find that decoding seems to have failed you might try toggling this setting to see if it helps. Remember to click “Interpret Previously Captured Frames” to recalculate things for previously captured traffic.</p>
|
||||
<p>Once you have messages in the table at the top of the window you can click on a message to get more details about it in the text box in the lower left. In the picture you can see that 0x7F 0x10 0x12 was interpreted as a UDS error response saying that the ECU does not support the requested sub-function passed to the diagnostic session control service. This is much easier than trying to remember what all those bytes mean off the top of your head!</p>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,125 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
|
||||
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title>1. Main / Start Up Screen</title>
|
||||
<link rel="stylesheet" href="_static/nonav.css" type="text/css" />
|
||||
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
|
||||
</head><body>
|
||||
|
||||
<div class="document">
|
||||
<div class="documentwrapper">
|
||||
<div class="body" role="main">
|
||||
|
||||
<div class="section" id="main-start-up-screen">
|
||||
<h1>1. Main / Start Up Screen</h1>
|
||||
<img alt="_images/MainScreen.png" src="_images/MainScreen.png" />
|
||||
<p>This screen embodies the core of the program. Here you will find the master list of all frames. Also, here you can navigate to the other aspects
|
||||
of the program. You can have multiple sub-windows open at once - in fact, quite often this is very beneficial.</p>
|
||||
</div>
|
||||
<div class="section" id="the-main-frame-list">
|
||||
<h1>2. The Main Frame List</h1>
|
||||
<p>The main frame list takes up the majority of the main screen. This list consists of the following sections:</p>
|
||||
<ul class="simple">
|
||||
<li>Timestamp: The timestamp is either in microseconds or seconds. This is a setting in preferences. Either way, the timestamp can have
|
||||
microsecond resolution. The difference is just whether there is a decimal point or not. GVRET has the ability to maintain full microsecond
|
||||
resolution for timestamping purposes. There is a third timing mode where the timestamp can be customized and is based upon the actual “clock” time.</li>
|
||||
<li>ID: The ID is specified either in hexadecimal or decimal (a preference you can set). This is the message identifier sent over the CAN bus.</li>
|
||||
<li>Ext: 0 = Standard message (11 bit ID). 1 = Extended message (29 bit ID)</li>
|
||||
<li>Dir: Either “Rx” or “Tx” to show whether SavvyCAN has received or sent this message.</li>
|
||||
<li>Bus: SavvyCAN supports a variety of capture hardware. GVRET compatible devices can support more than one bus. The bus a frame came in on
|
||||
is specified here. Many file formats do not specify bus and thus all frames will be loaded as bus 0.</li>
|
||||
<li>Len: The number of data bytes that were sent with this frame. It can range from 0 to 8.</li>
|
||||
<li>Data: All of the data bytes separated by spaces. Can be in either hexadecimal or decimal (preference). If “Interpet Frames” is checked you will
|
||||
also see extra data at the end of any frames that have DBC data. To see the rest of this data click upon the frame in the list. It will
|
||||
automatically expand to show all signals attached to that frame.</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="the-bottom-statusbar">
|
||||
<h1>3. The Bottom Statusbar</h1>
|
||||
<p>At the very bottom of the main screen is a status bar with two sections.</p>
|
||||
<ul class="simple">
|
||||
<li>The first section shows the connection status. You will see the number of currently connected buses here.</li>
|
||||
<li>The second second section shows which file is currently loaded. This is updated by loading or saving.</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="the-rest-of-the-main-window">
|
||||
<h1>4. The Rest of the Main Window</h1>
|
||||
<p>To the right of the main frames list is an area that shows the total number of captured frames and the frames per second. Total frames might not match
|
||||
the number of shown frames. If you’ve deselected any IDs in the filter list then fewer frames will be shown. Frames per second is calculated as
|
||||
an average and so will wind up or down when there is a sudden change.</p>
|
||||
<p>Suspend Capturing / Resume Capturing is a button that will temporarily disable frame capture or re-enable it. This can be used to keep everything
|
||||
connected without capturing traffic for a short time. This can help to not capture traffic in between tests.</p>
|
||||
<p>The “Normalize Frame Timing” button is used to reset the lowest timestamp to “0” and offset all other timestamps accordingly. This is useful to
|
||||
remove the starting offset when you start up the GVRET board long before actual traffic starts. SavvyCAN is designed such that this doesn’t really matter
|
||||
most of the time but normalizing the timing might be useful to help correlate the timing between two different captures.</p>
|
||||
<p>The “Clear Frames” button will erase all captured messages. They will be irreversably erased and all memory will be freed.</p>
|
||||
<p>The “Auto Scroll Window” checkbox will cause the main frame list to hunt toward the bottom of the list as frames come in. It will normally not be quite
|
||||
at the very bottom as, for performance reasons, the program runs at quarter second updates to things like the auto scroll. Thus, the main list will be
|
||||
scrolled to the bottom four times per second.</p>
|
||||
<p>The “Interpret Frames” checkbox is used to specify whether the loaded DBC file should be used to interpret all available messages and signals. One might want
|
||||
this off for performance reasons (interpreting takes some extra processor power and RAM) or to declutter the main frame list.</p>
|
||||
<p>The “Overwrite Mode” checkbox is used to ensure that only the newest frame for each message ID is shown. That is, if 100 messages with ID 0x105 come in you
|
||||
will see only the newest one. This is generally used along side “Interpret Frames” to interpret frames and always see the up-to-date information.</p>
|
||||
<p>“Frame Filtering” provides a list of all the frame IDs seen so far. Any ID which is checked will be shown in the main list. Any ID which is unchecked will not.
|
||||
This can be used to hone in on frames of importance while hiding frames that are currently of no interest. The filtered list can be saved as well.</p>
|
||||
</div>
|
||||
<div class="section" id="loading-and-saving-frames">
|
||||
<h1>5. Loading And Saving Frames</h1>
|
||||
<p>What CANBus analysis tool would be complete without an easy way to load and save frames?</p>
|
||||
<dl class="docutils">
|
||||
<dt>SavvyCAN can load and save in several formats (a few of which are listed below):</dt>
|
||||
<dd><ul class="first last simple">
|
||||
<li>CRTD: This format was made by Mark Webb-Johnson for OVMS (open vehicle monitoring system) and other related tools. It is a reasonably
|
||||
readable and compact format.</li>
|
||||
<li>GVRET: This is the native format for GVRET and SavvyCAN. The GVRET format saves more information such as the bus a frame originated on. This format is in CSV
|
||||
(comma delimited) format and as such can easily be loaded into your favorite spreadsheet program as well.</li>
|
||||
<li>Generic ID/DATA - Another CSV format. This is a very cut down format with limited information.</li>
|
||||
<li>BusMaster - This is the format output by the BusMaster CANBus program. BusMaster is an open source Windows-only somewhat clone
|
||||
of CANAlyzer (the 800lb gorilla in the analysis space). The ability to load and save in this format makes SavvyCAN fully capable
|
||||
of swapping data with BusMaster should you need to do so.</li>
|
||||
<li>Microchip - Format output by Microchip CANBus tools. Perhaps you have logs that were captured with a $100 Microchip dongle? You can load them in SavvyCAN.</li>
|
||||
</ul>
|
||||
</dd>
|
||||
</dl>
|
||||
<p>There are many other formats supported. Some are only supported for writing, some only for reading. The list of supported formats is expanded every so often.</p>
|
||||
</div>
|
||||
<div class="section" id="filters">
|
||||
<h1>6. Filters</h1>
|
||||
<p>You might notice that there are three entries in the file menu that mention filters. SavvyCAN can filter messages so that you only see some
|
||||
of the messages coming in on the bus. It still saves all incoming messages but you are able to filter which you will view at any given time.
|
||||
SavvyCAN allows for loading and saving the list of frames you’d like to view so that you can easily switch “sets” of frames to view. Also,
|
||||
when saving you can optionally save just the frames that you have filtered instead of every captured frame. Filters are set in the lower
|
||||
righthand of the this screen. All IDs are selected by default. To deselect an ID click on the checkbox next to it. You can also deselect
|
||||
all IDs or select all IDs. These are useful if you only want to view a couple of IDs (click None then the few you need) or you just want
|
||||
to remove a couple (click All and then deselect the ones you don’t care about).</p>
|
||||
</div>
|
||||
<div class="section" id="what-is-dbc-and-why-would-i-care">
|
||||
<h1>7. What is DBC and why would I care?!</h1>
|
||||
<p>I’m glad you asked. DBC is a file format used to specify how “signals” are stored in “messages.” A message is essentially a unique
|
||||
packet of data sent on the CAN bus. Ordinarily this message is differentiated by frame ID. Each ID is a different message (usually).
|
||||
A signal is a piece of data stored in a message. For instance, ID 0x105 might be a message from the vehicle control unit to the motor
|
||||
inverter. Within that message bytes 0 and 1 might encode the desired torque. That would be a signal. A DBC file allows these relationships
|
||||
to be specified and named. It also allows for scaling of values stored in a signal. Additionally, a signal can have values associated with
|
||||
textual output. For instance, if a signal encodes the current gear then a DBC file can define that a value of 0 means “Park” and a value
|
||||
of 1 means “Drive”. This makes analysis a lot easier since you do not need to remember the mapping yourself. In this way data can be better
|
||||
understood by users of the program. Also, other windows can use the DBC file for such things as being able to graph a signal without having
|
||||
to figure out the actual details of that signal.</p>
|
||||
</div>
|
||||
<div class="section" id="how-dbc-interacts-with-the-main-screen">
|
||||
<h1>8. How DBC interacts with the main screen:</h1>
|
||||
<p>First of all, one can load and save DBC files from the “DBC File Manager” found in the File menu. Also in the File menu it is possible to save the currently
|
||||
loaded frames but with DBC decoding. This is somewhat like the normal saving functionality with a two differences: there is only one output format
|
||||
and that format has all signals contained in each message listed and decoded.</p>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,53 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
|
||||
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title>70. Playback Window</title>
|
||||
<link rel="stylesheet" href="_static/nonav.css" type="text/css" />
|
||||
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
|
||||
</head><body>
|
||||
|
||||
<div class="document">
|
||||
<div class="documentwrapper">
|
||||
<div class="body" role="main">
|
||||
|
||||
<div class="section" id="playback-window">
|
||||
<h1>70. Playback Window</h1>
|
||||
<p>.</p>
|
||||
<img alt="_images/Playback.png" src="_images/Playback.png" />
|
||||
</div>
|
||||
<div class="section" id="preparing-frames-for-playback">
|
||||
<h1>71. Preparing Frames for Playback</h1>
|
||||
<p>The first order of business is to load some CAN frames that you’d like to play back onto a CAN bus. In the lower left is a section titled “Playback Sequence”. It is so named because this playback interface can play a chain of different CAN captures very configurably. It consists of a list of captures to playback along with how many times to play each sequence item. For instance, you could play a file twice then go to the next, then play a third one four times. A playback item can either come from a file (Load File) or from the current list of captured frames on the main window (Load Captured Data). If you load the currently captured frames it truly means “currently”. That is, if more traffic comes in it will not play that new traffic back. A snapshot is taken at the time you push the button. Each sequence item has its own list of ID filters. In this way you can send only some of the frame IDs from the capture and this list can be different for each file or capture you load. The list of ID filters can be saved and loaded to make the process faster in the future.</p>
|
||||
<p>Once you’ve set up a sequence of frames to playback you can also decide whether you’d like to loop that sequence forever or not. Up above the Playback Sequence and ID Filtering sections is the “Loop Sequence” checkbox.</p>
|
||||
</div>
|
||||
<div class="section" id="playing-back-frames">
|
||||
<h1>72. Playing Back Frames</h1>
|
||||
<p>The playback window can send frames on a specific bus, all buses (be careful with that!) or “From File.” Some file formats store which bus each frame came in on. Also, the main window stores that info. So, captures that stored the bus properly could be used to send frames out multiple buses always to the proper bus for the frame in question. But, if you load a capture without this info it will default to bus 0 so bear that in mind.</p>
|
||||
<p>The next order of business is frame timing. There are two approaches possible here. If you click “Use original frame timing from captured frames” then frames will be sent out in approximately the same timing as they came in with. The word approximately is used because it is difficult to get 1ms timing precision on a desktop OS. Frames that come in rapidly might have a 2-3ms jitter. In practice this is almost always irrelevant. This setting is suitable for nearly all uses.</p>
|
||||
<p>Alternatively, it is also possible to send on a set schedule. With the “Use original” checkbox not checked you can set a playback speed in milliseconds and a burst rate. Burst means that it’ll send that many frames every tick. So, if you have a burst of 5 and a timing of 10ms then every 10ms 5 frames will be sent. This mode can provide for a predictable number of frames per second and could be useful to test how quickly a device really requires traffic without faulting. But, it will potentially drastically alter the timing of frames compared to their timing when they were captured.</p>
|
||||
<p>The top of the window has a series of 6 icons all in a row:</p>
|
||||
<ol class="arabic simple">
|
||||
<li>White Left Arrow - Play the last frame (just one frame)</li>
|
||||
<li>Pause sign - Pause playback</li>
|
||||
<li>Green Left Arrow - Play frames backward</li>
|
||||
<li>Blue Stop Button - Stop playback and return to the first frame in the first capture in the sequence</li>
|
||||
<li>Green Right Arrow - Play frames forward</li>
|
||||
<li>White Right Arrow - Play the next frame (just one frame)</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div class="section" id="playback-status">
|
||||
<h1>73. Playback Status</h1>
|
||||
<p>Below the number spinners for Playback Speed and Burst Rate is text that displays the currently playing sequence item along with the current frame within that capture.</p>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,47 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
|
||||
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title>12. Preference Window</title>
|
||||
<link rel="stylesheet" href="_static/nonav.css" type="text/css" />
|
||||
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
|
||||
</head><body>
|
||||
|
||||
<div class="document">
|
||||
<div class="documentwrapper">
|
||||
<div class="body" role="main">
|
||||
|
||||
<div class="section" id="preference-window">
|
||||
<h1>12. Preference Window</h1>
|
||||
<p>.</p>
|
||||
<img alt="_images/Preferences.png" src="_images/Preferences.png" />
|
||||
</div>
|
||||
<div class="section" id="setting-preferences">
|
||||
<h1>13. Setting Preferences</h1>
|
||||
<p>There are a variety of preferences that can be set in the program and persisted for future sessions.</p>
|
||||
<p>“Save/Restore window positions and sizes”: If this is set then the size and placement of the various windows in this application will be saved when the program is closed and loaded when each window is brought back up in the future. This can be used to create your own preferred layout. If you’d rather things come up in their default state every time then you can uncheck this box.</p>
|
||||
<p>“Display values as hexadecimal”: A lot of the time people who are doing CAN reverse engineering like to see values in hexadecimal (base 16) instead of the more familar decimal (base 10) system. Checking this box will cause most of the values in the application to show up in hex. This applies to CAN ids and data bytes. Unchecking this causes values to default to decimal instead. The reason for using hex is that each hex digit is 4 bits. Integers on a computer tend to be in multiples of 8 - 8, 16, 32, 64. So, hex digits have a direct mapping to the underlying binary. Decimal does not have this correspondance AT ALL. But, the choice is yours.</p>
|
||||
<p>“Require validation of GVRET connection”: GVRET style devices run over a serial connection. Serial connections can be finicky sometimes and so the connection can be validated to prove that everything is really still operating and talking. There probably isn’t any reason to turn this off except while debugging to see if it changes anything. Mostly just don’t touch this.</p>
|
||||
<p>“Time Keeping”: There are a variety of ways one could timestamp CAN frames as they come into the program. Selecting “Seconds” will cause the timestamp to be expressed as seconds since the frame list was last cleared. This tends to be an easy choice to work with. “Microseconds” will express the timestamp as millionths of a second since the last time the frame list was cleared. This is exactly like “Seconds” mode but without any decimal point. You might find this to be a bit hard to conceptualize. The last option is “System Clock” this will timestamp frames with the current system time when the frame came in. This is still very precise but now you’ll get an absolute time stamp with the full date and time. The display of this mode can be changed by editing the “Time Format String” value. It defaults to an output that looks like “JAN-10 12:34:53.234” But you can set it to other values. Look here to find a reference for how you can create new format strings: <a class="reference external" href="http://doc.qt.io/qt-4.8/qdatetime.html#toString">http://doc.qt.io/qt-4.8/qdatetime.html#toString</a></p>
|
||||
<p>“Use filtered frames in sub-windows”: The main window has a filtering interface where you can uncheck IDs to hide them. Ordinarily when you bring up one of the other windows it will still use the main unfiltered list. Sometimes you really do want to deal with the filtered list of frames even in the other windows. If this is checked then the other windows will see the filtered list and not the unfiltered actual list of frames that have been captured.</p>
|
||||
<p>“OpenGL Accelerated AntiAliased Graphing”: A personal favorite of mine. Checking this will cause all of the graphs to use OpenGL 3D acceleration. Most modern machines have some form of 3D acceleration so this option should be OK to use. If you check this your graphs will look a lot better and on good hardware should also be faster. In the future other options are likely to be added to the graphing screen that will likely only be enabled if OpenGL mode is also enabled. Try enabling this and see if performance is still good. It’s safe to leave it off if in doubt.</p>
|
||||
<p>“Autoscroll main frame window by default”: If checked it will automatically check the relevant checkbox on the main screen when the program starts. This will cause the view to automatically scroll to the bottom by default. This is merely a convenience option if you’d prefer it to be the default.</p>
|
||||
<p>“Use timestamp mode by default”: This is another convenience option. With this checked the Flow view will default to using time stamps on the graphing area instead of frame numbers.</p>
|
||||
<p>“Set Auto Reference by default”: The Flow view can either use static referencing or dynamic referencing (see the Flow view documentation for more info). If you’d like to use dynamic referencing and automatically set the reference by default then check this option.</p>
|
||||
<p>“Loop by default”: Set the playback window to default to looping infinitely by default.</p>
|
||||
<p>“Default playback speed (ms)”: Set the default timing for playback</p>
|
||||
<p>“Default Sending Bus”: Set a default for which bus to send frames on.</p>
|
||||
<p>“Auto expand all nodes”: Both of the referenced windows have tree views that potentially have a large number of nodes. It’s more neat not to expand them all by default but it also then requires more clicks if you have to expand them to view the information. So, you can set whether you’d like to expand them all by default or not.</p>
|
||||
<p>After changing preferences it is the best practice to close and reopen the application to ensure that all settings have taken effect.</p>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,44 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
|
||||
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title>65. Range State Window</title>
|
||||
<link rel="stylesheet" href="_static/nonav.css" type="text/css" />
|
||||
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
|
||||
</head><body>
|
||||
|
||||
<div class="document">
|
||||
<div class="documentwrapper">
|
||||
<div class="body" role="main">
|
||||
|
||||
<div class="section" id="range-state-window">
|
||||
<h1>65. Range State Window</h1>
|
||||
<p>.</p>
|
||||
<img alt="_images/RangeState.png" src="_images/RangeState.png" />
|
||||
</div>
|
||||
<div class="section" id="using-the-range-state-window">
|
||||
<h1>66. Using the Range State Window</h1>
|
||||
<p>The purpose of this window is to search for signals that look like they might be important. That’s a rather nebulous description. But, the idea is to find signals that seem to vary somewhat coherently. As you can see in the picture in this help, the displayed signal has some jumps but they look like they might “be something.” That’s the idea. It <em>will</em> generate a lot of false positives but it will still provide some decent ideas for where to look for signals. It is up to you to figure out if the signal is anything important or anything you can use.</p>
|
||||
<p>To use it first set up the follow things:</p>
|
||||
<ol class="arabic simple">
|
||||
<li>The ID Filter list - Deselect any IDs you don’t want to search.</li>
|
||||
<li>Sensitivity - This is a subjective measurement. It doesn’t affect the results a lot but changing this value back and forth can alter what the program thinks is a relevant signal.</li>
|
||||
<li>Min Signal Size - The smallest signal you want to search for. You can search for signals all of the way down to 1 bit if you’re feeling adventurous.</li>
|
||||
<li>Max Signal Size - The largest signal you want to search for. The range between Min and Max changes how long the process will take. If you set Min to 8 and Max to 16 then it will search for signals with 8, 9, 10, 11, 12, 13, 14, 15, 16 bits. You will get duplicates as the same basic set of bits will be used for all of the sizes.</li>
|
||||
<li>Granularity - Sets the bit jump width. What is bit jump width? The number of bits that we move over each time we search for a new signal. If you have a granularity of 1 and a minimum signal width of 8 then it will search starting at bit 0 for an 8 bit message then starting at bit 1 then bit 2, etc. If you set a granularity of 8 then it will search for an 8 bit signal at bit 0 then search at bit 8, then 16, etc. So this sets how finely we will search for signals. A lot of times signals will be found on byte edges so a granularity of 8 will cause all signal searches to start on byte boundaries and this will be OK for many things. But, some designers are more devious and place signals at uneven boundaries. You might need finer granularity to find such signals.</li>
|
||||
<li>Signal Mode - For signals over 8 bits there is a choice to make. Signals over 8 bits can be either in big or little endian mode. This relates to whether bit 0 of a signal is the highest or lowest value. You can search for only big endian signals, only little endian, or try it both ways. <em>Usually</em> the developer of a CAN device will stick to one or the other but not always.</li>
|
||||
<li>Signed Mode - Likewise, any signal over 1 bit could be either unsigned or signed. Signed signals have their highest bit as 1 for negative numbers and 0 for positive numbers. You can search for only unsigned signals, only signed, or try it both ways. There really isn’t any rhyme or reason for when a signal would be signed or unsigned. It could easily be both ways so unless you’re sure it’s probably safest to allow the program to try it both ways and you can pick which looks best.</li>
|
||||
</ol>
|
||||
<p>Once you’ve got it all set up click “Recalculate Candidate Signals.” Be prepared to wait depending on what options you selected. Once it is done processing you’ll get a list of candidates in the upper list labeled “Candidate Signals.” Here you can see all of the signals it found. You get the ID, the starting bit (remember, bits start at 0 and go through 63), the length, and whether it was signed/unsigned and big/little endian. If you click on or otherwise select a signal in this list then a graphical view of it will appear in the graphing area beneath. You might try the arrow keys Up and Down to move through the list. You can even hold down the arrow key and let it rapidly scroll. As it scrolls through the signals you can look at the graph and stop when you see a signal that catches your eye. This is useful as you can have hundreds of candidates and it is tedious to view them explicitly one at a time.</p>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||