added (loconet) input monitor

Dieser Commit ist enthalten in:
Reinder Feenstra 2020-10-15 22:24:42 +02:00
Ursprung 90fb2ecaf8
Commit 914914a68d
48 geänderte Dateien mit 1421 neuen und 123 gelöschten Zeilen

Datei anzeigen

@ -304,7 +304,7 @@ void MainWindow::worldChanged()
if(m_connection)
m_world = m_connection->world();
else
m_world.clear();
m_world.reset();
if(m_world)
{
@ -416,11 +416,14 @@ void MainWindow::showObjectEdit(const ObjectPtr& object)
*/
void MainWindow::showObject(const ObjectPtr& object)
{
const QString& id = object->getProperty("id")->toString();
if(!m_mdiSubWindows.contains(id))
QString id;
if(auto* property = object->getProperty("id"))
id = property->toString();
if(id.isEmpty() || !m_mdiSubWindows.contains(id))
{
QMdiSubWindow* window = new ObjectSubWindow(object);
m_mdiSubWindows[id] = window;
if(!id.isEmpty())
m_mdiSubWindows[id] = window;
m_mdiArea->addSubWindow(window);
window->setAttribute(Qt::WA_DeleteOnClose);
connect(window, &QMdiSubWindow::destroyed, this,

Datei anzeigen

@ -31,6 +31,7 @@
#include "objectproperty.hpp"
#include "method.hpp"
#include "tablemodel.hpp"
#include "inputmonitor.hpp"
#include <traintastic/enum/interfaceitemtype.hpp>
#include <traintastic/enum/attributetype.hpp>
#include <traintastic/locale/locale.hpp>
@ -299,6 +300,37 @@ void Connection::setTableModelRegion(TableModel* tableModel, int columnMin, int
send(event);
}
int Connection::getInputMonitorInputInfo(InputMonitor& inputMonitor)
{
auto request = Message::newRequest(Message::Command::InputMonitorGetInputInfo);
request->write(inputMonitor.handle());
send(request,
[&inputMonitor](const std::shared_ptr<Message> message)
{
uint32_t count = message->read<uint32_t>();
while(count > 0)
{
const uint32_t address = message->read<uint32_t>();
const QString id = QString::fromUtf8(message->read<QByteArray>());
const TriState value = message->read<TriState>();
emit inputMonitor.inputIdChanged(address, id);
emit inputMonitor.inputValueChanged(address, value);
count--;
}
/*
TableModelPtr tableModel;
if(!message->isError())
{
tableModel = readTableModel(*message);
m_tableModels[tableModel->handle()] = tableModel.data();
}
callback(tableModel, message->errorCode());
*/
});
return request->requestId();
}
void Connection::send(std::unique_ptr<Message>& message)
{
Q_ASSERT(!message->isRequest());
@ -323,7 +355,10 @@ ObjectPtr Connection::readObject(const Message& message)
if(!obj)
{
const QString classId = QString::fromLatin1(message.read<QByteArray>());
obj = QSharedPointer<Object>::create(sharedFromThis(), handle, classId);
if(classId.startsWith(InputMonitor::classIdPrefix))
obj = std::make_shared<InputMonitor>(sharedFromThis(), handle, classId);
else
obj = std::make_shared<Object>(sharedFromThis(), handle, classId);
m_objects[handle] = obj;
message.readBlock(); // items
@ -806,6 +841,24 @@ void Connection::processMessage(const std::shared_ptr<Message> message)
}
break;
case Message::Command::InputMonitorInputIdChanged:
if(auto inputMonitor = std::dynamic_pointer_cast<InputMonitor>(m_objects.value(message->read<Handle>()).lock()))
{
const uint32_t address = message->read<uint32_t>();
const QString id = QString::fromUtf8(message->read<QByteArray>());
emit inputMonitor->inputIdChanged(address, id);
}
break;
case Message::Command::InputMonitorInputValueChanged:
if(auto inputMonitor = std::dynamic_pointer_cast<InputMonitor>(m_objects.value(message->read<Handle>()).lock()))
{
const uint32_t address = message->read<uint32_t>();
const TriState value = message->read<TriState>();
emit inputMonitor->inputValueChanged(address, value);
}
break;
default:
Q_ASSERT(false);
break;

Datei anzeigen

@ -37,6 +37,7 @@ class Property;
class ObjectProperty;
class UnitProperty;
class Method;
class InputMonitor;
class Connection : public QObject, public QEnableSharedFromThis<Connection>
{
@ -73,7 +74,7 @@ class Connection : public QObject, public QEnableSharedFromThis<Connection>
ObjectProperty* m_worldProperty;
int m_worldRequestId;
ObjectPtr m_world;
QMap<Handle, QWeakPointer<Object>> m_objects;
QMap<Handle, std::weak_ptr<Object>> m_objects;
QMap<Handle, TableModel*> m_tableModels;
void setState(State state);
@ -138,6 +139,8 @@ class Connection : public QObject, public QEnableSharedFromThis<Connection>
void releaseTableModel(TableModel* tableModel);
void setTableModelRegion(TableModel* tableModel, int columnMin, int columnMax, int rowMin, int rowMax);
[[nodiscard]] int getInputMonitorInputInfo(InputMonitor& object);
signals:
void stateChanged();
void worldChanged();

Datei anzeigen

@ -0,0 +1,43 @@
/**
* client/src/network/inputmonitor.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2020 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 "inputmonitor.hpp"
#include "connection.hpp"
InputMonitor::InputMonitor(const QSharedPointer<Connection>& connection, Handle handle, const QString& classId) :
Object(connection, handle, classId),
m_requestId{Connection::invalidRequestId}
{
}
InputMonitor::~InputMonitor()
{
if(m_requestId != Connection::invalidRequestId)
m_connection->cancelRequest(m_requestId);
}
void InputMonitor::refresh()
{
if(m_requestId != Connection::invalidRequestId)
m_connection->cancelRequest(m_requestId);
m_requestId = m_connection->getInputMonitorInputInfo(*this);
}

Datei anzeigen

@ -0,0 +1,49 @@
/**
* client/src/network/inputmonitor.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2020 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_NETWORK_INPUTMONITOR_HPP
#define TRAINTASTIC_CLIENT_NETWORK_INPUTMONITOR_HPP
#include "object.hpp"
#include <traintastic/enum/tristate.hpp>
class InputMonitor final : public Object
{
Q_OBJECT
private:
int m_requestId;
public:
inline static const QString classIdPrefix = QStringLiteral("input_monitor.");
InputMonitor(const QSharedPointer<Connection>& connection, Handle handle, const QString& classId);
~InputMonitor() final;
void refresh();
signals:
void inputIdChanged(uint32_t address, QString id);
void inputValueChanged(uint32_t address, TriState value);
};
#endif

Datei anzeigen

@ -46,8 +46,8 @@ class Object : public QObject
public:
explicit Object(const QSharedPointer<Connection>& connection, Handle handle, const QString& classId);
Object(const Object& copy) = delete;
~Object() final;
Object(const Object&) = delete;
~Object() override;
const QSharedPointer<Connection>& connection() const { return m_connection; }
Handle handle() const { return m_handle; }

Datei anzeigen

@ -23,10 +23,10 @@
#ifndef TRAINTASTIC_CLIENT_NETWORK_OBJECTPTR_HPP
#define TRAINTASTIC_CLIENT_NETWORK_OBJECTPTR_HPP
#include <QSharedPointer>
#include <memory>
class Object;
using ObjectPtr = QSharedPointer<Object>;
using ObjectPtr = std::shared_ptr<Object>;
#endif

Datei anzeigen

@ -28,7 +28,9 @@
#include "luascriptlistwidget.hpp"
#include "object/luascripteditwidget.hpp"
#include "object/objecteditwidget.hpp"
#include "inputmonitorwidget.hpp"
#include "../network/object.hpp"
#include "../network/inputmonitor.hpp"
QWidget* createWidgetIfCustom(const ObjectPtr& object, QWidget* parent)
{
@ -62,6 +64,8 @@ QWidget* createWidget(const ObjectPtr& object, QWidget* parent)
return widget;
else if(object->classId().startsWith("list."))
return new ObjectListWidget(object, parent);
else if(auto inputMonitor = std::dynamic_pointer_cast<InputMonitor>(object))
return new InputMonitorWidget(inputMonitor, parent);
else
return new ObjectEditWidget(object, parent);
}

Datei anzeigen

@ -0,0 +1,80 @@
/**
* client/src/widget/inputmonitorwidget.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2020 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 "inputmonitorwidget.hpp"
#include <QGridLayout>
#include "ledwidget.hpp"
#include "../network/inputmonitor.hpp"
constexpr LEDWidget::State toState(TriState value)
{
switch(value)
{
case TriState::False:
return LEDWidget::State::Off;
case TriState::True:
return LEDWidget::State::On;
case TriState::Undefined:
break;
}
return LEDWidget::State::Undefined;
}
InputMonitorWidget::InputMonitorWidget(std::shared_ptr<InputMonitor> object, QWidget* parent) :
QWidget(parent),
m_object{std::move(object)}
{
QGridLayout* grid = new QGridLayout();
for(int i = 1; i <= 128; i++)
{
auto* led = new LEDWidget(this);
led->setEnabled(false);
led->setText(QString::number(i));
grid->addWidget(led, (i - 1) / 16, (i - 1) % 16);
m_leds.emplace(i, led);
}
setLayout(grid);
connect(m_object.get(), &InputMonitor::inputIdChanged, this,
[this](uint32_t address, QString id)
{
if(auto* led = getLED(address))
led->setEnabled(!id.isEmpty());
});
connect(m_object.get(), &InputMonitor::inputValueChanged, this,
[this](uint32_t address, TriState value)
{
if(auto* led = getLED(address))
led->setState(toState(value));
});
m_object->refresh();
}
LEDWidget* InputMonitorWidget::getLED(uint32_t address)
{
auto it = m_leds.find(address);
return it != m_leds.end() ? it->second : nullptr;
}

Datei anzeigen

@ -0,0 +1,45 @@
/**
* client/src/widget/inputmonitorwidget.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2020 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_WIDGET_INPUTMONITORWIDGET_HPP
#define TRAINTASTIC_CLIENT_WIDGET_INPUTMONITORWIDGET_HPP
#include <QWidget>
#include <memory>
#include <unordered_map>
class InputMonitor;
class LEDWidget;
class InputMonitorWidget : public QWidget
{
protected:
std::shared_ptr<InputMonitor> m_object;
std::unordered_map<uint32_t, LEDWidget*> m_leds;
LEDWidget* getLED(uint32_t address);
public:
InputMonitorWidget(std::shared_ptr<InputMonitor> object, QWidget* parent = nullptr);
};
#endif

Datei anzeigen

@ -0,0 +1,103 @@
/**
* client/src/widget/ledwidget.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2020 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 "ledwidget.hpp"
#include <QPainter>
#include <QPainterPath>
LEDWidget::LEDWidget(QWidget* parent) :
QWidget(parent),
m_enabled{true},
m_state{State::Undefined}
{
setMinimumWidth(fontMetrics().averageCharWidth() * 4);
setMinimumHeight((fontMetrics().height() * 3) / 2);
}
void LEDWidget::setEnabled(bool value)
{
if(m_enabled != value)
{
m_enabled = value;
update();
}
}
void LEDWidget::setState(State value)
{
if(m_state != value)
{
m_state = value;
update();
}
}
void LEDWidget::setText(const QString& value)
{
if(m_text != value)
{
m_text = value;
update();
}
}
void LEDWidget::paintEvent(QPaintEvent*)
{
const int marginV = (height() - minimumHeight()) / 2;
const int ledHeight = minimumHeight() / 3;
const int ledWidth = fontMetrics().averageCharWidth() * 3;
const int ledLeft = (width() - ledWidth) / 2;
const int ledRadius = ledHeight / 2;
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
if(!m_text.isEmpty())
{
const QRect textRect = rect().adjusted(0, marginV + minimumHeight() / 2, 0, -marginV);
painter.setPen(palette().color(m_enabled ? QPalette::Normal : QPalette::Disabled, QPalette::WindowText));
painter.drawText(textRect, Qt::AlignCenter | Qt::AlignBaseline, m_text);
}
const QRect ledRect{ledLeft, marginV + 1, ledWidth, ledHeight};// = rect().adjusted(0, marginV, 0, -(marginV + minimumHeight() / 2));
QPainterPath path;
path.addRoundedRect(ledRect, ledRadius, ledRadius);
switch(m_state)
{
case State::Undefined:
break;
case State::Off:
painter.fillPath(path, QColor(0x20, 0x20, 0x20));//painter.fillPath(path, QColor(0x70, 0x80, 0x90));
break;
case State::On:
painter.fillPath(path, QColor(0x00, 0xBF, 0xFF));
break;
}
QPen pen = painter.pen();
pen.setColor(Qt::black);
pen.setWidth(2);
painter.setPen(pen);
painter.drawPath(path);
}

Datei anzeigen

@ -0,0 +1,57 @@
/**
* client/src/widget/ledwidget.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2020 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_WIDGET_LEDWIDGET_HPP
#define TRAINTASTIC_CLIENT_WIDGET_LEDWIDGET_HPP
#include <QWidget>
class LEDWidget : public QWidget
{
public:
enum State {
Undefined,
Off,
On,
};
protected:
bool m_enabled;
State m_state;
QString m_text;
void paintEvent(QPaintEvent*) final;
public:
explicit LEDWidget(QWidget *parent = nullptr);
bool enabled() const { return m_enabled; }
void setEnabled(bool value);
State state() const { return m_state; }
void setState(State value);
QString text() const { return m_text; }
void setText(const QString& value);
};
#endif

Datei anzeigen

@ -0,0 +1,76 @@
/**
* client/src/widget/methodpushbutton.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2020 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 "methodpushbutton.hpp"
#include "../network/connection.hpp"
#include "../network/method.hpp"
#include "../mainwindow.hpp"
MethodPushButton::MethodPushButton(Method& method, QWidget* parent) :
QPushButton(parent),
m_method{method},
m_requestId{Connection::invalidRequestId}
{
setEnabled(m_method.getAttributeBool(AttributeName::Enabled, true));
setVisible(m_method.getAttributeBool(AttributeName::Visible, true));
connect(&m_method, &Method::attributeChanged,
[this](AttributeName name, const QVariant& value)
{
switch(name)
{
case AttributeName::Enabled:
setEnabled(value.toBool());
break;
case AttributeName::Visible:
setVisible(value.toBool());
break;
default:
break;
}
});
connect(this, &MethodPushButton::clicked,
[this]()
{
if(m_method.argumentTypes().count() == 0)
switch(m_method.resultType())
{
case ValueType::Invalid:
m_method.call();
break;
case ValueType::Object:
m_requestId = m_method.call(
[this](const ObjectPtr& object, Message::ErrorCode)
{
if(object)
MainWindow::instance->showObject(object);
});
break;
default:
Q_ASSERT(false);
break;
}
});
}

Datei anzeigen

@ -0,0 +1,40 @@
/**
* client/src/widget/methodpushbutton.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2020 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_WIDGET_METHODPUSHBUTTON_HPP
#define TRAINTASTIC_CLIENT_WIDGET_METHODPUSHBUTTON_HPP
#include <QPushButton>
class Method;
class MethodPushButton : public QPushButton
{
protected:
Method& m_method;
int m_requestId;
public:
MethodPushButton(Method& method, QWidget* parent);
};
#endif

Datei anzeigen

@ -31,6 +31,7 @@
#include "../../network/property.hpp"
#include "../../network/objectproperty.hpp"
#include "../../network/unitproperty.hpp"
#include "../../network/method.hpp"
//#include "../../network/utils.hpp"
//#include "../alertwidget.hpp"
#include "../propertycheckbox.hpp"
@ -41,6 +42,7 @@
#include "../propertyobjectedit.hpp"
#include "../propertydirectioncontrol.hpp"
#include "../propertyvaluelabel.hpp"
#include "../methodpushbutton.hpp"
#include "../unitpropertyedit.hpp"
#include "../createwidget.hpp"
#include "../../utils/geticonforclassid.hpp"
@ -90,70 +92,77 @@ void ObjectEditWidget::buildForm()
for(const QString& name : m_object->interfaceItems().names())
{
if(AbstractProperty* baseProperty = m_object->getProperty(name))
if(InterfaceItem* item = m_object->getInterfaceItem(name))
{
if(!baseProperty->getAttributeBool(AttributeName::ObjectEditor, true))
continue;
QWidget* w = nullptr;
if(baseProperty->type() == ValueType::Object)
if(AbstractProperty* baseProperty = dynamic_cast<AbstractProperty*>(item))
{
ObjectProperty* property = static_cast<ObjectProperty*>(baseProperty);
if(contains(baseProperty->flags(), PropertyFlags::SubObject))
{
QWidget* w = new ObjectEditWidget(property->objectId());
w->setWindowTitle(property->displayName());
tabs.append(w);
if(!baseProperty->getAttributeBool(AttributeName::ObjectEditor, true))
continue;
if(baseProperty->type() == ValueType::Object)
{
ObjectProperty* property = static_cast<ObjectProperty*>(baseProperty);
if(contains(baseProperty->flags(), PropertyFlags::SubObject))
{
QWidget* w = new ObjectEditWidget(property->objectId());
w->setWindowTitle(property->displayName());
tabs.append(w);
continue;
}
else
{
w = new PropertyObjectEdit(*property);
}
}
else
{
w = new PropertyObjectEdit(*property);
Property* property = static_cast<Property*>(baseProperty);
if(UnitProperty* unitProperty = dynamic_cast<UnitProperty*>(property))
w = new UnitPropertyEdit(*unitProperty);
else if(!property->isWritable())
w = new PropertyValueLabel(*property);
else if(property->type() == ValueType::Boolean)
w = new PropertyCheckBox(*property);
else if(property->type() == ValueType::Integer)
w = new PropertySpinBox(*property);
else if(property->type() == ValueType::String)
{
if(property->name() == "notes")
{
PropertyTextEdit* edit = new PropertyTextEdit(*property);
edit->setWindowTitle(property->displayName());
edit->setPlaceholderText(property->displayName());
tabs.append(edit);
continue;
}
else if(property->name() == "code")
{
PropertyTextEdit* edit = new PropertyTextEdit(*property);
edit->setWindowTitle(property->displayName());
edit->setPlaceholderText(property->displayName());
tabs.append(edit);
continue;
}
else
w = new PropertyLineEdit(*property);
}
else if(property->type() == ValueType::Enum)
{
if(property->enumName() == EnumName<Direction>::value)
w = new PropertyDirectionControl(*property);
else
w = new PropertyComboBox(*property);
}
}
}
else
else if(Method* method = dynamic_cast<Method*>(item))
{
Property* property = static_cast<Property*>(baseProperty);
if(UnitProperty* unitProperty = dynamic_cast<UnitProperty*>(property))
w = new UnitPropertyEdit(*unitProperty);
else if(!property->isWritable())
w = new PropertyValueLabel(*property);
else if(property->type() == ValueType::Boolean)
w = new PropertyCheckBox(*property);
else if(property->type() == ValueType::Integer)
w = new PropertySpinBox(*property);
else if(property->type() == ValueType::String)
{
if(property->name() == "notes")
{
PropertyTextEdit* edit = new PropertyTextEdit(*property);
edit->setWindowTitle(property->displayName());
edit->setPlaceholderText(property->displayName());
tabs.append(edit);
continue;
}
else if(property->name() == "code")
{
PropertyTextEdit* edit = new PropertyTextEdit(*property);
edit->setWindowTitle(property->displayName());
edit->setPlaceholderText(property->displayName());
tabs.append(edit);
continue;
}
else
w = new PropertyLineEdit(*property);
}
else if(property->type() == ValueType::Enum)
{
if(property->enumName() == EnumName<Direction>::value)
w = new PropertyDirectionControl(*property);
else
w = new PropertyComboBox(*property);
}
w = new MethodPushButton(*method, this);
}
Category category = baseProperty->getAttributeEnum<Category>(AttributeName::Category, Category::General);
Category category = item->getAttributeEnum<Category>(AttributeName::Category, Category::General);
QWidget* tabWidget;
if(!categoryTabs.contains(category))
{
@ -166,7 +175,7 @@ void ObjectEditWidget::buildForm()
else
tabWidget = categoryTabs[category];
static_cast<QFormLayout*>(tabWidget->layout())->addRow(baseProperty->displayName(), w);
static_cast<QFormLayout*>(tabWidget->layout())->addRow(item->displayName(), w);
}
}

Datei anzeigen

@ -20,6 +20,7 @@ SOURCES += \
src/main.cpp \
src/mainwindow.cpp \
src/dialog/connectdialog.cpp \
src/network/inputmonitor.cpp \
src/network/object.cpp \
../shared/src/traintastic/locale/locale.cpp \
src/network/unitproperty.cpp \
@ -63,11 +64,15 @@ SOURCES += \
src/utils/geticonforclassid.cpp \
src/widget/propertyobjectedit.cpp \
src/utils/enum.cpp \
src/dialog/objectselectlistdialog.cpp
src/dialog/objectselectlistdialog.cpp \
src/widget/inputmonitorwidget.cpp \
src/widget/ledwidget.cpp \
src/widget/methodpushbutton.cpp
HEADERS += \
src/mainwindow.hpp \
src/dialog/connectdialog.hpp \
src/network/inputmonitor.hpp \
src/network/object.hpp \
../shared/src/traintastic/message.hpp \
../shared/src/traintastic/locale/locale.hpp \
@ -117,7 +122,10 @@ HEADERS += \
src/utils/geticonforclassid.hpp \
src/widget/propertyobjectedit.hpp \
src/utils/enum.hpp \
src/dialog/objectselectlistdialog.hpp
src/dialog/objectselectlistdialog.hpp \
src/widget/inputmonitorwidget.hpp \
src/widget/ledwidget.hpp \
src/widget/methodpushbutton.hpp
RESOURCES += \
dark.qrc

Datei anzeigen

@ -53,6 +53,13 @@ struct Attributes
item.setAttribute(AttributeName::Enabled, value);
}
template<typename T>
static inline void addMinMax(Property<T>& property, T min, T max)
{
property.addAttribute(AttributeName::Min, min);
property.addAttribute(AttributeName::Max, max);
}
static inline void addVisible(InterfaceItem& item, bool value)
{
item.addAttribute(AttributeName::Visible, value);

Datei anzeigen

@ -25,6 +25,7 @@
#include "abstractobjectlist.hpp"
#include "idobject.hpp"
#include "subobject.hpp"
template<typename T>
class ObjectListTableModel;
@ -34,7 +35,7 @@ class ObjectList : public AbstractObjectList
{
friend class ObjectListTableModel<T>;
static_assert(std::is_base_of<IdObject, T>::value);
static_assert(std::is_base_of_v<IdObject, T> || std::is_base_of_v<SubObject, T>);
public:
using Items = std::vector<std::shared_ptr<T>>;

Datei anzeigen

@ -115,7 +115,7 @@ class ObjectProperty : public AbstractObjectProperty
return *m_value;
}
inline operator bool()
inline operator bool() const
{
return m_value.operator bool();
}

Datei anzeigen

@ -35,6 +35,7 @@
#include "../world/world.hpp"
#include "idobject.hpp"
#include "subobject.hpp"
#include "../hardware/input/inputmonitor.hpp"
@ -194,7 +195,22 @@ bool Session::processMessage(const Message& message)
const std::string id = message.read<std::string>();
if(!id.empty())
{
if(ObjectPtr value = Traintastic::instance->world->getObject(id))
// TODO: move to function??
std::vector<std::string> ids;
boost::split(ids, id, [](char c){ return c == '.'; });
auto it = ids.cbegin();
ObjectPtr value = Traintastic::instance->world->getObject(*it);
while(value && ++it != ids.cend())
{
AbstractProperty* property = value->getProperty(*it);
if(property && property->type() == ValueType::Object)
value = property->toObject();
else
value = nullptr;
}
if(value)
property->fromObject(value);
else
throw std::runtime_error("");
@ -376,6 +392,24 @@ bool Session::processMessage(const Message& message)
}
break;
}
case Message::Command::InputMonitorGetInputInfo:
{
auto inputMonitor = std::dynamic_pointer_cast<InputMonitor>(m_handles.getItem(message.read<Handle>()));
if(inputMonitor)
{
auto inputInfo = inputMonitor->getInputInfo();
auto response = Message::newResponse(message.command(), message.requestId());
response->write<uint32_t>(inputInfo.size());
for(auto& info : inputInfo)
{
response->write(info.address);
response->write(info.id);
response->write(info.value);
}
m_client->sendMessage(std::move(response));
}
break;
}
default:
break;
}
@ -396,6 +430,12 @@ void Session::writeObject(Message& message, const ObjectPtr& object)
m_propertyChanged.emplace(handle, object->propertyChanged.connect(std::bind(&Session::objectPropertyChanged, this, std::placeholders::_1)));
m_attributeChanged.emplace(handle, object->attributeChanged.connect(std::bind(&Session::objectAttributeChanged, this, std::placeholders::_1)));
if(auto* inputMonitor = dynamic_cast<InputMonitor*>(object.get()))
{
inputMonitor->inputIdChanged = std::bind(&Session::inputMonitorInputIdChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
inputMonitor->inputValueChanged = std::bind(&Session::inputMonitorInputValueChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
}
message.write(handle);
message.write(object->getClassId());
@ -657,3 +697,21 @@ void Session::writeAttribute(Message& message , const AbstractAttribute& attribu
else
assert(false);
}
void Session::inputMonitorInputIdChanged(InputMonitor& inputMonitor, uint32_t address, const std::string& id)
{
auto event = Message::newEvent(Message::Command::InputMonitorInputIdChanged);
event->write(m_handles.getHandle(inputMonitor.shared_from_this()));
event->write(address);
event->write(id);
m_client->sendMessage(std::move(event));
}
void Session::inputMonitorInputValueChanged(InputMonitor& inputMonitor, uint32_t address, TriState value)
{
auto event = Message::newEvent(Message::Command::InputMonitorInputValueChanged);
event->write(m_handles.getHandle(inputMonitor.shared_from_this()));
event->write(address);
event->write(value);
m_client->sendMessage(std::move(event));
}

Datei anzeigen

@ -27,6 +27,7 @@
#include <boost/uuid/uuid.hpp>
#include <boost/signals2/connection.hpp>
#include <traintastic/network/message.hpp>
#include <traintastic/enum/tristate.hpp>
#include "handlelist.hpp"
#include "objectptr.hpp"
#include "tablemodelptr.hpp"
@ -34,6 +35,7 @@
class Client;
class AbstractProperty;
class AbstractAttribute;
class InputMonitor;
class Session : public std::enable_shared_from_this<Session>
{
@ -60,6 +62,9 @@ class Session : public std::enable_shared_from_this<Session>
void objectPropertyChanged(AbstractProperty& property);
void objectAttributeChanged(AbstractAttribute& attribute);
void inputMonitorInputIdChanged(InputMonitor& inputMonitor, uint32_t address, const std::string& id);
void inputMonitorInputValueChanged(InputMonitor& inputMonitor, uint32_t address, TriState value);
public:
Session(const std::shared_ptr<Client>& client);

Datei anzeigen

@ -40,9 +40,9 @@ class TableModel : public Object
Region() :
columnMin{0},
columnMin{1},
columnMax{0},
rowMin{0},
rowMin{1},
rowMax{0}
{
}
@ -77,6 +77,11 @@ class TableModel : public Object
this->rowMin >= rhs.rowMin &&
this->rowMax <= rhs.rowMax;
}
bool isValid() const
{
return columnMax >= columnMin && rowMax >= rowMin;
}
};
private:

34
server/src/enum/tristate.hpp Normale Datei
Datei anzeigen

@ -0,0 +1,34 @@
/**
* server/src/enum/tristate.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2020 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_SERVER_ENUM_TRISTATE_HPP
#define TRAINTASTIC_SERVER_ENUM_TRISTATE_HPP
#include <traintastic/enum/tristate.hpp>
inline constexpr std::array<TriState, 3> TriStateValues{{
TriState::Undefined,
TriState::False,
TriState::True,
}};
#endif

Datei anzeigen

@ -49,7 +49,7 @@ LocoNetSerial::LocoNetSerial(const std::weak_ptr<World>& world, std::string_view
loconet{this, "loconet", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject}
{
name = "LocoNet (serial)";
loconet.setValueInternal(std::make_shared<LocoNet::LocoNet>(*this, loconet.name(), std::bind(&LocoNetSerial::send, this, std::placeholders::_1)));
loconet.setValueInternal(LocoNet::LocoNet::create(*this, loconet.name(), std::bind(&LocoNetSerial::send, this, std::placeholders::_1)));
Attributes::addEnabled(interface, !online);
Attributes::addValues(interface, LocoNetSerialInterfaceValues);

Datei anzeigen

@ -82,7 +82,7 @@ RocoZ21::RocoZ21(const std::weak_ptr<World>& world, std::string_view _id) :
shortCircutExternal{this, "short_circut_external", false, PropertyFlags::ReadOnly}
{
name = "Z21";
loconet.setValueInternal(std::make_shared<LocoNet::LocoNet>(*this, loconet.name(),
loconet.setValueInternal(LocoNet::LocoNet::create(*this, loconet.name(),
[/*this*/](const ::LocoNet::Message& /*msg*/)
{
return false;

Datei anzeigen

@ -23,13 +23,19 @@
#include "input.hpp"
#include "../../world/world.hpp"
#include "inputlisttablemodel.hpp"
#include "../../core/attributes.hpp"
Input::Input(const std::weak_ptr<World> world, std::string_view _id) :
IdObject(world, _id),
name{this, "name", "", PropertyFlags::ReadWrite | PropertyFlags::Store},
value{this, "value", false, PropertyFlags::ReadOnly | PropertyFlags::StoreState}
value{this, "value", TriState::Undefined, PropertyFlags::ReadOnly | PropertyFlags::StoreState}
{
auto w = world.lock();
const bool editable = w && contains(w->state.value(), WorldState::Edit);
Attributes::addEnabled(name, editable);
m_interfaceItems.add(name);
Attributes::addValues(value, TriStateValues);
m_interfaceItems.add(value);
}
@ -41,7 +47,16 @@ void Input::addToWorld()
world->inputs->addObject(shared_ptr<Input>());
}
void Input::valueChanged(bool _value)
void Input::worldEvent(WorldState state, WorldEvent event)
{
IdObject::worldEvent(state, event);
const bool editable = contains(state, WorldState::Edit);
name.setAttributeEnabled(editable);
}
void Input::valueChanged(TriState _value)
{
// todo: delay in ms for 0->1 || 1->0
value.setValueInternal(_value);

Datei anzeigen

@ -24,17 +24,19 @@
#define TRAINTASTIC_SERVER_HARDWARE_INPUT_INPUT_HPP
#include "../../core/idobject.hpp"
#include "../../enum/tristate.hpp"
class Input : public IdObject
{
protected:
void addToWorld() override;
void worldEvent(WorldState state, WorldEvent event) override;
void valueChanged(bool _value);
void valueChanged(TriState _value);
public:
Property<std::string> name;
Property<bool> value;
Property<TriState> value;
Input(const std::weak_ptr<World> world, std::string_view _id);
};

Datei anzeigen

@ -22,10 +22,27 @@
#include "inputlist.hpp"
#include "inputlisttablemodel.hpp"
#include "inputs.hpp"
#include "../../world/getworld.hpp"
#include "../../core/attributes.hpp"
InputList::InputList(Object& _parent, const std::string& parentPropertyName) :
ObjectList<Input>(_parent, parentPropertyName)
ObjectList<Input>(_parent, parentPropertyName),
add{*this, "add",
[this](std::string_view classId)
{
auto world = getWorld(&this->parent());
if(!world)
return std::shared_ptr<Input>();
return Inputs::create(world, classId, world->getUniqueId("input"));
}}
{
auto w = getWorld(&_parent);
const bool editable = w && contains(w->state.value(), WorldState::Edit);
Attributes::addEnabled(add, editable);
Attributes::addClassList(add, Inputs::classList);
m_interfaceItems.add(add);
}
TableModelPtr InputList::getModel()
@ -33,6 +50,15 @@ TableModelPtr InputList::getModel()
return std::make_shared<InputListTableModel>(*this);
}
void InputList::worldEvent(WorldState state, WorldEvent event)
{
ObjectList<Input>::worldEvent(state, event);
const bool editable = contains(state, WorldState::Edit);
add.setAttributeEnabled(editable);
}
bool InputList::isListedProperty(const std::string& name)
{
return InputListTableModel::isListedProperty(name);

Datei anzeigen

@ -24,17 +24,21 @@
#define TRAINTASTIC_SERVER_HARDWARE_INPUT_INPUTLIST_HPP
#include "../../core/objectlist.hpp"
#include "../../core/method.hpp"
#include "inputlist.hpp"
#include "input.hpp"
class InputList : public ObjectList<Input>
{
protected:
void worldEvent(WorldState state, WorldEvent event) final;
bool isListedProperty(const std::string& name) final;
public:
CLASS_ID("input_list")
Method<std::shared_ptr<Input>(std::string_view)> add;
InputList(Object& _parent, const std::string& parentPropertyName);
TableModelPtr getModel() final;

Datei anzeigen

@ -22,23 +22,24 @@
#include "inputlisttablemodel.hpp"
#include "inputlist.hpp"
#include "loconetinput.hpp"
constexpr uint32_t columnId = 0;
constexpr uint32_t columnName = 1;
constexpr uint32_t columnValue = 2;
constexpr uint32_t columnBus = 2;
constexpr uint32_t columnAddress = 3;
bool InputListTableModel::isListedProperty(const std::string& name)
{
return
name == "id" ||
name == "name" ||
name == "value";
name == "name";
}
InputListTableModel::InputListTableModel(InputList& list) :
ObjectListTableModel<Input>(list)
{
setColumnHeaders({"Id", "Name", "Value"});
setColumnHeaders({"Id", "Name", "input_list:bus", "input_list:address"});
}
std::string InputListTableModel::getText(uint32_t column, uint32_t row) const
@ -46,6 +47,7 @@ std::string InputListTableModel::getText(uint32_t column, uint32_t row) const
if(row < rowCount())
{
const Input& input = getItem(row);
const LocoNetInput* inputLocoNet = dynamic_cast<const LocoNetInput*>(&input);
switch(column)
{
@ -55,8 +57,17 @@ std::string InputListTableModel::getText(uint32_t column, uint32_t row) const
case columnName:
return input.name;
case columnValue:
return input.value ? "1" : "0";
case columnBus: // virtual method @ Input ??
if(inputLocoNet && inputLocoNet->loconet)
return inputLocoNet->loconet->getObjectId();
else
return "";
case columnAddress: // virtual method @ Input ??
if(inputLocoNet)
return std::to_string(inputLocoNet->address);
else
return "";
default:
assert(false);
@ -73,6 +84,4 @@ void InputListTableModel::propertyChanged(AbstractProperty& property, uint32_t r
changed(row, columnId);
else if(property.name() == "name")
changed(row, columnName);
else if(property.name() == "value")
changed(row, columnValue);
}

Datei anzeigen

@ -0,0 +1,64 @@
/**
* server/src/hardware/input/inputmonitor.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2020 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_SERVER_HARDWARE_INPUT_INPUTMONITOR_HPP
#define TRAINTASTIC_SERVER_HARDWARE_INPUT_INPUTMONITOR_HPP
#include "../../core/object.hpp"
#include <vector>
#include "../../core/property.hpp"
#include "../../enum/tristate.hpp"
class InputMonitor : public Object
{
public:
std::function<void(InputMonitor&, uint32_t, const std::string&)> inputIdChanged;
std::function<void(InputMonitor&, uint32_t, TriState)> inputValueChanged;
struct InputInfo
{
uint32_t address;
std::string id;
TriState value;
InputInfo(uint32_t _address, std::string _id, TriState _value) :
address{_address},
id{std::move(_id)},
value{_value}
{
}
};
Property<uint32_t> addressMin;
Property<uint32_t> addressMax;
InputMonitor() :
Object(),
addressMin{this, "address_min", 0, PropertyFlags::ReadOnly | PropertyFlags::NoStore},
addressMax{this, "address_max", 0, PropertyFlags::ReadOnly | PropertyFlags::NoStore}
{
}
virtual std::vector<InputInfo> getInputInfo() const = 0;
};
#endif

Datei anzeigen

@ -0,0 +1,31 @@
/**
* server/src/hardware/input/inputs.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2020 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 "inputs.hpp"
std::shared_ptr<Input> Inputs::create(const std::weak_ptr<World>& world, std::string_view classId, std::string_view id)
{
if(classId == LocoNetInput::classId)
return LocoNetInput::create(world, id);
else
return std::shared_ptr<Input>();
}

Datei anzeigen

@ -0,0 +1,42 @@
/**
* server/src/hardware/input/inputs.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2020 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_SERVER_HARDWARE_INPUT_INPUTS_HPP
#define TRAINTASTIC_SERVER_HARDWARE_INPUT_INPUTS_HPP
#include "input.hpp"
#include "../../utils/makearray.hpp"
#include "loconetinput.hpp"
struct Inputs
{
static constexpr std::string_view classIdPrefix = "input.";
static constexpr auto classList = makeArray(
LocoNetInput::classId
);
static std::shared_ptr<Input> create(const std::weak_ptr<World>& world, std::string_view classId, std::string_view id);
};
#endif

Datei anzeigen

@ -22,6 +22,7 @@
#include "loconetinput.hpp"
#include "../../core/attributes.hpp"
#include "../../world/world.hpp"
LocoNetInput::LocoNetInput(const std::weak_ptr<World> world, std::string_view _id) :
Input(world, _id),
@ -36,18 +37,23 @@ LocoNetInput::LocoNetInput(const std::weak_ptr<World> world, std::string_view _i
}
return false;
}},
address{this, "address", 0, PropertyFlags::ReadWrite | PropertyFlags::Store, nullptr,
address{this, "address", addressMin, PropertyFlags::ReadWrite | PropertyFlags::Store, nullptr,
[this](const uint16_t& value)
{
if(loconet)
return loconet->isInputAddressAvailable(value);
return loconet->changeInputAddress(*this, value);
else
return false;
return true;
}}
{
Attributes::addEnabled(loconet, false);
auto w = world.lock();
const bool editable = w && contains(w->state.value(), WorldState::Edit);
Attributes::addEnabled(loconet, editable);
Attributes::addObjectList(loconet, w->loconets);
m_interfaceItems.add(loconet);
Attributes::addEnabled(address, false);
Attributes::addEnabled(address, editable);
Attributes::addMinMax(address, addressMin, addressMax);
m_interfaceItems.add(address);
}

Datei anzeigen

@ -34,12 +34,15 @@ class LocoNetInput : public Input
protected:
void worldEvent(WorldState state, WorldEvent event) final;
inline void valueChanged(bool _value) { Input::valueChanged(_value); }
inline void valueChanged(TriState _value) { Input::valueChanged(_value); }
public:
CLASS_ID("input.loconet")
CREATE(LocoNetInput)
static constexpr uint16_t addressMin = 1;
static constexpr uint16_t addressMax = 4096;
ObjectProperty<LocoNet::LocoNet> loconet;
Property<uint16_t> address;

Datei anzeigen

@ -28,6 +28,8 @@
#include "../../commandstation/commandstation.hpp"
#include "../../input/loconetinput.hpp"
#include "../../../core/attributes.hpp"
#include "../../../world/getworld.hpp"
#include "loconetlisttablemodel.hpp"
namespace LocoNet {
@ -41,7 +43,15 @@ void updateDecoderSpeed(const std::shared_ptr<Decoder>& decoder, uint8_t speed)
decoder->speedStep.setValueInternal(((speed - 1) * decoder->speedSteps) / (SPEED_MAX - 1));
}
LocoNet::LocoNet(Object& _parent, const std::string& parentPropertyName, std::function<bool(const Message&)> send) :
std::shared_ptr<LocoNet> LocoNet::create(Object& _parent, const std::string& parentPropertyName, std::function<bool(const Message&)> send)
{
std::shared_ptr<LocoNet> object{std::make_shared<LocoNet>(_parent, parentPropertyName, std::move(send), Private())};
if(auto w = getWorld(&_parent))
w->loconets->addObject(object);
return object;
}
LocoNet::LocoNet(Object& _parent, const std::string& parentPropertyName, std::function<bool(const Message&)> send, Private) :
SubObject(_parent, parentPropertyName),
m_commandStation{dynamic_cast<CommandStation*>(&_parent)},
m_send{std::move(send)},
@ -66,6 +76,11 @@ LocoNet::LocoNet(Object& _parent, const std::string& parentPropertyName, std::fu
[this](bool value)
{
m_debugLog = value;
}},
inputMonitor{*this, "input_monitor",
[this]()
{
return std::make_shared<LocoNetInputMonitor>(shared_ptr<LocoNet>());
}}
{
assert(m_send);
@ -74,6 +89,7 @@ LocoNet::LocoNet(Object& _parent, const std::string& parentPropertyName, std::fu
Attributes::addValues(commandStation, LocoNetCommandStationValues);
m_interfaceItems.add(commandStation);
m_interfaceItems.add(debugLog);
m_interfaceItems.add(inputMonitor);
}
bool LocoNet::send(const Message& message)
@ -204,9 +220,14 @@ void LocoNet::receive(const Message& message)
EventLoop::call(
[this, inputRep=*static_cast<const InputRep*>(&message)]()
{
auto it = m_inputs.find(1 + inputRep.address());
const uint16_t address = 1 + inputRep.fullAddress();
auto it = m_inputs.find(address);
if(it != m_inputs.end())
it->second->valueChanged(inputRep.value());
it->second->valueChanged(toTriState(inputRep.value()));
for(auto* inputMonitor : m_inputMonitors)
if(inputMonitor->inputValueChanged)
inputMonitor->inputValueChanged(*inputMonitor, address, toTriState(inputRep.value()));
});
break;
@ -372,14 +393,28 @@ std::shared_ptr<Decoder> LocoNet::getDecoder(uint8_t slot, bool request)
return nullptr;
}
bool LocoNet::isInputAddressAvailable(uint16_t address)
bool LocoNet::isInputAddressAvailable(uint16_t address) const
{
return m_inputs.find(address) == m_inputs.end();
}
bool LocoNet::changeInputAddress(const LocoNetInput& input, uint16_t newAddress)
{
assert(input.loconet.value().get() == this);
if(!isInputAddressAvailable(newAddress))
return false;
auto node = m_inputs.extract(input.address); // old address
node.key() = newAddress;
m_inputs.insert(std::move(node));
return true;
}
bool LocoNet::addInput(const std::shared_ptr<LocoNetInput>& input)
{
assert(input->loconet.value().get() == this);
assert(input);
if(isInputAddressAvailable(input->address))
{
m_inputs.insert({input->address, input});
@ -391,7 +426,7 @@ bool LocoNet::addInput(const std::shared_ptr<LocoNetInput>& input)
void LocoNet::removeInput(const std::shared_ptr<LocoNetInput>& input)
{
assert(input->loconet.value().get() == this);
assert(input && input->loconet.value().get() == this);
m_inputs.erase(m_inputs.find(input->address));
}

Datei anzeigen

@ -32,22 +32,31 @@
#include <vector>
#include "../../../core/subobject.hpp"
#include "../../../core/property.hpp"
#include "../../../core/method.hpp"
#include <traintastic/enum/direction.hpp>
#include "../../../enum/loconetcommandstation.hpp"
//#include <cstdint>
//#include <cassert>
#include "../../../hardware/decoder/decoderchangeflags.hpp"
#include "messages.hpp"
#include "loconetinputmonitor.hpp"
class CommandStation;
class Decoder;
class LocoNetInput;
class LocoNetInputMonitor;
namespace LocoNet {
class LocoNet : public SubObject
{
//friend class LocoNetInput;
friend class ::LocoNetInputMonitor;
private:
struct Private
{
};
protected:
static constexpr bool isLongAddress(uint16_t address)
@ -97,6 +106,7 @@ class LocoNet : public SubObject
std::unordered_map<uint16_t, std::vector<std::byte>> m_slotRequests;
uint8_t m_queryLocoSlots;
std::unordered_map<uint16_t, std::shared_ptr<LocoNetInput>> m_inputs;
std::vector<LocoNetInputMonitor*> m_inputMonitors;
std::shared_ptr<Decoder> getDecoder(uint8_t slot, bool request = true);
@ -108,7 +118,8 @@ class LocoNet : public SubObject
}
public://protected:
bool isInputAddressAvailable(uint16_t address);
bool isInputAddressAvailable(uint16_t address) const;
bool changeInputAddress(const LocoNetInput& input, uint16_t newAddress);
bool addInput(const std::shared_ptr<LocoNetInput>& input);
void removeInput(const std::shared_ptr<LocoNetInput>& input);
@ -117,8 +128,11 @@ class LocoNet : public SubObject
Property<LocoNetCommandStation> commandStation;
Property<bool> debugLog;
Method<std::shared_ptr<LocoNetInputMonitor>()> inputMonitor;
LocoNet(Object& _parent, const std::string& parentPropertyName, std::function<bool(const Message&)> send);
static std::shared_ptr<LocoNet> create(Object& _parent, const std::string& parentPropertyName, std::function<bool(const Message&)> send);
LocoNet(Object& _parent, const std::string& parentPropertyName, std::function<bool(const Message&)> send, Private);
bool send(const Message& message);
void receive(const Message& message);

Datei anzeigen

@ -0,0 +1,52 @@
/**
* server/src/hardware/protocol/loconet/loconetinputmonitor.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2020 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 "loconetinputmonitor.hpp"
#include "loconet.hpp"
#include "../../input/loconetinput.hpp"
LocoNetInputMonitor::LocoNetInputMonitor(std::shared_ptr<LocoNet::LocoNet> loconet) :
InputMonitor(),
m_loconet{std::move(loconet)}
{
addressMin.setValueInternal(LocoNetInput::addressMin);
addressMax.setValueInternal(LocoNetInput::addressMax);
m_loconet->m_inputMonitors.emplace_back(this);
}
LocoNetInputMonitor::~LocoNetInputMonitor()
{
if(auto it = std::find(m_loconet->m_inputMonitors.begin(), m_loconet->m_inputMonitors.end(), this); it != m_loconet->m_inputMonitors.end())
m_loconet->m_inputMonitors.erase(it);
}
std::vector<InputMonitor::InputInfo> LocoNetInputMonitor::getInputInfo() const
{
std::vector<InputInfo> inputInfo;
for(auto it : m_loconet->m_inputs)
{
LocoNetInput& input = *(it.second);
InputInfo info(input.address, input.id, input.value);
inputInfo.push_back(info);
}
return inputInfo;
}

Datei anzeigen

@ -0,0 +1,48 @@
/**
* server/src/hardware/protocol/loconet/loconetinputmonitor.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2020 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_SERVER_HARDWARE_PROTOCOL_LOCONET_LOCONETINPUTMONITOR_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_LOCONET_LOCONETINPUTMONITOR_HPP
#include "../../input/inputmonitor.hpp"
namespace LocoNet {
class LocoNet;
}
class LocoNetInputMonitor final : public InputMonitor
{
protected:
std::shared_ptr<LocoNet::LocoNet> m_loconet;
public:
CLASS_ID("input_monitor.loconet")
LocoNetInputMonitor(std::shared_ptr<LocoNet::LocoNet> loconet);
~LocoNetInputMonitor() final;
std::string getObjectId() const final { return ""; }
std::vector<InputInfo> getInputInfo() const final;
};
#endif

Datei anzeigen

@ -0,0 +1,39 @@
/**
* server/src/hardware/protocol/loconet/loconetlist.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2020 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 "loconetlist.hpp"
#include "loconetlisttablemodel.hpp"
LocoNetList::LocoNetList(Object& _parent, const std::string& parentPropertyName) :
ObjectList<LocoNet::LocoNet>(_parent, parentPropertyName)
{
}
TableModelPtr LocoNetList::getModel()
{
return std::make_shared<LocoNetListTableModel>(*this);
}
bool LocoNetList::isListedProperty(const std::string& name)
{
return LocoNetListTableModel::isListedProperty(name);
}

Datei anzeigen

@ -0,0 +1,42 @@
/**
* server/src/hardware/protocol/loconet/loconetlist.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2020 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_SERVER_HARDWARE_PROTOCOL_LOCONET_LOCONETLIST_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_LOCONET_LOCONETLIST_HPP
#include "../../../core/objectlist.hpp"
#include "loconet.hpp"
class LocoNetList : public ObjectList<LocoNet::LocoNet>
{
protected:
bool isListedProperty(const std::string& name) final;
public:
CLASS_ID("loconet_list")
LocoNetList(Object& _parent, const std::string& parentPropertyName);
TableModelPtr getModel() final;
};
#endif

Datei anzeigen

@ -0,0 +1,63 @@
/**
* server/src/hardware/input/loconetlisttablemodel.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2020 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 "loconetlisttablemodel.hpp"
#include "loconetlist.hpp"
constexpr uint32_t columnId = 0;
bool LocoNetListTableModel::isListedProperty(const std::string& name)
{
return name == "id";
}
LocoNetListTableModel::LocoNetListTableModel(LocoNetList& list) :
ObjectListTableModel<LocoNet::LocoNet>(list)
{
setColumnHeaders({"id"});
}
std::string LocoNetListTableModel::getText(uint32_t column, uint32_t row) const
{
if(row < rowCount())
{
const LocoNet::LocoNet& loconet = getItem(row);
switch(column)
{
case columnId:
return loconet.getObjectId();
default:
assert(false);
break;
}
}
return "";
}
void LocoNetListTableModel::propertyChanged(AbstractProperty& property, uint32_t row)
{
if(property.name() == "id")
changed(row, columnId);
}

Datei anzeigen

@ -0,0 +1,48 @@
/**
* server/src/hardware/protocol/loconet/loconetlisttablemodel.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2020 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_SERVER_HARDWARE_PROTOCOL_LOCONET_LOCONETLISTTABLEMODEL_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_LOCONET_LOCONETLISTTABLEMODEL_HPP
#include "../../../core/objectlisttablemodel.hpp"
#include "loconet.hpp"
class LocoNetList;
class LocoNetListTableModel : public ObjectListTableModel<LocoNet::LocoNet>
{
friend class LocoNetList;
protected:
void propertyChanged(AbstractProperty& property, uint32_t row) final;
public:
CLASS_ID("loconet_list_table_model")
static bool isListedProperty(const std::string& name);
LocoNetListTableModel(LocoNetList& list);
std::string getText(uint32_t column, uint32_t row) const final;
};
#endif

Datei anzeigen

@ -77,31 +77,31 @@ constexpr std::string_view toString(OpCode value)
{
switch(value)
{
case LocoNet::OPC_BUSY: return "OPC_BUSY";
case LocoNet::OPC_GPOFF: return "OPC_GPOFFqqq";
case LocoNet::OPC_GPON: return "OPC_GPON";
case LocoNet::OPC_IDLE: return "OPC_IDLE";
case LocoNet::OPC_LOCO_SPD: return "OPC_LOCO_SPD";
case LocoNet::OPC_LOCO_DIRF: return "OPC_LOCO_DIRF";
case LocoNet::OPC_LOCO_SND: return "OPC_LOCO_SND";
case LocoNet::OPC_SW_REQ: return "OPC_SW_REQ";
case LocoNet::OPC_SW_REP: return "OPC_SW_REP";
case LocoNet::OPC_INPUT_REP: return "OPC_INPUT_REP";
case LocoNet::OPC_LONG_ACK: return "OPC_LONG_ACK";
case LocoNet::OPC_SLOT_STAT1: return "OPC_SLOT_STAT1";
case LocoNet::OPC_CONSIST_FUNC: return "OPC_CONSIST_FUNC";
case LocoNet::OPC_UNLINK_SLOTS: return "OPC_UNLINK_SLOTS";
case LocoNet::OPC_LINK_SLOTS: return "OPC_LINK_SLOTS";
case LocoNet::OPC_MOVE_SLOTS: return "OPC_MOVE_SLOTS";
case LocoNet::OPC_RQ_SL_DATA: return "OPC_RQ_SL_DATA";
case LocoNet::OPC_SW_STATE: return "OPC_SW_STATE";
case LocoNet::OPC_SW_ACK: return "OPC_SW_ACK";
case LocoNet::OPC_LOCO_ADR: return "OPC_LOCO_ADR";
case LocoNet::OPC_MULTI_SENSE: return "OPC_MULTI_SENSE";
case LocoNet::OPC_PEER_XFER: return "OPC_PEER_XFER";
case LocoNet::OPC_SL_RD_DATA: return "OPC_SL_RD_DATA";
case LocoNet::OPC_IMM_PACKET: return "OPC_IMM_PACKET";
case LocoNet::OPC_WR_SL_DATA: return "OPC_WR_SL_DATA";
case OPC_BUSY: return "OPC_BUSY";
case OPC_GPOFF: return "OPC_GPOFFqqq";
case OPC_GPON: return "OPC_GPON";
case OPC_IDLE: return "OPC_IDLE";
case OPC_LOCO_SPD: return "OPC_LOCO_SPD";
case OPC_LOCO_DIRF: return "OPC_LOCO_DIRF";
case OPC_LOCO_SND: return "OPC_LOCO_SND";
case OPC_SW_REQ: return "OPC_SW_REQ";
case OPC_SW_REP: return "OPC_SW_REP";
case OPC_INPUT_REP: return "OPC_INPUT_REP";
case OPC_LONG_ACK: return "OPC_LONG_ACK";
case OPC_SLOT_STAT1: return "OPC_SLOT_STAT1";
case OPC_CONSIST_FUNC: return "OPC_CONSIST_FUNC";
case OPC_UNLINK_SLOTS: return "OPC_UNLINK_SLOTS";
case OPC_LINK_SLOTS: return "OPC_LINK_SLOTS";
case OPC_MOVE_SLOTS: return "OPC_MOVE_SLOTS";
case OPC_RQ_SL_DATA: return "OPC_RQ_SL_DATA";
case OPC_SW_STATE: return "OPC_SW_STATE";
case OPC_SW_ACK: return "OPC_SW_ACK";
case OPC_LOCO_ADR: return "OPC_LOCO_ADR";
case OPC_MULTI_SENSE: return "OPC_MULTI_SENSE";
case OPC_PEER_XFER: return "OPC_PEER_XFER";
case OPC_SL_RD_DATA: return "OPC_SL_RD_DATA";
case OPC_IMM_PACKET: return "OPC_IMM_PACKET";
case OPC_WR_SL_DATA: return "OPC_WR_SL_DATA";
}
return {};

Datei anzeigen

@ -45,6 +45,7 @@ void World::init(const std::shared_ptr<World>& world)
world->decoders.setValueInternal(std::make_shared<DecoderList>(*world, world->decoders.name()));
world->inputs.setValueInternal(std::make_shared<InputList>(*world, world->inputs.name()));
world->controllers.setValueInternal(std::make_shared<ControllerList>(*world, world->controllers.name()));
world->loconets.setValueInternal(std::make_shared<LocoNetList>(*world, world->loconets.name()));
world->clock.setValueInternal(std::make_shared<Clock>(*world, world->clock.name()));
world->trains.setValueInternal(std::make_shared<TrainList>(*world, world->trains.name()));
world->railVehicles.setValueInternal(std::make_shared<RailVehicleList>(*world, world->railVehicles.name()));
@ -62,6 +63,7 @@ World::World(Private) :
decoders{this, "decoders", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject},
inputs{this, "inputs", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject},
controllers{this, "controllers", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject},
loconets{this, "loconets", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject},
clock{this, "clock", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject},
trains{this, "trains", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject},
railVehicles{this, "rail_vehicles", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject},
@ -129,6 +131,7 @@ World::World(Private) :
m_interfaceItems.add(decoders);
m_interfaceItems.add(inputs);
m_interfaceItems.add(controllers);
m_interfaceItems.add(loconets);
m_interfaceItems.add(clock);
m_interfaceItems.add(trains);
m_interfaceItems.add(railVehicles);

Datei anzeigen

@ -37,6 +37,7 @@
#include "../hardware/decoder/decoderlist.hpp"
#include "../hardware/input/inputlist.hpp"
#include "../hardware/controller/controllerlist.hpp"
#include "../hardware/protocol/loconet/loconetlist.hpp"
#include "../train/trainlist.hpp"
#include "../vehicle/rail/railvehiclelist.hpp"
#ifndef DISABLE_LUA_SCRIPTING
@ -81,6 +82,7 @@ class World : public Object
ObjectProperty<DecoderList> decoders;
ObjectProperty<InputList> inputs;
ObjectProperty<ControllerList> controllers;
ObjectProperty<LocoNetList> loconets;
ObjectProperty<Clock> clock;
ObjectProperty<TrainList> trains;
ObjectProperty<RailVehicleList> railVehicles;

Datei anzeigen

@ -0,0 +1,63 @@
/**
* shared/src/enum/tristate.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2020 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_SHARED_TRAINTASTIC_ENUM_TRISTATE_HPP
#define TRAINTASTIC_SHARED_TRAINTASTIC_ENUM_TRISTATE_HPP
#include <cstdint>
#include "enum.hpp"
enum class TriState : uint8_t
{
Undefined = 0,
False = 1,
True = 2,
};
ENUM_NAME(TriState, "tri_state")
ENUM_VALUES(TriState, 3,
{
{TriState::Undefined, "undefined"},
{TriState::False, "false"},
{TriState::True, "true"},
})
constexpr TriState toTriState(bool value)
{
return value ? TriState::True : TriState::False;
}
constexpr TriState operator!(TriState value)
{
switch(value)
{
case TriState::False:
return TriState::True;
case TriState::True:
return TriState::False;
default:
return TriState::Undefined;
}
}
#endif

Datei anzeigen

@ -71,6 +71,10 @@ class Message
TableModelSetRegion = 23,
TableModelUpdateRegion = 24,
InputMonitorGetInputInfo = 30,
InputMonitorInputIdChanged = 31,
InputMonitorInputValueChanged = 32,
Discover = 255,
};