WIP: LNCV programming widget

Dieser Commit ist enthalten in:
Reinder Feenstra 2022-10-08 23:29:22 +02:00
Ursprung 914157cc47
Commit 467df25d5a
13 geänderte Dateien mit 888 neuen und 8 gelöschten Zeilen

3
.gitmodules vendored Normale Datei
Datei anzeigen

@ -0,0 +1,3 @@
[submodule "shared/data/lncv"]
path = shared/data/lncv
url = git@github.com:traintastic/lncv.git

Datei anzeigen

@ -44,6 +44,8 @@ file(GLOB SOURCES
"src/misc/*.cpp"
"src/network/*.hpp"
"src/network/*.cpp"
"src/programming/lncv/*.hpp"
"src/programming/lncv/*.cpp"
"src/settings/*.hpp"
"src/settings/*.cpp"
"src/style/*.hpp"
@ -67,11 +69,11 @@ file(GLOB SOURCES
"thirdparty/QtWaitingSpinner/*.hpp"
"thirdparty/QtWaitingSpinner/*.cpp")
find_package(Qt5 COMPONENTS Widgets Network Svg REQUIRED)
find_package(Qt5 COMPONENTS Widgets Network Svg Xml REQUIRED)
target_sources(traintastic-client PRIVATE ${SOURCES})
target_link_libraries(traintastic-client PRIVATE Qt5::Widgets Qt5::Network Qt5::Svg)
target_link_libraries(traintastic-client PRIVATE Qt5::Widgets Qt5::Network Qt5::Svg Qt5::Xml)
### PLATFORM ###

Datei anzeigen

@ -45,6 +45,7 @@
#include "network/object.hpp"
#include "network/property.hpp"
#include "network/method.hpp"
#include "programming/lncv/lncvprogrammer.hpp"
#include "subwindow/objectsubwindow.hpp"
#include "subwindow/boardsubwindow.hpp"
#include "subwindow/throttlesubwindow.hpp"
@ -412,6 +413,16 @@ MainWindow::MainWindow(QWidget* parent) :
if(Method* method = traintastic->getMethod("shutdown"))
method->call();
});
m_menuProgramming = menu->addMenu(Locale::tr("qtapp.mainmenu:programming"));
m_menuProgramming->addAction(Locale::tr("lncv_programmer:lncv_programmer") + "...",
[this]()
{
auto* window = new QMdiSubWindow();
window->setWidget(new LNCVProgrammer(m_connection));
window->setAttribute(Qt::WA_DeleteOnClose);
m_mdiArea->addSubWindow(window);
window->show();
});
menu = menuBar()->addMenu(Locale::tr("qtapp.mainmenu:help"));
menu->addAction(Locale::tr("qtapp.mainmenu:help"),
@ -773,6 +784,7 @@ void MainWindow::updateActions()
m = m_connection->traintastic()->getMethod("shutdown");
m_actionServerShutdown->setEnabled(m && m->getAttributeBool(AttributeName::Enabled, false));
}
m_menuProgramming->setEnabled(haveWorld);
setMenuEnabled(m_menuWorld, haveWorld);
m_worldOnlineOfflineToolButton->setEnabled(haveWorld);

Datei anzeigen

