From 86b0d078291ff18060440e7cf6bef5d8acb7e398 Mon Sep 17 00:00:00 2001 From: Brandon Rozek Date: Tue, 14 May 2019 23:09:55 -0400 Subject: [PATCH] Basic Implementation of Radio Server/Client Client written in QT and communicates to SDR server over TCP sockets. Currently getting and setting frequencies is supported --- .gitignore | 73 ++++++++++++++++++++++++++ CMakeLists.txt | 15 ++++++ client.cpp | 42 +++++++++++++++ client.h | 20 +++++++ main.cpp | 27 ++++++++++ main.qml | 59 +++++++++++++++++++++ qml.qrc | 5 ++ tuner.py | 139 +++++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 380 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 client.cpp create mode 100644 client.h create mode 100644 main.cpp create mode 100644 main.qml create mode 100644 qml.qrc create mode 100755 tuner.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fab7372 --- /dev/null +++ b/.gitignore @@ -0,0 +1,73 @@ +# This file is used to ignore files which are generated +# ---------------------------------------------------------------------------- + +*~ +*.autosave +*.a +*.core +*.moc +*.o +*.obj +*.orig +*.rej +*.so +*.so.* +*_pch.h.cpp +*_resource.rc +*.qm +.#* +*.*# +core +!core/ +tags +.DS_Store +.directory +*.debug +Makefile* +*.prl +*.app +moc_*.cpp +ui_*.h +qrc_*.cpp +Thumbs.db +*.res +*.rc +/.qmake.cache +/.qmake.stash + +# qtcreator generated files +*.pro.user* + +# xemacs temporary files +*.flc + +# Vim temporary files +.*.swp + +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.sdf +*.opensdf +*.vcxproj +*vcxproj.* + +# MinGW generated files +*.Debug +*.Release + +# Python byte code +*.pyc + +# Binaries +# -------- +*.dll +*.exe + diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..58c0d99 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.1) + +project(RadioTuner LANGUAGES CXX) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(Qt5 COMPONENTS Core Quick REQUIRED) + +add_executable(${PROJECT_NAME} "main.cpp" "qml.qrc" "client.cpp") +target_compile_definitions(${PROJECT_NAME} PRIVATE $<$,$>:QT_QML_DEBUG>) +target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Core Qt5::Quick) diff --git a/client.cpp b/client.cpp new file mode 100644 index 0000000..8dfeffd --- /dev/null +++ b/client.cpp @@ -0,0 +1,42 @@ +#include "client.h" +#include +#include +#include + + +Client::Client(QObject *parent) : QObject(parent) { + socket = new QTcpSocket(nullptr); + socket->connectToHost("localhost", 65432); + socket->waitForConnected(); + std::cout << "Connected" << std::endl; +} + +Client::~Client() { + socket->close(); +} + +void Client::send(QString data) { + std::cout << "SENDING MESSAGE: \"" << data.toStdString() << "\"" << std::endl; + QByteArray dataStream = data.toUtf8(); + socket->write(dataStream); +} + +QString Client::recv(void) { + socket->waitForReadyRead(1000); + QByteArray data = socket->readAll(); + QString msg = QString(data); + std::cout << "RECEIVED MESSAGE SO FAR: \"" << msg.toStdString() << "\"" << std::endl; + return msg; +} + +void Client::setFrequency(double freq) { + QString msg = QString(":FREQ="); + msg.push_back(QString::number(freq)); + msg.push_back(";"); + this->send(msg); +} + +double Client::getFrequency(void) { + this->send(QString("?FREQ;")); + return this->recv().toDouble(); +} diff --git a/client.h b/client.h new file mode 100644 index 0000000..c404a26 --- /dev/null +++ b/client.h @@ -0,0 +1,20 @@ +#ifndef CLIENT_H +#define CLIENT_H + +#include +#include + +class Client : public QObject { + Q_OBJECT +public: + explicit Client(QObject *parent = nullptr); + ~Client(); + void send(QString data); + QString recv(void); + Q_INVOKABLE void setFrequency(double freq); + Q_INVOKABLE double getFrequency(void); +private: + QTcpSocket *socket = nullptr; +}; + +#endif // CLIENT_H diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..6d65991 --- /dev/null +++ b/main.cpp @@ -0,0 +1,27 @@ +#include +#include +#include + +#include "client.h" + +int main(int argc, char *argv[]) +{ + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + + QGuiApplication app(argc, argv); + +// qmlRegisterType("io.qt.client", 1, 0, "Client"); + + QQmlApplicationEngine engine; + const QUrl url(QStringLiteral("qrc:/main.qml")); + QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, + &app, [url](QObject *obj, const QUrl &objUrl) { + if (!obj && url == objUrl) + QCoreApplication::exit(-1); + }, Qt::QueuedConnection); + Client *client = new Client(); + engine.rootContext()->setContextProperty("client", client); + engine.load(url); + + return app.exec(); +} diff --git a/main.qml b/main.qml new file mode 100644 index 0000000..6c04409 --- /dev/null +++ b/main.qml @@ -0,0 +1,59 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 2.3 + +Window { + visible: true + width: 640 + height: 480 + property alias freqText: freqText + property alias dial: dial + title: qsTr("Radio Tuner") + property var initialFreq : { + return client.getFrequency() / 100000; + } + + Dial { + id: dial + x: 74 + y: 248 + font.pointSize: 10 + stepSize: 1 + to: 1080 + from: 875 + value: initialFreq + } + + Text { + id: title + x: 38 + y: 37 + text: qsTr("Radio Tuner") + font.pixelSize: 32 + } + + Text { + id: freqText + x: 108 + y: 197 + text: { + var dialNumber = initialFreq / 10; + var dialString = (dialNumber < 100) ? dialNumber.toString().substring(0, 4) : dialNumber.toString().substring(0, 5) + return "Tuned To: " + dialString + } + + font.pixelSize: 24 + } + Connections { + target: dial + onMoved: { + var dialNumber = (dial.value / 10) + var dialString = (dialNumber < 100) ? dialNumber.toString().substring(0, 4) : dialNumber.toString().substring(0, 5) + freqText.text = qsTr("Tuned To: " + dialString) + dialNumber = parseFloat(dialString) + console.log("The radio then dials to: " + dialNumber + "MHz") + client.setFrequency((dialNumber * 1000000)) + + } + } +} diff --git a/qml.qrc b/qml.qrc new file mode 100644 index 0000000..5f6483a --- /dev/null +++ b/qml.qrc @@ -0,0 +1,5 @@ + + + main.qml + + diff --git a/tuner.py b/tuner.py new file mode 100755 index 0000000..0ab1290 --- /dev/null +++ b/tuner.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- +################################################## +# GNU Radio Python Flow Graph +# Title: Top Block +# Generated: Sun May 12 22:10:45 2019 +################################################## + + +from gnuradio import analog +from gnuradio import audio +from gnuradio import blocks +from gnuradio import eng_notation +from gnuradio import filter +from gnuradio import gr +from gnuradio.eng_option import eng_option +from gnuradio.filter import firdes +from optparse import OptionParser +import osmosdr +import time +import socket +import re + +class top_block(gr.top_block): + + def __init__(self): + gr.top_block.__init__(self, "Top Block") + + ################################################## + # Variables + ################################################## + self.samp_rate = samp_rate = 1536000 + self.frequency = frequency = 96900000 + + ################################################## + # Blocks + ################################################## + self.rtlsdr_source_0 = osmosdr.source( args="numchan=" + str(1) + " " + '' ) + self.rtlsdr_source_0.set_sample_rate(samp_rate) + self.rtlsdr_source_0.set_center_freq(frequency, 0) + self.rtlsdr_source_0.set_freq_corr(0, 0) + self.rtlsdr_source_0.set_dc_offset_mode(2, 0) + self.rtlsdr_source_0.set_iq_balance_mode(2, 0) + self.rtlsdr_source_0.set_gain_mode(True, 0) + self.rtlsdr_source_0.set_gain(0, 0) + self.rtlsdr_source_0.set_if_gain(0, 0) + self.rtlsdr_source_0.set_bb_gain(0, 0) + self.rtlsdr_source_0.set_antenna('', 0) + self.rtlsdr_source_0.set_bandwidth(0, 0) + + self.low_pass_filter_0 = filter.fir_filter_ccf(4, firdes.low_pass( + 1, samp_rate, 500000, 60000, firdes.WIN_KAISER, 6.76)) + self.blocks_throttle_0 = blocks.throttle(gr.sizeof_gr_complex*1, samp_rate,True) + self.audio_sink_0 = audio.sink(48000, 'pulse', True) + self.analog_wfm_rcv_0 = analog.wfm_rcv( + quad_rate=384000, + audio_decimation=8, + ) + + ################################################## + # Connections + ################################################## + self.connect((self.analog_wfm_rcv_0, 0), (self.audio_sink_0, 0)) + self.connect((self.blocks_throttle_0, 0), (self.low_pass_filter_0, 0)) + self.connect((self.low_pass_filter_0, 0), (self.analog_wfm_rcv_0, 0)) + self.connect((self.rtlsdr_source_0, 0), (self.blocks_throttle_0, 0)) + + def get_samp_rate(self): + return self.samp_rate + + def set_samp_rate(self, samp_rate): + self.samp_rate = samp_rate + self.rtlsdr_source_0.set_sample_rate(self.samp_rate) + self.low_pass_filter_0.set_taps(firdes.low_pass(1, self.samp_rate, 500000, 60000, firdes.WIN_KAISER, 6.76)) + self.blocks_throttle_0.set_sample_rate(self.samp_rate) + + def get_frequency(self): + return self.frequency + + def set_frequency(self, frequency): + self.frequency = frequency + self.rtlsdr_source_0.set_center_freq(self.frequency, 0) + + +def main(top_block_cls=top_block, options=None): + tb = top_block_cls() + tb.start() + HOST = '127.0.0.1' # Loopback Address (Localhost) + PORT = 65432 # Semi-Random Port that is non-priviledged (> 1023) + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + # Avoid bind() exception: OSError: [Errno 48] Address already in use + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.bind((HOST, PORT)) + quit = False + while not quit: + s.listen(1) # Number of unaccepted connections before it rejects new ones + conn, addr = s.accept() + print("Connected by ", addr) + while not quit: + data = conn.recv(1024) # Not sure about the bytes argument... + data = data.strip() # Removes \r\n + print("DATA: ", data) + commands = re.split(";", data) + if data == "": + break + for command in commands: + if command == "": + continue + elif command[0] == '?': + # Query Section + if command[1:] == "VER": + conn.sendall("Version 0.1\n") + elif command[1:] == "FREQ": + conn.sendall(str(tb.get_frequency()) + '\n') + else: + print("UNKNOWN COMMAND: ", command) + elif command[0] == ':': + # Setter Section + if command[1:5] == "FREQ": + print("FREQUENCY SET") + frequency = float(command[6:]) + if frequency >= 87500000 and frequency <= 108000000: + tb.set_frequency(frequency) + else: + print("ERROR: Invalid Frequency ", frequency) + elif command[1:] == "QUIT": + print("QUITTING") + quit = True + else: + print("UNKNOWN COMMAND: ", command) + else: + print("UNKNOWN COMMAND", command) + + conn.close() + s.close() + + +if __name__ == '__main__': + main()