Compare commits

...

6 Commits

6 changed files with 205 additions and 173 deletions

17
dmi.py
View File

@ -3,6 +3,7 @@ import numpy as np
import gc import gc
from noptica import * from noptica import *
from gui import GUI
def main(): def main():
@ -11,20 +12,26 @@ def main():
freq_sample = 1e6 freq_sample = 1e6
freq_base = 1088230e3 freq_base = 1088230e3
bufsize = 4096 block_size = 4096
gui = GUI(freq_sample, freq_base, block_size)
try:
induction = InductionHeater("/dev/ttyUSB0", 350e3, 445e3) induction = InductionHeater("/dev/ttyUSB0", 350e3, 445e3)
induction.start() induction.start()
try: try:
stabilizer = Stabilizer(bufsize, 80.0, 1088.1e6 - freq_base, 10e-6, induction) def stabilizer_cb(spectrum, peak_freq, tuning):
position_tracker = PositionTracker(int(0.1*freq_sample/bufsize)) gui.update_beat_spectrum(spectrum, peak_freq)
induction.set(tuning)
stabilizer = Stabilizer(block_size, 80.0, 1088.1e6 - freq_base, 10e-6, stabilizer_cb)
position_tracker = PositionTracker(int(0.1*freq_sample/block_size))
sdr = SoapySDR.Device() sdr = SoapySDR.Device()
for channel in range(2): for channel in range(2):
sdr.setSampleRate(SoapySDR.SOAPY_SDR_RX, channel, freq_sample) sdr.setSampleRate(SoapySDR.SOAPY_SDR_RX, channel, freq_sample)
sdr.setFrequency(SoapySDR.SOAPY_SDR_RX, channel, freq_base) sdr.setFrequency(SoapySDR.SOAPY_SDR_RX, channel, freq_base)
buf_sdr = BufferedSDR(sdr, [0, 1], bufsize, 256) buf_sdr = BufferedSDR(sdr, [0, 1], block_size, 256)
buf_sdr.start() buf_sdr.start()
try: try:
stabilizer_throttle = 0 stabilizer_throttle = 0
@ -47,6 +54,8 @@ def main():
buf_sdr.stop() buf_sdr.stop()
finally: finally:
induction.stop() induction.stop()
finally:
gui.close()
if __name__ == "__main__": if __name__ == "__main__":

114
gui.py
View File

@ -1,84 +1,64 @@
import asyncio
import os import os
import logging import sys
import subprocess
import time
import numpy as np import numpy as np
from quamash import QEventLoop, QtWidgets
import pyqtgraph as pg
from sipyco.pipe_ipc import AsyncioChildComm
from sipyco import pyon from sipyco import pyon
class SpectrogramWidget(pg.PlotWidget): class ParentComm:
def __init__(self, block_size=4096): def __init__(self):
super(SpectrogramWidget, self).__init__() self.c_rfd, self.p_wfd = os.pipe()
self.p_rfd, self.c_wfd = os.pipe()
self.rf = open(int(self.p_rfd), "rb", 0)
self.wf = open(int(self.p_wfd), "wb", 0)
self.process = None
self.block_size = block_size def get_address(self):
return "{},{}".format(self.c_rfd, self.c_wfd)
self.img = pg.ImageItem() def read(self, n):
self.addItem(self.img) return self.rf.read(n)
self.img_array = np.zeros((100, block_size)) def readline(self):
return self.rf.readline()
pos = np.array([0., 1., 0.5, 0.25, 0.75]) def write(self, data):
color = np.array([[0,255,255,255], [255,255,0,255], [0,0,0,255], (0, 0, 255, 255), (255, 0, 0, 255)], dtype=np.ubyte) return self.wf.write(data)
cmap = pg.ColorMap(pos, color)
lut = cmap.getLookupTable(0.0, 1.0, 256)
self.img.setLookupTable(lut) def write_pyon(self, obj):
self.img.setLevels([-50,40]) self.write(pyon.encode(obj).encode() + b"\n")
self.show() def close(self):
self.rf.close()
self.wf.close()
if self.process is not None:
self.process.wait()
def update(self, block): def create_subprocess(self, *args):
self.img_array = np.roll(self.img_array, -1, 0) env = os.environ.copy()
self.img_array[-1:] = block env["PYTHONUNBUFFERED"] = "1"
self.img.setImage(self.img_array, autoLevels=True) env["NOPTICA2_IPC"] = self.get_address()
self.process = subprocess.Popen(
*args, pass_fds={self.c_rfd, self.c_wfd},
env=env)
os.close(self.c_rfd)
os.close(self.c_wfd)
class IPCClient(AsyncioChildComm): class GUI:
def set_close_cb(self, close_cb): def __init__(self, freq_sample, freq_base, block_size):
self.close_cb = close_cb self.impl = ParentComm()
self.impl.create_subprocess([sys.executable,
os.path.join(os.path.dirname(os.path.abspath(__file__)), "gui_impl.py"),
str(freq_sample), str(freq_base), str(block_size)])
async def read_pyon(self): def update_beat_spectrum(self, block, peak_freq):
line = await self.readline() obj = {"action": "update_beat_spectrum", "block": block, "peak_freq": peak_freq}
return pyon.decode(line.decode()) self.impl.write_pyon(obj)
async def listen(self, spectrogram): def close(self):
while True: obj = {"action": "terminate"}
obj = await self.read_pyon() self.impl.write_pyon(obj)
try: self.impl.close()
action = obj["action"]
if action == "update":
spectrogram.update(obj["data"])
if action == "terminate":
self.close_cb()
return
except:
logging.error("error processing parent message",
exc_info=True)
self.close_cb()
def main():
app = QtWidgets.QApplication([])
loop = QEventLoop(app)
asyncio.set_event_loop(loop)
try:
ipc = IPCClient(os.getenv("NOPTICA2_IPC"))
loop.run_until_complete(ipc.connect())
try:
main_widget = SpectrogramWidget()
main_widget.show()
ipc.set_close_cb(main_widget.close)
asyncio.ensure_future(ipc.listen(main_widget))
loop.run_forever()
finally:
ipc.close()
finally:
loop.close()
if __name__ == "__main__":
main()

