Implement ganged switches

This commit is contained in:
Carl Philipp Klemm 2025-10-13 10:45:44 +02:00
parent 7a6e674756
commit 81e7e1a5b3
3 changed files with 251 additions and 133 deletions

View file

@ -1,3 +1,4 @@
#include "channelwidget.h" #include "channelwidget.h"
#include <QDebug> #include <QDebug>
#include <QMessageBox> #include <QMessageBox>
@ -5,93 +6,166 @@
ChannelWidget::ChannelWidget(uint16_t deviceSerial, uint16_t channelNumber, ChannelWidget::ChannelWidget(uint16_t deviceSerial, uint16_t channelNumber,
std::shared_ptr<struct eismultiplexer> multiplexer, std::shared_ptr<struct eismultiplexer> multiplexer,
QWidget *parent) QWidget *parent)
: :
QWidget(parent), QWidget(parent),
deviceSerial(deviceSerial), deviceSerial(deviceSerial),
channelNumber(channelNumber), channelNumber(channelNumber),
multiplexer(multiplexer), multiplexer(multiplexer),
checkbox("Enable"), checkbox("Enable"),
devicelabel(QString::asprintf("Device %04u", deviceSerial)), devicelabel(QString::asprintf("Device %04u", deviceSerial)),
channellabel(QString::asprintf("Channel %u", channelNumber)), channellabel(QString::asprintf("Channel %u", channelNumber)),
ganglabel("Ganged:") ganglabel("Ganged:")
{ {
hlayout.addLayout(&labellayout); hlayout.addLayout(&labellayout);
vlayout.addLayout(&hlayout); vlayout.addLayout(&hlayout);
labellayout.addWidget(&devicelabel); labellayout.addWidget(&devicelabel);
labellayout.addWidget(&channellabel); labellayout.addWidget(&channellabel);
line.setGeometry(QRect(320, 150, 118, 3)); line.setGeometry(QRect(320, 150, 118, 3));
line.setFrameShape(QFrame::HLine); line.setFrameShape(QFrame::HLine);
line.setFrameShadow(QFrame::Sunken); line.setFrameShadow(QFrame::Sunken);
vlayout.addWidget(&line); vlayout.addWidget(&line);
gangcombo.addItem("Unganged"); // Add Unganged option first
gangcombo.addItem("Unganged");
hlayout.addStretch(); hlayout.addStretch();
hlayout.addWidget(&ganglabel); hlayout.addWidget(&ganglabel);
hlayout.addWidget(&gangcombo); hlayout.addWidget(&gangcombo);
hlayout.addWidget(&checkbox); hlayout.addWidget(&checkbox);
connect(&checkbox, &QCheckBox::toggled, this, &ChannelWidget::onChannelToggled);
setFixedHeight(96); // Connect signals
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); connect(&checkbox, &QCheckBox::toggled, this, &ChannelWidget::onChannelToggled);
connect(&gangcombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ChannelWidget::onGangComboChanged);
setLayout(&vlayout); setFixedHeight(96);
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
setLayout(&vlayout);
} }
ChannelWidget::~ChannelWidget() ChannelWidget::~ChannelWidget()
{ {
// Nothing to clean up // Nothing to clean up
} }
uint16_t ChannelWidget::getDeviceSerial() const uint16_t ChannelWidget::getDeviceSerial() const
{ {
return deviceSerial; return deviceSerial;
} }
uint16_t ChannelWidget::getChannelNumber() const uint16_t ChannelWidget::getChannelNumber() const
{ {
return channelNumber; return channelNumber;
} }
bool ChannelWidget::isChecked() const bool ChannelWidget::isChecked() const
{ {
return checkbox.isChecked(); return checkbox.isChecked();
} }
void ChannelWidget::onChannelToggled(bool checked) void ChannelWidget::onChannelToggled(bool checked)
{ {
if (checked) if (checked)
{ {
// Emit signal before actually turning on the channel // Emit signal before actually turning on the channel
emit channelAboutToBeTurnedOn(deviceSerial, channelNumber); emit channelAboutToBeTurnedOn(deviceSerial, channelNumber);
} }
channel_t channelFlag = static_cast<channel_t>(1 << channelNumber); channel_t channelFlag = static_cast<channel_t>(1 << channelNumber);
if (checked) if (checked)
{ {
if (eismultiplexer_connect_channel(multiplexer.get(), channelFlag) < 0) if (eismultiplexer_connect_channel(multiplexer.get(), channelFlag) < 0)
{ {
QMessageBox::warning(this, tr("Connection Failed"), QMessageBox::warning(this, tr("Connection Failed"),
tr("Failed to connect channel %1 on device %2").arg(channelNumber).arg(deviceSerial)); tr("Failed to connect channel %1 on device %2").arg(channelNumber).arg(deviceSerial));
qWarning() << "Failed to connect channel" << channelNumber << "on device" << deviceSerial; qWarning() << "Failed to connect channel" << channelNumber << "on device" << deviceSerial;
checkbox.blockSignals(true); checkbox.blockSignals(true);
checkbox.setChecked(false); checkbox.setChecked(false);
setEnabled(false); // Gray out the widget setEnabled(false); // Gray out the widget
} }
} }
else else
{ {
if (eismultiplexer_disconnect_channel(multiplexer.get(), channelFlag) < 0) if (eismultiplexer_disconnect_channel(multiplexer.get(), channelFlag) < 0)
{ {
QMessageBox::warning(this, tr("Disconnection Failed"), QMessageBox::warning(this, tr("Disconnection Failed"),
tr("Failed to disconnect channel %1 on device %2").arg(channelNumber).arg(deviceSerial)); tr("Failed to disconnect channel %1 on device %2").arg(channelNumber).arg(deviceSerial));
qWarning() << "Failed to disconnect channel" << channelNumber << "on device" << deviceSerial; qWarning() << "Failed to disconnect channel" << channelNumber << "on device" << deviceSerial;
checkbox.blockSignals(true); checkbox.blockSignals(true);
checkbox.setChecked(true); checkbox.setChecked(true);
setEnabled(false); // Gray out the widget setEnabled(false); // Gray out the widget
} }
} }
// Emit state change signal for other channels that might be ganged to this one
emit channelStateChanged(deviceSerial, channelNumber, checked);
}
void ChannelWidget::onGangComboChanged(int index)
{
if (index == 0) {
// Unganged selected - reset ganged channel tracking
gangedDeviceSerial = 0;
gangedChannelNumber = 0;
checkbox.setEnabled(true);
checkbox.setChecked(false);
checkbox.setChecked(false); // Reset to false to avoid leaving it checked when unganged
} else {
// A ganged channel was selected
QString currentText = gangcombo.currentText();
QStringList parts = currentText.split(", ");
if (parts.size() == 2) {
bool ok1, ok2;
uint16_t gangedSerial = parts[0].toUInt(&ok1);
uint16_t gangedChannel = parts[1].toUInt(&ok2);
if (ok1 && ok2) {
setGangedChannel(gangedSerial, gangedChannel);
}
}
}
}
void ChannelWidget::onOtherChannelStateChanged(uint16_t deviceSerial, uint16_t channelNumber, bool checked)
{
// If this channel is ganged to the channel that changed state
if (gangedDeviceSerial == deviceSerial && gangedChannelNumber == channelNumber) {
// Update our checkbox state to follow the ganged channel
checkbox.blockSignals(true);
checkbox.setChecked(checked);
checkbox.blockSignals(false);
}
}
void ChannelWidget::updateGangCombo()
{
// Clear existing items except "Unganged"
while (gangcombo.count() > 1) {
gangcombo.removeItem(1);
}
}
void ChannelWidget::updateCheckboxState()
{
// If we're ganged, update our state to match the ganged channel
if (gangedDeviceSerial != 0 && gangedChannelNumber != 0) {
checkbox.setEnabled(false);
// We need to check the state of the ganged channel and sync
// This would typically be done by querying the actual channel state
// but for now we'll assume the ganged channel's state is known
} else {
checkbox.setEnabled(true);
}
}
void ChannelWidget::setGangedChannel(uint16_t gangedDeviceSerial, uint16_t gangedChannelNumber)
{
this->gangedDeviceSerial = gangedDeviceSerial;
this->gangedChannelNumber = gangedChannelNumber;
// Update checkbox state to follow the ganged channel
updateCheckboxState();
} }

