mirror of
https://github.com/slsdetectorgroup/slsDetectorPackage.git
synced 2026-07-09 02:27:22 +02:00
Merge branch 'developer' into dev/rx_disable_datastream_port
This commit is contained in:
@@ -37,7 +37,8 @@ jobs:
|
||||
-DSLS_USE_PYTHON=ON \
|
||||
-DSLS_USE_HDF5=ON \
|
||||
-DSLS_USE_GUI=ON \
|
||||
-DSLS_USE_MOENCH=ON
|
||||
-DSLS_USE_MOENCH=ON \
|
||||
-DSLS_TREAT_WARNINGS_AS_ERRORS=ON
|
||||
|
||||
- name: Build
|
||||
# Build your program with the given configuration
|
||||
|
||||
+12
-3
@@ -236,6 +236,7 @@ option(SLS_USE_MOENCH "compile zmq and post processing for Moench" OFF)
|
||||
option(SLS_USE_JUNGFRAU "compile post processing for Jungfrau" OFF)
|
||||
option(SLS_USE_MATTERHORN "compile matterhorn server" OFF)
|
||||
option(SLS_INSTALL_VERSIONED_BINARIES "Add version number to binaries on install" OFF) #Needed for multi version RPM
|
||||
option(SLS_TREAT_WARNINGS_AS_ERRORS "Treat warnings as errors" OFF)
|
||||
|
||||
#Convenience option to switch off defaults when building Moench binaries only
|
||||
option(SLS_BUILD_ONLY_MOENCH "compile only Moench" OFF)
|
||||
@@ -347,7 +348,9 @@ if (NOT TARGET slsProjectWarnings)
|
||||
-Wno-missing-field-initializers)
|
||||
endif()
|
||||
|
||||
|
||||
if(SLS_TREAT_WARNINGS_AS_ERRORS)
|
||||
target_compile_options(slsProjectWarnings INTERFACE -Werror)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
@@ -376,6 +379,10 @@ if (NOT TARGET slsProjectCSettings)
|
||||
target_compile_options(slsProjectCSettings INTERFACE -fsanitize=address,undefined -fno-omit-frame-pointer)
|
||||
target_link_libraries(slsProjectCSettings INTERFACE -fsanitize=address,undefined)
|
||||
endif()
|
||||
|
||||
if(SLS_TREAT_WARNINGS_AS_ERRORS)
|
||||
target_compile_options(slsProjectCSettings INTERFACE -Werror)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
@@ -483,13 +490,15 @@ if(SLS_BUILD_DOCS)
|
||||
add_subdirectory(docs)
|
||||
endif(SLS_BUILD_DOCS)
|
||||
|
||||
if(SLS_USE_MOENCH)
|
||||
if(SLS_USE_MOENCH OR SLS_USE_JUNGFRAU)
|
||||
add_subdirectory(slsDetectorCalibration/tiffio)
|
||||
endif()
|
||||
|
||||
if(SLS_USE_MOENCH)
|
||||
add_subdirectory(slsDetectorCalibration/moenchExecutables)
|
||||
endif(SLS_USE_MOENCH)
|
||||
|
||||
if(SLS_USE_JUNGFRAU)
|
||||
add_subdirectory(slsDetectorCalibration/tiffio)
|
||||
add_subdirectory(slsDetectorCalibration/jungfrauExecutables)
|
||||
endif(SLS_USE_JUNGFRAU)
|
||||
|
||||
|
||||
@@ -133,6 +133,7 @@ class AcquisitionTab(QtWidgets.QWidget):
|
||||
self.view.labelDigital.setDisabled(True)
|
||||
self.view.labelTransceiver.setDisabled(True)
|
||||
self.view.spinBoxTransceiver.setDisabled(True)
|
||||
self.plotTab.enableallCounterCheckBox(False)
|
||||
case readoutMode.DIGITAL_ONLY:
|
||||
self.view.spinBoxAnalog.setDisabled(True)
|
||||
self.view.labelAnalog.setDisabled(True)
|
||||
@@ -140,6 +141,7 @@ class AcquisitionTab(QtWidgets.QWidget):
|
||||
self.view.labelDigital.setEnabled(True)
|
||||
self.view.labelTransceiver.setDisabled(True)
|
||||
self.view.spinBoxTransceiver.setDisabled(True)
|
||||
self.plotTab.enableallCounterCheckBox(False)
|
||||
case readoutMode.ANALOG_AND_DIGITAL:
|
||||
self.view.spinBoxAnalog.setEnabled(True)
|
||||
self.view.labelAnalog.setEnabled(True)
|
||||
@@ -147,6 +149,7 @@ class AcquisitionTab(QtWidgets.QWidget):
|
||||
self.view.labelDigital.setEnabled(True)
|
||||
self.view.labelTransceiver.setDisabled(True)
|
||||
self.view.spinBoxTransceiver.setDisabled(True)
|
||||
self.plotTab.enableallCounterCheckBox(False)
|
||||
case readoutMode.TRANSCEIVER_ONLY:
|
||||
self.view.spinBoxAnalog.setDisabled(True)
|
||||
self.view.labelAnalog.setDisabled(True)
|
||||
@@ -154,6 +157,7 @@ class AcquisitionTab(QtWidgets.QWidget):
|
||||
self.view.labelDigital.setDisabled(True)
|
||||
self.view.labelTransceiver.setEnabled(True)
|
||||
self.view.spinBoxTransceiver.setEnabled(True)
|
||||
self.plotTab.enableallCounterCheckBox(self.plotTab.view.radioButtonImage.isChecked()) # enable counter checkboxes for matterhorn
|
||||
case _:
|
||||
self.view.spinBoxAnalog.setDisabled(True)
|
||||
self.view.labelAnalog.setDisabled(True)
|
||||
@@ -161,6 +165,7 @@ class AcquisitionTab(QtWidgets.QWidget):
|
||||
self.view.labelDigital.setEnabled(True)
|
||||
self.view.labelTransceiver.setEnabled(True)
|
||||
self.view.spinBoxTransceiver.setEnabled(True)
|
||||
self.plotTab.enableallCounterCheckBox(self.plotTab.view.radioButtonImage.isChecked()) # enable counter checkboxes for matterhorn
|
||||
|
||||
self.view.comboBoxROMode.currentIndexChanged.connect(self.setReadOut)
|
||||
self.view.spinBoxAnalog.editingFinished.connect(self.setAnalog)
|
||||
|
||||
@@ -4,10 +4,10 @@ import random
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
from PyQt5 import QtWidgets, QtGui, uic
|
||||
from PyQt5 import QtWidgets, QtGui, QtCore, uic
|
||||
|
||||
from aare import transform, ReadoutMode
|
||||
from aare._aare import Matterhorn10, Matterhorn02, Moench04
|
||||
from aare._aare import Matterhorn10, Matterhorn02, Moench04, Moench05
|
||||
|
||||
import pyqtgraph as pg
|
||||
from pyctbgui.utils import recordOrApplyPedestal
|
||||
@@ -43,6 +43,7 @@ class PlotTab(QtWidgets.QWidget):
|
||||
self.pedestalApply: bool = True
|
||||
self.__acqFrames = None
|
||||
self.logger = logging.getLogger('PlotTab')
|
||||
self.plotSplitter = None
|
||||
|
||||
def setup_ui(self):
|
||||
self.signalsTab = self.mainWindow.signalsTab
|
||||
@@ -51,10 +52,17 @@ class PlotTab(QtWidgets.QWidget):
|
||||
self.adcTab = self.mainWindow.adcTab
|
||||
self.initializeColorMaps()
|
||||
|
||||
# TODO use list comprehension
|
||||
self.checkBoxCounters = [self.view.checkBoxCounter0, self.view.checkBoxCounter1, self.view.checkBoxCounter2, self.view.checkBoxCounter3]
|
||||
|
||||
for checkBox in self.checkBoxCounters:
|
||||
checkBox.setChecked(False)
|
||||
checkBox.setEnabled(False)
|
||||
|
||||
self.imagePlots = (
|
||||
self.mainWindow.plotAnalogImage,
|
||||
self.mainWindow.plotDigitalImage,
|
||||
self.mainWindow.plotTransceiverImage,
|
||||
self.transceiverTab.transceiverImageViews,
|
||||
)
|
||||
|
||||
def connect_ui(self):
|
||||
@@ -93,12 +101,54 @@ class PlotTab(QtWidgets.QWidget):
|
||||
self.view.radioButtonFixed.clicked.connect(partial(self.setColorRangeMode, Defines.colorRange.fixed))
|
||||
self.view.radioButtonCenter.clicked.connect(partial(self.setColorRangeMode, Defines.colorRange.center))
|
||||
|
||||
for plot in self.imagePlots:
|
||||
plot.scene.sigMouseMoved.connect(partial(self.showPlotValues, plot))
|
||||
plot.getHistogramWidget().item.sigLevelChangeFinished.connect(partial(self.handleHistogramChange, plot))
|
||||
for index, checkBox in enumerate(self.checkBoxCounters):
|
||||
checkBox.stateChanged.connect(partial(self.displayCounter, index))
|
||||
|
||||
# show image Values for analog image
|
||||
nMaxY = lambda : self.mainWindow.nAnalogRows
|
||||
nMaxX = lambda : self.mainWindow.nAnalogCols
|
||||
frame = lambda : self.mainWindow.analog_frame
|
||||
plot = self.mainWindow.plotAnalogImage
|
||||
plot.scene.sigMouseMoved.connect(partial(self.showPlotValues, plot, nMaxX, nMaxY, frame))
|
||||
plot.getHistogramWidget().item.sigLevelChangeFinished.connect(partial(self.handleHistogramChange, plot))
|
||||
|
||||
# show image Values for digital image
|
||||
nMaxY = lambda : self.mainWindow.nDigitalRows
|
||||
nMaxX = lambda : self.mainWindow.nDigitalCols
|
||||
frame = lambda : self.mainWindow.digital_frame
|
||||
plot = self.mainWindow.plotDigitalImage
|
||||
plot.scene.sigMouseMoved.connect(partial(self.showPlotValues, plot, nMaxX, nMaxY, frame))
|
||||
plot.getHistogramWidget().item.sigLevelChangeFinished.connect(partial(self.handleHistogramChange, plot))
|
||||
|
||||
# show image Values for transceiver image
|
||||
nMaxY = lambda : self.transceiverTab.nTransceiverRows
|
||||
nMaxX = lambda : self.transceiverTab.nTransceiverCols
|
||||
|
||||
for index, image_view in enumerate(self.transceiverTab.transceiverImageViews):
|
||||
frame = lambda : image_view.getImageItem().image
|
||||
image_view.scene.sigMouseMoved.connect(partial(self.showPlotValues, image_view, nMaxX, nMaxY, frame))
|
||||
image_view.getHistogramWidget().item.sigLevelChangeFinished.connect(partial(self.handleHistogramChange, image_view))
|
||||
|
||||
self.view.checkBoxShowLegend.stateChanged.connect(self.toggleLegend)
|
||||
|
||||
|
||||
def displayCounter(self, index : int, state : int):
|
||||
# toggle the display of the counter i and update the splitter
|
||||
self.transceiverTab.shownCounters[index] = (state == QtCore.Qt.Checked)
|
||||
self.transceiverTab.update_ImageSplitter()
|
||||
|
||||
def setCounterCheckBox(self, index : int, check : bool):
|
||||
self.checkBoxCounters[index].setChecked(check)
|
||||
|
||||
def enableCounterCheckBox(self, index : int, enabled : bool):
|
||||
is_transceiver = self.mainWindow.romode.value in [3, 4]
|
||||
is_image = self.view.radioButtonImage.isChecked()
|
||||
self.checkBoxCounters[index].setEnabled(enabled and is_transceiver and is_image)
|
||||
|
||||
def enableallCounterCheckBox(self, enabled : bool):
|
||||
for checkBox in self.checkBoxCounters:
|
||||
checkBox.setEnabled(enabled)
|
||||
|
||||
def refresh(self):
|
||||
self.getZMQHWM()
|
||||
|
||||
@@ -262,7 +312,11 @@ class PlotTab(QtWidgets.QWidget):
|
||||
updates UI views should be called after every change to cmin or cmax
|
||||
"""
|
||||
for plot in self.imagePlots:
|
||||
plot.getHistogramWidget().item.setLevels(min=self.cmin, max=self.cmax)
|
||||
if isinstance(plot, list): # hacky for now only transceiver image is a PlotSplitter
|
||||
for p in plot:
|
||||
p.getHistogramWidget().item.setLevels(min=self.cmin, max=self.cmax)
|
||||
else:
|
||||
plot.getHistogramWidget().item.setLevels(min=self.cmin, max=self.cmax)
|
||||
self.view.cminSpinBox.setValue(self.cmin)
|
||||
self.view.cmaxSpinBox.setValue(self.cmax)
|
||||
|
||||
@@ -271,7 +325,9 @@ class PlotTab(QtWidgets.QWidget):
|
||||
# print(f'color map:{self.comboBoxColorMap.currentText()}')
|
||||
self.mainWindow.plotAnalogImage.setColorMap(cm)
|
||||
self.mainWindow.plotDigitalImage.setColorMap(cm)
|
||||
self.mainWindow.plotTransceiverImage.setColorMap(cm)
|
||||
for i, showncounter in enumerate(self.transceiverTab.shownCounters):
|
||||
if showncounter:
|
||||
self.transceiverTab.transceiverImageViews[i].setColorMap(cm)
|
||||
|
||||
def getZMQHWM(self):
|
||||
|
||||
@@ -343,11 +399,11 @@ class PlotTab(QtWidgets.QWidget):
|
||||
self.mainWindow.transceiverPlots[i].hide()
|
||||
|
||||
def addAllSelectedTransceiverPlots(self):
|
||||
for i in range(Defines.transceiver.count):
|
||||
for i in range(Defines.transceiver.maxcount):
|
||||
self.addSelectedTransceiverPlots(i)
|
||||
|
||||
def removeAllTransceiverPlots(self):
|
||||
for i in range(Defines.transceiver.count):
|
||||
for i in range(Defines.transceiver.maxcount):
|
||||
self.mainWindow.transceiverPlots[i].hide()
|
||||
|
||||
def showPlot(self):
|
||||
@@ -356,7 +412,7 @@ class PlotTab(QtWidgets.QWidget):
|
||||
self.mainWindow.plotTransceiverWaveform.hide()
|
||||
self.mainWindow.plotAnalogImage.hide()
|
||||
self.mainWindow.plotDigitalImage.hide()
|
||||
self.mainWindow.plotTransceiverImage.hide()
|
||||
self.transceiverTab.transceiverImageSplitter.hide()
|
||||
self.view.labelDigitalWaveformOption.setDisabled(True)
|
||||
self.view.radioButtonOverlay.setDisabled(True)
|
||||
self.view.radioButtonStripe.setDisabled(True)
|
||||
@@ -379,7 +435,10 @@ class PlotTab(QtWidgets.QWidget):
|
||||
if self.view.radioButtonWaveform.isChecked():
|
||||
self.mainWindow.plotTransceiverWaveform.show()
|
||||
elif self.view.radioButtonImage.isChecked():
|
||||
self.mainWindow.plotTransceiverImage.show()
|
||||
for i, showncounter in enumerate(self.transceiverTab.shownCounters):
|
||||
self.transceiverTab.transceiverImageViews[i].setVisible(showncounter)
|
||||
|
||||
self.transceiverTab.transceiverImageSplitter.show()
|
||||
|
||||
def plotOptions(self):
|
||||
|
||||
@@ -404,14 +463,18 @@ class PlotTab(QtWidgets.QWidget):
|
||||
'bottom', "<span style=\"color:black;font-size:14px\">Transceiver Sample [#]</span>")
|
||||
|
||||
self.view.stackedWidgetPlotType.setCurrentIndex(0)
|
||||
self.enableallCounterCheckBox(False) # disable counter checkboxes for waveform
|
||||
|
||||
elif self.view.radioButtonImage.isChecked():
|
||||
self.view.stackedWidgetPlotType.setCurrentIndex(2)
|
||||
is_transceiver = self.mainWindow.romode.value in [3, 4]
|
||||
self.enableallCounterCheckBox(is_transceiver) # enable counter checkboxes for matterhorn
|
||||
self.setDecoder()
|
||||
|
||||
if self.view.radioButtonNoPlot.isChecked():
|
||||
self.view.labelPlotOptions.hide()
|
||||
self.view.stackedWidgetPlotType.hide()
|
||||
self.enableallCounterCheckBox(False) # disable counter checkboxes when no plot is selected
|
||||
# enable plotting
|
||||
else:
|
||||
self.view.labelPlotOptions.show()
|
||||
@@ -419,45 +482,57 @@ class PlotTab(QtWidgets.QWidget):
|
||||
self.mainWindow.read_timer.start(Defines.Time_Plot_Refresh_ms)
|
||||
|
||||
def setDecoder(self):
|
||||
# TODO: really dont like to set attributes on the fly - hard to understand whats going on
|
||||
if self.view.comboBoxPlot.currentText() == "Matterhorn02":
|
||||
print("Initializing decoder for Matterhorn02")
|
||||
self.mainWindow.nTransceiverRows = Matterhorn02.nRows
|
||||
self.mainWindow.nTransceiverCols = Matterhorn02.nCols
|
||||
self.transceiverTab.nTransceiverRows = Matterhorn02.nRows
|
||||
self.transceiverTab.nTransceiverCols = Matterhorn02.nCols
|
||||
self.transceiverTab.update_numCounters(1)
|
||||
self.mainWindow.decoder = transform.Matterhorn02TransceiverTransform()
|
||||
elif self.view.comboBoxPlot.currentText() == "Matterhorn1_16bit_1_counter":
|
||||
print("Initializing decoder for Matterhorn1 with 1 counter 16 bit dynamic range")
|
||||
self.mainWindow.nTransceiverRows = Matterhorn10.nRows
|
||||
self.mainWindow.nTransceiverCols = Matterhorn10.nCols
|
||||
self.transceiverTab.nTransceiverRows = Matterhorn10.nRows
|
||||
self.transceiverTab.nTransceiverCols = Matterhorn10.nCols
|
||||
self.transceiverTab.update_numCounters(1)
|
||||
self.mainWindow.decoder = transform.Matterhorn10Transform(16, 1)
|
||||
elif self.view.comboBoxPlot.currentText() == "Matterhorn1_16bit_4_counters":
|
||||
print("Initializing decoder for Matterhorn1 with 4 counters 16 bit dynamic range")
|
||||
self.mainWindow.nTransceiverRows = Matterhorn10.nRows*4
|
||||
self.mainWindow.nTransceiverCols = Matterhorn10.nCols
|
||||
self.transceiverTab.nTransceiverRows = Matterhorn10.nRows
|
||||
self.transceiverTab.nTransceiverCols = Matterhorn10.nCols
|
||||
self.transceiverTab.update_numCounters(4)
|
||||
self.mainWindow.decoder = transform.Matterhorn10Transform(16, 4)
|
||||
elif self.view.comboBoxPlot.currentText() == "Matterhorn1_8bit_1_counter":
|
||||
print("Initializing decoder for Matterhorn1 with 1 counter 8 bit dynamic range")
|
||||
self.mainWindow.nTransceiverRows = Matterhorn10.nRows
|
||||
self.mainWindow.nTransceiverCols = Matterhorn10.nCols
|
||||
self.transceiverTab.nTransceiverRows = Matterhorn10.nRows
|
||||
self.transceiverTab.nTransceiverCols = Matterhorn10.nCols
|
||||
self.transceiverTab.update_numCounters(1)
|
||||
self.mainWindow.decoder = transform.Matterhorn10Transform(8, 1)
|
||||
elif self.view.comboBoxPlot.currentText() == "Matterhorn1_8bit_4_counters":
|
||||
print("Initializing decoder for Matterhorn1 with 4 counters 8 bit dynamic range")
|
||||
self.mainWindow.nTransceiverRows = Matterhorn10.nRows*4
|
||||
self.mainWindow.nTransceiverCols = Matterhorn10.nCols
|
||||
self.transceiverTab.nTransceiverRows = Matterhorn10.nRows
|
||||
self.transceiverTab.nTransceiverCols = Matterhorn10.nCols
|
||||
self.transceiverTab.update_numCounters(4)
|
||||
self.mainWindow.decoder = transform.Matterhorn10Transform(8, 4)
|
||||
elif self.view.comboBoxPlot.currentText() == "Matterhorn1_4bit_4_counters":
|
||||
print("Initializing decoder for Matterhorn1 with 4 counters 4 bit dynamic range")
|
||||
self.mainWindow.nTransceiverRows = Matterhorn10.nRows*4
|
||||
self.mainWindow.nTransceiverCols = Matterhorn10.nCols
|
||||
self.transceiverTab.nTransceiverRows = Matterhorn10.nRows
|
||||
self.transceiverTab.nTransceiverCols = Matterhorn10.nCols
|
||||
self.transceiverTab.update_numCounters(4)
|
||||
self.mainWindow.decoder = transform.Matterhorn10Transform(4, 4)
|
||||
elif self.view.comboBoxPlot.currentText() == "Matterhorn1_4bit_1_counter":
|
||||
print("Initializing decoder for Matterhorn1 with 1 counter 4 bit dynamic range")
|
||||
self.mainWindow.nTransceiverRows = Matterhorn10.nRows
|
||||
self.mainWindow.nTransceiverCols = Matterhorn10.nCols
|
||||
self.transceiverTab.nTransceiverRows = Matterhorn10.nRows
|
||||
self.transceiverTab.nTransceiverCols = Matterhorn10.nCols
|
||||
self.transceiverTab.update_numCounters(1)
|
||||
self.mainWindow.decoder = transform.Matterhorn10Transform(4, 1)
|
||||
elif self.view.comboBoxPlot.currentText() == "Moench04":
|
||||
self.mainWindow.nAnalogRows = Moench04.nRows
|
||||
self.mainWindow.nAnalogCols = Moench04.nCols
|
||||
self.mainWindow.decoder = transform.Moench04AnalogTransform()
|
||||
elif self.view.comboBoxPlot.currentText() == "Moench05":
|
||||
self.mainWindow.nAnalogRows = Moench05.nRows
|
||||
self.mainWindow.nAnalogCols = Moench05.nCols
|
||||
self.mainWindow.decoder = transform.Moench05Transform()
|
||||
|
||||
try:
|
||||
if hasattr(self.mainWindow.decoder, "compatibility") and callable(getattr(self.mainWindow.decoder, "compatibility")):
|
||||
@@ -533,21 +608,13 @@ class PlotTab(QtWidgets.QWidget):
|
||||
# get the RGB Values
|
||||
# print(color.getRgb())
|
||||
|
||||
def showPlotValues(self, sender, pos):
|
||||
def showPlotValues(self, sender, get_nMaxX, get_nMaxY, get_frame, pos):
|
||||
x = sender.getImageItem().mapFromScene(pos).x()
|
||||
y = sender.getImageItem().mapFromScene(pos).y()
|
||||
val = 0
|
||||
nMaxY = self.mainWindow.nAnalogRows
|
||||
nMaxX = self.mainWindow.nAnalogCols
|
||||
frame = self.mainWindow.analog_frame
|
||||
if sender == self.mainWindow.plotDigitalImage:
|
||||
nMaxY = self.mainWindow.nDigitalRows
|
||||
nMaxX = self.mainWindow.nDigitalCols
|
||||
frame = self.mainWindow.digital_frame
|
||||
elif sender == self.mainWindow.plotTransceiverImage:
|
||||
nMaxY = self.mainWindow.nTransceiverRows
|
||||
nMaxX = self.mainWindow.nTransceiverCols
|
||||
frame = self.mainWindow.transceiver_frame
|
||||
nMaxX = get_nMaxX()
|
||||
nMaxY = get_nMaxY()
|
||||
frame = get_frame()
|
||||
if 0 <= x < nMaxX and 0 <= y < nMaxY and not np.array_equal(frame, []):
|
||||
val = frame[int(y), int(x)]
|
||||
message = f'[row, col]: [{y:.2f}, {x:.2f}] = {val:.2f}'
|
||||
|
||||
@@ -2,7 +2,7 @@ from functools import partial
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
from PyQt5 import QtWidgets, uic
|
||||
from PyQt5 import QtWidgets, QtCore, uic
|
||||
import pyqtgraph as pg
|
||||
from pyqtgraph import LegendItem
|
||||
|
||||
@@ -24,11 +24,18 @@ class TransceiverTab(QtWidgets.QWidget):
|
||||
self.plotTab = None
|
||||
self.legend: LegendItem | None = None
|
||||
self.acquisitionTab = None
|
||||
|
||||
self.nCounters: int = Defines.transceiver.maxcount
|
||||
self.nTransceiverRows : int = 0
|
||||
self.nTransceiverCols : int = 0
|
||||
self.shownCounters: list[bool] = [True] * Defines.transceiver.maxcount # per default show all 4 counters
|
||||
self.transceiverImageSplitter = QtWidgets.QSplitter(QtCore.Qt.Vertical)
|
||||
self.transceiverImageViews : list = [] # pg image view for each counter
|
||||
self.firstTransceiverImage : list[bool] = [True] * Defines.transceiver.maxcount # to keep track of first image for each counter to maintain zoom state
|
||||
|
||||
def setup_ui(self):
|
||||
self.plotTab = self.mainWindow.plotTab
|
||||
self.acquisitionTab = self.mainWindow.acquisitionTab
|
||||
for i in range(Defines.transceiver.count):
|
||||
for i in range(Defines.transceiver.maxcount):
|
||||
self.setTransceiverButtonColor(i, self.plotTab.getRandomColor())
|
||||
self.initializeAllTransceiverPlots()
|
||||
|
||||
@@ -39,7 +46,7 @@ class TransceiverTab(QtWidgets.QWidget):
|
||||
self.plotTab.subscribeToggleLegend(self.updateLegend)
|
||||
|
||||
def connect_ui(self):
|
||||
for i in range(Defines.transceiver.count):
|
||||
for i in range(Defines.transceiver.maxcount):
|
||||
getattr(self.view, f"checkBoxTransceiver{i}").stateChanged.connect(partial(self.setTransceiverEnable, i))
|
||||
getattr(self.view,
|
||||
f"checkBoxTransceiver{i}Plot").stateChanged.connect(partial(self.setTransceiverEnablePlot, i))
|
||||
@@ -55,7 +62,7 @@ class TransceiverTab(QtWidgets.QWidget):
|
||||
"""
|
||||
enabledPlots = []
|
||||
self.legend.clear()
|
||||
for i in range(Defines.transceiver.count):
|
||||
for i in range(Defines.transceiver.maxcount):
|
||||
if getattr(self.view, f'checkBoxTransceiver{i}Plot').isChecked():
|
||||
plotName = getattr(self.view, f"labelTransceiver{i}").text()
|
||||
enabledPlots.append((self.mainWindow.transceiverPlots[i], plotName))
|
||||
@@ -104,7 +111,7 @@ class TransceiverTab(QtWidgets.QWidget):
|
||||
trans_array = self._processWaveformData(data, dSamples, self.mainWindow.romode.value,
|
||||
self.mainWindow.nDBitEnabled, self.nTransceiverEnabled)
|
||||
idx = 0
|
||||
for i in range(Defines.transceiver.count):
|
||||
for i in range(Defines.transceiver.maxcount):
|
||||
checkBoxPlot = getattr(self.view, f"checkBoxTransceiver{i}Plot")
|
||||
checkBoxEn = getattr(self.view, f"checkBoxTransceiver{i}")
|
||||
if checkBoxEn.isChecked() and checkBoxPlot.isChecked():
|
||||
@@ -133,9 +140,7 @@ class TransceiverTab(QtWidgets.QWidget):
|
||||
transceiverOffset += nDBitEnabled * (nbitsPerDBit // 8)
|
||||
trans_array = np.array(np.frombuffer(data, offset=transceiverOffset, dtype=np.uint8))
|
||||
|
||||
tmp = self.mainWindow.decoder(trans_array)
|
||||
|
||||
return tmp
|
||||
return self.mainWindow.decoder(trans_array)
|
||||
|
||||
def processImageData(self, data, dSamples):
|
||||
"""
|
||||
@@ -145,28 +150,47 @@ class TransceiverTab(QtWidgets.QWidget):
|
||||
data: raw image data
|
||||
"""
|
||||
# get zoom state
|
||||
viewBox = self.mainWindow.plotTransceiverImage.getView()
|
||||
|
||||
state = viewBox.getState()
|
||||
image_states = [image_view.getView().getState() for image_view in self.transceiverImageViews]
|
||||
|
||||
transceiver_frame : np.ndarray = None
|
||||
try:
|
||||
self.mainWindow.transceiver_frame = self._processImageData(data, dSamples, self.mainWindow.romode.value,
|
||||
transceiver_frame = self._processImageData(data, dSamples, self.mainWindow.romode.value,
|
||||
self.mainWindow.nDBitEnabled)
|
||||
self.plotTab.ignoreHistogramSignal = True
|
||||
self.mainWindow.plotTransceiverImage.setImage(self.mainWindow.transceiver_frame)
|
||||
|
||||
for i in range(transceiver_frame.shape[0]):
|
||||
self.transceiverImageViews[i].setImage(transceiver_frame[i])
|
||||
except Exception as e:
|
||||
self.mainWindow.statusbar.setStyleSheet("color:red")
|
||||
self.acquisitionTab.updateCurrentFrame('Invalid Image')
|
||||
self.mainWindow.statusbar.showMessage(str(e))
|
||||
print("Error: ", str(e))
|
||||
|
||||
self.plotTab.setFrameLimits(self.mainWindow.transceiver_frame)
|
||||
self.plotTab.setFrameLimits(transceiver_frame)
|
||||
|
||||
# keep the zoomed in state (not 1st image)
|
||||
if self.mainWindow.firstTransceiverImage:
|
||||
self.mainWindow.firstTransceiverImage = False
|
||||
else:
|
||||
viewBox.setState(state)
|
||||
return self.mainWindow.transceiver_frame
|
||||
for idx, image_view in enumerate(self.transceiverImageViews):
|
||||
if(self.firstTransceiverImage[idx] and self.shownCounters[idx]):
|
||||
self.firstTransceiverImage[idx] = False
|
||||
else:
|
||||
image_view.getView().setState(image_states[idx])
|
||||
|
||||
return transceiver_frame
|
||||
|
||||
def update_numCounters(self, num_counters):
|
||||
# update the number of counters and adjust the image splitter accordingly
|
||||
self.nCounters = num_counters
|
||||
for i in range(Defines.transceiver.maxcount):
|
||||
self.shownCounters[i] = i < self.nCounters
|
||||
self.plotTab.setCounterCheckBox(i, self.shownCounters[i]) # check the counter checkbox
|
||||
self.plotTab.enableCounterCheckBox(i, self.shownCounters[i]) # disable counter checkbox
|
||||
|
||||
self.update_ImageSplitter() # update the splitter to show/hide image views based on the number of counters
|
||||
|
||||
|
||||
def update_ImageSplitter(self):
|
||||
for i, showncounter in enumerate(self.shownCounters):
|
||||
self.transceiverImageViews[i].setVisible(showncounter)
|
||||
|
||||
def initializeAllTransceiverPlots(self):
|
||||
self.mainWindow.plotTransceiverWaveform = pg.plot()
|
||||
@@ -174,7 +198,7 @@ class TransceiverTab(QtWidgets.QWidget):
|
||||
self.mainWindow.verticalLayoutPlot.addWidget(self.mainWindow.plotTransceiverWaveform, 5)
|
||||
self.mainWindow.transceiverPlots = {}
|
||||
waveform = np.zeros(1000)
|
||||
for i in range(Defines.transceiver.count):
|
||||
for i in range(Defines.transceiver.maxcount):
|
||||
pen = pg.mkPen(color=self.getTransceiverButtonColor(i), width=1)
|
||||
legendName = getattr(self.view, f"labelTransceiver{i}").text()
|
||||
self.mainWindow.transceiverPlots[i] = self.mainWindow.plotTransceiverWaveform.plot(waveform,
|
||||
@@ -182,16 +206,19 @@ class TransceiverTab(QtWidgets.QWidget):
|
||||
name=legendName)
|
||||
self.mainWindow.transceiverPlots[i].hide()
|
||||
|
||||
self.mainWindow.plotTransceiverImage = pg.ImageView()
|
||||
self.mainWindow.nTransceiverRows = 0
|
||||
self.mainWindow.nTransceiverCols = 0
|
||||
self.mainWindow.transceiver_frame = np.zeros(
|
||||
(self.mainWindow.nTransceiverRows, self.mainWindow.nTransceiverCols))
|
||||
self.mainWindow.plotTransceiverImage.setImage(self.mainWindow.transceiver_frame)
|
||||
self.mainWindow.verticalLayoutPlot.addWidget(self.mainWindow.plotTransceiverImage, 6)
|
||||
|
||||
# initialize image
|
||||
cm = pg.colormap.get('CET-L9') # prepare a linear color map
|
||||
self.mainWindow.plotTransceiverImage.setColorMap(cm)
|
||||
|
||||
for i in range(self.nCounters):
|
||||
imageView = pg.ImageView()
|
||||
imageView.setColorMap(cm)
|
||||
self.transceiverImageViews.append(imageView)
|
||||
self.transceiverImageSplitter.addWidget(imageView)
|
||||
|
||||
self.update_ImageSplitter() # update the splitter
|
||||
self.mainWindow.verticalLayoutPlot.addWidget(self.transceiverImageSplitter, 6)
|
||||
|
||||
|
||||
|
||||
def getTransceiverEnableReg(self):
|
||||
retval = self.det.transceiverenable
|
||||
|
||||
+1118
-654
File diff suppressed because it is too large
Load Diff
@@ -35,8 +35,8 @@ class Defines:
|
||||
loops_count = 6
|
||||
|
||||
class transceiver:
|
||||
count = 4
|
||||
tabIndex = 4
|
||||
maxcount = 4
|
||||
maxtabIndex = 4
|
||||
|
||||
class slowAdc:
|
||||
tabIndex = 2
|
||||
|
||||
@@ -76,6 +76,14 @@ foreach(exe ${MOENCH_EXECUTABLES})
|
||||
slsProjectOptions
|
||||
)
|
||||
|
||||
target_compile_options(${exe} PRIVATE
|
||||
-Wno-unused-but-set-variable
|
||||
-Wno-format-nonliteral
|
||||
-Wno-format-security
|
||||
-Wno-double-promotion
|
||||
-Wno-unused-variable
|
||||
-Wno-format-overflow)
|
||||
|
||||
|
||||
set_target_properties(${exe} PROPERTIES
|
||||
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
|
||||
|
||||
@@ -204,9 +204,9 @@ int main(int argc, char *argv[]) {
|
||||
*/
|
||||
//#endif
|
||||
|
||||
if (totquad > cmin && cl.x >= xmin && cl.x <= xmax &&
|
||||
if (totquad > static_cast<double>(cmin) && cl.x >= xmin && cl.x <= xmax &&
|
||||
cl.y >= ymin && cl.y <= ymax &&
|
||||
totquad < cmax) {
|
||||
totquad < static_cast<double>(cmax)) {
|
||||
|
||||
// if (sum > cmin && totquad / sum > 0.8 && totquad / sum < 1.2 &&
|
||||
// sum < cmax) {
|
||||
|
||||
@@ -21,6 +21,7 @@ class ClientSocket : public DataSocket {
|
||||
|
||||
private:
|
||||
void readReply(int &ret, void *retval, size_t retval_size);
|
||||
[[noreturn]] void throwError(const std::string &msg) const;
|
||||
struct sockaddr_in serverAddr {};
|
||||
std::string socketType;
|
||||
};
|
||||
|
||||
@@ -77,8 +77,6 @@ class DataSocket {
|
||||
|
||||
std::string Receive(size_t length);
|
||||
|
||||
int read(void *buffer, size_t size);
|
||||
int write(void *buffer, size_t size);
|
||||
int setTimeOut(int t_seconds);
|
||||
int setReceiveTimeout(int us);
|
||||
void close();
|
||||
@@ -88,6 +86,8 @@ class DataSocket {
|
||||
private:
|
||||
int sockfd_ = -1;
|
||||
int fnum_{0};
|
||||
|
||||
std::string_view errno_name(int e);
|
||||
};
|
||||
|
||||
}; // namespace sls
|
||||
|
||||
@@ -96,4 +96,6 @@ bool replace_first(std::string *s, const std::string &substr,
|
||||
|
||||
std::pair<std::string, uint16_t> ParseHostPort(const std::string &s);
|
||||
|
||||
std::string to_lower(const std::string &s);
|
||||
|
||||
} // namespace sls
|
||||
|
||||
@@ -5,9 +5,11 @@
|
||||
#include "sls/sls_detector_defs.h"
|
||||
#include "sls/sls_detector_exceptions.h"
|
||||
#include "sls/sls_detector_funcs.h"
|
||||
#include "sls/string_utils.h"
|
||||
#include <arpa/inet.h>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <fmt/format.h>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <unistd.h>
|
||||
@@ -24,9 +26,10 @@ ClientSocket::ClientSocket(std::string stype, const std::string &host,
|
||||
hints.ai_flags |= AI_CANONNAME;
|
||||
|
||||
if (getaddrinfo(host.c_str(), nullptr, &hints, &result) != 0) {
|
||||
std::string msg = "ClientSocket cannot decode host:" + host +
|
||||
" on port " + std::to_string(port) + "\n";
|
||||
throw SocketError(msg);
|
||||
|
||||
|
||||
auto msg = fmt::format("Cannot resolve {} hostname: '{}'", to_lower(socketType), host);
|
||||
throwError(msg);
|
||||
}
|
||||
|
||||
// TODO! Erik, results could have multiple entries do we need to loop
|
||||
@@ -40,10 +43,11 @@ ClientSocket::ClientSocket(std::string stype, const std::string &host,
|
||||
if (::connect(getSocketId(), (struct sockaddr *)&serverAddr,
|
||||
sizeof(serverAddr)) != 0) {
|
||||
freeaddrinfo(result);
|
||||
std::string msg = "ClientSocket: Cannot connect to " + socketType +
|
||||
":" + host + " on port " + std::to_string(port) +
|
||||
"\n";
|
||||
throw SocketError(msg);
|
||||
auto msg = fmt::format(
|
||||
"Cannot connect to {} on {}:{}\n",
|
||||
to_lower(socketType), host, port);
|
||||
|
||||
throwError(msg);
|
||||
}
|
||||
freeaddrinfo(result);
|
||||
}
|
||||
@@ -54,10 +58,18 @@ ClientSocket::ClientSocket(std::string sType, struct sockaddr_in addr)
|
||||
if (::connect(getSocketId(), (struct sockaddr *)&addr, sizeof(addr)) != 0) {
|
||||
char address[INET_ADDRSTRLEN];
|
||||
inet_ntop(AF_INET, &addr.sin_addr, address, INET_ADDRSTRLEN);
|
||||
std::string msg = "ClientSocket: Cannot connect to " + socketType +
|
||||
":" + address + " on port " +
|
||||
std::to_string(addr.sin_port) + "\n";
|
||||
throw SocketError(msg);
|
||||
auto msg = fmt::format("Cannot connect to {} on {}:{}", to_lower(socketType), address, addr.sin_port);
|
||||
throwError(msg);
|
||||
}
|
||||
}
|
||||
|
||||
void ClientSocket::throwError(const std::string &msg) const {
|
||||
if (socketType == "Receiver") {
|
||||
throw ReceiverError(msg);
|
||||
} else if (socketType == "Detector") {
|
||||
throw DetectorError(msg);
|
||||
} else {
|
||||
throw GuiError(msg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,26 +92,15 @@ void ClientSocket::readReply(int &ret, void *retval, size_t retval_size) {
|
||||
std::string mess = readErrorMessage();
|
||||
// Do we need to know hostname here?
|
||||
// In that case save it???
|
||||
if (socketType == "Receiver") {
|
||||
throw ReceiverError("Receiver returned: " + std::string(mess));
|
||||
} else if (socketType == "Detector") {
|
||||
throw DetectorError("Detector returned: " + std::string(mess));
|
||||
} else {
|
||||
throw GuiError(mess);
|
||||
}
|
||||
throwError(socketType + " returned: " + mess);
|
||||
}
|
||||
// get retval
|
||||
Receive(retval, retval_size);
|
||||
}
|
||||
// debugging
|
||||
catch (SocketError &e) {
|
||||
if (socketType == "Receiver") {
|
||||
throw ReceiverError("Receiver returned: " + std::string(e.what()));
|
||||
} else if (socketType == "Detector") {
|
||||
throw DetectorError("Detector returned: " + std::string(e.what()));
|
||||
} else {
|
||||
throw GuiError(e.what());
|
||||
}
|
||||
auto msg = fmt::format("While reading reply from {} {}", to_lower(socketType), e.what());
|
||||
throwError(msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-other
|
||||
// Copyright (C) 2021 Contributors to the SLS Detector Package
|
||||
#include "sls/DataSocket.h"
|
||||
#include "sls/Timer.h"
|
||||
#include "sls/logger.h"
|
||||
#include "sls/sls_detector_exceptions.h"
|
||||
#include "sls/sls_detector_funcs.h"
|
||||
#include <algorithm>
|
||||
#include <arpa/inet.h>
|
||||
#include <cassert>
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <fcntl.h>
|
||||
#include <iostream>
|
||||
@@ -21,6 +23,14 @@ namespace sls {
|
||||
DataSocket::DataSocket(int socketId) : sockfd_(socketId) {
|
||||
int value = 1;
|
||||
setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value));
|
||||
#ifdef SO_NOSIGPIPE
|
||||
// macOS/BSD: suppress SIGPIPE when sending to a peer that closed the
|
||||
// connection, so a failed send returns an error instead of killing the
|
||||
// process. On Linux we instead pass MSG_NOSIGNAL to send() (see Send()).
|
||||
int nosigpipe = 1;
|
||||
setsockopt(sockfd_, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe,
|
||||
sizeof(nosigpipe));
|
||||
#endif
|
||||
}
|
||||
|
||||
DataSocket::~DataSocket() {
|
||||
@@ -36,6 +46,7 @@ DataSocket::~DataSocket() {
|
||||
|
||||
void DataSocket::swap(DataSocket &other) noexcept {
|
||||
std::swap(sockfd_, other.sockfd_);
|
||||
std::swap(fnum_, other.fnum_);
|
||||
}
|
||||
|
||||
DataSocket::DataSocket(DataSocket &&move) noexcept { move.swap(*this); }
|
||||
@@ -48,23 +59,34 @@ void DataSocket::setFnum(const int fnum) { fnum_ = fnum; }
|
||||
|
||||
int DataSocket::Receive(void *buffer, size_t size) {
|
||||
// TODO!(Erik) Add sleep? how many reties?
|
||||
int bytes_expected = static_cast<int>(size); // signed size
|
||||
int bytes_read = 0;
|
||||
ssize_t bytes_expected = static_cast<ssize_t>(size);
|
||||
ssize_t bytes_read = 0;
|
||||
ssize_t this_read = 0; // last read result, kept for diagnostics
|
||||
Timer timer;
|
||||
while (bytes_read < bytes_expected) {
|
||||
auto this_read =
|
||||
this_read =
|
||||
::read(getSocketId(), reinterpret_cast<char *>(buffer) + bytes_read,
|
||||
bytes_expected - bytes_read);
|
||||
if (this_read < 0 && errno == EINTR)
|
||||
continue; // interrupted by a signal, retry
|
||||
if (this_read <= 0)
|
||||
break;
|
||||
bytes_read += this_read;
|
||||
}
|
||||
if (bytes_read == bytes_expected) {
|
||||
return bytes_read;
|
||||
return static_cast<int>(bytes_read);
|
||||
} else {
|
||||
int err = errno; // capture before any other call can clobber it
|
||||
std::ostringstream ss;
|
||||
ss << "TCP socket read " << bytes_read << " bytes instead of "
|
||||
<< bytes_expected << " bytes ("
|
||||
<< getFunctionNameFromEnum(static_cast<detFuncs>(fnum_)) << ')';
|
||||
if (this_read == 0)
|
||||
ss << ": connection closed by peer (EOF)";
|
||||
else if (this_read < 0)
|
||||
ss << ": read error: " << std::strerror(err) << " ("
|
||||
<< errno_name(err) << ")";
|
||||
ss << " after " << timer.elapsed_ms() << " ms";
|
||||
throw SocketError(ss.str());
|
||||
}
|
||||
}
|
||||
@@ -78,38 +100,51 @@ std::string DataSocket::Receive(size_t length) {
|
||||
return buff;
|
||||
}
|
||||
int DataSocket::Send(const void *buffer, size_t size) {
|
||||
int bytes_sent = 0;
|
||||
int data_size = static_cast<int>(size); // signed size
|
||||
while (bytes_sent < (data_size)) {
|
||||
auto this_send = ::write(getSocketId(), buffer, size);
|
||||
ssize_t bytes_expected = static_cast<ssize_t>(size);
|
||||
ssize_t bytes_sent = 0;
|
||||
ssize_t this_send = 0; // last write result, kept for diagnostics
|
||||
// Linux: avoid SIGPIPE on a broken connection by using send() with
|
||||
// MSG_NOSIGNAL. macOS/BSD lack the flag and use SO_NOSIGPIPE instead
|
||||
// (set in the constructor).
|
||||
#ifdef MSG_NOSIGNAL
|
||||
const int send_flags = MSG_NOSIGNAL;
|
||||
#else
|
||||
const int send_flags = 0;
|
||||
#endif
|
||||
Timer timer;
|
||||
while (bytes_sent < bytes_expected) {
|
||||
this_send = ::send(
|
||||
getSocketId(),
|
||||
reinterpret_cast<const char *>(buffer) + bytes_sent,
|
||||
bytes_expected - bytes_sent, send_flags);
|
||||
if (this_send < 0 && errno == EINTR)
|
||||
continue; // interrupted by a signal, retry
|
||||
if (this_send <= 0)
|
||||
break;
|
||||
bytes_sent += this_send;
|
||||
}
|
||||
if (bytes_sent != data_size) {
|
||||
if (bytes_sent == bytes_expected) {
|
||||
return static_cast<int>(bytes_sent);
|
||||
} else {
|
||||
int err = errno; // capture before any other call can clobber it
|
||||
std::ostringstream ss;
|
||||
ss << "TCP socket sent " << bytes_sent << " bytes instead of "
|
||||
<< data_size << " bytes ("
|
||||
<< bytes_expected << " bytes ("
|
||||
<< getFunctionNameFromEnum(static_cast<detFuncs>(fnum_)) << ')';
|
||||
if (this_send < 0)
|
||||
ss << ": write error: " << std::strerror(err) << " ("
|
||||
<< errno_name(err) << ")";
|
||||
ss << " after " << timer.elapsed_ms() << " ms";
|
||||
throw SocketError(ss.str());
|
||||
}
|
||||
return bytes_sent;
|
||||
}
|
||||
|
||||
int DataSocket::Send(const std::string &s) { return Send(&s[0], s.size()); }
|
||||
|
||||
int DataSocket::write(void *buffer, size_t size) {
|
||||
return ::write(getSocketId(), buffer, size);
|
||||
}
|
||||
|
||||
int DataSocket::read(void *buffer, size_t size) {
|
||||
return ::read(getSocketId(), buffer, size);
|
||||
}
|
||||
|
||||
int DataSocket::setReceiveTimeout(int us) {
|
||||
timeval t{};
|
||||
t.tv_sec = 0;
|
||||
t.tv_usec = us;
|
||||
t.tv_sec = us / 1000000;
|
||||
t.tv_usec = us % 1000000;
|
||||
return ::setsockopt(getSocketId(), SOL_SOCKET, SO_RCVTIMEO, &t,
|
||||
sizeof(struct timeval));
|
||||
}
|
||||
@@ -140,7 +175,9 @@ int DataSocket::setTimeOut(int t_seconds) {
|
||||
void DataSocket::close() {
|
||||
if (sockfd_ > 0) {
|
||||
if (::close(sockfd_)) {
|
||||
throw SocketError("could not close socket");
|
||||
std::stringstream ss;
|
||||
ss << "could not close socket (fd: " << sockfd_ << ")";
|
||||
throw SocketError(ss.str());
|
||||
}
|
||||
sockfd_ = -1;
|
||||
} else {
|
||||
@@ -155,4 +192,61 @@ void DataSocket::shutDownSocket() {
|
||||
|
||||
void DataSocket::shutdown() { ::shutdown(sockfd_, SHUT_RDWR); }
|
||||
|
||||
std::string_view DataSocket::errno_name(int e) {
|
||||
switch (e) {
|
||||
#ifdef EACCES
|
||||
case EACCES:
|
||||
return "EACCES";
|
||||
#endif
|
||||
#ifdef EAGAIN
|
||||
case EAGAIN:
|
||||
return "EAGAIN";
|
||||
#endif
|
||||
#ifdef EBADF
|
||||
case EBADF:
|
||||
return "EBADF";
|
||||
#endif
|
||||
#ifdef ECONNABORTED
|
||||
case ECONNABORTED:
|
||||
return "ECONNABORTED";
|
||||
#endif
|
||||
#ifdef ECONNREFUSED
|
||||
case ECONNREFUSED:
|
||||
return "ECONNREFUSED";
|
||||
#endif
|
||||
#ifdef ECONNRESET
|
||||
case ECONNRESET:
|
||||
return "ECONNRESET";
|
||||
#endif
|
||||
#ifdef EINPROGRESS
|
||||
case EINPROGRESS:
|
||||
return "EINPROGRESS";
|
||||
#endif
|
||||
#ifdef EINTR
|
||||
case EINTR:
|
||||
return "EINTR";
|
||||
#endif
|
||||
#ifdef EINVAL
|
||||
case EINVAL:
|
||||
return "EINVAL";
|
||||
#endif
|
||||
#ifdef EPIPE
|
||||
case EPIPE:
|
||||
return "EPIPE";
|
||||
#endif
|
||||
#ifdef ETIMEDOUT
|
||||
case ETIMEDOUT:
|
||||
return "ETIMEDOUT";
|
||||
#endif
|
||||
#ifdef EWOULDBLOCK
|
||||
#if EWOULDBLOCK != EAGAIN
|
||||
case EWOULDBLOCK:
|
||||
return "EWOULDBLOCK";
|
||||
#endif
|
||||
#endif
|
||||
default:
|
||||
return "UNKNOWN_ERRNO";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace sls
|
||||
|
||||
@@ -10,16 +10,16 @@ namespace sls {
|
||||
int ServerInterface::sendResult(int ret, void *retval, int retvalSize,
|
||||
char *mess) {
|
||||
|
||||
write(&ret, sizeof(ret));
|
||||
Send(&ret, sizeof(ret));
|
||||
if (ret == defs::FAIL) {
|
||||
if (mess != nullptr) {
|
||||
write(mess, MAX_STR_LENGTH);
|
||||
Send(mess, MAX_STR_LENGTH);
|
||||
} else {
|
||||
LOG(logERROR) << "No error message provided for this "
|
||||
"failure. Will mess up TCP\n";
|
||||
}
|
||||
} else {
|
||||
write(retval, retvalSize);
|
||||
Send(retval, retvalSize);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -75,4 +75,11 @@ std::pair<std::string, uint16_t> ParseHostPort(const std::string &s) {
|
||||
return std::make_pair(host, port);
|
||||
}
|
||||
|
||||
std::string to_lower(const std::string &s) {
|
||||
std::string result = s;
|
||||
std::transform(result.begin(), result.end(), result.begin(),
|
||||
[](unsigned char c) { return std::tolower(c); });
|
||||
return result;
|
||||
}
|
||||
|
||||
}; // namespace sls
|
||||
@@ -3,37 +3,117 @@
|
||||
#include "catch.hpp"
|
||||
#include "sls/ClientSocket.h"
|
||||
#include "sls/ServerSocket.h"
|
||||
#include "sls/Timer.h"
|
||||
#include "sls/sls_detector_defs.h"
|
||||
#include "sls/sls_detector_exceptions.h"
|
||||
#include "sls/sls_detector_funcs.h"
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <future>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/time.h>
|
||||
#include <thread>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace sls {
|
||||
|
||||
std::vector<char> server() {
|
||||
std::cout << "starting server\n";
|
||||
auto server = ServerSocket(1950);
|
||||
// One configurable test server: accept a connection, read a 100-byte request,
|
||||
// reply with `bytes_to_send` bytes (first two set to 'O','K'), optionally wait
|
||||
// `hold` so the client can time out, then close. Returns the received request.
|
||||
std::vector<char> echo_server(uint16_t port, size_t bytes_to_send,
|
||||
std::chrono::milliseconds hold) {
|
||||
std::cout << "starting server on port " << port << '\n';
|
||||
auto server = ServerSocket(port);
|
||||
auto s = server.accept();
|
||||
std::vector<char> buffer(100, '\0');
|
||||
s.Receive(buffer.data(), buffer.size());
|
||||
std::cout << "ServerReceived: " << std::string(buffer.begin(), buffer.end())
|
||||
<< '\n';
|
||||
|
||||
std::vector<char> to_send(100, '\0');
|
||||
to_send[0] = 'O';
|
||||
to_send[1] = 'K';
|
||||
s.Send(to_send.data(), to_send.size());
|
||||
if (port==1960){
|
||||
struct linger ling = {
|
||||
.l_onoff = 1,
|
||||
.l_linger = 0
|
||||
};
|
||||
|
||||
auto fd = s.getSocketId();
|
||||
setsockopt(fd, SOL_SOCKET, SO_LINGER, &ling, sizeof ling);
|
||||
::close(fd);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
if (bytes_to_send > 0) {
|
||||
std::vector<char> to_send(bytes_to_send, '\0');
|
||||
to_send[0] = 'O';
|
||||
to_send[1] = 'K';
|
||||
s.Send(to_send.data(), to_send.size());
|
||||
}
|
||||
std::this_thread::sleep_for(hold);
|
||||
s.close();
|
||||
return buffer;
|
||||
}
|
||||
|
||||
// Minimal command server speaking the same protocol as sendCommandThenRead:
|
||||
// accept a connection, read the function number and a single int argument,
|
||||
// then reply via ServerInterface::sendResult with OK and (arg * 2). Returns
|
||||
// the {fnum, arg} pair the server received so the test can verify them.
|
||||
std::pair<int, int> command_server(uint16_t port) {
|
||||
auto server = ServerSocket(port);
|
||||
auto s = server.accept();
|
||||
int fnum = -1;
|
||||
int arg = 0;
|
||||
s.Receive(&fnum, sizeof(fnum));
|
||||
s.Receive(&arg, sizeof(arg));
|
||||
int retval = arg * 2;
|
||||
s.sendResult(slsDetectorDefs::OK, &retval, sizeof(retval));
|
||||
s.close();
|
||||
return {fnum, arg};
|
||||
}
|
||||
|
||||
// Command server that replies with a truncated message: it reads the request,
|
||||
// sends the OK return code, but then sends fewer retval bytes than the client
|
||||
// expects before closing, so the client's Receive hits EOF mid-read.
|
||||
void short_reply_server(uint16_t port, size_t retval_bytes_to_send) {
|
||||
auto server = ServerSocket(port);
|
||||
auto s = server.accept();
|
||||
int fnum = -1;
|
||||
int arg = 0;
|
||||
s.Receive(&fnum, sizeof(fnum));
|
||||
s.Receive(&arg, sizeof(arg));
|
||||
int ret = slsDetectorDefs::OK;
|
||||
s.Send(&ret, sizeof(ret));
|
||||
if (retval_bytes_to_send > 0) {
|
||||
std::vector<char> partial(retval_bytes_to_send, '\0');
|
||||
s.Send(partial.data(), partial.size());
|
||||
}
|
||||
s.close();
|
||||
}
|
||||
|
||||
// Server that accepts a connection but never reads from it, so a client
|
||||
// trying to send more than fits in the kernel buffers will stall. A small
|
||||
// receive buffer keeps the amount the test must send modest. Stays open until
|
||||
// the client signals it is done (or a safety timeout) so it never closes
|
||||
// mid-transfer and races the client's Send.
|
||||
void non_reading_server(uint16_t port, std::atomic<bool> *client_done) {
|
||||
auto server = ServerSocket(port);
|
||||
auto s = server.accept();
|
||||
int rcvbuf = 1024;
|
||||
setsockopt(s.getSocketId(), SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf));
|
||||
// Intentionally never Receive(): let the client's send path back up.
|
||||
Timer t;
|
||||
while (!client_done->load() && t.elapsed_ms() < 10000)
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
s.close();
|
||||
}
|
||||
|
||||
TEST_CASE("The server recive the same message as we send", "[support]") {
|
||||
std::vector<char> received_message(100, '\0');
|
||||
std::vector<char> sent_message(100, '\0');
|
||||
const char m[]{"some message"};
|
||||
std::copy(std::begin(m), std::end(m), sent_message.data());
|
||||
|
||||
auto s = std::async(std::launch::async, server);
|
||||
auto s = std::async(std::launch::async, echo_server, 1950, 100,
|
||||
std::chrono::milliseconds(0));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
auto client = DetectorSocket("localhost", 1950);
|
||||
client.Send(sent_message.data(), sent_message.size());
|
||||
@@ -48,6 +128,207 @@ TEST_CASE("The server recive the same message as we send", "[support]") {
|
||||
|
||||
TEST_CASE("throws on no server", "[support]") {
|
||||
CHECK_THROWS(DetectorSocket("localhost", 1950));
|
||||
CHECK_THROWS(ReceiverSocket("localhost", 1950));
|
||||
CHECK_THROWS(GuiSocket("localhost", 1950));
|
||||
}
|
||||
|
||||
TEST_CASE("Receiving a too short message throws and reports EOF", "[support]") {
|
||||
std::vector<char> received_message(100, '\0');
|
||||
std::vector<char> sent_message(100, '\0');
|
||||
const char m[]{"some message"};
|
||||
std::copy(std::begin(m), std::end(m), sent_message.data());
|
||||
|
||||
// Server replies with only 10 of the 100 expected bytes, then closes.
|
||||
auto s = std::async(std::launch::async, echo_server, 1951, 10,
|
||||
std::chrono::milliseconds(0));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
auto client = DetectorSocket("localhost", 1951);
|
||||
client.Send(sent_message.data(), sent_message.size());
|
||||
|
||||
client.setFnum(F_GET_SERVER_VERSION);
|
||||
|
||||
// The server only sends 10 of the 100 expected bytes and then closes the
|
||||
// connection, so Receive must throw a SocketError reporting the EOF.
|
||||
std::string error_message;
|
||||
try {
|
||||
client.Receive(received_message.data(), received_message.size());
|
||||
FAIL("Receive should have thrown on a too short message");
|
||||
} catch (const SocketError &e) {
|
||||
error_message = e.what();
|
||||
}
|
||||
client.close();
|
||||
s.get();
|
||||
|
||||
CHECK_THAT(error_message,
|
||||
Catch::Matchers::Contains("read 10 bytes instead of 100 bytes"));
|
||||
CHECK_THAT(error_message,
|
||||
Catch::Matchers::Contains("connection closed by peer (EOF)"));
|
||||
}
|
||||
|
||||
TEST_CASE("Receiving with a socket error throws and reports the error",
|
||||
"[support]") {
|
||||
std::vector<char> received_message(100, '\0');
|
||||
std::vector<char> sent_message(100, '\0');
|
||||
const char m[]{"some message"};
|
||||
std::copy(std::begin(m), std::end(m), sent_message.data());
|
||||
|
||||
// Server stays silent (sends nothing) but keeps the connection open long
|
||||
// enough for the client to time out, so the read fails with an error.
|
||||
auto s = std::async(std::launch::async, echo_server, 1952, 0,
|
||||
std::chrono::milliseconds(500));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
auto client = DetectorSocket("localhost", 1952);
|
||||
client.Send(sent_message.data(), sent_message.size());
|
||||
|
||||
// Force read() to fail with EAGAIN/EWOULDBLOCK instead of returning EOF by
|
||||
// setting a short receive timeout while the server stays silent.
|
||||
client.setReceiveTimeout(100000); // 100 ms
|
||||
|
||||
std::string error_message;
|
||||
try {
|
||||
client.Receive(received_message.data(), received_message.size());
|
||||
FAIL("Receive should have thrown on a socket error");
|
||||
} catch (const SocketError &e) {
|
||||
error_message = e.what();
|
||||
}
|
||||
client.close();
|
||||
s.get();
|
||||
|
||||
CHECK_THAT(error_message,
|
||||
Catch::Matchers::Contains("read 0 bytes instead of 100 bytes"));
|
||||
CHECK_THAT(error_message, Catch::Matchers::Contains("read error:"));
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("Socket crash?", "[support]") {
|
||||
std::vector<char> received_message(100, '\0');
|
||||
std::vector<char> sent_message(100, '\0');
|
||||
const char m[]{"some message"};
|
||||
std::copy(std::begin(m), std::end(m), sent_message.data());
|
||||
|
||||
auto s = std::async(std::launch::async, echo_server, 1960, 100,
|
||||
std::chrono::milliseconds(0));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
auto client = DetectorSocket("localhost", 1960);
|
||||
client.Send(sent_message.data(), sent_message.size());
|
||||
|
||||
|
||||
REQUIRE_THROWS(client.Receive(received_message.data(), received_message.size()));
|
||||
|
||||
//Now try to send more
|
||||
// client.Send(sent_message.data(), sent_message.size());
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("ClientSocket throws on invalid hostname", "[support]") {
|
||||
CHECK_THROWS(ReceiverSocket("invalidhostname", 1950));
|
||||
CHECK_THROWS(DetectorSocket("invalidhostname", 1950));
|
||||
CHECK_THROWS(GuiSocket("invalidhostname", 1950));
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("Using DetectorSocket to talk to a Server Socket", "[support]") {
|
||||
constexpr uint16_t port = 1961;
|
||||
constexpr int fnum = F_GET_DETECTOR_TYPE;
|
||||
constexpr int arg = 21;
|
||||
|
||||
auto s = std::async(std::launch::async, command_server, port);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
auto client = DetectorSocket("localhost", port);
|
||||
int retval = 0;
|
||||
int ret =
|
||||
client.sendCommandThenRead(fnum, &arg, sizeof(arg), &retval,
|
||||
sizeof(retval));
|
||||
client.close();
|
||||
|
||||
auto server_received = s.get();
|
||||
|
||||
// Client got OK and the expected return value back from the server
|
||||
CHECK(ret == slsDetectorDefs::OK);
|
||||
CHECK(retval == arg * 2);
|
||||
// Server received the function number and argument we sent
|
||||
CHECK(server_received.first == fnum);
|
||||
CHECK(server_received.second == arg);
|
||||
// close() resets the underlying fd
|
||||
CHECK(client.getSocketId() == -1);
|
||||
}
|
||||
|
||||
TEST_CASE("ServerSocket replies with a too short message", "[support]") {
|
||||
constexpr uint16_t port = 1962;
|
||||
constexpr int fnum = F_GET_DETECTOR_TYPE;
|
||||
constexpr int arg = 21;
|
||||
|
||||
// Server sends the OK return code, then only 1 of the 4 expected retval
|
||||
// bytes before closing.
|
||||
auto s = std::async(std::launch::async, short_reply_server, port, 1);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
auto client = DetectorSocket("localhost", port);
|
||||
int retval = 0;
|
||||
|
||||
std::string error_message;
|
||||
try {
|
||||
client.sendCommandThenRead(fnum, &arg, sizeof(arg), &retval,
|
||||
sizeof(retval));
|
||||
FAIL("sendCommandThenRead should have thrown on a too short message");
|
||||
} catch (const DetectorError &e) {
|
||||
error_message = e.what();
|
||||
}
|
||||
client.close();
|
||||
s.get();
|
||||
|
||||
// The client read only 1 of the 4 expected retval bytes and then hit EOF.
|
||||
CHECK_THAT(error_message,
|
||||
Catch::Matchers::Contains("read 1 bytes instead of 4 bytes"));
|
||||
CHECK_THAT(error_message,
|
||||
Catch::Matchers::Contains("connection closed by peer (EOF)"));
|
||||
}
|
||||
|
||||
TEST_CASE("Client cannot send the expected number of bytes", "[support]") {
|
||||
constexpr uint16_t port = 1963;
|
||||
|
||||
// Server accepts but never reads; it stays open until we tell it the
|
||||
// client is done, so it cannot close mid-transfer.
|
||||
std::atomic<bool> client_done{false};
|
||||
auto s = std::async(std::launch::async, non_reading_server, port,
|
||||
&client_done);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
auto client = DetectorSocket("localhost", port);
|
||||
|
||||
// Shrink the send buffer and add a short send timeout so the write stalls
|
||||
// and returns before all the data is sent.
|
||||
int sndbuf = 4096;
|
||||
setsockopt(client.getSocketId(), SOL_SOCKET, SO_SNDBUF, &sndbuf,
|
||||
sizeof(sndbuf));
|
||||
struct timeval tv {};
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = 300000; // 300 ms
|
||||
setsockopt(client.getSocketId(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
|
||||
|
||||
// Much more than fits in the (shrunken) send + receive buffers, so the
|
||||
// send cannot complete while the server refuses to read.
|
||||
std::vector<char> big_message(8 * 1024 * 1024, '\0');
|
||||
|
||||
std::string error_message;
|
||||
try {
|
||||
client.Send(big_message.data(), big_message.size());
|
||||
FAIL("Send should have thrown when it could not send all bytes");
|
||||
} catch (const SocketError &e) {
|
||||
error_message = e.what();
|
||||
}
|
||||
client_done = true;
|
||||
client.close();
|
||||
s.get();
|
||||
|
||||
// Fewer bytes were sent than expected, reported as a write error (the
|
||||
// send timed out with EAGAIN/EWOULDBLOCK).
|
||||
CHECK_THAT(error_message, Catch::Matchers::Contains("bytes instead of"));
|
||||
CHECK_THAT(error_message, Catch::Matchers::Contains("write error:"));
|
||||
}
|
||||
|
||||
} // namespace sls
|
||||
|
||||
@@ -123,6 +123,33 @@ TEST_CASE("port missing") {
|
||||
REQUIRE(res.second == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("to_lower converts uppercase to lowercase") {
|
||||
REQUIRE(to_lower("HELLO") == "hello");
|
||||
REQUIRE(to_lower("Hello World") == "hello world");
|
||||
}
|
||||
|
||||
TEST_CASE("to_lower leaves an already lowercase string unchanged") {
|
||||
REQUIRE(to_lower("already lower") == "already lower");
|
||||
}
|
||||
|
||||
TEST_CASE("to_lower only affects alphabetic characters") {
|
||||
REQUIRE(to_lower("ABC123!?_-XYZ") == "abc123!?_-xyz");
|
||||
}
|
||||
|
||||
TEST_CASE("to_lower on an empty string returns an empty string") {
|
||||
REQUIRE(to_lower("").empty());
|
||||
}
|
||||
|
||||
TEST_CASE("to_lower does not modify the original string") {
|
||||
std::string original = "MixedCase";
|
||||
auto result = to_lower(original);
|
||||
REQUIRE(result == "mixedcase");
|
||||
// the source string must be untouched
|
||||
REQUIRE(original == "MixedCase");
|
||||
}
|
||||
|
||||
|
||||
|
||||
// TEST_CASE("concat things not being strings")
|
||||
|
||||
} // namespace sls
|
||||
|
||||
Reference in New Issue
Block a user