108
gui_impl.py Normal file
View File

@ -0,0 +1,108 @@
import asyncio
import os
import sys
import logging
import numpy as np
from quamash import QEventLoop, QtWidgets, QtCore
import pyqtgraph as pg
from sipyco.pipe_ipc import AsyncioChildComm
from sipyco import pyon
class SpectrogramItem(pg.ImageItem):
def __init__(self, freq_sample, freq_base, block_size):
pg.ImageItem.__init__(self)
depth = 100
self.img_array = np.zeros((depth, block_size))
self.setImage(self.img_array, autoLevels=True)
pos = np.array([0., 1., 0.5, 0.25, 0.75])
color = np.array([[0,255,255,255], [255,255,0,255], [0,0,0,255], (0, 0, 255, 255), (255, 0, 0, 255)], dtype=np.ubyte)
cmap = pg.ColorMap(pos, color)
lut = cmap.getLookupTable(0.0, 1.0, 256)
self.setLookupTable(lut)
self.setRect(QtCore.QRectF(0.0, (freq_base-freq_sample/2)/1e6, float(depth), freq_sample/1e6))
def add_block(self, block):
self.img_array = np.roll(self.img_array, -1, 0)
self.img_array[-1:] = np.fft.fftshift(block)
self.setImage(self.img_array, autoLevels=True)
class MainWindow(pg.GraphicsWindow):
def __init__(self, freq_sample, freq_base, block_size):
pg.GraphicsWindow.__init__(self)
self.setWindowTitle("NOPTICA Wavemeter")
self.freq_sample = freq_sample
self.freq_base = freq_base
self.text = pg.LabelItem()
self.addItem(self.text)
p1 = self.addPlot(row=1, col=0)
self.beat_spectrum = SpectrogramItem(freq_sample, freq_base, block_size)
p1.addItem(self.beat_spectrum)
def update_params(self, peak_freq):
if peak_freq is None:
self.text.setText("REF: NO BEAT")
else:
self.text.setText("REF: {:.3f}MHz".format((self.freq_base + self.freq_sample*peak_freq)/1e6))
class IPCClient(AsyncioChildComm):
def set_close_cb(self, close_cb):
self.close_cb = close_cb
async def read_pyon(self):
line = await self.readline()
return pyon.decode(line.decode())
async def listen(self, main_window):
while True:
obj = await self.read_pyon()
try:
action = obj["action"]
if action == "update_beat_spectrum":
main_window.beat_spectrum.add_block(obj["block"])
main_window.update_params(obj["peak_freq"])
if action == "terminate":
self.close_cb()
return
except:
logging.error("error processing parent message",
exc_info=True)
self.close_cb()
def main():
freq_sample = float(sys.argv[1])
freq_base = float(sys.argv[2])
block_size = int(sys.argv[3])
app = QtWidgets.QApplication([])
loop = QEventLoop(app)
asyncio.set_event_loop(loop)
try:
ipc = IPCClient(os.getenv("NOPTICA2_IPC"))
loop.run_until_complete(ipc.connect())
try:
main_window = MainWindow(freq_sample, freq_base, block_size)
main_window.show()
ipc.set_close_cb(main_window.close)
asyncio.ensure_future(ipc.listen(main_window))
loop.run_forever()
finally:
ipc.close()
finally:
loop.close()
if __name__ == "__main__":
main()