View file

@ -1,3 +1,5 @@
#ifndef CHANNELWIDGET_H #ifndef CHANNELWIDGET_H
#define CHANNELWIDGET_H #define CHANNELWIDGET_H
@ -11,39 +13,54 @@
class ChannelWidget : public QWidget class ChannelWidget : public QWidget
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit ChannelWidget(uint16_t deviceSerial, uint16_t channelNumber, explicit ChannelWidget(uint16_t deviceSerial, uint16_t channelNumber,
std::shared_ptr<struct eismultiplexer> multiplexer, std::shared_ptr<struct eismultiplexer> multiplexer,
QWidget *parent = nullptr); QWidget *parent = nullptr);
~ChannelWidget() override; ~ChannelWidget() override;
uint16_t getDeviceSerial() const; uint16_t getDeviceSerial() const;
uint16_t getChannelNumber() const; uint16_t getChannelNumber() const;
bool isChecked() const; bool isChecked() const;
signals: // Accessors for MainWindow to populate and access the gang combo
void channelAboutToBeTurnedOn(uint16_t deviceSerial, uint16_t channelNumber); QComboBox* getGangCombo() { return &gangcombo; }
void setGangedChannel(uint16_t deviceSerial, uint16_t channelNumber);
public slots:
void onOtherChannelStateChanged(uint16_t deviceSerial, uint16_t channelNumber, bool checked);
private slots: private slots:
void onChannelToggled(bool checked); void onChannelToggled(bool checked);
void onGangComboChanged(int index);
signals:
void channelAboutToBeTurnedOn(uint16_t deviceSerial, uint16_t channelNumber);
void channelStateChanged(uint16_t deviceSerial, uint16_t channelNumber, bool checked);
private: private:
uint16_t deviceSerial; void updateGangCombo();
uint16_t channelNumber; void updateCheckboxState();
std::shared_ptr<struct eismultiplexer> multiplexer;
QCheckBox checkbox; uint16_t deviceSerial;
QLabel devicelabel; uint16_t channelNumber;
QLabel channellabel; std::shared_ptr<struct eismultiplexer> multiplexer;
QLabel ganglabel; QCheckBox checkbox;
QComboBox gangcombo; QLabel devicelabel;
QFrame line; QLabel channellabel;
QVBoxLayout vlayout; QLabel ganglabel;
QHBoxLayout hlayout; QComboBox gangcombo;
QVBoxLayout labellayout; QFrame line;
QVBoxLayout vlayout;
QHBoxLayout hlayout;
QVBoxLayout labellayout;
// Track the channel this one is ganged to (if any)
uint16_t gangedDeviceSerial = 0;
uint16_t gangedChannelNumber = 0;
}; };
#endif // CHANNELWIDGET_H #endif // CHANNELWIDGET_H

