diff --git a/.github/workflows/cmake.yaml b/.github/workflows/cmake.yaml
index 1c4d7b0f6..d3eeee0bd 100644
--- a/.github/workflows/cmake.yaml
+++ b/.github/workflows/cmake.yaml
@@ -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
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8830eb86e..d29e4782b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -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)
diff --git a/pyctbgui/pyctbgui/services/Acquisition.py b/pyctbgui/pyctbgui/services/Acquisition.py
index b32b8a659..dcf06707d 100644
--- a/pyctbgui/pyctbgui/services/Acquisition.py
+++ b/pyctbgui/pyctbgui/services/Acquisition.py
@@ -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)
diff --git a/pyctbgui/pyctbgui/services/Plot.py b/pyctbgui/pyctbgui/services/Plot.py
index a14ef1a0a..688265484 100644
--- a/pyctbgui/pyctbgui/services/Plot.py
+++ b/pyctbgui/pyctbgui/services/Plot.py
@@ -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', "Transceiver Sample [#]")
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}'
diff --git a/pyctbgui/pyctbgui/services/Transceiver.py b/pyctbgui/pyctbgui/services/Transceiver.py
index 13bb72fca..828decd26 100644
--- a/pyctbgui/pyctbgui/services/Transceiver.py
+++ b/pyctbgui/pyctbgui/services/Transceiver.py
@@ -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
diff --git a/pyctbgui/pyctbgui/ui/plot.ui b/pyctbgui/pyctbgui/ui/plot.ui
index 848d48f16..01d2b5ec1 100644
--- a/pyctbgui/pyctbgui/ui/plot.ui
+++ b/pyctbgui/pyctbgui/ui/plot.ui
@@ -6,8 +6,8 @@
0
0
- 860
- 800
+ 1937
+ 1104
@@ -25,7 +25,7 @@
10
50
841
- 141
+ 161
@@ -35,13 +35,10 @@
QFrame::Raised
- -
-
-
- true
-
+
-
+
- Type:
+ Options:
@@ -50,6 +47,12 @@
false
+
+
+ 0
+ 31
+
+
Distribution
@@ -58,28 +61,193 @@
- -
-
-
- Qt::Vertical
+
-
+
+
+ true
-
+
- 20
- 40
+ 0
+ 31
-
+
+ Waveform
+
+
+ true
+
+
+ buttonGroup
+
+
+
+ -
+
+
+
+ 0
+ 31
+
+
+
+ Counter 2
+
+
+
+ -
+
+
+
+ 0
+ 31
+
+
+
+ Counter 3
+
+
+
+ -
+
+
+ true
+
+
+
+ 0
+ 31
+
+
+
+ No Plot
+
+
+ false
+
+
+ buttonGroup
+
+
+
+ -
+
+
+
+ 0
+ 31
+
+
+
+ Counter 0
+
+
+
+ -
+
+
+ true
+
+
+
+ 0
+ 31
+
+
+
+ <html><head/><body><p>If set to high readout, zmq HWM is set to 2 and buffer size to 1MB to drop zmq packets to catch up.</p><p>If set to low readout (default), zmq HWM is set to zmq default (1000) and buffer size to os default to not drop any zmq packets.</p></body></html>
+
+
-
+
+ Low - drop no zmq packet
+
+
+ -
+
+ High - drop zmq packets to catch up
+
+
+
+
+ -
+
+
+ true
+
+
+
+ 0
+ 31
+
+
+
+ Type:
+
+
+
+ -
+
+
+
+ 150
+ 16777215
+
+
+
+ <html><head/><body><p>If set to high readout, zmq HWM is set to 2 and buffer size to 1MB to drop zmq packets to catch up.</p><p>If set to low readout (default), zmq HWM is set to zmq default (1000) and buffer size to os default to not drop any zmq packets.</p></body></html>
+
+
+ Readout speed:
+
+
+
+ -
+
+
+ true
+
+
+
+ 0
+ 31
+
+
+
+ Image
+
+
+ buttonGroup
+
+
-
+
+
+ 16777215
+ 31
+
+
- 2
+ 0
+
+ 0
+
+
+ 0
+
-
+
+
+ 0
+ 31
+
+
Digital plot:
@@ -87,6 +255,12 @@
-
+
+
+ 0
+ 31
+
+
Overlay
@@ -110,6 +284,12 @@
-
+
+
+ 0
+ 31
+
+
show legend
@@ -120,6 +300,12 @@
-
+
+
+ 0
+ 31
+
+
Stripe
@@ -149,6 +335,42 @@
+
+ 0
+
+
+ 0
+
+
-
+
+
+ Decoder:
+
+
+
+ -
+
+
+ Color Map:
+
+
+
+ -
+
+
+ true
+
+
+
+ 120
+ 31
+
+
+
+ Color map of the image
+
+
+
-
@@ -202,6 +424,11 @@
Moench04
+
+ -
+
+ Moench05
+
-
@@ -210,35 +437,6 @@
- -
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
- -
-
-
- true
-
-
-
- 120
- 31
-
-
-
- Color map of the image
-
-
-
-
@@ -255,292 +453,661 @@
- -
-
-
- Decoder:
+
-
+
+
+ Qt::Horizontal
-
-
- -
-
-
- Color Map:
+
+
+ 40
+ 20
+
-
+
- -
-
+
-
+
+
+
+ 0
+ 31
+
+
- Options:
+ Counter 1
- -
-
-
- true
-
-
- No Plot
-
-
- false
-
-
- buttonGroup
-
-
-
- -
-
-
- true
-
+
-
+
0
31
-
- <html><head/><body><p>If set to high readout, zmq HWM is set to 2 and buffer size to 1MB to drop zmq packets to catch up.</p><p>If set to low readout (default), zmq HWM is set to zmq default (1000) and buffer size to os default to not drop any zmq packets.</p></body></html>
-
-
-
-
- Low - drop no zmq packet
-
-
- -
-
- High - drop zmq packets to catch up
-
-
-
-
- -
-
<html><head/><body><p>If set to high readout, zmq HWM is set to 2 and buffer size to 1MB to drop zmq packets to catch up.</p><p>If set to low readout (default), zmq HWM is set to zmq default (1000) and buffer size to os default to not drop any zmq packets.</p></body></html>
- Readout speed:
+ Counters:
- -
-
-
- true
-
-
- Image
-
-
- buttonGroup
-
-
-
- -
-
-
- true
-
-
- Waveform
-
-
- true
-
-
- buttonGroup
-
-
-
- 0
- 480
+ 10
+ 440
841
- 311
+ 400
+
+
+ 0
+ 400
+
+
+
+
+ 16777215
+ 16777215
+
+
QFrame::StyledPanel
QFrame::Sunken
-
- -
-
+
+
-
+
false
+
+
+ 0
+ 31
+
+
+
+
+ 16777215
+ 16777215
+
+
Fit Panel ADC:
- -
-
+
-
+
false
-
- Max:
+
+
+ 0
+ 31
+
-
-
- -
-
-
- false
+
+
+ 16777215
+ 16777215
+
-
- Values
-
-
-
- -
-
-
- false
+
+ QFrame::NoFrame
Dynamic Range:
- -
-
+
-
+
false
+
+
+ 0
+ 31
+
+
+
+
+ 16777215
+ 16777215
+
+
- Y:
+ Serial Offset:
- -
-
+
-
+
false
+
+
+ 100
+ 31
+
+
+
+
+ 45
+ 16777215
+
+
Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
- -
-
+
-
+
false
+
+
+ 100
+ 31
+
+
+
+
+ 16777215
+ 16777215
+
+
Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
- -
-
+
-
+
false
+
+
+ 0
+ 0
+
+
+
+
+ 16777215
+ 16777215
+
+
Min:
- -
-
+
-
+
false
+
+
+ 245
+ 31
+
+
+
+
+ 200
+ 16777215
+
+
- X:
+ Pedestal Subtract
- -
+
-
false
+
+
+ 100
+ 31
+
+
+
+
+ 45
+ 16777215
+
+
Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
- -
-
+
-
+
false
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+ 0
+ 31
+
+
+
+ Raw Data
- -
-
+
-
+
false
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+ 0
+ 31
+
+
+
+
+ 16777215
+ 16777215
+
+
+
+ Min:
- -
+
-
+
+
+ false
+
+
+
+ 0
+ 31
+
+
+
+
+ 16777215
+ 16777215
+
+
+
+ N Counters:
+
+
+
+ -
+
+
+ false
+
+
+
+ 0
+ 0
+
+
+
+
+ 16777215
+ 16777215
+
+
+
+ Max:
+
+
+
+ -
false
+
+
+ 100
+ 31
+
+
+
+
+ 45
+ 16777215
+
+
Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
- -
-
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
false
+
+
+ 100
+ 31
+
+
+
+
+ 45
+ 16777215
+
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ false
+
+
+
+ 0
+ 31
+
+
Image Pixels:
- -
+
-
+
+
+ false
+
+
+
+ 100
+ 31
+
+
+
+
+ 45
+ 16777215
+
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ false
+
+
+
+ 0
+ 31
+
+
+
+
+ 16777215
+ 16777215
+
+
+
+ X:
+
+
+
+ -
false
+
+
+ 100
+ 31
+
+
+
+
+ 16777215
+ 16777215
+
+
Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
- -
+
-
+
+
+ false
+
+
+
+ 100
+ 31
+
+
+
+
+ 16777215
+ 16777215
+
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ false
+
+
+
+ 100
+ 31
+
+
+
+
+ 45
+ 16777215
+
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
false
+
+
+ 100
+ 31
+
+
+
+
+ 45
+ 16777215
+
+
Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
- -
-
+
-
+
+
+ false
+
+
+
+ 0
+ 0
+
+
+
+
+ 16777215
+ 16777215
+
+
+
+ Plot Bit:
+
+
+
+ -
+
+
+ false
+
+
+
+ 115
+ 0
+
+
+
+
+ 156
+ 16777215
+
+
+
+ Max:
+
+
+
+ -
+
+
+ false
+
+
+
+ 0
+ 31
+
+
+
+
+ 16777215
+ 31
+
+
+
+ Y:
+
+
+
+ -
+
+
+ false
+
+
+
+ 100
+ 31
+
+
+
+
+ 45
+ 16777215
+
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ false
+
+
+ X
+
+
+
+ -
+
false
@@ -549,77 +1116,17 @@
- -
-
+
-
+
false
- Min:
+ Values
- -
-
-
- false
-
-
- X
-
-
-
- -
-
-
- false
-
-
- Max:
-
-
-
- -
-
-
- false
-
-
- Serial offset:
-
-
-
- -
-
-
- false
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
- -
-
-
- false
-
-
- Plot Bit:
-
-
-
- -
-
-
- false
-
-
- Raw Data
-
-
-
- -
+
-
@@ -635,241 +1142,223 @@
- -
-
-
- false
-
-
- Pedestal Subtract
-
-
-
- -
-
-
- false
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
- -
-
-
- false
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
- -
-
-
- false
-
-
- N Counters:
-
-
-
10
- 219
+ 221
841
- 81
+ 91
+
+
+ 0
+ 0
+
+
QFrame::StyledPanel
QFrame::Raised
-
-
-
- 0
- -7
- 841
- 111
-
+
+
+ 9
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 40
- 20
-
-
-
-
- -
-
-
- Color Range:
-
-
-
- -
-
-
-
-
- 0
- 0
- 121
- 91
-
-
-
-
-
-
-
-
- 16777215
- 15
-
-
-
- All
-
-
- true
-
-
- buttonGroup_4
-
-
-
- -
-
-
- true
-
-
-
- 16777215
- 15
-
-
-
- 3-97%
-
-
- buttonGroup_4
-
-
-
- -
-
-
-
- 16777215
- 15
-
-
-
- Fixed
-
-
- buttonGroup_4
-
-
-
-
-
-
-
- -
-
-
-
- 50
- 16777215
-
-
-
- min:
-
-
-
- -
-
-
- false
-
-
- -1000.000000000000000
-
-
- 100000.000000000000000
-
-
-
- -
-
-
-
- 50
- 16777215
-
-
-
- max:
-
-
-
- -
-
-
- false
-
-
- 100000.000000000000000
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 40
- 20
-
-
-
-
-
-
+
+ 9
+
+ -
+
+
+ QFrame::NoFrame
+
+
+ QFrame::Plain
+
+
+
-
+
+
+ true
+
+
+
+ 16777215
+ 15
+
+
+
+ 3-97%
+
+
+ buttonGroup_4
+
+
+
+ -
+
+
+
+ 16777215
+ 15
+
+
+
+ All
+
+
+ true
+
+
+ buttonGroup_4
+
+
+
+ -
+
+
+
+ 16777215
+ 15
+
+
+
+ Fixed
+
+
+ buttonGroup_4
+
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 31
+
+
+
+
+ 16777215
+ 31
+
+
+
+ Color Range:
+
+
+
+ -
+
+
+ false
+
+
+
+ 0
+ 31
+
+
+
+ -1000.000000000000000
+
+
+ 100000.000000000000000
+
+
+
+ -
+
+
+
+ 50
+ 31
+
+
+
+ max:
+
+
+
+ -
+
+
+ false
+
+
+
+ 0
+ 31
+
+
+
+ 100000.000000000000000
+
+
+
+ -
+
+
+
+ 50
+ 31
+
+
+
+ min:
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 40
+ 20
+
+
+
+
+
10
- 330
+ 322
841
- 101
+ 108
@@ -878,208 +1367,183 @@
QFrame::Raised
-
-
-
- 0
- 0
- 841
- 51
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 40
- 20
-
-
-
-
- -
-
-
- true
-
-
- Pedestal:
-
-
-
- -
-
-
-
- 250
- 0
-
-
-
- recorded frames: 0
-
-
-
- -
-
-
- true
-
-
- Record
-
-
- true
-
-
- false
-
-
- buttonGroup_3
-
-
-
- -
-
-
- true
-
-
- Apply
-
-
- true
-
-
- buttonGroup_3
-
-
-
- -
-
-
- true
-
-
-
- 0
- 31
-
-
-
- background-color: rgb(199, 213, 207);
-
-
- Reset
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Fixed
-
-
-
- 40
- 20
-
-
-
-
-
-
-
-
-
- -1
- 49
- 841
- 51
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
- -
-
-
-
- 120
- 31
-
-
-
- background-color: rgb(199, 213, 207);
-
-
- Load Pedestal
-
-
-
- -
-
-
-
- 120
- 31
-
-
-
- background-color: rgb(199, 213, 207);
-
-
- Save Pedestal
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::Preferred
-
-
-
- 40
- 20
-
-
-
-
-
-
+
+ -
+
+
+ true
+
+
+
+ 16777215
+ 31
+
+
+
+ Apply
+
+
+ true
+
+
+ buttonGroup_3
+
+
+
+ -
+
+
+ true
+
+
+
+ 16777215
+ 31
+
+
+
+ Record
+
+
+ true
+
+
+ false
+
+
+ buttonGroup_3
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 16777215
+ 31
+
+
+
+ recorded frames: 0
+
+
+
+ -
+
+
+ true
+
+
+
+ 0
+ 31
+
+
+
+ background-color: rgb(199, 213, 207);
+
+
+ Reset
+
+
+
+ -
+
+
+ true
+
+
+
+ 150
+ 0
+
+
+
+
+ 16777215
+ 31
+
+
+
+ Pedestal:
+
+
+
+ -
+
+
+ QFrame::NoFrame
+
+
+ QFrame::Raised
+
+
+
-
+
+
+
+ 120
+ 31
+
+
+
+ background-color: rgb(199, 213, 207);
+
+
+ Save Pedestal
+
+
+
+ -
+
+
+
+ 120
+ 31
+
+
+
+ background-color: rgb(199, 213, 207);
+
+
+ Load Pedestal
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+
+
+
-
diff --git a/pyctbgui/pyctbgui/utils/defines.py b/pyctbgui/pyctbgui/utils/defines.py
index 2226b5c33..b4ecfc04e 100644
--- a/pyctbgui/pyctbgui/utils/defines.py
+++ b/pyctbgui/pyctbgui/utils/defines.py
@@ -35,8 +35,8 @@ class Defines:
loops_count = 6
class transceiver:
- count = 4
- tabIndex = 4
+ maxcount = 4
+ maxtabIndex = 4
class slowAdc:
tabIndex = 2
diff --git a/slsDetectorCalibration/moenchExecutables/CMakeLists.txt b/slsDetectorCalibration/moenchExecutables/CMakeLists.txt
index 44b212df3..55faa558d 100644
--- a/slsDetectorCalibration/moenchExecutables/CMakeLists.txt
+++ b/slsDetectorCalibration/moenchExecutables/CMakeLists.txt
@@ -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
diff --git a/slsDetectorCalibration/moenchExecutables/moench03Interpolation.cpp b/slsDetectorCalibration/moenchExecutables/moench03Interpolation.cpp
index cbbe32746..dc3e71583 100644
--- a/slsDetectorCalibration/moenchExecutables/moench03Interpolation.cpp
+++ b/slsDetectorCalibration/moenchExecutables/moench03Interpolation.cpp
@@ -204,9 +204,9 @@ int main(int argc, char *argv[]) {
*/
//#endif
- if (totquad > cmin && cl.x >= xmin && cl.x <= xmax &&
+ if (totquad > static_cast(cmin) && cl.x >= xmin && cl.x <= xmax &&
cl.y >= ymin && cl.y <= ymax &&
- totquad < cmax) {
+ totquad < static_cast(cmax)) {
// if (sum > cmin && totquad / sum > 0.8 && totquad / sum < 1.2 &&
// sum < cmax) {
diff --git a/slsSupportLib/include/sls/ClientSocket.h b/slsSupportLib/include/sls/ClientSocket.h
index dcfc02b25..4ef8e5da4 100644
--- a/slsSupportLib/include/sls/ClientSocket.h
+++ b/slsSupportLib/include/sls/ClientSocket.h
@@ -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;
};
diff --git a/slsSupportLib/include/sls/DataSocket.h b/slsSupportLib/include/sls/DataSocket.h
index 31d9afa72..c8c7fc75e 100644
--- a/slsSupportLib/include/sls/DataSocket.h
+++ b/slsSupportLib/include/sls/DataSocket.h
@@ -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
diff --git a/slsSupportLib/include/sls/string_utils.h b/slsSupportLib/include/sls/string_utils.h
index dc1c037ac..abeed79a7 100644
--- a/slsSupportLib/include/sls/string_utils.h
+++ b/slsSupportLib/include/sls/string_utils.h
@@ -96,4 +96,6 @@ bool replace_first(std::string *s, const std::string &substr,
std::pair ParseHostPort(const std::string &s);
+std::string to_lower(const std::string &s);
+
} // namespace sls
diff --git a/slsSupportLib/src/ClientSocket.cpp b/slsSupportLib/src/ClientSocket.cpp
index 117e7621c..6dc6aac42 100644
--- a/slsSupportLib/src/ClientSocket.cpp
+++ b/slsSupportLib/src/ClientSocket.cpp
@@ -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
#include
#include
+#include
#include
#include
#include
@@ -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);
}
}
diff --git a/slsSupportLib/src/DataSocket.cpp b/slsSupportLib/src/DataSocket.cpp
index 25d016d0b..a8b4d2bfd 100644
--- a/slsSupportLib/src/DataSocket.cpp
+++ b/slsSupportLib/src/DataSocket.cpp
@@ -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
#include
#include
+#include
#include
#include
#include
@@ -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(size); // signed size
- int bytes_read = 0;
+ ssize_t bytes_expected = static_cast(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(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(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(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(size); // signed size
- while (bytes_sent < (data_size)) {
- auto this_send = ::write(getSocketId(), buffer, size);
+ ssize_t bytes_expected = static_cast(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(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(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(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
diff --git a/slsSupportLib/src/ServerInterface.cpp b/slsSupportLib/src/ServerInterface.cpp
index 92e3d7a67..8e2d4a503 100644
--- a/slsSupportLib/src/ServerInterface.cpp
+++ b/slsSupportLib/src/ServerInterface.cpp
@@ -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;
}
diff --git a/slsSupportLib/src/string_utils.cpp b/slsSupportLib/src/string_utils.cpp
index 1426049ff..68e389c99 100644
--- a/slsSupportLib/src/string_utils.cpp
+++ b/slsSupportLib/src/string_utils.cpp
@@ -75,4 +75,11 @@ std::pair 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
\ No newline at end of file
diff --git a/slsSupportLib/tests/test-Sockets.cpp b/slsSupportLib/tests/test-Sockets.cpp
index b41c44cb4..211df5858 100644
--- a/slsSupportLib/tests/test-Sockets.cpp
+++ b/slsSupportLib/tests/test-Sockets.cpp
@@ -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
#include
#include
#include
+#include
+#include
+#include
#include
+#include
namespace sls {
-std::vector 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 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 buffer(100, '\0');
s.Receive(buffer.data(), buffer.size());
- std::cout << "ServerReceived: " << std::string(buffer.begin(), buffer.end())
- << '\n';
- std::vector 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 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 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 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 *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 received_message(100, '\0');
std::vector 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 received_message(100, '\0');
+ std::vector 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 received_message(100, '\0');
+ std::vector 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 received_message(100, '\0');
+ std::vector 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 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 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
diff --git a/slsSupportLib/tests/test-string_utils.cpp b/slsSupportLib/tests/test-string_utils.cpp
index 15dcb7dbc..21200eca9 100644
--- a/slsSupportLib/tests/test-string_utils.cpp
+++ b/slsSupportLib/tests/test-string_utils.cpp
@@ -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