commit 228fc3ab24e376b8f9aba864aec944daffd12f17 Author: leo Date: Sun Jun 6 00:10:20 2021 +0200 Forgot about version control, here's the whole code at once diff --git a/LivePow.pro b/LivePow.pro new file mode 100644 index 0000000..c022d9e --- /dev/null +++ b/LivePow.pro @@ -0,0 +1,25 @@ +QT += core gui + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +CONFIG += c++11 + +# You can make your code fail to compile if it uses deprecated APIs. +# In order to do so, uncomment the following line. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + displayarea.cpp \ + inputparser.cpp \ + main.cpp \ + mainwindow.cpp + +HEADERS += \ + displayarea.h \ + inputparser.h \ + mainwindow.h + +# Default rules for deployment. +qnx: target.path = /tmp/$${TARGET}/bin +else: unix:!android: target.path = /opt/$${TARGET}/bin +!isEmpty(target.path): INSTALLS += target diff --git a/README.md b/README.md new file mode 100644 index 0000000..fc6ffc3 --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# LivePow +An reader for making heatmap from soapy_power output + +## Building +``` +mkdir build +qmake .. +make -j4 +``` + +## Usage +### Example scanning the FM radio band +`soapy_power -f 80M:110M -n 10 -e 30 -B 30k -k 30 --pow2 -F rtl_power -R | ./Livepow` diff --git a/displayarea.cpp b/displayarea.cpp new file mode 100644 index 0000000..9b6239b --- /dev/null +++ b/displayarea.cpp @@ -0,0 +1,47 @@ +#include "displayarea.h" +#include +#include +#include + +DisplayArea::DisplayArea(QWidget *parent) : QWidget(parent) +{ + setAttribute(Qt::WA_StaticContents); + + QTimer* timer = new QTimer(this); + connect(timer,SIGNAL(timeout()),this,SLOT(updateDisp())); + timer->start(100); +} + +void DisplayArea::paintEvent(QPaintEvent *event) +{ + QPainter painter(this); + QRect rect=event->rect(); + painter.drawImage(rect,image,rect); +} + +void DisplayArea::setPixel(int x, int y, unsigned int col) +{ + if(x>=image.width()||y>=image.height()) resizeImage(&image,QSize(qMax(x+255,image.width()),qMax(y+255,image.height()))); + image.setPixel(x,y,col); +} + +void DisplayArea::resizeImage(QImage *image, const QSize &newSize) +{ + if(image->size()==newSize) return; + + QImage newImage(newSize,QImage::Format_ARGB32); + newImage.fill(qRgba(255,255,255,0)); + QPainter painter(&newImage); + painter.drawImage(QPoint(0,0),*image); + *image=newImage; +} + +void DisplayArea::updateDisp() +{ + update(); +} + +bool DisplayArea::saveImage(const QString &fileName, const char *fileFormat) +{ + return image.save(fileName, fileFormat); +} diff --git a/displayarea.h b/displayarea.h new file mode 100644 index 0000000..03e9322 --- /dev/null +++ b/displayarea.h @@ -0,0 +1,29 @@ +#ifndef DISPLAYAREA_H +#define DISPLAYAREA_H + +#include + +class DisplayArea : public QWidget +{ + Q_OBJECT +public: + DisplayArea(QWidget *parent = nullptr); + void setPixel(int x,int y,unsigned int col); + bool saveImage(const QString &fileName, const char *fileFormat); + +protected: + void paintEvent(QPaintEvent *event) override; + +private: + void resizeImage(QImage *image, const QSize &newSize); + + QImage image; + +public slots: + void updateDisp(); + +signals: + +}; + +#endif // DISPLAYAREA_H diff --git a/inputparser.cpp b/inputparser.cpp new file mode 100644 index 0000000..b69fc3b --- /dev/null +++ b/inputparser.cpp @@ -0,0 +1,132 @@ +#include "inputparser.h" +#include +#include "displayarea.h" +#include +#include + +InputParser::InputParser(FILE* input,DisplayArea* display) +{ + InputParser::input=input; + + InputParser::display=display; + + random=new QRandomGenerator(); + + /*QTimer* timer = new QTimer(this); + connect(timer,SIGNAL(timeout()),this,SLOT(process())); + timer->start(0);*/ +} + + +void InputParser::process() +{ + while(1){ + int c=fgetc(input); + if(c==EOF) continue; + + switch(c){ + case ',': + switch(index){ + case 1: + computeEpochDate(); + break; + default: + if(index>5){ + sendPixel(); + } + break; + } + index++; + decimal=false; + break; + case '\n': + index=0; + minFreq=qMin(minFreq,currentLine.minFreq); + maxFreq=qMax(maxFreq,currentLine.maxFreq); + + sendPixel(); + + if(currentLine.maxFreq<=lastMaxFreq){ + currentY++; + currentX=0; + } + lastMaxFreq=currentLine.maxFreq; + + currentLine=line(); + decimal=false; + break; + case '.': + decimal=true; + break; + default: + switch(index){ + case 0: + addToString(c,¤tLine.firstDate); + break; + case 1: + addToString(c,¤tLine.secondDate); + break; + case 2: + addToInt(c,¤tLine.minFreq); + break; + case 3: + addToInt(c,¤tLine.maxFreq); + break; + case 4: + addToInt(c,¤tLine.freqStep); + break; + default: + if(index>5) addToFloat(c,¤tPowerValue); + break; + } + break; + } + } + //printf("%c aaa"); +} + +void InputParser::addToString(char c, QString* str) +{ + str->append(c); +} + +void InputParser::addToInt(char c, unsigned long* nb) +{ + if(decimal||c<'0'||c>'9') return; + *nb*=10; + *nb+=(c-'0'); +} + +void InputParser::addToFloat(char c, float* nb) +{ + if(c<'0'||c>'9') return; + if(!decimal){ + *nb*=10; + *nb+=(c-'0'); + } + else{ + float inc=((c-'0')/pow(10,decimalIndex)); + *nb+=inc; + decimalIndex++; + } +} + +void InputParser::computeEpochDate() +{ + QString dateMerged=QString(); + dateMerged.append(currentLine.firstDate); + dateMerged.append(currentLine.secondDate); + //2021-05-28 23:20:36 + currentLine.parsedDate=QDateTime::fromString(dateMerged,"yyyy-MM-dd hh:mm:ss"); +} + +void InputParser::sendPixel() +{ + currentPowerValue*=sign; + int col=qRound((80-currentPowerValue)*6); + display->setPixel(currentX,currentY,qRgba(col,col,col,255)); + currentX++; + currentPowerValue=0; + decimalIndex=1; + sign=1; +} diff --git a/inputparser.h b/inputparser.h new file mode 100644 index 0000000..a01fd23 --- /dev/null +++ b/inputparser.h @@ -0,0 +1,63 @@ +#ifndef INPUTPARSER_H +#define INPUTPARSER_H + +#include +#include +#include +#include +#include + +class DisplayArea; + +struct line{ + QString firstDate; + QString secondDate; + QDateTime parsedDate; + unsigned long minFreq=0; + unsigned long maxFreq=0; + unsigned long freqStep=0; +}; + +class InputParser : public QObject +{ + Q_OBJECT +public: + InputParser(FILE* input,DisplayArea* display); + +private: + unsigned long minFreq=-1; + unsigned long maxFreq=0; + + unsigned long lastMaxFreq=0; + + unsigned long index=0; + + line currentLine; + + FILE* input; + + bool decimal=false; + int decimalIndex=1; + int sign=1; + + unsigned int currentX=0; + unsigned int currentY=0; + + DisplayArea* display; + + QRandomGenerator* random; + + float currentPowerValue=0; + + void addToString(char c,QString* str); + void addToInt(char c,unsigned long* nb); + void addToFloat(char c,float* nb); + void computeEpochDate(); + + void sendPixel(); + +public slots: + void process(); +}; + +#endif // INPUTPARSER_H diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..fd3e533 --- /dev/null +++ b/main.cpp @@ -0,0 +1,11 @@ +#include "mainwindow.h" + +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + MainWindow w; + w.show(); + return a.exec(); +} diff --git a/mainwindow.cpp b/mainwindow.cpp new file mode 100644 index 0000000..ab7ec70 --- /dev/null +++ b/mainwindow.cpp @@ -0,0 +1,82 @@ +#include +#include "mainwindow.h" +#include "displayarea.h" +#include "inputparser.h" +#include +#include +#include +#include +#include +#include +#include +#include + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent),displayArea(new DisplayArea(this)) +{ + setCentralWidget(displayArea); + + FILE* input=stdin; + + parser=new InputParser(input,displayArea); + + QThread* workerThread=new QThread(); + + parser->moveToThread(workerThread); + connect(workerThread, &QThread::started, parser, &InputParser::process); + connect(workerThread, &QThread::finished, workerThread, &QThread::deleteLater); + workerThread->start(); + + createActions(); + createMenus(); + + setWindowTitle(tr("LivePow")); +} + +MainWindow::~MainWindow() +{ +} + +void MainWindow::save(){ + QAction *action = qobject_cast(sender()); + QByteArray fileFormat = action->data().toByteArray(); + saveFile(fileFormat); +} + +bool MainWindow::saveFile(const QByteArray &fileFormat) +{ + QString initialPath = QDir::currentPath() + "/untitled." + fileFormat; + + QString fileName = QFileDialog::getSaveFileName(this, tr("Save As"), + initialPath, + tr("%1 Files (*.%2);;All Files (*)") + .arg(QString::fromLatin1(fileFormat.toUpper()),QString::fromLatin1(fileFormat))); + if (fileName.isEmpty()) + return false; + return displayArea->saveImage(fileName, fileFormat.constData()); +} + +void MainWindow::createActions() +{ + const QList imageFormats = QImageWriter::supportedImageFormats(); + for (const QByteArray &format : imageFormats) { + QString text = tr("%1...").arg(QString::fromLatin1(format).toUpper()); + + QAction *action = new QAction(text, this); + action->setData(format); + connect(action, &QAction::triggered, this, &MainWindow::save); + saveAsActs.append(action); + } +} + +void MainWindow::createMenus() +{ + saveAsMenu = new QMenu(tr("&Save As"), this); + for (QAction *action : qAsConst(saveAsActs)) + saveAsMenu->addAction(action); + + fileMenu = new QMenu(tr("&File"), this); + fileMenu->addMenu(saveAsMenu); + + menuBar()->addMenu(fileMenu); +} diff --git a/mainwindow.h b/mainwindow.h new file mode 100644 index 0000000..0d88a3a --- /dev/null +++ b/mainwindow.h @@ -0,0 +1,39 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include + +class DisplayArea; +class InputParser; + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + + static void tick(); + + InputParser *parser; + +private: + DisplayArea *displayArea; + void createActions(); + void createMenus(); + + QMenu *saveAsMenu; + QMenu *fileMenu; + + QList saveAsActs; + + bool saveFile(const QByteArray &fileFormat); + +private slots: + void save(); + +signals: + void operate(const QString &); +}; +#endif // MAINWINDOW_H