@ -83,6 +83,7 @@ class MainWindow : public QMainWindow
QAction* m_actionServerRestart;
QAction* m_actionServerShutdown;
QAction* m_actionServerLog;
QMenu* m_menuProgramming;
// Main toolbar:
QToolBar* m_toolbar;
QToolButton* m_worldOnlineOfflineToolButton;

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2020-2021 Reinder Feenstra
* Copyright (C) 2020-2022 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -77,7 +77,8 @@ int callMethod(Connection& connection, Method& method, std::function<void(const
request->write(value_type_v<R>);
request->write<uint8_t>(sizeof...(A)); // N arguments
writeArguments(*request, args...);
if constexpr(sizeof...(A) > 0)
writeArguments(*request, args...);
connection.send(request,
[&connection, callback](const std::shared_ptr<Message> message)
@ -101,7 +102,8 @@ int callMethodR(Method& method, std::function<void(const R&, Message::ErrorCode)
request->write(value_type_v<R>);
request->write<uint8_t>(sizeof...(A)); // N arguments
writeArguments(*request, args...);
if constexpr(sizeof...(A) > 0)
writeArguments(*request, args...);
auto c = method.object().connection();
c->send(request,
@ -125,7 +127,8 @@ int callMethod(Method& method, std::function<void(Message::ErrorCode)> callback,
request->write(ValueType::Invalid);
request->write<uint8_t>(sizeof...(A)); // N arguments
writeArguments(*request, args...);
if constexpr(sizeof...(A) > 0)
writeArguments(*request, args...);
method.object().connection()->send(request,
[callback=std::move(callback)](const std::shared_ptr<Message> message)
@ -137,7 +140,7 @@ int callMethod(Method& method, std::function<void(Message::ErrorCode)> callback,
}
template<class... A>
void callMethod(Method& method, std::nullptr_t, A... args)
void callMethod(Method& method, std::nullptr_t = nullptr, A... args)
{
auto event = Message::newEvent(Message::Command::ObjectCallMethod);
event->write(method.object().handle());
@ -145,7 +148,8 @@ void callMethod(Method& method, std::nullptr_t, A... args)
event->write(ValueType::Invalid);
event->write<uint8_t>(sizeof...(A)); // N arguments
writeArguments(*event, args...);
if constexpr(sizeof...(A) > 0)
writeArguments(*event, args...);
method.object().connection()->send(event);
}

Datei anzeigen

@ -0,0 +1,640 @@
/**
* client/src/programming/lncv/lncvprogrammer.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2022 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "lncvprogrammer.hpp"
#include <cmath>
#include <QStackedWidget>
#include <QStatusBar>
#include <QFormLayout>
#include <QComboBox>
#include <QSpinBox>
#include <QCheckBox>
#include <QPushButton>
#include <QTableWidget>
#include <QHeaderView>
#include <QDir>
#include <QDomDocument>
#include <QDomElement>
#include <QDebug>
#include <traintastic/locale/locale.hpp>
#include <traintastic/utils/standardpaths.hpp>
#include "lncvprogramminglistmodel.hpp"
#include "../../network/connection.hpp"
#include "../../network/callmethod.hpp"
#include "../../network/event.hpp"
static QDomElement getElementByLanguage(const QList<QDomElement>& list, const QString& language)
{
for(const auto element : list)
if(element.attribute("language", "") == language)
return element;
return {};
}
static QDomElement getElementByLanguage(const QDomElement& element, const QString& tagName)
{
static const auto appLanguage = QString::fromStdU32String(Locale::instance->filename.filename().u32string());
QList<QDomElement> list;
for(QDomElement e = element.firstChildElement(tagName); !e.isNull(); e = e.nextSiblingElement(tagName))
list.append(e);
QDomElement e = getElementByLanguage(list, appLanguage);
if(!e.isNull())
return e;
e = getElementByLanguage(list, "en-us");
if(!e.isNull())
return e;
e = getElementByLanguage(list, "");
if(!e.isNull())
return e;
return {};
}
static int getAttributeBool(const QDomElement element, const QString attribute, bool default_ = false)
{
const auto s = element.attribute(attribute);
if(s == "true")
return true;
else if(s == "false")
return false;
return default_;
}
static int getAttributeInt(const QDomElement element, const QString attribute, int default_ = 0, int min = std::numeric_limits<int>::min(), int max = std::numeric_limits<int>::max())
{
bool ok;
const int r = element.attribute(attribute).toInt(&ok);
return (ok && r >= min && r <= max) ? r : default_;
}
static double getAttributeFloat(const QDomElement element, const QString attribute, double default_ = std::numeric_limits<double>::quiet_NaN())
{
bool ok;
const double r = element.attribute(attribute).toDouble(&ok);
return ok ? r : default_;
}
LNCVProgrammer::LNCVProgrammer(std::shared_ptr<Connection> connection, QWidget* parent, Qt::WindowFlags f)
: QWidget(parent, f)
, m_connection{std::move(connection)}
, m_requestId{Connection::invalidRequestId}
, m_pages{new QStackedWidget(this)}
, m_statusBar{new QStatusBar(this)}
, m_interface{new QComboBox(this)}
, m_module{new QComboBox(this)}
, m_otherModule{new QSpinBox(this)}
, m_address{new QSpinBox(this)}
, m_broadcastAddress{new QCheckBox(Locale::tr("lncv_programmer:use_broadcast_address"), this)}
, m_lncvs{new QTableWidget(0, 3, this)}
, m_start{new QPushButton(Locale::tr("lncv_programmer:start"))}
, m_read{new QPushButton(Locale::tr("lncv_programmer:read"))}
, m_write{new QPushButton(Locale::tr("lncv_programmer:write"))}
, m_stop{new QPushButton(Locale::tr("lncv_programmer:stop"))}
{
setWindowTitle(Locale::tr("lncv_programmer:lncv_programmer"));
loadInterfaces();
connect(m_interface, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &LNCVProgrammer::updateStartEnabled);
loadModules();
connect(m_module, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &LNCVProgrammer::moduleChanged);
m_otherModule->setValue(0);
m_otherModule->setRange(moduleIdMin, moduleIdMax);
m_address->setValue(1);
m_address->setRange(1, 65535);
connect(m_broadcastAddress, &QCheckBox::toggled, this, &LNCVProgrammer::useBroadcastAddressChanged);
connect(m_start, &QPushButton::clicked,
[this]()
{
loadLNCVs();
m_pages->widget(pageStart)->setEnabled(false);
m_statusBar->showMessage(Locale::tr("lncv_programmer:sending_start"));
if(const auto& world = m_connection->world())
{
if(auto* getLNCVProgrammer = world->getMethod("get_lncv_programmer"))
{
callMethodR<ObjectPtr>(*getLNCVProgrammer,
[this](const ObjectPtr& object, Message::ErrorCode /*ec*/)
{
if(object)
{
if(auto* onReadResponse = object->getEvent("on_read_response"))
{
connect(onReadResponse, &Event::fired, this,
[this](QVariantList arguments)
{
const bool success = arguments[0].toBool();
const int lncv = arguments[1].toInt();
const int value = arguments[2].toInt();
if(success)
{
m_statusBar->clearMessage();
if(auto it = m_lncvToRow.find(lncv); it != m_lncvToRow.end())
{
auto* w = m_lncvs->cellWidget(it->second, columnValue);
if(auto* spinBox = dynamic_cast<QSpinBox*>(w))
{
spinBox->setEnabled(true);
spinBox->setValue(value);
}
else if(auto* doubleSpinBox = dynamic_cast<QDoubleSpinBox*>(w))
{
const double gain = doubleSpinBox->property("lncv_gain").toDouble();
const double offset = doubleSpinBox->property("lncv_offset").toDouble();
doubleSpinBox->setEnabled(true);
doubleSpinBox->setValue(value * gain + offset);
}
else if(auto* comboBox = dynamic_cast<QComboBox*>(w))
{
comboBox->setEnabled(true);
const int itemCount = comboBox->count();
for(int i = 0; i < itemCount; i++)
{
if(comboBox->itemData(i) == value)
{
comboBox->setCurrentIndex(i);
return;
}
}
comboBox->addItem(QString::number(value), value);
comboBox->setCurrentIndex(itemCount);
}
else
{
m_lncvs->setItem(it->second, columnValue, new QTableWidgetItem(QString::number(value)));
}
}
}
else
{
m_statusBar->showMessage(Locale::tr("lncv_programmer:reading_lncv_x_failed").arg(lncv), showMessageTimeout);
}
setState(State::Idle);
});
}
else
assert(false);
if(auto* start = object->getMethod("start"))
{
m_object = object;
m_pages->setCurrentIndex(pageProgramming);
m_read->setEnabled(false);
m_write->setEnabled(false);
const int moduleId =
(m_module->currentIndex() == m_module->count() - 1)
? m_otherModule->value()
: m_module->currentData(roleModuleId).toInt();
setState(State::WaitForStart);
callMethodR<bool>(*start,
[this](const bool sent, Message::ErrorCode /*ec*/)
{
if(sent)
{
m_statusBar->showMessage(Locale::tr("lncv_programmer:waiting_for_module_to_respond"));
}
else
{
m_statusBar->showMessage(Locale::tr("lncv_programmer:sending_start_failed"), showMessageTimeout);
reset();
}
}, moduleId, m_address->value());
}
else
assert(false);
}
}, m_interface->currentText());
}
else
assert(false);
}
else
assert(false);
});
connect(m_read, &QPushButton::clicked,
[this]()
{
if(const int lncv = getSelectedLNCV(); lncv != -1)
{
m_statusBar->showMessage(Locale::tr("lncv_programmer:reading_lncv_x").arg(lncv));
if(auto* read = m_object->getMethod("read"))
{
callMethod(*read, nullptr, lncv);
setState(State::WaitForRead);
}
}
});
connect(m_write, &QPushButton::clicked,
[this]()
{
if(const int lncv = getSelectedLNCV(); lncv != -1)
{
if(const int lncvValue = getSelectedLNCVValue(); lncvValue != -1)
{
m_statusBar->showMessage(Locale::tr("lncv_programmer:writing_lncv_x").arg(lncv));
if(auto* write = m_object->getMethod("write"))
{
callMethod(*write, nullptr, lncv, lncvValue);
setState(State::WaitForWrite);
}
}
}
});
connect(m_stop, &QPushButton::clicked,
[this]()
{
if(auto* stop = m_object->getMethod("stop"))
{
m_statusBar->clearMessage();
callMethod(*stop);
reset();
}
else
assert(false);
});
m_lncvs->setHorizontalHeaderLabels({QString("LNCV"), Locale::tr("lncv_programmer:value"), Locale::tr("lncv_programmer:description")});
m_lncvs->horizontalHeader()->setStretchLastSection(true);
m_lncvs->verticalHeader()->setVisible(false);
m_lncvs->setEditTriggers(QAbstractItemView::NoEditTriggers);
m_lncvs->setSelectionMode(QAbstractItemView::SingleSelection);
m_lncvs->setSelectionBehavior(QAbstractItemView::SelectRows);
connect(m_lncvs, &QTableWidget::currentItemChanged,
[this](QTableWidgetItem* /*current*/, QTableWidgetItem* /*previous*/)
{
updateReadWriteEnabled();
});
// start page:
{
auto* form = new QFormLayout();
form->addRow(Locale::tr("hardware:interface"), m_interface);
form->addRow(Locale::tr("lncv_programmer:module"), m_module);
form->addRow("", m_otherModule);
form->addRow(Locale::tr("hardware:address"), m_address);
form->addRow("", m_broadcastAddress);
form->addRow("", m_start);
auto* w = new QWidget(this);
w->setLayout(form);
m_pages->addWidget(w);
}
// programming page:
{
auto* v = new QVBoxLayout();
v->addWidget(m_lncvs);
auto* h = new QHBoxLayout();
h->addStretch();
h->addWidget(m_read);
h->addWidget(m_write);
h->addWidget(m_stop);
h->addStretch();
v->addLayout(h);
auto* w = new QWidget(this);
w->setLayout(v);
m_pages->addWidget(w);
}
auto* l = new QVBoxLayout();
l->setMargin(0);
l->addWidget(m_pages);
l->addWidget(m_statusBar);
setLayout(l);
}
LNCVProgrammer::~LNCVProgrammer()
{
if(m_requestId != Connection::invalidRequestId)
m_connection->cancelRequest(m_requestId);
m_object.reset();
}
void LNCVProgrammer::loadInterfaces()
{
m_requestId = m_connection->getObject("world.lncv_programming_controllers",
[this](const ObjectPtr& object, Message::ErrorCode /*ec*/)
{
m_requestId = Connection::invalidRequestId;
if(object)
m_requestId = m_connection->getTableModel(object,
[this](const TableModelPtr& table, Message::ErrorCode /*ec*/)
{
m_requestId = Connection::invalidRequestId;
if(table)
m_interface->setModel(new LNCVProgrammingListModel(table, m_interface));
});
});
}
void LNCVProgrammer::loadModules()
{
m_module->clear();
m_module->addItem("");
QDir moduleDir(QString::fromStdString(getLNCVXMLPath().string()));
moduleDir.setNameFilters({"*.xml"});
for(const QString& entry : moduleDir.entryList(QDir::Files | QDir::Readable, QDir::Name))
{
const QString filename = moduleDir.absoluteFilePath(entry);
QFile file(filename);
if(!file.open(QIODevice::ReadOnly))
continue;
QDomDocument doc;
if(!doc.setContent(&file))
continue;
QDomElement lncvModule = doc.documentElement();
if(lncvModule.tagName() != "lncvmodule" || !lncvModule.hasAttribute("id"))
continue;
bool ok;
if(int moduleId = lncvModule.attribute("id").toInt(&ok); ok && moduleId >= moduleIdMin && moduleId <= moduleIdMax)
{
if(auto name = getElementByLanguage(lncvModule, "name"); !name.isNull())
{
QString label{name.text()};
if(auto vendor = getElementByLanguage(lncvModule, "vendor"); !name.isNull())
label.prepend(" ").prepend(vendor.text());
m_module->addItem(label, filename);
m_module->setItemData(m_module->count() - 1, moduleId, roleModuleId);
}
}
}
m_module->addItem(Locale::tr("lncv_programmer:other_module"));
moduleChanged();
}
void LNCVProgrammer::loadLNCVs()
{
m_lncvs->setRowCount(0);
m_lncvToRow.clear();
if(const auto filename = m_module->currentData(roleFilename).toString(); !filename.isEmpty())
{
QFile file(filename);
if(file.open(QIODevice::ReadOnly))
{
QDomDocument doc;
if(doc.setContent(&file))
{
QDomElement lncvModule = doc.documentElement();
bool ok;
QDomElement lncv = lncvModule.firstChildElement("lncv");
while(!lncv.isNull())
{
if(int number = lncv.attribute("lncv").toInt(&ok); ok && number >= lncvMin && number <= lncvMax)
{
const int row = m_lncvs->rowCount();
m_lncvs->setRowCount(row + 1);
m_lncvs->setItem(row, columnLNCV, new QTableWidgetItem(lncv.attribute("lncv")));
m_lncvs->setItem(row, columnDescription, new QTableWidgetItem(getElementByLanguage(lncv, "name").text()));
QWidget* w = nullptr;
if(QDomElement value = lncv.firstChildElement("value"); !value.isNull())
{
if(getAttributeBool(value, "readonly"))
{
// no widget
}
else
{
const int defaultValue = getAttributeInt(value, "default", -1, lncvValueMin, lncvValueMax);
QDomElement option = value.firstChildElement("option");
if(!option.isNull())
{
auto* cb = new QComboBox(this);
while(!option.isNull())
{
const int v = getAttributeInt(option, "value", -1, lncvValueMin, lncvValueMax);
if(v != -1)
{
auto name = getElementByLanguage(option, "name");
if(!name.isNull())
cb->addItem(name.text(), v);
else
cb->addItem(QString::number(v), v);
if(defaultValue == v)
cb->setCurrentIndex(cb->count() - 1);
}
option = option.nextSiblingElement("option");
}
w = cb;
}
else
{
const int min = getAttributeInt(value, "min", lncvValueMin, lncvValueMin, lncvValueMax);
const int max = getAttributeInt(value, "max", lncvValueMax, lncvValueMin, lncvValueMax);
double gain = getAttributeFloat(value, "gain");
double offset = getAttributeFloat(value, "offset");
double dummy;
if((std::isfinite(gain) && std::modf(gain, &dummy) != 0) ||
(std::isfinite(offset) && std::modf(offset, &dummy) != 0))
{
if(std::isnan(gain))
gain = 1;
if(std::isnan(offset))
offset = 0;
auto* sb = new QDoubleSpinBox(this);
sb->setProperty("lncv_gain", gain);
sb->setProperty("lncv_offset", offset);
sb->setSingleStep(gain);
sb->setDecimals(-std::floor(std::log10(std::abs(gain))));
if(max >= min)
sb->setRange(min * gain + offset, max * gain + offset);
else
sb->setRange(lncvValueMin * gain + offset, lncvValueMax * gain + offset);
if(auto unit = value.attribute("unit", ""); !unit.isEmpty())
sb->setSuffix(unit.prepend(" "));
if(defaultValue != -1)
sb->setValue(defaultValue * gain + offset);
w = sb;
}
else
{
auto* spinBox = new QSpinBox(this);
if(max >= min)
spinBox->setRange(min, max);
else
spinBox->setRange(lncvValueMin, lncvValueMax);
if(auto unit = value.attribute("unit", ""); !unit.isEmpty())
spinBox->setSuffix(unit.prepend(" "));
if(defaultValue != -1)
spinBox->setValue(defaultValue);
w = spinBox;
}
}
}
}
else
{
auto* sb = new QSpinBox(this);
sb->setRange(lncvValueMin, lncvValueMax);
w = sb;
}
if(w)
{
m_lncvs->setCellWidget(row, columnValue, w);
w->setEnabled(false);
}
m_lncvToRow[number] = row;
}
lncv = lncv.nextSiblingElement("lncv");
}
}
}
}
}
void LNCVProgrammer::moduleChanged()
{
m_otherModule->setVisible(m_module->currentIndex() == m_module->count() - 1);
updateStartEnabled();
}
void LNCVProgrammer::useBroadcastAddressChanged()
{
if(m_broadcastAddress->isChecked())
{
m_lastAddress = m_address->value();
m_address->setValue(broadcastAddress);
m_address->setEnabled(false);
}
else
{
m_address->setValue(m_lastAddress);
m_address->setEnabled(true);
}
}
void LNCVProgrammer::updateStartEnabled()
{
m_start->setEnabled(
m_interface->currentIndex() != 0 && m_interface->count() > 1 &&
m_module->currentIndex() != 0);
}
void LNCVProgrammer::updateReadWriteEnabled()
{
const bool b = (getSelectedLNCV() != -1) && (m_state == State::Idle);
m_read->setEnabled(b);
m_write->setEnabled(b && getSelectedLNCVValue() != -1);
}
void LNCVProgrammer::reset()
{
m_pages->widget(pageStart)->setEnabled(true);
m_pages->setCurrentIndex(pageStart);
m_object.reset();
}
int LNCVProgrammer::getSelectedLNCV() const
{
if(const auto* item = m_lncvs->item(m_lncvs->currentRow(), columnLNCV))
{
bool ok;
if(int lncv = item->text().toInt(&ok); ok && lncv >= lncvMin && lncv <= lncvMax)
return lncv;
}
return -1;
}
int LNCVProgrammer::getSelectedLNCVValue() const
{
if(const auto* widget = m_lncvs->cellWidget(m_lncvs->currentRow(), columnValue))
{
if(!widget->isEnabled())
return -1;
if(const auto* spinBox = dynamic_cast<const QSpinBox*>(widget))
{
return spinBox->value();
}
if(const auto* comboBox = dynamic_cast<const QComboBox*>(widget))
{
return comboBox->currentData().toInt();
}
if(const auto* doubleSpinBox = dynamic_cast<const QDoubleSpinBox*>(widget))
{
const double gain = doubleSpinBox->property("lncv_gain").toDouble();
const double offset = doubleSpinBox->property("lncv_offset").toDouble();
return std::round((doubleSpinBox->value() - offset) / gain);
}
}
else if(const auto* item = m_lncvs->item(m_lncvs->currentRow(), columnValue))
{
bool ok;
if(int lncv = item->text().toInt(&ok); ok && lncv >= lncvMin && lncv <= lncvMax)
return lncv;
}
return -1;
}
void LNCVProgrammer::setState(State value)
{
m_state = value;
updateReadWriteEnabled();
}

