diff --git a/client/src/mainwindow.cpp b/client/src/mainwindow.cpp index 1501736f..cde88f65 100644 --- a/client/src/mainwindow.cpp +++ b/client/src/mainwindow.cpp @@ -225,11 +225,10 @@ MainWindow::MainWindow(QWidget* parent) : m_menuObjects = menuBar()->addMenu(Locale::tr("qtapp.mainmenu:objects")); menu = m_menuObjects->addMenu(Theme::getIcon("hardware"), Locale::tr("qtapp.mainmenu:hardware")); - menu->addAction(Locale::tr("world:command_stations") + "...", [this](){ showObject("world.command_stations", Locale::tr("world:command_stations")); }); + menu->addAction(Locale::tr("world:interfaces") + "...", [this](){ showObject("world.interfaces", Locale::tr("world:interfaces")); }); menu->addAction(Locale::tr("world:decoders") + "...", [this](){ showObject("world.decoders", Locale::tr("world:decoders")); }); menu->addAction(Locale::tr("world:inputs") + "...", [this](){ showObject("world.inputs", Locale::tr("world:inputs")); }); menu->addAction(Locale::tr("world:outputs") + "...", [this](){ showObject("world.outputs", Locale::tr("world:outputs")); }); - menu->addAction(Locale::tr("world:controllers") + "...", [this](){ showObject("world.controllers", Locale::tr("world:controllers")); }); m_menuObjects->addAction(Locale::tr("world:boards") + "...", [this](){ showObject("world.boards", Locale::tr("world:boards")); }); m_menuObjects->addAction(Locale::tr("world:clock") + "...", [this](){ showObject("world.clock", Locale::tr("world:clock")); }); m_menuObjects->addAction(Locale::tr("world:trains") + "...", [this](){ showObject("world.trains", Locale::tr("world:trains")); }); diff --git a/client/src/network/connection.cpp b/client/src/network/connection.cpp index 0bbe2b67..7b0346e5 100644 --- a/client/src/network/connection.cpp +++ b/client/src/network/connection.cpp @@ -476,9 +476,9 @@ ObjectPtr Connection::readObject(const Message& message) else { const QString classId = QString::fromLatin1(message.read()); - if(classId.startsWith(InputMonitor::classIdPrefix)) + if(classId == InputMonitor::classId) p = new InputMonitor(shared_from_this(), handle, classId); - else if(classId.startsWith(OutputKeyboard::classIdPrefix)) + else if(classId == OutputKeyboard::classId) p = new OutputKeyboard(shared_from_this(), handle, classId); else if(classId.startsWith(OutputMap::classIdPrefix)) p = new OutputMap(shared_from_this(), handle, classId); diff --git a/client/src/network/inputmonitor.hpp b/client/src/network/inputmonitor.hpp index c9d999bd..356bf7e8 100644 --- a/client/src/network/inputmonitor.hpp +++ b/client/src/network/inputmonitor.hpp @@ -34,7 +34,7 @@ class InputMonitor final : public Object int m_requestId; public: - inline static const QString classIdPrefix = QStringLiteral("input_monitor."); + inline static const QString classId = QStringLiteral("input_monitor"); InputMonitor(const std::shared_ptr& connection, Handle handle, const QString& classId); ~InputMonitor() final; diff --git a/client/src/network/outputkeyboard.hpp b/client/src/network/outputkeyboard.hpp index 7cdfad8b..3e5c0e41 100644 --- a/client/src/network/outputkeyboard.hpp +++ b/client/src/network/outputkeyboard.hpp @@ -34,7 +34,7 @@ class OutputKeyboard final : public Object int m_requestId; public: - inline static const QString classIdPrefix = QStringLiteral("output_keyboard."); + inline static const QString classId = QStringLiteral("output_keyboard"); OutputKeyboard(std::shared_ptr connection, Handle handle, const QString& classId); ~OutputKeyboard() final; diff --git a/client/src/widget/createwidget.cpp b/client/src/widget/createwidget.cpp index 883d8c85..33aad0fd 100644 --- a/client/src/widget/createwidget.cpp +++ b/client/src/widget/createwidget.cpp @@ -42,8 +42,6 @@ QWidget* createWidgetIfCustom(const ObjectPtr& object, QWidget* parent) return new ObjectListWidget(object, parent); // todo remove else if(classId == "decoder_list") return new ThrottleObjectListWidget(object, parent); // todo remove - else if(classId == "input_list") - return new ObjectListWidget(object, parent); // todo remove else if(classId == "controller_list") return new ObjectListWidget(object, parent); // todo remove else if(classId == "rail_vehicle_list") @@ -52,6 +50,8 @@ QWidget* createWidgetIfCustom(const ObjectPtr& object, QWidget* parent) return new ObjectListWidget(object, parent); // todo remove else if(classId == "world_list") return new ObjectListWidget(object, parent); + else if(object->classId().startsWith("list.")) + return new ObjectListWidget(object, parent); else if(classId == "lua.script") return new LuaScriptEditWidget(object, parent); else if(auto outputMap = std::dynamic_pointer_cast(object)) @@ -66,8 +66,6 @@ QWidget* createWidget(const ObjectPtr& object, QWidget* parent) { if(QWidget* widget = createWidgetIfCustom(object, parent)) return widget; - else if(object->classId().startsWith("list.")) - return new ObjectListWidget(object, parent); else if(auto inputMonitor = std::dynamic_pointer_cast(object)) return new InputMonitorWidget(inputMonitor, parent); else if(auto outputKeyboard = std::dynamic_pointer_cast(object)) diff --git a/client/src/widget/objectlist/objectlistwidget.cpp b/client/src/widget/objectlist/objectlistwidget.cpp index caec0d98..2be858ab 100644 --- a/client/src/widget/objectlist/objectlistwidget.cpp +++ b/client/src/widget/objectlist/objectlistwidget.cpp @@ -49,12 +49,16 @@ ObjectListWidget::ObjectListWidget(const ObjectPtr& object, QWidget* parent) : QWidget(parent), + m_requestIdInputMonitor{Connection::invalidRequestId}, + m_requestIdOutputKeyboard{Connection::invalidRequestId}, m_buttonAdd{nullptr}, m_object{object}, m_toolbar{new QToolBar()}, m_actionAdd{nullptr}, m_actionEdit{nullptr}, m_actionDelete{nullptr}, + m_actionInputMonitor{nullptr}, + m_actionOutputKeyboard{nullptr}, m_tableWidget{new TableWidget()} { m_tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows); @@ -188,6 +192,43 @@ ObjectListWidget::ObjectListWidget(const ObjectPtr& object, QWidget* parent) : m_actionDelete->setForceDisabled(true); m_toolbar->addAction(m_actionDelete); } + + if(Method* method = m_object->getMethod("input_monitor")) + { + m_actionInputMonitor = new MethodAction(Theme::getIcon("input_monitor"), *method, + [this]() + { + m_requestIdInputMonitor = m_actionInputMonitor->method().call( + [this](const ObjectPtr& inputMonitor, Message::ErrorCode) + { + if(inputMonitor) + MainWindow::instance->showObject(inputMonitor); + }); + }); + } + + if(Method* method = m_object->getMethod("output_keyboard")) + { + m_actionOutputKeyboard = new MethodAction(Theme::getIcon("output_keyboard"), *method, + [this]() + { + m_requestIdOutputKeyboard = m_actionOutputKeyboard->method().call( + [this](const ObjectPtr& outputKeyboard, Message::ErrorCode) + { + if(outputKeyboard) + MainWindow::instance->showObject(outputKeyboard); + }); + }); + } + + if(m_actionInputMonitor || m_actionOutputKeyboard) + { + m_toolbar->addSeparator(); + if(m_actionInputMonitor) + m_toolbar->addAction(m_actionInputMonitor); + if(m_actionOutputKeyboard) + m_toolbar->addAction(m_actionOutputKeyboard); + } } ObjectListWidget::~ObjectListWidget() diff --git a/client/src/widget/objectlist/objectlistwidget.hpp b/client/src/widget/objectlist/objectlistwidget.hpp index 07b45032..8b6ee34f 100644 --- a/client/src/widget/objectlist/objectlistwidget.hpp +++ b/client/src/widget/objectlist/objectlistwidget.hpp @@ -36,15 +36,18 @@ class ObjectListWidget : public QWidget Q_OBJECT private: - //const QString m_id; int m_requestId; int m_requestIdAdd; + int m_requestIdInputMonitor; + int m_requestIdOutputKeyboard; ObjectPtr m_object; QToolBar* m_toolbar; QToolButton* m_buttonAdd; QAction* m_actionAdd; QAction* m_actionEdit; MethodAction* m_actionDelete; + MethodAction* m_actionInputMonitor; + MethodAction* m_actionOutputKeyboard; TableWidget* m_tableWidget; void tableSelectionChanged(); diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index 751b4adc..f1d06c7e 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -77,10 +77,6 @@ file(GLOB SOURCES "src/core/*.hpp" "src/core/*.cpp" "src/enum/*.hpp" - "src/hardware/commandstation/*.hpp" - "src/hardware/commandstation/*.cpp" - "src/hardware/controller/*.hpp" - "src/hardware/controller/*.cpp" "src/hardware/decoder/*.hpp" "src/hardware/decoder/*.cpp" "src/hardware/input/*.hpp" @@ -91,6 +87,8 @@ file(GLOB SOURCES "src/hardware/input/map/*.cpp" "src/hardware/input/monitor/*.hpp" "src/hardware/input/monitor/*.cpp" + "src/hardware/interface/*.hpp" + "src/hardware/interface/*.cpp" "src/hardware/output/*.hpp" "src/hardware/output/*.cpp" "src/hardware/output/keyboard/*.hpp" @@ -99,14 +97,16 @@ file(GLOB SOURCES "src/hardware/output/list/*.cpp" "src/hardware/output/map/*.hpp" "src/hardware/output/map/*.cpp" - "src/hardware/protocol/dccplusplus/*.hpp" - "src/hardware/protocol/dccplusplus/*.cpp" +# "src/hardware/protocol/dccplusplus/*.hpp" +# "src/hardware/protocol/dccplusplus/*.cpp" "src/hardware/protocol/loconet/*.hpp" "src/hardware/protocol/loconet/*.cpp" - "src/hardware/protocol/xpressnet/*.hpp" - "src/hardware/protocol/xpressnet/*.cpp" - "src/hardware/protocol/z21/*.hpp" - "src/hardware/protocol/z21/*.cpp" + "src/hardware/protocol/loconet/iohandler/*.hpp" + "src/hardware/protocol/loconet/iohandler/*.cpp" +# "src/hardware/protocol/xpressnet/*.hpp" +# "src/hardware/protocol/xpressnet/*.cpp" +# "src/hardware/protocol/z21/*.hpp" +# "src/hardware/protocol/z21/*.cpp" "src/log/*.hpp" "src/log/*.cpp" "src/train/*.hpp" diff --git a/server/src/core/commandstationproperty.cpp b/server/src/core/commandstationproperty.cpp deleted file mode 100644 index cba78f0c..00000000 --- a/server/src/core/commandstationproperty.cpp +++ /dev/null @@ -1,140 +0,0 @@ -/** - * server/src/core/commandstationproperty.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 "commandstationproperty.hpp" -#include "../hardware/commandstation/commandstation.hpp" - -using T = CommandStation; - -CommandStationProperty::CommandStationProperty(Object* object, const std::string& name, const std::shared_ptr& value, PropertyFlags flags) : - AbstractObjectProperty(object, name, flags), - m_value{value} -{ -} - -CommandStationProperty::CommandStationProperty(Object* object, const std::string& name, std::nullptr_t, PropertyFlags flags) : - CommandStationProperty(object, name, std::shared_ptr(), flags) -{ -} - -CommandStationProperty::CommandStationProperty(Object* object, const std::string& name, const std::shared_ptr& value, PropertyFlags flags, OnSet onSet) : - CommandStationProperty(object, name, value, flags) -{ - m_onSet = onSet; -} - -CommandStationProperty::CommandStationProperty(Object* object, const std::string& name, std::nullptr_t, PropertyFlags flags, OnSet onSet) : - CommandStationProperty(object, name, std::shared_ptr(), flags, onSet) -{ -} - -const std::shared_ptr& CommandStationProperty::value() const -{ - return m_value; -} - -void CommandStationProperty::setValue(const std::shared_ptr& value) -{ - assert(isWriteable()); - if(m_value == value) - return; - else if(!isWriteable()) - throw not_writable_error(); - else if(!m_onSet || m_onSet(value)) - { - m_value = value; - changed(); - } - else - throw invalid_value_error(); - } - -void CommandStationProperty::setValueInternal(const std::shared_ptr& value) -{ - if(m_value != value) - { - m_value = value; - changed(); - } -} - -const T* CommandStationProperty::operator ->() const -{ - return m_value.get(); -} - -T* CommandStationProperty::operator ->() -{ - return m_value.get(); -} - -const T& CommandStationProperty::operator *() const -{ - return *m_value; -} - -T& CommandStationProperty::operator *() -{ - return *m_value; -} - -CommandStationProperty::operator bool() -{ - return m_value.operator bool(); -} - -CommandStationProperty& CommandStationProperty::operator =(const std::shared_ptr& value) -{ - setValue(value); - return *this; -} - -ObjectPtr CommandStationProperty::toObject() const -{ - return std::dynamic_pointer_cast(m_value); -} - -void CommandStationProperty::fromObject(const ObjectPtr& value) -{ - if(value) - { - if(std::shared_ptr v = std::dynamic_pointer_cast(value)) - setValue(v); - else - throw conversion_error(); - } - else - setValue(nullptr); -} - -void CommandStationProperty::load(const ObjectPtr& value) -{ - if(value) - { - if(std::shared_ptr v = std::dynamic_pointer_cast(value)) - m_value = v; - else - throw conversion_error(); - } - else - m_value.reset(); -} diff --git a/server/src/core/commandstationproperty.hpp b/server/src/core/commandstationproperty.hpp deleted file mode 100644 index 27cad650..00000000 --- a/server/src/core/commandstationproperty.hpp +++ /dev/null @@ -1,68 +0,0 @@ -/** - * server/src/core/commandstationproperty.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_CORE_COMMANDSTATIONPROPERTY_HPP -#define TRAINTASTIC_SERVER_CORE_COMMANDSTATIONPROPERTY_HPP - -#include "abstractobjectproperty.hpp" -#include - -class CommandStation; - -//! workaround for ObjectProperty -class CommandStationProperty : public AbstractObjectProperty -{ - public: - using OnSet = std::function& value)>; - - protected: - std::shared_ptr m_value; - OnSet m_onSet; - - public: - CommandStationProperty(Object* object, const std::string& name, const std::shared_ptr& value, PropertyFlags flags); - CommandStationProperty(Object* object, const std::string& name, std::nullptr_t, PropertyFlags flags); - CommandStationProperty(Object* object, const std::string& name, const std::shared_ptr& value, PropertyFlags flags, OnSet onSet); - CommandStationProperty(Object* object, const std::string& name, std::nullptr_t, PropertyFlags flags, OnSet onSet); - - const std::shared_ptr& value() const; - - void setValue(const std::shared_ptr& value); - void setValueInternal(const std::shared_ptr& value); - - const CommandStation* operator ->() const; - CommandStation* operator ->(); - - const CommandStation& operator *() const; - CommandStation& operator *(); - - operator bool(); - - CommandStationProperty& operator =(const std::shared_ptr& value); - - ObjectPtr toObject() const final; - void fromObject(const ObjectPtr& value) final; - - void load(const ObjectPtr& value) final; -}; - -#endif diff --git a/server/src/core/controllerlist.hpp b/server/src/core/controllerlist.hpp new file mode 100644 index 00000000..f37e250e --- /dev/null +++ b/server/src/core/controllerlist.hpp @@ -0,0 +1,51 @@ +/** + * server/src/core/controllerlist.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021 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_CORE_CONTROLLERLIST_HPP +#define TRAINTASTIC_SERVER_CORE_CONTROLLERLIST_HPP + +#include "controllerlistbase.hpp" + +template +class ControllerList : public ControllerListBase +{ + public: + ControllerList(Object& _parent, const std::string& parentPropertyName) + : ControllerListBase(_parent, parentPropertyName) + { + } + + inline void add(const std::shared_ptr& controller) + { + if(ObjectPtr object = std::dynamic_pointer_cast(controller)) + ControllerListBase::add(std::move(object)); + else + assert(false); + } + + inline void remove(const std::shared_ptr& controller) + { + ControllerListBase::remove(std::dynamic_pointer_cast(controller)); + } +}; + +#endif diff --git a/server/src/core/controllerlistbase.cpp b/server/src/core/controllerlistbase.cpp new file mode 100644 index 00000000..43587d4a --- /dev/null +++ b/server/src/core/controllerlistbase.cpp @@ -0,0 +1,79 @@ +/** + * server/src/core/controllerlistbase.cpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021 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 "controllerlistbase.hpp" +#include "controllerlistbasetablemodel.hpp" + +ControllerListBase::ControllerListBase(Object& _parent, const std::string& parentPropertyName) + : AbstractObjectList(_parent, parentPropertyName) + , length{this, "length", 0, PropertyFlags::ReadOnly} +{ +} + +TableModelPtr ControllerListBase::getModel() +{ + return std::make_shared(*this); +} + +void ControllerListBase::add(ObjectPtr controller) +{ + assert(controller); + m_propertyChanged.emplace(controller.get(), controller->propertyChanged.connect( + [this](BaseProperty& property) + { + if(!m_models.empty() && ControllerListBaseTableModel::isListedProperty(property.name())) + { + ObjectPtr obj = property.object().shared_from_this(); + const uint32_t rows = static_cast(m_items.size()); + for(uint32_t row = 0; row < rows; row++) + if(m_items[row] == obj) + { + for(auto& model : m_models) + model->propertyChanged(property, row); + break; + } + } + })); + m_items.emplace_back(std::move(controller)); + rowCountChanged(); +} + +void ControllerListBase::remove(const ObjectPtr& controller) +{ + assert(controller); + auto it = std::find(m_items.begin(), m_items.end(), controller); + if(it != m_items.end()) + { + m_propertyChanged.erase(controller.get()); + m_items.erase(it); + rowCountChanged(); + } +} + +void ControllerListBase::rowCountChanged() +{ + const auto size = m_items.size(); + length.setValueInternal(static_cast(size)); + for(auto& model : m_models) + model->setRowCount(static_cast(size)); +} + diff --git a/server/src/core/controllerlistbase.hpp b/server/src/core/controllerlistbase.hpp new file mode 100644 index 00000000..68d5812d --- /dev/null +++ b/server/src/core/controllerlistbase.hpp @@ -0,0 +1,59 @@ +/** + * server/src/core/controllerlistbase.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021 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_CORE_CONTROLLERLISTBASE_HPP +#define TRAINTASTIC_SERVER_CORE_CONTROLLERLISTBASE_HPP + +#include "abstractobjectlist.hpp" +#include "property.hpp" + +class ControllerListBaseTableModel; + +class ControllerListBase : public AbstractObjectList +{ + friend class ControllerListBaseTableModel; + + CLASS_ID("list.controller") + + private: + std::vector m_items; + std::unordered_map m_propertyChanged; + std::vector m_models; + + void rowCountChanged(); + + protected: + std::vector getItems() const final { return m_items; } + void setItems(const std::vector& items) final { m_items = items; } + + void add(ObjectPtr object); + void remove(const ObjectPtr& object); + + public: + Property length; + + ControllerListBase(Object& _parent, const std::string& parentPropertyName); + + TableModelPtr getModel() final; +}; + +#endif diff --git a/server/src/core/controllerlistbasetablemodel.cpp b/server/src/core/controllerlistbasetablemodel.cpp new file mode 100644 index 00000000..5e9a53a2 --- /dev/null +++ b/server/src/core/controllerlistbasetablemodel.cpp @@ -0,0 +1,81 @@ +/** + * server/src/core/controllerlistbasetablemodel.cpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021 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 "controllerlistbasetablemodel.hpp" +#include "controllerlistbase.hpp" +#include "../utils/displayname.hpp" + +constexpr uint32_t columnId = 0; +constexpr uint32_t columnName = 1; + +bool ControllerListBaseTableModel::isListedProperty(const std::string& name) +{ + return + name == "id" || + name == "name"; +} + +ControllerListBaseTableModel::ControllerListBaseTableModel(ControllerListBase& list) + : TableModel() + , m_list{list.shared_ptr()} +{ + list.m_models.push_back(this); + setRowCount(static_cast(list.m_items.size())); + + setColumnHeaders({ + DisplayName::Object::id, + DisplayName::Object::name, + }); +} + +std::string ControllerListBaseTableModel::getText(uint32_t column, uint32_t row) const +{ + if(row < rowCount()) + { + const Object& object = *m_list->m_items[row]; + + switch(column) + { + case columnId: + return object.getObjectId(); + + case columnName: + if(const auto* property = object.getProperty("name")) + return property->toString(); + break; + + default: + assert(false); + break; + } + } + + return ""; +} + +void ControllerListBaseTableModel::propertyChanged(BaseProperty& property, uint32_t row) +{ + if(property.name() == "id") + changed(row, columnId); + else if(property.name() == "name") + changed(row, columnName); +} diff --git a/server/src/hardware/input/monitor/xpressnetinputmonitor.hpp b/server/src/core/controllerlistbasetablemodel.hpp similarity index 55% rename from server/src/hardware/input/monitor/xpressnetinputmonitor.hpp rename to server/src/core/controllerlistbasetablemodel.hpp index fbf8e773..ec20f191 100644 --- a/server/src/hardware/input/monitor/xpressnetinputmonitor.hpp +++ b/server/src/core/controllerlistbasetablemodel.hpp @@ -1,5 +1,5 @@ /** - * server/src/hardware/input/monitor/xpressnetinputmonitor.hpp + * server/src/core/controllerlistbasetablemodel.hpp * * This file is part of the traintastic source code. * @@ -20,29 +20,30 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef TRAINTASTIC_SERVER_HARDWARE_INPUT_MONITOR_XPRESSNETINPUTMONITOR_HPP -#define TRAINTASTIC_SERVER_HARDWARE_INPUT_MONITOR_XPRESSNETINPUTMONITOR_HPP +#ifndef TRAINTASTIC_SERVER_CORE_CONTROLLERLISTBASETABLEMODEL_HPP +#define TRAINTASTIC_SERVER_CORE_CONTROLLERLISTBASETABLEMODEL_HPP -#include "inputmonitor.hpp" +#include "tablemodel.hpp" -namespace XpressNet { - class XpressNet; -} +class ControllerListBase; -class XpressNetInputMonitor final : public InputMonitor +class ControllerListBaseTableModel final : public TableModel { - protected: - std::shared_ptr m_xpressnet; + CLASS_ID("table_model.controller_list") + + friend class ControllerListBase; + + private: + std::shared_ptr m_list; + + void propertyChanged(BaseProperty& property, uint32_t row); public: - CLASS_ID("input_monitor.xpressnet") + static bool isListedProperty(const std::string& name); - XpressNetInputMonitor(std::shared_ptr xpressnet); - ~XpressNetInputMonitor() final; + ControllerListBaseTableModel(ControllerListBase& list); - std::string getObjectId() const final { return ""; } - - std::vector getInputInfo() const final; + std::string getText(uint32_t column, uint32_t row) const final; }; #endif diff --git a/server/src/core/objectlisttablemodel.hpp b/server/src/core/objectlisttablemodel.hpp index d48f077b..5ea8b952 100644 --- a/server/src/core/objectlisttablemodel.hpp +++ b/server/src/core/objectlisttablemodel.hpp @@ -35,6 +35,8 @@ class ObjectListTableModel : public TableModel std::shared_ptr> m_list; protected: + static constexpr uint32_t invalidColumn = std::numeric_limits::max(); + const T& getItem(uint32_t row) const { return *m_list->m_items[row]; } //T& getItem(uint32_t row) { return *m_list.m_items[row]; } virtual void propertyChanged(BaseProperty& property, uint32_t row) = 0; diff --git a/server/src/core/session.cpp b/server/src/core/session.cpp index fed962be..5037f9d0 100644 --- a/server/src/core/session.cpp +++ b/server/src/core/session.cpp @@ -49,7 +49,6 @@ #include "settings.hpp" -#include "../hardware/commandstation/commandstationlist.hpp" #include "../hardware/decoder/decoderlist.hpp" #include @@ -563,13 +562,13 @@ void Session::writeObject(Message& message, const ObjectPtr& object) if(auto* inputMonitor = dynamic_cast(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); + m_objectSignals.emplace(handle, inputMonitor->inputIdChanged.connect(std::bind(&Session::inputMonitorInputIdChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3))); + m_objectSignals.emplace(handle, inputMonitor->inputValueChanged.connect(std::bind(&Session::inputMonitorInputValueChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3))); } else if(auto* outputKeyboard = dynamic_cast(object.get())) { - outputKeyboard->outputIdChanged = std::bind(&Session::outputKeyboardOutputIdChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3); - outputKeyboard->outputValueChanged = std::bind(&Session::outputKeyboardOutputValueChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3); + m_objectSignals.emplace(handle, outputKeyboard->outputIdChanged.connect(std::bind(&Session::outputKeyboardOutputIdChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3))); + m_objectSignals.emplace(handle, outputKeyboard->outputValueChanged.connect(std::bind(&Session::outputKeyboardOutputValueChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3))); } else if(auto* board = dynamic_cast(object.get())) { diff --git a/server/src/hardware/output/outputs.cpp b/server/src/enum/interfacestatus.hpp similarity index 64% rename from server/src/hardware/output/outputs.cpp rename to server/src/enum/interfacestatus.hpp index 152d6dad..abe04307 100644 --- a/server/src/hardware/output/outputs.cpp +++ b/server/src/enum/interfacestatus.hpp @@ -1,9 +1,9 @@ /** - * server/src/hardware/output/outputs.cpp + * server/src/enum/interfacestatus.hpp * * This file is part of the traintastic source code. * - * Copyright (C) 2019-2020 Reinder Feenstra + * Copyright (C) 2021 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -20,12 +20,16 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "outputs.hpp" +#ifndef TRAINTASTIC_SERVER_ENUM_INTERFACESTATUS_HPP +#define TRAINTASTIC_SERVER_ENUM_INTERFACESTATUS_HPP -std::shared_ptr Outputs::create(const std::weak_ptr& world, std::string_view classId, std::string_view id) -{ - if(classId == LocoNetOutput::classId) - return LocoNetOutput::create(world, id); - else - return std::shared_ptr(); -} +#include + +inline constexpr std::array interfaceStatusValues{{ + InterfaceStatus::Offline, + InterfaceStatus::Initializing, + InterfaceStatus::Online, + InterfaceStatus::Error, +}}; + +#endif diff --git a/server/src/hardware/decoder/decoder.cpp b/server/src/hardware/decoder/decoder.cpp index 933ca1f3..78ef50c5 100644 --- a/server/src/hardware/decoder/decoder.cpp +++ b/server/src/hardware/decoder/decoder.cpp @@ -26,41 +26,33 @@ #include "decoderchangeflags.hpp" #include "decoderfunction.hpp" #include "decoderfunctions.hpp" +#include "../protocol/dcc/dcc.hpp" #include "../../world/world.hpp" -#include "../commandstation/commandstation.hpp" #include "../../core/attributes.hpp" +#include "../../log/log.hpp" #include "../../utils/displayname.hpp" #include "../../utils/almostzero.hpp" -//constexpr uint16_t addressDCCMin = 1; -constexpr uint16_t addressDCCShortMax = 127; - const std::shared_ptr Decoder::null; Decoder::Decoder(const std::weak_ptr& world, std::string_view _id) : IdObject(world, _id), name{this, "name", "", PropertyFlags::ReadWrite | PropertyFlags::Store}, - commandStation{this, "command_station", nullptr, PropertyFlags::ReadWrite | PropertyFlags::Store, - [this](const std::shared_ptr& value) + interface{this, "interface", nullptr, PropertyFlags::ReadWrite | PropertyFlags::Store, nullptr, + [this](const std::shared_ptr& newValue) { - std::shared_ptr decoder = std::dynamic_pointer_cast(shared_from_this()); - assert(decoder); - - //if(value) - // TODO: check compatible?? - - if(commandStation) - commandStation->decoders->removeObject(decoder); - - if(value) - value->decoders->addObject(decoder); - - return true; + if(!newValue || newValue->addDecoder(*this)) + { + if(interface.value()) + interface->removeDecoder(*this); + return true; + } + return false; }}, protocol{this, "protocol", DecoderProtocol::Auto, PropertyFlags::ReadWrite | PropertyFlags::Store, [this](const DecoderProtocol& value) { - if(value == DecoderProtocol::DCC && address > addressDCCShortMax) + if(value == DecoderProtocol::DCC && DCC::isLongAddress(address)) longAddress = true; updateEditable(); }}, @@ -69,7 +61,7 @@ Decoder::Decoder(const std::weak_ptr& world, std::string_view _id) : { if(protocol == DecoderProtocol::DCC) { - if(value > addressDCCShortMax) + if(DCC::isLongAddress(value)) longAddress = true; updateEditable(); } @@ -103,20 +95,19 @@ Decoder::Decoder(const std::weak_ptr& world, std::string_view _id) : functions.setValueInternal(std::make_shared(*this, functions.name())); auto w = world.lock(); + assert(w); m_worldMute = contains(w->state.value(), WorldState::Mute); m_worldNoSmoke = contains(w->state.value(), WorldState::NoSmoke); -// const bool editable = w && contains(w->state.value(), WorldState::Edit) && speedStep == 0; - Attributes::addDisplayName(name, DisplayName::Object::name); Attributes::addEnabled(name, false); m_interfaceItems.add(name); - Attributes::addDisplayName(commandStation, DisplayName::Hardware::commandStation); - Attributes::addEnabled(commandStation, false); - Attributes::addObjectList(commandStation, w->commandStations); - m_interfaceItems.add(commandStation); + Attributes::addDisplayName(interface, DisplayName::Hardware::interface); + Attributes::addEnabled(interface, false); + Attributes::addObjectList(interface, w->decoderControllers); + m_interfaceItems.add(interface); Attributes::addEnabled(protocol, false); Attributes::addValues(protocol, DecoderProtocolValues); @@ -156,6 +147,20 @@ void Decoder::addToWorld() world->decoders->addObject(shared_ptr()); } +void Decoder::loaded() +{ + IdObject::loaded(); + if(interface) + { + if(!interface->addDecoder(*this)) + { + if(auto object = std::dynamic_pointer_cast(interface.value())) + Log::log(*this, LogMessage::C2001_ADDRESS_ALREADY_USED_AT_X, *object); + interface.setValueInternal(nullptr); + } + } +} + bool Decoder::hasFunction(uint32_t number) const { for(auto& f : *functions) @@ -220,8 +225,8 @@ void Decoder::setFunctionValue(uint32_t number, bool value) void Decoder::destroying() { - if(commandStation.value()) - commandStation = nullptr; + if(interface.value()) + interface = nullptr; if(auto world = m_world.lock()) world->decoders->removeObject(shared_ptr()); IdObject::destroying(); @@ -273,15 +278,15 @@ void Decoder::updateEditable(bool editable) { const bool stopped = editable && almostZero(throttle.value()); Attributes::setEnabled(name, editable); - Attributes::setEnabled(commandStation, stopped); + Attributes::setEnabled(interface, stopped); Attributes::setEnabled(protocol, stopped); Attributes::setEnabled(address, stopped); - Attributes::setEnabled(longAddress, stopped && protocol == DecoderProtocol::DCC && address < addressDCCShortMax); + Attributes::setEnabled(longAddress, stopped && protocol == DecoderProtocol::DCC && !DCC::isLongAddress(address)); Attributes::setEnabled(speedSteps, stopped); } void Decoder::changed(DecoderChangeFlags changes, uint32_t functionNumber) { - if(commandStation) - commandStation->decoderChanged(*this, changes, functionNumber); + if(interface) + interface->decoderChanged(*this, changes, functionNumber); } diff --git a/server/src/hardware/decoder/decoder.hpp b/server/src/hardware/decoder/decoder.hpp index 29dc9964..7c9aef47 100644 --- a/server/src/hardware/decoder/decoder.hpp +++ b/server/src/hardware/decoder/decoder.hpp @@ -26,9 +26,9 @@ #include #include "../../core/idobject.hpp" #include "../../core/objectproperty.hpp" -#include "../../core/commandstationproperty.hpp" #include "../../enum/decoderprotocol.hpp" #include "../../enum/direction.hpp" +#include "decodercontroller.hpp" #include "decoderfunctions.hpp" enum class DecoderChangeFlags; @@ -43,6 +43,7 @@ class Decoder : public IdObject bool m_worldNoSmoke; protected: + void loaded() final; void destroying() override; void worldEvent(WorldState state, WorldEvent event) final; void updateEditable(); @@ -74,7 +75,7 @@ class Decoder : public IdObject static const std::shared_ptr null; Property name; - CommandStationProperty commandStation; + ObjectProperty interface; Property protocol; Property address; Property longAddress; diff --git a/server/src/hardware/decoder/decodercontroller.cpp b/server/src/hardware/decoder/decodercontroller.cpp new file mode 100644 index 00000000..5c9c0dd1 --- /dev/null +++ b/server/src/hardware/decoder/decodercontroller.cpp @@ -0,0 +1,83 @@ +/** + * server/src/hardware/decoder/decodercontroller.cpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021 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 "decodercontroller.hpp" +#include "decoder.hpp" + +bool DecoderController::addDecoder(Decoder& decoder) +{ + if(decoder.protocol != DecoderProtocol::Auto && findDecoder(decoder) != m_decoders.end()) + return false; + else if(findDecoder(DecoderProtocol::Auto, decoder.address) != m_decoders.end()) + return false; + + m_decoders.emplace_back(decoder.shared_ptr()); + return true; +} + +bool DecoderController::removeDecoder(Decoder& decoder) +{ + auto it = findDecoder(decoder); + if(it != m_decoders.end()) + { + m_decoders.erase(it); + return true; + } + else + return false; +} + +const std::shared_ptr& DecoderController::getDecoder(DecoderProtocol protocol, uint16_t address, bool dccLongAddress, bool fallbackToProtocolAuto) +{ + auto it = findDecoder(protocol, address, dccLongAddress); + if(it != m_decoders.end()) + return *it; + else if(fallbackToProtocolAuto && protocol != DecoderProtocol::Auto && (it = findDecoder(DecoderProtocol::Auto, address)) != m_decoders.end()) + return *it; + else + return Decoder::null; +} + +DecoderController::DecoderVector::iterator DecoderController::findDecoder(const Decoder& decoder) +{ + return findDecoder(decoder.protocol, decoder.address, decoder.longAddress); +} + +DecoderController::DecoderVector::iterator DecoderController::findDecoder(DecoderProtocol protocol, uint16_t address, bool dccLongAddress) +{ + if(protocol == DecoderProtocol::DCC) + { + return std::find_if(m_decoders.begin(), m_decoders.end(), + [address, dccLongAddress](const auto& it) + { + return it->protocol == DecoderProtocol::DCC && it->address == address && it->longAddress == dccLongAddress; + }); + } + else + { + return std::find_if(m_decoders.begin(), m_decoders.end(), + [protocol, address](const auto& it) + { + return it->protocol == protocol && it->address == address; + }); + } +} diff --git a/server/src/hardware/decoder/decodercontroller.hpp b/server/src/hardware/decoder/decodercontroller.hpp new file mode 100644 index 00000000..2a250f08 --- /dev/null +++ b/server/src/hardware/decoder/decodercontroller.hpp @@ -0,0 +1,54 @@ +/** + * server/src/hardware/decoder/decodercontroller.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021 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_DECODER_DECODERCONTROLLER_HPP +#define TRAINTASTIC_SERVER_HARDWARE_DECODER_DECODERCONTROLLER_HPP + +#include +#include +#include + +class Decoder; +enum class DecoderChangeFlags; +enum class DecoderProtocol : uint8_t; + +class DecoderController +{ + public: + using DecoderVector = std::vector>; + + protected: + DecoderVector m_decoders; + + DecoderVector::iterator findDecoder(const Decoder& decoder); + DecoderVector::iterator findDecoder(DecoderProtocol protocol, uint16_t address, bool dccLongAddress = false); + + public: + [[nodiscard]] virtual bool addDecoder(Decoder& decoder); + [[nodiscard]] virtual bool removeDecoder(Decoder& decoder); + + const std::shared_ptr& getDecoder(DecoderProtocol protocol, uint16_t address, bool dccLongAddress = false, bool fallbackToProtocolAuto = false); + + virtual void decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber) = 0; +}; + +#endif diff --git a/server/src/hardware/decoder/decoderlist.cpp b/server/src/hardware/decoder/decoderlist.cpp index 12c2177f..48547672 100644 --- a/server/src/hardware/decoder/decoderlist.cpp +++ b/server/src/hardware/decoder/decoderlist.cpp @@ -22,7 +22,6 @@ #include "decoderlist.hpp" #include "decoderlisttablemodel.hpp" -#include "../commandstation/commandstation.hpp" #include "../../world/getworld.hpp" #include "../../core/attributes.hpp" #include "../../utils/displayname.hpp" @@ -35,12 +34,13 @@ DecoderList::DecoderList(Object& _parent, const std::string& parentPropertyName) auto world = getWorld(&this->parent()); if(!world) return std::shared_ptr(); + auto decoder = Decoder::create(world, world->getUniqueId("decoder")); - //addObject(decoder); - if(auto* cs = dynamic_cast(&this->parent())) - decoder->commandStation = cs->shared_ptr(); - //else if(world->commandStations->length() == 1) - // decoder->commandStation = cs->shared_ptr(); + if(const auto controller = std::dynamic_pointer_cast(parent().shared_from_this())) + { + // todo: select free address? + decoder->interface = controller; + } return decoder; }} , remove{*this, "remove", diff --git a/server/src/hardware/input/input.cpp b/server/src/hardware/input/input.cpp index a9a84c4c..abcea739 100644 --- a/server/src/hardware/input/input.cpp +++ b/server/src/hardware/input/input.cpp @@ -24,12 +24,31 @@ #include "../../world/world.hpp" #include "list/inputlisttablemodel.hpp" #include "../../core/attributes.hpp" +#include "../../log/log.hpp" #include "../../utils/displayname.hpp" -Input::Input(const std::weak_ptr world, std::string_view _id) : - IdObject(world, _id), - name{this, "name", id, PropertyFlags::ReadWrite | PropertyFlags::Store}, - value{this, "value", TriState::Undefined, PropertyFlags::ReadOnly | PropertyFlags::StoreState} +Input::Input(const std::weak_ptr world, std::string_view _id) + : IdObject(world, _id) + , name{this, "name", id, PropertyFlags::ReadWrite | PropertyFlags::Store} + , interface{this, "interface", nullptr, PropertyFlags::ReadWrite | PropertyFlags::Store, nullptr, + [this](const std::shared_ptr& newValue) + { + if(!newValue || newValue->addInput(*this)) + { + if(interface.value()) + interface->removeInput(*this); + return true; + } + return false; + }} + , address{this, "address", 1, PropertyFlags::ReadWrite | PropertyFlags::Store, nullptr, + [this](const uint32_t& newValue) + { + if(interface) + return interface->changeInputAddress(*this, newValue); + return true; + }} + , value{this, "value", TriState::Undefined, PropertyFlags::ReadOnly | PropertyFlags::StoreState} , consumers{*this, "consumers", {}, PropertyFlags::ReadOnly | PropertyFlags::NoStore} { auto w = world.lock(); @@ -38,9 +57,21 @@ Input::Input(const std::weak_ptr world, std::string_view _id) : Attributes::addDisplayName(name, DisplayName::Object::name); Attributes::addEnabled(name, editable); m_interfaceItems.add(name); + + Attributes::addDisplayName(interface, DisplayName::Hardware::interface); + Attributes::addEnabled(interface, editable); + Attributes::addObjectList(interface, w->inputControllers); + m_interfaceItems.add(interface); + + Attributes::addDisplayName(address, DisplayName::Hardware::address); + Attributes::addEnabled(address, editable); + Attributes::addMinMax(address, std::numeric_limits::min(), std::numeric_limits::max()); + m_interfaceItems.add(address); + Attributes::addObjectEditor(value, false); Attributes::addValues(value, TriStateValues); m_interfaceItems.add(value); + Attributes::addObjectEditor(consumers, false); //! \todo add client support first m_interfaceItems.add(consumers); } @@ -53,8 +84,24 @@ void Input::addToWorld() world->inputs->addObject(shared_ptr()); } +void Input::loaded() +{ + IdObject::loaded(); + if(interface) + { + if(!interface->addInput(*this)) + { + if(auto object = std::dynamic_pointer_cast(interface.value())) + Log::log(*this, LogMessage::C2001_ADDRESS_ALREADY_USED_AT_X, *object); + interface.setValueInternal(nullptr); + } + } +} + void Input::destroying() { + if(interface.value()) + interface = nullptr; if(auto world = m_world.lock()) world->inputs->removeObject(shared_ptr()); IdObject::destroying(); @@ -67,6 +114,8 @@ void Input::worldEvent(WorldState state, WorldEvent event) const bool editable = contains(state, WorldState::Edit); Attributes::setEnabled(name, editable); + Attributes::setEnabled(interface, editable); + Attributes::setEnabled(address, editable); } void Input::updateValue(TriState _value) diff --git a/server/src/hardware/input/input.hpp b/server/src/hardware/input/input.hpp index ac508de9..50300021 100644 --- a/server/src/hardware/input/input.hpp +++ b/server/src/hardware/input/input.hpp @@ -27,13 +27,19 @@ #include "../../core/objectproperty.hpp" #include "../../core/objectvectorproperty.hpp" #include "../../enum/tristate.hpp" +#include "inputcontroller.hpp" class Input : public IdObject { + CLASS_ID("input") DEFAULT_ID("input") + CREATE(Input) + + friend class InputController; protected: void addToWorld() override; + void loaded() override; void destroying() override; void worldEvent(WorldState state, WorldEvent event) override; virtual void valueChanged(TriState /*_value*/) {} @@ -41,7 +47,11 @@ class Input : public IdObject void updateValue(TriState _value); public: + static constexpr uint32_t invalidAddress = std::numeric_limits::max(); + Property name; + ObjectProperty interface; + Property address; Property value; ObjectVectorProperty consumers; diff --git a/server/src/hardware/input/inputcontroller.cpp b/server/src/hardware/input/inputcontroller.cpp new file mode 100644 index 00000000..d10045c6 --- /dev/null +++ b/server/src/hardware/input/inputcontroller.cpp @@ -0,0 +1,103 @@ +/** + * server/src/hardware/input/inputcontroller.cpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021 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 "inputcontroller.hpp" +#include "input.hpp" +#include "monitor/inputmonitor.hpp" +#include "../../utils/inrange.hpp" + +bool InputController::isInputAddressAvailable(uint32_t address) const +{ + return + inRange(address, inputAddressMinMax()) && + m_inputs.find(address) == m_inputs.end(); +} + +uint32_t InputController::getUnusedInputAddress() const +{ + const auto end = m_inputs.cend(); + const auto range = inputAddressMinMax(); + for(uint32_t address = range.first; address < range.second; address++) + if(m_inputs.find(address) == end) + return address; + return Input::invalidAddress; +} + +bool InputController::changeInputAddress(Input& input, uint32_t newAddress) +{ + assert(input.interface.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)); + input.value.setValueInternal(TriState::Undefined); + + return true; +} + +bool InputController::addInput(Input& input) +{ + if(isInputAddressAvailable(input.address)) + { + m_inputs.insert({input.address, input.shared_ptr()}); + input.value.setValueInternal(TriState::Undefined); + return true; + } + else + return false; +} + +bool InputController::removeInput(Input& input) +{ + assert(input.interface.value().get() == this); + auto it = m_inputs.find(input.address); + if(it != m_inputs.end() && it->second.get() == &input) + { + m_inputs.erase(it); + input.value.setValueInternal(TriState::Undefined); + return true; + } + else + return false; +} + +void InputController::updateInputValue(uint32_t address, TriState value) +{ + if(auto it = m_inputs.find(address); it != m_inputs.end()) + it->second->updateValue(value); + if(auto monitor = m_inputMonitor.lock()) + monitor->inputValueChanged(*monitor, address, value); +} + +std::shared_ptr InputController::inputMonitor() +{ + auto monitor = m_inputMonitor.lock(); + if(!monitor) + { + monitor = std::make_shared(*this); + m_inputMonitor = monitor; + } + return monitor; +} diff --git a/server/src/hardware/input/inputcontroller.hpp b/server/src/hardware/input/inputcontroller.hpp new file mode 100644 index 00000000..51bde3fe --- /dev/null +++ b/server/src/hardware/input/inputcontroller.hpp @@ -0,0 +1,101 @@ +/** + * server/src/hardware/input/inputcontroller.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021 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_INPUTCONTROLLER_HPP +#define TRAINTASTIC_SERVER_HARDWARE_INPUT_INPUTCONTROLLER_HPP + +#include +#include +#include +#include "../../enum/tristate.hpp" + +class Input; +class InputMonitor; + +class InputController +{ + public: + using InputMap = std::unordered_map>; + + protected: + InputMap m_inputs; + std::weak_ptr m_inputMonitor; + + public: + /** + * + */ + inline const InputMap& inputs() const { return m_inputs; } + + /** + * + */ + virtual std::pair inputAddressMinMax() const = 0; + + /** + * + */ + [[nodiscard]] virtual bool isInputAddressAvailable(uint32_t address) const; + + /** + * @brief Get the next unused input address + * + * @return An usused address or #Input::invalidAddress if no unused address is available. + */ + uint32_t getUnusedInputAddress() const; + + /** + * + * @return \c true if changed, \c false otherwise. + */ + [[nodiscard]] virtual bool changeInputAddress(Input& input, uint32_t newAddress); + + /** + * + * @return \c true if added, \c false otherwise. + */ + [[nodiscard]] virtual bool addInput(Input& input); + + /** + * + * @return \c true if removed, \c false otherwise. + */ + [[nodiscard]] virtual bool removeInput(Input& input); + + /** + * @brief Update the input value + * + * This function should be called by the hardware layer whenever the input value changes. + * + * @param[in] address Input address + * @param[in] value New input value + */ + void updateInputValue(uint32_t address, TriState value); + + /** + * + * + */ + std::shared_ptr inputMonitor(); +}; + +#endif diff --git a/server/src/hardware/input/list/inputlist.cpp b/server/src/hardware/input/list/inputlist.cpp index 3cd290df..a5c9a861 100644 --- a/server/src/hardware/input/list/inputlist.cpp +++ b/server/src/hardware/input/list/inputlist.cpp @@ -22,59 +22,64 @@ #include "inputlist.hpp" #include "inputlisttablemodel.hpp" -#include "../inputs.hpp" +#include "../inputcontroller.hpp" #include "../../../world/getworld.hpp" #include "../../../core/attributes.hpp" #include "../../../utils/displayname.hpp" -InputList::InputList(Object& _parent, const std::string& parentPropertyName) : - ObjectList(_parent, parentPropertyName), - add{*this, "add", - [this](std::string_view inputClassId) - { - auto world = getWorld(&this->parent()); - if(!world) - return std::shared_ptr(); - auto input = Inputs::create(world, inputClassId, world->getUniqueId("input")); - if(auto locoNetInput = std::dynamic_pointer_cast(input); locoNetInput && world->loconets->length == 1) +InputList::InputList(Object& _parent, const std::string& parentPropertyName) + : ObjectList(_parent, parentPropertyName) + , m_parentIsInputController{dynamic_cast(&_parent)} + , add{*this, "add", + [this]() { - auto& loconet = world->loconets->operator[](0); - if(uint16_t address = loconet->getUnusedInputAddress(); address != LocoNetInput::addressInvalid) + auto world = getWorld(&parent()); + if(!world) + return std::shared_ptr(); + + auto input = Input::create(world, world->getUniqueId(Input::defaultId)); + if(const auto controller = std::dynamic_pointer_cast(parent().shared_from_this())) { - locoNetInput->address = address; - locoNetInput->loconet = loconet; + if(const uint32_t address = controller->getUnusedInputAddress(); address != Input::invalidAddress) + { + input->address = address; + input->interface = controller; + } } - } - else if(auto xpressNetInput = std::dynamic_pointer_cast(input); xpressNetInput && world->xpressnets->length == 1) - { - auto& xpressnet = world->xpressnets->operator[](0); - if(uint16_t address = xpressnet->getUnusedInputAddress(); address != XpressNetInput::addressInvalid) - { - xpressNetInput->address = address; - xpressNetInput->xpressnet = xpressnet; - } - } - return input; - }} + return input; + }} , remove{*this, "remove", - [this](const std::shared_ptr& input) - { - if(containsObject(input)) - input->destroy(); - assert(!containsObject(input)); - }} + [this](const std::shared_ptr& input) + { + if(containsObject(input)) + input->destroy(); + assert(!containsObject(input)); + }} + , inputMonitor{*this, "input_monitor", + [this]() + { + if(const auto controller = std::dynamic_pointer_cast(parent().shared_from_this())) + return controller->inputMonitor(); + else + return std::shared_ptr(); + }} { auto w = getWorld(&_parent); const bool editable = w && contains(w->state.value(), WorldState::Edit); Attributes::addDisplayName(add, DisplayName::List::add); Attributes::addEnabled(add, editable); - Attributes::addClassList(add, Inputs::classList); m_interfaceItems.add(add); Attributes::addDisplayName(remove, DisplayName::List::remove); Attributes::addEnabled(remove, editable); m_interfaceItems.add(remove); + + if(m_parentIsInputController) + { + Attributes::addDisplayName(inputMonitor, DisplayName::Hardware::inputMonitor); + m_interfaceItems.add(inputMonitor); + } } TableModelPtr InputList::getModel() diff --git a/server/src/hardware/input/list/inputlist.hpp b/server/src/hardware/input/list/inputlist.hpp index 53bd3c43..6658566b 100644 --- a/server/src/hardware/input/list/inputlist.hpp +++ b/server/src/hardware/input/list/inputlist.hpp @@ -26,21 +26,28 @@ #include "../../../core/objectlist.hpp" #include "../../../core/method.hpp" #include "../input.hpp" +#include "../monitor/inputmonitor.hpp" class InputList : public ObjectList { + CLASS_ID("list.input") + + private: + const bool m_parentIsInputController; + protected: void worldEvent(WorldState state, WorldEvent event) final; bool isListedProperty(const std::string& name) final; public: - CLASS_ID("input_list") - - Method(std::string_view)> add; + Method()> add; Method&)> remove; + Method()> inputMonitor; InputList(Object& _parent, const std::string& parentPropertyName); + inline bool parentIsInputController() const { return m_parentIsInputController; } + TableModelPtr getModel() final; }; diff --git a/server/src/hardware/input/list/inputlisttablemodel.cpp b/server/src/hardware/input/list/inputlisttablemodel.cpp index 07eb47a7..22d0ac7c 100644 --- a/server/src/hardware/input/list/inputlisttablemodel.cpp +++ b/server/src/hardware/input/list/inputlisttablemodel.cpp @@ -22,30 +22,39 @@ #include "inputlisttablemodel.hpp" #include "inputlist.hpp" -#include "../loconetinput.hpp" #include "../../../utils/displayname.hpp" -constexpr uint32_t columnId = 0; -constexpr uint32_t columnName = 1; -constexpr uint32_t columnBus = 2; -constexpr uint32_t columnAddress = 3; - bool InputListTableModel::isListedProperty(const std::string& name) { return name == "id" || - name == "name"; + name == "name" || + name == "interface" || + name == "address"; } -InputListTableModel::InputListTableModel(InputList& list) : - ObjectListTableModel(list) +InputListTableModel::InputListTableModel(InputList& list) + : ObjectListTableModel(list) + , m_columnInterface(list.parentIsInputController() ? invalidColumn : 2) + , m_columnAddress(list.parentIsInputController() ? 2 : 3) { - setColumnHeaders({ - DisplayName::Object::id, - DisplayName::Object::name, - "input_list:bus", - DisplayName::Hardware::address, - }); + if(list.parentIsInputController()) + { + setColumnHeaders({ + DisplayName::Object::id, + DisplayName::Object::name, + DisplayName::Hardware::address, + }); + } + else + { + setColumnHeaders({ + DisplayName::Object::id, + DisplayName::Object::name, + DisplayName::Hardware::interface, + DisplayName::Hardware::address, + }); + } } std::string InputListTableModel::getText(uint32_t column, uint32_t row) const @@ -53,32 +62,27 @@ std::string InputListTableModel::getText(uint32_t column, uint32_t row) const if(row < rowCount()) { const Input& input = getItem(row); - const LocoNetInput* inputLocoNet = dynamic_cast(&input); - switch(column) + if(column == columnId) + return input.id; + else if(column == columnName) + return input.name; + else if(column == m_columnInterface) { - case columnId: - return input.id; - - case columnName: - return input.name; - - case columnBus: // virtual method @ Input ?? - if(inputLocoNet && inputLocoNet->loconet) - return inputLocoNet->loconet->getObjectId(); + if(const auto& interface = std::dynamic_pointer_cast(input.interface.value())) + { + if(auto property = interface->getProperty("name"); property && !property->toString().empty()) + return property->toString(); + else + return interface->getObjectId(); + } else return ""; - - case columnAddress: // virtual method @ Input ?? - if(inputLocoNet) - return std::to_string(inputLocoNet->address); - else - return ""; - - default: - assert(false); - break; } + else if(column == m_columnAddress) + return std::to_string(input.address.value()); + else + assert(false); } return ""; @@ -90,4 +94,8 @@ void InputListTableModel::propertyChanged(BaseProperty& property, uint32_t row) changed(row, columnId); else if(property.name() == "name") changed(row, columnName); -} + else if(property.name() == "interface" && m_columnInterface != invalidColumn) + changed(row, m_columnInterface); + else if(property.name() == "address") + changed(row, m_columnAddress); + } diff --git a/server/src/hardware/input/list/inputlisttablemodel.hpp b/server/src/hardware/input/list/inputlisttablemodel.hpp index 8920559b..95822c50 100644 --- a/server/src/hardware/input/list/inputlisttablemodel.hpp +++ b/server/src/hardware/input/list/inputlisttablemodel.hpp @@ -32,6 +32,12 @@ class InputListTableModel : public ObjectListTableModel { friend class InputList; + private: + static constexpr uint32_t columnId = 0; + static constexpr uint32_t columnName = 1; + const uint32_t m_columnInterface; + const uint32_t m_columnAddress; + protected: void propertyChanged(BaseProperty& property, uint32_t row) final; diff --git a/server/src/hardware/input/loconetinput.cpp b/server/src/hardware/input/loconetinput.cpp deleted file mode 100644 index d84e0e71..00000000 --- a/server/src/hardware/input/loconetinput.cpp +++ /dev/null @@ -1,104 +0,0 @@ -/** - * server/src/hardware/input/loconetinput.cpp - * - * This file is part of the traintastic source code. - * - * Copyright (C) 2019-2021 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 "loconetinput.hpp" -#include "../../core/attributes.hpp" -#include "../../world/world.hpp" -#include "../../log/log.hpp" -#include "../../utils/displayname.hpp" - -LocoNetInput::LocoNetInput(const std::weak_ptr world, std::string_view _id) : - Input(world, _id), - loconet{this, "loconet", nullptr, PropertyFlags::ReadWrite | PropertyFlags::Store, - [this](const std::shared_ptr& newValue) - { - if(!newValue || newValue->addInput(*this)) - { - if(loconet.value()) - loconet->removeInput(*this); - return true; - } - return false; - }}, - address{this, "address", addressMin, PropertyFlags::ReadWrite | PropertyFlags::Store, nullptr, - [this](const uint16_t& newValue) - { - if(loconet) - return loconet->changeInputAddress(*this, newValue); - return true; - }} -{ - auto w = world.lock(); - const bool editable = w && contains(w->state.value(), WorldState::Edit); - - Attributes::addDisplayName(loconet, DisplayName::Hardware::loconet); - Attributes::addEnabled(loconet, editable); - Attributes::addObjectList(loconet, w->loconets); - m_interfaceItems.add(loconet); - - Attributes::addDisplayName(address, DisplayName::Hardware::address); - Attributes::addEnabled(address, editable); - Attributes::addMinMax(address, addressMin, addressMax); - m_interfaceItems.add(address); -} - -void LocoNetInput::loaded() -{ - Input::loaded(); - if(loconet) - { - if(!loconet->addInput(*this)) - { - Log::log(*this, LogMessage::C2001_ADDRESS_ALREADY_USED_AT_X, *loconet); - loconet.setValueInternal(nullptr); - } - } -} - -void LocoNetInput::destroying() -{ - if(loconet) - loconet = nullptr; - Input::destroying(); -} - -void LocoNetInput::worldEvent(WorldState state, WorldEvent event) -{ - Input::worldEvent(state, event); - - const bool editable = contains(state, WorldState::Edit); - - Attributes::setEnabled(loconet, editable); - Attributes::setEnabled(address, editable); -} - -void LocoNetInput::idChanged(const std::string& newId) -{ - if(loconet) - loconet->inputMonitorIdChanged(address, newId); -} - -void LocoNetInput::valueChanged(TriState _value) -{ - if(loconet) - loconet->inputMonitorValueChanged(address, _value); -} diff --git a/server/src/hardware/input/loconetinput.hpp b/server/src/hardware/input/loconetinput.hpp deleted file mode 100644 index ce11c03f..00000000 --- a/server/src/hardware/input/loconetinput.hpp +++ /dev/null @@ -1,57 +0,0 @@ -/** - * server/src/hardware/input/loconetinput.hpp - * - * This file is part of the traintastic source code. - * - * Copyright (C) 2019-2021 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_LOCONETINPUT_HPP -#define TRAINTASTIC_SERVER_HARDWARE_INPUT_LOCONETINPUT_HPP - -#include "input.hpp" -#include "../../core/objectproperty.hpp" -#include "../protocol/loconet/loconet.hpp" - -class LocoNetInput : public Input -{ - friend class LocoNet::LocoNet; - - protected: - void loaded() final; - void destroying() final; - void worldEvent(WorldState state, WorldEvent event) final; - void idChanged(const std::string& newId) final; - void valueChanged(TriState _value) final; - - inline void updateValue(TriState _value) { Input::updateValue(_value); } - - public: - CLASS_ID("input.loconet") - CREATE(LocoNetInput) - - static constexpr uint16_t addressInvalid = 0; - static constexpr uint16_t addressMin = 1; - static constexpr uint16_t addressMax = 4096; - - ObjectProperty loconet; - Property address; - - LocoNetInput(const std::weak_ptr world, std::string_view _id); -}; - -#endif diff --git a/server/src/hardware/input/monitor/loconetinputmonitor.cpp b/server/src/hardware/input/monitor/inputmonitor.cpp similarity index 53% rename from server/src/hardware/input/monitor/loconetinputmonitor.cpp rename to server/src/hardware/input/monitor/inputmonitor.cpp index 6096c531..d2ca2bd2 100644 --- a/server/src/hardware/input/monitor/loconetinputmonitor.cpp +++ b/server/src/hardware/input/monitor/inputmonitor.cpp @@ -1,5 +1,5 @@ /** - * server/src/hardware/input/monitor/loconetinputmonitor.cpp + * server/src/hardware/input/monitor/inputmonitor.cpp * * This file is part of the traintastic source code. * @@ -20,31 +20,29 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "loconetinputmonitor.hpp" -#include "../../protocol/loconet/loconet.hpp" -#include "../loconetinput.hpp" +#include "inputmonitor.hpp" +#include "../inputcontroller.hpp" +#include "../input.hpp" -LocoNetInputMonitor::LocoNetInputMonitor(std::shared_ptr loconet) : - InputMonitor(), - m_loconet{std::move(loconet)} +InputMonitor::InputMonitor(InputController& controller) + : Object() + , m_controller{controller} + , addressMin{this, "address_min", m_controller.inputAddressMinMax().first, PropertyFlags::ReadOnly | PropertyFlags::NoStore} + , addressMax{this, "address_max", m_controller.inputAddressMinMax().second, PropertyFlags::ReadOnly | PropertyFlags::NoStore} { - addressMin.setValueInternal(LocoNetInput::addressMin); - addressMax.setValueInternal(LocoNetInput::addressMax); - m_loconet->m_inputMonitors.emplace_back(this); } -LocoNetInputMonitor::~LocoNetInputMonitor() +std::string InputMonitor::getObjectId() const { - 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); + return ""; // todo } -std::vector LocoNetInputMonitor::getInputInfo() const +std::vector InputMonitor::getInputInfo() const { std::vector inputInfo; - for(auto it : m_loconet->m_inputs) + for(auto it : m_controller.inputs()) { - LocoNetInput& input = *(it.second); + const auto& input = *(it.second); InputInfo info(input.address, input.id, input.value); inputInfo.push_back(info); } diff --git a/server/src/hardware/input/monitor/inputmonitor.hpp b/server/src/hardware/input/monitor/inputmonitor.hpp index c0ce4abd..cf7ac92d 100644 --- a/server/src/hardware/input/monitor/inputmonitor.hpp +++ b/server/src/hardware/input/monitor/inputmonitor.hpp @@ -28,11 +28,18 @@ #include "../../../core/property.hpp" #include "../../../enum/tristate.hpp" +class InputController; + class InputMonitor : public Object { + CLASS_ID("input_monitor") + + private: + InputController& m_controller; + public: - std::function inputIdChanged; - std::function inputValueChanged; + boost::signals2::signal inputIdChanged; + boost::signals2::signal inputValueChanged; struct InputInfo { @@ -51,14 +58,11 @@ class InputMonitor : public Object Property addressMin; Property addressMax; - InputMonitor() : - Object(), - addressMin{this, "address_min", 0, PropertyFlags::ReadOnly | PropertyFlags::NoStore}, - addressMax{this, "address_max", 0, PropertyFlags::ReadOnly | PropertyFlags::NoStore} - { - } + InputMonitor(InputController& controller); - virtual std::vector getInputInfo() const = 0; + std::string getObjectId() const final; + + virtual std::vector getInputInfo() const; }; #endif diff --git a/server/src/hardware/input/monitor/xpressnetinputmonitor.cpp b/server/src/hardware/input/monitor/xpressnetinputmonitor.cpp deleted file mode 100644 index 0572a0a0..00000000 --- a/server/src/hardware/input/monitor/xpressnetinputmonitor.cpp +++ /dev/null @@ -1,52 +0,0 @@ -/** - * server/src/hardware/input/monitor/xpressnetinputmonitor.cpp - * - * This file is part of the traintastic source code. - * - * Copyright (C) 2021 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 "xpressnetinputmonitor.hpp" -#include "../../protocol/xpressnet/xpressnet.hpp" -#include "../xpressnetinput.hpp" - -XpressNetInputMonitor::XpressNetInputMonitor(std::shared_ptr xpressnet) : - InputMonitor(), - m_xpressnet{std::move(xpressnet)} -{ - addressMin.setValueInternal(XpressNetInput::addressMin); - addressMax.setValueInternal(XpressNetInput::addressMax); - m_xpressnet->m_inputMonitors.emplace_back(this); -} - -XpressNetInputMonitor::~XpressNetInputMonitor() -{ - if(auto it = std::find(m_xpressnet->m_inputMonitors.begin(), m_xpressnet->m_inputMonitors.end(), this); it != m_xpressnet->m_inputMonitors.end()) - m_xpressnet->m_inputMonitors.erase(it); -} - -std::vector XpressNetInputMonitor::getInputInfo() const -{ - std::vector inputInfo; - for(auto it : m_xpressnet->m_inputs) - { - XpressNetInput& input = *(it.second); - InputInfo info(input.address, input.id, input.value); - inputInfo.push_back(info); - } - return inputInfo; -} diff --git a/server/src/hardware/input/xpressnetinput.cpp b/server/src/hardware/input/xpressnetinput.cpp deleted file mode 100644 index b72565a1..00000000 --- a/server/src/hardware/input/xpressnetinput.cpp +++ /dev/null @@ -1,104 +0,0 @@ -/** - * server/src/hardware/input/xpressnetinput.cpp - * - * This file is part of the traintastic source code. - * - * Copyright (C) 2021 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 "xpressnetinput.hpp" -#include "../../core/attributes.hpp" -#include "../../world/world.hpp" -#include "../../log/log.hpp" -#include "../../utils/displayname.hpp" - -XpressNetInput::XpressNetInput(const std::weak_ptr world, std::string_view _id) : - Input(world, _id), - xpressnet{this, "xpressnet", nullptr, PropertyFlags::ReadWrite | PropertyFlags::Store, - [this](const std::shared_ptr& newValue) - { - if(!newValue || newValue->addInput(*this)) - { - if(xpressnet.value()) - xpressnet->removeInput(*this); - return true; - } - return false; - }}, - address{this, "address", addressMin, PropertyFlags::ReadWrite | PropertyFlags::Store, nullptr, - [this](const uint16_t& newValue) - { - if(xpressnet) - return xpressnet->changeInputAddress(*this, newValue); - return true; - }} -{ - auto w = world.lock(); - const bool editable = w && contains(w->state.value(), WorldState::Edit); - - Attributes::addDisplayName(xpressnet, DisplayName::Hardware::xpressnet); - Attributes::addEnabled(xpressnet, editable); - Attributes::addObjectList(xpressnet, w->xpressnets); - m_interfaceItems.add(xpressnet); - - Attributes::addDisplayName(address, DisplayName::Hardware::address); - Attributes::addEnabled(address, editable); - Attributes::addMinMax(address, addressMin, addressMax); - m_interfaceItems.add(address); -} - -void XpressNetInput::loaded() -{ - Input::loaded(); - if(xpressnet) - { - if(!xpressnet->addInput(*this)) - { - Log::log(*this, LogMessage::C2001_ADDRESS_ALREADY_USED_AT_X, *xpressnet); - xpressnet.setValueInternal(nullptr); - } - } -} - -void XpressNetInput::destroying() -{ - if(xpressnet) - xpressnet = nullptr; - Input::destroying(); -} - -void XpressNetInput::worldEvent(WorldState state, WorldEvent event) -{ - Input::worldEvent(state, event); - - const bool editable = contains(state, WorldState::Edit); - - Attributes::setEnabled(xpressnet, editable); - Attributes::setEnabled(address, editable); -} - -void XpressNetInput::idChanged(const std::string& newId) -{ - if(xpressnet) - xpressnet->inputMonitorIdChanged(address, newId); -} - -void XpressNetInput::valueChanged(TriState _value) -{ - if(xpressnet) - xpressnet->inputMonitorValueChanged(address, _value); -} diff --git a/server/src/hardware/input/xpressnetinput.hpp b/server/src/hardware/input/xpressnetinput.hpp deleted file mode 100644 index 5bf27d42..00000000 --- a/server/src/hardware/input/xpressnetinput.hpp +++ /dev/null @@ -1,57 +0,0 @@ -/** - * server/src/hardware/input/xpressnetinput.hpp - * - * This file is part of the traintastic source code. - * - * Copyright (C) 2021 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_XPRESSNETINPUT_HPP -#define TRAINTASTIC_SERVER_HARDWARE_INPUT_XPRESSNETINPUT_HPP - -#include "input.hpp" -#include "../../core/objectproperty.hpp" -#include "../protocol/xpressnet/xpressnet.hpp" - -class XpressNetInput : public Input -{ - friend class XpressNet::XpressNet; - - protected: - void loaded() final; - void destroying() final; - void worldEvent(WorldState state, WorldEvent event) final; - void idChanged(const std::string& newId) final; - void valueChanged(TriState _value) final; - - inline void updateValue(TriState _value) { Input::updateValue(_value); } - - public: - CLASS_ID("input.xpressnet") - CREATE(XpressNetInput) - - static constexpr uint16_t addressInvalid = 0; - static constexpr uint16_t addressMin = 1; - static constexpr uint16_t addressMax = 2048; - - ObjectProperty xpressnet; - Property address; - - XpressNetInput(const std::weak_ptr world, std::string_view _id); -}; - -#endif diff --git a/server/src/hardware/interface/interface.cpp b/server/src/hardware/interface/interface.cpp new file mode 100644 index 00000000..3e1583af --- /dev/null +++ b/server/src/hardware/interface/interface.cpp @@ -0,0 +1,88 @@ +/** + * server/src/hardware/interface/interface.cpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021 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 "interface.hpp" +#include "interfacelisttablemodel.hpp" +#include "../../core/attributes.hpp" +#include "../../utils/displayname.hpp" +#include "../../world/world.hpp" + +Interface::Interface(const std::weak_ptr& world, std::string_view _id) + : IdObject(world, _id) + , name{this, "name", "", PropertyFlags::ReadWrite | PropertyFlags::Store} + , online{this, "online", false, PropertyFlags::ReadWrite | PropertyFlags::NoStore, nullptr, std::bind(&Interface::setOnline, this, std::placeholders::_1)} + , status{this, "status", InterfaceStatus::Offline, PropertyFlags::ReadOnly | PropertyFlags::NoStore} + , notes{this, "notes", "", PropertyFlags::ReadWrite | PropertyFlags::Store} +{ + auto w = world.lock(); + const bool editable = w && contains(w->state.value(), WorldState::Edit); + + Attributes::addDisplayName(name, DisplayName::Object::name); + Attributes::addEnabled(name, editable); + m_interfaceItems.add(name); + + Attributes::addDisplayName(online, DisplayName::CommandStation::online); + m_interfaceItems.add(online); + + Attributes::addDisplayName(status, DisplayName::Interface::status); + Attributes::addValues(status, interfaceStatusValues); + m_interfaceItems.add(status); + + Attributes::addDisplayName(notes, DisplayName::Object::notes); + m_interfaceItems.add(notes); +} + +void Interface::addToWorld() +{ + IdObject::addToWorld(); + + if(auto world = m_world.lock()) + world->interfaces->addObject(shared_ptr()); +} + +void Interface::destroying() +{ + if(auto world = m_world.lock()) + world->interfaces->removeObject(shared_ptr()); + IdObject::destroying(); +} + +void Interface::worldEvent(WorldState state, WorldEvent event) +{ + IdObject::worldEvent(state, event); + + Attributes::setEnabled(name, contains(state, WorldState::Edit)); + + switch(event) + { + case WorldEvent::Offline: + online = false; + break; + + case WorldEvent::Online: + online = true; + break; + + default: + break; + } +} diff --git a/server/src/hardware/interface/interface.hpp b/server/src/hardware/interface/interface.hpp new file mode 100644 index 00000000..7b5c4a7e --- /dev/null +++ b/server/src/hardware/interface/interface.hpp @@ -0,0 +1,50 @@ +/** + * server/src/hardware/interface/interface.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021 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_INTERFACE_INTERFACE_HPP +#define TRAINTASTIC_SERVER_HARDWARE_INTERFACE_INTERFACE_HPP + +#include "../../core/idobject.hpp" +#include "../../enum/interfacestatus.hpp" + +/** + * @brief Base class for a hardware interface + */ +class Interface : public IdObject +{ + protected: + Interface(const std::weak_ptr& world, std::string_view _id); + + void addToWorld() override; + void destroying() override; + void worldEvent(WorldState state, WorldEvent event) override; + + virtual bool setOnline(bool& value) = 0; + + public: + Property name; + Property online; + Property status; + Property notes; +}; + +#endif diff --git a/server/src/hardware/interface/interfacelist.cpp b/server/src/hardware/interface/interfacelist.cpp new file mode 100644 index 00000000..756a9f17 --- /dev/null +++ b/server/src/hardware/interface/interfacelist.cpp @@ -0,0 +1,80 @@ +/** + * server/src/hardware/interface/interfacelist.cpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021 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 "interfacelist.hpp" +#include "interfacelisttablemodel.hpp" +#include "interfaces.hpp" +#include "../../world/world.hpp" +#include "../../world/getworld.hpp" +#include "../../core/attributes.hpp" +#include "../../utils/displayname.hpp" + +InterfaceList::InterfaceList(Object& _parent, const std::string& parentPropertyName) : + ObjectList(_parent, parentPropertyName), + add{*this, "add", + [this](std::string_view interfaceClassId) + { + auto world = getWorld(this); + if(!world) + return std::shared_ptr(); + return Interfaces::create(world, interfaceClassId); + }}, + remove{*this, "remove", + [this](const std::shared_ptr& object) + { + if(containsObject(object)) + object->destroy(); + assert(!containsObject(object)); + }} +{ + auto world = getWorld(&_parent); + const bool editable = world && contains(world->state.value(), WorldState::Edit); + + Attributes::addDisplayName(add, DisplayName::List::add); + Attributes::addEnabled(add, editable); + Attributes::addClassList(add, Interfaces::classList); + m_interfaceItems.add(add); + + Attributes::addDisplayName(remove, DisplayName::List::remove); + Attributes::addEnabled(remove, editable); + m_interfaceItems.add(remove); +} + +TableModelPtr InterfaceList::getModel() +{ + return std::make_shared(*this); +} + +void InterfaceList::worldEvent(WorldState state, WorldEvent event) +{ + ObjectList::worldEvent(state, event); + + const bool editable = contains(state, WorldState::Edit); + + Attributes::setEnabled(add, editable); + Attributes::setEnabled(remove, editable); +} + +bool InterfaceList::isListedProperty(const std::string& name) +{ + return InterfaceListTableModel::isListedProperty(name); +} diff --git a/server/src/hardware/output/keyboard/loconetoutputkeyboard.hpp b/server/src/hardware/interface/interfacelist.hpp similarity index 52% rename from server/src/hardware/output/keyboard/loconetoutputkeyboard.hpp rename to server/src/hardware/interface/interfacelist.hpp index 5b699fbe..8b03cd18 100644 --- a/server/src/hardware/output/keyboard/loconetoutputkeyboard.hpp +++ b/server/src/hardware/interface/interfacelist.hpp @@ -1,9 +1,9 @@ /** - * server/src/hardware/output/keyboard/loconetoutputkeyboard.hpp + * server/src/hardware/interface/interfacelist.hpp * * This file is part of the traintastic source code. * - * Copyright (C) 2019-2021 Reinder Feenstra + * Copyright (C) 2021 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -20,30 +20,28 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef TRAINTASTIC_SERVER_HARDWARE_OUTPUT_KEYBOARD_LOCONETOUTPUTKEYBOARD_HPP -#define TRAINTASTIC_SERVER_HARDWARE_OUTPUT_KEYBOARD_LOCONETOUTPUTKEYBOARD_HPP +#ifndef TRAINTASTIC_SERVER_HARDWARE_INTERFACE_INTERFACELIST_HPP +#define TRAINTASTIC_SERVER_HARDWARE_INTERFACE_INTERFACELIST_HPP -#include "outputkeyboard.hpp" +#include "../../core/objectlist.hpp" +#include "../../core/method.hpp" +#include "interface.hpp" -namespace LocoNet { - class LocoNet; -} - -class LocoNetOutputKeyboard final : public OutputKeyboard +class InterfaceList : public ObjectList { protected: - std::shared_ptr m_loconet; + void worldEvent(WorldState state, WorldEvent event) final; + bool isListedProperty(const std::string& name) final; public: - CLASS_ID("output_keyboard.loconet") + CLASS_ID("list.interface") - LocoNetOutputKeyboard(std::shared_ptr loconet); - ~LocoNetOutputKeyboard() final; + Method(std::string_view)> add; + Method&)> remove; - std::string getObjectId() const final { return ""; } + InterfaceList(Object& _parent, const std::string& parentPropertyName); - std::vector getOutputInfo() const final; - void setOutputValue(uint32_t address, bool value) final; + TableModelPtr getModel() final; }; #endif diff --git a/server/src/hardware/interface/interfacelisttablemodel.cpp b/server/src/hardware/interface/interfacelisttablemodel.cpp new file mode 100644 index 00000000..5b57e274 --- /dev/null +++ b/server/src/hardware/interface/interfacelisttablemodel.cpp @@ -0,0 +1,83 @@ +/** + * server/src/hardware/interface/interfacelisttablemodel.cpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021 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 "interfacelisttablemodel.hpp" +#include "interfacelist.hpp" +#include "../../utils/displayname.hpp" + +constexpr uint32_t columnId = 0; +constexpr uint32_t columnName = 1; +constexpr uint32_t columnStatus = 2; + +bool InterfaceListTableModel::isListedProperty(const std::string& name) +{ + return + name == "id" || + name == "name" || + name == "status"; +} + +InterfaceListTableModel::InterfaceListTableModel(InterfaceList& list) : + ObjectListTableModel(list) +{ + setColumnHeaders({ + DisplayName::Object::id, + DisplayName::Object::name, + DisplayName::Interface::status, + }); +} + +std::string InterfaceListTableModel::getText(uint32_t column, uint32_t row) const +{ + if(row < rowCount()) + { + const Interface& interface = getItem(row); + + switch(column) + { + case columnId: + return interface.id; + + case columnName: + return interface.name; + + case columnStatus: + return ""; + + default: + assert(false); + break; + } + } + + return ""; +} + +void InterfaceListTableModel::propertyChanged(BaseProperty& property, uint32_t row) +{ + if(property.name() == "id") + changed(row, columnId); + else if(property.name() == "name") + changed(row, columnName); + else if(property.name() == "status") + changed(row, columnStatus); +} diff --git a/server/src/hardware/input/monitor/loconetinputmonitor.hpp b/server/src/hardware/interface/interfacelisttablemodel.hpp similarity index 52% rename from server/src/hardware/input/monitor/loconetinputmonitor.hpp rename to server/src/hardware/interface/interfacelisttablemodel.hpp index c8e6e0c3..629bb2e9 100644 --- a/server/src/hardware/input/monitor/loconetinputmonitor.hpp +++ b/server/src/hardware/interface/interfacelisttablemodel.hpp @@ -1,9 +1,9 @@ /** - * server/src/hardware/input/monitor/loconetinputmonitor.hpp + * server/src/hardware/interface/interfacelisttablemodel.hpp * * This file is part of the traintastic source code. * - * Copyright (C) 2019-2021 Reinder Feenstra + * Copyright (C) 2021 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -20,29 +20,29 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef TRAINTASTIC_SERVER_HARDWARE_INPUT_MONITOR_LOCONETINPUTMONITOR_HPP -#define TRAINTASTIC_SERVER_HARDWARE_INPUT_MONITOR_LOCONETINPUTMONITOR_HPP +#ifndef TRAINTASTIC_SERVER_HARDWARE_INTERFACE_INTERFACELISTTABLEMODEL_HPP +#define TRAINTASTIC_SERVER_HARDWARE_INTERFACE_INTERFACELISTTABLEMODEL_HPP -#include "inputmonitor.hpp" +#include "../../core/objectlisttablemodel.hpp" +#include "interfacelist.hpp" -namespace LocoNet { - class LocoNet; -} +class InterfaceList; -class LocoNetInputMonitor final : public InputMonitor +class InterfaceListTableModel : public ObjectListTableModel { + friend class InterfaceList; + protected: - std::shared_ptr m_loconet; + void propertyChanged(BaseProperty& property, uint32_t row) final; public: - CLASS_ID("input_monitor.loconet") + CLASS_ID("table_model.list.interface") - LocoNetInputMonitor(std::shared_ptr loconet); - ~LocoNetInputMonitor() final; + static bool isListedProperty(const std::string& name); - std::string getObjectId() const final { return ""; } + InterfaceListTableModel(InterfaceList& interfaceList); - std::vector getInputInfo() const final; + std::string getText(uint32_t column, uint32_t row) const final; }; #endif diff --git a/server/src/hardware/input/inputs.cpp b/server/src/hardware/interface/interfaces.cpp similarity index 72% rename from server/src/hardware/input/inputs.cpp rename to server/src/hardware/interface/interfaces.cpp index 7dc73088..0c2767e0 100644 --- a/server/src/hardware/input/inputs.cpp +++ b/server/src/hardware/interface/interfaces.cpp @@ -1,9 +1,9 @@ /** - * server/src/hardware/input/inputs.cpp + * server/src/hardware/interface/interfaces.cpp * * This file is part of the traintastic source code. * - * Copyright (C) 2019-2021 Reinder Feenstra + * Copyright (C) 2021 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -20,13 +20,11 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "inputs.hpp" +#include "interfaces.hpp" #include "../../utils/ifclassidcreate.hpp" #include "../../world/world.hpp" -std::shared_ptr Inputs::create(const std::shared_ptr& world, std::string_view classId, std::string_view id) +std::shared_ptr Interfaces::create(const std::shared_ptr& world, std::string_view classId, std::string_view id) { - IF_CLASSID_CREATE(LocoNetInput) - IF_CLASSID_CREATE(XpressNetInput) - return std::shared_ptr(); + return std::shared_ptr(); } diff --git a/server/src/hardware/output/outputs.hpp b/server/src/hardware/interface/interfaces.hpp similarity index 65% rename from server/src/hardware/output/outputs.hpp rename to server/src/hardware/interface/interfaces.hpp index ed65c8b4..97aeb230 100644 --- a/server/src/hardware/output/outputs.hpp +++ b/server/src/hardware/interface/interfaces.hpp @@ -1,9 +1,9 @@ /** - * server/src/hardware/output/outputs.hpp + * server/src/hardware/interface/interfaces.hpp * * This file is part of the traintastic source code. * - * Copyright (C) 2019-2020 Reinder Feenstra + * Copyright (C) 2021 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -20,23 +20,21 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef TRAINTASTIC_SERVER_HARDWARE_OUTPUT_OUTPUTS_HPP -#define TRAINTASTIC_SERVER_HARDWARE_OUTPUT_OUTPUTS_HPP +#ifndef TRAINTASTIC_SERVER_HARDWARE_INTERFACE_INTERFACES_HPP +#define TRAINTASTIC_SERVER_HARDWARE_INTERFACE_INTERFACES_HPP -#include "output.hpp" +#include "interface.hpp" #include "../../utils/makearray.hpp" -#include "loconetoutput.hpp" -struct Outputs +struct Interfaces { - static constexpr std::string_view classIdPrefix = "output."; + static constexpr std::string_view classIdPrefix = "interface."; static constexpr auto classList = makeArray( - LocoNetOutput::classId ); - static std::shared_ptr create(const std::weak_ptr& world, std::string_view classId, std::string_view id); + static std::shared_ptr create(const std::shared_ptr& world, std::string_view classId, std::string_view id); }; #endif diff --git a/server/src/hardware/output/keyboard/loconetoutputkeyboard.cpp b/server/src/hardware/output/keyboard/loconetoutputkeyboard.cpp deleted file mode 100644 index d80b2e62..00000000 --- a/server/src/hardware/output/keyboard/loconetoutputkeyboard.cpp +++ /dev/null @@ -1,64 +0,0 @@ -/** - * server/src/hardware/output/keyboard/loconetoutputkeyboard.cpp - * - * This file is part of the traintastic source code. - * - * Copyright (C) 2019-2021 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 "loconetoutputkeyboard.hpp" -#include "../../protocol/loconet/loconet.hpp" -#include "../loconetoutput.hpp" - -LocoNetOutputKeyboard::LocoNetOutputKeyboard(std::shared_ptr loconet) : - OutputKeyboard(), - m_loconet{std::move(loconet)} -{ - addressMin.setValueInternal(LocoNetOutput::addressMin); - addressMax.setValueInternal(LocoNetOutput::addressMax); - m_loconet->m_outputKeyboards.emplace_back(this); -} - -LocoNetOutputKeyboard::~LocoNetOutputKeyboard() -{ - if(auto it = std::find(m_loconet->m_outputKeyboards.begin(), m_loconet->m_outputKeyboards.end(), this); it != m_loconet->m_outputKeyboards.end()) - m_loconet->m_outputKeyboards.erase(it); -} - -std::vector LocoNetOutputKeyboard::getOutputInfo() const -{ - std::vector outputInfo; - for(auto it : m_loconet->m_outputs) - { - LocoNetOutput& output = *(it.second); - OutputInfo info(output.address, output.id, output.value); - outputInfo.push_back(info); - } - return outputInfo; -} - -void LocoNetOutputKeyboard::setOutputValue(uint32_t address, bool value) -{ - if(address < LocoNetOutput::addressMin || address > LocoNetOutput::addressMax) - return; - - auto it = m_loconet->m_outputs.find(address); - if(it != m_loconet->m_outputs.end()) - it->second->value = toTriState(value); - else - m_loconet->send(LocoNet::SwitchRequest(address - 1, value)); -} diff --git a/server/src/hardware/output/keyboard/outputkeyboard.cpp b/server/src/hardware/output/keyboard/outputkeyboard.cpp new file mode 100644 index 00000000..bfeb09e5 --- /dev/null +++ b/server/src/hardware/output/keyboard/outputkeyboard.cpp @@ -0,0 +1,55 @@ +/** + * server/src/hardware/output/keyboard/outputkeyboard.cpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2019-2021 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 "outputkeyboard.hpp" +#include "../output.hpp" +#include "../outputcontroller.hpp" +#include "../../../utils/inrange.hpp" + +OutputKeyboard::OutputKeyboard(OutputController& controller) + : Object() + , m_controller{controller} + , addressMin{this, "address_min", m_controller.outputAddressMinMax().first, PropertyFlags::ReadOnly | PropertyFlags::NoStore} + , addressMax{this, "address_max", m_controller.outputAddressMinMax().second, PropertyFlags::ReadOnly | PropertyFlags::NoStore} +{ +} + +std::string OutputKeyboard::getObjectId() const +{ + return ""; +} + +std::vector OutputKeyboard::getOutputInfo() const +{ + std::vector outputInfo; + for(auto it : m_controller.outputs()) + { + const auto& output = *(it.second); + outputInfo.emplace_back(OutputInfo{output.address, output.id, output.value}); + } + return outputInfo; +} + +void OutputKeyboard::setOutputValue(uint32_t address, bool value) +{ + m_controller.setOutputValue(address, value); +} diff --git a/server/src/hardware/output/keyboard/outputkeyboard.hpp b/server/src/hardware/output/keyboard/outputkeyboard.hpp index dd0f68ef..107542f8 100644 --- a/server/src/hardware/output/keyboard/outputkeyboard.hpp +++ b/server/src/hardware/output/keyboard/outputkeyboard.hpp @@ -28,11 +28,18 @@ #include "../../../core/property.hpp" #include "../../../enum/tristate.hpp" +class OutputController; + class OutputKeyboard : public Object { + CLASS_ID("output_keyboard") + + private: + OutputController& m_controller; + public: - std::function outputIdChanged; - std::function outputValueChanged; + boost::signals2::signal outputIdChanged; + boost::signals2::signal outputValueChanged; struct OutputInfo { @@ -51,15 +58,12 @@ class OutputKeyboard : public Object Property addressMin; Property addressMax; - OutputKeyboard() : - Object(), - addressMin{this, "address_min", 0, PropertyFlags::ReadOnly | PropertyFlags::NoStore}, - addressMax{this, "address_max", 0, PropertyFlags::ReadOnly | PropertyFlags::NoStore} - { - } + OutputKeyboard(OutputController& controller); - virtual std::vector getOutputInfo() const = 0; - virtual void setOutputValue(uint32_t address, bool value) = 0; + std::string getObjectId() const final; + + std::vector getOutputInfo() const; + void setOutputValue(uint32_t address, bool value); }; #endif diff --git a/server/src/hardware/output/list/outputlist.cpp b/server/src/hardware/output/list/outputlist.cpp index e9303663..2eba1bfa 100644 --- a/server/src/hardware/output/list/outputlist.cpp +++ b/server/src/hardware/output/list/outputlist.cpp @@ -22,31 +22,32 @@ #include "outputlist.hpp" #include "outputlisttablemodel.hpp" -#include "../outputs.hpp" +#include "../outputcontroller.hpp" #include "../../../world/getworld.hpp" #include "../../../core/attributes.hpp" #include "../../../utils/displayname.hpp" -OutputList::OutputList(Object& _parent, const std::string& parentPropertyName) : - ObjectList(_parent, parentPropertyName), - add{*this, "add", - [this](std::string_view outputClassId) - { - auto world = getWorld(&this->parent()); - if(!world) - return std::shared_ptr(); - auto output = Outputs::create(world, outputClassId, world->getUniqueId("output")); - if(auto locoNetOutput = std::dynamic_pointer_cast(output); locoNetOutput && world->loconets->length == 1) +OutputList::OutputList(Object& _parent, const std::string& parentPropertyName) + : ObjectList(_parent, parentPropertyName) + , m_parentIsOutputController{dynamic_cast(&_parent)} + , add{*this, "add", + [this]() { - auto& loconet = world->loconets->operator[](0); - if(uint16_t address = loconet->getUnusedOutputAddress(); address != LocoNetOutput::addressInvalid) + auto world = getWorld(&parent()); + if(!world) + return std::shared_ptr(); + + auto output = Output::create(world, world->getUniqueId(Output::defaultId)); + if(const auto controller = std::dynamic_pointer_cast(parent().shared_from_this())) { - locoNetOutput->address = address; - locoNetOutput->loconet = loconet; + if(const uint32_t address = controller->getUnusedOutputAddress(); address != Output::invalidAddress) + { + output->address = address; + output->interface = controller; + } } - } - return output; - }} + return output; + }} , remove{*this, "remove", [this](const std::shared_ptr& output) { @@ -54,18 +55,31 @@ OutputList::OutputList(Object& _parent, const std::string& parentPropertyName) : output->destroy(); assert(!containsObject(output)); }} + , outputKeyboard{*this, "output_keyboard", + [this]() + { + if(const auto controller = std::dynamic_pointer_cast(parent().shared_from_this())) + return controller->outputKeyboard(); + else + return std::shared_ptr(); + }} { auto w = getWorld(&_parent); const bool editable = w && contains(w->state.value(), WorldState::Edit); Attributes::addDisplayName(add, DisplayName::List::add); Attributes::addEnabled(add, editable); - Attributes::addClassList(add, Outputs::classList); m_interfaceItems.add(add); Attributes::addDisplayName(remove, DisplayName::List::remove); Attributes::addEnabled(remove, editable); m_interfaceItems.add(remove); + + if(m_parentIsOutputController) + { + Attributes::addDisplayName(outputKeyboard, DisplayName::Hardware::outputKeyboard); + m_interfaceItems.add(outputKeyboard); + } } TableModelPtr OutputList::getModel() diff --git a/server/src/hardware/output/list/outputlist.hpp b/server/src/hardware/output/list/outputlist.hpp index 91ce5eff..a382721d 100644 --- a/server/src/hardware/output/list/outputlist.hpp +++ b/server/src/hardware/output/list/outputlist.hpp @@ -26,21 +26,28 @@ #include "../../../core/objectlist.hpp" #include "../../../core/method.hpp" #include "../output.hpp" +#include "../keyboard/outputkeyboard.hpp" class OutputList : public ObjectList { + CLASS_ID("list.output") + + private: + const bool m_parentIsOutputController; + protected: void worldEvent(WorldState state, WorldEvent event) final; bool isListedProperty(const std::string& name) final; public: - CLASS_ID("list.output") - - Method(std::string_view)> add; + Method()> add; Method&)> remove; + Method()> outputKeyboard; OutputList(Object& _parent, const std::string& parentPropertyName); + inline bool parentIsOutputController() const { return m_parentIsOutputController; } + TableModelPtr getModel() final; }; diff --git a/server/src/hardware/output/list/outputlisttablemodel.cpp b/server/src/hardware/output/list/outputlisttablemodel.cpp index e20d5292..10d88163 100644 --- a/server/src/hardware/output/list/outputlisttablemodel.cpp +++ b/server/src/hardware/output/list/outputlisttablemodel.cpp @@ -22,30 +22,39 @@ #include "outputlisttablemodel.hpp" #include "outputlist.hpp" -#include "../loconetoutput.hpp" #include "../../../utils/displayname.hpp" -constexpr uint32_t columnId = 0; -constexpr uint32_t columnName = 1; -constexpr uint32_t columnBus = 2; -constexpr uint32_t columnAddress = 3; - bool OutputListTableModel::isListedProperty(const std::string& name) { return name == "id" || - name == "name"; + name == "name" || + name == "interface" || + name == "address"; } OutputListTableModel::OutputListTableModel(OutputList& list) : ObjectListTableModel(list) + , m_columnInterface(list.parentIsOutputController() ? invalidColumn : 2) + , m_columnAddress(list.parentIsOutputController() ? 2 : 3) { - setColumnHeaders({ - DisplayName::Object::id, - DisplayName::Object::name, - "output_list:bus", - DisplayName::Hardware::address, - }); + if(list.parentIsOutputController()) + { + setColumnHeaders({ + DisplayName::Object::id, + DisplayName::Object::name, + DisplayName::Hardware::address, + }); + } + else + { + setColumnHeaders({ + DisplayName::Object::id, + DisplayName::Object::name, + DisplayName::Hardware::interface, + DisplayName::Hardware::address, + }); + } } std::string OutputListTableModel::getText(uint32_t column, uint32_t row) const @@ -53,32 +62,27 @@ std::string OutputListTableModel::getText(uint32_t column, uint32_t row) const if(row < rowCount()) { const Output& output = getItem(row); - const LocoNetOutput* outputLocoNet = dynamic_cast(&output); - switch(column) + if(column == columnId) + return output.id; + else if(column == columnName) + return output.name; + else if(column == m_columnInterface) { - case columnId: - return output.id; - - case columnName: - return output.name; - - case columnBus: // virtual method @ Output ?? - if(outputLocoNet && outputLocoNet->loconet) - return outputLocoNet->loconet->getObjectId(); + if(const auto& interface = std::dynamic_pointer_cast(output.interface.value())) + { + if(auto property = interface->getProperty("name"); property && !property->toString().empty()) + return property->toString(); + else + return interface->getObjectId(); + } else return ""; - - case columnAddress: // virtual method @ Output ?? - if(outputLocoNet) - return std::to_string(outputLocoNet->address); - else - return ""; - - default: - assert(false); - break; } + else if(column == m_columnAddress) + return std::to_string(output.address.value()); + else + assert(false); } return ""; @@ -90,4 +94,8 @@ void OutputListTableModel::propertyChanged(BaseProperty& property, uint32_t row) changed(row, columnId); else if(property.name() == "name") changed(row, columnName); + else if(property.name() == "interface" && m_columnInterface != invalidColumn) + changed(row, m_columnInterface); + else if(property.name() == "address") + changed(row, m_columnAddress); } diff --git a/server/src/hardware/output/list/outputlisttablemodel.hpp b/server/src/hardware/output/list/outputlisttablemodel.hpp index 28f8689d..ac24c3ad 100644 --- a/server/src/hardware/output/list/outputlisttablemodel.hpp +++ b/server/src/hardware/output/list/outputlisttablemodel.hpp @@ -32,6 +32,12 @@ class OutputListTableModel : public ObjectListTableModel { friend class OutputList; + private: + static constexpr uint32_t columnId = 0; + static constexpr uint32_t columnName = 1; + const uint32_t m_columnInterface; + const uint32_t m_columnAddress; + protected: void propertyChanged(BaseProperty& property, uint32_t row) final; diff --git a/server/src/hardware/output/loconetoutput.cpp b/server/src/hardware/output/loconetoutput.cpp deleted file mode 100644 index 60812777..00000000 --- a/server/src/hardware/output/loconetoutput.cpp +++ /dev/null @@ -1,112 +0,0 @@ -/** - * server/src/hardware/output/loconetoutput.cpp - * - * This file is part of the traintastic source code. - * - * Copyright (C) 2019-2021 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 "loconetoutput.hpp" -#include "../../core/attributes.hpp" -#include "../../world/world.hpp" -#include "../../log/log.hpp" -#include "../../utils/displayname.hpp" - -LocoNetOutput::LocoNetOutput(const std::weak_ptr world, std::string_view _id) : - Output(world, _id), - loconet{this, "loconet", nullptr, PropertyFlags::ReadWrite | PropertyFlags::Store, - [this](const std::shared_ptr& newValue) - { - if(!newValue || newValue->addOutput(*this)) - { - if(loconet.value()) - loconet->removeOutput(*this); - return true; - } - return false; - }}, - address{this, "address", addressMin, PropertyFlags::ReadWrite | PropertyFlags::Store, nullptr, - [this](const uint16_t& newValue) - { - if(loconet) - return loconet->changeOutputAddress(*this, newValue); - return true; - }} -{ - auto w = world.lock(); - const bool editable = w && contains(w->state.value(), WorldState::Edit); - - Attributes::addDisplayName(loconet, DisplayName::Hardware::loconet); - Attributes::addEnabled(loconet, editable); - Attributes::addObjectList(loconet, w->loconets); - m_interfaceItems.add(loconet); - - Attributes::addDisplayName(address, DisplayName::Hardware::address); - Attributes::addEnabled(address, editable); - Attributes::addMinMax(address, addressMin, addressMax); - m_interfaceItems.add(address); -} - -void LocoNetOutput::loaded() -{ - Output::loaded(); - if(loconet) - { - if(!loconet->addOutput(*this)) - { - Log::log(*this, LogMessage::C2001_ADDRESS_ALREADY_USED_AT_X, *loconet); - loconet.setValueInternal(nullptr); - } - } -} - -void LocoNetOutput::destroying() -{ - if(loconet) - loconet = nullptr; - Output::destroying(); -} - -void LocoNetOutput::worldEvent(WorldState state, WorldEvent event) -{ - Output::worldEvent(state, event); - - const bool editable = contains(state, WorldState::Edit); - - Attributes::setEnabled(loconet, editable); - Attributes::setEnabled(address, editable); -} - -void LocoNetOutput::idChanged(const std::string& newId) -{ - if(loconet) - loconet->outputKeyboardIdChanged(address, newId); -} - -void LocoNetOutput::valueChanged(TriState _value) -{ - if(loconet) - loconet->outputKeyboardValueChanged(address, _value); -} - -bool LocoNetOutput::setValue(TriState& _value) -{ - if(!loconet || _value == TriState::Undefined) - return false; - else - return loconet->send(LocoNet::SwitchRequest(address - 1, _value == TriState::True)); -} diff --git a/server/src/hardware/output/loconetoutput.hpp b/server/src/hardware/output/loconetoutput.hpp deleted file mode 100644 index 00513083..00000000 --- a/server/src/hardware/output/loconetoutput.hpp +++ /dev/null @@ -1,58 +0,0 @@ -/** - * server/src/hardware/output/loconetoutput.hpp - * - * This file is part of the traintastic source code. - * - * Copyright (C) 2019-2021 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_OUTPUT_LOCONETOUTPUT_HPP -#define TRAINTASTIC_SERVER_HARDWARE_OUTPUT_LOCONETOUTPUT_HPP - -#include "output.hpp" -#include "../../core/objectproperty.hpp" -#include "../protocol/loconet/loconet.hpp" - -class LocoNetOutput : public Output -{ - friend class LocoNet::LocoNet; - - protected: - void loaded() final; - void destroying() final; - void worldEvent(WorldState state, WorldEvent event) final; - void idChanged(const std::string& newId) final; - void valueChanged(TriState _value) final; - bool setValue(TriState& _value) final; - - inline void updateValue(TriState _value) { Output::updateValue(_value); } - - public: - CLASS_ID("output.loconet") - CREATE(LocoNetOutput) - - static constexpr uint16_t addressInvalid = 0; - static constexpr uint16_t addressMin = 1; - static constexpr uint16_t addressMax = 4096; - - ObjectProperty loconet; - Property address; - - LocoNetOutput(const std::weak_ptr world, std::string_view _id); -}; - -#endif diff --git a/server/src/hardware/output/output.cpp b/server/src/hardware/output/output.cpp index 001c1f2e..97bceb9d 100644 --- a/server/src/hardware/output/output.cpp +++ b/server/src/hardware/output/output.cpp @@ -24,20 +24,55 @@ #include "../../world/world.hpp" #include "list/outputlisttablemodel.hpp" #include "../../core/attributes.hpp" +#include "../../log/log.hpp" #include "../../utils/displayname.hpp" -Output::Output(const std::weak_ptr world, std::string_view _id) : - IdObject(world, _id), - name{this, "name", id, PropertyFlags::ReadWrite | PropertyFlags::Store}, - value{this, "value", TriState::Undefined, PropertyFlags::ReadWrite | PropertyFlags::StoreState, - [this](TriState newValue) - { - valueChanged(newValue); - }, - [this](TriState& newValue) -> bool - { - return setValue(newValue); - }} +Output::Output(const std::weak_ptr world, std::string_view _id) + : IdObject(world, _id) + , name{this, "name", id, PropertyFlags::ReadWrite | PropertyFlags::Store} + , interface{this, "interface", nullptr, PropertyFlags::ReadWrite | PropertyFlags::Store, + [this](const std::shared_ptr& newValue) + { + if(newValue) + { + const auto limits = newValue->outputAddressMinMax(); + Attributes::setMinMax(address, limits.first, limits.second); + } + else + { + Attributes::setMinMax(address, addressMinDefault, addressMaxDefault); + value.setValueInternal(TriState::Undefined); + } + }, + [this](const std::shared_ptr& newValue) + { + if(!newValue || newValue->addOutput(*this)) + { + if(interface.value()) + interface->removeOutput(*this); + return true; + } + return false; + }} + , address{this, "address", 1, PropertyFlags::ReadWrite | PropertyFlags::Store, nullptr, + [this](const uint32_t& newValue) + { + if(interface) + return interface->changeOutputAddress(*this, newValue); + return true; + }} + , value{this, "value", TriState::Undefined, PropertyFlags::ReadWrite | PropertyFlags::StoreState, + [this](TriState newValue) + { + valueChanged(newValue); + }, + [this](TriState& newValue) -> bool + { + if(!interface || newValue == TriState::Undefined) + return false; + else + return interface->setOutputValue(address, newValue == TriState::True); + }} , controllers{*this, "controllers", {}, PropertyFlags::ReadWrite | PropertyFlags::NoStore} { auto w = world.lock(); @@ -46,9 +81,21 @@ Output::Output(const std::weak_ptr world, std::string_view _id) : Attributes::addDisplayName(name, DisplayName::Object::name); Attributes::addEnabled(name, editable); m_interfaceItems.add(name); + + Attributes::addDisplayName(interface, DisplayName::Hardware::interface); + Attributes::addEnabled(interface, editable); + Attributes::addObjectList(interface, w->outputControllers); + m_interfaceItems.add(interface); + + Attributes::addDisplayName(address, DisplayName::Hardware::address); + Attributes::addEnabled(address, editable); + Attributes::addMinMax(address, addressMinDefault, addressMaxDefault); + m_interfaceItems.add(address); + Attributes::addObjectEditor(value, false); Attributes::addValues(value, TriStateValues); m_interfaceItems.add(value); + Attributes::addObjectEditor(controllers, false); //! \todo add client support first m_interfaceItems.add(controllers); } @@ -61,10 +108,26 @@ void Output::addToWorld() world->outputs->addObject(shared_ptr()); } +void Output::loaded() +{ + IdObject::loaded(); + if(interface) + { + if(!interface->addOutput(*this)) + { + if(auto object = std::dynamic_pointer_cast(interface.value())) + Log::log(*this, LogMessage::C2001_ADDRESS_ALREADY_USED_AT_X, *object); + interface.setValueInternal(nullptr); + } + } +} + void Output::destroying() { if(auto world = m_world.lock()) world->outputs->removeObject(shared_ptr()); + if(interface) + interface = nullptr; IdObject::destroying(); } @@ -75,6 +138,8 @@ void Output::worldEvent(WorldState state, WorldEvent event) const bool editable = contains(state, WorldState::Edit); Attributes::setEnabled(name, editable); + Attributes::setEnabled(interface, editable); + Attributes::setEnabled(address, editable); } void Output::updateValue(TriState _value) diff --git a/server/src/hardware/output/output.hpp b/server/src/hardware/output/output.hpp index bbb67e36..954cac04 100644 --- a/server/src/hardware/output/output.hpp +++ b/server/src/hardware/output/output.hpp @@ -24,21 +24,38 @@ #define TRAINTASTIC_SERVER_HARDWARE_OUTPUT_OUTPUT_HPP #include "../../core/idobject.hpp" +#include "../../core/objectproperty.hpp" #include "../../core/objectvectorproperty.hpp" #include "../../enum/tristate.hpp" +#include "outputcontroller.hpp" -class Output : public IdObject +class Output final : public IdObject { + CLASS_ID("output") + DEFAULT_ID("output") + CREATE(Output) + + friend class OutputController; + + private: + static constexpr uint32_t addressMinDefault = 0; + static constexpr uint32_t addressMaxDefault = 1'000'000; + protected: - void addToWorld() override; - void destroying() override; - void worldEvent(WorldState state, WorldEvent event) override; + void addToWorld() final; + void loaded() final; + void destroying() final; + void worldEvent(WorldState state, WorldEvent event) final; virtual void valueChanged(TriState /*_value*/) {} virtual bool setValue(TriState& /*_value*/) { return true; } void updateValue(TriState _value); public: + static constexpr uint32_t invalidAddress = std::numeric_limits::max(); + Property name; + ObjectProperty interface; + Property address; Property value; ObjectVectorProperty controllers; diff --git a/server/src/hardware/output/outputcontroller.cpp b/server/src/hardware/output/outputcontroller.cpp new file mode 100644 index 00000000..a9b66c12 --- /dev/null +++ b/server/src/hardware/output/outputcontroller.cpp @@ -0,0 +1,103 @@ +/** + * server/src/hardware/output/outputcontroller.cpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021 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 "outputcontroller.hpp" +#include "output.hpp" +#include "keyboard/outputkeyboard.hpp" +#include "../../utils/inrange.hpp" + +bool OutputController::isOutputAddressAvailable(uint32_t address) const +{ + return + inRange(address, outputAddressMinMax()) && + m_outputs.find(address) == m_outputs.end(); +} + +uint32_t OutputController::getUnusedOutputAddress() const +{ + const auto end = m_outputs.cend(); + const auto range = outputAddressMinMax(); + for(uint32_t address = range.first; address < range.second; address++) + if(m_outputs.find(address) == end) + return address; + return Output::invalidAddress; +} + +bool OutputController::changeOutputAddress(Output& output, uint32_t newAddress) +{ + assert(output.interface.value().get() == this); + + if(!isOutputAddressAvailable(newAddress)) + return false; + + auto node = m_outputs.extract(output.address); // old address + node.key() = newAddress; + m_outputs.insert(std::move(node)); + output.value.setValueInternal(TriState::Undefined); + + return true; +} + +bool OutputController::addOutput(Output& output) +{ + if(isOutputAddressAvailable(output.address)) + { + m_outputs.insert({output.address, output.shared_ptr()}); + output.value.setValueInternal(TriState::Undefined); + return true; + } + else + return false; +} + +bool OutputController::removeOutput(Output& output) +{ + assert(output.interface.value().get() == this); + auto it = m_outputs.find(output.address); + if(it != m_outputs.end() && it->second.get() == &output) + { + m_outputs.erase(it); + output.value.setValueInternal(TriState::Undefined); + return true; + } + else + return false; +} + +void OutputController::updateOutputValue(uint32_t address, TriState value) +{ + if(auto it = m_outputs.find(address); it != m_outputs.end()) + it->second->updateValue(value); + if(auto keyboard = m_outputKeyboard.lock()) + keyboard->outputValueChanged(*keyboard, address, value); +} + +std::shared_ptr OutputController::outputKeyboard() +{ + auto keyboard = m_outputKeyboard.lock(); + if(!keyboard) + { + keyboard = std::make_shared(*this); + m_outputKeyboard = keyboard; + } + return keyboard; +} diff --git a/server/src/hardware/output/outputcontroller.hpp b/server/src/hardware/output/outputcontroller.hpp new file mode 100644 index 00000000..fb0427ea --- /dev/null +++ b/server/src/hardware/output/outputcontroller.hpp @@ -0,0 +1,106 @@ +/** + * server/src/hardware/output/outputcontroller.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021 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_OUTPUT_OUTPUTCONTROLLER_HPP +#define TRAINTASTIC_SERVER_HARDWARE_OUTPUT_OUTPUTCONTROLLER_HPP + +#include +#include +#include +#include "../../enum/tristate.hpp" + +class Output; +class OutputKeyboard; + +class OutputController +{ + public: + using OutputMap = std::unordered_map>; + + protected: + OutputMap m_outputs; + std::weak_ptr m_outputKeyboard; + + public: + /** + * + */ + inline const OutputMap& outputs() const { return m_outputs; } + + /** + * + */ + virtual std::pair outputAddressMinMax() const = 0; + + /** + * + */ + [[nodiscard]] virtual bool isOutputAddressAvailable(uint32_t address) const; + + /** + * @brief Get the next unused output address + * + * @return An usused address or Output::invalidAddress if no unused address is available. + */ + uint32_t getUnusedOutputAddress() const; + + /** + * + * @return \c true if changed, \c false otherwise. + */ + [[nodiscard]] virtual bool changeOutputAddress(Output& output, uint32_t newAddress); + + /** + * + * @return \c true if added, \c false otherwise. + */ + [[nodiscard]] virtual bool addOutput(Output& output); + + /** + * + * @return \c true if removed, \c false otherwise. + */ + [[nodiscard]] virtual bool removeOutput(Output& output); + + /** + * @brief ... + */ + [[nodiscard]] virtual bool setOutputValue(uint32_t address, bool value) = 0; + + /** + * @brief Update the output value + * + * This function should be called by the hardware layer whenever the output value changes. + * + * @param[in] address Output address + * @param[in] value New output value + */ + void updateOutputValue(uint32_t address, TriState value); + + /** + * + * + */ + std::shared_ptr outputKeyboard(); +}; + +#endif diff --git a/server/src/hardware/protocol/dcc/dcc.hpp b/server/src/hardware/protocol/dcc/dcc.hpp new file mode 100644 index 00000000..70f12f17 --- /dev/null +++ b/server/src/hardware/protocol/dcc/dcc.hpp @@ -0,0 +1,44 @@ +/** + * server/src/hardware/protocol/dcc/dcc.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021 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_DCC_DCC_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_DCC_DCC_HPP + +#include +#include "../../../utils/inrange.hpp" + +namespace DCC { + +constexpr uint16_t addressBroadcast = 0; +constexpr uint16_t addressMin = 1; +constexpr uint16_t addressShortMax = 127; +constexpr uint16_t addressLongStart = 128; +constexpr uint16_t addressLongMax = 10239; + +constexpr bool isLongAddress(uint16_t address) +{ + return inRange(address, addressLongStart, addressLongMax); +} + +} + +#endif diff --git a/server/src/lua/class.cpp b/server/src/lua/class.cpp index 776e967f..7cd72044 100644 --- a/server/src/lua/class.cpp +++ b/server/src/lua/class.cpp @@ -54,30 +54,6 @@ #include "../clock/clock.hpp" -#include "../hardware/controller/wlanmaus.hpp" -#include "../hardware/controller/controllerlist.hpp" -#ifdef USB_XPRESSNET - #include "../hardware/controller/usbxpressnetcontroller.hpp" -#endif - -#include "../hardware/commandstation/rocoz21.hpp" -#include "../hardware/commandstation/virtualcommandstation.hpp" -#include "../hardware/commandstation/loconettcpbinary.hpp" -#ifdef USB_XPRESSNET - #include "../hardware/commandstation/usbxpressnetinterface.hpp" -#endif -#include "../hardware/commandstation/xpressnetserial.hpp" -#include "../hardware/commandstation/dccplusplusserial.hpp" -#include "../hardware/commandstation/commandstationlist.hpp" -#include "../hardware/commandstation/loconetserial.hpp" - -#include "../hardware/protocol/loconet/loconetlist.hpp" -#include "../hardware/protocol/loconet/loconet.hpp" -#include "../hardware/protocol/loconet/loconetlisttablemodel.hpp" -#include "../hardware/protocol/xpressnet/xpressnetlist.hpp" -#include "../hardware/protocol/xpressnet/xpressnetlisttablemodel.hpp" -#include "../hardware/protocol/xpressnet/xpressnet.hpp" -#include "../hardware/protocol/dccplusplus/dccplusplus.hpp" #include "../hardware/decoder/decoderfunction.hpp" #include "../hardware/decoder/decoderlist.hpp" @@ -85,24 +61,18 @@ #include "../hardware/decoder/decoderfunctions.hpp" #include "../hardware/decoder/decoderlisttablemodel.hpp" -#include "../hardware/input/loconetinput.hpp" -#include "../hardware/input/monitor/xpressnetinputmonitor.hpp" -#include "../hardware/input/monitor/loconetinputmonitor.hpp" -#include "../hardware/input/xpressnetinput.hpp" +#include "../hardware/input/input.hpp" #include "../hardware/input/list/inputlist.hpp" -#include "../hardware/input/list/inputlisttablemodel.hpp" #include "../hardware/input/map/blockinputmap.hpp" #include "../hardware/input/map/blockinputmapitem.hpp" +#include "../hardware/output/output.hpp" #include "../hardware/output/list/outputlist.hpp" -#include "../hardware/output/list/outputlisttablemodel.hpp" -#include "../hardware/output/keyboard/loconetoutputkeyboard.hpp" #include "../hardware/output/map/outputmapoutputaction.hpp" #include "../hardware/output/map/signaloutputmap.hpp" #include "../hardware/output/map/turnoutoutputmap.hpp" #include "../hardware/output/map/turnoutoutputmapitem.hpp" #include "../hardware/output/map/signaloutputmapitem.hpp" -#include "../hardware/output/loconetoutput.hpp" #include "../vehicle/rail/railvehiclelist.hpp" #include "../vehicle/rail/locomotive.hpp" @@ -170,34 +140,16 @@ void Class::registerValues(lua_State* L) setField(L, "CLOCK"); - setField(L, "WLANMAUS_CONTROLLER"); - setField(L, "CONTROLLER_LIST"); -#ifdef USB_XPRESSNET - setField(L, "USB_XPRESSNET_CONTROLLER"); -#endif - - setField(L, "Z21_COMMAND_STATION"); - setField(L, "VIRTUAL_COMMAND_STATION"); - setField(L, "LOCONET_TCP_BINARY_COMMAND_STATION"); -#ifdef USB_XPRESSNET - setField(L, "USB_XPRESSNET_INTERFACE"); -#endif - setField(L, "XPRESSNET_SERIAL_COMMAND_STATION"); - setField(L, "DCCPLUSPLUS_SERIAL_COMMAND_STATION"); - setField(L, "COMMAND_STATION_LIST"); - setField(L, "LOCONET_SERIAL_COMMAND_STATION"); - setField(L, "DECODER_FUNCTION"); setField(L, "DECODER_LIST"); setField(L, "DECODER"); setField(L, "DECODER_FUNCTIONS"); - setField(L, "LOCONET_INPUT"); - setField(L, "XPRESSNET_INPUT"); + setField(L, "INPUT"); setField(L, "INPUT_LIST"); + setField(L, "OUTPUT"); setField(L, "OUTPUT_LIST"); - setField(L, "LOCONET_OUTPUT"); setField(L, "RAIL_VEHICLE_LIST"); setField(L, "LOCOMOTIVE"); diff --git a/server/src/utils/displayname.hpp b/server/src/utils/displayname.hpp index 066f421e..7de4ecae 100644 --- a/server/src/utils/displayname.hpp +++ b/server/src/utils/displayname.hpp @@ -46,12 +46,20 @@ namespace DisplayName { constexpr std::string_view address = "hardware:address"; constexpr std::string_view commandStation = "hardware:command_station"; + constexpr std::string_view decoders = "hardware:decoders"; constexpr std::string_view inputMonitor = "hardware:input_monitor"; + constexpr std::string_view inputs = "hardware:inputs"; + constexpr std::string_view interface = "hardware:interface"; constexpr std::string_view loconet = "hardware:loconet"; constexpr std::string_view outputKeyboard = "hardware:output_keyboard"; + constexpr std::string_view outputs = "hardware:outputs"; constexpr std::string_view speedSteps = "hardware:speed_steps"; constexpr std::string_view xpressnet = "hardware:xpressnet"; } + namespace Interface + { + constexpr std::string_view status = "interface:status"; + } namespace IP { constexpr std::string_view hostname = "ip:hostname"; @@ -72,9 +80,9 @@ namespace DisplayName } namespace Serial { + constexpr std::string_view device = "serial:device"; constexpr std::string_view baudrate = "serial:baudrate"; constexpr std::string_view flowControl = "serial:flow_control"; - constexpr std::string_view port = "serial:port"; } namespace Vehicle { diff --git a/server/src/utils/inrange.hpp b/server/src/utils/inrange.hpp new file mode 100644 index 00000000..e7032606 --- /dev/null +++ b/server/src/utils/inrange.hpp @@ -0,0 +1,40 @@ +/** + * server/src/utils/inrange.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021 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_UTILS_INRANGE_HPP +#define TRAINTASTIC_SERVER_UTILS_INRANGE_HPP + +#include + +template +constexpr bool inRange(const T value, const T min, const T max) +{ + return value >= min && value <= max; +} + +template +constexpr bool inRange(const T value, const std::pair& range) +{ + return inRange(value, range.first, range.second); +} + +#endif diff --git a/server/src/world/world.cpp b/server/src/world/world.cpp index 31658bb4..88f70b9d 100644 --- a/server/src/world/world.cpp +++ b/server/src/world/world.cpp @@ -47,13 +47,14 @@ std::shared_ptr World::create() void World::init(const std::shared_ptr& world) { - world->commandStations.setValueInternal(std::make_shared(*world, world->commandStations.name())); + world->decoderControllers.setValueInternal(std::make_shared>(*world, world->decoderControllers.name())); + world->inputControllers.setValueInternal(std::make_shared>(*world, world->inputControllers.name())); + world->outputControllers.setValueInternal(std::make_shared>(*world, world->outputControllers.name())); + + world->interfaces.setValueInternal(std::make_shared(*world, world->interfaces.name())); world->decoders.setValueInternal(std::make_shared(*world, world->decoders.name())); world->inputs.setValueInternal(std::make_shared(*world, world->inputs.name())); world->outputs.setValueInternal(std::make_shared(*world, world->outputs.name())); - world->controllers.setValueInternal(std::make_shared(*world, world->controllers.name())); - world->loconets.setValueInternal(std::make_shared(*world, world->loconets.name())); - world->xpressnets.setValueInternal(std::make_shared(*world, world->xpressnets.name())); world->boards.setValueInternal(std::make_shared(*world, world->boards.name())); world->clock.setValueInternal(std::make_shared(*world, world->clock.name())); world->trains.setValueInternal(std::make_shared(*world, world->trains.name())); @@ -69,13 +70,13 @@ World::World(Private) : name{this, "name", "", PropertyFlags::ReadWrite | PropertyFlags::Store}, scale{this, "scale", WorldScale::H0, PropertyFlags::ReadWrite | PropertyFlags::Store, [this](WorldScale /*value*/){ updateScaleRatio(); }}, scaleRatio{this, "scale_ratio", 87, PropertyFlags::ReadWrite | PropertyFlags::Store}, - commandStations{this, "command_stations", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore}, + decoderControllers{this, "input_controllers", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore}, + inputControllers{this, "input_controllers", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore}, + outputControllers{this, "output_controllers", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore}, + interfaces{this, "interfaces", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore}, decoders{this, "decoders", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore}, inputs{this, "inputs", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore}, outputs{this, "outputs", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore}, - controllers{this, "controllers", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore}, - loconets{this, "loconets", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore}, - xpressnets{this, "xpressnets", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore}, boards{this, "boards", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore}, clock{this, "clock", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore}, trains{this, "trains", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore}, @@ -242,20 +243,21 @@ World::World(Private) : Attributes::addVisible(scaleRatio, false); m_interfaceItems.add(scaleRatio); - Attributes::addObjectEditor(commandStations, false); - m_interfaceItems.add(commandStations); + Attributes::addObjectEditor(decoderControllers, false); + m_interfaceItems.add(decoderControllers); + Attributes::addObjectEditor(inputControllers, false); + m_interfaceItems.add(inputControllers); + Attributes::addObjectEditor(outputControllers, false); + m_interfaceItems.add(outputControllers); + + Attributes::addObjectEditor(interfaces, false); + m_interfaceItems.add(interfaces); Attributes::addObjectEditor(decoders, false); m_interfaceItems.add(decoders); Attributes::addObjectEditor(inputs, false); m_interfaceItems.add(inputs); Attributes::addObjectEditor(outputs, false); m_interfaceItems.add(outputs); - Attributes::addObjectEditor(controllers, false); - m_interfaceItems.add(controllers); - Attributes::addObjectEditor(loconets, false); - m_interfaceItems.add(loconets); - Attributes::addObjectEditor(xpressnets, false); - m_interfaceItems.add(xpressnets); Attributes::addObjectEditor(boards, false); m_interfaceItems.add(boards); Attributes::addObjectEditor(clock, false); diff --git a/server/src/world/world.hpp b/server/src/world/world.hpp index c70e658a..5b95af72 100644 --- a/server/src/world/world.hpp +++ b/server/src/world/world.hpp @@ -26,6 +26,7 @@ #include "../core/object.hpp" #include "../core/property.hpp" #include "../core/objectproperty.hpp" +#include "../core/controllerlist.hpp" #include #include #include @@ -34,13 +35,13 @@ #include #include "../clock/clock.hpp" #include "../board/boardlist.hpp" -#include "../hardware/commandstation/commandstationlist.hpp" +#include "../hardware/interface/interfacelist.hpp" #include "../hardware/decoder/decoderlist.hpp" +#include "../hardware/decoder/decodercontroller.hpp" #include "../hardware/input/list/inputlist.hpp" +#include "../hardware/input/inputcontroller.hpp" #include "../hardware/output/list/outputlist.hpp" -#include "../hardware/controller/controllerlist.hpp" -#include "../hardware/protocol/loconet/loconetlist.hpp" -#include "../hardware/protocol/xpressnet/xpressnetlist.hpp" +#include "../hardware/output/outputcontroller.hpp" #include "../train/trainlist.hpp" #include "../vehicle/rail/railvehiclelist.hpp" #ifndef DISABLE_LUA_SCRIPTING @@ -85,13 +86,14 @@ class World : public Object Property scale; Property scaleRatio; - ObjectProperty commandStations; + ObjectProperty> decoderControllers; + ObjectProperty> inputControllers; + ObjectProperty> outputControllers; + + ObjectProperty interfaces; ObjectProperty decoders; ObjectProperty inputs; ObjectProperty outputs; - ObjectProperty controllers; - ObjectProperty loconets; - ObjectProperty xpressnets; ObjectProperty boards; ObjectProperty clock; ObjectProperty trains; diff --git a/server/src/world/worldloader.cpp b/server/src/world/worldloader.cpp index 9929d6dd..b919b8fe 100644 --- a/server/src/world/worldloader.cpp +++ b/server/src/world/worldloader.cpp @@ -31,12 +31,9 @@ #include "../board/board.hpp" #include "../board/tile/tiles.hpp" -#include "../hardware/commandstation/commandstations.hpp" -#include "../hardware/controller/controllers.hpp" +#include "../hardware/interface/interfaces.hpp" #include "../hardware/decoder/decoder.hpp" #include "../hardware/decoder/decoderfunction.hpp" -#include "../hardware/input/inputs.hpp" -#include "../hardware/output/outputs.hpp" #include "../vehicle/rail/railvehicles.hpp" #include "../train/train.hpp" #ifndef DISABLE_LUA_SCRIPTING @@ -154,10 +151,8 @@ void WorldLoader::createObject(ObjectData& objectData) std::string_view classId = objectData.json["class_id"]; std::string_view id = objectData.json["id"]; - if(startsWith(classId, CommandStations::classIdPrefix)) - objectData.object = CommandStations::create(m_world, classId, id); - else if(startsWith(classId, Controllers::classIdPrefix)) - objectData.object = Controllers::create(m_world, classId, id); + if(startsWith(classId, Interfaces::classIdPrefix)) + objectData.object = Interfaces::create(m_world, classId, id); else if(classId == Decoder::classId) objectData.object = Decoder::create(m_world, id); else if(classId == DecoderFunction::classId) @@ -171,10 +166,10 @@ void WorldLoader::createObject(ObjectData& objectData) decoder->functions->items.appendInternal(f); } } - else if(startsWith(classId, Inputs::classIdPrefix)) - objectData.object = Inputs::create(m_world, classId, id); - else if(startsWith(classId, Outputs::classIdPrefix)) - objectData.object = Outputs::create(m_world, classId, id); + else if(classId == Input::classId) + objectData.object = Input::create(m_world, id); + else if(classId == Output::classId) + objectData.object = Output::create(m_world, id); else if(classId == Board::classId) objectData.object = Board::create(m_world, id); else if(startsWith(classId, Tiles::classIdPrefix)) diff --git a/server/test/objectcreatedestroy.cpp b/server/test/objectcreatedestroy.cpp index c511158c..0dd465a5 100644 --- a/server/test/objectcreatedestroy.cpp +++ b/server/test/objectcreatedestroy.cpp @@ -24,12 +24,7 @@ #include "../src/world/world.hpp" #include "../src/board/board.hpp" -//#include "../src/hardware/commandstation/dccplusplusserial.hpp" -//#include "../src/hardware/commandstation/loconetserial.hpp" -//#include "../src/hardware/commandstation/loconettcpbinary.hpp" -//#include "../src/hardware/commandstation/rocoz21.hpp" -#include "../src/hardware/commandstation/virtualcommandstation.hpp" -//#include "../src/hardware/commandstation/xpressnetserial.hpp" +#include "../src/hardware/interface/loconetinterface.hpp" TEST_CASE("Create world => destroy world", "[object-create-destroy]") { @@ -77,49 +72,39 @@ TEST_CASE("Create world and board => destroy board", "[object-create-destroy]") REQUIRE(worldWeak.expired()); } -TEMPLATE_TEST_CASE("Create world and command station => destroy world", "[object-create-destroy]" - //, DCCPlusPlusSerial - //, LocoNetSerial - //, LocoNetTCPBinary - //, RocoZ21 - , VirtualCommandStation - //, XpressNetSerial +TEMPLATE_TEST_CASE("Create world and interface => destroy world", "[object-create-destroy]" + , LocoNetInterface ) { auto world = World::create(); std::weak_ptr worldWeak = world; REQUIRE_FALSE(worldWeak.expired()); - std::weak_ptr commandStationWeak = world->commandStations->add(TestType::classId); - REQUIRE_FALSE(commandStationWeak.expired()); - REQUIRE(commandStationWeak.lock()->getClassId() == TestType::classId); + std::weak_ptr interfaceWeak = std::dynamic_pointer_cast(world->interfaces->add(TestType::classId)); + REQUIRE_FALSE(interfaceWeak.expired()); + REQUIRE(interfaceWeak.lock()->getClassId() == TestType::classId); world.reset(); - REQUIRE(commandStationWeak.expired()); + REQUIRE(interfaceWeak.expired()); REQUIRE(worldWeak.expired()); } -TEMPLATE_TEST_CASE("Create world and command station => destroy command station", "[object-create-destroy]" - //, DCCPlusPlusSerial - //, LocoNetSerial - //, LocoNetTCPBinary - //, RocoZ21 - , VirtualCommandStation - //, XpressNetSerial +TEMPLATE_TEST_CASE("Create world and interface => destroy interface", "[object-create-destroy]" + , LocoNetInterface ) { auto world = World::create(); std::weak_ptr worldWeak = world; REQUIRE_FALSE(worldWeak.expired()); - REQUIRE(worldWeak.lock()->commandStations->length == 0); + REQUIRE(worldWeak.lock()->interfaces->length == 0); - std::weak_ptr commandStationWeak = world->commandStations->add(TestType::classId); - REQUIRE_FALSE(commandStationWeak.expired()); - REQUIRE(worldWeak.lock()->commandStations->length == 1); + std::weak_ptr interfaceWeak = std::dynamic_pointer_cast(world->interfaces->add(TestType::classId)); + REQUIRE_FALSE(interfaceWeak.expired()); + REQUIRE(worldWeak.lock()->interfaces->length == 1); - world->commandStations->remove(commandStationWeak.lock()); - REQUIRE(commandStationWeak.expired()); - REQUIRE(worldWeak.lock()->commandStations->length == 0); + world->interfaces->remove(interfaceWeak.lock()); + REQUIRE(interfaceWeak.expired()); + REQUIRE(worldWeak.lock()->interfaces->length == 0); world.reset(); REQUIRE(worldWeak.expired()); @@ -216,39 +201,34 @@ TEST_CASE("Create world, decoder and function => destroy function", "[object-cre REQUIRE(worldWeak.expired()); } -TEMPLATE_TEST_CASE("Create world, command station and decoder => destroy command station", "[object-create-destroy]" - //, DCCPlusPlusSerial - //, LocoNetSerial - //, LocoNetTCPBinary - //, RocoZ21 - , VirtualCommandStation - //, XpressNetSerial +TEMPLATE_TEST_CASE("Create world, interface and decoder => destroy interface", "[object-create-destroy]" + , LocoNetInterface ) { auto world = World::create(); std::weak_ptr worldWeak = world; REQUIRE_FALSE(worldWeak.expired()); - REQUIRE(worldWeak.lock()->commandStations->length == 0); + REQUIRE(worldWeak.lock()->interfaces->length == 0); REQUIRE(worldWeak.lock()->decoders->length == 0); - std::weak_ptr commandStationWeak = world->commandStations->add(TestType::classId); - REQUIRE_FALSE(commandStationWeak.expired()); - REQUIRE(worldWeak.lock()->commandStations->length == 1); + std::weak_ptr interfaceWeak = std::dynamic_pointer_cast(world->interfaces->add(TestType::classId)); + REQUIRE_FALSE(interfaceWeak.expired()); + REQUIRE(worldWeak.lock()->interfaces->length == 1); REQUIRE(worldWeak.lock()->decoders->length == 0); - REQUIRE(commandStationWeak.lock()->decoders->length == 0); + REQUIRE(interfaceWeak.lock()->decoders->length == 0); - std::weak_ptr decoderWeak = commandStationWeak.lock()->decoders->add(); + std::weak_ptr decoderWeak = interfaceWeak.lock()->decoders->add(); REQUIRE_FALSE(decoderWeak.expired()); - REQUIRE(decoderWeak.lock()->commandStation.value() == commandStationWeak.lock()); - REQUIRE(worldWeak.lock()->commandStations->length == 1); + REQUIRE(decoderWeak.lock()->interface.value() == std::dynamic_pointer_cast(interfaceWeak.lock())); + REQUIRE(worldWeak.lock()->interfaces->length == 1); REQUIRE(worldWeak.lock()->decoders->length == 1); - REQUIRE(commandStationWeak.lock()->decoders->length == 1); + REQUIRE(interfaceWeak.lock()->decoders->length == 1); - world->commandStations->remove(commandStationWeak.lock()); - REQUIRE(commandStationWeak.expired()); + world->interfaces->remove(interfaceWeak.lock()); + REQUIRE(interfaceWeak.expired()); REQUIRE_FALSE(decoderWeak.expired()); - REQUIRE_FALSE(decoderWeak.lock()->commandStation.value().operator bool()); - REQUIRE(worldWeak.lock()->commandStations->length == 0); + REQUIRE_FALSE(decoderWeak.lock()->interface.value().operator bool()); + REQUIRE(worldWeak.lock()->interfaces->length == 0); REQUIRE(worldWeak.lock()->decoders->length == 1); world.reset(); @@ -256,42 +236,37 @@ TEMPLATE_TEST_CASE("Create world, command station and decoder => destroy command REQUIRE(worldWeak.expired()); } -TEMPLATE_TEST_CASE("Create world, command station and decoder => destroy decoder", "[object-create-destroy]" - //, DCCPlusPlusSerial - //, LocoNetSerial - //, LocoNetTCPBinary - //, RocoZ21 - , VirtualCommandStation - //, XpressNetSerial +TEMPLATE_TEST_CASE("Create world, interface and decoder => destroy decoder", "[object-create-destroy]" + , LocoNetInterface ) { auto world = World::create(); std::weak_ptr worldWeak = world; REQUIRE_FALSE(worldWeak.expired()); - REQUIRE(worldWeak.lock()->commandStations->length == 0); + REQUIRE(worldWeak.lock()->interfaces->length == 0); REQUIRE(worldWeak.lock()->decoders->length == 0); - std::weak_ptr commandStationWeak = world->commandStations->add(TestType::classId); - REQUIRE_FALSE(commandStationWeak.expired()); - REQUIRE(worldWeak.lock()->commandStations->length == 1); + std::weak_ptr interfaceWeak = std::dynamic_pointer_cast(world->interfaces->add(TestType::classId)); + REQUIRE_FALSE(interfaceWeak.expired()); + REQUIRE(worldWeak.lock()->interfaces->length == 1); REQUIRE(worldWeak.lock()->decoders->length == 0); - REQUIRE(commandStationWeak.lock()->decoders->length == 0); + REQUIRE(interfaceWeak.lock()->decoders->length == 0); - std::weak_ptr decoderWeak = commandStationWeak.lock()->decoders->add(); + std::weak_ptr decoderWeak = interfaceWeak.lock()->decoders->add(); REQUIRE_FALSE(decoderWeak.expired()); - REQUIRE(decoderWeak.lock()->commandStation.value() == commandStationWeak.lock()); - REQUIRE(worldWeak.lock()->commandStations->length == 1); + REQUIRE(decoderWeak.lock()->interface.value() == std::dynamic_pointer_cast(interfaceWeak.lock())); + REQUIRE(worldWeak.lock()->interfaces->length == 1); REQUIRE(worldWeak.lock()->decoders->length == 1); - REQUIRE(commandStationWeak.lock()->decoders->length == 1); + REQUIRE(interfaceWeak.lock()->decoders->length == 1); world->decoders->remove(decoderWeak.lock()); - REQUIRE_FALSE(commandStationWeak.expired()); + REQUIRE_FALSE(interfaceWeak.expired()); REQUIRE(decoderWeak.expired()); - REQUIRE(worldWeak.lock()->commandStations->length == 1); + REQUIRE(worldWeak.lock()->interfaces->length == 1); REQUIRE(worldWeak.lock()->decoders->length == 0); - REQUIRE(commandStationWeak.lock()->decoders->length == 0); + REQUIRE(interfaceWeak.lock()->decoders->length == 0); world.reset(); - REQUIRE(commandStationWeak.expired()); + REQUIRE(interfaceWeak.expired()); REQUIRE(worldWeak.expired()); } diff --git a/server/src/hardware/input/inputs.hpp b/shared/src/traintastic/enum/interfacestatus.hpp similarity index 56% rename from server/src/hardware/input/inputs.hpp rename to shared/src/traintastic/enum/interfacestatus.hpp index 66da0287..cbd7f4f1 100644 --- a/server/src/hardware/input/inputs.hpp +++ b/shared/src/traintastic/enum/interfacestatus.hpp @@ -1,9 +1,9 @@ /** - * server/src/hardware/input/inputs.hpp + * shared/src/enum/interfacestatus.hpp * * This file is part of the traintastic source code. * - * Copyright (C) 2019-2021 Reinder Feenstra + * Copyright (C) 2021 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -20,25 +20,28 @@ * 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 +#ifndef TRAINTASTIC_SHARED_TRAINTASTIC_ENUM_INTERFACESTATUS_HPP +#define TRAINTASTIC_SHARED_TRAINTASTIC_ENUM_INTERFACESTATUS_HPP -#include "input.hpp" -#include "../../utils/makearray.hpp" +#include +#include "enum.hpp" -#include "loconetinput.hpp" -#include "xpressnetinput.hpp" - -struct Inputs +enum class InterfaceStatus : uint8_t { - static constexpr std::string_view classIdPrefix = "input."; - - static constexpr auto classList = makeArray( - LocoNetInput::classId, - XpressNetInput::classId - ); - - static std::shared_ptr create(const std::shared_ptr& world, std::string_view classId, std::string_view id); + Offline = 0, + Initializing = 1, + Online = 2, + Error = 255, }; -#endif \ No newline at end of file +ENUM_NAME(InterfaceStatus, "interface_status") + +ENUM_VALUES(InterfaceStatus, 4, +{ + {InterfaceStatus::Offline, "offline"}, + {InterfaceStatus::Initializing, "initializing"}, + {InterfaceStatus::Online, "online"}, + {InterfaceStatus::Error, "error"}, +}) + +#endif diff --git a/shared/translations/en-us.txt b/shared/translations/en-us.txt index 3fb91f0b..a0b9fca3 100644 --- a/shared/translations/en-us.txt +++ b/shared/translations/en-us.txt @@ -121,9 +121,13 @@ direction:reverse=Reverse hardware:address=Address hardware:command_station=Command station +hardware:decoders=Decoders hardware:input_monitor=Input monitor +hardware:inputs=Inputs +hardware:interface=Interface hardware:loconet=LocoNet hardware:output_keyboard=Output keyboard +hardware:outputs=Outputs hardware:speed_steps=Speed steps hardware:xpressnet=XpressNet @@ -133,6 +137,8 @@ input_map_item.block:input=Input input_map_item.block:invert=Invert input_map_item.block:type=Type +interface:status=Status + ip:hostname=Hostname ip:port=Port @@ -433,6 +439,7 @@ world:controllers=Controllers world:decoders=Decoders world:edit=Edit world world:inputs=Inputs +world:interfaces=Interfaces world:lua_scripts=Lua scripts world:mute=Mute world:no_smoke=No smoke