View file

@ -1,69 +1,96 @@
#include <eismultiplexer.h> #include <eismultiplexer.h>
#include <QMessageBox> #include <QMessageBox>
#include "mainwindow.h" #include "mainwindow.h"
#include "ui_mainwindow.h" #include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent) : QMainWindow(parent)
, ui(new Ui::MainWindow) , ui(new Ui::MainWindow)
{ {
ui->setupUi(this); ui->setupUi(this);
enumerateDevices(); enumerateDevices();
connect(ui->actionQuit, &QAction::triggered, this, [this]() connect(ui->actionQuit, &QAction::triggered, this, [this]()
{ {
close(); close();
}); });
} }
MainWindow::~MainWindow() MainWindow::~MainWindow()
{ {
delete ui; delete ui;
} }
void MainWindow::enumerateDevices() void MainWindow::enumerateDevices()
{ {
size_t count = 0; size_t count = 0;
uint16_t* serials = eismultiplexer_list_available_devices(&count); uint16_t* serials = eismultiplexer_list_available_devices(&count);
if (!serials || count == 0) if (!serials || count == 0)
{ {
QMessageBox::warning(nullptr, tr("No Devices Found"), QMessageBox::warning(nullptr, tr("No Devices Found"),
tr("No EIS multiplexer devices were found. Please connect a device and try again.")); tr("No EIS multiplexer devices were found. Please connect a device and try again."));
qWarning() << "No EIS multiplexer devices found"; qWarning() << "No EIS multiplexer devices found";
exit(0); exit(0);
return; return;
} }
for (size_t i = 0; i < count; i++) // First pass: create all widgets without connecting signals
{ for (size_t i = 0; i < count; i++)
uint16_t serial = serials[i]; {
std::shared_ptr<struct eismultiplexer> multiplexer(new struct eismultiplexer); uint16_t serial = serials[i];
int ret = eismultiplexer_connect(multiplexer.get(), serial); std::shared_ptr<struct eismultiplexer> multiplexer(new struct eismultiplexer);
if (ret == 0) int ret = eismultiplexer_connect(multiplexer.get(), serial);
{ if (ret == 0)
uint16_t channelCount = 0; {
qDebug()<<"Adding channels from device "<<serial; uint16_t channelCount = 0;
if (eismultiplexer_get_channel_count(multiplexer.get(), &channelCount) >= 0) qDebug()<<"Adding channels from device "<<serial;
{ if (eismultiplexer_get_channel_count(multiplexer.get(), &channelCount) >= 0)
for (uint16_t channel = 0; channel < channelCount; channel++) {
{ for (uint16_t channel = 0; channel < channelCount; channel++)
std::shared_ptr<ChannelWidget> widget(new ChannelWidget(serial, channel, multiplexer)); {
qDebug()<<"Added widget from device "<<serial<<" channel "<<channel; std::shared_ptr<ChannelWidget> widget(new ChannelWidget(serial, channel, multiplexer));
channels.push_back(widget); qDebug()<<"Added widget from device "<<serial<<" channel "<<channel;
ui->channelLayout->addWidget(widget.get()); channels.push_back(widget);
} ui->channelLayout->addWidget(widget.get());
} }
} }
else }
{ else
QMessageBox::warning(this, tr("Connection Failed"), {
tr("Failed to connect to device with serial %1").arg(serial)); QMessageBox::warning(this, tr("Connection Failed"),
qWarning()<<"Failed to connect to device with serial"<<serial<<"eismultiplexer_connect returned"<<ret; tr("Failed to connect to device with serial %1").arg(serial));
} qWarning()<<"Failed to connect to device with serial"<<serial<<"eismultiplexer_connect returned"<<ret;
ui->channelLayout->addStretch(); }
} }
ui->statusbar->showMessage("Ready"); ui->channelLayout->addStretch();
free(serials); // Second pass: populate gang combos and connect signals
for (const auto& widget : channels) {
// Populate gang combo with all other channels
for (const auto& otherWidget : channels) {
if (widget->getDeviceSerial() != otherWidget->getDeviceSerial() ||
widget->getChannelNumber() != otherWidget->getChannelNumber()) {
QString channelText = QString::asprintf("%04u, %u",
otherWidget->getDeviceSerial(),
otherWidget->getChannelNumber());
widget->getGangCombo()->addItem(channelText);
}
}
// Connect state change signals
for (const auto& otherWidget : channels) {
if (widget.get() != otherWidget.get()) {
connect(otherWidget.get(), &ChannelWidget::channelStateChanged,
widget.get(), &ChannelWidget::onOtherChannelStateChanged);
}
}
}
ui->statusbar->showMessage("Ready");
free(serials);
} }