Datei anzeigen

@ -0,0 +1,102 @@
/**
* client/src/programming/lncv/lncvprogrammer.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2022 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef TRAINTASTIC_CLIENT_PROGRAMMING_LNCV_LNCVPROGRAMMER_HPP
#define TRAINTASTIC_CLIENT_PROGRAMMING_LNCV_LNCVPROGRAMMER_HPP
#include <QWidget>
#include <memory>
#include "../../network/objectptr.hpp"
class Connection;
class QStackedWidget;
class QStatusBar;
class QComboBox;
class QSpinBox;
class QCheckBox;
class QPushButton;
class QTableWidget;
class LNCVProgrammer final : public QWidget
{
private:
enum class State
{
Idle,
WaitForStart,
WaitForRead,
WaitForWrite,
};
static constexpr int pageStart = 0;
static constexpr int pageProgramming = 1;
static constexpr int roleFilename = Qt::UserRole + 0;
static constexpr int roleModuleId = Qt::UserRole + 1;
static constexpr int showMessageTimeout = 3000;
static constexpr int moduleIdMin = 0;
static constexpr int moduleIdMax = 65535;
static constexpr uint16_t broadcastAddress = 65535;
static constexpr int lncvMin = 0;
static constexpr int lncvMax = 655535;
static constexpr int lncvValueMin = 0;
static constexpr int lncvValueMax = 655535;
static constexpr int columnLNCV = 0;
static constexpr int columnValue = 1;
static constexpr int columnDescription = 2;
std::shared_ptr<Connection> m_connection;
int m_requestId;
ObjectPtr m_object;
QStackedWidget* m_pages;
QStatusBar* m_statusBar;
QComboBox* m_interface;
QComboBox* m_module;
QSpinBox* m_otherModule;
QSpinBox* m_address;
QCheckBox* m_broadcastAddress;
QTableWidget* m_lncvs;
QPushButton* m_start;
QPushButton* m_read;
QPushButton* m_write;
QPushButton* m_stop;
int m_lastAddress = 1;
State m_state = State::Idle;
std::unordered_map<int, int> m_lncvToRow;
void loadInterfaces();
void loadModules();
void loadLNCVs();
void moduleChanged();
void useBroadcastAddressChanged();
void updateStartEnabled();
void updateReadWriteEnabled();
void reset();
int getSelectedLNCV() const;
int getSelectedLNCVValue() const;
void setState(State value);
public:
explicit LNCVProgrammer(std::shared_ptr<Connection>, QWidget* parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags());
~LNCVProgrammer() final;
};
#endif

Datei anzeigen

@ -0,0 +1,44 @@
/**
* client/src/programming/lncv/lncvprogramminglistmodel.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2022 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "lncvprogramminglistmodel.hpp"
#include "../../network/tablemodel.hpp"
LNCVProgrammingListModel::LNCVProgrammingListModel(TableModelPtr tableModel, QObject* parent)
: QAbstractListModel(parent)
, m_tableModel{std::move(tableModel)}
{
m_tableModel->setRegion(0, std::numeric_limits<int>::max(), 0, std::numeric_limits<int>::max());
}
int LNCVProgrammingListModel::rowCount(const QModelIndex& parent) const
{
return 1 + m_tableModel->rowCount(parent);
}
QVariant LNCVProgrammingListModel::data(const QModelIndex& index, int role) const
{
if(index.row() == 0)
return role == Qt::DisplayRole ? QVariant("") : QVariant{};
return m_tableModel->data(QAbstractListModel::index(index.row() - 1, index.column()), role);
}

Datei anzeigen

@ -0,0 +1,42 @@
/**
* client/src/programming/lncv/lncvprogramminglistmodel.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2022 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef TRAINTASTIC_CLIENT_PROGRAMMING_LNCV_LNCVPROGRAMMINGLISTMODEL_HPP
#define TRAINTASTIC_CLIENT_PROGRAMMING_LNCV_LNCVPROGRAMMINGLISTMODEL_HPP
#include <QAbstractListModel>
#include "../../network/tablemodelptr.hpp"
class LNCVProgrammingListModel : public QAbstractListModel
{
private:
TableModelPtr m_tableModel;
public:
explicit LNCVProgrammingListModel(TableModelPtr tableModel, QObject* parent = nullptr);
int rowCount(const QModelIndex& parent = QModelIndex()) const final;
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const final;
};
#endif

1
shared/data/lncv Submodul

@ -0,0 +1 @@
Subproject commit 1ee4f2678a35e8e42f204a2eb03935c9f4919c20

Datei anzeigen

@ -72,3 +72,14 @@ std::filesystem::path getManualPath()
return {};
#endif
}
std::filesystem::path getLNCVXMLPath()
{
#ifdef WIN32
return getProgramDataPath() / "traintastic" / "lncv";
#elif defined(__linux__)
return "/opt/traintastic/lncv";
#else
return std::filesystem::current_path() / "lncv";
#endif
}

Datei anzeigen

@ -32,5 +32,6 @@ std::filesystem::path getLocalAppDataPath();
std::filesystem::path getLocalePath();
std::filesystem::path getManualPath();
std::filesystem::path getLNCVXMLPath();
#endif

Datei anzeigen

@ -227,6 +227,22 @@ list:move_down=Move down
list:move_up=Move up
list:remove=Remove
lncv_programmer:description=Description
lncv_programmer:lncv_programmer=LNCV programmer
lncv_programmer:module=Module
lncv_programmer:other_module=Other module
lncv_programmer:read=Read
lncv_programmer:reading_lncv_x=Reading LNCV %1
lncv_programmer:reading_lncv_x_failed=Reading LNCV %1 failed
lncv_programmer:sending_start=Sending start
lncv_programmer:start=Start
lncv_programmer:stop=Stop
lncv_programmer:use_broadcast_address=Use broadcast address
lncv_programmer:value=Value
lncv_programmer:waiting_for_module_to_respond=Waiting for module to respond
lncv_programmer:write=Write
lncv_programmer:writing_lncv_x=Writing LNCV %1
loconet_command_station:custom=Custom
loconet_command_station:digikeijs_dr5000=Digikeijs DR5000
loconet_command_station:uhlenbrock_ibcom=Uhlenbrock IB-Com
@ -439,6 +455,7 @@ qtapp.mainmenu:load_world=Load world
qtapp.mainmenu:new_world=New world
qtapp.mainmenu:objects=Objects
qtapp.mainmenu:power=Power
qtapp.mainmenu:programming=Programming
qtapp.mainmenu:quit=Quit
qtapp.mainmenu:restart_server=Restart server
qtapp.mainmenu:restart_server_question=Are you sure you want to restart the server?