View File

@ -1,68 +0,0 @@
import os
import sys
import subprocess
import time
import numpy as np
from sipyco import pyon
# env QT_QPA_PLATFORM=wayland python gui_test.py
class ParentComm:
def __init__(self):
self.c_rfd, self.p_wfd = os.pipe()
self.p_rfd, self.c_wfd = os.pipe()
self.rf = open(int(self.p_rfd), "rb", 0)
self.wf = open(int(self.p_wfd), "wb", 0)
self.process = None
def get_address(self):
return "{},{}".format(self.c_rfd, self.c_wfd)
def read(self, n):
return self.rf.read(n)
def readline(self):
return self.rf.readline()
def write(self, data):
return self.wf.write(data)
def write_pyon(self, obj):
self.write(pyon.encode(obj).encode() + b"\n")
def close(self):
self.rf.close()
self.wf.close()
if self.process is not None:
self.process.wait()
def create_subprocess(self, *args):
env = os.environ.copy()
env["PYTHONUNBUFFERED"] = "1"
env["NOPTICA2_IPC"] = self.get_address()
self.process = subprocess.Popen(
*args, pass_fds={self.c_rfd, self.c_wfd},
env=env)
os.close(self.c_rfd)
os.close(self.c_wfd)
def main():
gui = ParentComm()
try:
gui.create_subprocess([sys.executable, "gui.py"])
for i in range(600):
obj = {"action": "update", "data": np.random.normal(size=4096)}
gui.write_pyon(obj)
time.sleep(1/60)
obj = {"action": "terminate"}
gui.write_pyon(obj)
finally:
gui.close()
if __name__ == "__main__":
main()

View File

@ -104,25 +104,27 @@ class InductionHeater:
self.thread.join() self.thread.join()
self.serial.close() self.serial.close()
class Stabilizer: class Stabilizer:
def __init__(self, fft_size, amp_threshold, freq_target, k, tuner): def __init__(self, fft_size, amp_threshold, freq_target, k, cb):
self.freqs = np.fft.fftfreq(fft_size) self.freqs = np.fft.fftfreq(fft_size)
self.amp_threshold = amp_threshold self.amp_threshold = amp_threshold
self.freq_target = freq_target self.freq_target = freq_target
self.k = k self.k = k
self.tuner = tuner self.cb = cb
def input(self, samples): def input(self, samples):
spectrum = np.abs(np.fft.fft(samples*blackmanharris(len(samples)))) spectrum = np.abs(np.fft.fft(samples*blackmanharris(len(samples))))
i = np.argmax(spectrum) i = np.argmax(spectrum)
freq = self.freqs[i]
amplitude = spectrum[i] amplitude = spectrum[i]
if amplitude > self.amp_threshold: if amplitude > self.amp_threshold:
freq = self.freqs[i]
tuning = (freq - self.freq_target)*self.k tuning = (freq - self.freq_target)*self.k
else: else:
freq = None
tuning = 0.0 tuning = 0.0
self.tuner.set(tuning) self.cb(spectrum, freq, tuning)
def continuous_unwrap(last_phase, last_phase_unwrapped, p): def continuous_unwrap(last_phase, last_phase_unwrapped, p):

View File

@ -42,4 +42,5 @@ in
${pkgs.libbladeRF}/bin/bladeRF-cli -l ${bitstream} ${pkgs.libbladeRF}/bin/bladeRF-cli -l ${bitstream}
${pkgs.libbladeRF}/bin/bladeRF-cli -e "set biastee rx on" ${pkgs.libbladeRF}/bin/bladeRF-cli -e "set biastee rx on"
''; '';
QT_QPA_PLATFORM = "wayland";
} }