commit 86b0d078291ff18060440e7cf6bef5d8acb7e398 Author: Brandon Rozek Date: Tue May 14 23:09:55 2019 -0400 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 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()