Histogram implementation
This commit is contained in:
235
Manager.cpp
235
Manager.cpp
@@ -9,7 +9,8 @@
|
||||
#include <sstream>
|
||||
#include <chrono>
|
||||
#include <iomanip>
|
||||
#include <thread>>
|
||||
#include <thread>
|
||||
#include <random>
|
||||
|
||||
#include <QStandardPaths>
|
||||
#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 NUC3 = "1c:1b:b5:ef:a2:9a";
|
||||
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;
|
||||
|
||||
@@ -49,178 +51,81 @@ Manager::Manager() {
|
||||
|
||||
}
|
||||
|
||||
void Manager::test() {
|
||||
|
||||
void Manager::trigger() {
|
||||
}
|
||||
|
||||
QString folder = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation) + "/ftm/";
|
||||
|
||||
QString prefix = GetCurrentTimeForFileName();
|
||||
|
||||
dataLogger = std::make_shared<QFile>(folder+"/"+prefix+"_ftm.txt");
|
||||
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;
|
||||
bool Manager::trigger() {
|
||||
if (isRunning) {
|
||||
stop();
|
||||
} else {
|
||||
start();
|
||||
}
|
||||
|
||||
if (!gtLogger->open(QIODevice::ReadWrite)) {
|
||||
qWarning() << "Failed to create gt data logger file" << gtLogger->fileName();
|
||||
gtLogger = nullptr;
|
||||
}
|
||||
return isRunning;
|
||||
}
|
||||
|
||||
void Manager::start() {
|
||||
startTime = nowInMsec();
|
||||
|
||||
#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");
|
||||
QAndroidJniObject::callStaticMethod<int>("android/net/wifi/RTT", "start", "()I");
|
||||
|
||||
|
||||
isRunning = true;
|
||||
#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");
|
||||
onData("1337;"+NUC1+";6230;1231");
|
||||
onData("1337;"+NUC2+";3430;3423");
|
||||
onData("1337;"+NUC3+";5630;2341");
|
||||
onData("1337;"+NUC4+";8830;2241");
|
||||
WifiRttResult res;
|
||||
res.success = 1;
|
||||
res.mac = NUC1;
|
||||
res.numAttemptedMeas = 8;
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
void Manager::stop() {
|
||||
#ifdef ANDROID
|
||||
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
|
||||
|
||||
dataLogger->flush();
|
||||
dataLogger->close();
|
||||
|
||||
uwbLogger->flush();
|
||||
uwbLogger->close();
|
||||
|
||||
gtLogger->flush();
|
||||
gtLogger->close();
|
||||
isRunning = false;
|
||||
}
|
||||
|
||||
int Manager::runTimeInMs() const {
|
||||
return static_cast<int>(nowInMsec() - startTime);
|
||||
}
|
||||
|
||||
void Manager::manualCheckpoint() {
|
||||
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();
|
||||
timestamp -= startTime;
|
||||
if(NUC1 == result.mac) {nucIdx = 0;}
|
||||
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;
|
||||
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();
|
||||
emit newDistMeas(nucIdx, result.distMM);
|
||||
}
|
||||
}
|
||||
|
||||
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 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;
|
||||
@@ -262,19 +160,34 @@ Manager mgmt;
|
||||
#ifdef ANDROID
|
||||
extern "C" {
|
||||
|
||||
JNIEXPORT void JNICALL Java_android_net_wifi_RTT_onRTTComplete(JNIEnv* env, jobject jobj, jbyteArray arrayID) {
|
||||
(void) env; (void) jobj;
|
||||
jsize length = env->GetArrayLength(arrayID);
|
||||
jbyte* data = env->GetByteArrayElements(arrayID, 0);
|
||||
std::string str((char*)data, length);
|
||||
env->ReleaseByteArrayElements(arrayID, data, JNI_ABORT);
|
||||
mgmt.onData(str);
|
||||
}
|
||||
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;
|
||||
|
||||
jsize length = env->GetArrayLength(macString);
|
||||
jbyte* data = env->GetByteArrayElements(macString, nullptr);
|
||||
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) {
|
||||
(void) env; (void) jobj;
|
||||
(void) env;
|
||||
(void) jobj;
|
||||
|
||||
jsize length = env->GetArrayLength(arrayID);
|
||||
jbyte* data = env->GetByteArrayElements(arrayID, 0);
|
||||
jbyte* data = env->GetByteArrayElements(arrayID, nullptr);
|
||||
std::vector<uchar> c_data;
|
||||
c_data.assign(reinterpret_cast<char*>(data), reinterpret_cast<char*>(data)+length);
|
||||
env->ReleaseByteArrayElements(arrayID, data, JNI_ABORT);
|
||||
|
||||
85
Manager.h
85
Manager.h
@@ -1,69 +1,68 @@
|
||||
#ifndef MANAGER_H
|
||||
#define MANAGER_H
|
||||
|
||||
#include <QDebug>
|
||||
#include <QObject>
|
||||
#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 {
|
||||
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
|
||||
float _dist[4];
|
||||
float _stdDev[4];
|
||||
float _offset = 500;
|
||||
|
||||
float _uwbDist[4];
|
||||
|
||||
std::shared_ptr<QFile> dataLogger;
|
||||
std::shared_ptr<QFile> uwbLogger;
|
||||
std::shared_ptr<QFile> gtLogger;
|
||||
bool isRunning = false;
|
||||
|
||||
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_PROPERTY(float dist2 READ getDist2() NOTIFY distChanged)
|
||||
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 bool trigger();
|
||||
Q_INVOKABLE void start();
|
||||
Q_INVOKABLE void stop();
|
||||
|
||||
Q_INVOKABLE int runTimeInMs() const;
|
||||
|
||||
Q_INVOKABLE void test();
|
||||
|
||||
Q_INVOKABLE void manualCheckpoint();
|
||||
|
||||
void onData(std::string str);
|
||||
void onWifiData(WifiRttResult result);
|
||||
void onUWBData(std::vector<uchar> data);
|
||||
|
||||
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 getUwbDist2() {return _uwbDist[1];}
|
||||
float getUwbDist3() {return _uwbDist[2];}
|
||||
@@ -71,8 +70,8 @@ public:
|
||||
|
||||
signals:
|
||||
|
||||
void distChanged();
|
||||
void offsetChanged();
|
||||
void uwbDistChanged();
|
||||
void newDistMeas(int idx, int value);
|
||||
|
||||
public:
|
||||
|
||||
|
||||
5
RTT.pro
5
RTT.pro
@@ -3,7 +3,8 @@ QT += quick
|
||||
android {
|
||||
QT += androidextras
|
||||
}
|
||||
CONFIG += c++11
|
||||
|
||||
CONFIG += c++17
|
||||
|
||||
# The following define makes your compiler emit warnings if you use
|
||||
# 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
|
||||
|
||||
SOURCES += \
|
||||
histogramchart.cpp \
|
||||
main.cpp \
|
||||
Manager.cpp
|
||||
|
||||
@@ -47,6 +49,7 @@ else: unix:!android: target.path = /opt/$${TARGET}/bin
|
||||
|
||||
HEADERS += \
|
||||
Manager.h \
|
||||
histogramchart.h \
|
||||
uwb.h
|
||||
|
||||
DISTFILES += \
|
||||
|
||||
@@ -35,8 +35,7 @@ public class RTT {
|
||||
private static Thread ftmThread;
|
||||
private static boolean ftmRunning;
|
||||
|
||||
// called when a RTT is completed successfully
|
||||
public static native void onRTTComplete(final byte[] result);
|
||||
public static native void onRttData(int success, final byte[] mac, long timeMS, int distMM, int distStdDevMM, int numAttemptedMeas, int numSuccessfullMeas, int rssi);
|
||||
|
||||
// result callback
|
||||
private static final RangingResultCallback callback = new RangingResultCallback() {
|
||||
@@ -52,57 +51,34 @@ public class RTT {
|
||||
Log.d("RTT", "onRangingResults: " + list.size());
|
||||
|
||||
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) {
|
||||
final int dist = res.getDistanceMm();
|
||||
final int stdDevDist = res.getDistanceStdDevMm();
|
||||
Log.d("RTT", mac.toString() + " " + dist + " " + stdDevDist);
|
||||
} else {
|
||||
Log.d("RTT", mac.toString() + " FAILED");
|
||||
}
|
||||
if (res.getStatus() == RangingResult.STATUS_SUCCESS) {
|
||||
success = 1;
|
||||
timeStampInMS = res.getRangingTimestampMillis();
|
||||
dist = res.getDistanceMm();
|
||||
stdDevDist = res.getDistanceStdDevMm();
|
||||
rssi = res.getRssi();
|
||||
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() {
|
||||
|
||||
if (ftmRunning)
|
||||
@@ -116,10 +92,11 @@ public class RTT {
|
||||
mainExecutor = act.getMainExecutor();
|
||||
|
||||
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("1c:1b:b5:ef:a2:9a")); // NUC 3
|
||||
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;
|
||||
|
||||
|
||||
137
histogramchart.cpp
Normal file
137
histogramchart.cpp
Normal 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
100
histogramchart.h
Normal 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
|
||||
10
main.cpp
10
main.cpp
@@ -2,6 +2,9 @@
|
||||
#include <QQmlApplicationEngine>
|
||||
|
||||
#include <QQmlContext>
|
||||
#include <QQuickItem>
|
||||
|
||||
#include "histogramchart.h"
|
||||
|
||||
#include "Manager.h"
|
||||
extern Manager mgmt;
|
||||
@@ -10,7 +13,10 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
||||
|
||||
QGuiApplication app(argc, argv);
|
||||
QGuiApplication app(argc, argv);
|
||||
|
||||
qmlRegisterType<HistogramChart>("Test", 1, 0, "HistogramChart");
|
||||
|
||||
|
||||
QQmlApplicationEngine engine;
|
||||
|
||||
@@ -18,6 +24,8 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
|
||||
|
||||
engine.rootObjects().first()->dumpObjectTree();
|
||||
|
||||
if (engine.rootObjects().isEmpty())
|
||||
return -1;
|
||||
|
||||
|
||||
245
main.qml
245
main.qml
@@ -3,217 +3,96 @@ import QtQuick.Window 2.2
|
||||
import QtQuick.Controls 2.4
|
||||
import QtQuick.Layouts 1.3
|
||||
|
||||
import Test 1.0
|
||||
|
||||
Window {
|
||||
id: window
|
||||
objectName: "qmlWindow"
|
||||
|
||||
visible: true
|
||||
width: 640
|
||||
height: 680
|
||||
title: qsTr("Hello World")
|
||||
|
||||
Column {
|
||||
id: column
|
||||
Connections {
|
||||
target: mgmt
|
||||
|
||||
Label {
|
||||
text: "FTM dist1: " + ((mgmt.dist1 ? mgmt.dist1+sld1.value : 0)/1000).toFixed(2);
|
||||
}
|
||||
onNewDistMeas: {
|
||||
|
||||
Label {
|
||||
text: "FTM dist2: " + ((mgmt.dist2 ? mgmt.dist2+sld1.value : 0)/1000).toFixed(2);
|
||||
}
|
||||
|
||||
Label {
|
||||
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();
|
||||
if (cbNUC.currentIndex == idx) {
|
||||
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);
|
||||
lblRunTime.text = "Time: " + (mgmt.runTimeInMs() / 1000.0).toFixed(0) + "s";
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
text: "offset: " + sld1.value;
|
||||
}
|
||||
|
||||
Label {
|
||||
text: "UWB dist1: " + mgmt.uwbDist1.toFixed(2);
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
text: "UWB dist2: " + mgmt.uwbDist2.toFixed(2);
|
||||
}
|
||||
GridLayout {
|
||||
anchors.fill: parent;
|
||||
|
||||
Label {
|
||||
text: "UWB dist3: " + mgmt.uwbDist3.toFixed(2);
|
||||
}
|
||||
Row {
|
||||
Layout.row: 0;
|
||||
Layout.column: 0;
|
||||
spacing: 3;
|
||||
|
||||
Label {
|
||||
text: "UWB dist4: " + mgmt.uwbDist4.toFixed(2);
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
ComboBox {
|
||||
id: cbNUC;
|
||||
model: ["NUC1", "NUC2", "NUC3", "NUC4", "NUC5"];
|
||||
onCurrentIndexChanged: {
|
||||
chart.reset();
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "High Five"
|
||||
id: btnTrigger;
|
||||
text: "Start";
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
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();
|
||||
Label {
|
||||
id: lblMean;
|
||||
}
|
||||
}
|
||||
|
||||
onPaint: {
|
||||
var ctx = getContext("2d")
|
||||
Label {
|
||||
id: lblCount;
|
||||
}
|
||||
|
||||
ctx.reset();
|
||||
Label {
|
||||
id: lblRunTime;
|
||||
}
|
||||
}
|
||||
|
||||
ctx.fillStyle = "#dddddd";
|
||||
ctx.clearRect(0, 0, leCanvas.width, leCanvas.height);
|
||||
HistogramChart {
|
||||
Layout.row: 2;
|
||||
Layout.column: 0;
|
||||
Layout.fillHeight: true;
|
||||
Layout.fillWidth: true;
|
||||
|
||||
//ctx.strokeStyle = Qt.rgba(0, 0, 0, 1)
|
||||
//ctx.lineWidth = 1
|
||||
id: chart
|
||||
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);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user