Histogram implementation

This commit is contained in:
2019-11-09 11:09:36 +01:00
parent 5a7507a93e
commit f377535ed8
8 changed files with 453 additions and 437 deletions

View File

@@ -9,7 +9,8 @@
#include <sstream> #include <sstream>
#include <chrono> #include <chrono>
#include <iomanip> #include <iomanip>
#include <thread>> #include <thread>
#include <random>
#include <QStandardPaths> #include <QStandardPaths>
#include <QtGlobal> #include <QtGlobal>
@@ -23,6 +24,7 @@ const std::string NUC1 = "38:de:ad:6d:77:25";
const std::string NUC2 = "38:de:ad:6d:60:ff"; const std::string NUC2 = "38:de:ad:6d:60:ff";
const std::string NUC3 = "1c:1b:b5:ef:a2:9a"; const std::string NUC3 = "1c:1b:b5:ef:a2:9a";
const std::string NUC4 = "1c:1b:b5:ec:d1:82"; const std::string NUC4 = "1c:1b:b5:ec:d1:82";
const std::string NUC5 = "d0:c6:37:bc:5c:41";
static long long startTime = 0; static long long startTime = 0;
@@ -49,178 +51,81 @@ Manager::Manager() {
} }
void Manager::test() {
void Manager::trigger() { }
QString folder = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation) + "/ftm/"; bool Manager::trigger() {
if (isRunning) {
QString prefix = GetCurrentTimeForFileName(); stop();
} else {
dataLogger = std::make_shared<QFile>(folder+"/"+prefix+"_ftm.txt"); start();
uwbLogger = std::make_shared<QFile>(folder+"/"+prefix+"_uwb.txt");
gtLogger = std::make_shared<QFile>(folder+"/"+prefix+"_gt.txt");
if (!dataLogger->exists()) {
// create the folder, if necessary
QDir dir(folder);
if (!dir.exists()) {
qWarning("creating new folder");
if (!dir.mkpath(".")) {
qWarning() << "Failed to create new folder";
}
}
}
if (!dataLogger->open(QIODevice::ReadWrite)) {
qWarning() << "Failed to create data logger file" << dataLogger->fileName();
dataLogger = nullptr;
}
if (!uwbLogger->open(QIODevice::ReadWrite)) {
qWarning() << "Failed to create uwb data logger file" << uwbLogger->fileName();
uwbLogger = nullptr;
} }
if (!gtLogger->open(QIODevice::ReadWrite)) { return isRunning;
qWarning() << "Failed to create gt data logger file" << gtLogger->fileName(); }
gtLogger = nullptr;
}
void Manager::start() {
startTime = nowInMsec(); startTime = nowInMsec();
#ifdef ANDROID #ifdef ANDROID
//QAndroidJniObject::callStaticMethod<int>("android/net/wifi/UWB", "start", "()I");
QAndroidJniObject::callStaticMethod<int>("android/net/wifi/RTT", "start", "()I");
QAndroidJniObject::callStaticMethod<int>("android/net/wifi/UWB", "start", "()I"); isRunning = true;
QAndroidJniObject::callStaticMethod<int>("android/net/wifi/RTT", "start", "()I");
#else #else
std::random_device rd;
std::mt19937 gen{rd()};
std::normal_distribution<float> d{5000,1000};
//std::uniform_real<float> d{0, 500};
//onData("38:de:ad:6d:77:25;FAILED"); WifiRttResult res;
onData("1337;"+NUC1+";6230;1231"); res.success = 1;
onData("1337;"+NUC2+";3430;3423"); res.mac = NUC1;
onData("1337;"+NUC3+";5630;2341"); res.numAttemptedMeas = 8;
onData("1337;"+NUC4+";8830;2241"); res.numSuccessfullMeas = 8;
for(int n=0; n<1000; ++n) {
res.timeMS = nowInMsec();
res.distMM = static_cast<int>(std::round(d(gen)));
onWifiData(res);
}
#endif #endif
} }
void Manager::stop() { void Manager::stop() {
#ifdef ANDROID #ifdef ANDROID
QAndroidJniObject::callStaticMethod<int>("android/net/wifi/RTT", "stop", "()I"); QAndroidJniObject::callStaticMethod<int>("android/net/wifi/RTT", "stop", "()I");
QAndroidJniObject::callStaticMethod<int>("android/net/wifi/UWB", "stop", "()I"); //QAndroidJniObject::callStaticMethod<int>("android/net/wifi/UWB", "stop", "()I");
#endif #endif
dataLogger->flush(); isRunning = false;
dataLogger->close();
uwbLogger->flush();
uwbLogger->close();
gtLogger->flush();
gtLogger->close();
} }
int Manager::runTimeInMs() const {
return static_cast<int>(nowInMsec() - startTime);
}
void Manager::manualCheckpoint() { void Manager::manualCheckpoint() {
qDebug() << "Manual checkpoint"; qDebug() << "Manual checkpoint";
long long timestamp = nowInMsec();
timestamp -= startTime;
QTextStream out(gtLogger.get());
out << timestamp << endl;
} }
void Manager::onData(std::string str) { void Manager::onWifiData(WifiRttResult result) {
qDebug() << result;
qDebug() << QString(str.c_str()); if (result.success == 1 && result.numSuccessfullMeas > 3) {
int nucIdx = 0;
long long timestamp = nowInMsec(); if(NUC1 == result.mac) {nucIdx = 0;}
timestamp -= startTime; if(NUC2 == result.mac) {nucIdx = 1;}
if(NUC3 == result.mac) {nucIdx = 2;}
if(NUC4 == result.mac) {nucIdx = 3;}
if(NUC5 == result.mac) {nucIdx = 4;}
bool successfullMeas = true; emit newDistMeas(nucIdx, result.distMM);
std::stringstream lineStream(str); }
std::string cell;
std::string mac;
int distIndex = 0;
int i = 0;
const float alpha = 0.7f;
QTextStream out(dataLogger.get());
out << timestamp << ";";
while (std::getline(lineStream, cell, ';')) {
switch(i) {
case 0: {
// success flag
successfullMeas = (cell == "1");
out << (successfullMeas ? 1 : 0) << ";";
break;
}
case 1: {
// timestamp; ignore;
break;
}
case 2: {
if(NUC1 == cell) {distIndex = 0;}
if(NUC2 == cell) {distIndex = 1;}
if(NUC3 == cell) {distIndex = 2;}
if(NUC4 == cell) {distIndex = 3;}
out << QString(cell.c_str()) << ";";
break;
}
case 3: {
if (successfullMeas) {
_dist[distIndex] = std::stoi(cell) + _offset;
//_dist[distIndex] = _dist[distIndex] * alpha + atoi(cell.c_str()) * (1-alpha);
} else {
_dist[distIndex] = 0;
}
out << _dist[distIndex] << ";";
break;
}
case 4: {
if (successfullMeas) {
_stdDev[distIndex] = atoi(cell.c_str());
} else {
_stdDev[distIndex] = 0;
}
out << _stdDev[distIndex] << ";";
break;
}
case 5: {
// RSSI
out << cell.c_str() << ";";
break;
}
}
++i;
}
out << endl;
// if (dataLogger && successfullMeas) {
// dataLogger->write(str.c_str());
// dataLogger->write("\n");
// }
emit distChanged();
} }
void Manager::onUWBData(std::vector<uchar> data) { void Manager::onUWBData(std::vector<uchar> data) {
@@ -245,16 +150,9 @@ void Manager::onUWBData(std::vector<uchar> data) {
case 0x863B: _uwbDist[2] = static_cast<float>(dist.distance); break; case 0x863B: _uwbDist[2] = static_cast<float>(dist.distance); break;
case 0x58B4: _uwbDist[3] = static_cast<float>(dist.distance); break; case 0x58B4: _uwbDist[3] = static_cast<float>(dist.distance); break;
} }
if (uwbLogger) {
QTextStream out(uwbLogger.get());
out << timestamp << ";" << "1;" << "DW" << hex << dist.nodeID << dec << ";" << dist.distance << endl;
}
} }
emit distChanged(); emit uwbDistChanged();
} }
Manager mgmt; Manager mgmt;
@@ -262,19 +160,34 @@ Manager mgmt;
#ifdef ANDROID #ifdef ANDROID
extern "C" { extern "C" {
JNIEXPORT void JNICALL Java_android_net_wifi_RTT_onRTTComplete(JNIEnv* env, jobject jobj, jbyteArray arrayID) { JNIEXPORT void JNICALL Java_android_net_wifi_RTT_onRttData(JNIEnv* env, jobject jobj, jint success, jbyteArray macString, jlong timeMS, jint distMM, jint distStdDevMM, jint numMeas, jint numSuccessfullMeas, jint rssi) {
(void) env; (void) jobj; (void) env;
jsize length = env->GetArrayLength(arrayID); (void) jobj;
jbyte* data = env->GetByteArrayElements(arrayID, 0);
std::string str((char*)data, length); jsize length = env->GetArrayLength(macString);
env->ReleaseByteArrayElements(arrayID, data, JNI_ABORT); jbyte* data = env->GetByteArrayElements(macString, nullptr);
mgmt.onData(str); std::string str(reinterpret_cast<char*>(data), static_cast<std::string::size_type>(length));
} env->ReleaseByteArrayElements(macString, data, JNI_ABORT);
WifiRttResult result;
result.success = success;
result.mac = str;
result.timeMS = timeMS;
result.distMM = distMM;
result.distStdDevMM = distStdDevMM;
result.numAttemptedMeas = numMeas;
result.numSuccessfullMeas = numSuccessfullMeas;
result.rssi = rssi;
mgmt.onWifiData(result);
}
JNIEXPORT void JNICALL Java_android_net_wifi_UWB_onUWBComplete(JNIEnv* env, jobject jobj, jbyteArray arrayID) { JNIEXPORT void JNICALL Java_android_net_wifi_UWB_onUWBComplete(JNIEnv* env, jobject jobj, jbyteArray arrayID) {
(void) env; (void) jobj; (void) env;
(void) jobj;
jsize length = env->GetArrayLength(arrayID); jsize length = env->GetArrayLength(arrayID);
jbyte* data = env->GetByteArrayElements(arrayID, 0); jbyte* data = env->GetByteArrayElements(arrayID, nullptr);
std::vector<uchar> c_data; std::vector<uchar> c_data;
c_data.assign(reinterpret_cast<char*>(data), reinterpret_cast<char*>(data)+length); c_data.assign(reinterpret_cast<char*>(data), reinterpret_cast<char*>(data)+length);
env->ReleaseByteArrayElements(arrayID, data, JNI_ABORT); env->ReleaseByteArrayElements(arrayID, data, JNI_ABORT);

View File

@@ -1,69 +1,68 @@
#ifndef MANAGER_H #ifndef MANAGER_H
#define MANAGER_H #define MANAGER_H
#include <QDebug>
#include <QObject> #include <QObject>
#include <QFile> #include <QFile>
#include <array>
#include <ostream>
#include <map>
struct WifiRttResult {
int success;
std::string mac;
int64_t timeMS;
int distMM;
int distStdDevMM;
int numAttemptedMeas;
int numSuccessfullMeas;
int rssi;
};
static QDebug operator<<(QDebug dbg, const WifiRttResult& v)
{
dbg << v.success << ";"
<< QString::fromStdString(v.mac) << ";"
<< v.timeMS << ";"
<< v.distMM << ";"
<< v.distStdDevMM << ";"
<< v.numSuccessfullMeas << "/" << v.numAttemptedMeas << ";"
<< v.rssi;
return dbg;
}
class Manager : public QObject { class Manager : public QObject {
Q_OBJECT Q_OBJECT
private: private:
float _dist[4];
float _stdDev[4];
float _offset = 500;
float _uwbDist[4]; float _uwbDist[4];
std::shared_ptr<QFile> dataLogger; bool isRunning = false;
std::shared_ptr<QFile> uwbLogger;
std::shared_ptr<QFile> gtLogger;
public: public:
Q_PROPERTY(float uwbDist1 READ getUwbDist1() NOTIFY uwbDistChanged)
Q_PROPERTY(float uwbDist2 READ getUwbDist2() NOTIFY uwbDistChanged)
Q_PROPERTY(float uwbDist3 READ getUwbDist3() NOTIFY uwbDistChanged)
Q_PROPERTY(float uwbDist4 READ getUwbDist4() NOTIFY uwbDistChanged)
Q_PROPERTY(float dist1 READ getDist1() NOTIFY distChanged) Q_INVOKABLE bool trigger();
Q_PROPERTY(float dist2 READ getDist2() NOTIFY distChanged) Q_INVOKABLE void start();
Q_PROPERTY(float dist3 READ getDist3() NOTIFY distChanged)
Q_PROPERTY(float dist4 READ getDist4() NOTIFY distChanged)
Q_PROPERTY(float stdDev1 READ getStdDev1() NOTIFY distChanged)
Q_PROPERTY(float stdDev2 READ getStdDev2() NOTIFY distChanged)
Q_PROPERTY(float stdDev3 READ getStdDev3() NOTIFY distChanged)
Q_PROPERTY(float stdDev4 READ getStdDev4() NOTIFY distChanged)
// FTM offset
Q_PROPERTY(float offset READ getOffset() WRITE setOffset() NOTIFY offsetChanged)
Q_PROPERTY(float uwbDist1 READ getUwbDist1() NOTIFY distChanged)
Q_PROPERTY(float uwbDist2 READ getUwbDist2() NOTIFY distChanged)
Q_PROPERTY(float uwbDist3 READ getUwbDist3() NOTIFY distChanged)
Q_PROPERTY(float uwbDist4 READ getUwbDist4() NOTIFY distChanged)
Q_INVOKABLE void trigger();
Q_INVOKABLE void stop(); Q_INVOKABLE void stop();
Q_INVOKABLE int runTimeInMs() const;
Q_INVOKABLE void test();
Q_INVOKABLE void manualCheckpoint(); Q_INVOKABLE void manualCheckpoint();
void onData(std::string str); void onWifiData(WifiRttResult result);
void onUWBData(std::vector<uchar> data); void onUWBData(std::vector<uchar> data);
public: public:
float getDist1() {return _dist[0];}
float getDist2() {return _dist[1];}
float getDist3() {return _dist[2];}
float getDist4() {return _dist[3];}
float getStdDev1() {return _stdDev[0];}
float getStdDev2() {return _stdDev[1];}
float getStdDev3() {return _stdDev[2];}
float getStdDev4() {return _stdDev[3];}
float getOffset() {return _offset;}
void setOffset(float value) { _offset = value; emit offsetChanged(); }
float getUwbDist1() {return _uwbDist[0];} float getUwbDist1() {return _uwbDist[0];}
float getUwbDist2() {return _uwbDist[1];} float getUwbDist2() {return _uwbDist[1];}
float getUwbDist3() {return _uwbDist[2];} float getUwbDist3() {return _uwbDist[2];}
@@ -71,8 +70,8 @@ public:
signals: signals:
void distChanged(); void uwbDistChanged();
void offsetChanged(); void newDistMeas(int idx, int value);
public: public:

View File

@@ -3,7 +3,8 @@ QT += quick
android { android {
QT += androidextras QT += androidextras
} }
CONFIG += c++11
CONFIG += c++17
# The following define makes your compiler emit warnings if you use # The following define makes your compiler emit warnings if you use
# any feature of Qt which as been marked deprecated (the exact warnings # any feature of Qt which as been marked deprecated (the exact warnings
@@ -17,6 +18,7 @@ DEFINES += QT_DEPRECATED_WARNINGS
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \ SOURCES += \
histogramchart.cpp \
main.cpp \ main.cpp \
Manager.cpp Manager.cpp
@@ -47,6 +49,7 @@ else: unix:!android: target.path = /opt/$${TARGET}/bin
HEADERS += \ HEADERS += \
Manager.h \ Manager.h \
histogramchart.h \
uwb.h uwb.h
DISTFILES += \ DISTFILES += \

View File

@@ -35,8 +35,7 @@ public class RTT {
private static Thread ftmThread; private static Thread ftmThread;
private static boolean ftmRunning; private static boolean ftmRunning;
// called when a RTT is completed successfully public static native void onRttData(int success, final byte[] mac, long timeMS, int distMM, int distStdDevMM, int numAttemptedMeas, int numSuccessfullMeas, int rssi);
public static native void onRTTComplete(final byte[] result);
// result callback // result callback
private static final RangingResultCallback callback = new RangingResultCallback() { private static final RangingResultCallback callback = new RangingResultCallback() {
@@ -52,57 +51,34 @@ public class RTT {
Log.d("RTT", "onRangingResults: " + list.size()); Log.d("RTT", "onRangingResults: " + list.size());
for (final RangingResult res : list) { for (final RangingResult res : list) {
int success = 0;
long timeStampInMS = 0;
MacAddress mac = res.getMacAddress();
int dist = 0;
int stdDevDist = 0;
int rssi = 0;
int numAttemptedMeas = 0;
int numSuccessfulMeas = 0;
final MacAddress mac = res.getMacAddress(); if (res.getStatus() == RangingResult.STATUS_SUCCESS) {
if (res.getStatus() == RangingResult.STATUS_SUCCESS) { success = 1;
final int dist = res.getDistanceMm(); timeStampInMS = res.getRangingTimestampMillis();
final int stdDevDist = res.getDistanceStdDevMm(); dist = res.getDistanceMm();
Log.d("RTT", mac.toString() + " " + dist + " " + stdDevDist); stdDevDist = res.getDistanceStdDevMm();
} else { rssi = res.getRssi();
Log.d("RTT", mac.toString() + " FAILED"); numAttemptedMeas = res.getNumAttemptedMeasurements();
} numSuccessfulMeas = res.getNumSuccessfulMeasurements();
RTT.onRTTComplete(serialize(res)); //Log.d("RTT", mac.toString() + " " + dist + " " + stdDevDist + " " + rssi);
} else {
//Log.d("RTT", mac.toString() + " FAILED");
}
onRttData(success, mac.toString().getBytes(), timeStampInMS, dist, stdDevDist, numAttemptedMeas, numSuccessfulMeas, rssi);
} }
} }
}; };
private static byte[] serialize(final RangingResult res) {
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
char delim = ';';
boolean success = res.getStatus() == RangingResult.STATUS_SUCCESS;
baos.write(success ? '1' : '0');
baos.write(delim);
baos.write(("" + System.currentTimeMillis()).getBytes());
baos.write(delim);
baos.write(res.getMacAddress().toString().getBytes());
baos.write(delim);
int distValue = 0;
int distStdDev = 0;
int rssi = 0;
if (success) {
distValue = res.getDistanceMm();
distStdDev = res.getDistanceStdDevMm();
rssi = res.getRssi();
}
baos.write( ("" + distValue).getBytes() );
baos.write(delim);
baos.write( ("" + distStdDev).getBytes() );
baos.write(delim);
baos.write( ("" + rssi).getBytes() );
} catch (final Exception e) {;}
return baos.toByteArray();
}
public static int start() { public static int start() {
if (ftmRunning) if (ftmRunning)
@@ -116,10 +92,11 @@ public class RTT {
mainExecutor = act.getMainExecutor(); mainExecutor = act.getMainExecutor();
final ArrayList<MacAddress> macs = new ArrayList<>(); final ArrayList<MacAddress> macs = new ArrayList<>();
macs.add(MacAddress.fromString("38:de:ad:6d:77:25")); // NUC 1 macs.add(MacAddress.fromString("38:de:ad:6d:77:25")); // NUC 1
macs.add(MacAddress.fromString("38:de:ad:6d:60:ff")); // NUC 2 macs.add(MacAddress.fromString("38:de:ad:6d:60:ff")); // NUC 2
macs.add(MacAddress.fromString("1c:1b:b5:ef:a2:9a")); // NUC 3 macs.add(MacAddress.fromString("1c:1b:b5:ef:a2:9a")); // NUC 3
macs.add(MacAddress.fromString("1c:1b:b5:ec:d1:82")); // NUC 4 macs.add(MacAddress.fromString("1c:1b:b5:ec:d1:82")); // NUC 4
macs.add(MacAddress.fromString("d0:c6:37:bc:5c:41")); // NUC 5
ftmRunning = true; ftmRunning = true;

137
histogramchart.cpp Normal file
View File

@@ -0,0 +1,137 @@
#include "histogramchart.h"
#include <cmath>
#include <QPen>
#include <QPainter>
HistogramChart::HistogramChart(QQuickItem *parent)
: QQuickPaintedItem(parent)
{
}
void HistogramChart::paint(QPainter* painter)
{
const int leftPadding = 32;
const int bottomPadding = 32;
const int height = static_cast<int>(this->height());
const int width = static_cast<int>(this->width());
painter->fillRect(0, 0, width, height, Qt::white);
if (_histogram.empty()) {
return;
}
const qreal barHeight = height - bottomPadding;
const qreal barWidth = (width - leftPadding) / static_cast<qreal>(_histogram.size());
for (size_t i = 0; i < _histogram.size(); i++) {
int count = _histogram[i];
qreal h = (count / static_cast<qreal>(maxBinCount)) * barHeight;
qreal x = i * barWidth;
qreal y = barHeight - h;
const QColor darkBlue = QColor::fromRgb(32, 74, 135);
const QColor lightBlue = QColor::fromRgb(52, 101, 164);
const QColor lighterBlue = QColor::fromRgb(114, 159, 207);
QColor c = (lastUpdatedBinIndex == i) ? lighterBlue : lightBlue;
QRectF barRect(x + leftPadding, y, barWidth, h);
painter->fillRect(barRect, c);
if (i % 10 == 0) {
QRectF txtRect(x + leftPadding, barHeight, barWidth, 32);
std::string str(16, '\0');
std::snprintf(&str[0], str.size(), "%g", minValue + i*_binWidth);
painter->drawText(txtRect, Qt::AlignCenter | Qt::TextDontClip, QString::fromStdString(str));
}
}
}
void HistogramChart::pushData(qreal value)
{
if(_histogram.empty()) {
minValue = value;
maxValue = value;
_histogram.resize(1);
}
else if (value > maxValue) {
int newMaxIndex = binIndex(value);
Q_ASSERT(newMaxIndex > 0);
_histogram.resize(static_cast<size_t>(newMaxIndex + 1));
maxValue = value;
}
else if (value < minValue) {
int oldMinIndex = binIndex(minValue);
int newMinIndex = binIndex(value);
int diff = oldMinIndex - newMinIndex;
Q_ASSERT(diff > 0);
_histogram.insert(_histogram.begin(), static_cast<size_t>(diff), 0);
minValue = value;
}
int idx = binIndex(value);
int& count = _histogram.at(static_cast<size_t>(idx));
count++;
if (count >= maxBinCount) {
maxBinCount = count;
maxBinIndex = idx;
}
lastUpdatedBinIndex = static_cast<size_t>(idx);
stats.add(value);
update();
}
int HistogramChart::binIndex(qreal value) const
{
Q_ASSERT(!std::isnan(minValue));
return static_cast<int>( std::floor( (value-minValue) / _binWidth));
}
int HistogramChart::numberOfBins() const
{
Q_ASSERT(!std::isnan(minValue));
Q_ASSERT(!std::isnan(maxValue));
return static_cast<int>( std::ceil((maxValue - minValue)/_binWidth) );
}
void HistogramChart::clear()
{
std::fill(_histogram.begin(), _histogram.end(), 0);
maxBinCount = 0;
stats.clear();
}
void HistogramChart::reset()
{
_histogram.resize(0);
minValue = std::numeric_limits<qreal>::quiet_NaN();
maxValue = std::numeric_limits<qreal>::quiet_NaN();
maxBinCount = 0;
stats.clear();
}

100
histogramchart.h Normal file
View File

@@ -0,0 +1,100 @@
#ifndef HISTOGRAMCHART_H
#define HISTOGRAMCHART_H
#include <cmath>
#include <QQuickItem>
#include <QtQuick/QQuickPaintedItem>
#include <QColor>
struct Statistics {
qreal sum = 0;
qreal sumSquares = 0;
int count = 0;
void add(qreal v) {
sum += v;
sumSquares += v*v;
count++;
}
void clear() {
sum = 0;
sumSquares = 0;
count = 0;
}
int getCount() const {
return count;
}
qreal getMean() const {
return sum / static_cast<qreal>(count);
}
qreal getStdDev() const {
const qreal x = sumSquares / static_cast<qreal>(count);
const qreal e = getMean();
return std::sqrt(x - (e*e));
}
};
class HistogramChart : public QQuickPaintedItem
{
Q_OBJECT
Q_PROPERTY(QString name READ name WRITE setName)
Q_PROPERTY(QColor color READ color WRITE setColor)
Q_PROPERTY(qreal binWidth READ binWidth WRITE setBinWidth)
Q_PROPERTY(qreal mean READ mean)
Q_PROPERTY(qreal stdDev READ stdDev)
Q_PROPERTY(int totalCount READ totalCount)
Q_PROPERTY(qreal maxBinValue READ maxBinValue)
public:
HistogramChart(QQuickItem* parent = nullptr);
QString name() const { return m_name; }
void setName(const QString &name) { m_name = name; }
QColor color() const { return m_color; }
void setColor(const QColor& color) { m_color = color; }
qreal binWidth() const { return _binWidth; }
void setBinWidth(qreal newBinWidth) { _binWidth = newBinWidth; reset(); }
qreal mean() const { return stats.getMean(); }
qreal stdDev() const { return stats.getStdDev(); }
int totalCount() const { return stats.getCount(); }
qreal maxBinValue() const { return maxBinIndex * binWidth(); }
void paint(QPainter *painter);
Q_INVOKABLE void pushData(qreal value);
Q_INVOKABLE void clear(); // soft reset
Q_INVOKABLE void reset(); // hard reset
public:
int numberOfBins() const;
int binIndex(qreal value) const;
private:
QString m_name;
QColor m_color;
std::vector<int> _histogram;
qreal _binWidth = 100;
qreal minValue = std::numeric_limits<qreal>::quiet_NaN();
qreal maxValue = std::numeric_limits<qreal>::quiet_NaN();
size_t lastUpdatedBinIndex = 0;
int maxBinCount = 0;
int maxBinIndex = 0;
// Satistics
Statistics stats;
};
#endif // HISTOGRAMCHART_H

View File

@@ -2,6 +2,9 @@
#include <QQmlApplicationEngine> #include <QQmlApplicationEngine>
#include <QQmlContext> #include <QQmlContext>
#include <QQuickItem>
#include "histogramchart.h"
#include "Manager.h" #include "Manager.h"
extern Manager mgmt; extern Manager mgmt;
@@ -10,7 +13,10 @@ int main(int argc, char *argv[]) {
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv); QGuiApplication app(argc, argv);
qmlRegisterType<HistogramChart>("Test", 1, 0, "HistogramChart");
QQmlApplicationEngine engine; QQmlApplicationEngine engine;
@@ -18,6 +24,8 @@ int main(int argc, char *argv[]) {
engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
engine.rootObjects().first()->dumpObjectTree();
if (engine.rootObjects().isEmpty()) if (engine.rootObjects().isEmpty())
return -1; return -1;

245
main.qml
View File

@@ -3,217 +3,96 @@ import QtQuick.Window 2.2
import QtQuick.Controls 2.4 import QtQuick.Controls 2.4
import QtQuick.Layouts 1.3 import QtQuick.Layouts 1.3
import Test 1.0
Window { Window {
id: window id: window
objectName: "qmlWindow"
visible: true visible: true
width: 640 width: 640
height: 680 height: 680
title: qsTr("Hello World") title: qsTr("Hello World")
Column { Connections {
id: column target: mgmt
Label { onNewDistMeas: {
text: "FTM dist1: " + ((mgmt.dist1 ? mgmt.dist1+sld1.value : 0)/1000).toFixed(2);
}
Label { if (cbNUC.currentIndex == idx) {
text: "FTM dist2: " + ((mgmt.dist2 ? mgmt.dist2+sld1.value : 0)/1000).toFixed(2); chart.pushData(value);
} lblMean.text = "Mean: " + chart.mean.toFixed(3) + "\nStdDev: " + chart.stdDev.toFixed(3);
lblCount.text = "Count: " + chart.totalCount + "\nMax bin: " + chart.maxBinValue.toFixed(3);
Label { lblRunTime.text = "Time: " + (mgmt.runTimeInMs() / 1000.0).toFixed(0) + "s";
text: "FTM dist3: " + ((mgmt.dist3 ? mgmt.dist3+sld1.value : 0)/1000).toFixed(2);
}
Label {
text: "FTM dist4: " + ((mgmt.dist4 ? mgmt.dist4+sld1.value : 0)/1000).toFixed(2);
}
Slider {
width: 400;
id: sld1;
from: -1000;
to: 1000;
value: mgmt.offset;
onValueChanged: {
mgmt.offset = value;
leCanvas.requestPaint();
} }
}
Label {
text: "offset: " + sld1.value;
}
Label {
text: "UWB dist1: " + mgmt.uwbDist1.toFixed(2);
} }
}
Label { GridLayout {
text: "UWB dist2: " + mgmt.uwbDist2.toFixed(2); anchors.fill: parent;
}
Label { Row {
text: "UWB dist3: " + mgmt.uwbDist3.toFixed(2); Layout.row: 0;
} Layout.column: 0;
spacing: 3;
Label { ComboBox {
text: "UWB dist4: " + mgmt.uwbDist4.toFixed(2); id: cbNUC;
} model: ["NUC1", "NUC2", "NUC3", "NUC4", "NUC5"];
onCurrentIndexChanged: {
chart.reset();
Connections {
target: mgmt
onDistChanged: leCanvas.requestPaint();
}
RowLayout {
Button {
text: "party hard";
onClicked: mgmt.trigger();
}
Button {
text: "stop"
onClicked: mgmt.stop();
}
Button {
text: "reset sld"
onClicked: {
sld1.value = 0;
} }
} }
Button { Button {
text: "High Five" id: btnTrigger;
text: "Start";
onClicked: { onClicked: {
mgmt.manualCheckpoint(); if (mgmt.trigger()) {
// running
btnTrigger.text = "Stop";
} else {
btnTrigger.text = "Start";
}
}
}
Button {
text: "Reset"
onClicked: {
chart.reset();
chart.update();
} }
} }
} }
Row {
Layout.row: 1;
Layout.column: 0;
spacing: 5;
} Label {
id: lblMean;
Canvas {
id: leCanvas;
anchors.right: parent.right
anchors.rightMargin: 0
anchors.left: parent.left
anchors.leftMargin: 0
anchors.bottom: parent.bottom
anchors.bottomMargin: 0
anchors.top: column.bottom
anchors.topMargin: 20
readonly property double s: 0.02;
function leArc(ctx, cx, cy, dist, stdDev, caption) {
// center circle
ctx.fillStyle = (dist < 0 ? "#FF0000" : "#000000");
ctx.beginPath();
ctx.arc(cx*s, cy*s, 2, 0, 360);
ctx.fill();
ctx.font = "12px Arial";
ctx.fillText(caption, cx*s, cy*s);
// error circle
// ctx.beginPath();
// ctx.arc(cx*s, cy*s, (dist+sld1.value)*s, 0, 360);
// ctx.closePath();
// ctx.lineWidth = stdDev*s*0.5;
// ctx.strokeStyle = "#66000000";
// ctx.stroke();
// circle
if (dist > 0) {
ctx.lineWidth = 1;
ctx.strokeStyle = "#aa000000";
ctx.beginPath();
ctx.arc(cx*s, cy*s, dist*s, 0, 360);
ctx.stroke();
} }
}
onPaint: { Label {
var ctx = getContext("2d") id: lblCount;
}
ctx.reset(); Label {
id: lblRunTime;
}
}
ctx.fillStyle = "#dddddd"; HistogramChart {
ctx.clearRect(0, 0, leCanvas.width, leCanvas.height); Layout.row: 2;
Layout.column: 0;
Layout.fillHeight: true;
Layout.fillWidth: true;
//ctx.strokeStyle = Qt.rgba(0, 0, 0, 1) id: chart
//ctx.lineWidth = 1 objectName: "qmlChart"
}
// var ox = 4000; }
// var oy = 4000;
// var cx1 = ox+0;
// var cy1 = oy+0;
// var cx2 = ox+0;
// var cy2 = oy+7250;
// var cx3 = ox+9000;
// var cy3 = oy+7250;
// var cx4 = ox+9000;
// var cy4 = oy+0;
var ox = 4000;
var oy = 4000;
var cx1 = ox+0;
var cy1 = oy+0;
var cx2 = ox+9000;
var cy2 = oy+0;
var cx3 = ox+9000;
var cy3 = oy+6200;
var cx4 = ox+0;
var cy4 = oy+6200;
leArc(ctx, cx1, cy1, mgmt.dist1, mgmt.stdDev1, 1);
leArc(ctx, cx2, cy2, mgmt.dist2, mgmt.stdDev2, 2);
leArc(ctx, cx3, cy3, mgmt.dist3, mgmt.stdDev3, 3);
leArc(ctx, cx4, cy4, mgmt.dist4, mgmt.stdDev4, 4);
var sigma = 2000;
var stepSize = 333;
var maxP = Math.pow( 1.0 / Math.sqrt(2*Math.PI*sigma), 4.1);
// for (var y = 0; y < leCanvas.height/s; y += stepSize) {
// for (var x = 0; x < leCanvas.width/s; x += stepSize) {
// var d1 = Math.sqrt( Math.pow(x-cx1, 2) + Math.pow(y-cy1, 2) ) - (mgmt.dist1-sld1.value);
// var d2 = Math.sqrt( Math.pow(x-cx2, 2) + Math.pow(y-cy2, 2) ) - (mgmt.dist2-sld1.value);
// var d3 = Math.sqrt( Math.pow(x-cx3, 2) + Math.pow(y-cy3, 2) ) - (mgmt.dist3-sld1.value);
// var d4 = Math.sqrt( Math.pow(x-cx4, 2) + Math.pow(y-cy4, 2) ) - (mgmt.dist4-sld1.value);
// var p = 1;
// p *= 1.0 / Math.sqrt(2*Math.PI*sigma) * Math.exp( - d1*d1/(2*sigma*sigma) );
// p *= 1.0 / Math.sqrt(2*Math.PI*sigma) * Math.exp( - d2*d2/(2*sigma*sigma) );
// p *= 1.0 / Math.sqrt(2*Math.PI*sigma) * Math.exp( - d3*d3/(2*sigma*sigma) );
// p *= 1.0 / Math.sqrt(2*Math.PI*sigma) * Math.exp( - d4*d4/(2*sigma*sigma) );
// //p = Math.pow(p, 1);
// if (p > maxP / 50) {
// ctx.fillStyle = Qt.rgba(1,0,0, p/maxP);
// ctx.fillRect(x*s, y*s, stepSize*s, stepSize*s);
// }
// }
// }
}
}
} }