Compare commits

...

6 Commits

6 changed files with 205 additions and 173 deletions

67
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,42 +12,50 @@ def main():
freq_sample = 1e6 freq_sample = 1e6
freq_base = 1088230e3 freq_base = 1088230e3
bufsize = 4096 block_size = 4096
induction = InductionHeater("/dev/ttyUSB0", 350e3, 445e3) gui = GUI(freq_sample, freq_base, block_size)
induction.start()
try: try:
stabilizer = Stabilizer(bufsize, 80.0, 1088.1e6 - freq_base, 10e-6, induction) induction = InductionHeater("/dev/ttyUSB0", 350e3, 445e3)
position_tracker = PositionTracker(int(0.1*freq_sample/bufsize)) induction.start()
sdr = SoapySDR.Device()
for channel in range(2):
sdr.setSampleRate(SoapySDR.SOAPY_SDR_RX, channel, freq_sample)
sdr.setFrequency(SoapySDR.SOAPY_SDR_RX, channel, freq_base)
buf_sdr = BufferedSDR(sdr, [0, 1], bufsize, 256)
buf_sdr.start()
try: try:
stabilizer_throttle = 0 def stabilizer_cb(spectrum, peak_freq, tuning):
while True: gui.update_beat_spectrum(spectrum, peak_freq)
buffers = buf_sdr.get() induction.set(tuning)
try:
samples_ref, samples_meas = buffers
# We can't update faster than the MHS5200A serial interface stabilizer = Stabilizer(block_size, 80.0, 1088.1e6 - freq_base, 10e-6, stabilizer_cb)
stabilizer_throttle += 1 position_tracker = PositionTracker(int(0.1*freq_sample/block_size))
if stabilizer_throttle == 8:
stabilizer.input(samples_ref)
stabilizer_throttle = 0
#position, leakage = position_tracker.input(samples_ref, samples_meas) sdr = SoapySDR.Device()
#print(np.sum(position)/len(position), leakage) for channel in range(2):
finally: sdr.setSampleRate(SoapySDR.SOAPY_SDR_RX, channel, freq_sample)
buf_sdr.dispose(buffers) sdr.setFrequency(SoapySDR.SOAPY_SDR_RX, channel, freq_base)
buf_sdr = BufferedSDR(sdr, [0, 1], block_size, 256)
buf_sdr.start()
try:
stabilizer_throttle = 0
while True:
buffers = buf_sdr.get()
try:
samples_ref, samples_meas = buffers
# We can't update faster than the MHS5200A serial interface
stabilizer_throttle += 1
if stabilizer_throttle == 8:
stabilizer.input(samples_ref)
stabilizer_throttle = 0
#position, leakage = position_tracker.input(samples_ref, samples_meas)
#print(np.sum(position)/len(position), leakage)
finally:
buf_sdr.dispose(buffers)
finally:
buf_sdr.stop()
finally: finally:
buf_sdr.stop() induction.stop()
finally: finally:
induction.stop() gui.close()
if __name__ == "__main__": if __name__ == "__main__":

124
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):
def __init__(self, block_size=4096):
super(SpectrogramWidget, self).__init__()
self.block_size = block_size
self.img = pg.ImageItem()
self.addItem(self.img)
self.img_array = np.zeros((100, block_size))
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.img.setLookupTable(lut)
self.img.setLevels([-50,40])
self.show()
def update(self, block):
self.img_array = np.roll(self.img_array, -1, 0)
self.img_array[-1:] = block
self.img.setImage(self.img_array, autoLevels=True)
class IPCClient(AsyncioChildComm): class ParentComm:
def set_close_cb(self, close_cb): def __init__(self):
self.close_cb = close_cb 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
async def read_pyon(self): def get_address(self):
line = await self.readline() return "{},{}".format(self.c_rfd, self.c_wfd)
return pyon.decode(line.decode())
async def listen(self, spectrogram): def read(self, n):
while True: return self.rf.read(n)
obj = await self.read_pyon()
try: def readline(self):
action = obj["action"] return self.rf.readline()
if action == "update":
spectrogram.update(obj["data"]) def write(self, data):
if action == "terminate": return self.wf.write(data)
self.close_cb()
return def write_pyon(self, obj):
except: self.write(pyon.encode(obj).encode() + b"\n")
logging.error("error processing parent message",
exc_info=True) def close(self):
self.close_cb() 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(): class GUI:
app = QtWidgets.QApplication([]) def __init__(self, freq_sample, freq_base, block_size):
loop = QEventLoop(app) self.impl = ParentComm()
asyncio.set_event_loop(loop) 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)])
try: def update_beat_spectrum(self, block, peak_freq):
ipc = IPCClient(os.getenv("NOPTICA2_IPC")) obj = {"action": "update_beat_spectrum", "block": block, "peak_freq": peak_freq}
loop.run_until_complete(ipc.connect()) self.impl.write_pyon(obj)
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()
def close(self):
if __name__ == "__main__": obj = {"action": "terminate"}
main() self.impl.write_pyon(obj)
self.impl.close()

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";
} }