diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2640827e..1ec04cee 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,7 +28,7 @@ jobs: jobs: 2 build_type: Release build_deb: true - defines: "-DDEBIAN_PACKAGE_VERSION_EXTRA=~ubuntu~bionic~$CI_REF_NAME_SLUG~${{github.run_number}}~$CI_SHA_SHORT" + defines: "-DCMAKE_CXX_COMPILER=g++-8 -DDEBIAN_PACKAGE_VERSION_EXTRA=~ubuntu~bionic~$CI_REF_NAME_SLUG~${{github.run_number}}~$CI_SHA_SHORT" - name: "ubuntu_20.04" os: ubuntu-20.04 @@ -98,6 +98,11 @@ jobs: - name: Install packages if: startswith(matrix.config.os, 'ubuntu') run: sudo apt install qt5-default libqt5svg5-dev + + # Ubuntu 18.04 only: + - name: Install g++-8 + if: matrix.config.os == 'ubuntu-18.04' + run: sudo apt install g++-8 # All: - name: Create Build Environment @@ -173,7 +178,7 @@ jobs: jobs: 2 build_type: Release build_deb: true - defines: "-DDEBIAN_PACKAGE_VERSION_EXTRA=~ubuntu~bionic~$CI_REF_NAME_SLUG~${{github.run_number}}~$CI_SHA_SHORT" + defines: "-DCMAKE_CXX_COMPILER=g++-8 -DDEBIAN_PACKAGE_VERSION_EXTRA=~ubuntu~bionic~$CI_REF_NAME_SLUG~${{github.run_number}}~$CI_SHA_SHORT" ccov: false - name: "ubuntu_20.04" @@ -242,6 +247,11 @@ jobs: if: startswith(matrix.config.os, 'ubuntu') run: sudo apt install liblua5.3-dev lcov libarchive-dev + # Ubuntu 18.04 only: + - name: Install g++-8 + if: matrix.config.os == 'ubuntu-18.04' + run: sudo apt install g++-8 + # All: - name: Create Build Environment # Some projects don't allow in-source building, so create a separate build directory 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 f7198dd6..8cad557c 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/utils/enum.cpp b/client/src/utils/enum.cpp index 4abf622d..c886f6be 100644 --- a/client/src/utils/enum.cpp +++ b/client/src/utils/enum.cpp @@ -3,7 +3,7 @@ * * This file is part of the traintastic source code. * - * Copyright (C) 2019-2021 Reinder Feenstra + * Copyright (C) 2019-2022 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -39,7 +40,8 @@ #include #include #include -#include +#include +#include #include "../settings/boardsettings.hpp" #define GET_ENUM_VALUES(_type) \ @@ -73,6 +75,7 @@ QString translateEnum(const QString& enumName, qint64 value) TRANSLATE_ENUM(Direction) TRANSLATE_ENUM(LengthUnit) TRANSLATE_ENUM(LocoNetCommandStation) + TRANSLATE_ENUM(LocoNetInterfaceType) TRANSLATE_ENUM(LocoNetSerialInterface) TRANSLATE_ENUM(OutputAction) TRANSLATE_ENUM(SensorType) @@ -83,7 +86,8 @@ QString translateEnum(const QString& enumName, qint64 value) TRANSLATE_ENUM(WeightUnit) TRANSLATE_ENUM(WorldScale) TRANSLATE_ENUM(XpressNetCommandStation) - TRANSLATE_ENUM(XpressNetSerialInterface) + TRANSLATE_ENUM(XpressNetInterfaceType) + TRANSLATE_ENUM(XpressNetSerialInterfaceType) TRANSLATE_ENUM(BoardSettings::ColorScheme) return enumName + "@" + QString::number(value); } 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 16e302d3..e56eef83 100644 --- a/client/src/widget/objectlist/objectlistwidget.cpp +++ b/client/src/widget/objectlist/objectlistwidget.cpp @@ -3,7 +3,7 @@ * * This file is part of the traintastic source code. * - * Copyright (C) 2019-2021 Reinder Feenstra + * Copyright (C) 2019-2022 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -49,12 +49,16 @@ ObjectListWidget::ObjectListWidget(const ObjectPtr& object, QWidget* parent) : QWidget(parent), + m_requestIdInputMonitor{Connection::invalidRequestId}, + m_requestIdOutputKeyboard{Connection::invalidRequestId}, m_object{object}, m_toolbar{new QToolBar()}, m_buttonAdd{nullptr}, 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/client/src/widget/throttle/throttlewidget.cpp b/client/src/widget/throttle/throttlewidget.cpp index cc44b0b7..0496306d 100644 --- a/client/src/widget/throttle/throttlewidget.cpp +++ b/client/src/widget/throttle/throttlewidget.cpp @@ -91,7 +91,7 @@ ThrottleWidget::ThrottleWidget(ObjectPtr object, QWidget* parent) }); } - if(m_emergencyStop = m_object->getProperty("emergency_stop")) + if((m_emergencyStop = m_object->getProperty("emergency_stop"))) { m_speedoMeter->setEStop(m_emergencyStop->toBool()); connect(m_emergencyStop, &AbstractProperty::valueChangedBool, m_speedoMeter, &SpeedoMeterWidget::setEStop); @@ -100,7 +100,7 @@ ThrottleWidget::ThrottleWidget(ObjectPtr object, QWidget* parent) if(auto* p = dynamic_cast(m_object->getProperty("functions"))) { m_functionsRequestId = m_object->connection()->getObject(p->objectId(), - [this](const ObjectPtr& functions, Message::ErrorCode ec) + [this](const ObjectPtr& functions, Message::ErrorCode /*ec*/) { m_functionsRequestId = Connection::invalidRequestId; @@ -113,7 +113,7 @@ ThrottleWidget::ThrottleWidget(ObjectPtr object, QWidget* parent) for(const QString& id : *items) { const int functionRequestId = functions->connection()->getObject(id, - [this, i](const ObjectPtr& function, Message::ErrorCode ec) + [this, i](const ObjectPtr& function, Message::ErrorCode /*ec*/) { m_functionRequestIds.erase(i); @@ -263,9 +263,7 @@ ThrottleFunctionButton* ThrottleWidget::getFunctionButton(DecoderFunctionFunctio auto it = std::find_if(m_functionButtons.begin(), m_functionButtons.end(), [function](const auto* btn) { - auto n = btn->number(); - auto f = btn->function(); - return f == function; + return btn->function() == function; }); return (it != m_functionButtons.end()) ? *it : nullptr; } diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index 751b4adc..7b10113f 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" @@ -101,12 +99,26 @@ file(GLOB SOURCES "src/hardware/output/map/*.cpp" "src/hardware/protocol/dccplusplus/*.hpp" "src/hardware/protocol/dccplusplus/*.cpp" + "src/hardware/protocol/dccplusplus/iohandler/*.hpp" + "src/hardware/protocol/dccplusplus/iohandler/*.cpp" + "src/hardware/protocol/ecos/*.hpp" + "src/hardware/protocol/ecos/*.cpp" + "src/hardware/protocol/ecos/iohandler/*.hpp" + "src/hardware/protocol/ecos/iohandler/*.cpp" + "src/hardware/protocol/ecos/object/*.hpp" + "src/hardware/protocol/ecos/object/*.cpp" "src/hardware/protocol/loconet/*.hpp" "src/hardware/protocol/loconet/*.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/xpressnet/iohandler/*.hpp" + "src/hardware/protocol/xpressnet/iohandler/*.cpp" "src/hardware/protocol/z21/*.hpp" "src/hardware/protocol/z21/*.cpp" + "src/hardware/protocol/z21/iohandler/*.hpp" + "src/hardware/protocol/z21/iohandler/*.cpp" "src/log/*.hpp" "src/log/*.cpp" "src/train/*.hpp" @@ -212,29 +224,6 @@ else() add_definitions(-DDISABLE_LUA_SCRIPTING) endif() -option(USB_XPRESSNET "USB XpressNet interface/controller support" ON) -message(STATUS "USB XpressNet interface/controller support: ${USB_XPRESSNET}") -if(ENABLE_USB_XPRESSNET_INTERFACE) - #pkg_check_modules(USBXPRESSNET REQUIRED IMPORTED_TARGET usbxpressnet) - find_path(USBXPRESSNET_INCLUDE_DIR usbxpressnet.h) - #message(${USBXPRESSNET_INCLUDE_DIR}) - find_library(USBXPRESSNET_LIB usbxpressnet) - #message(${USBXPRESSNET_LIB}) - #if(NOT USBXPRESSNET_LIB) - # message(FATAL_ERROR "usbxpressnet library not found") - #endif() - - add_definitions(-DUSB_XPRESSNET) - - target_include_directories(traintastic-server PRIVATE ${USBXPRESSNET_INCLUDE_DIR}) - target_link_libraries(traintastic-server PRIVATE ${USBXPRESSNET_LIB}) - - target_include_directories(traintastic-server-test PRIVATE ${USBXPRESSNET_INCLUDE_DIR}) - target_link_libraries(traintastic-server-test PRIVATE ${USBXPRESSNET_LIB}) -else() - list(FILTER SOURCES EXCLUDE REGEX ".*usbxpressnet(interface|controller)\.(hpp|cpp)$") -endif() - ### OPTIONS END ### target_sources(traintastic-server PRIVATE ${SOURCES}) diff --git a/server/simulate/ecossimulator.py b/server/simulate/ecossimulator.py new file mode 100755 index 00000000..1e2a7d5e --- /dev/null +++ b/server/simulate/ecossimulator.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 + +import socket +import re + +ADDRESS = '127.0.0.1' +PORT = 15471 + +OBJECTID_ECOS = 1 + + +def process(request): + print('RX:\n' + request) + m = re.match(r'^(queryObjects|set|get|create|delete|request|release)\(\s*([0-9]+)\s*(.*)\)$', request) + if m is not None: + command = m.group(1) + objectId = int(m.group(2)) + args = [s for s in re.split(r'\s*,\s*', m.group(3)) if s] + + if objectId == OBJECTID_ECOS: + if command == 'get': + if args[0] == 'info': + return \ + '\n' + \ + '1 ECoS\n' + \ + '1 ProtocolVersion[0.1]\n' + \ + '1 ApplicationVersion[1.0.1]\n' + \ + '1 HardwareVersion[1.3]\n' + \ + '\n' + + return None + + +print('Traintastic ECoS simulator') +with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind((ADDRESS, PORT)) + s.listen() + print('Listening at {:s}:{:d}'.format(ADDRESS, PORT)) + conn, addr = s.accept() + with conn: + print('Connection from {:s}:{:d}'.format(addr[0], addr[1])) + while True: + try: + for request in conn.recv(1024).decode('utf-8').split('\n'): + if request != '': + response = process(request) + if response is not None: + print('TX:\n' + response) + conn.sendall(response.encode('utf-8')) + except ConnectionError: + print('Connection lost') + break diff --git a/server/src/core/attributes.hpp b/server/src/core/attributes.hpp index 80ecd9b1..0d848886 100644 --- a/server/src/core/attributes.hpp +++ b/server/src/core/attributes.hpp @@ -62,6 +62,12 @@ struct Attributes item.setAttribute(AttributeName::Enabled, value); } + static inline void setEnabled(std::initializer_list> items, bool value) + { + for(auto& item : items) + item.get().setAttribute(AttributeName::Enabled, value); + } + template static inline void addMinMax(Property& property, T min, T max) { @@ -106,6 +112,22 @@ struct Attributes property.setAttribute(AttributeName::Max, convertUnit(value, unit, property.unit())); } + template + static inline void setMinMax(Property& property, T min, T max) + { + static_assert(std::is_integral_v || std::is_floating_point_v); + property.setAttribute(AttributeName::Min, min); + property.setAttribute(AttributeName::Max, max); + } + + template + static inline void setMinMax(UnitProperty& property, T min, T max, Unit unit) + { + static_assert(std::is_floating_point_v); + property.setAttribute(AttributeName::Min, convertUnit(min, unit, property.unit())); + property.setAttribute(AttributeName::Max, convertUnit(max, unit, property.unit())); + } + static inline void addVisible(InterfaceItem& item, bool value) { item.addAttribute(AttributeName::Visible, value); 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..aa43cb89 --- /dev/null +++ b/server/src/core/controllerlistbase.cpp @@ -0,0 +1,85 @@ +/** + * server/src/core/controllerlistbase.cpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021-2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "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); +} + +ObjectPtr ControllerListBase::getObject(uint32_t index) +{ + assert(index < m_items.size()); + return std::static_pointer_cast(m_items[index]); +} + +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..8dd2bec7 --- /dev/null +++ b/server/src/core/controllerlistbase.hpp @@ -0,0 +1,61 @@ +/** + * 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; + + ObjectPtr getObject(uint32_t index) final; +}; + +#endif diff --git a/server/src/hardware/protocol/xpressnet/xpressnetlisttablemodel.cpp b/server/src/core/controllerlistbasetablemodel.cpp similarity index 50% rename from server/src/hardware/protocol/xpressnet/xpressnetlisttablemodel.cpp rename to server/src/core/controllerlistbasetablemodel.cpp index d842ff0f..5e9a53a2 100644 --- a/server/src/hardware/protocol/xpressnet/xpressnetlisttablemodel.cpp +++ b/server/src/core/controllerlistbasetablemodel.cpp @@ -1,5 +1,5 @@ /** - * server/src/hardware/input/xpressnetlisttablemodel.cpp + * server/src/core/controllerlistbasetablemodel.cpp * * This file is part of the traintastic source code. * @@ -20,35 +20,48 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "xpressnetlisttablemodel.hpp" -#include "xpressnetlist.hpp" -#include "../../../utils/displayname.hpp" +#include "controllerlistbasetablemodel.hpp" +#include "controllerlistbase.hpp" +#include "../utils/displayname.hpp" constexpr uint32_t columnId = 0; +constexpr uint32_t columnName = 1; -bool XpressNetListTableModel::isListedProperty(const std::string& name) +bool ControllerListBaseTableModel::isListedProperty(const std::string& name) { - return name == "id"; + return + name == "id" || + name == "name"; } -XpressNetListTableModel::XpressNetListTableModel(XpressNetList& list) : - ObjectListTableModel(list) +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::id, + DisplayName::Object::name, }); } -std::string XpressNetListTableModel::getText(uint32_t column, uint32_t row) const +std::string ControllerListBaseTableModel::getText(uint32_t column, uint32_t row) const { if(row < rowCount()) { - const XpressNet::XpressNet& xpressnet = getItem(row); + const Object& object = *m_list->m_items[row]; switch(column) { case columnId: - return xpressnet.getObjectId(); + return object.getObjectId(); + + case columnName: + if(const auto* property = object.getProperty("name")) + return property->toString(); + break; default: assert(false); @@ -59,8 +72,10 @@ std::string XpressNetListTableModel::getText(uint32_t column, uint32_t row) cons return ""; } -void XpressNetListTableModel::propertyChanged(BaseProperty& property, uint32_t row) +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/protocol/xpressnet/xpressnetlisttablemodel.hpp b/server/src/core/controllerlistbasetablemodel.hpp similarity index 61% rename from server/src/hardware/protocol/xpressnet/xpressnetlisttablemodel.hpp rename to server/src/core/controllerlistbasetablemodel.hpp index 6b9dd477..ec20f191 100644 --- a/server/src/hardware/protocol/xpressnet/xpressnetlisttablemodel.hpp +++ b/server/src/core/controllerlistbasetablemodel.hpp @@ -1,5 +1,5 @@ /** - * server/src/hardware/protocol/xpressnet/xpressnetlisttablemodel.hpp + * server/src/core/controllerlistbasetablemodel.hpp * * This file is part of the traintastic source code. * @@ -20,27 +20,28 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_XPRESSNET_XPRESSNETLISTTABLEMODEL_HPP -#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_XPRESSNET_XPRESSNETLISTTABLEMODEL_HPP +#ifndef TRAINTASTIC_SERVER_CORE_CONTROLLERLISTBASETABLEMODEL_HPP +#define TRAINTASTIC_SERVER_CORE_CONTROLLERLISTBASETABLEMODEL_HPP -#include "../../../core/objectlisttablemodel.hpp" -#include "xpressnet.hpp" +#include "tablemodel.hpp" -class XpressNetList; +class ControllerListBase; -class XpressNetListTableModel : public ObjectListTableModel +class ControllerListBaseTableModel final : public TableModel { - friend class XpressNetList; + CLASS_ID("table_model.controller_list") - protected: - void propertyChanged(BaseProperty& property, uint32_t row) final; + friend class ControllerListBase; + + private: + std::shared_ptr m_list; + + void propertyChanged(BaseProperty& property, uint32_t row); public: - CLASS_ID("xpressnet_list_table_model") - static bool isListedProperty(const std::string& name); - XpressNetListTableModel(XpressNetList& list); + ControllerListBaseTableModel(ControllerListBase& list); std::string getText(uint32_t column, uint32_t row) const final; }; diff --git a/server/src/core/object.cpp b/server/src/core/object.cpp index 6559d848..90f1f436 100644 --- a/server/src/core/object.cpp +++ b/server/src/core/object.cpp @@ -45,21 +45,41 @@ void Object::destroy() } } +const InterfaceItem* Object::getItem(std::string_view name) const +{ + return m_interfaceItems.find(name); +} + InterfaceItem* Object::getItem(std::string_view name) { return m_interfaceItems.find(name); } +const AbstractMethod* Object::getMethod(std::string_view name) const +{ + return dynamic_cast(getItem(name)); +} + AbstractMethod* Object::getMethod(std::string_view name) { return dynamic_cast(getItem(name)); } +const AbstractProperty* Object::getProperty(std::string_view name) const +{ + return dynamic_cast(getItem(name)); +} + AbstractProperty* Object::getProperty(std::string_view name) { return dynamic_cast(getItem(name)); } +const AbstractVectorProperty* Object::getVectorProperty(std::string_view name) const +{ + return dynamic_cast(getItem(name)); +} + AbstractVectorProperty* Object::getVectorProperty(std::string_view name) { return dynamic_cast(getItem(name)); diff --git a/server/src/core/object.hpp b/server/src/core/object.hpp index 2a0b5875..d7854852 100644 --- a/server/src/core/object.hpp +++ b/server/src/core/object.hpp @@ -99,9 +99,13 @@ class Object : public std::enable_shared_from_this const InterfaceItems& interfaceItems() const { return m_interfaceItems; } + const InterfaceItem* getItem(std::string_view name) const; InterfaceItem* getItem(std::string_view name); + const AbstractMethod* getMethod(std::string_view name) const; AbstractMethod* getMethod(std::string_view name); + const AbstractProperty* getProperty(std::string_view name) const; AbstractProperty* getProperty(std::string_view name); + const AbstractVectorProperty* getVectorProperty(std::string_view name) const; AbstractVectorProperty* getVectorProperty(std::string_view name); }; 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 01876e29..f14eb129 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/enum/interfacestatus.hpp b/server/src/enum/interfacestatus.hpp new file mode 100644 index 00000000..abe04307 --- /dev/null +++ b/server/src/enum/interfacestatus.hpp @@ -0,0 +1,35 @@ +/** + * server/src/enum/interfacestatus.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_ENUM_INTERFACESTATUS_HPP +#define TRAINTASTIC_SERVER_ENUM_INTERFACESTATUS_HPP + +#include + +inline constexpr std::array interfaceStatusValues{{ + InterfaceStatus::Offline, + InterfaceStatus::Initializing, + InterfaceStatus::Online, + InterfaceStatus::Error, +}}; + +#endif diff --git a/server/src/enum/loconetinterfacetype.hpp b/server/src/enum/loconetinterfacetype.hpp new file mode 100644 index 00000000..2f5a2604 --- /dev/null +++ b/server/src/enum/loconetinterfacetype.hpp @@ -0,0 +1,49 @@ +/** + * server/src/enum/loconetinterface.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_ENUM_LOCONETINTERFACETYPE_HPP +#define TRAINTASTIC_SERVER_ENUM_LOCONETINTERFACETYPE_HPP + +#include +#include + +inline constexpr std::array locoNetInterfaceTypeValues{{ + LocoNetInterfaceType::Serial, + LocoNetInterfaceType::TCPBinary, + LocoNetInterfaceType::LBServer, + LocoNetInterfaceType::Z21, +}}; + +constexpr bool isSerial(LocoNetInterfaceType value) +{ + return (value == LocoNetInterfaceType::Serial); +} + +constexpr bool isNetwork(LocoNetInterfaceType value) +{ + return + value == LocoNetInterfaceType::TCPBinary || + value == LocoNetInterfaceType::LBServer || + value == LocoNetInterfaceType::Z21; +} + +#endif diff --git a/server/src/enum/xpressnetinterfacetype.hpp b/server/src/enum/xpressnetinterfacetype.hpp new file mode 100644 index 00000000..25d2566b --- /dev/null +++ b/server/src/enum/xpressnetinterfacetype.hpp @@ -0,0 +1,34 @@ +/** + * server/src/enum/xpressnetinterface.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_ENUM_XPRESSNETINTERFACETYPE_HPP +#define TRAINTASTIC_SERVER_ENUM_XPRESSNETINTERFACETYPE_HPP + +#include +#include + +inline constexpr std::array xpressNetInterfaceTypeValues{{ + XpressNetInterfaceType::Serial, + XpressNetInterfaceType::Network, +}}; + +#endif diff --git a/server/src/enum/xpressnetserialinterface.hpp b/server/src/enum/xpressnetserialinterface.hpp deleted file mode 100644 index 132bead0..00000000 --- a/server/src/enum/xpressnetserialinterface.hpp +++ /dev/null @@ -1,37 +0,0 @@ -/** - * server/src/enum/xpressnetserialinterface.hpp - * - * This file is part of the traintastic source code. - * - * Copyright (C) 2019-2020 Reinder Feenstra - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef TRAINTASTIC_SERVER_ENUM_XPRESSNETSERIALINTERFACE_HPP -#define TRAINTASTIC_SERVER_ENUM_XPRESSNETSERIALINTERFACE_HPP - -#include -#include - -inline constexpr std::array XpressNetSerialInterfaceValues{{ - XpressNetSerialInterface::Custom, - XpressNetSerialInterface::LenzLI100, - XpressNetSerialInterface::LenzLI100F, - XpressNetSerialInterface::LenzLI101F, - XpressNetSerialInterface::RoSoftS88XPressNetLI, -}}; - -#endif diff --git a/server/src/hardware/input/inputs.hpp b/server/src/enum/xpressnetserialinterfacetype.hpp similarity index 56% rename from server/src/hardware/input/inputs.hpp rename to server/src/enum/xpressnetserialinterfacetype.hpp index 66da0287..83dde7f5 100644 --- a/server/src/hardware/input/inputs.hpp +++ b/server/src/enum/xpressnetserialinterfacetype.hpp @@ -1,5 +1,5 @@ /** - * server/src/hardware/input/inputs.hpp + * server/src/enum/xpressnetserialinterfacetype.hpp * * This file is part of the traintastic source code. * @@ -20,25 +20,19 @@ * 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_SERVER_ENUM_XPRESSNETSERIALINTERFACETYPE_HPP +#define TRAINTASTIC_SERVER_ENUM_XPRESSNETSERIALINTERFACETYPE_HPP -#include "input.hpp" -#include "../../utils/makearray.hpp" +#include +#include -#include "loconetinput.hpp" -#include "xpressnetinput.hpp" +constexpr std::array XpressNetSerialInterfaceTypeValues{{ + XpressNetSerialInterfaceType::DigikeijsDR5000, + XpressNetSerialInterfaceType::LenzLI100, + XpressNetSerialInterfaceType::LenzLI100F, + XpressNetSerialInterfaceType::LenzLI101F, + XpressNetSerialInterfaceType::LenzLIUSB, + XpressNetSerialInterfaceType::RoSoftS88XPressNetLI, +}}; -struct Inputs -{ - 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); -}; - -#endif \ No newline at end of file +#endif diff --git a/server/src/hardware/commandstation/commandstation.cpp b/server/src/hardware/commandstation/commandstation.cpp deleted file mode 100644 index bafc8254..00000000 --- a/server/src/hardware/commandstation/commandstation.cpp +++ /dev/null @@ -1,204 +0,0 @@ -/** - * server/src/hardware/commandstation/commandstation.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 "commandstation.hpp" -#include "commandstationlist.hpp" -#include "commandstationlisttablemodel.hpp" -#include -#include "../../world/world.hpp" -#include "../decoder/decoder.hpp" -#include "../decoder/decoderlist.hpp" -#include "../../core/attributes.hpp" -#include "../../utils/displayname.hpp" -#include "../../utils/almostzero.hpp" - -CommandStation::CommandStation(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, - [this](bool value) - { - Attributes::setEnabled(emergencyStop, value); - Attributes::setEnabled(powerOn, value); - }, - std::bind(&CommandStation::setOnline, this, std::placeholders::_1)}, - //status{this, "status", CommandStationStatus::Offline, PropertyFlags::ReadOnly}, - emergencyStop{this, "emergency_stop", true, PropertyFlags::ReadWrite | PropertyFlags::NoStore, - [this](bool value) - { - emergencyStopChanged(value); - }}, - powerOn{this, "power_on", false, PropertyFlags::ReadWrite | PropertyFlags::NoStore, - [this](bool value) - { - powerOnChanged(value); - }}, - decoders{this, "decoders", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject}, - controllers{this, "controllers", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject}, - notes{this, "notes", "", PropertyFlags::ReadWrite | PropertyFlags::Store} -{ - decoders.setValueInternal(std::make_shared(*this, decoders.name())); - controllers.setValueInternal(std::make_shared(*this, controllers.name())); - - 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(emergencyStop, DisplayName::CommandStation::emergencyStop); - Attributes::addEnabled(emergencyStop, online); - Attributes::addObjectEditor(emergencyStop, false); - m_interfaceItems.insertBefore(emergencyStop, notes); - - Attributes::addDisplayName(powerOn, DisplayName::CommandStation::powerOn); - Attributes::addEnabled(powerOn, online); - Attributes::addObjectEditor(powerOn, false); - m_interfaceItems.insertBefore(powerOn, notes); - - Attributes::addDisplayName(decoders, DisplayName::World::decoders); - m_interfaceItems.add(decoders); - - Attributes::addDisplayName(controllers, DisplayName::World::controllers); - m_interfaceItems.add(controllers); - - Attributes::addDisplayName(notes, DisplayName::Object::notes); - m_interfaceItems.add(notes); -} - -void CommandStation::loaded() -{ - IdObject::loaded(); - - checkAllDecoders(); -} - -void CommandStation::addToWorld() -{ - IdObject::addToWorld(); - - if(auto world = m_world.lock()) - world->commandStations->addObject(shared_ptr()); -} - -void CommandStation::destroying() -{ - for(const auto& decoder : *decoders) - { - assert(decoder->commandStation.value() == shared_ptr()); - decoder->commandStation = nullptr; - } - if(auto world = m_world.lock()) - world->commandStations->removeObject(shared_ptr()); - IdObject::destroying(); -} - -void CommandStation::worldEvent(WorldState state, WorldEvent event) -{ - IdObject::worldEvent(state, event); - - Attributes::setEnabled(name, contains(state, WorldState::Edit)); - - try - { - switch(event) - { - case WorldEvent::Offline: - online = false; - break; - - case WorldEvent::Online: - online = true; - break; - - case WorldEvent::PowerOff: - powerOn = false; - break; - - case WorldEvent::PowerOn: - powerOn = true; - if(!emergencyStop) - restoreSpeed(); - break; - - case WorldEvent::Stop: - emergencyStop = true; - break; - - case WorldEvent::Run: - emergencyStop = false; - if(powerOn) - restoreSpeed(); - break; - - default: - break; - } - } - catch(...) - { - } -} - -const std::shared_ptr<::Decoder>& CommandStation::getDecoder(DecoderProtocol protocol, uint16_t address, bool longAddress) const -{ - auto it = std::find_if(decoders->begin(), decoders->end(), [=](auto& decoder){ return decoder->protocol.value() == protocol && decoder->address.value() == address && decoder->longAddress == longAddress; }); - if(it != decoders->end()) - return *it; - return Decoder::null; -} - -void CommandStation::emergencyStopChanged(bool value) -{ - for(auto& controller : *controllers) - controller->emergencyStopChanged(value); -} - -void CommandStation::powerOnChanged(bool value) -{ - for(auto& controller : *controllers) - controller->powerOnChanged(value); -} - -void CommandStation::checkAllDecoders() const -{ - for(const auto& decoder : *decoders) - checkDecoder(*decoder); -} - -void CommandStation::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber) -{ - for(auto& controller : *controllers) - controller->decoderChanged(decoder, changes, functionNumber); -} - -//! \brief restore speed of all decoders that are not (emergency) stopped -void CommandStation::restoreSpeed() -{ - for(const auto& decoder : *decoders) - if(!decoder->emergencyStop && !almostZero(decoder->throttle.value())) - decoderChanged(*decoder, DecoderChangeFlags::Throttle, 0); -} diff --git a/server/src/hardware/commandstation/commandstation.hpp b/server/src/hardware/commandstation/commandstation.hpp deleted file mode 100644 index 62373af7..00000000 --- a/server/src/hardware/commandstation/commandstation.hpp +++ /dev/null @@ -1,68 +0,0 @@ -/** - * server/src/hardware/commandstation/commandstation.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_COMMANDSTATION_COMMANDSTATION_HPP -#define TRAINTASTIC_SERVER_HARDWARE_COMMANDSTATION_COMMANDSTATION_HPP - -#include "../../core/idobject.hpp" -#include "../../core/objectproperty.hpp" -#include "../../enum/commandstationstatus.hpp" -#include "../decoder/decoderlist.hpp" -#include "../controller/controllerlist.hpp" - -class Decoder; -enum class DecoderChangeFlags; - -class CommandStation : public IdObject -{ - friend class ::Decoder; - - protected: - void loaded() override; - void addToWorld() final; - void destroying() override; - void worldEvent(WorldState state, WorldEvent event) override; - - virtual bool setOnline(bool& value) = 0; - virtual void emergencyStopChanged(bool value); - virtual void powerOnChanged(bool value); - void checkAllDecoders() const; - virtual void checkDecoder(const Decoder& /*decoder*/) const {} - virtual void decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber); - - void restoreSpeed(); - - public: - CommandStation(const std::weak_ptr& world, std::string_view _id); - - Property name; - Property online; - Property emergencyStop; - Property powerOn; - ObjectProperty decoders; - ObjectProperty controllers; - Property notes; - - const std::shared_ptr& getDecoder(DecoderProtocol protocol, uint16_t address, bool longAddress = false) const; -}; - -#endif diff --git a/server/src/hardware/commandstation/commandstationlist.hpp b/server/src/hardware/commandstation/commandstationlist.hpp deleted file mode 100644 index 4a4c6cc7..00000000 --- a/server/src/hardware/commandstation/commandstationlist.hpp +++ /dev/null @@ -1,46 +0,0 @@ -/** - * server/src/hardware/commandstation/commandstationlist.hpp - * - * This file is part of the traintastic source code. - * - * Copyright (C) 2019-2020 Reinder Feenstra - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef TRAINTASTIC_SERVER_HARDWARE_COMMANDSTATION_COMMANDSTATIONLIST_HPP -#define TRAINTASTIC_SERVER_HARDWARE_COMMANDSTATION_COMMANDSTATIONLIST_HPP - -#include "../../core/objectlist.hpp" -#include "commandstation.hpp" - -class CommandStationList : public ObjectList -{ - protected: - void worldEvent(WorldState state, WorldEvent event) final; - bool isListedProperty(const std::string& name) final; - - public: - CLASS_ID("command_station_list") - - Method(std::string_view)> add; - Method&)> remove; - - CommandStationList(Object& _parent, const std::string& parentPropertyName); - - TableModelPtr getModel() final; -}; - -#endif diff --git a/server/src/hardware/commandstation/commandstationlisttablemodel.cpp b/server/src/hardware/commandstation/commandstationlisttablemodel.cpp deleted file mode 100644 index d7a01446..00000000 --- a/server/src/hardware/commandstation/commandstationlisttablemodel.cpp +++ /dev/null @@ -1,100 +0,0 @@ -/** - * server/src/core/ - * - * 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 "commandstationlisttablemodel.hpp" -#include "commandstationlist.hpp" -#include "../../utils/utf8.hpp" -#include "../../utils/displayname.hpp" - -constexpr uint32_t columnId = 0; -constexpr uint32_t columnName = 1; -constexpr uint32_t columnOnline = 2; -constexpr uint32_t columnEmergencyStop = 3; -constexpr uint32_t columnTrackPower = 4; - -bool CommandStationListTableModel::isListedProperty(const std::string& name) -{ - return - name == "id" || - name == "name" || - name == "online" || - name == "emergency_stop" || - name == "power_on"; -} - -CommandStationListTableModel::CommandStationListTableModel(CommandStationList& list) : - ObjectListTableModel(list) -{ - setColumnHeaders({ - DisplayName::Object::id, - DisplayName::Object::name, - DisplayName::CommandStation::online, - DisplayName::CommandStation::emergencyStop, - DisplayName::CommandStation::powerOn, - }); -} - -std::string CommandStationListTableModel::getText(uint32_t column, uint32_t row) const -{ - if(row < rowCount()) - { - const CommandStation& cs = getItem(row); - - switch(column) - { - case columnId: - return cs.id; - - case columnName: - return cs.name; - - case columnOnline: - return cs.online ? "\u2022" : ""; - - case columnEmergencyStop: - return cs.emergencyStop ? "\u2022" : ""; - - case columnTrackPower: - return cs.powerOn ? UTF8_CHECKMARK : UTF8_BALLOT_X; - - default: - assert(false); - break; - } - } - - return ""; -} - -void CommandStationListTableModel::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() == "online") - changed(row, columnOnline); - else if(property.name() == "emergency_stop") - changed(row, columnEmergencyStop); - else if(property.name() == "track_voltage_off") - changed(row, columnTrackPower); -} diff --git a/server/src/hardware/commandstation/commandstationlisttablemodel.hpp b/server/src/hardware/commandstation/commandstationlisttablemodel.hpp deleted file mode 100644 index 1b96dbd1..00000000 --- a/server/src/hardware/commandstation/commandstationlisttablemodel.hpp +++ /dev/null @@ -1,48 +0,0 @@ -/** - * server/src/hardware/commandstation/commandstationlisttablemodel.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_COMMANDSTATION_COMMANDSTATIONLISTTABLEMODEL_HPP -#define TRAINTASTIC_SERVER_HARDWARE_COMMANDSTATION_COMMANDSTATIONLISTTABLEMODEL_HPP - -#include "../../core/objectlisttablemodel.hpp" -#include "commandstationlist.hpp" - -class CommandStationList; - -class CommandStationListTableModel : public ObjectListTableModel -{ - friend class CommandStationList; - - protected: - void propertyChanged(BaseProperty& property, uint32_t row) final; - - public: - CLASS_ID("command_station_list_table_model") - - static bool isListedProperty(const std::string& name); - - CommandStationListTableModel(CommandStationList& commandStationList); - - std::string getText(uint32_t column, uint32_t row) const final; -}; - -#endif diff --git a/server/src/hardware/commandstation/commandstations.cpp b/server/src/hardware/commandstation/commandstations.cpp deleted file mode 100644 index ebacdb1e..00000000 --- a/server/src/hardware/commandstation/commandstations.cpp +++ /dev/null @@ -1,45 +0,0 @@ -/** - * server/src/hardware/commandstation/commandstations.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 "commandstations.hpp" - -std::shared_ptr CommandStations::create(const std::weak_ptr& world, std::string_view classId, std::string_view id) -{ - if(classId == DCCPlusPlusSerial::classId) - return DCCPlusPlusSerial::create(world, id); - if(classId == LocoNetSerial::classId) - return LocoNetSerial::create(world, id); - else if(classId == LocoNetTCPBinary::classId) - return LocoNetTCPBinary::create(world, id); -#ifdef USB_XPRESSNET - else if(classId == USBXpressNetInterface::classId) - return USBXpressNetInterface::create(world, id); -#endif - else if(classId == XpressNetSerial::classId) - return XpressNetSerial::create(world, id); - else if(classId == RocoZ21::classId) - return RocoZ21::create(world, id); - else if(classId == VirtualCommandStation::classId) - return VirtualCommandStation::create(world, id); - else - return std::shared_ptr(); -} diff --git a/server/src/hardware/commandstation/commandstations.hpp b/server/src/hardware/commandstation/commandstations.hpp deleted file mode 100644 index 6314d51d..00000000 --- a/server/src/hardware/commandstation/commandstations.hpp +++ /dev/null @@ -1,58 +0,0 @@ -/** - * server/src/hardware/commandstation/create.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_COMMANDSTATION_COMMANDSTATIONS_HPP -#define TRAINTASTIC_SERVER_HARDWARE_COMMANDSTATION_COMMANDSTATIONS_HPP - -#include "commandstation.hpp" -#include "../../utils/makearray.hpp" - -#include "dccplusplusserial.hpp" -#include "loconetserial.hpp" -#include "loconettcpbinary.hpp" -#ifdef USB_XPRESSNET - #include "usbxpressnetinterface.hpp" -#endif -#include "xpressnetserial.hpp" -#include "rocoz21.hpp" -#include "virtualcommandstation.hpp" - -struct CommandStations -{ - static constexpr std::string_view classIdPrefix = "command_station."; - - static constexpr auto classList = makeArray( - DCCPlusPlusSerial::classId, - LocoNetSerial::classId, - LocoNetTCPBinary::classId, -#ifdef USB_XPRESSNET - USBXpressNetInterface::classId, -#endif - XpressNetSerial::classId, - RocoZ21::classId, - VirtualCommandStation::classId - ); - - static std::shared_ptr create(const std::weak_ptr& world, std::string_view classId, std::string_view id); -}; - -#endif diff --git a/server/src/hardware/commandstation/dccplusplusserial.cpp b/server/src/hardware/commandstation/dccplusplusserial.cpp deleted file mode 100644 index 9d172bd9..00000000 --- a/server/src/hardware/commandstation/dccplusplusserial.cpp +++ /dev/null @@ -1,133 +0,0 @@ -/** - * server/src/hardware/commandstation/dccplusplusserial.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 "dccplusplusserial.hpp" -#include "../protocol/dccplusplus/messages.hpp" -#include "../../core/eventloop.hpp" -#include "../../log/log.hpp" - -DCCPlusPlusSerial::DCCPlusPlusSerial(const std::weak_ptr& world, std::string_view _id) : - SerialCommandStation(world, _id), - dccPlusPlus{this, "dcc_plus_plus", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject} -{ - name = "DCC++ (serial)"; - baudrate = 115200; - dccPlusPlus.setValueInternal(std::make_shared(*this, dccPlusPlus.name(), std::bind(&DCCPlusPlusSerial::send, this, std::placeholders::_1))); - - m_interfaceItems.insertBefore(dccPlusPlus, notes); -} - -void DCCPlusPlusSerial::emergencyStopChanged(bool value) -{ - CommandStation::emergencyStopChanged(value); - - if(online) - dccPlusPlus->emergencyStopChanged(value); -} - -void DCCPlusPlusSerial::powerOnChanged(bool value) -{ - CommandStation::powerOnChanged(value); - - if(online) - dccPlusPlus->powerOnChanged(value); -} - -void DCCPlusPlusSerial::checkDecoder(const Decoder& decoder) const -{ - CommandStation::checkDecoder(decoder); - dccPlusPlus->checkDecoder(decoder); -} - -void DCCPlusPlusSerial::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber) -{ - CommandStation::decoderChanged(decoder, changes, functionNumber); - - if(online) - dccPlusPlus->decoderChanged(decoder, changes, functionNumber); -} - -bool DCCPlusPlusSerial::send(std::string_view message) -{ - if(!m_serialPort.is_open()) - return false; - - // for now a blocking implementation, will be replaced by async. - - boost::system::error_code ec; - size_t todo = message.size(); - while(todo > 0) - { - todo -= m_serialPort.write_some(boost::asio::buffer(message.data(), message.size()), ec); - if(ec) - { - Log::log(*this, LogMessage::E2001_SERIAL_WRITE_FAILED_X, ec); - return false; - } - } - return true; -} - -void DCCPlusPlusSerial::started() -{ - send(DCCPlusPlus::Ex::setSpeedSteps(dccPlusPlus->speedSteps)); -} - -void DCCPlusPlusSerial::read() -{ - m_serialPort.async_read_some(boost::asio::buffer(m_readBuffer.data() + m_readBufferOffset, m_readBuffer.size() - m_readBufferOffset), - [this](const boost::system::error_code& ec, std::size_t bytesTransferred) - { - if(!ec) - { - const char* pos = reinterpret_cast(m_readBuffer.data()); - bytesTransferred += m_readBufferOffset; - - size_t i = 0; - while(i < bytesTransferred) - { - if(*(pos + i) == '\n') - { - dccPlusPlus->receive(std::string_view{pos, i + 1}); - pos += i + 1; - bytesTransferred -= i + 1; - i = 0; - } - else - i++; - } - - if(bytesTransferred != 0) - memmove(m_readBuffer.data(), pos, bytesTransferred); - m_readBufferOffset = bytesTransferred; - - read(); - } - else - EventLoop::call( - [this, ec]() - { - Log::log(*this, LogMessage::E2002_SERIAL_READ_FAILED_X, ec); - online = false; - }); - }); -} diff --git a/server/src/hardware/commandstation/loconetserial.cpp b/server/src/hardware/commandstation/loconetserial.cpp deleted file mode 100644 index 054fccb8..00000000 --- a/server/src/hardware/commandstation/loconetserial.cpp +++ /dev/null @@ -1,162 +0,0 @@ -/** - * hardware/commandstation/loconetserial.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 "loconetserial.hpp" -#include "../../core/traintastic.hpp" -#include "../../core/eventloop.hpp" -#include "../../core/attributes.hpp" -#include "../../log/log.hpp" -#include "../../utils/displayname.hpp" - -LocoNetSerial::LocoNetSerial(const std::weak_ptr& world, std::string_view _id) : - SerialCommandStation(world, _id), - interface{this, "interface", LocoNetSerialInterface::Custom, PropertyFlags::ReadWrite | PropertyFlags::Store, - [this](LocoNetSerialInterface value) - { - switch(value) - { - case LocoNetSerialInterface::Custom: - break; - - case LocoNetSerialInterface::DigikeijsDR5000: - baudrate = 115200; - flowControl = SerialFlowControl::Hardware; - break; - - case LocoNetSerialInterface::RoSoftLocoNetInterface: - baudrate = 19200; - flowControl = SerialFlowControl::Hardware; - break; - } - }}, - loconet{this, "loconet", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject} -{ - name = "LocoNet (serial)"; - loconet.setValueInternal(LocoNet::LocoNet::create(*this, loconet.name(), std::bind(&LocoNetSerial::send, this, std::placeholders::_1))); - - Attributes::addEnabled(interface, !online); - Attributes::addValues(interface, LocoNetSerialInterfaceValues); - m_interfaceItems.insertBefore(interface, baudrate); - - Attributes::addDisplayName(loconet, DisplayName::Hardware::loconet); - m_interfaceItems.insertBefore(loconet, notes); -} - -void LocoNetSerial::emergencyStopChanged(bool value) -{ - CommandStation::emergencyStopChanged(value); - - if(online) - loconet->emergencyStopChanged(value); -} - -void LocoNetSerial::powerOnChanged(bool value) -{ - CommandStation::powerOnChanged(value); - - if(online) - loconet->powerOnChanged(value); -} - -void LocoNetSerial::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber) -{ - CommandStation::decoderChanged(decoder, changes, functionNumber); - - if(online) - loconet->decoderChanged(decoder, changes, functionNumber); -} - -bool LocoNetSerial::send(const LocoNet::Message& message) -{ - if(!m_serialPort.is_open()) - return false; - boost::system::error_code ec; - m_serialPort.write_some(boost::asio::buffer(static_cast(&message), message.size()), ec); // TODO async - if(ec) - { - Log::log(*this, LogMessage::E2001_SERIAL_WRITE_FAILED_X, ec); - return false; - } - return true; -} - -void LocoNetSerial::started() -{ - loconet->queryLocoSlots(); -} - -void LocoNetSerial::read() -{ - m_serialPort.async_read_some(boost::asio::buffer(m_readBuffer.data() + m_readBufferOffset, m_readBuffer.size() - m_readBufferOffset), - [this](const boost::system::error_code& ec, std::size_t bytesTransferred) - { - if(!ec) - { - const uint8_t* pos = m_readBuffer.data(); - bytesTransferred += m_readBufferOffset; - - while(bytesTransferred > 1) - { - const LocoNet::Message* message = reinterpret_cast(pos); - - size_t drop = 0; - while((message->size() == 0 || (message->size() <= bytesTransferred && !LocoNet::isValid(*message))) && drop < bytesTransferred) - { - drop++; - pos++; - bytesTransferred--; - message = reinterpret_cast(pos); - } - - if(drop != 0) - { - EventLoop::call( - [this, drop]() - { - Log::log(*this, LogMessage::W2001_RECEIVED_MALFORMED_DATA_DROPPED_X_BYTES, drop); - }); - } - else if(message->size() <= bytesTransferred) - { - loconet->receive(*message); - pos += message->size(); - bytesTransferred -= message->size(); - } - else - break; - } - - if(bytesTransferred != 0) - memmove(m_readBuffer.data(), pos, bytesTransferred); - m_readBufferOffset = bytesTransferred; - - read(); - } - else - EventLoop::call( - [this, ec]() - { - Log::log(*this, LogMessage::E2002_SERIAL_READ_FAILED_X, ec); - online = false; - }); - }); -} diff --git a/server/src/hardware/commandstation/loconetserial.hpp b/server/src/hardware/commandstation/loconetserial.hpp deleted file mode 100644 index ec9886ab..00000000 --- a/server/src/hardware/commandstation/loconetserial.hpp +++ /dev/null @@ -1,52 +0,0 @@ -/** - * server/src/hardware/commandstation/loconetserial.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_COMMANDSTATION_LOCONETSERIAL_HPP -#define TRAINTASTIC_SERVER_HARDWARE_COMMANDSTATION_LOCONETSERIAL_HPP - -#include "serialcommandstation.hpp" -#include "../protocol/loconet/loconet.hpp" -#include "../../enum/loconetserialinterface.hpp" - -class LocoNetSerial : public SerialCommandStation -{ - protected: - void emergencyStopChanged(bool value) final; - void powerOnChanged(bool value) final; - void decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber) final; - - bool start(); - bool send(const LocoNet::Message& msg); - void started() final; - void read() final; - - public: - CLASS_ID("command_station.loconet_serial") - CREATE(LocoNetSerial) - - Property interface; - ObjectProperty loconet; - - LocoNetSerial(const std::weak_ptr& world, std::string_view _id); -}; - -#endif diff --git a/server/src/hardware/commandstation/loconettcpbinary.cpp b/server/src/hardware/commandstation/loconettcpbinary.cpp deleted file mode 100644 index 24624777..00000000 --- a/server/src/hardware/commandstation/loconettcpbinary.cpp +++ /dev/null @@ -1,192 +0,0 @@ -/** - * hardware/commandstation/loconettcpbinary.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 "loconettcpbinary.hpp" -#include "../../core/traintastic.hpp" -#include "../../core/eventloop.hpp" -#include "../../core/attributes.hpp" -#include "../../log/log.hpp" -#include "../../utils/displayname.hpp" - -LocoNetTCPBinary::LocoNetTCPBinary(const std::weak_ptr& world, std::string_view _id) : - CommandStation(world, _id), - m_socket{Traintastic::instance->ioContext()}, - hostname{this, "hostname", "", PropertyFlags::ReadWrite | PropertyFlags::Store}, - port{this, "port", 5550, PropertyFlags::ReadWrite | PropertyFlags::Store}, - loconet{this, "loconet", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject} -{ - name = "LocoNet (TCP binary)"; - loconet.setValueInternal(LocoNet::LocoNet::create(*this, loconet.name(), std::bind(&LocoNetTCPBinary::send, this, std::placeholders::_1))); - - Attributes::addDisplayName(hostname, DisplayName::IP::hostname); - Attributes::addEnabled(hostname, true); - m_interfaceItems.insertBefore(hostname, notes); - - Attributes::addDisplayName(port, DisplayName::IP::port); - Attributes::addEnabled(port, true); - m_interfaceItems.insertBefore(port, notes); - - Attributes::addDisplayName(loconet, DisplayName::Hardware::loconet); - m_interfaceItems.insertBefore(loconet, notes); -} - -bool LocoNetTCPBinary::setOnline(bool& value) -{ - if(!m_socket.is_open() && value) - { - boost::system::error_code ec; - - boost::asio::ip::tcp::endpoint endpoint; - endpoint.port(port); - endpoint.address(boost::asio::ip::make_address(hostname, ec)); - if(ec) - { - Log::log(*this, LogMessage::E2003_MAKE_ADDRESS_FAILED_X, ec); - return false; - } - - m_socket.connect(endpoint, ec); - if(ec) - { - Log::log(*this, LogMessage::E2005_SOCKET_CONNECT_FAILED_X, ec); - return false; - } - - m_socket.set_option(boost::asio::socket_base::linger(true, 0)); - m_socket.set_option(boost::asio::ip::tcp::no_delay(true)); - - receive(); // start receiving messages - - Attributes::setEnabled(hostname, false); - Attributes::setEnabled(port, false); - } - else if(m_socket.is_open() && !value) - { - Attributes::setEnabled(hostname, true); - Attributes::setEnabled(port, true); - - m_socket.close(); - } - return true; -} - -void LocoNetTCPBinary::emergencyStopChanged(bool value) -{ - CommandStation::emergencyStopChanged(value); - - if(online) - loconet->emergencyStopChanged(value); -} - -void LocoNetTCPBinary::powerOnChanged(bool value) -{ - CommandStation::powerOnChanged(value); - - if(online) - loconet->powerOnChanged(value); -} - -void LocoNetTCPBinary::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber) -{ - CommandStation::decoderChanged(decoder, changes, functionNumber); - - if(online) - loconet->decoderChanged(decoder, changes, functionNumber); -} - -void LocoNetTCPBinary::receive() -{ - m_socket.async_read_some(boost::asio::buffer(m_readBuffer.data() + m_readBufferOffset, m_readBuffer.size() - m_readBufferOffset), - [this](const boost::system::error_code& ec, std::size_t bytesTransferred) - { - if(!ec) - { - const uint8_t* pos = m_readBuffer.data(); - bytesTransferred += m_readBufferOffset; - - while(bytesTransferred > 1) - { - const LocoNet::Message* message = reinterpret_cast(pos); - - size_t drop = 0; - while((message->size() == 0 || (message->size() <= bytesTransferred && !LocoNet::isValid(*message))) && drop < bytesTransferred) - { - drop++; - pos++; - bytesTransferred--; - message = reinterpret_cast(pos); - } - - if(drop != 0) - { - EventLoop::call( - [this, drop]() - { - Log::log(*this, LogMessage::W2001_RECEIVED_MALFORMED_DATA_DROPPED_X_BYTES, drop); - }); - } - else if(message->size() <= bytesTransferred) - { - loconet->receive(*message); - pos += message->size(); - bytesTransferred -= message->size(); - } - else - break; - } - - if(bytesTransferred != 0) - memmove(m_readBuffer.data(), pos, bytesTransferred); - m_readBufferOffset = bytesTransferred; - - receive(); - } - else - EventLoop::call( - [this, ec]() - { - Log::log(*this, LogMessage::E2008_SOCKET_READ_FAILED_X, ec); - online = false; - }); - }); -} - -bool LocoNetTCPBinary::send(const LocoNet::Message& message) -{ - if(!m_socket.is_open()) - return false; - - // for now a blocking implementation, will be replaced by async. - - boost::system::error_code ec; - size_t todo = message.size(); - while(todo > 0) - { - todo -= m_socket.write_some(boost::asio::buffer(static_cast(&message), message.size()), ec); - if(ec) - { - Log::log(*this, LogMessage::E2007_SOCKET_WRITE_FAILED_X, ec); - return false; - } - } - return true; -} diff --git a/server/src/hardware/commandstation/loconettcpbinary.hpp b/server/src/hardware/commandstation/loconettcpbinary.hpp deleted file mode 100644 index 5f88a8a1..00000000 --- a/server/src/hardware/commandstation/loconettcpbinary.hpp +++ /dev/null @@ -1,56 +0,0 @@ -/** - * server/src/hardware/commandstation/loconettcpbinary.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_COMMANDSTATION_LOCONETTCPBINARY_HPP -#define TRAINTASTIC_SERVER_HARDWARE_COMMANDSTATION_LOCONETTCPBINARY_HPP - -#include "commandstation.hpp" -#include -#include "../protocol/loconet/loconet.hpp" - -class LocoNetTCPBinary : public CommandStation -{ - protected: - boost::asio::ip::tcp::socket m_socket; - std::array m_readBuffer; - uint16_t m_readBufferOffset; - - bool setOnline(bool& value) final; - void emergencyStopChanged(bool value) final; - void powerOnChanged(bool value) final; - void decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber) final; - - void receive(); - bool send(const LocoNet::Message& msg); - - public: - CLASS_ID("command_station.loconet_tcp_binary") - CREATE(LocoNetTCPBinary) - - Property hostname; - Property port; - ObjectProperty loconet; - - LocoNetTCPBinary(const std::weak_ptr& world, std::string_view _id); -}; - -#endif diff --git a/server/src/hardware/commandstation/rocoz21.cpp b/server/src/hardware/commandstation/rocoz21.cpp deleted file mode 100644 index a305246c..00000000 --- a/server/src/hardware/commandstation/rocoz21.cpp +++ /dev/null @@ -1,524 +0,0 @@ -/** - * server/src/hardware/commandstation/rocoz21.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 "rocoz21.hpp" -#include "../../core/traintastic.hpp" -#include "../../world/world.hpp" -#include "../../core/eventloop.hpp" -#include "../../core/attributes.hpp" -#include "../decoder/decoderchangeflags.hpp" -#include "../protocol/xpressnet/messages.hpp" -#include "../protocol/z21/messages.hpp" -#include "../../utils/tohex.hpp" -#include "../../utils/category.hpp" -#include "../../log/log.hpp" -#include "../../utils/displayname.hpp" - -#define SET_ADDRESS \ - if(decoder.longAddress) \ - { \ - cmd.addressHigh = 0xc0 | (decoder.address >> 8); \ - cmd.addressLow = decoder.address & 0xff; \ - } \ - else \ - { \ - cmd.addressHigh = 0; \ - cmd.addressLow = decoder.address; \ - } - - - - - - -RocoZ21::RocoZ21(const std::weak_ptr& world, std::string_view _id) : - CommandStation(world, _id), - m_socket{Traintastic::instance->ioContext()}, - hostname{this, "hostname", "", PropertyFlags::ReadWrite | PropertyFlags::Store}, - port{this, "port", 21105, PropertyFlags::ReadWrite | PropertyFlags::Store}, - loconet{this, "loconet", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject}, - serialNumber{this, "serial_number", "", PropertyFlags::ReadOnly}, - hardwareType{this, "hardware_type", "", PropertyFlags::ReadOnly}, - firmwareVersion{this, "firmware_version", "", PropertyFlags::ReadOnly}, - mainCurrent{this, "main_current", std::numeric_limits::quiet_NaN(), PropertyFlags::ReadOnly}, - progCurrent{this, "prog_current", std::numeric_limits::quiet_NaN(), PropertyFlags::ReadOnly}, - filteredMainCurrent{this, "filtered_main_current", std::numeric_limits::quiet_NaN(), PropertyFlags::ReadOnly}, - temperature{this, "temperature", std::numeric_limits::quiet_NaN(), PropertyFlags::ReadOnly}, - supplyVoltage{this, "supply_voltage", std::numeric_limits::quiet_NaN(), PropertyFlags::ReadOnly}, - vccVoltage{this, "vcc_voltage", std::numeric_limits::quiet_NaN(), PropertyFlags::ReadOnly}, - shortCircuit{this, "short_circuit", false, PropertyFlags::ReadOnly}, - programmingModeActive{this, "programming_mode_active", false, PropertyFlags::ReadOnly}, - highTemperature{this, "high_temperature", false, PropertyFlags::ReadOnly}, - powerLost{this, "power_lost", false, PropertyFlags::ReadOnly}, - shortCircutInternal{this, "short_circut_internal", false, PropertyFlags::ReadOnly}, - shortCircutExternal{this, "short_circut_external", false, PropertyFlags::ReadOnly} -{ - name = "Z21"; - loconet.setValueInternal(LocoNet::LocoNet::create(*this, loconet.name(), - [/*this*/](const ::LocoNet::Message& /*msg*/) - { - return false; - })); - - Attributes::addDisplayName(hostname, DisplayName::IP::hostname); - Attributes::addEnabled(hostname, true); - m_interfaceItems.insertBefore(hostname, notes); - - Attributes::addDisplayName(port, DisplayName::IP::port); - Attributes::addEnabled(port, true); - m_interfaceItems.insertBefore(port, notes); - - Attributes::addDisplayName(loconet, DisplayName::Hardware::loconet); - m_interfaceItems.insertBefore(loconet, notes); - - Attributes::addCategory(serialNumber, Category::info); - m_interfaceItems.insertBefore(serialNumber, notes); - Attributes::addCategory(hardwareType, Category::info); - m_interfaceItems.insertBefore(hardwareType, notes); - Attributes::addCategory(firmwareVersion, Category::info); - m_interfaceItems.insertBefore(firmwareVersion, notes); - Attributes::addCategory(mainCurrent, Category::info); - m_interfaceItems.insertBefore(mainCurrent, notes); - Attributes::addCategory(progCurrent, Category::info); - m_interfaceItems.insertBefore(progCurrent, notes); - Attributes::addCategory(filteredMainCurrent, Category::info); - m_interfaceItems.insertBefore(filteredMainCurrent, notes); - Attributes::addCategory(temperature, Category::info); - m_interfaceItems.insertBefore(temperature, notes); - Attributes::addCategory(supplyVoltage, Category::info); - m_interfaceItems.insertBefore(supplyVoltage, notes); - Attributes::addCategory(vccVoltage, Category::info); - m_interfaceItems.insertBefore(vccVoltage, notes); - Attributes::addCategory(shortCircuit, Category::info); - m_interfaceItems.insertBefore(shortCircuit, notes); - Attributes::addCategory(programmingModeActive, Category::info); - m_interfaceItems.insertBefore(programmingModeActive, notes); - Attributes::addCategory(highTemperature, Category::info); - m_interfaceItems.insertBefore(highTemperature, notes); - Attributes::addCategory(powerLost, Category::info); - m_interfaceItems.insertBefore(powerLost, notes); - Attributes::addCategory(shortCircutInternal, Category::info); - m_interfaceItems.insertBefore(shortCircutInternal, notes); - Attributes::addCategory(shortCircutExternal, Category::info); - m_interfaceItems.insertBefore(shortCircutExternal, notes); -} - -void RocoZ21::emergencyStopChanged(bool value) -{ - if(online && value) - send(Z21::LanXSetStop()); -} - -void RocoZ21::powerOnChanged(bool value) -{ - if(online) - { - if(value) - send(Z21::LanXSetTrackPowerOn()); - else - send(Z21::LanXSetTrackPowerOff()); - } -} - -/* -bool RocoZ21::isDecoderSupported(Decoder& decoder) const -{ - return - decoder.protocol == DecoderProtocol::DCC && - decoder.address >= 1 && - decoder.address <= (decoder.longAddress ? 9999 : 127); -} -*/ - - - - -void RocoZ21::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber) -{ - if(has(changes, DecoderChangeFlags::EmergencyStop | DecoderChangeFlags::Direction | DecoderChangeFlags::Throttle | DecoderChangeFlags::SpeedSteps)) - { - Z21::LanXSetLocoDrive cmd; - //cmd.dataLen = sizeof(cmd); - //cmd.header = Z21::LAN_X; - SET_ADDRESS; - - switch(decoder.speedSteps) - { - case 14: - { - const uint8_t speedStep = Decoder::throttleToSpeedStep(decoder.throttle, 14); - cmd.db0 = 0x10; - if(decoder.emergencyStop) - cmd.speedAndDirection = 0x01; - else if(speedStep > 0) - cmd.speedAndDirection = speedStep + 1; - break; - } - case 28: - { - uint8_t speedStep = Decoder::throttleToSpeedStep(decoder.throttle, 28); - cmd.db0 = 0x12; - if(decoder.emergencyStop) - cmd.speedAndDirection = 0x01; - else if(speedStep > 0) - { - speedStep++; - cmd.speedAndDirection = ((speedStep & 0x01) << 4) | (speedStep >> 1); - } - break; - } - case 126: - case 128: - default: - { - const uint8_t speedStep = Decoder::throttleToSpeedStep(decoder.throttle, 126); - cmd.db0 = 0x13; - if(decoder.emergencyStop) - cmd.speedAndDirection = 0x01; - else if(speedStep > 0) - cmd.speedAndDirection = speedStep + 1; - break; - } - } - - if(decoder.direction.value() == Direction::Forward) - cmd.speedAndDirection |= 0x80; - - cmd.checksum = XpressNet::calcChecksum(*reinterpret_cast(&cmd.xheader)); - send(cmd); - } - else if(has(changes, DecoderChangeFlags::FunctionValue)) - { - if(functionNumber <= 28) - { - if(const auto& f = decoder.getFunction(functionNumber)) - { - Z21::LanXSetLocoFunction cmd; - //cmd.dataLen = sizeof(cmd); - //cmd.header = Z21_LAN_X; - SET_ADDRESS; - cmd.db3 = (f->value ? 0x40 : 0x00) | static_cast(functionNumber); - cmd.checksum = XpressNet::calcChecksum(*reinterpret_cast(&cmd.xheader)); - send(cmd); - } - } - } -} - -bool RocoZ21::setOnline(bool& value) -{ - if(!m_socket.is_open() && value) - { - boost::system::error_code ec; - - m_remoteEndpoint.port(port); - m_remoteEndpoint.address(boost::asio::ip::make_address(hostname, ec)); - if(ec) - { - Log::log(*this, LogMessage::E2003_MAKE_ADDRESS_FAILED_X, ec); - return false; - } - - if(m_socket.open(boost::asio::ip::udp::v4(), ec)) - { - Log::log(*this, LogMessage::E2004_SOCKET_OPEN_FAILED_X, ec); - return false; - } - else if(m_socket.bind(boost::asio::ip::udp::endpoint(boost::asio::ip::address_v4::any(), port), ec)) - { - m_socket.close(); - Log::log(*this, LogMessage::E2006_SOCKET_BIND_FAILED_X, ec); - return false; - } - - receive(); // start receiving messages - - send(Z21::LanSetBroadcastFlags(0x07000000 | /*0x00010000 |*/ 0x00000100 | 0x00000001)); - - // try to communicate with Z21 - - send(Z21::LanGetSerialNumber()); - send(Z21::LanGetHardwareInfo()); - send(Z21::LanGetBroadcastFlags()); - send(Z21::LanSystemStateGetData()); - for(auto& decoder : *decoders) - send(Z21::LanXGetLocoInfo(decoder->address, decoder->longAddress)); - - Attributes::setEnabled(hostname, false); - Attributes::setEnabled(port, false); - } - else if(m_socket.is_open() && !value) - { - send(Z21::LanLogoff()); - - serialNumber.setValueInternal(""); - hardwareType.setValueInternal(""); - firmwareVersion.setValueInternal(""); - - Attributes::setEnabled(hostname, true); - Attributes::setEnabled(port, true); - - m_socket.close(); - } - return true; -} - -void RocoZ21::receive() -{ - m_socket.async_receive_from(boost::asio::buffer(m_receiveBuffer), m_receiveEndpoint, - [this](const boost::system::error_code& ec, std::size_t bytesReceived) - { - if(!ec) - { - if((bytesReceived >= sizeof(Z21::Message))) - { - const Z21::Message* message = reinterpret_cast(m_receiveBuffer.data()); - //const z21_lan_header* cmd = reinterpret_cast(m_receiveBuffer.data()); - switch(message->header()) - { - case Z21::LAN_GET_SERIAL_NUMBER: - if(message->dataLen() == sizeof(Z21::LanGetSerialNumberReply)) - { - EventLoop::call( - [this, value=std::to_string(static_cast(message)->serialNumber())]() - { - serialNumber.setValueInternal(value); - }); - } - break; - - case Z21::LAN_GET_HWINFO: - { - const Z21::LanGetHardwareInfoReply* reply = static_cast(message); - - std::string hwType; - switch(reply->hardwareType()) - { - case Z21::HWT_Z21_OLD: - hwType = "Black Z21 (hardware variant from 2012)"; - break; - case Z21::HWT_Z21_NEW: - hwType = "Black Z21 (hardware variant from 2013)"; - break; - case Z21::HWT_SMARTRAIL: - hwType = "SmartRail (from 2012)"; - break; - case Z21::HWT_Z21_SMALL: - hwType = "White Z21 (starter set variant from 2013)"; - break; - case Z21::HWT_Z21_START : - hwType = "Z21 start (starter set variant from 2016)"; - break; - default: - hwType = "0x" + toHex(reply->hardwareType()); - break; - } - - const std::string fwVersion = std::to_string(reply->firmwareVersionMajor()) + "." + std::to_string(reply->firmwareVersionMinor()); - - EventLoop::call( - [this, hwType, fwVersion]() - { - hardwareType.setValueInternal(hwType); - firmwareVersion.setValueInternal(fwVersion); - }); - break; - } - case Z21::LAN_X: - { - // TODO check XOR - const uint8_t xheader = static_cast(message)->xheader; - switch(xheader) - { - case Z21::LAN_X_LOCO_INFO: - { - const Z21::LanXLocoInfo* info = static_cast(message); - const uint16_t address = (static_cast(info->addressHigh) << 8) | info->addressLow; - const uint8_t speedStepMode = info->db2 & 0x07; - const Direction direction = (info->speedAndDirection & 0x80) ? Direction::Forward : Direction::Reverse; - const uint8_t speedStep = info->speedAndDirection & 0x7f; - const uint32_t functions = - ((info->db4 & 0x10) >> 4) | - ((info->db4 & 0x0f) << 1) | - (static_cast(info->f5f12) << 5) | - (static_cast(info->f13f20) << 13) | - (static_cast(info->f21f28) << 21); - - EventLoop::call( - [this, address, speedStepMode, direction, speedStep, functions]() - { - const std::shared_ptr& decoder = getDecoder(DecoderProtocol::DCC, address & 0x3fff, address & 0xc000); - if(decoder) - { - decoder->direction = direction; - switch(speedStepMode) - { - case 0: - decoder->throttle.setValueInternal(Decoder::speedStepToThrottle(speedStep, 14)); - break; - - case 2: - decoder->throttle.setValueInternal(Decoder::speedStepToThrottle(speedStep, 28)); - break; - - case 4: - decoder->throttle.setValueInternal(Decoder::speedStepToThrottle(speedStep, 126)); - break; - } - - for(auto& function : *decoder->functions) - { - const uint8_t number = function->number; - if(number <= 28) - function->value.setValueInternal(functions & (1 << number)); - } - } - }); - break; - } - case Z21::LAN_X_BC: - { - - break; - } - case Z21::LAN_X_BC_STOPPED: - EventLoop::call( - [this]() - { - emergencyStop.setValueInternal(true); - }); - break; - - default: - EventLoop::call([this, xheader](){ Log::log(*this, LogMessage::D2003_UNKNOWN_XHEADER_0XX, toHex(xheader)); }); - break; - } - break; - } - case Z21::LAN_SYSTEMSTATE_DATACHANGED: - { - /* - const Z21::LanSystemStateDataChanged state = *reinterpret_cast(m_receiveBuffer.data()); - EventLoop::call( - [this, state]() - { - mainCurrent.setValueInternal(state.mainCurrent / 1e3); //!< Current on the main track in mA - progCurrent.setValueInternal(state.progCurrent / 1e3); //!< Current on programming track in mA; - filteredMainCurrent.setValueInternal(state.filteredMainCurrent / 1e3); //!< Smoothed current on the main track in mA - temperature.setValueInternal(state.temperature); //!< Command station internal temperature in °C - supplyVoltage.setValueInternal(state.supplyVoltage / 1e3); //!< Supply voltage in mV - vccVoltage.setValueInternal(state.vccVoltage / 1e3); //!< Internal voltage, identical to track voltage in mV - emergencyStop.setValueInternal(state.centralState & Z21_CENTRALSTATE_EMERGENCYSTOP); - trackVoltageOff.setValueInternal(state.centralState & Z21_CENTRALSTATE_TRACKVOLTAGEOFF); - shortCircuit.setValueInternal(state.centralState & Z21_CENTRALSTATE_SHORTCIRCUIT); - programmingModeActive.setValueInternal(state.centralState & Z21_CENTRALSTATE_PROGRAMMINGMODEACTIVE); - highTemperature.setValueInternal(state.centralStateEx & Z21_CENTRALSTATEEX_HIGHTEMPERATURE); - powerLost.setValueInternal(state.centralStateEx & Z21_CENTRALSTATEEX_POWERLOST); - shortCircutInternal.setValueInternal(state.centralStateEx & Z21_CENTRALSTATEEX_SHORTCIRCUITEXTERNAL); - shortCircutExternal.setValueInternal(state.centralStateEx & Z21_CENTRALSTATEEX_SHORTCIRCUITINTERNAL); - }); - */ - break; - } - case Z21::LAN_LOCONET_Z21_RX: - //case Z21_LAN_LOCONET_Z21_TX: - //case Z21_LAN_LOCONET_Z21_LAN: - loconet->receive(*reinterpret_cast(m_receiveBuffer.data() + sizeof(Z21::Message))); - break; -/* - - using LocoNet = LocoNet; - - const LocoNet::Header* message = reinterpret_cast(m_receiveBuffer.data() + sizeof(z21_lan_header)); - - switch(message->opCode) - { - case LocoNet::OPC_INPUT_REP: - { - const LocoNet::InputRep* inputRep = static_cast(message); - - //if(debugEnabled) - { - const std::string message = "loconet rx OPC_INPUT_REP:" - " address=" + std::to_string(inputRep->address()) + - " input=" + (inputRep->isAuxInput() ? "aux" : "switch") + - " value=" + (inputRep->value() ? "high" : "low"); - } - - - break; - } - - - default: - //if(debugEnabled) - { - std::string message = "unknown loconet message: "; - for(int i = 4; i < cmd->dataLen; i++) - message += toHex(reinterpret_cast(cmd)[i]); - } - break; - } - - - */ - - default: - //if(debugEnabled) - { - std::string data = "dataLen=0x" + toHex(message->dataLen()) + ", header=0x" + toHex(message->header()); - if(message->dataLen() > 4) - { - data += ", data="; - for(int i = sizeof(Z21::Message); i < message->dataLen(); i++) - data += toHex(reinterpret_cast(message)[i]); - } - EventLoop::call([this, data](){ Log::log(*this, LogMessage::D2006_UNKNOWN_MESSAGE_X, data); }); - } - break; - } - } - receive(); - } - else - EventLoop::call([this, ec](){ Log::log(*this, LogMessage::E2009_SOCKET_RECEIVE_FAILED_X, ec); }); - }); -} - -void RocoZ21::send(const Z21::Message& message) -{ - // TODO async - - // TODO: add to queue, send async - - boost::system::error_code ec; - m_socket.send_to(boost::asio::buffer(&message, message.dataLen()), m_remoteEndpoint, 0, ec); - if(ec) - Log::log(*this, LogMessage::E2011_SOCKET_SEND_FAILED_X, ec); -/* - m_socket.send_to(boost::asio:buffer(&message, message.dataLen()), 0, m_remoteEndpoint);, - [this](const boost::system::error_code& ec, std::size_t) - { - if(ec) - EventLoop::call([this, ec](){ Log::log(*this, LogMessage::E2011_SOCKET_SEND_FAILED_X, ec); }); - });*/ -} diff --git a/server/src/hardware/commandstation/rocoz21.hpp b/server/src/hardware/commandstation/rocoz21.hpp deleted file mode 100644 index aa832b8a..00000000 --- a/server/src/hardware/commandstation/rocoz21.hpp +++ /dev/null @@ -1,84 +0,0 @@ -/** - * server/src/hardware/commandstation/rocoz21.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_COMMANDSTATION_ROCOZ21_HPP -#define TRAINTASTIC_SERVER_HARDWARE_COMMANDSTATION_ROCOZ21_HPP - -#include "commandstation.hpp" -#include -#include "../../core/objectproperty.hpp" -#include "../protocol/loconet/loconet.hpp" - -struct z21_lan_header; - -namespace Z21 { - class Message; -} - -class RocoZ21 : public CommandStation -{ - protected: - boost::asio::ip::udp::socket m_socket; - boost::asio::ip::udp::endpoint m_remoteEndpoint; - boost::asio::ip::udp::endpoint m_receiveEndpoint; - std::array m_receiveBuffer; - - bool setOnline(bool& value) final; - //bool isDecoderSupported(Decoder& decoder) const final; - void emergencyStopChanged(bool value) final; - void powerOnChanged(bool value) final; - void decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber) final; - - void receive(); - void send(const Z21::Message& message); - void send(const z21_lan_header* msg); - inline void send(const z21_lan_header& msg) { send(&msg); } - - public: - CLASS_ID("command_station.z21") - CREATE(RocoZ21) - - RocoZ21(const std::weak_ptr& world, std::string_view _id); - - Property hostname; - Property port; - ObjectProperty loconet; - Property serialNumber; - Property hardwareType; - Property firmwareVersion; - Property mainCurrent; - Property progCurrent; - Property filteredMainCurrent; - Property temperature; - Property supplyVoltage; - Property vccVoltage; - //Property emergencyStop; - //Property trackVoltageOff; - Property shortCircuit; - Property programmingModeActive; - Property highTemperature; - Property powerLost; - Property shortCircutInternal; - Property shortCircutExternal; -}; - -#endif diff --git a/server/src/hardware/commandstation/serialcommandstation.cpp b/server/src/hardware/commandstation/serialcommandstation.cpp deleted file mode 100644 index e828125a..00000000 --- a/server/src/hardware/commandstation/serialcommandstation.cpp +++ /dev/null @@ -1,133 +0,0 @@ -/** - * server/src/hardware/commandstation/serialcommandstation.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. - */ - -#include "serialcommandstation.hpp" -#include "../../core/traintastic.hpp" -#include "../../core/eventloop.hpp" -#include "../../core/attributes.hpp" -#include "../../log/log.hpp" -#include "../../utils/displayname.hpp" - -SerialCommandStation::SerialCommandStation(const std::weak_ptr& world, std::string_view _id) : - CommandStation(world, _id), - m_serialPort{Traintastic::instance->ioContext()}, - m_readBufferOffset{0}, - port{this, "port", "/dev/ttyUSB0", PropertyFlags::ReadWrite | PropertyFlags::Store}, - baudrate{this, "baudrate", 19200, PropertyFlags::ReadWrite | PropertyFlags::Store, - [this](uint32_t) - { - //interface = LocoNetSerialInterface::Custom; - }}, - flowControl{this, "flow_control", SerialFlowControl::None, PropertyFlags::ReadWrite | PropertyFlags::Store, - [this](SerialFlowControl) - { - //interface = LocoNetSerialInterface::Custom; - }} -{ - Attributes::addDisplayName(port, DisplayName::Serial::port); - Attributes::addEnabled(port, !online); - m_interfaceItems.insertBefore(port, notes); - - Attributes::addDisplayName(baudrate, DisplayName::Serial::baudrate); - Attributes::addEnabled(baudrate, !online); - m_interfaceItems.insertBefore(baudrate, notes); - - Attributes::addDisplayName(flowControl, DisplayName::Serial::flowControl); - Attributes::addEnabled(flowControl, !online); - Attributes::addValues(flowControl, SerialFlowControlValues); - m_interfaceItems.insertBefore(flowControl, notes); -} - -void SerialCommandStation::loaded() -{ - CommandStation::loaded(); - updateEnabled(); -} - -void SerialCommandStation::worldEvent(WorldState state, WorldEvent event) -{ - CommandStation::worldEvent(state, event); - if(event == WorldEvent::EditEnabled || event == WorldEvent::EditDisabled) - updateEnabled(); -} - -bool SerialCommandStation::setOnline(bool& value) -{ - if(!m_serialPort.is_open() && value) - { - if(!start()) - { - value = false; - return false; - } - m_readBufferOffset = 0; - read(); - started(); - } - else if(m_serialPort.is_open() && !value) - stop(); - - return true; -} - -bool SerialCommandStation::start() -{ - boost::system::error_code ec; - m_serialPort.open(port, ec); - if(ec) - { - Log::log(*this, LogMessage::E2010_SERIAL_PORT_OPEN_FAILED_X, ec); - return false; - } - - m_serialPort.set_option(boost::asio::serial_port_base::baud_rate(baudrate)); - m_serialPort.set_option(boost::asio::serial_port_base::character_size(8)); - m_serialPort.set_option(boost::asio::serial_port_base::stop_bits(boost::asio::serial_port_base::stop_bits::one)); - m_serialPort.set_option(boost::asio::serial_port_base::parity(boost::asio::serial_port_base::parity::none)); - switch(flowControl) - { - case SerialFlowControl::None: - m_serialPort.set_option(boost::asio::serial_port_base::flow_control(boost::asio::serial_port_base::flow_control::none)); - break; - - case SerialFlowControl::Hardware: - m_serialPort.set_option(boost::asio::serial_port_base::flow_control(boost::asio::serial_port_base::flow_control::hardware)); - break; - } - return true; -} - -void SerialCommandStation::stop() -{ - // TODO: send power off cmd?? - m_serialPort.close(); -} - -void SerialCommandStation::updateEnabled() -{ - auto w = m_world.lock(); - bool enabled = w && contains(w->state.value(), WorldState::Edit) && !online; - - Attributes::setEnabled(port, enabled); - Attributes::setEnabled(baudrate, enabled); - Attributes::setEnabled(flowControl, enabled); -} \ No newline at end of file diff --git a/server/src/hardware/commandstation/serialcommandstation.hpp b/server/src/hardware/commandstation/serialcommandstation.hpp deleted file mode 100644 index 324e1098..00000000 --- a/server/src/hardware/commandstation/serialcommandstation.hpp +++ /dev/null @@ -1,59 +0,0 @@ -/** - * server/src/hardware/commandstation/serialcommandstation.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_COMMANDSTATION_SERIALCOMMANDSTATION_HPP -#define TRAINTASTIC_SERVER_HARDWARE_COMMANDSTATION_SERIALCOMMANDSTATION_HPP - -#include "commandstation.hpp" -#include "../../enum/serialflowcontrol.hpp" -#include - -class SerialCommandStation : public CommandStation -{ - private: - void updateEnabled(); - - protected: - boost::asio::serial_port m_serialPort; - std::array m_readBuffer; - uint16_t m_readBufferOffset; - - void loaded() override; - void worldEvent(WorldState state, WorldEvent event) override; - - bool setOnline(bool& value) final; - - bool start(); - virtual void stop(); - - virtual void started() {} - virtual void read() = 0; - - public: - Property port; - Property baudrate; - Property flowControl; - - SerialCommandStation(const std::weak_ptr& world, std::string_view _id); -}; - -#endif diff --git a/server/src/hardware/commandstation/usbxpressnetinterface.cpp b/server/src/hardware/commandstation/usbxpressnetinterface.cpp deleted file mode 100644 index f45a3140..00000000 --- a/server/src/hardware/commandstation/usbxpressnetinterface.cpp +++ /dev/null @@ -1,112 +0,0 @@ -/** - * server/src/hardware/commandstation/usbxpressnetinterface.cpp - * - * 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 "usbxpressnetinterface.hpp" -#include "../../core/traintastic.hpp" -#include "../../world/world.hpp" - -USBXpressNetInterface::USBXpressNetInterface(const std::weak_ptr& world, std::string_view _id) : - CommandStation(world, _id), - m_handle{nullptr}, - serial{this, "serial", "", PropertyFlags::ReadWrite | PropertyFlags::Store}, - address{this, "address", 31, PropertyFlags::ReadWrite | PropertyFlags::Store}, - xpressnet{this, "xpressnet", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject} -{ - name = "USB XpressNet interface"; - xpressnet.setValueInternal(std::make_shared(*this, xpressnet.name(), std::bind(&USBXpressNetInterface::send, this, std::placeholders::_1))); - - m_interfaceItems.insertBefore(serial, notes); - m_interfaceItems.insertBefore(address, notes); - m_interfaceItems.insertBefore(xpressnet, notes); - - usbxpressnet_init(); -} - -USBXpressNetInterface::~USBXpressNetInterface() -{ - if(m_handle) - { - usbxpressnet_reset(m_handle); - usbxpressnet_close(m_handle); - } - usbxpressnet_fini(); -} - -bool USBXpressNetInterface::setOnline(bool& value) -{ - if(!m_handle && value) - { - usbxpressnet_status status = usbxpressnet_open(!serial.value().empty() ? serial.value().c_str() : nullptr, &m_handle); - if(status != USBXPRESSNET_STATUS_SUCCESS) - { - // \todo Log::log(*this, LogMessage::E0001_X, std::string("usbxpressnet_open: ") + usbxpressnet_status_str(status)); - return false; - } - - status = usbxpressnet_reset(m_handle); - if(status != USBXPRESSNET_STATUS_SUCCESS) - { - // \todo Log::log(*this, LogMessage::E0001_X, std::string("usbxpressnet_reset: ") + usbxpressnet_status_str(status)); - return false; - } - - status = usbxpressnet_set_mode(m_handle, USBXPRESSNET_MODE_DEVICE, address); - if(status != USBXPRESSNET_STATUS_SUCCESS) - { - // \todo Log::log(*this, LogMessage::E0001_X, std::string("usbxpressnet_set_mode: ") + usbxpressnet_status_str(status)); - return false; - } - } - else if(m_handle && !value) - { - usbxpressnet_close(m_handle); - m_handle = nullptr; - } - return true; -} - -void USBXpressNetInterface::emergencyStopChanged(bool value) -{ - -} - -void USBXpressNetInterface::powerOnChanged(bool value) -{ -} - -void USBXpressNetInterface::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber) -{ - if(online) - xpressnet->decoderChanged(decoder, changes, functionNumber); -} - -bool USBXpressNetInterface::send(const XpressNet::Message& msg) -{ - assert(XpressNet::isChecksumValid(msg)); - if(!m_handle) - return false; - usbxpressnet_status status; - if((status = usbxpressnet_send_message(m_handle, &msg)) != USBXPRESSNET_STATUS_SUCCESS) - { - Traintastic::instance->console->critical(id, std::string("usbxpressnet_send_message: ") + usbxpressnet_status_str(status)); - return false; - } - return true; -} diff --git a/server/src/hardware/commandstation/usbxpressnetinterface.hpp b/server/src/hardware/commandstation/usbxpressnetinterface.hpp deleted file mode 100644 index c6e2d2ea..00000000 --- a/server/src/hardware/commandstation/usbxpressnetinterface.hpp +++ /dev/null @@ -1,57 +0,0 @@ -/** - * server/src/hardware/commandstation/usbxpressnetinterface.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_COMMANDSTATION_USBXPRESSNETINTERFACE_HPP -#define TRAINTASTIC_SERVER_HARDWARE_COMMANDSTATION_USBXPRESSNETINTERFACE_HPP - -#include "commandstation.hpp" -#include "../protocol/xpressnet/xpressnet.hpp" -#include - -class USBXpressNetInterface : public CommandStation -{ - protected: - static const uint8_t addressMin = 1; - static const uint8_t addressMax = 31; - - usbxpressnet_handle m_handle; - - bool setOnline(bool& value) final; - void emergencyStopChanged(bool value) final; - void powerOnChanged(bool value) final; - void decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber) final; - - bool send(const XpressNet::Message& msg); - - public: - CLASS_ID("command_station.usb_xpressnet_interface") - CREATE(USBXpressNetInterface) - - Property serial; - Property address; - ObjectProperty xpressnet; - - USBXpressNetInterface(const std::weak_ptr& world, std::string_view _id); - ~USBXpressNetInterface() final; -}; - -#endif diff --git a/server/src/hardware/commandstation/xpressnetserial.cpp b/server/src/hardware/commandstation/xpressnetserial.cpp deleted file mode 100644 index 664e0905..00000000 --- a/server/src/hardware/commandstation/xpressnetserial.cpp +++ /dev/null @@ -1,273 +0,0 @@ -/** - * hardware/commandstation/xpressnetserial.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 "xpressnetserial.hpp" -#include "../../world/world.hpp" -#include "../../core/traintastic.hpp" -#include "../../core/eventloop.hpp" -#include "../../core/attributes.hpp" -#include "../../log/log.hpp" -#include "../../utils/displayname.hpp" - -XpressNetSerial::XpressNetSerial(const std::weak_ptr& world, std::string_view _id) : - SerialCommandStation(world, _id), - interface{this, "interface", XpressNetSerialInterface::LenzLI100, PropertyFlags::ReadWrite | PropertyFlags::Store, - [this](XpressNetSerialInterface value) - { - switch(value) - { - case XpressNetSerialInterface::Custom: - break; - - case XpressNetSerialInterface::LenzLI100: - case XpressNetSerialInterface::RoSoftS88XPressNetLI: - baudrate.setValueInternal(9600); - flowControl.setValueInternal(SerialFlowControl::Hardware); - break; - - case XpressNetSerialInterface::LenzLI100F: - baudrate.setValueInternal(19200); - flowControl.setValueInternal(SerialFlowControl::Hardware); - break; - - case XpressNetSerialInterface::LenzLI101F: - baudrate.setValueInternal(19200); - flowControl.setValueInternal(SerialFlowControl::Hardware); - break; - } - updateEnabled(); - updateVisible(); - }}, - xpressnet{this, "xpressnet", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject}, - s88StartAddress{this, "s88_start_address", XpressNet::RoSoftS88XpressNetLI::S88StartAddress::startAddressDefault, PropertyFlags::ReadWrite | PropertyFlags::Store}, - s88ModuleCount{this, "s88_module_count", XpressNet::RoSoftS88XpressNetLI::S88ModuleCount::moduleCountDefault, PropertyFlags::ReadWrite | PropertyFlags::Store} -{ - name = "XpressNet (serial)"; - xpressnet.setValueInternal(std::make_shared(*this, xpressnet.name(), std::bind(&XpressNetSerial::send, this, std::placeholders::_1))); - - Attributes::addValues(interface, XpressNetSerialInterfaceValues); - Attributes::addEnabled(interface, !online); - m_interfaceItems.insertBefore(interface, baudrate); - - Attributes::addDisplayName(xpressnet, DisplayName::Hardware::xpressnet); - m_interfaceItems.insertBefore(xpressnet, notes); - - Attributes::addMinMax(s88StartAddress, XpressNet::RoSoftS88XpressNetLI::S88StartAddress::startAddressMin, XpressNet::RoSoftS88XpressNetLI::S88StartAddress::startAddressMax); - Attributes::addEnabled(s88StartAddress, !online); - Attributes::addVisible(s88StartAddress, false); - m_interfaceItems.insertBefore(s88StartAddress, notes); - - Attributes::addMinMax(s88ModuleCount, XpressNet::RoSoftS88XpressNetLI::S88ModuleCount::moduleCountMin, XpressNet::RoSoftS88XpressNetLI::S88ModuleCount::moduleCountMax); - Attributes::addEnabled(s88ModuleCount, !online); - Attributes::addVisible(s88ModuleCount, false); - m_interfaceItems.insertBefore(s88ModuleCount, notes); - - updateEnabled(); - updateVisible(); -} - -void XpressNetSerial::loaded() -{ - SerialCommandStation::loaded(); - updateEnabled(); - updateVisible(); -} - -void XpressNetSerial::worldEvent(WorldState state, WorldEvent event) -{ - SerialCommandStation::worldEvent(state, event); - if(event == WorldEvent::EditEnabled || event == WorldEvent::EditDisabled) - updateEnabled(); -} - -void XpressNetSerial::emergencyStopChanged(bool value) -{ - CommandStation::emergencyStopChanged(value); - - if(online) - xpressnet->emergencyStopChanged(value); -} - -void XpressNetSerial::powerOnChanged(bool value) -{ - CommandStation::powerOnChanged(value); - - if(online) - xpressnet->powerOnChanged(value); -} - -void XpressNetSerial::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber) -{ - CommandStation::decoderChanged(decoder, changes, functionNumber); - - if(online) - xpressnet->decoderChanged(decoder, changes, functionNumber); -} - -bool XpressNetSerial::send(const XpressNet::Message& msg) -{ - assert(XpressNet::isChecksumValid(msg)); - if(!m_serialPort.is_open()) - return false; - boost::system::error_code ec; - m_serialPort.write_some(boost::asio::buffer(static_cast(&msg), msg.size()), ec); // TODO async - if(ec) - { - Log::log(*this, LogMessage::E2001_SERIAL_WRITE_FAILED_X, ec); - return false; - } - return true; -} - -void XpressNetSerial::stop() -{ - SerialCommandStation::stop(); - updateEnabled(); -} - -void XpressNetSerial::started() -{ - SerialCommandStation::started(); - - updateEnabled(); - - switch(interface.value()) - { - case XpressNetSerialInterface::RoSoftS88XPressNetLI: - { - send(XpressNet::RoSoftS88XpressNetLI::S88StartAddress(s88StartAddress)); - send(XpressNet::RoSoftS88XpressNetLI::S88ModuleCount(s88ModuleCount)); - break; - } - default: - break; - } -} - -void XpressNetSerial::read() -{ - m_serialPort.async_read_some(boost::asio::buffer(m_readBuffer.data() + m_readBufferOffset, m_readBuffer.size() - m_readBufferOffset), - [this](const boost::system::error_code& ec, std::size_t bytesTransferred) - { - if(!ec) - { - const uint8_t* pos = m_readBuffer.data(); - bytesTransferred += m_readBufferOffset; - - while(bytesTransferred > 1) - { - const XpressNet::Message* message = reinterpret_cast(pos); - - size_t drop = 0; - while(message->size() <= bytesTransferred && !XpressNet::isChecksumValid(*message) && drop < bytesTransferred) - { - drop++; - pos++; - bytesTransferred--; - message = reinterpret_cast(pos); - } - - if(drop != 0) - { - EventLoop::call( - [this, drop]() - { - Log::log(*this, LogMessage::W2001_RECEIVED_MALFORMED_DATA_DROPPED_X_BYTES, drop); - }); - } - - if(message->size() <= bytesTransferred) - { - bool handled = false; - - if(message->header == 0xF2) - { - switch(*(pos + 1)) - { - case 0xF1: - { - using XpressNet::RoSoftS88XpressNetLI::S88StartAddress; - EventLoop::call( - [this, msg = *static_cast(message)]() - { - assert(msg.startAddress >= S88StartAddress::startAddressMin && msg.startAddress <= S88StartAddress::startAddressMax); - s88StartAddress.setValueInternal(msg.startAddress); - }); - handled = true; - break; - } - case 0xF2: - { - using XpressNet::RoSoftS88XpressNetLI::S88ModuleCount; - EventLoop::call( - [this, msg = *static_cast(message)]() - { - assert(msg.moduleCount >= S88ModuleCount::moduleCountMin && msg.moduleCount <= S88ModuleCount::moduleCountMax); - s88ModuleCount.setValueInternal(msg.moduleCount); - }); - handled = true; - break; - } - } - } - - if(!handled) - xpressnet->receive(*message); - pos += message->size(); - bytesTransferred -= message->size(); - } - else - break; - } - - if(bytesTransferred != 0) - memmove(m_readBuffer.data(), pos, bytesTransferred); - m_readBufferOffset = bytesTransferred; - - read(); - } - else - EventLoop::call( - [this, ec]() - { - Log::log(*this, LogMessage::E2002_SERIAL_READ_FAILED_X, ec); - online = false; - }); - }); -} - -void XpressNetSerial::updateEnabled() -{ - auto w = m_world.lock(); - const bool enabled = w && contains(w->state.value(), WorldState::Edit) && !online; - - Attributes::setEnabled(interface, enabled); - Attributes::setEnabled(s88StartAddress, enabled); - Attributes::setEnabled(s88ModuleCount, enabled); -} - -void XpressNetSerial::updateVisible() -{ - const bool visible = interface == XpressNetSerialInterface::RoSoftS88XPressNetLI; - Attributes::setVisible(s88StartAddress, visible); - Attributes::setVisible(s88ModuleCount, visible); -} diff --git a/server/src/hardware/commandstation/xpressnetserial.hpp b/server/src/hardware/commandstation/xpressnetserial.hpp deleted file mode 100644 index a7d55bf5..00000000 --- a/server/src/hardware/commandstation/xpressnetserial.hpp +++ /dev/null @@ -1,61 +0,0 @@ -/** - * server/src/hardware/commandstation/xpressnetserial.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_COMMANDSTATION_XPRESSNETSERIAL_HPP -#define TRAINTASTIC_SERVER_HARDWARE_COMMANDSTATION_XPRESSNETSERIAL_HPP - -#include "serialcommandstation.hpp" -#include "../../enum/xpressnetserialinterface.hpp" -#include "../protocol/xpressnet/xpressnet.hpp" - -class XpressNetSerial : public SerialCommandStation -{ - private: - void updateEnabled(); - void updateVisible(); - - protected: - void loaded() final; - void worldEvent(WorldState state, WorldEvent event) final; - - void emergencyStopChanged(bool value) final; - void powerOnChanged(bool value) final; - void decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber) final; - - bool send(const XpressNet::Message& msg); - void stop() final; - void started() final; - void read() final; - - public: - CLASS_ID("command_station.xpressnet_serial") - CREATE(XpressNetSerial) - - Property interface; - ObjectProperty xpressnet; - Property s88StartAddress; - Property s88ModuleCount; - - XpressNetSerial(const std::weak_ptr& world, std::string_view _id); -}; - -#endif diff --git a/server/src/hardware/controller/controller.cpp b/server/src/hardware/controller/controller.cpp deleted file mode 100644 index dffaf9df..00000000 --- a/server/src/hardware/controller/controller.cpp +++ /dev/null @@ -1,114 +0,0 @@ -/** - * server/src/hardware/controller/controller.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 "controller.hpp" -#include "controllerlist.hpp" -#include "controllerlisttablemodel.hpp" -#include "../commandstation/commandstation.hpp" -#include "../../world/getworld.hpp" -#include "../../core/attributes.hpp" -#include "../../utils/displayname.hpp" - -Controller::Controller(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) - { - std::shared_ptr controller = shared_ptr(); - assert(controller); - - if(commandStation) - commandStation->controllers->removeObject(controller); - - if(value) - value->controllers->addObject(controller); - - return true; - }}, - active{this, "active", false, PropertyFlags::ReadWrite | PropertyFlags::StoreState, nullptr, - std::bind(&Controller::setActive, this, std::placeholders::_1)}, - notes{this, "notes", "", PropertyFlags::ReadWrite | PropertyFlags::Store} -{ - auto world = _world.lock(); - const bool editable = world && contains(world->state.value(), WorldState::Edit); - - Attributes::addDisplayName(name, DisplayName::Object::name); - m_interfaceItems.add(name); - - Attributes::addDisplayName(commandStation, DisplayName::Hardware::commandStation); - Attributes::addEnabled(commandStation, editable); - Attributes::addObjectList(commandStation, world->commandStations); - m_interfaceItems.add(commandStation); - - Attributes::addDisplayName(active, DisplayName::Controller::active); - m_interfaceItems.add(active); - - Attributes::addDisplayName(notes, DisplayName::Object::notes); - m_interfaceItems.add(notes); -} - -void Controller::addToWorld() -{ - IdObject::addToWorld(); - - if(auto world = m_world.lock()) - world->controllers->addObject(shared_ptr()); -} - -void Controller::destroying() -{ - if(commandStation.value()) - commandStation = nullptr; - if(auto world = m_world.lock()) - world->controllers->removeObject(shared_ptr()); - IdObject::destroying(); -} - -void Controller::worldEvent(WorldState state, WorldEvent event) -{ - IdObject::worldEvent(state, event); - - const bool editable = contains(state, WorldState::Edit); - - Attributes::setEnabled(commandStation, editable); - - try - { - switch(event) - { - case WorldEvent::Offline: - active = false; - break; - - case WorldEvent::Online: - active = true; - break; - - default: - break; - } - } - catch(...) - { - } -} diff --git a/server/src/hardware/controller/controller.hpp b/server/src/hardware/controller/controller.hpp deleted file mode 100644 index 36752e84..00000000 --- a/server/src/hardware/controller/controller.hpp +++ /dev/null @@ -1,59 +0,0 @@ -/** - * server/src/hardware/controller/controller.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_CONTROLLER_CONTROLLER_HPP -#define TRAINTASTIC_SERVER_HARDWARE_CONTROLLER_CONTROLLER_HPP - -#include "../../core/idobject.hpp" -#include "../../core/property.hpp" -#include "../../core/objectproperty.hpp" -#include "../../core/commandstationproperty.hpp" - -class Decoder; -enum class DecoderChangeFlags; - -class Controller : public IdObject -{ - friend class CommandStation; - - protected: - void addToWorld() final; - void destroying() final; - void worldEvent(WorldState state, WorldEvent event) override; - - virtual bool setActive(bool& value) = 0; - - virtual void emergencyStopChanged(bool value) = 0; - virtual void powerOnChanged(bool value) = 0; - virtual void decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber) = 0; - - public: - Property name; - CommandStationProperty commandStation; - Property active; - Property notes; - - Controller(const std::weak_ptr& _world, std::string_view _id); -}; - -#endif diff --git a/server/src/hardware/controller/controllerlist.cpp b/server/src/hardware/controller/controllerlist.cpp deleted file mode 100644 index 7fc4ad34..00000000 --- a/server/src/hardware/controller/controllerlist.cpp +++ /dev/null @@ -1,85 +0,0 @@ -/** - * server/src/hardware/controller/controllerlist.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 "controllerlist.hpp" -#include "controllerlisttablemodel.hpp" -#include "controllers.hpp" -#include "../commandstation/commandstation.hpp" -#include "../../world/getworld.hpp" -#include "../../core/attributes.hpp" -#include "../../utils/displayname.hpp" - -ControllerList::ControllerList(Object& _parent, const std::string& parentPropertyName) : - ObjectList(_parent, parentPropertyName), - add{*this, "add", - [this](std::string_view controllerClassId) - { - auto world = getWorld(&this->parent()); - if(!world) - return std::shared_ptr(); - auto controller = Controllers::create(world, controllerClassId, world->getUniqueId("controller")); - if(auto* cs = dynamic_cast(&this->parent())) - controller->commandStation = cs->shared_ptr(); - //else if(world->commandStations->length() == 1) - // decoder->commandStation = cs->shared_ptr(); - return controller; - }} - , remove{*this, "remove", - [this](const std::shared_ptr& controller) - { - if(containsObject(controller)) - controller->destroy(); - assert(!containsObject(controller)); - }} -{ - 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, Controllers::classList); - m_interfaceItems.add(add); - - Attributes::addDisplayName(remove, DisplayName::List::remove); - Attributes::addEnabled(remove, editable); - m_interfaceItems.add(remove); -} - -TableModelPtr ControllerList::getModel() -{ - return std::make_shared(*this); -} - -void ControllerList::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 ControllerList::isListedProperty(const std::string& name) -{ - return ControllerListTableModel::isListedProperty(name); -} diff --git a/server/src/hardware/controller/usbxpressnetcontroller.cpp b/server/src/hardware/controller/usbxpressnetcontroller.cpp deleted file mode 100644 index 87228698..00000000 --- a/server/src/hardware/controller/usbxpressnetcontroller.cpp +++ /dev/null @@ -1,96 +0,0 @@ -/** - * server/src/hardware/controller/usbxpressnetcontroller.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 "usbxpressnetcontroller.hpp" -#include "../../core/traintastic.hpp" -#include "../../world/world.hpp" -#include "../../core/attributes.hpp" - -USBXpressNetController::USBXpressNetController(const std::weak_ptr& world, std::string_view _id) : - Controller(world, _id), - m_handle{nullptr}, - serial{this, "serial", "", PropertyFlags::ReadWrite | PropertyFlags::Store}, - mode{this, "mode", USBXpressNetControllerMode::Direct, PropertyFlags::ReadWrite | PropertyFlags::Store} -{ - name = "USB XpressNet controller"; - - m_interfaceItems.insertBefore(serial, notes); - Attributes::addValues(mode, USBXpressNetControllerModeValues); - m_interfaceItems.insertBefore(mode, notes); - - usbxpressnet_init(); -} - -USBXpressNetController::~USBXpressNetController() -{ - if(m_handle) - { - usbxpressnet_reset(m_handle); - usbxpressnet_close(m_handle); - } - usbxpressnet_fini(); -} - -bool USBXpressNetController::setActive(bool& value) -{ - if(!m_handle && value) - { - usbxpressnet_status status = usbxpressnet_open(!serial.value().empty() ? serial.value().c_str() : nullptr, &m_handle); - if(status != USBXPRESSNET_STATUS_SUCCESS) - { - // \todo Log::log(*this, LogMessage::E0001_X, std::string("usbxpressnet_open: ") + usbxpressnet_status_str(status)); - return false; - } - - status = usbxpressnet_reset(m_handle); - if(status != USBXPRESSNET_STATUS_SUCCESS) - { - // \todo Log::log(*this, LogMessage::E0001_X, std::string("usbxpressnet_reset: ") + usbxpressnet_status_str(status)); - return false; - } - - status = usbxpressnet_set_mode(m_handle, USBXPRESSNET_MODE_STATION, 0); - if(status != USBXPRESSNET_STATUS_SUCCESS) - { - // \todo Log::log(*this, LogMessage::E0001_X, std::string("usbxpressnet_set_mode: ") + usbxpressnet_status_str(status)); - return false; - } - } - else if(m_handle && !value) - { - usbxpressnet_close(m_handle); - m_handle = nullptr; - } - return true; -} - -void USBXpressNetController::emergencyStopChanged(bool value) -{ -} - -void USBXpressNetController::trackPowerChanged(bool value) -{ -} - -void USBXpressNetController::decoderChanged(const Decoder& decoder, DecoderChangeFlags, uint32_t) -{ -} diff --git a/server/src/hardware/controller/usbxpressnetcontroller.hpp b/server/src/hardware/controller/usbxpressnetcontroller.hpp deleted file mode 100644 index e7179893..00000000 --- a/server/src/hardware/controller/usbxpressnetcontroller.hpp +++ /dev/null @@ -1,52 +0,0 @@ -/** - * server/src/hardware/controller/usbxpressnetcontroller.hpp - * - * This file is part of the traintastic source code. - * - * Copyright (C) 2019-2020 Reinder Feenstra - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef TRAINTASTIC_SERVER_HARDWARE_CONTROLLER_USBXPRESSNETCONTROLLER_HPP -#define TRAINTASTIC_SERVER_HARDWARE_CONTROLLER_USBXPRESSNETCONTROLLER_HPP - -#include "controller.hpp" -#include -#include "../../enum/usbxpressnetcontrollermode.hpp" - -class USBXpressNetController : public Controller -{ - protected: - usbxpressnet_handle m_handle; - - bool setActive(bool& value) final; - - void emergencyStopChanged(bool value) final; - void trackPowerChanged(bool value) final; - void decoderChanged(const Decoder& decoder, DecoderChangeFlags, uint32_t) final; - - public: - CLASS_ID("controller.usb_xpressnet_controller") - CREATE(USBXpressNetController) - - Property serial; - Property mode; - - USBXpressNetController(const std::weak_ptr& world, std::string_view _id); - ~USBXpressNetController() final; -}; - -#endif diff --git a/server/src/hardware/controller/wlanmaus.cpp b/server/src/hardware/controller/wlanmaus.cpp deleted file mode 100644 index 75520884..00000000 --- a/server/src/hardware/controller/wlanmaus.cpp +++ /dev/null @@ -1,437 +0,0 @@ -/** - * server/src/hardware/controller/wlanmaus.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 "wlanmaus.hpp" -#include "../../core/traintastic.hpp" -#include "../../core/eventloop.hpp" -#include "../commandstation/commandstation.hpp" -#include "../decoder/decoder.hpp" -#include "../protocol/z21/messages.hpp" -#include "../../utils/tohex.hpp" -#include "../../core/attributes.hpp" -#include "../../log/log.hpp" -#include "../../utils/displayname.hpp" - -static std::string toString(const boost::asio::ip::udp::endpoint& endpoint) -{ - std::stringstream ss; - ss << endpoint; - return ss.str(); -} - -WLANmaus::WLANmaus(const std::weak_ptr world, std::string_view _id) : - Controller(world, _id), - m_socket{Traintastic::instance->ioContext()}, - m_blockLocoInfo{nullptr}, - m_debugLog{false}, - port{this, "port", 21105, PropertyFlags::ReadWrite | PropertyFlags::Store}, - debugLog{this, "debug_log", m_debugLog, PropertyFlags::ReadWrite | PropertyFlags::Store, - [this](bool value) - { - m_debugLog = value; - }} -{ - Attributes::addDisplayName(port, DisplayName::IP::port); - Attributes::addEnabled(port, !active); - m_interfaceItems.add(port); - - m_interfaceItems.add(debugLog); -} - -bool WLANmaus::setActive(bool& value) -{ - if(!m_socket.is_open() && value) - { - boost::system::error_code ec; - - if(m_socket.open(boost::asio::ip::udp::v4(), ec)) - { - Log::log(*this, LogMessage::E2004_SOCKET_OPEN_FAILED_X, ec); - return false; - } - else if(m_socket.bind(boost::asio::ip::udp::endpoint(boost::asio::ip::address_v4::any(), port), ec)) - { - m_socket.close(); - Log::log(*this, LogMessage::E2006_SOCKET_BIND_FAILED_X, ec); - return false; - } - - receive(); // start receiving messages - - // TODO: send message were alive ?? - - Attributes::setEnabled(port, false); - } - else if(m_socket.is_open() && !value) - { - Attributes::setEnabled(port, true); - - m_socket.close(); - } - return true; -} - -void WLANmaus::emergencyStopChanged(bool value) -{ - if(value) - { - for(auto it : m_clients) - if(it.second.broadcastFlags & Z21::PowerLocoTurnout) - sendTo(Z21::LanXBCStopped(), it.first); - } - else if(commandStation && commandStation->powerOn) // send z21_lan_x_bc_track_power_on if power is on - { - for(auto it : m_clients) - if(it.second.broadcastFlags & Z21::PowerLocoTurnout) - sendTo(Z21::LanXBCTrackPowerOn(), it.first); - } -} - -void WLANmaus::powerOnChanged(bool value) -{ - if(value) - { - for(auto it : m_clients) - if(it.second.broadcastFlags & Z21::PowerLocoTurnout) - sendTo(Z21::LanXBCTrackPowerOn(), it.first); - } - else - { - for(auto it : m_clients) - if(it.second.broadcastFlags & Z21::PowerLocoTurnout) - sendTo(Z21::LanXBCTrackPowerOff(), it.first); - } -} - -void WLANmaus::decoderChanged(const Decoder& decoder, DecoderChangeFlags, uint32_t) -{ - if(&decoder == m_blockLocoInfo) - return; - - EventLoop::call( - [this, dec=decoder.shared_ptr_c()]() - { - broadcastLocoInfo(*dec); - }); -} - -void WLANmaus::receive() -{ - m_socket.async_receive_from(boost::asio::buffer(m_receiveBuffer), m_receiveEndpoint, - [this](const boost::system::error_code& ec, std::size_t bytesReceived) - { - if(!ec) - { - if((bytesReceived >= sizeof(Z21::Message))) - { - const Z21::Message* message = reinterpret_cast(m_receiveBuffer.data()); - - if(m_debugLog) - EventLoop::call( - [this, src=toString(m_receiveEndpoint), data=Z21::toString(*message)]() - { - Log::log(*this, LogMessage::D2005_X_RX_X, src, data); - }); - - switch(message->header()) - { - case Z21::LAN_X: - { - // TODO check XOR - const uint8_t xheader = static_cast(message)->xheader; - switch(xheader) - { - case 0x21: - if(*message == Z21::LanXGetStatus()) - { - EventLoop::call( - [this, endpoint=m_receiveEndpoint]() - { - Z21::LanXStatusChanged response; - - if(!commandStation || commandStation->emergencyStop) - response.db1 |= Z21_CENTRALSTATE_EMERGENCYSTOP; - if(!commandStation || !commandStation->powerOn) - response.db1 |= Z21_CENTRALSTATE_TRACKVOLTAGEOFF; - - response.calcChecksum(); - - sendTo(response, endpoint); - }); - } - else if(*message == Z21::LanXSetTrackPowerOn()) - { - EventLoop::call( - [this, endpoint=m_receiveEndpoint]() - { - if(commandStation) - { - commandStation->emergencyStop = false; - commandStation->powerOn = true; - if(commandStation->powerOn) - sendTo(Z21::LanXBCTrackPowerOn(), endpoint); - } - }); - } - else if(*message == Z21::LanXSetTrackPowerOff()) - { - EventLoop::call( - [this, endpoint=m_receiveEndpoint]() - { - if(commandStation) - { - commandStation->powerOn = false; - if(!commandStation->powerOn) - sendTo(Z21::LanXBCTrackPowerOff(), endpoint); - } - }); - } - break; - - case 0x80: - if(*message == Z21::LanXSetStop()) - { - EventLoop::call( - [this, endpoint=m_receiveEndpoint]() - { - if(commandStation) - { - commandStation->emergencyStop = true; - if(commandStation->emergencyStop) - sendTo(Z21::LanXBCStopped(), endpoint); - } - }); - } - break; - - case 0xE3: - if(const Z21::LanXGetLocoInfo* r = static_cast(message); - r->db0 == 0xF0) - { - EventLoop::call( - [this, request=*r, endpoint=m_receiveEndpoint]() - { - if(commandStation) - if(auto decoder = commandStation->getDecoder(DecoderProtocol::DCC, request.address(), request.isLongAddress())) - { - m_clients[endpoint].locoInfo.insert(locoInfoKey(request.address(), request.isLongAddress())); - sendTo(Z21::LanXLocoInfo(*decoder), endpoint); - } - }); - } - break; - - case 0xE4: - if(const Z21::LanXSetLocoDrive* locoDrive = static_cast(message); - locoDrive->db0 >= 0x10 && locoDrive->db0 <= 0x13) - { - EventLoop::call( - [this, request=*locoDrive]() - { - if(!commandStation) - return; - - if(auto decoder = commandStation->getDecoder(DecoderProtocol::DCC, request.address(), request.isLongAddress())) - { - //m_blockLocoInfo = decoder.get(); - decoder->direction = request.direction(); - decoder->emergencyStop = request.isEmergencyStop(); - decoder->throttle = Decoder::speedStepToThrottle(request.speedStep(), request.speedSteps()); - //broadcastLocoInfo(*decoder); - //m_blockLocoInfo = nullptr; - } - else - Log::log(*this, LogMessage::I2001_UNKNOWN_LOCO_ADDRESS_X, request.address()); - }); - } - else if(const Z21::LanXSetLocoFunction* locoFunction = static_cast(message); - locoFunction->db0 == 0xF8 && - locoFunction->switchType() != Z21::LanXSetLocoFunction::SwitchType::Invalid) - { - EventLoop::call( - [this, request=*locoFunction]() - { - if(commandStation) - if(auto decoder = commandStation->getDecoder(DecoderProtocol::DCC, request.address(), request.isLongAddress())) - if(auto function = decoder->getFunction(request.functionIndex())) - switch(request.switchType()) - { - case Z21::LanXSetLocoFunction::SwitchType::Off: - function->value = false; - break; - - case Z21::LanXSetLocoFunction::SwitchType::On: - function->value = true; - break; - - case Z21::LanXSetLocoFunction::SwitchType::Toggle: - function->value = !function->value; - break; - - case Z21::LanXSetLocoFunction::SwitchType::Invalid: - assert(false); - break; - } - }); - } - break; - - case 0xF1: - if(*message == Z21::LanXGetFirmwareVersion()) - { - EventLoop::call( - [this, endpoint=m_receiveEndpoint]() - { - sendTo(Z21::LanXGetFirmwareVersionReply(1, 30), endpoint); - }); - } - break; - } - break; - } - case Z21::LAN_GET_LOCO_MODE: - if(message->dataLen() == sizeof(Z21::LanGetLocoMode)) - { - // TODO: reply without invoking event loop - EventLoop::call( - [this, address=static_cast(message)->address(), endpoint=m_receiveEndpoint]() - { - sendTo(Z21::LanGetLocoModeReply(address, Z21::LocoMode::DCC), endpoint); - }); - } - break; - - case Z21::LAN_SET_LOCO_MODE: - if(message->dataLen() == sizeof(Z21::LanSetLocoMode)) - { - // ignore, we always report DCC - } - break; - - case Z21::LAN_GET_SERIAL_NUMBER: - if(message->dataLen() == sizeof(Z21::LanGetSerialNumber)) - { - EventLoop::call( - [this, endpoint=m_receiveEndpoint]() - { - sendTo(Z21::LanGetSerialNumberReply(123456789), endpoint); - }); - } - break; - - case Z21::LAN_GET_HWINFO: - if(message->dataLen() == sizeof(Z21::LanGetHardwareInfo)) - { - EventLoop::call( - [this, endpoint=m_receiveEndpoint]() - { - sendTo(Z21::LanGetHardwareInfoReply(Z21::HWT_Z21_START, 1, 30), endpoint); - }); - } - break; - - case Z21::LAN_SET_BROADCASTFLAGS: - if(message->dataLen() == sizeof(Z21::LanSetBroadcastFlags)) - { - EventLoop::call( - [this, broadcastFlags=static_cast(message)->broadcastFlags(), endpoint=m_receiveEndpoint]() - { - m_clients[endpoint].broadcastFlags = broadcastFlags; - }); - } - break; - - case Z21::LAN_SYSTEMSTATE_GETDATA: - if(message->dataLen() == sizeof(Z21::LanSystemStateGetData)) - { - EventLoop::call( - [this, endpoint=m_receiveEndpoint]() - { - Z21::LanSystemStateDataChanged response; - - if(!commandStation || commandStation->emergencyStop) - response.centralState |= Z21_CENTRALSTATE_EMERGENCYSTOP; - if(!commandStation || !commandStation->powerOn) - response.centralState |= Z21_CENTRALSTATE_TRACKVOLTAGEOFF; - - sendTo(response, endpoint); - }); - } - break; - - case Z21::LAN_LOGOFF: - if(message->dataLen() == sizeof(Z21::LanLogoff)) - { - EventLoop::call( - [this, endpoint=m_receiveEndpoint]() - { - m_clients.erase(endpoint); - }); - } - break; - - default: - break; - } - } - receive(); - } - else - EventLoop::call( - [this, ec]() - { - Log::log(*this, LogMessage::E2009_SOCKET_RECEIVE_FAILED_X, ec); - }); - }); -} - -void WLANmaus::sendTo(const Z21::Message& message, const boost::asio::ip::udp::endpoint& endpoint) -{ - if(debugLog) - Log::log(*this, LogMessage::D2004_X_TX_X, toString(endpoint), Z21::toString(message)); - - // TODO: add to queue, send async - - boost::system::error_code ec; - m_socket.send_to(boost::asio::buffer(&message, message.dataLen()), endpoint, 0, ec); - if(ec) - EventLoop::call([this, ec](){ Log::log(*this, LogMessage::E2011_SOCKET_SEND_FAILED_X, ec); }); -/* - m_socket.async_send_to(boost::asio::buffer(&msg, msg.dataLen), endpoint, - [this](const boost::system::error_code& ec, std::size_t) - { - if(ec) - EventLoop::call([this, ec](){ Log::log(*this, LogMessage::E2011_SOCKET_SEND_FAILED_X, ec); }); - }); - */ -} - -void WLANmaus::broadcastLocoInfo(const Decoder& decoder) -{ - const uint16_t key = locoInfoKey(decoder.address, decoder.longAddress); - const Z21::LanXLocoInfo message(decoder); - - for(auto it : m_clients) - if(it.second.broadcastFlags & Z21::PowerLocoTurnout) - if(it.second.locoInfo.count(key)) - sendTo(message, it.first); -} diff --git a/server/src/hardware/controller/wlanmaus.hpp b/server/src/hardware/controller/wlanmaus.hpp deleted file mode 100644 index fc72d667..00000000 --- a/server/src/hardware/controller/wlanmaus.hpp +++ /dev/null @@ -1,82 +0,0 @@ -/** - * server/src/hardware/controller/z21app.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_CONTROLLER_WLANMAUS_HPP -#define TRAINTASTIC_SERVER_HARDWARE_CONTROLLER_WLANMAUS_HPP - -#include "controller.hpp" -#include -#include -#include - -struct z21_lan_header; - -namespace Z21 { - enum BroadcastFlags : uint32_t; - class Message; -} - -class WLANmaus : public Controller -{ - protected: - struct Client - { - Z21::BroadcastFlags broadcastFlags = static_cast(0); - std::set locoInfo; - }; - - boost::asio::ip::udp::socket m_socket; - boost::asio::ip::udp::endpoint m_receiveEndpoint; - std::array m_receiveBuffer; - std::map m_clients; - Decoder* m_blockLocoInfo; - std::atomic_bool m_debugLog; - - constexpr uint16_t locoInfoKey(uint16_t address, bool longAddress) - { - if(longAddress) - return address | 0xC000; - else - return address; - } - - bool setActive(bool& value) final; - - void emergencyStopChanged(bool value) final; - void powerOnChanged(bool value) final; - void decoderChanged(const Decoder& decoder, DecoderChangeFlags, uint32_t) final; - - void receive(); - void sendTo(const Z21::Message& message, const boost::asio::ip::udp::endpoint& endpoint); - void broadcastLocoInfo(const Decoder& decoder); - - public: - CLASS_ID("controller.wlanmaus") - CREATE(WLANmaus); - - Property port; - Property debugLog; - - WLANmaus(const std::weak_ptr world, std::string_view _id); -}; - -#endif diff --git a/server/src/hardware/decoder/decoder.cpp b/server/src/hardware/decoder/decoder.cpp index 933ca1f3..14a5d540 100644 --- a/server/src/hardware/decoder/decoder.cpp +++ b/server/src/hardware/decoder/decoder.cpp @@ -3,7 +3,7 @@ * * This file is part of the traintastic source code. * - * Copyright (C) 2019-2021 Reinder Feenstra + * Copyright (C) 2019-2022 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -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,16 @@ 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); + decoderChanged(*this, changes, functionNumber); } diff --git a/server/src/hardware/decoder/decoder.hpp b/server/src/hardware/decoder/decoder.hpp index 29dc9964..df97f0d7 100644 --- a/server/src/hardware/decoder/decoder.hpp +++ b/server/src/hardware/decoder/decoder.hpp @@ -3,7 +3,7 @@ * * This file is part of the traintastic source code. * - * Copyright (C) 2019-2021 Reinder Feenstra + * Copyright (C) 2019-2022 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -26,11 +26,15 @@ #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" +#ifdef interface + #undef interface // interface is defined in combaseapi.h +#endif + enum class DecoderChangeFlags; class DecoderFunction; @@ -43,6 +47,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 +79,7 @@ class Decoder : public IdObject static const std::shared_ptr null; Property name; - CommandStationProperty commandStation; + ObjectProperty interface; Property protocol; Property address; Property longAddress; @@ -85,6 +90,8 @@ class Decoder : public IdObject ObjectProperty functions; Property notes; + boost::signals2::signal decoderChanged; + Decoder(const std::weak_ptr& world, std::string_view _id); void addToWorld() final; diff --git a/server/src/hardware/decoder/decodercontroller.cpp b/server/src/hardware/decoder/decodercontroller.cpp new file mode 100644 index 00000000..4962bbfb --- /dev/null +++ b/server/src/hardware/decoder/decodercontroller.cpp @@ -0,0 +1,92 @@ +/** + * 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" +#include "decoderchangeflags.hpp" +#include "../../utils/almostzero.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; + }); + } +} + +void DecoderController::restoreDecoderSpeed() +{ + for(const auto& decoder : m_decoders) + if(!decoder->emergencyStop && !almostZero(decoder->throttle.value())) + decoderChanged(*decoder, DecoderChangeFlags::Throttle, 0); +} diff --git a/server/src/hardware/decoder/decodercontroller.hpp b/server/src/hardware/decoder/decodercontroller.hpp new file mode 100644 index 00000000..448142e0 --- /dev/null +++ b/server/src/hardware/decoder/decodercontroller.hpp @@ -0,0 +1,57 @@ +/** + * 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); + + /// \brief restore speed of all decoders that are not (emergency) stopped + void restoreDecoderSpeed(); + + 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..fa764e73 100644 --- a/server/src/hardware/decoder/decoderlist.cpp +++ b/server/src/hardware/decoder/decoderlist.cpp @@ -3,7 +3,7 @@ * * This file is part of the traintastic source code. * - * Copyright (C) 2019-2020 Reinder Feenstra + * Copyright (C) 2019-2022 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -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", @@ -69,6 +69,33 @@ TableModelPtr DecoderList::getModel() return std::make_shared(*this); } +std::shared_ptr DecoderList::getDecoder(uint16_t address) const +{ + auto it = std::find_if(begin(), end(), + [address](const auto& decoder) + { + return decoder->address.value() == address; + }); + if(it != end()) + return *it; + return {}; +} + +std::shared_ptr DecoderList::getDecoder(DecoderProtocol protocol, uint16_t address, bool longAddress) const +{ + auto it = std::find_if(begin(), end(), + [protocol, address, longAddress](const auto& decoder) + { + return + decoder->protocol.value() == protocol && + decoder->address.value() == address && + (protocol != DecoderProtocol::DCC || decoder->longAddress == longAddress); + }); + if(it != end()) + return *it; + return {}; +} + void DecoderList::worldEvent(WorldState state, WorldEvent event) { ObjectList::worldEvent(state, event); diff --git a/server/src/hardware/decoder/decoderlist.hpp b/server/src/hardware/decoder/decoderlist.hpp index 570bcf71..910669c9 100644 --- a/server/src/hardware/decoder/decoderlist.hpp +++ b/server/src/hardware/decoder/decoderlist.hpp @@ -3,7 +3,7 @@ * * This file is part of the traintastic source code. * - * Copyright (C) 2019-2020 Reinder Feenstra + * Copyright (C) 2019-2022 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -42,6 +42,9 @@ class DecoderList : public ObjectList DecoderList(Object& _parent, const std::string& parentPropertyName); TableModelPtr getModel() final; + + std::shared_ptr getDecoder(uint16_t address) const; + std::shared_ptr getDecoder(DecoderProtocol protocol, uint16_t address, bool longAddress = false) const; }; #endif 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..c5750ba8 100644 --- a/server/src/hardware/input/input.hpp +++ b/server/src/hardware/input/input.hpp @@ -27,13 +27,23 @@ #include "../../core/objectproperty.hpp" #include "../../core/objectvectorproperty.hpp" #include "../../enum/tristate.hpp" +#include "inputcontroller.hpp" + +#ifdef interface + #undef interface // interface is defined in combaseapi.h +#endif 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 +51,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..5a6c139a 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/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/dccplusplusinterface.cpp b/server/src/hardware/interface/dccplusplusinterface.cpp new file mode 100644 index 00000000..b5714341 --- /dev/null +++ b/server/src/hardware/interface/dccplusplusinterface.cpp @@ -0,0 +1,263 @@ +/** + * server/src/hardware/interface/dccplusplusinterface.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 "dccplusplusinterface.hpp" +#include "../input/list/inputlisttablemodel.hpp" +#include "../output/list/outputlisttablemodel.hpp" +#include "../protocol/dccplusplus/messages.hpp" +#include "../protocol/dccplusplus/iohandler/serialiohandler.hpp" +#include "../../core/attributes.hpp" +#include "../../log/log.hpp" +#include "../../log/logmessageexception.hpp" +#include "../../utils/displayname.hpp" +#include "../../utils/inrange.hpp" +#include "../../utils/serialport.hpp" +#include "../../world/world.hpp" + +DCCPlusPlusInterface::DCCPlusPlusInterface(const std::weak_ptr& world, std::string_view _id) + : Interface(world, _id) + , device{this, "device", "", PropertyFlags::ReadWrite | PropertyFlags::Store} + , baudrate{this, "baudrate", 115200, PropertyFlags::ReadWrite | PropertyFlags::Store} + , flowControl{this, "flow_control", SerialFlowControl::None, PropertyFlags::ReadWrite | PropertyFlags::Store} + , dccplusplus{this, "dccplusplus", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject} + , decoders{this, "decoders", nullptr, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::SubObject} +{ + dccplusplus.setValueInternal(std::make_shared(*this, dccplusplus.name())); + decoders.setValueInternal(std::make_shared(*this, decoders.name())); + + Attributes::addDisplayName(device, DisplayName::Serial::device); + Attributes::addEnabled(device, !online); + m_interfaceItems.insertBefore(device, notes); + + Attributes::addDisplayName(baudrate, DisplayName::Serial::baudrate); + Attributes::addEnabled(baudrate, !online); + Attributes::addMinMax(baudrate, SerialPort::baudrateMin, SerialPort::baudrateMax); + Attributes::addValues(baudrate, SerialPort::baudrateValues); + m_interfaceItems.insertBefore(baudrate, notes); + + Attributes::addDisplayName(flowControl, DisplayName::Serial::flowControl); + Attributes::addEnabled(flowControl, !online); + Attributes::addValues(flowControl, SerialFlowControlValues); + m_interfaceItems.insertBefore(flowControl, notes); + + Attributes::addDisplayName(dccplusplus, DisplayName::Hardware::dccplusplus); + m_interfaceItems.insertBefore(dccplusplus, notes); + + Attributes::addDisplayName(decoders, DisplayName::Hardware::decoders); + m_interfaceItems.insertBefore(decoders, notes); +} + +bool DCCPlusPlusInterface::addDecoder(Decoder& decoder) +{ + const bool success = DecoderController::addDecoder(decoder); + if(success) + decoders->addObject(decoder.shared_ptr()); + return success; +} + +bool DCCPlusPlusInterface::removeDecoder(Decoder& decoder) +{ + const bool success = DecoderController::removeDecoder(decoder); + if(success) + decoders->removeObject(decoder.shared_ptr()); + return success; +} + +void DCCPlusPlusInterface::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber) +{ + if(m_kernel) + m_kernel->decoderChanged(decoder, changes, functionNumber); +} + +bool DCCPlusPlusInterface::setOnline(bool& value) +{ + if(!m_kernel && value) + { + try + { + m_kernel = DCCPlusPlus::Kernel::create(dccplusplus->config(), device.value(), baudrate.value(), flowControl.value()); + + status.setValueInternal(InterfaceStatus::Initializing); + + m_kernel->setLogId(id.value()); + m_kernel->setOnStarted( + [this]() + { + status.setValueInternal(InterfaceStatus::Online); + + if(auto w = m_world.lock()) + { + const bool powerOn = contains(w->state.value(), WorldState::PowerOn); + + if(!powerOn) + m_kernel->powerOff(); + + if(contains(w->state.value(), WorldState::Run)) + { + m_kernel->clearEmergencyStop(); + restoreDecoderSpeed(); + } + else + m_kernel->emergencyStop(); + + if(powerOn) + m_kernel->powerOn(); + } + }); + m_kernel->setOnPowerOnChanged( + [this](bool powerOn) + { + if(auto w = m_world.lock()) + { + if(powerOn && !contains(w->state.value(), WorldState::PowerOn)) + w->powerOn(); + else if(!powerOn && contains(w->state.value(), WorldState::PowerOn)) + w->powerOff(); + } + }); + m_kernel->setDecoderController(this); + m_kernel->start(); + + m_dccplusplusPropertyChanged = dccplusplus->propertyChanged.connect( + [this](BaseProperty& property) + { + if(&property != &dccplusplus->startupDelay) + m_kernel->setConfig(dccplusplus->config()); + }); + + Attributes::setEnabled({device, baudrate, flowControl}, false); + } + catch(const LogMessageException& e) + { + status.setValueInternal(InterfaceStatus::Offline); + Log::log(*this, e.message(), e.args()); + return false; + } + } + else if(m_kernel && !value) + { + Attributes::setEnabled({device, baudrate, flowControl}, true); + + m_dccplusplusPropertyChanged.disconnect(); + + m_kernel->stop(); + m_kernel.reset(); + + status.setValueInternal(InterfaceStatus::Offline); + } + return true; +} + +void DCCPlusPlusInterface::addToWorld() +{ + Interface::addToWorld(); + + if(auto world = m_world.lock()) + { + world->decoderControllers->add(std::dynamic_pointer_cast(shared_from_this())); + } +} + +void DCCPlusPlusInterface::loaded() +{ + Interface::loaded(); + + check(); +} + +void DCCPlusPlusInterface::destroying() +{ + for(const auto& decoder : *decoders) + { + assert(decoder->interface.value() == std::dynamic_pointer_cast(shared_from_this())); + decoder->interface = nullptr; + } + + if(auto world = m_world.lock()) + { + world->decoderControllers->remove(std::dynamic_pointer_cast(shared_from_this())); + } + + Interface::destroying(); +} + +void DCCPlusPlusInterface::worldEvent(WorldState state, WorldEvent event) +{ + Interface::worldEvent(state, event); + + if(m_kernel) + { + switch(event) + { + case WorldEvent::PowerOff: + m_kernel->powerOff(); + break; + + case WorldEvent::PowerOn: + m_kernel->powerOn(); + break; + + case WorldEvent::Stop: + m_kernel->emergencyStop(); + break; + + case WorldEvent::Run: + m_kernel->clearEmergencyStop(); + restoreDecoderSpeed(); + break; + + default: + break; + } + } +} + +void DCCPlusPlusInterface::check() const +{ + for(const auto& decoder : *decoders) + checkDecoder(*decoder); +} + +void DCCPlusPlusInterface::checkDecoder(const Decoder& decoder) const +{ + if(decoder.protocol != DecoderProtocol::Auto && decoder.protocol != DecoderProtocol::DCC) + Log::log(decoder, LogMessage::C2002_DCCPLUSPLUS_ONLY_SUPPORTS_THE_DCC_PROTOCOL); + + if(decoder.protocol == DecoderProtocol::DCC && decoder.address <= 127 && decoder.longAddress) + Log::log(decoder, LogMessage::C2003_DCCPLUSPLUS_DOESNT_SUPPORT_DCC_LONG_ADDRESSES_BELOW_128); + + if(decoder.speedSteps != Decoder::speedStepsAuto && decoder.speedSteps != dccplusplus->speedSteps) + Log::log(decoder, LogMessage::W2003_COMMAND_STATION_DOESNT_SUPPORT_X_SPEEDSTEPS_USING_X, decoder.speedSteps.value(), dccplusplus->speedSteps.value()); + + for(const auto& function : *decoder.functions) + if(function->number > DCCPlusPlus::Config::functionNumberMax) + { + Log::log(decoder, LogMessage::W2002_COMMAND_STATION_DOESNT_SUPPORT_FUNCTIONS_ABOVE_FX, DCCPlusPlus::Config::functionNumberMax); + break; + } +} + +void DCCPlusPlusInterface::idChanged(const std::string& newId) +{ + if(m_kernel) + m_kernel->setLogId(newId); +} diff --git a/server/src/hardware/interface/dccplusplusinterface.hpp b/server/src/hardware/interface/dccplusplusinterface.hpp new file mode 100644 index 00000000..2dad70a2 --- /dev/null +++ b/server/src/hardware/interface/dccplusplusinterface.hpp @@ -0,0 +1,77 @@ +/** + * server/src/hardware/interface/dccplusplusinterface.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_DCCPLUSPLUSINTERFACE_HPP +#define TRAINTASTIC_SERVER_HARDWARE_INTERFACE_DCCPLUSPLUSINTERFACE_HPP + +#include "interface.hpp" +#include "../protocol/dccplusplus/kernel.hpp" +#include "../protocol/dccplusplus/settings.hpp" +#include "../decoder/decodercontroller.hpp" +#include "../decoder/decoderlist.hpp" +#include "../../core/objectproperty.hpp" +#include "../../enum/serialflowcontrol.hpp" + +/** + * @brief DCC++(EX) hardware interface + */ +class DCCPlusPlusInterface final + : public Interface + , public DecoderController +{ + CLASS_ID("interface.dccplusplus") + CREATE(DCCPlusPlusInterface) + DEFAULT_ID("dccplusplus") + + private: + std::unique_ptr m_kernel; + boost::signals2::connection m_dccplusplusPropertyChanged; + + void addToWorld() final; + void loaded() final; + void destroying() final; + void worldEvent(WorldState state, WorldEvent event) final; + + void check() const; + void checkDecoder(const Decoder& decoder) const; + + void idChanged(const std::string& newId) final; + + protected: + bool setOnline(bool& value) final; + + public: + Property device; + Property baudrate; + Property flowControl; + ObjectProperty dccplusplus; + ObjectProperty decoders; + + DCCPlusPlusInterface(const std::weak_ptr& world, std::string_view _id); + + // DecoderController: + [[nodiscard]] bool addDecoder(Decoder& decoder) final; + [[nodiscard]] bool removeDecoder(Decoder& decoder) final; + void decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber) final; +}; + +#endif diff --git a/server/src/hardware/interface/ecosinterface.cpp b/server/src/hardware/interface/ecosinterface.cpp new file mode 100644 index 00000000..a25e071b --- /dev/null +++ b/server/src/hardware/interface/ecosinterface.cpp @@ -0,0 +1,266 @@ +/** + * server/src/hardware/interface/ecosinterface.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 "ecosinterface.hpp" +#include "../input/list/inputlisttablemodel.hpp" +#include "../output/list/outputlisttablemodel.hpp" +#include "../protocol/ecos/iohandler/tcpiohandler.hpp" +#include "../../core/attributes.hpp" +#include "../../log/log.hpp" +#include "../../log/logmessageexception.hpp" +#include "../../utils/displayname.hpp" +#include "../../utils/inrange.hpp" +#include "../../world/world.hpp" + +ECoSInterface::ECoSInterface(const std::weak_ptr& world, std::string_view _id) + : Interface(world, _id) + , hostname{this, "hostname", "", PropertyFlags::ReadWrite | PropertyFlags::Store} + , ecos{this, "ecos", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject} + , decoders{this, "decoders", nullptr, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::SubObject} + , inputs{this, "inputs", nullptr, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::SubObject} + , outputs{this, "outputs", nullptr, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::SubObject} + , testCommand{this, "test_command", "", PropertyFlags::ReadWrite | PropertyFlags::NoStore} + , testCommandSend{*this, "test_command_send", + [this]() + { + if(m_kernel) + m_kernel->postSend(testCommand.value() + "\n"); + }} +{ + name = "ECoS"; + ecos.setValueInternal(std::make_shared(*this, ecos.name())); + decoders.setValueInternal(std::make_shared(*this, decoders.name())); + inputs.setValueInternal(std::make_shared(*this, inputs.name())); + outputs.setValueInternal(std::make_shared(*this, outputs.name())); + + Attributes::addDisplayName(hostname, DisplayName::IP::hostname); + Attributes::addEnabled(hostname, !online); + m_interfaceItems.insertBefore(hostname, notes); + + m_interfaceItems.insertBefore(ecos, notes); + + Attributes::addDisplayName(decoders, DisplayName::Hardware::decoders); + m_interfaceItems.insertBefore(decoders, notes); + + Attributes::addDisplayName(inputs, DisplayName::Hardware::inputs); + m_interfaceItems.insertBefore(inputs, notes); + + Attributes::addDisplayName(outputs, DisplayName::Hardware::outputs); + m_interfaceItems.insertBefore(outputs, notes); + + m_interfaceItems.add(testCommand); + m_interfaceItems.add(testCommandSend); +} + +bool ECoSInterface::addDecoder(Decoder& decoder) +{ + const bool success = DecoderController::addDecoder(decoder); + if(success) + decoders->addObject(decoder.shared_ptr()); + return success; +} + +bool ECoSInterface::removeDecoder(Decoder& decoder) +{ + const bool success = DecoderController::removeDecoder(decoder); + if(success) + decoders->removeObject(decoder.shared_ptr()); + return success; +} + +void ECoSInterface::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber) +{ + if(m_kernel) + m_kernel->decoderChanged(decoder, changes, functionNumber); +} + +bool ECoSInterface::addInput(Input& input) +{ + const bool success = InputController::addInput(input); + if(success) + inputs->addObject(input.shared_ptr()); + return success; +} + +bool ECoSInterface::removeInput(Input& input) +{ + const bool success = InputController::removeInput(input); + if(success) + inputs->removeObject(input.shared_ptr()); + return success; +} + +bool ECoSInterface::addOutput(Output& output) +{ + const bool success = OutputController::addOutput(output); + if(success) + outputs->addObject(output.shared_ptr()); + return success; +} + +bool ECoSInterface::removeOutput(Output& output) +{ + const bool success = OutputController::removeOutput(output); + if(success) + outputs->removeObject(output.shared_ptr()); + return success; +} + +bool ECoSInterface::setOutputValue(uint32_t address, bool value) +{ + return + m_kernel && + inRange(address, outputAddressMinMax()) && + m_kernel->setOutput(static_cast(address), value); +} + +bool ECoSInterface::setOnline(bool& value) +{ + if(!m_kernel && value) + { + try + { + m_kernel = ECoS::Kernel::create(ecos->config(), hostname.value()); + + status.setValueInternal(InterfaceStatus::Initializing); + + m_kernel->setLogId(id.value()); + m_kernel->setOnStarted( + [this]() + { + status.setValueInternal(InterfaceStatus::Online); + }); + + m_kernel->setDecoderController(this); + m_kernel->setInputController(this); + m_kernel->setOutputController(this); + m_kernel->start(); + + m_ecosPropertyChanged = ecos->propertyChanged.connect( + [this](BaseProperty&) + { + m_kernel->setConfig(ecos->config()); + }); + + if(auto w = m_world.lock()) + { + if(contains(w->state.value(), WorldState::Run)) + m_kernel->go(); + else + m_kernel->emergencyStop(); + } + + Attributes::setEnabled(hostname, false); + } + catch(const LogMessageException& e) + { + status.setValueInternal(InterfaceStatus::Offline); + Log::log(*this, e.message(), e.args()); + return false; + } + } + else if(m_kernel && !value) + { + Attributes::setEnabled(hostname, true); + + m_ecosPropertyChanged.disconnect(); + + m_kernel->stop(); + m_kernel.reset(); + + status.setValueInternal(InterfaceStatus::Offline); + } + return true; +} + +void ECoSInterface::addToWorld() +{ + Interface::addToWorld(); + + if(auto world = m_world.lock()) + { + world->decoderControllers->add(std::dynamic_pointer_cast(shared_from_this())); + world->inputControllers->add(std::dynamic_pointer_cast(shared_from_this())); + world->outputControllers->add(std::dynamic_pointer_cast(shared_from_this())); + } +} + +void ECoSInterface::destroying() +{ + for(const auto& decoder : *decoders) + { + assert(decoder->interface.value() == std::dynamic_pointer_cast(shared_from_this())); + decoder->interface = nullptr; + } + + for(const auto& input : *inputs) + { + assert(input->interface.value() == std::dynamic_pointer_cast(shared_from_this())); + input->interface = nullptr; + } + + for(const auto& output : *outputs) + { + assert(output->interface.value() == std::dynamic_pointer_cast(shared_from_this())); + output->interface = nullptr; + } + + if(auto world = m_world.lock()) + { + world->decoderControllers->remove(std::dynamic_pointer_cast(shared_from_this())); + world->inputControllers->remove(std::dynamic_pointer_cast(shared_from_this())); + world->outputControllers->remove(std::dynamic_pointer_cast(shared_from_this())); + } + + Interface::destroying(); +} + +void ECoSInterface::worldEvent(WorldState state, WorldEvent event) +{ + Interface::worldEvent(state, event); + + if(m_kernel) + { + switch(event) + { + case WorldEvent::PowerOff: + case WorldEvent::Stop: + m_kernel->emergencyStop(); + break; + + case WorldEvent::PowerOn: + case WorldEvent::Run: + if(contains(state, WorldState::PowerOn | WorldState::Run)) + m_kernel->go(); + break; + + default: + break; + } + } +} + +void ECoSInterface::idChanged(const std::string& newId) +{ + if(m_kernel) + m_kernel->setLogId(newId); +} diff --git a/server/src/hardware/interface/ecosinterface.hpp b/server/src/hardware/interface/ecosinterface.hpp new file mode 100644 index 00000000..762409f2 --- /dev/null +++ b/server/src/hardware/interface/ecosinterface.hpp @@ -0,0 +1,93 @@ +/** + * server/src/hardware/interface/ecosinterface.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_ECOSINTERFACE_HPP +#define TRAINTASTIC_SERVER_HARDWARE_INTERFACE_ECOSINTERFACE_HPP + +#include "interface.hpp" +#include "../protocol/ecos/kernel.hpp" +#include "../protocol/ecos/settings.hpp" +#include "../decoder/decodercontroller.hpp" +#include "../decoder/decoderlist.hpp" +#include "../input/inputcontroller.hpp" +#include "../input/list/inputlist.hpp" +#include "../output/outputcontroller.hpp" +#include "../output/list/outputlist.hpp" +#include "../../core/objectproperty.hpp" + +/** + * @brief ECoS hardware interface + */ +class ECoSInterface final + : public Interface + , public DecoderController + , public InputController + , public OutputController +{ + CLASS_ID("interface.ecos") + DEFAULT_ID("ecos") + CREATE(ECoSInterface) + + private: + std::unique_ptr m_kernel; + boost::signals2::connection m_ecosPropertyChanged; + + void addToWorld() final; + void destroying() final; + void worldEvent(WorldState state, WorldEvent event) final; + + void idChanged(const std::string& newId) final; + + void typeChanged(); + + protected: + bool setOnline(bool& value) final; + + public: + Property hostname; + ObjectProperty ecos; + ObjectProperty decoders; + ObjectProperty inputs; + ObjectProperty outputs; + Property testCommand; + Method testCommandSend; + + ECoSInterface(const std::weak_ptr& world, std::string_view _id); + + // DecoderController: + [[nodiscard]] bool addDecoder(Decoder& decoder) final; + [[nodiscard]] bool removeDecoder(Decoder& decoder) final; + void decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber) final; + + // InputController: + std::pair inputAddressMinMax() const final { return {1, 1}; } + [[nodiscard]] bool addInput(Input& input) final; + [[nodiscard]] bool removeInput(Input& input) final; + + // OutputController: + std::pair outputAddressMinMax() const final { return {1, 1}; } + [[nodiscard]] bool addOutput(Output& output) final; + [[nodiscard]] bool removeOutput(Output& output) final; + [[nodiscard]] bool setOutputValue(uint32_t address, bool value) final; +}; + +#endif diff --git a/server/src/hardware/interface/interface.cpp b/server/src/hardware/interface/interface.cpp new file mode 100644 index 00000000..e8d11d63 --- /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-2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "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::Interface::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/commandstation/commandstationlist.cpp b/server/src/hardware/interface/interfacelist.cpp similarity index 62% rename from server/src/hardware/commandstation/commandstationlist.cpp rename to server/src/hardware/interface/interfacelist.cpp index 742bc380..756a9f17 100644 --- a/server/src/hardware/commandstation/commandstationlist.cpp +++ b/server/src/hardware/interface/interfacelist.cpp @@ -1,9 +1,9 @@ /** - * server/src/hardware/commandstation/commandstationlist.cpp + * server/src/hardware/interface/interfacelist.cpp * * 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,26 +20,26 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "commandstationlist.hpp" -#include "commandstationlisttablemodel.hpp" -#include "commandstations.hpp" +#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" -CommandStationList::CommandStationList(Object& _parent, const std::string& parentPropertyName) : - ObjectList(_parent, parentPropertyName), +InterfaceList::InterfaceList(Object& _parent, const std::string& parentPropertyName) : + ObjectList(_parent, parentPropertyName), add{*this, "add", - [this](std::string_view commandStationClassId) + [this](std::string_view interfaceClassId) { auto world = getWorld(this); if(!world) - return std::shared_ptr(); - return CommandStations::create(world, commandStationClassId, world->getUniqueId("cs")); + return std::shared_ptr(); + return Interfaces::create(world, interfaceClassId); }}, remove{*this, "remove", - [this](const std::shared_ptr& object) + [this](const std::shared_ptr& object) { if(containsObject(object)) object->destroy(); @@ -51,7 +51,7 @@ CommandStationList::CommandStationList(Object& _parent, const std::string& paren Attributes::addDisplayName(add, DisplayName::List::add); Attributes::addEnabled(add, editable); - Attributes::addClassList(add, CommandStations::classList); + Attributes::addClassList(add, Interfaces::classList); m_interfaceItems.add(add); Attributes::addDisplayName(remove, DisplayName::List::remove); @@ -59,14 +59,14 @@ CommandStationList::CommandStationList(Object& _parent, const std::string& paren m_interfaceItems.add(remove); } -TableModelPtr CommandStationList::getModel() +TableModelPtr InterfaceList::getModel() { - return std::make_shared(*this); + return std::make_shared(*this); } -void CommandStationList::worldEvent(WorldState state, WorldEvent event) +void InterfaceList::worldEvent(WorldState state, WorldEvent event) { - ObjectList::worldEvent(state, event); + ObjectList::worldEvent(state, event); const bool editable = contains(state, WorldState::Edit); @@ -74,7 +74,7 @@ void CommandStationList::worldEvent(WorldState state, WorldEvent event) Attributes::setEnabled(remove, editable); } -bool CommandStationList::isListedProperty(const std::string& name) +bool InterfaceList::isListedProperty(const std::string& name) { - return CommandStationListTableModel::isListedProperty(name); + return InterfaceListTableModel::isListedProperty(name); } diff --git a/server/src/hardware/controller/controllerlist.hpp b/server/src/hardware/interface/interfacelist.hpp similarity index 66% rename from server/src/hardware/controller/controllerlist.hpp rename to server/src/hardware/interface/interfacelist.hpp index ed428b25..8b03cd18 100644 --- a/server/src/hardware/controller/controllerlist.hpp +++ b/server/src/hardware/interface/interfacelist.hpp @@ -1,9 +1,9 @@ /** - * server/src/hardware/controller/controllerlist.hpp + * server/src/hardware/interface/interfacelist.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,27 +20,26 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ - -#ifndef TRAINTASTIC_SERVER_HARDWARE_CONTROLLER_CONTROLLERLIST_HPP -#define TRAINTASTIC_SERVER_HARDWARE_CONTROLLER_CONTROLLERLIST_HPP +#ifndef TRAINTASTIC_SERVER_HARDWARE_INTERFACE_INTERFACELIST_HPP +#define TRAINTASTIC_SERVER_HARDWARE_INTERFACE_INTERFACELIST_HPP #include "../../core/objectlist.hpp" #include "../../core/method.hpp" -#include "controller.hpp" +#include "interface.hpp" -class ControllerList : public ObjectList +class InterfaceList : public ObjectList { protected: void worldEvent(WorldState state, WorldEvent event) final; bool isListedProperty(const std::string& name) final; public: - CLASS_ID("controller_list") + CLASS_ID("list.interface") - Method(std::string_view)> add; - Method&)> remove; + Method(std::string_view)> add; + Method&)> remove; - ControllerList(Object& _parent, const std::string& parentPropertyName); + InterfaceList(Object& _parent, const std::string& parentPropertyName); TableModelPtr getModel() final; }; diff --git a/server/src/hardware/controller/controllerlisttablemodel.cpp b/server/src/hardware/interface/interfacelisttablemodel.cpp similarity index 59% rename from server/src/hardware/controller/controllerlisttablemodel.cpp rename to server/src/hardware/interface/interfacelisttablemodel.cpp index 3929966a..5b57e274 100644 --- a/server/src/hardware/controller/controllerlisttablemodel.cpp +++ b/server/src/hardware/interface/interfacelisttablemodel.cpp @@ -1,9 +1,9 @@ /** - * server/src/core/ + * server/src/hardware/interface/interfacelisttablemodel.cpp * - * This file is part of the traintastic source code + * 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,48 +20,48 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "controllerlisttablemodel.hpp" -#include "controllerlist.hpp" +#include "interfacelisttablemodel.hpp" +#include "interfacelist.hpp" #include "../../utils/displayname.hpp" constexpr uint32_t columnId = 0; constexpr uint32_t columnName = 1; -constexpr uint32_t columnActive = 2; +constexpr uint32_t columnStatus = 2; -bool ControllerListTableModel::isListedProperty(const std::string& name) +bool InterfaceListTableModel::isListedProperty(const std::string& name) { return name == "id" || name == "name" || - name == "active"; + name == "status"; } -ControllerListTableModel::ControllerListTableModel(ControllerList& list) : - ObjectListTableModel(list) +InterfaceListTableModel::InterfaceListTableModel(InterfaceList& list) : + ObjectListTableModel(list) { setColumnHeaders({ DisplayName::Object::id, DisplayName::Object::name, - DisplayName::Controller::active, + DisplayName::Interface::status, }); } -std::string ControllerListTableModel::getText(uint32_t column, uint32_t row) const +std::string InterfaceListTableModel::getText(uint32_t column, uint32_t row) const { if(row < rowCount()) { - const Controller& controller = getItem(row); + const Interface& interface = getItem(row); switch(column) { case columnId: - return controller.id; + return interface.id; case columnName: - return controller.name; + return interface.name; - case columnActive: - return controller.active ? "\u2022" : ""; + case columnStatus: + return ""; default: assert(false); @@ -72,12 +72,12 @@ std::string ControllerListTableModel::getText(uint32_t column, uint32_t row) con return ""; } -void ControllerListTableModel::propertyChanged(BaseProperty& property, uint32_t row) +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() == "active") - changed(row, columnActive); + else if(property.name() == "status") + changed(row, columnStatus); } diff --git a/server/src/hardware/controller/controllerlisttablemodel.hpp b/server/src/hardware/interface/interfacelisttablemodel.hpp similarity index 67% rename from server/src/hardware/controller/controllerlisttablemodel.hpp rename to server/src/hardware/interface/interfacelisttablemodel.hpp index 0f0a3ec2..629bb2e9 100644 --- a/server/src/hardware/controller/controllerlisttablemodel.hpp +++ b/server/src/hardware/interface/interfacelisttablemodel.hpp @@ -1,9 +1,9 @@ /** - * server/src/hardware/commandstation/controllerlisttablemodel.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,27 +20,27 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef TRAINTASTIC_SERVER_HARDWARE_CONTROLLER_CONTROLLERLISTTABLEMODEL_HPP -#define TRAINTASTIC_SERVER_HARDWARE_CONTROLLER_CONTROLLERLISTTABLEMODEL_HPP +#ifndef TRAINTASTIC_SERVER_HARDWARE_INTERFACE_INTERFACELISTTABLEMODEL_HPP +#define TRAINTASTIC_SERVER_HARDWARE_INTERFACE_INTERFACELISTTABLEMODEL_HPP #include "../../core/objectlisttablemodel.hpp" -#include "controllerlist.hpp" +#include "interfacelist.hpp" -class ControllerList; +class InterfaceList; -class ControllerListTableModel : public ObjectListTableModel +class InterfaceListTableModel : public ObjectListTableModel { - friend class ControllerList; + friend class InterfaceList; protected: void propertyChanged(BaseProperty& property, uint32_t row) final; public: - CLASS_ID("controller_list_table_model") + CLASS_ID("table_model.list.interface") static bool isListedProperty(const std::string& name); - ControllerListTableModel(ControllerList& list); + InterfaceListTableModel(InterfaceList& interfaceList); std::string getText(uint32_t column, uint32_t row) const final; }; diff --git a/server/src/hardware/controller/controllers.cpp b/server/src/hardware/interface/interfaces.cpp similarity index 62% rename from server/src/hardware/controller/controllers.cpp rename to server/src/hardware/interface/interfaces.cpp index 9061cd0e..f327dd7e 100644 --- a/server/src/hardware/controller/controllers.cpp +++ b/server/src/hardware/interface/interfaces.cpp @@ -1,9 +1,9 @@ /** - * server/src/hardware/controller/controllers.cpp + * server/src/hardware/interface/interfaces.cpp * * This file is part of the traintastic source code. * - * Copyright (C) 2019-2020 Reinder Feenstra + * Copyright (C) 2021-2022 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -20,16 +20,17 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "controllers.hpp" +#include "interfaces.hpp" +#include "../../utils/ifclassidcreate.hpp" +#include "../../world/world.hpp" -std::shared_ptr Controllers::create(const std::weak_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 == WLANmaus::classId) - return WLANmaus::create(world, id); -#ifdef USB_XPRESSNET - else if(classId == USBXpressNetController::classId) - return USBXpressNetController::create(world, id); -#endif - else - return std::shared_ptr(); + IF_CLASSID_CREATE(DCCPlusPlusInterface) + IF_CLASSID_CREATE(ECoSInterface) + IF_CLASSID_CREATE(LocoNetInterface) + IF_CLASSID_CREATE(WlanMausInterface) + IF_CLASSID_CREATE(XpressNetInterface) + IF_CLASSID_CREATE(Z21Interface) + return std::shared_ptr(); } diff --git a/server/src/hardware/controller/controllers.hpp b/server/src/hardware/interface/interfaces.hpp similarity index 51% rename from server/src/hardware/controller/controllers.hpp rename to server/src/hardware/interface/interfaces.hpp index 1d9171b3..bd3431d4 100644 --- a/server/src/hardware/controller/controllers.hpp +++ b/server/src/hardware/interface/interfaces.hpp @@ -1,9 +1,9 @@ /** - * server/src/hardware/controller/controllers.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-2022 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -20,29 +20,33 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef TRAINTASTIC_SERVER_HARDWARE_CONTROLLER_CONTROLLERS_HPP -#define TRAINTASTIC_SERVER_HARDWARE_CONTROLLER_CONTROLLERS_HPP +#ifndef TRAINTASTIC_SERVER_HARDWARE_INTERFACE_INTERFACES_HPP +#define TRAINTASTIC_SERVER_HARDWARE_INTERFACE_INTERFACES_HPP -#include "controller.hpp" +#include "interface.hpp" #include "../../utils/makearray.hpp" -#include "wlanmaus.hpp" -#ifdef USB_XPRESSNET - #include "usbxpressnetcontroller.hpp" -#endif +#include "dccplusplusinterface.hpp" +#include "ecosinterface.hpp" +#include "loconetinterface.hpp" +#include "wlanmausinterface.hpp" +#include "xpressnetinterface.hpp" +#include "z21interface.hpp" -struct Controllers +struct Interfaces { - static constexpr std::string_view classIdPrefix = "controller."; + static constexpr std::string_view classIdPrefix = "interface."; static constexpr auto classList = makeArray( -#ifdef USB_XPRESSNET - USBXpressNetController::classId, -#endif - WLANmaus::classId + DCCPlusPlusInterface::classId, + ECoSInterface::classId, + LocoNetInterface::classId, + WlanMausInterface::classId, + XpressNetInterface::classId, + Z21Interface::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 = std::string_view{}); }; -#endif \ No newline at end of file +#endif diff --git a/server/src/hardware/interface/loconetinterface.cpp b/server/src/hardware/interface/loconetinterface.cpp new file mode 100644 index 00000000..58732eb5 --- /dev/null +++ b/server/src/hardware/interface/loconetinterface.cpp @@ -0,0 +1,374 @@ +/** + * server/src/hardware/interface/loconetinterface.cpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2019-2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "loconetinterface.hpp" +#include "../input/list/inputlisttablemodel.hpp" +#include "../output/list/outputlisttablemodel.hpp" +#include "../protocol/loconet/iohandler/serialiohandler.hpp" +#include "../protocol/loconet/iohandler/tcpbinaryiohandler.hpp" +#include "../protocol/loconet/iohandler/lbserveriohandler.hpp" +#include "../protocol/loconet/iohandler/z21iohandler.hpp" +#include "../../core/attributes.hpp" +#include "../../log/log.hpp" +#include "../../log/logmessageexception.hpp" +#include "../../utils/displayname.hpp" +#include "../../utils/inrange.hpp" +#include "../../world/world.hpp" + +LocoNetInterface::LocoNetInterface(const std::weak_ptr& world, std::string_view _id) + : Interface(world, _id) + , type{this, "type", LocoNetInterfaceType::Serial, PropertyFlags::ReadWrite | PropertyFlags::Store, + [this](LocoNetInterfaceType) + { + typeChanged(); + }} + , device{this, "device", "", PropertyFlags::ReadWrite | PropertyFlags::Store} + , baudrate{this, "baudrate", 19200, PropertyFlags::ReadWrite | PropertyFlags::Store} + , flowControl{this, "flow_control", SerialFlowControl::None, PropertyFlags::ReadWrite | PropertyFlags::Store} + , hostname{this, "hostname", "192.168.1.203", PropertyFlags::ReadWrite | PropertyFlags::Store} + , port{this, "port", 5550, PropertyFlags::ReadWrite | PropertyFlags::Store} + , loconet{this, "loconet", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject} + , decoders{this, "decoders", nullptr, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::SubObject} + , inputs{this, "inputs", nullptr, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::SubObject} + , outputs{this, "outputs", nullptr, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::SubObject} +{ + name = "LocoNet"; + loconet.setValueInternal(std::make_shared(*this, loconet.name())); + decoders.setValueInternal(std::make_shared(*this, decoders.name())); + inputs.setValueInternal(std::make_shared(*this, inputs.name())); + outputs.setValueInternal(std::make_shared(*this, outputs.name())); + + Attributes::addDisplayName(type, DisplayName::Interface::type); + Attributes::addEnabled(type, !online); + Attributes::addValues(type, locoNetInterfaceTypeValues); + m_interfaceItems.insertBefore(type, notes); + + Attributes::addDisplayName(device, DisplayName::Serial::device); + Attributes::addEnabled(device, !online); + Attributes::addVisible(device, false); + m_interfaceItems.insertBefore(device, notes); + + Attributes::addDisplayName(baudrate, DisplayName::Serial::baudrate); + Attributes::addEnabled(baudrate, !online); + Attributes::addVisible(baudrate, false); + m_interfaceItems.insertBefore(baudrate, notes); + + Attributes::addDisplayName(flowControl, DisplayName::Serial::flowControl); + Attributes::addEnabled(flowControl, !online); + Attributes::addValues(flowControl, SerialFlowControlValues); + Attributes::addVisible(flowControl, false); + m_interfaceItems.insertBefore(flowControl, notes); + + Attributes::addDisplayName(hostname, DisplayName::IP::hostname); + Attributes::addEnabled(hostname, !online); + Attributes::addVisible(hostname, false); + m_interfaceItems.insertBefore(hostname, notes); + + Attributes::addDisplayName(port, DisplayName::IP::port); + Attributes::addEnabled(port, !online); + Attributes::addVisible(port, false); + m_interfaceItems.insertBefore(port, notes); + + Attributes::addDisplayName(loconet, DisplayName::Hardware::loconet); + m_interfaceItems.insertBefore(loconet, notes); + + Attributes::addDisplayName(decoders, DisplayName::Hardware::decoders); + m_interfaceItems.insertBefore(decoders, notes); + + Attributes::addDisplayName(inputs, DisplayName::Hardware::inputs); + m_interfaceItems.insertBefore(inputs, notes); + + Attributes::addDisplayName(outputs, DisplayName::Hardware::outputs); + m_interfaceItems.insertBefore(outputs, notes); + + typeChanged(); +} + +bool LocoNetInterface::addDecoder(Decoder& decoder) +{ + const bool success = DecoderController::addDecoder(decoder); + if(success) + decoders->addObject(decoder.shared_ptr()); + return success; +} + +bool LocoNetInterface::removeDecoder(Decoder& decoder) +{ + const bool success = DecoderController::removeDecoder(decoder); + if(success) + decoders->removeObject(decoder.shared_ptr()); + return success; +} + +void LocoNetInterface::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber) +{ + if(m_kernel) + m_kernel->decoderChanged(decoder, changes, functionNumber); +} + +bool LocoNetInterface::addInput(Input& input) +{ + const bool success = InputController::addInput(input); + if(success) + inputs->addObject(input.shared_ptr()); + return success; +} + +bool LocoNetInterface::removeInput(Input& input) +{ + const bool success = InputController::removeInput(input); + if(success) + inputs->removeObject(input.shared_ptr()); + return success; +} + +bool LocoNetInterface::addOutput(Output& output) +{ + const bool success = OutputController::addOutput(output); + if(success) + outputs->addObject(output.shared_ptr()); + return success; +} + +bool LocoNetInterface::removeOutput(Output& output) +{ + const bool success = OutputController::removeOutput(output); + if(success) + outputs->removeObject(output.shared_ptr()); + return success; +} + +bool LocoNetInterface::setOutputValue(uint32_t address, bool value) +{ + return + m_kernel && + inRange(address, outputAddressMinMax()) && + m_kernel->setOutput(static_cast(address), value); +} + +bool LocoNetInterface::setOnline(bool& value) +{ + if(!m_kernel && value) + { + try + { + switch(type) + { + case LocoNetInterfaceType::Serial: + m_kernel = LocoNet::Kernel::create(loconet->config(), device.value(), baudrate.value(), flowControl.value()); + break; + + case LocoNetInterfaceType::TCPBinary: + m_kernel = LocoNet::Kernel::create(loconet->config(), hostname.value(), port.value()); + break; + + case LocoNetInterfaceType::LBServer: + m_kernel = LocoNet::Kernel::create(loconet->config(), hostname.value(), port.value()); + break; + + case LocoNetInterfaceType::Z21: + m_kernel = LocoNet::Kernel::create(loconet->config(), hostname.value()); + break; + + default: + assert(false); + return false; + } + + status.setValueInternal(InterfaceStatus::Initializing); + + m_kernel->setLogId(id.value()); + m_kernel->setOnStarted( + [this]() + { + status.setValueInternal(InterfaceStatus::Online); + }); + m_kernel->setOnGlobalPowerChanged( + [this](bool powerOn) + { + if(auto w = m_world.lock()) + { + if(powerOn && !contains(w->state.value(), WorldState::PowerOn)) + w->powerOn(); + else if(!powerOn && contains(w->state.value(), WorldState::PowerOn)) + w->powerOff(); + } + }); + m_kernel->setOnIdle( + [this]() + { + if(auto w = m_world.lock(); w && contains(w->state.value(), WorldState::Run)) + w->stop(); + }); + m_kernel->setDecoderController(this); + m_kernel->setInputController(this); + m_kernel->setOutputController(this); + m_kernel->start(); + + m_loconetPropertyChanged = loconet->propertyChanged.connect( + [this](BaseProperty&) + { + m_kernel->setConfig(loconet->config()); + }); + + if(auto w = m_world.lock()) + { + m_kernel->setPowerOn(contains(w->state.value(), WorldState::PowerOn)); + + if(contains(w->state.value(), WorldState::Run)) + m_kernel->resume(); + else + m_kernel->emergencyStop(); + } + + Attributes::setEnabled(type, false); + Attributes::setEnabled(device, false); + Attributes::setEnabled(baudrate, false); + Attributes::setEnabled(flowControl, false); + Attributes::setEnabled(hostname, false); + Attributes::setEnabled(port, false); + } + catch(const LogMessageException& e) + { + status.setValueInternal(InterfaceStatus::Offline); + Log::log(*this, e.message(), e.args()); + return false; + } + } + else if(m_kernel && !value) + { + Attributes::setEnabled(type, true); + Attributes::setEnabled(device, true); + Attributes::setEnabled(baudrate, true); + Attributes::setEnabled(flowControl, true); + Attributes::setEnabled(hostname, true); + Attributes::setEnabled(port, true); + + m_loconetPropertyChanged.disconnect(); + + m_kernel->stop(); + m_kernel.reset(); + + status.setValueInternal(InterfaceStatus::Offline); + } + return true; +} + +void LocoNetInterface::addToWorld() +{ + Interface::addToWorld(); + + if(auto world = m_world.lock()) + { + world->decoderControllers->add(std::dynamic_pointer_cast(shared_from_this())); + world->inputControllers->add(std::dynamic_pointer_cast(shared_from_this())); + world->outputControllers->add(std::dynamic_pointer_cast(shared_from_this())); + } +} + +void LocoNetInterface::loaded() +{ + Interface::loaded(); + + typeChanged(); +} + +void LocoNetInterface::destroying() +{ + for(const auto& decoder : *decoders) + { + assert(decoder->interface.value() == std::dynamic_pointer_cast(shared_from_this())); + decoder->interface = nullptr; + } + + for(const auto& input : *inputs) + { + assert(input->interface.value() == std::dynamic_pointer_cast(shared_from_this())); + input->interface = nullptr; + } + + for(const auto& output : *outputs) + { + assert(output->interface.value() == std::dynamic_pointer_cast(shared_from_this())); + output->interface = nullptr; + } + + if(auto world = m_world.lock()) + { + world->decoderControllers->remove(std::dynamic_pointer_cast(shared_from_this())); + world->inputControllers->remove(std::dynamic_pointer_cast(shared_from_this())); + world->outputControllers->remove(std::dynamic_pointer_cast(shared_from_this())); + } + + Interface::destroying(); +} + +void LocoNetInterface::worldEvent(WorldState state, WorldEvent event) +{ + Interface::worldEvent(state, event); + + if(m_kernel) + { + switch(event) + { + case WorldEvent::PowerOff: + m_kernel->setPowerOn(false); + break; + + case WorldEvent::PowerOn: + m_kernel->setPowerOn(true); + if(contains(state, WorldState::Run)) + m_kernel->resume(); + break; + + case WorldEvent::Stop: + m_kernel->emergencyStop(); + break; + + case WorldEvent::Run: + if(contains(state, WorldState::PowerOn)) + m_kernel->resume(); + break; + + default: + break; + } + } +} + +void LocoNetInterface::idChanged(const std::string& newId) +{ + if(m_kernel) + m_kernel->setLogId(newId); +} + +void LocoNetInterface::typeChanged() +{ + const bool serialVisible = isSerial(type); + Attributes::setVisible(device, serialVisible); + Attributes::setVisible(baudrate, serialVisible); + Attributes::setVisible(flowControl, serialVisible); + + const bool networkVisible = isNetwork(type); + Attributes::setVisible(hostname, networkVisible); + Attributes::setVisible(port, networkVisible && type != LocoNetInterfaceType::Z21); +} diff --git a/server/src/hardware/interface/loconetinterface.hpp b/server/src/hardware/interface/loconetinterface.hpp new file mode 100644 index 00000000..0a62cbc9 --- /dev/null +++ b/server/src/hardware/interface/loconetinterface.hpp @@ -0,0 +1,99 @@ +/** + * server/src/hardware/interface/loconetinterface.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_INTERFACE_LOCONETINTERFACE_HPP +#define TRAINTASTIC_SERVER_HARDWARE_INTERFACE_LOCONETINTERFACE_HPP + +#include "interface.hpp" +#include "../protocol/loconet/kernel.hpp" +#include "../protocol/loconet/settings.hpp" +#include "../decoder/decodercontroller.hpp" +#include "../decoder/decoderlist.hpp" +#include "../input/inputcontroller.hpp" +#include "../input/list/inputlist.hpp" +#include "../output/outputcontroller.hpp" +#include "../output/list/outputlist.hpp" +#include "../../core/objectproperty.hpp" +#include "../../enum/loconetinterfacetype.hpp" +#include "../../enum/serialflowcontrol.hpp" + +/** + * @brief LocoNet hardware interface + */ +class LocoNetInterface final + : public Interface + , public DecoderController + , public InputController + , public OutputController +{ + CLASS_ID("interface.loconet") + DEFAULT_ID("loconet") + CREATE(LocoNetInterface) + + private: + std::unique_ptr m_kernel; + boost::signals2::connection m_loconetPropertyChanged; + + void addToWorld() final; + void loaded() final; + void destroying() final; + void worldEvent(WorldState state, WorldEvent event) final; + + void idChanged(const std::string& newId) final; + + void typeChanged(); + + protected: + bool setOnline(bool& value) final; + + public: + Property type; + Property device; + Property baudrate; + Property flowControl; + Property hostname; + Property port; + ObjectProperty loconet; + ObjectProperty decoders; + ObjectProperty inputs; + ObjectProperty outputs; + + LocoNetInterface(const std::weak_ptr& world, std::string_view _id); + + // DecoderController: + [[nodiscard]] bool addDecoder(Decoder& decoder) final; + [[nodiscard]] bool removeDecoder(Decoder& decoder) final; + void decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber) final; + + // InputController: + std::pair inputAddressMinMax() const final { return {LocoNet::Kernel::inputAddressMin, LocoNet::Kernel::inputAddressMax}; } + [[nodiscard]] bool addInput(Input& input) final; + [[nodiscard]] bool removeInput(Input& input) final; + + // OutputController: + std::pair outputAddressMinMax() const final { return {LocoNet::Kernel::outputAddressMin, LocoNet::Kernel::outputAddressMax}; } + [[nodiscard]] bool addOutput(Output& output) final; + [[nodiscard]] bool removeOutput(Output& output) final; + [[nodiscard]] bool setOutputValue(uint32_t address, bool value) final; +}; + +#endif diff --git a/server/src/hardware/interface/wlanmausinterface.cpp b/server/src/hardware/interface/wlanmausinterface.cpp new file mode 100644 index 00000000..ba70d285 --- /dev/null +++ b/server/src/hardware/interface/wlanmausinterface.cpp @@ -0,0 +1,137 @@ +/** + * server/src/hardware/interface/wlanmausinterface.cpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2019-2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "wlanmausinterface.hpp" +#include "../protocol/z21/iohandler/udpserveriohandler.hpp" +#include "../../core/attributes.hpp" +#include "../../log/log.hpp" +#include "../../log/logmessageexception.hpp" +#include "../../utils/displayname.hpp" +#include "../../world/world.hpp" + +WlanMausInterface::WlanMausInterface(const std::weak_ptr& world, std::string_view _id) + : Interface(world, _id) + , z21{this, "z21", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject} +{ + z21.setValueInternal(std::make_shared(*this, z21.name())); + + Attributes::addDisplayName(z21, DisplayName::Hardware::z21); + m_interfaceItems.insertBefore(z21, notes); +} + +void WlanMausInterface::worldEvent(WorldState state, WorldEvent event) +{ + Interface::worldEvent(state, event); + + if(m_kernel) + { + switch(event) + { + case WorldEvent::PowerOff: + case WorldEvent::PowerOn: + case WorldEvent::Stop: + case WorldEvent::Run: + m_kernel->setState(contains(state, WorldState::PowerOn), !contains(state, WorldState::Run)); + break; + + default: + break; + } + } +} + +void WlanMausInterface::idChanged(const std::string& newId) +{ + if(m_kernel) + m_kernel->setLogId(newId); +} + +bool WlanMausInterface::setOnline(bool& value) +{ + if(!m_kernel && value) + { + try + { + auto world = m_world.lock(); + assert(world); + m_kernel = Z21::ServerKernel::create(z21->config(), world->decoders.value()); + + status.setValueInternal(InterfaceStatus::Initializing); + + m_kernel->setLogId(id.value()); + m_kernel->setOnStarted( + [this]() + { + status.setValueInternal(InterfaceStatus::Online); + }); + + m_kernel->setOnTrackPowerOff( + [this]() + { + if(auto w = m_world.lock(); w && contains(w->state.value(), WorldState::PowerOn)) + w->powerOff(); + }); + m_kernel->setOnTrackPowerOn( + [this]() + { + if(auto w = m_world.lock()) + { + if(!contains(w->state.value(), WorldState::PowerOn)) + w->powerOn(); + if(!contains(w->state.value(), WorldState::Run)) + w->run(); + } + }); + m_kernel->setOnEmergencyStop( + [this]() + { + if(auto w = m_world.lock(); w && contains(w->state.value(), WorldState::Run)) + w->stop(); + }); + + m_kernel->start(); + + m_z21PropertyChanged = z21->propertyChanged.connect( + [this](BaseProperty&) + { + m_kernel->setConfig(z21->config()); + }); + + if(auto w = m_world.lock()) + m_kernel->setState(contains(w->state.value(), WorldState::PowerOn), !contains(w->state.value(), WorldState::Run)); + } + catch(const LogMessageException& e) + { + status.setValueInternal(InterfaceStatus::Offline); + Log::log(*this, e.message(), e.args()); + return false; + } + } + else if(m_kernel && !value) + { + m_kernel->stop(); + m_kernel.reset(); + + status.setValueInternal(InterfaceStatus::Offline); + } + return true; +} diff --git a/server/src/hardware/input/loconetinput.hpp b/server/src/hardware/interface/wlanmausinterface.hpp similarity index 53% rename from server/src/hardware/input/loconetinput.hpp rename to server/src/hardware/interface/wlanmausinterface.hpp index ce11c03f..08c2b217 100644 --- a/server/src/hardware/input/loconetinput.hpp +++ b/server/src/hardware/interface/wlanmausinterface.hpp @@ -1,9 +1,9 @@ /** - * server/src/hardware/input/loconetinput.hpp + * server/src/hardware/interface/wlanmausinterface.hpp * * This file is part of the traintastic source code. * - * Copyright (C) 2019-2021 Reinder Feenstra + * Copyright (C) 2019-2022 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -20,38 +20,37 @@ * 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 +#ifndef TRAINTASTIC_SERVER_HARDWARE_INTERFACE_WLANMAUSINTERFACE_HPP +#define TRAINTASTIC_SERVER_HARDWARE_INTERFACE_WLANMAUSINTERFACE_HPP -#include "input.hpp" +#include "interface.hpp" +#include "../protocol/z21/serverkernel.hpp" +#include "../protocol/z21/serversettings.hpp" #include "../../core/objectproperty.hpp" -#include "../protocol/loconet/loconet.hpp" -class LocoNetInput : public Input +/** + * @brief WLANmaus/Z21 app hardware interface + */ +class WlanMausInterface : public Interface { - friend class LocoNet::LocoNet; + CLASS_ID("interface.wlanmaus") + DEFAULT_ID("wlanmaus") + CREATE(WlanMausInterface) + + private: + std::unique_ptr m_kernel; + boost::signals2::connection m_z21PropertyChanged; 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); } + bool setOnline(bool& value) final; public: - CLASS_ID("input.loconet") - CREATE(LocoNetInput) + ObjectProperty z21; - 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); + WlanMausInterface(const std::weak_ptr& world, std::string_view _id); }; #endif + diff --git a/server/src/hardware/interface/xpressnetinterface.cpp b/server/src/hardware/interface/xpressnetinterface.cpp new file mode 100644 index 00000000..9a555a41 --- /dev/null +++ b/server/src/hardware/interface/xpressnetinterface.cpp @@ -0,0 +1,437 @@ +/** + * server/src/hardware/interface/xpressnetinterface.cpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2019-2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "xpressnetinterface.hpp" +#include "../input/list/inputlisttablemodel.hpp" +#include "../output/list/outputlisttablemodel.hpp" +#include "../protocol/xpressnet/messages.hpp" +#include "../protocol/xpressnet/iohandler/serialiohandler.hpp" +#include "../protocol/xpressnet/iohandler/liusbiohandler.hpp" +#include "../protocol/xpressnet/iohandler/rosofts88xpressnetliiohandler.hpp" +#include "../protocol/xpressnet/iohandler/tcpiohandler.hpp" +#include "../../core/attributes.hpp" +#include "../../log/log.hpp" +#include "../../log/logmessageexception.hpp" +#include "../../utils/displayname.hpp" +#include "../../utils/inrange.hpp" +#include "../../world/world.hpp" + +XpressNetInterface::XpressNetInterface(const std::weak_ptr& world, std::string_view _id) + : Interface(world, _id) + , type{this, "type", XpressNetInterfaceType::Serial, PropertyFlags::ReadWrite | PropertyFlags::Store, + [this](XpressNetInterfaceType) + { + updateVisible(); + }} + , serialInterfaceType{this, "interface", XpressNetSerialInterfaceType::LenzLI100, PropertyFlags::ReadWrite | PropertyFlags::Store, + [this](XpressNetSerialInterfaceType value) + { + switch(value) + { + case XpressNetSerialInterfaceType::LenzLI100: + case XpressNetSerialInterfaceType::RoSoftS88XPressNetLI: + baudrate.setValueInternal(9600); + flowControl.setValueInternal(SerialFlowControl::Hardware); + break; + + case XpressNetSerialInterfaceType::LenzLI100F: + case XpressNetSerialInterfaceType::LenzLI101F: + baudrate.setValueInternal(19200); + flowControl.setValueInternal(SerialFlowControl::Hardware); + break; + + case XpressNetSerialInterfaceType::LenzLIUSB: + baudrate.setValueInternal(57600); + flowControl.setValueInternal(SerialFlowControl::None); + break; + + case XpressNetSerialInterfaceType::DigikeijsDR5000: + baudrate.setValueInternal(115200); + flowControl.setValueInternal(SerialFlowControl::None); + break; + } + updateVisible(); + }} + , device{this, "device", "", PropertyFlags::ReadWrite | PropertyFlags::Store} + , baudrate{this, "baudrate", 19200, PropertyFlags::ReadWrite | PropertyFlags::Store} + , flowControl{this, "flow_control", SerialFlowControl::None, PropertyFlags::ReadWrite | PropertyFlags::Store} + , hostname{this, "hostname", "192.168.1.203", PropertyFlags::ReadWrite | PropertyFlags::Store} + , port{this, "port", 5550, PropertyFlags::ReadWrite | PropertyFlags::Store} + , s88StartAddress{this, "s88_start_address", XpressNet::RoSoftS88XpressNetLI::S88StartAddress::startAddressDefault, PropertyFlags::ReadWrite | PropertyFlags::Store} + , s88ModuleCount{this, "s88_module_count", XpressNet::RoSoftS88XpressNetLI::S88ModuleCount::moduleCountDefault, PropertyFlags::ReadWrite | PropertyFlags::Store} + , xpressnet{this, "xpressnet", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject} + , decoders{this, "decoders", nullptr, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::SubObject} + , inputs{this, "inputs", nullptr, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::SubObject} + , outputs{this, "outputs", nullptr, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::SubObject} +{ + xpressnet.setValueInternal(std::make_shared(*this, xpressnet.name())); + decoders.setValueInternal(std::make_shared(*this, decoders.name())); + inputs.setValueInternal(std::make_shared(*this, inputs.name())); + outputs.setValueInternal(std::make_shared(*this, outputs.name())); + + Attributes::addDisplayName(type, DisplayName::Interface::type); + Attributes::addEnabled(type, !online); + Attributes::addValues(type, xpressNetInterfaceTypeValues); + m_interfaceItems.insertBefore(type, notes); + + Attributes::addValues(serialInterfaceType, XpressNetSerialInterfaceTypeValues); + Attributes::addEnabled(serialInterfaceType, !online); + Attributes::addVisible(serialInterfaceType, false); + m_interfaceItems.insertBefore(serialInterfaceType, notes); + + Attributes::addDisplayName(device, DisplayName::Serial::device); + Attributes::addEnabled(device, !online); + Attributes::addVisible(device, false); + m_interfaceItems.insertBefore(device, notes); + + Attributes::addDisplayName(baudrate, DisplayName::Serial::baudrate); + Attributes::addEnabled(baudrate, !online); + Attributes::addVisible(baudrate, false); + m_interfaceItems.insertBefore(baudrate, notes); + + Attributes::addDisplayName(flowControl, DisplayName::Serial::flowControl); + Attributes::addEnabled(flowControl, !online); + Attributes::addValues(flowControl, SerialFlowControlValues); + Attributes::addVisible(flowControl, false); + m_interfaceItems.insertBefore(flowControl, notes); + + Attributes::addDisplayName(hostname, DisplayName::IP::hostname); + Attributes::addEnabled(hostname, !online); + Attributes::addVisible(hostname, false); + m_interfaceItems.insertBefore(hostname, notes); + + Attributes::addDisplayName(port, DisplayName::IP::port); + Attributes::addEnabled(port, !online); + Attributes::addVisible(port, false); + m_interfaceItems.insertBefore(port, notes); + + Attributes::addMinMax(s88StartAddress, XpressNet::RoSoftS88XpressNetLI::S88StartAddress::startAddressMin, XpressNet::RoSoftS88XpressNetLI::S88StartAddress::startAddressMax); + Attributes::addEnabled(s88StartAddress, !online); + Attributes::addVisible(s88StartAddress, false); + m_interfaceItems.insertBefore(s88StartAddress, notes); + + Attributes::addMinMax(s88ModuleCount, XpressNet::RoSoftS88XpressNetLI::S88ModuleCount::moduleCountMin, XpressNet::RoSoftS88XpressNetLI::S88ModuleCount::moduleCountMax); + Attributes::addEnabled(s88ModuleCount, !online); + Attributes::addVisible(s88ModuleCount, false); + m_interfaceItems.insertBefore(s88ModuleCount, notes); + + Attributes::addDisplayName(xpressnet, DisplayName::Hardware::xpressnet); + m_interfaceItems.insertBefore(xpressnet, notes); + + Attributes::addDisplayName(decoders, DisplayName::Hardware::decoders); + m_interfaceItems.insertBefore(decoders, notes); + + Attributes::addDisplayName(inputs, DisplayName::Hardware::inputs); + m_interfaceItems.insertBefore(inputs, notes); + + Attributes::addDisplayName(outputs, DisplayName::Hardware::outputs); + m_interfaceItems.insertBefore(outputs, notes); + + updateVisible(); +} + +bool XpressNetInterface::addDecoder(Decoder& decoder) +{ + const bool success = DecoderController::addDecoder(decoder); + if(success) + decoders->addObject(decoder.shared_ptr()); + return success; +} + +bool XpressNetInterface::removeDecoder(Decoder& decoder) +{ + const bool success = DecoderController::removeDecoder(decoder); + if(success) + decoders->removeObject(decoder.shared_ptr()); + return success; +} + +void XpressNetInterface::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber) +{ + if(m_kernel) + m_kernel->decoderChanged(decoder, changes, functionNumber); +} + +bool XpressNetInterface::addInput(Input& input) +{ + const bool success = InputController::addInput(input); + if(success) + inputs->addObject(input.shared_ptr()); + return success; +} + +bool XpressNetInterface::removeInput(Input& input) +{ + const bool success = InputController::removeInput(input); + if(success) + inputs->removeObject(input.shared_ptr()); + return success; +} + +bool XpressNetInterface::addOutput(Output& output) +{ + const bool success = OutputController::addOutput(output); + if(success) + outputs->addObject(output.shared_ptr()); + return success; +} + +bool XpressNetInterface::removeOutput(Output& output) +{ + const bool success = OutputController::removeOutput(output); + if(success) + outputs->removeObject(output.shared_ptr()); + return success; +} + +bool XpressNetInterface::setOutputValue(uint32_t address, bool value) +{ + return + m_kernel && + inRange(address, outputAddressMinMax()) && + m_kernel->setOutput(static_cast(address), value); +} + +bool XpressNetInterface::setOnline(bool& value) +{ + if(!m_kernel && value) + { + try + { + switch(type) + { + case XpressNetInterfaceType::Serial: + switch(serialInterfaceType) + { + case XpressNetSerialInterfaceType::LenzLI100: + case XpressNetSerialInterfaceType::LenzLI100F: + case XpressNetSerialInterfaceType::LenzLI101F: + m_kernel = XpressNet::Kernel::create(xpressnet->config(), device.value(), baudrate.value(), flowControl.value()); + break; + + case XpressNetSerialInterfaceType::RoSoftS88XPressNetLI: + m_kernel = XpressNet::Kernel::create(xpressnet->config(), device.value(), baudrate.value(), flowControl.value(), s88StartAddress.value(), s88ModuleCount.value()); + break; + + case XpressNetSerialInterfaceType::LenzLIUSB: + case XpressNetSerialInterfaceType::DigikeijsDR5000: + m_kernel = XpressNet::Kernel::create(xpressnet->config(), device.value(), baudrate.value(), flowControl.value()); + break; + } + break; + + case XpressNetInterfaceType::Network: + m_kernel = XpressNet::Kernel::create(xpressnet->config(), hostname.value(), port.value()); + break; + } + + if(!m_kernel) + { + assert(false); + return false; + } + + status.setValueInternal(InterfaceStatus::Initializing); + + m_kernel->setLogId(id.value()); + m_kernel->setOnStarted( + [this]() + { + status.setValueInternal(InterfaceStatus::Online); + }); + m_kernel->setOnNormalOperationResumed( + [this]() + { + if(auto w = m_world.lock()) + { + if(!contains(w->state.value(), WorldState::PowerOn)) + w->powerOn(); + if(!contains(w->state.value(), WorldState::Run)) + w->run(); + } + }); + m_kernel->setOnTrackPowerOff( + [this]() + { + if(auto w = m_world.lock()) + { + if(contains(w->state.value(), WorldState::PowerOn)) + w->powerOff(); + if(contains(w->state.value(), WorldState::Run)) + w->stop(); + } + }); + m_kernel->setOnEmergencyStop( + [this]() + { + if(auto w = m_world.lock(); w && contains(w->state.value(), WorldState::Run)) + w->stop(); + }); + + m_kernel->setDecoderController(this); + m_kernel->setInputController(this); + m_kernel->setOutputController(this); + m_kernel->start(); + + m_xpressnetPropertyChanged = xpressnet->propertyChanged.connect( + [this](BaseProperty&) + { + m_kernel->setConfig(xpressnet->config()); + }); + + if(auto w = m_world.lock()) + { + if(!contains(w->state.value(), WorldState::PowerOn)) + m_kernel->trackPowerOff(); + else if(!contains(w->state.value(), WorldState::Run)) + m_kernel->emergencyStop(); + else + m_kernel->normalOperationsResumed(); + } + + Attributes::setEnabled({type, serialInterfaceType, device, baudrate, flowControl, hostname, port, s88StartAddress, s88ModuleCount}, false); + } + catch(const LogMessageException& e) + { + status.setValueInternal(InterfaceStatus::Offline); + Log::log(*this, e.message(), e.args()); + return false; + } + } + else if(m_kernel && !value) + { + Attributes::setEnabled({type, serialInterfaceType, device, baudrate, flowControl, hostname, port, s88StartAddress, s88ModuleCount}, true); + + m_xpressnetPropertyChanged.disconnect(); + + m_kernel->stop(); + m_kernel.reset(); + + status.setValueInternal(InterfaceStatus::Offline); + } + return true; +} + +void XpressNetInterface::addToWorld() +{ + Interface::addToWorld(); + + if(auto world = m_world.lock()) + { + world->decoderControllers->add(std::dynamic_pointer_cast(shared_from_this())); + world->inputControllers->add(std::dynamic_pointer_cast(shared_from_this())); + world->outputControllers->add(std::dynamic_pointer_cast(shared_from_this())); + } +} + +void XpressNetInterface::loaded() +{ + Interface::loaded(); + + updateVisible(); +} + +void XpressNetInterface::destroying() +{ + for(const auto& decoder : *decoders) + { + assert(decoder->interface.value() == std::dynamic_pointer_cast(shared_from_this())); + decoder->interface = nullptr; + } + + for(const auto& input : *inputs) + { + assert(input->interface.value() == std::dynamic_pointer_cast(shared_from_this())); + input->interface = nullptr; + } + + for(const auto& output : *outputs) + { + assert(output->interface.value() == std::dynamic_pointer_cast(shared_from_this())); + output->interface = nullptr; + } + + if(auto world = m_world.lock()) + { + world->decoderControllers->remove(std::dynamic_pointer_cast(shared_from_this())); + world->inputControllers->remove(std::dynamic_pointer_cast(shared_from_this())); + world->outputControllers->remove(std::dynamic_pointer_cast(shared_from_this())); + } + + Interface::destroying(); +} + +void XpressNetInterface::worldEvent(WorldState state, WorldEvent event) +{ + Interface::worldEvent(state, event); + + if(m_kernel) + { + switch(event) + { + case WorldEvent::PowerOff: + m_kernel->trackPowerOff(); + break; + + case WorldEvent::PowerOn: + m_kernel->normalOperationsResumed(); + if(!contains(state, WorldState::Run)) + m_kernel->emergencyStop(); + break; + + case WorldEvent::Stop: + m_kernel->emergencyStop(); + break; + + case WorldEvent::Run: + if(contains(state, WorldState::PowerOn)) + m_kernel->normalOperationsResumed(); + break; + + default: + break; + } + } +} + +void XpressNetInterface::idChanged(const std::string& newId) +{ + if(m_kernel) + m_kernel->setLogId(newId); +} + +void XpressNetInterface::updateVisible() +{ + const bool isSerial = (type == XpressNetInterfaceType::Serial); + Attributes::setVisible(serialInterfaceType, isSerial); + Attributes::setVisible(device, isSerial); + Attributes::setVisible(baudrate, isSerial); + Attributes::setVisible(flowControl, isSerial); + + const bool isNetwork = (type == XpressNetInterfaceType::Network); + Attributes::setVisible(hostname, isNetwork); + Attributes::setVisible(port, isNetwork); + + const bool isRoSoftS88XPressNetLI = isSerial && (serialInterfaceType == XpressNetSerialInterfaceType::RoSoftS88XPressNetLI); + Attributes::setVisible(s88StartAddress, isRoSoftS88XPressNetLI); + Attributes::setVisible(s88ModuleCount, isRoSoftS88XPressNetLI); +} diff --git a/server/src/hardware/interface/xpressnetinterface.hpp b/server/src/hardware/interface/xpressnetinterface.hpp new file mode 100644 index 00000000..77eba9e7 --- /dev/null +++ b/server/src/hardware/interface/xpressnetinterface.hpp @@ -0,0 +1,103 @@ +/** + * server/src/hardware/interface/xpressnetinterface.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_INTERFACE_XPRESSNETINTERFACE_HPP +#define TRAINTASTIC_SERVER_HARDWARE_INTERFACE_XPRESSNETINTERFACE_HPP + +#include "interface.hpp" +#include "../protocol/xpressnet/kernel.hpp" +#include "../protocol/xpressnet/settings.hpp" +#include "../decoder/decodercontroller.hpp" +#include "../decoder/decoderlist.hpp" +#include "../input/inputcontroller.hpp" +#include "../input/list/inputlist.hpp" +#include "../output/outputcontroller.hpp" +#include "../output/list/outputlist.hpp" +#include "../../core/objectproperty.hpp" +#include "../../enum/xpressnetinterfacetype.hpp" +#include "../../enum/xpressnetserialinterfacetype.hpp" +#include "../../enum/serialflowcontrol.hpp" + +/** + * @brief XpressNet hardware interface + */ +class XpressNetInterface final + : public Interface + , public DecoderController + , public InputController + , public OutputController +{ + CLASS_ID("interface.xpressnet") + DEFAULT_ID("xpressnet") + CREATE(XpressNetInterface) + + private: + std::unique_ptr m_kernel; + boost::signals2::connection m_xpressnetPropertyChanged; + + void addToWorld() final; + void loaded() final; + void destroying() final; + void worldEvent(WorldState state, WorldEvent event) final; + + void idChanged(const std::string& newId) final; + + void updateVisible(); + + protected: + bool setOnline(bool& value) final; + + public: + Property type; + Property serialInterfaceType; + Property device; + Property baudrate; + Property flowControl; + Property hostname; + Property port; + Property s88StartAddress; + Property s88ModuleCount; + ObjectProperty xpressnet; + ObjectProperty decoders; + ObjectProperty inputs; + ObjectProperty outputs; + + XpressNetInterface(const std::weak_ptr& world, std::string_view _id); + + // DecoderController: + [[nodiscard]] bool addDecoder(Decoder& decoder) final; + [[nodiscard]] bool removeDecoder(Decoder& decoder) final; + void decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber) final; + + // InputController: + std::pair inputAddressMinMax() const final { return {XpressNet::Kernel::ioAddressMin, XpressNet::Kernel::ioAddressMax}; } + [[nodiscard]] bool addInput(Input& input) final; + [[nodiscard]] bool removeInput(Input& input) final; + + // OutputController: + std::pair outputAddressMinMax() const final { return {XpressNet::Kernel::ioAddressMin, XpressNet::Kernel::ioAddressMax}; } + [[nodiscard]] bool addOutput(Output& output) final; + [[nodiscard]] bool removeOutput(Output& output) final; + [[nodiscard]] bool setOutputValue(uint32_t address, bool value) final; +}; + +#endif diff --git a/server/src/hardware/interface/z21interface.cpp b/server/src/hardware/interface/z21interface.cpp new file mode 100644 index 00000000..9538f9c3 --- /dev/null +++ b/server/src/hardware/interface/z21interface.cpp @@ -0,0 +1,260 @@ +/** + * server/src/hardware/interface/z21interface.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 "z21interface.hpp" +#include "../decoder/decoderlisttablemodel.hpp" +#include "../protocol/z21/messages.hpp" +#include "../protocol/z21/iohandler/udpclientiohandler.hpp" +#include "../../core/attributes.hpp" +#include "../../log/log.hpp" +#include "../../log/logmessageexception.hpp" +#include "../../utils/category.hpp" +#include "../../utils/displayname.hpp" +#include "../../utils/inrange.hpp" +#include "../../world/world.hpp" + +Z21Interface::Z21Interface(const std::weak_ptr& world, std::string_view _id) + : Interface(world, _id) + , hostname{this, "hostname", "192.168.1.203", PropertyFlags::ReadWrite | PropertyFlags::Store} + , port{this, "port", 21105, PropertyFlags::ReadWrite | PropertyFlags::Store} + , z21{this, "z21", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject} + , decoders{this, "decoders", nullptr, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::SubObject} + , hardwareType{this, "hardware_type", "", PropertyFlags::ReadOnly | PropertyFlags::NoStore} + , serialNumber{this, "serial_number", "", PropertyFlags::ReadOnly | PropertyFlags::NoStore} + , firmwareVersion{this, "firmware_version", "", PropertyFlags::ReadOnly | PropertyFlags::NoStore} +{ + z21.setValueInternal(std::make_shared(*this, z21.name())); + decoders.setValueInternal(std::make_shared(*this, decoders.name())); + + Attributes::addDisplayName(hostname, DisplayName::IP::hostname); + Attributes::addEnabled(hostname, !online); + m_interfaceItems.insertBefore(hostname, notes); + + Attributes::addDisplayName(port, DisplayName::IP::port); + Attributes::addEnabled(port, !online); + m_interfaceItems.insertBefore(port, notes); + + Attributes::addDisplayName(z21, DisplayName::Hardware::z21); + m_interfaceItems.insertBefore(z21, notes); + + Attributes::addDisplayName(decoders, DisplayName::Hardware::decoders); + m_interfaceItems.insertBefore(decoders, notes); + + Attributes::addCategory(hardwareType, Category::info); + m_interfaceItems.insertBefore(hardwareType, notes); + + Attributes::addCategory(serialNumber, Category::info); + m_interfaceItems.insertBefore(serialNumber, notes); + + Attributes::addCategory(firmwareVersion, Category::info); + m_interfaceItems.insertBefore(firmwareVersion, notes); +} + +bool Z21Interface::addDecoder(Decoder& decoder) +{ + const bool success = DecoderController::addDecoder(decoder); + if(success) + decoders->addObject(decoder.shared_ptr()); + return success; +} + +bool Z21Interface::removeDecoder(Decoder& decoder) +{ + const bool success = DecoderController::removeDecoder(decoder); + if(success) + decoders->removeObject(decoder.shared_ptr()); + return success; +} + +void Z21Interface::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber) +{ + if(m_kernel) + m_kernel->decoderChanged(decoder, changes, functionNumber); +} + +bool Z21Interface::setOnline(bool& value) +{ + if(!m_kernel && value) + { + try + { + m_kernel = Z21::ClientKernel::create(z21->config(), hostname.value(), port.value()); + + status.setValueInternal(InterfaceStatus::Initializing); + + m_kernel->setLogId(id.value()); + m_kernel->setOnStarted( + [this]() + { + status.setValueInternal(InterfaceStatus::Online); + }); + m_kernel->setOnSerialNumberChanged( + [this](uint32_t newValue) + { + serialNumber.setValueInternal(std::to_string(newValue)); + }); + m_kernel->setOnHardwareInfoChanged( + [this](Z21::HardwareType type, uint8_t versionMajor, uint8_t versionMinor) + { + hardwareType.setValueInternal(std::string(Z21::toString(type))); + Log::log(*this, LogMessage::I2002_HARDWARE_TYPE_X, hardwareType.value()); + + if(versionMajor != 0 || versionMinor != 0) + { + firmwareVersion.setValueInternal(std::to_string(versionMajor).append(".").append(std::to_string(versionMinor))); + Log::log(*this, LogMessage::I2003_FIRMWARE_VERSION_X, firmwareVersion.value()); + } + else + firmwareVersion.setValueInternal(""); + }); + m_kernel->setOnTrackPowerOnChanged( + [this](bool powerOn) + { + if(auto w = m_world.lock()) + { + if(powerOn == contains(w->state.value(), WorldState::PowerOn)) + return; + + if(powerOn) + w->powerOn(); + else + w->powerOff(); + } + }); + m_kernel->setOnEmergencyStop( + [this]() + { + if(auto w = m_world.lock(); w && contains(w->state.value(), WorldState::Run)) + w->stop(); + }); + + m_kernel->setDecoderController(this); + + m_kernel->start(); + + m_z21PropertyChanged = z21->propertyChanged.connect( + [this](BaseProperty&) + { + m_kernel->setConfig(z21->config()); + }); + + if(auto w = m_world.lock()) + { + if(contains(w->state.value(), WorldState::PowerOn)) + m_kernel->trackPowerOn(); + else + m_kernel->trackPowerOff(); + + if(!contains(w->state.value(), WorldState::Run)) + m_kernel->emergencyStop(); + } + + Attributes::setEnabled({hostname, port}, false); + } + catch(const LogMessageException& e) + { + status.setValueInternal(InterfaceStatus::Offline); + Log::log(*this, e.message(), e.args()); + return false; + } + } + else if(m_kernel && !value) + { + Attributes::setEnabled({hostname, port}, true); + + m_z21PropertyChanged.disconnect(); + + m_kernel->stop(); + m_kernel.reset(); + + status.setValueInternal(InterfaceStatus::Offline); + hardwareType.setValueInternal(""); + serialNumber.setValueInternal(""); + firmwareVersion.setValueInternal(""); + } + return true; +} + +void Z21Interface::addToWorld() +{ + Interface::addToWorld(); + + if(auto world = m_world.lock()) + { + world->decoderControllers->add(std::dynamic_pointer_cast(shared_from_this())); + } +} + +void Z21Interface::destroying() +{ + for(const auto& decoder : *decoders) + { + assert(decoder->interface.value() == std::dynamic_pointer_cast(shared_from_this())); + decoder->interface = nullptr; + } + + if(auto world = m_world.lock()) + { + world->decoderControllers->remove(std::dynamic_pointer_cast(shared_from_this())); + } + + Interface::destroying(); +} + +void Z21Interface::worldEvent(WorldState state, WorldEvent event) +{ + Interface::worldEvent(state, event); + + if(m_kernel) + { + switch(event) + { + case WorldEvent::PowerOff: + m_kernel->trackPowerOff(); + break; + + case WorldEvent::PowerOn: + m_kernel->trackPowerOn(); + if(!contains(state, WorldState::Run)) + m_kernel->emergencyStop(); + break; + + case WorldEvent::Stop: + m_kernel->emergencyStop(); + break; + + case WorldEvent::Run: + if(contains(state, WorldState::PowerOn)) + m_kernel->trackPowerOn(); + break; + + default: + break; + } + } +} + +void Z21Interface::idChanged(const std::string& newId) +{ + if(m_kernel) + m_kernel->setLogId(newId); +} diff --git a/server/src/hardware/interface/z21interface.hpp b/server/src/hardware/interface/z21interface.hpp new file mode 100644 index 00000000..e4607b4b --- /dev/null +++ b/server/src/hardware/interface/z21interface.hpp @@ -0,0 +1,76 @@ +/** + * server/src/hardware/interface/z21interface.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_INTERFACE_Z21INTERFACE_HPP +#define TRAINTASTIC_SERVER_HARDWARE_INTERFACE_Z21INTERFACE_HPP + +#include "interface.hpp" +#include "../protocol/z21/clientkernel.hpp" +#include "../protocol/z21/clientsettings.hpp" +#include "../decoder/decodercontroller.hpp" +#include "../decoder/decoderlist.hpp" +#include "../../core/objectproperty.hpp" + +/** + * @brief Z21 hardware interface + */ +class Z21Interface final + : public Interface + , public DecoderController +{ + CLASS_ID("interface.z21") + DEFAULT_ID("z21") + CREATE(Z21Interface) + + private: + std::unique_ptr m_kernel; + boost::signals2::connection m_z21PropertyChanged; + + void addToWorld() final; + void destroying() final; + void worldEvent(WorldState state, WorldEvent event) final; + + void idChanged(const std::string& newId) final; + + void updateVisible(); + + protected: + bool setOnline(bool& value) final; + + public: + Property hostname; + Property port; + ObjectProperty z21; + ObjectProperty decoders; + Property hardwareType; + Property serialNumber; + Property firmwareVersion; + + Z21Interface(const std::weak_ptr& world, std::string_view _id); + + // DecoderController: + [[nodiscard]] bool addDecoder(Decoder& decoder) final; + [[nodiscard]] bool removeDecoder(Decoder& decoder) final; + void decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber) final; +}; + +#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..dea2a424 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..fde0a2c0 100644 --- a/server/src/hardware/output/output.hpp +++ b/server/src/hardware/output/output.hpp @@ -24,21 +24,42 @@ #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 +#ifdef interface + #undef interface // interface is defined in combaseapi.h +#endif + +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/output/outputs.hpp b/server/src/hardware/protocol/dcc/dcc.hpp similarity index 57% rename from server/src/hardware/output/outputs.hpp rename to server/src/hardware/protocol/dcc/dcc.hpp index ed65c8b4..70f12f17 100644 --- a/server/src/hardware/output/outputs.hpp +++ b/server/src/hardware/protocol/dcc/dcc.hpp @@ -1,9 +1,9 @@ /** - * server/src/hardware/output/outputs.hpp + * server/src/hardware/protocol/dcc/dcc.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,25 @@ * 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_PROTOCOL_DCC_DCC_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_DCC_DCC_HPP -#include "output.hpp" -#include "../../utils/makearray.hpp" +#include +#include "../../../utils/inrange.hpp" -#include "loconetoutput.hpp" +namespace DCC { -struct Outputs +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) { - static constexpr std::string_view classIdPrefix = "output."; + return inRange(address, addressLongStart, addressLongMax); +} - 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); -}; +} #endif diff --git a/server/src/hardware/protocol/dccplusplus/config.hpp b/server/src/hardware/protocol/dccplusplus/config.hpp new file mode 100644 index 00000000..6fa06210 --- /dev/null +++ b/server/src/hardware/protocol/dccplusplus/config.hpp @@ -0,0 +1,42 @@ +/** + * server/src/hardware/protocol/dccplusplus/config.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_DCCPLUSPLUS_CONFIG_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_DCCPLUSPLUS_CONFIG_HPP + +#include + +namespace DCCPlusPlus { + +struct Config +{ + static constexpr uint32_t functionNumberMax = 56; + + bool useEx; + uint8_t speedSteps; + uint16_t startupDelay; + bool debugLogRXTX; +}; + +} + +#endif diff --git a/server/src/hardware/protocol/dccplusplus/dccplusplus.cpp b/server/src/hardware/protocol/dccplusplus/dccplusplus.cpp deleted file mode 100644 index 8fe2d099..00000000 --- a/server/src/hardware/protocol/dccplusplus/dccplusplus.cpp +++ /dev/null @@ -1,146 +0,0 @@ -/** - * server/src/hardware/protocol/dccplusplus/dccplusplus.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 "dccplusplus.hpp" -#include "messages.hpp" -#include "../../../core/eventloop.hpp" -#include "../../commandstation/commandstation.hpp" -#include "../../decoder/decoder.hpp" -#include "../../decoder/decoderchangeflags.hpp" -#include "../../../core/attributes.hpp" -#include "../../../log/log.hpp" -#include "../../../utils/displayname.hpp" - -namespace DCCPlusPlus { - -constexpr std::array speedStepValues{28, 128}; - -DCCPlusPlus::DCCPlusPlus(Object& _parent, const std::string& parentPropertyName, std::function send) : - SubObject(_parent, parentPropertyName), - m_commandStation{dynamic_cast(&_parent)}, - m_send{std::move(send)}, - m_debugLogRXTX{false}, - useEx{this, "use_ex", true, PropertyFlags::ReadWrite | PropertyFlags::Store}, - speedSteps{this, "speed_steps", 128, PropertyFlags::ReadWrite | PropertyFlags::Store, - [this](uint8_t value) - { - this->send(Ex::setSpeedSteps(value)); - }}, - debugLogRXTX{this, "debug_log", m_debugLogRXTX, PropertyFlags::ReadWrite | PropertyFlags::Store, - [this](bool value) - { - m_debugLogRXTX = value; - }} -{ - assert(m_send); - - Attributes::addEnabled(useEx, false); // disable for now, only ex is currently supported - m_interfaceItems.add(useEx); - - Attributes::addDisplayName(speedSteps, DisplayName::Hardware::speedSteps); - Attributes::addValues(speedSteps, speedStepValues); - m_interfaceItems.add(speedSteps); - - m_interfaceItems.add(debugLogRXTX); -} - -bool DCCPlusPlus::send(std::string_view message) -{ - if(m_debugLogRXTX) - Log::log(*this, LogMessage::D2001_TX_X, message); - return m_send(message); -} - -void DCCPlusPlus::receive(std::string_view message) -{ - // NOTE: this function is called async! - - if(m_debugLogRXTX) - EventLoop::call([this, msg=std::string(message)](){ Log::log(*this, LogMessage::D2002_RX_X, msg); }); - - if(message.size() > 1 && message[0] == '<') - { - switch(message[1]) - { - case 'p': // Power on/off response - if(m_commandStation) - { - if(message[2] == '0') - m_commandStation->powerOn.setValueInternal(false); - else if(message[2] == '1') - m_commandStation->powerOn.setValueInternal(true); - } - break; - } - } -} - -void DCCPlusPlus::emergencyStopChanged(bool value) -{ - if(value) - send(Ex::emergencyStop()); -} - -void DCCPlusPlus::powerOnChanged(bool value) -{ - if(value) - send(Ex::powerOn()); - else - send(Ex::powerOff()); -} - -void DCCPlusPlus::checkDecoder(const Decoder& decoder) const -{ - assert(m_commandStation); - - if(decoder.protocol != DecoderProtocol::Auto && decoder.protocol != DecoderProtocol::DCC) - Log::log(decoder, LogMessage::C2002_DCCPLUSPLUS_ONLY_SUPPORTS_THE_DCC_PROTOCOL); - - if(decoder.protocol == DecoderProtocol::DCC && decoder.address <= 127 && decoder.longAddress) - Log::log(decoder, LogMessage::C2003_DCCPLUSPLUS_DOESNT_SUPPORT_DCC_LONG_ADDRESSES_BELOW_128); - - if(decoder.speedSteps != Decoder::speedStepsAuto && decoder.speedSteps != speedSteps) - Log::log(decoder, LogMessage::W2003_COMMAND_STATION_DOESNT_SUPPORT_X_SPEEDSTEPS_USING_X, decoder.speedSteps.value(), speedSteps.value()); - - for(const auto& function : *decoder.functions) - if(function->number > functionNumberMax) - { - Log::log(decoder, LogMessage::W2002_COMMAND_STATION_DOESNT_SUPPORT_FUNCTIONS_ABOVE_FX, functionNumberMax); - break; - } -} - -void DCCPlusPlus::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber) -{ - if(has(changes, DecoderChangeFlags::EmergencyStop | DecoderChangeFlags::Throttle | DecoderChangeFlags::Direction)) - send(Ex::setLocoSpeedAndDirection(decoder.address, Decoder::throttleToSpeedStep(decoder.throttle, 126), decoder.emergencyStop, decoder.direction)); - else if(has(changes, DecoderChangeFlags::FunctionValue) && functionNumber <= functionNumberMax) - send(Ex::setLocoFunction(decoder.address, static_cast(functionNumber), decoder.getFunctionValue(functionNumber))); -} - -void DCCPlusPlus::loaded() -{ - SubObject::loaded(); - m_debugLogRXTX = debugLogRXTX; -} - -} diff --git a/server/src/hardware/protocol/dccplusplus/dccplusplus.hpp b/server/src/hardware/protocol/dccplusplus/dccplusplus.hpp deleted file mode 100644 index 4866537f..00000000 --- a/server/src/hardware/protocol/dccplusplus/dccplusplus.hpp +++ /dev/null @@ -1,70 +0,0 @@ -/** - * server/src/hardware/protocol/dccplusplus/dccplusplus.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_DCCPLUSPLUS_DCCPLUSPLUS_HPP -#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_DCCPLUSPLUS_DCCPLUSPLUS_HPP - -#include "../../../core/subobject.hpp" -#include -#include "../../../core/property.hpp" - -class CommandStation; -class Decoder; -enum class DecoderChangeFlags; - -namespace DCCPlusPlus { - -class DCCPlusPlus : public SubObject -{ - CLASS_ID("protocol.dccplusplus") - - private: - static constexpr uint32_t functionNumberMax = 68; - - CommandStation* const m_commandStation; // valid if parent is command station, else nullptr - std::function m_send; - std::atomic_bool m_debugLogRXTX; - - protected: - void loaded() final; - - public: - Property useEx; - Property speedSteps; - Property debugLogRXTX; - - DCCPlusPlus(Object& _parent, const std::string& parentPropertyName, std::function send); - - bool send(std::string_view message); - void receive(std::string_view message); - - void emergencyStopChanged(bool value); - void powerOnChanged(bool value); - - void checkDecoder(const Decoder& decoder) const; - void decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber); -}; - -} - -#endif diff --git a/server/src/hardware/protocol/dccplusplus/iohandler/iohandler.cpp b/server/src/hardware/protocol/dccplusplus/iohandler/iohandler.cpp new file mode 100644 index 00000000..8e2ca0da --- /dev/null +++ b/server/src/hardware/protocol/dccplusplus/iohandler/iohandler.cpp @@ -0,0 +1,74 @@ +/** + * server/src/hardware/protocol/dccplusplus/iohandler/iohandler.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 "iohandler.hpp" +#include "../kernel.hpp" + +namespace DCCPlusPlus { + +IOHandler::IOHandler(Kernel& kernel) + : m_kernel{kernel} + , m_readBufferOffset{0} + , m_writeBufferOffset{0} +{ +} + +bool IOHandler::send(std::string_view message) +{ + if(m_writeBufferOffset + message.size() > m_writeBuffer.size()) + return false; + + const bool wasEmpty = m_writeBufferOffset == 0; + memcpy(m_writeBuffer.data() + m_writeBufferOffset, message.data(), message.size()); + m_writeBufferOffset += message.size(); + + if(wasEmpty) + write(); + + return true; +} + +void IOHandler::processRead(size_t bytesTransferred) +{ + const char* pos = reinterpret_cast(m_readBuffer.data()); + bytesTransferred += m_readBufferOffset; + + size_t i = 0; + while(i < bytesTransferred) + { + if(*(pos + i) == '\n') + { + m_kernel.receive(std::string_view{pos, i + 1}); + pos += i + 1; + bytesTransferred -= i + 1; + i = 0; + } + else + i++; + } + + if(bytesTransferred != 0) + memmove(m_readBuffer.data(), pos, bytesTransferred); + m_readBufferOffset = bytesTransferred; +} + +} diff --git a/server/src/hardware/commandstation/dccplusplusserial.hpp b/server/src/hardware/protocol/dccplusplus/iohandler/iohandler.hpp similarity index 51% rename from server/src/hardware/commandstation/dccplusplusserial.hpp rename to server/src/hardware/protocol/dccplusplus/iohandler/iohandler.hpp index 7262fe66..0aa9c6e2 100644 --- a/server/src/hardware/commandstation/dccplusplusserial.hpp +++ b/server/src/hardware/protocol/dccplusplus/iohandler/iohandler.hpp @@ -1,5 +1,5 @@ /** - * server/src/hardware/commandstation/dccplusplusserial.hpp + * server/src/hardware/protocol/dccplusplus/iohandler/iohandler.hpp * * This file is part of the traintastic source code. * @@ -20,31 +20,43 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef TRAINTASTIC_SERVER_HARDWARE_COMMANDSTATION_dccplusplusserial_HPP -#define TRAINTASTIC_SERVER_HARDWARE_COMMANDSTATION_dccplusplusserial_HPP +#ifndef TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_DCCPLUSPLUS_IOHANDLER_IOHANDLER_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_DCCPLUSPLUS_IOHANDLER_IOHANDLER_HPP -#include "serialcommandstation.hpp" -#include "../protocol/dccplusplus/dccplusplus.hpp" +#include +#include +#include -class DCCPlusPlusSerial : public SerialCommandStation +namespace DCCPlusPlus { + +class Kernel; + +class IOHandler { protected: - void emergencyStopChanged(bool value) final; - void powerOnChanged(bool value) final; - void checkDecoder(const Decoder& decoder) const; - void decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber) final; + Kernel& m_kernel; + std::array m_readBuffer; + size_t m_readBufferOffset; + std::array m_writeBuffer; + size_t m_writeBufferOffset; - bool send(std::string_view message); - void started() final; - void read() final; + IOHandler(Kernel& kernel); + + void processRead(size_t bytesTransferred); + virtual void write() = 0; public: - CLASS_ID("command_station.dccplusplus_serial") - CREATE(DCCPlusPlusSerial) + IOHandler(const IOHandler&) = delete; + IOHandler& operator =(const IOHandler&) = delete; - ObjectProperty dccPlusPlus; + virtual ~IOHandler() = default; - DCCPlusPlusSerial(const std::weak_ptr& world, std::string_view _id); + virtual void start() = 0; + virtual void stop() = 0; + + bool send(std::string_view message); }; +} + #endif diff --git a/server/src/hardware/protocol/dccplusplus/iohandler/serialiohandler.cpp b/server/src/hardware/protocol/dccplusplus/iohandler/serialiohandler.cpp new file mode 100644 index 00000000..d329be8d --- /dev/null +++ b/server/src/hardware/protocol/dccplusplus/iohandler/serialiohandler.cpp @@ -0,0 +1,105 @@ +/** + * server/src/hardware/protocol/dccplusplus/iohandler/serialiohandler.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 "serialiohandler.hpp" +#include "../kernel.hpp" +#include "../messages.hpp" +#include "../../../../core/eventloop.hpp" +#include "../../../../log/log.hpp" +#include "../../../../utils/serialport.hpp" + +namespace DCCPlusPlus { + +SerialIOHandler::SerialIOHandler(Kernel& kernel, const std::string& device, uint32_t baudrate, SerialFlowControl flowControl) + : IOHandler(kernel) + , m_serialPort{m_kernel.ioContext()} +{ + SerialPort::open(m_serialPort, device, baudrate, 8, SerialParity::None, SerialStopBits::One, flowControl); +} + +SerialIOHandler::~SerialIOHandler() +{ + if(m_serialPort.is_open()) + m_serialPort.close(); +} + +void SerialIOHandler::start() +{ + read(); +} + +void SerialIOHandler::stop() +{ + m_serialPort.close(); +} + +void SerialIOHandler::read() +{ + m_serialPort.async_read_some(boost::asio::buffer(m_readBuffer.data() + m_readBufferOffset, m_readBuffer.size() - m_readBufferOffset), + [this](const boost::system::error_code& ec, std::size_t bytesTransferred) + { + if(!ec) + { + processRead(bytesTransferred); + read(); + } + else if(ec != boost::asio::error::operation_aborted) + { + EventLoop::call( + [this, ec]() + { + Log::log(m_kernel.logId(), LogMessage::E2002_SERIAL_READ_FAILED_X, ec); + // TODO interface status -> error + }); + } + }); +} + +void SerialIOHandler::write() +{ + m_serialPort.async_write_some(boost::asio::buffer(m_writeBuffer.data(), m_writeBufferOffset), + [this](const boost::system::error_code& ec, std::size_t bytesTransferred) + { + if(!ec) + { + if(bytesTransferred < m_writeBufferOffset) + { + m_writeBufferOffset -= bytesTransferred; + memmove(m_writeBuffer.data(), m_writeBuffer.data() + bytesTransferred, m_writeBufferOffset); + write(); + } + else + m_writeBufferOffset = 0; + } + else if(ec != boost::asio::error::operation_aborted) + { + EventLoop::call( + [this, ec]() + { + Log::log(m_kernel.logId(), LogMessage::E2001_SERIAL_WRITE_FAILED_X, ec); + // TODO interface status -> error + }); + } + }); +} + +} diff --git a/server/src/hardware/protocol/dccplusplus/iohandler/serialiohandler.hpp b/server/src/hardware/protocol/dccplusplus/iohandler/serialiohandler.hpp new file mode 100644 index 00000000..142419ed --- /dev/null +++ b/server/src/hardware/protocol/dccplusplus/iohandler/serialiohandler.hpp @@ -0,0 +1,53 @@ +/** + * server/src/hardware/protocol/dccplusplus/iohandler/serialiohandler.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_DCCPLUSPLUS_IOHANDLER_SERIALIOHANDLER_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_DCCPLUSPLUS_IOHANDLER_SERIALIOHANDLER_HPP + +#include "iohandler.hpp" +#include +#include "../../../../enum/serialflowcontrol.hpp" + +namespace DCCPlusPlus { + +class SerialIOHandler final : public IOHandler +{ + private: + boost::asio::serial_port m_serialPort; + + void read(); + + protected: + void write() final; + + public: + SerialIOHandler(Kernel& kernel, const std::string& device, uint32_t baudrate, SerialFlowControl flowControl); + ~SerialIOHandler() final; + + void start() final; + void stop() final; +}; + +} + +#endif + diff --git a/server/src/hardware/protocol/dccplusplus/kernel.cpp b/server/src/hardware/protocol/dccplusplus/kernel.cpp new file mode 100644 index 00000000..1df94ac6 --- /dev/null +++ b/server/src/hardware/protocol/dccplusplus/kernel.cpp @@ -0,0 +1,247 @@ +/** + * server/src/hardware/protocol/dccplusplus/kernel.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 "kernel.hpp" +#include "messages.hpp" +#include "../../decoder/decoder.hpp" +#include "../../decoder/decoderchangeflags.hpp" +#include "../../../utils/setthreadname.hpp" +#include "../../../utils/rtrim.hpp" +#include "../../../core/eventloop.hpp" +#include "../../../log/log.hpp" + +namespace DCCPlusPlus { + +Kernel::Kernel(const Config& config) + : m_ioContext{1} + , m_startupDelayTimer{m_ioContext} + , m_decoderController{nullptr} + , m_config{config} +#ifndef NDEBUG + , m_started{false} +#endif +{ +} + +void Kernel::setConfig(const Config& config) +{ + m_ioContext.post( + [this, newConfig=config]() + { + if(newConfig.useEx && newConfig.speedSteps != m_config.speedSteps) + send(Ex::setSpeedSteps(newConfig.speedSteps)); + + m_config = newConfig; + }); +} + +void Kernel::start() +{ + assert(m_ioHandler); + assert(!m_started); + + // reset all state values + m_powerOn = TriState::Undefined; + m_emergencyStop = TriState::Undefined; + + m_thread = std::thread( + [this]() + { + setThreadName("dcc++"); + auto work = std::make_shared(m_ioContext); + m_ioContext.run(); + }); + + m_ioContext.post( + [this]() + { + m_ioHandler->start(); + + m_startupDelayTimer.expires_after(boost::asio::chrono::milliseconds(m_config.startupDelay)); + m_startupDelayTimer.async_wait(std::bind(&Kernel::startupDelayExpired, this, std::placeholders::_1)); + }); + +#ifndef NDEBUG + m_started = true; +#endif +} + +void Kernel::stop() +{ + m_ioContext.post( + [this]() + { + m_startupDelayTimer.cancel(); + + m_ioHandler->stop(); + }); + + m_ioContext.stop(); + + m_thread.join(); + +#ifndef NDEBUG + m_started = false; +#endif +} + +void Kernel::receive(std::string_view message) +{ + if(m_config.debugLogRXTX) + EventLoop::call( + [this, msg=std::string(rtrim(message, '\n'))]() + { + Log::log(m_logId, LogMessage::D2002_RX_X, msg); + }); + + if(message.size() > 1 && message[0] == '<') + { + switch(message[1]) + { + case 'p': // Power on/off response + if(message[2] == '0') + { + if(m_powerOn != TriState::False) + { + m_powerOn = TriState::False; + + if(m_onPowerOnChanged) + EventLoop::call( + [this]() + { + m_onPowerOnChanged(false); + }); + } + } + else if(message[2] == '1') + { + if(m_powerOn != TriState::True) + { + m_powerOn = TriState::True; + + if(m_onPowerOnChanged) + EventLoop::call( + [this]() + { + m_onPowerOnChanged(true); + }); + } + } + break; + } + } +} + +void Kernel::powerOn() +{ + m_ioContext.post( + [this]() + { + if(m_powerOn != TriState::True) + { + send(Ex::powerOn()); + } + }); +} + +void Kernel::powerOff() +{ + m_ioContext.post( + [this]() + { + if(m_powerOn != TriState::False) + { + send(Ex::powerOff()); + } + }); +} + +void Kernel::emergencyStop() +{ + m_ioContext.post( + [this]() + { + if(m_emergencyStop != TriState::True) + { + m_emergencyStop = TriState::True; + send(Ex::emergencyStop()); + } + }); +} + +void Kernel::clearEmergencyStop() +{ + m_ioContext.post( + [this]() + { + m_emergencyStop = TriState::False; + }); +} + +void Kernel::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber) +{ + if(has(changes, DecoderChangeFlags::EmergencyStop | DecoderChangeFlags::Throttle | DecoderChangeFlags::Direction)) + { + const uint8_t speed = Decoder::throttleToSpeedStep(decoder.throttle, 126); + m_ioContext.post( + [this, address=decoder.address.value(), emergencyStop=decoder.emergencyStop.value(), speed, direction=decoder.direction.value()]() + { + send(Ex::setLocoSpeedAndDirection(address, speed, emergencyStop | (m_emergencyStop != TriState::False), direction)); + }); + } + else if(has(changes, DecoderChangeFlags::FunctionValue) && functionNumber <= Config::functionNumberMax) + { + postSend(Ex::setLocoFunction(decoder.address, static_cast(functionNumber), decoder.getFunctionValue(functionNumber))); + } +} + +void Kernel::setIOHandler(std::unique_ptr handler) +{ + assert(handler); + assert(!m_ioHandler); + m_ioHandler = std::move(handler); +} + +void Kernel::send(std::string_view message) +{ + if(m_ioHandler->send(message)) + { + if(m_config.debugLogRXTX) + EventLoop::call( + [this, msg=std::string(rtrim(message, '\n'))]() + { + Log::log(m_logId, LogMessage::D2001_TX_X, msg); + }); + } + else + {} // log message and go to error state +} + +void Kernel::startupDelayExpired(const boost::system::error_code& ec) +{ + if(ec) + return; + + m_onStarted(); +} + +} diff --git a/server/src/hardware/protocol/dccplusplus/kernel.hpp b/server/src/hardware/protocol/dccplusplus/kernel.hpp new file mode 100644 index 00000000..41bc5f42 --- /dev/null +++ b/server/src/hardware/protocol/dccplusplus/kernel.hpp @@ -0,0 +1,232 @@ +/** + * server/src/hardware/protocol/dccplusplus/kernel.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_DCCPLUSPLUS_KERNEL_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_DCCPLUSPLUS_KERNEL_HPP + +#include +#include +#include +#include +#include +#include "config.hpp" +#include "iohandler/iohandler.hpp" + +class Decoder; +enum class DecoderChangeFlags; +class DecoderController; + +namespace DCCPlusPlus { + +struct Message; + +class Kernel +{ + private: + boost::asio::io_context m_ioContext; + std::unique_ptr m_ioHandler; + std::thread m_thread; + std::string m_logId; + boost::asio::steady_timer m_startupDelayTimer; + std::function m_onStarted; + + TriState m_powerOn; + TriState m_emergencyStop; + std::function m_onPowerOnChanged; + + DecoderController* m_decoderController; + + Config m_config; +#ifndef NDEBUG + bool m_started; +#endif + + Kernel(const Config& config); + + void setIOHandler(std::unique_ptr handler); + + void postSend(const std::string& message) + { + m_ioContext.post( + [this, message]() + { + send(message); + }); + } + + void send(std::string_view message); + + void startupDelayExpired(const boost::system::error_code& ec); + + public: + Kernel(const Kernel&) = delete; + Kernel& operator =(const Kernel&) = delete; + + /** + * @brief IO context for DCC++ kernel and IO handler + * + * @return The IO context + */ + boost::asio::io_context& ioContext() { return m_ioContext; } + + /** + * @brief Create kernel and IO handler + * + * @param[in] config DCC++ configuration + * @param[in] args IO handler arguments + * @return The kernel instance + */ + template + static std::unique_ptr create(const Config& config, Args... args) + { + static_assert(std::is_base_of_v); + std::unique_ptr kernel{new Kernel(config)}; + kernel->setIOHandler(std::make_unique(*kernel, std::forward(args)...)); + return kernel; + } + + /** + * @brief Access the IO handler + * + * @return The IO handler + * @note The IO handler runs in the kernel's IO context, not all functions can be called safely! + */ + template + T& ioHandler() + { + assert(dynamic_cast(m_ioHandler.get())); + return static_cast(*m_ioHandler); + } + + /** + * + * + */ + inline const std::string& logId() { return m_logId; } + + /** + * @brief Set object id used for log messages + * + * @param[in] value The object id + */ + inline void setLogId(std::string value) + { + m_logId = std::move(value); + } + + /** + * @brief Set DCC++ configuration + * + * @param[in] config The DCC++ configuration + */ + void setConfig(const Config& config); + + /** + * @brief ... + * + * @param[in] callback ... + * @note This function may not be called when the kernel is running. + */ + inline void setOnStarted(std::function callback) + { + assert(!m_started); + m_onStarted = std::move(callback); + } + + /** + * @brief ... + * + * @param[in] callback ... + * @note This function may not be called when the kernel is running. + */ + inline void setOnPowerOnChanged(std::function callback) + { + assert(!m_started); + m_onPowerOnChanged = std::move(callback); + } + + /** + * @brief Set the decoder controller + * + * @param[in] decoderController The decoder controller + * @note This function may not be called when the kernel is running. + */ + inline void setDecoderController(DecoderController* decoderController) + { + assert(!m_started); + m_decoderController = decoderController; + } + + /** + * @brief Start the kernel and IO handler + */ + void start(); + + /** + * @brief Stop the kernel and IO handler + */ + void stop(); + + /** + * @brief ... + * + * This must be called by the IO handler whenever a DCC++ message is received. + * + * @param[in] message The received DCC++ message + * @note This function must run in the kernel's IO context + */ + void receive(std::string_view message); + + /** + * + * + */ + void powerOn(); + + /** + * + * + */ + void powerOff(); + + /** + * + * + */ + void emergencyStop(); + + /** + * + * + */ + void clearEmergencyStop(); + + /** + * + * + */ + void decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber); +}; + +} + +#endif diff --git a/server/src/hardware/protocol/dccplusplus/settings.cpp b/server/src/hardware/protocol/dccplusplus/settings.cpp new file mode 100644 index 00000000..6171f29d --- /dev/null +++ b/server/src/hardware/protocol/dccplusplus/settings.cpp @@ -0,0 +1,62 @@ +/** + * server/src/hardware/protocol/dccplusplus/settings.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 "settings.hpp" +#include "../../../core/attributes.hpp" +#include "../../../utils/displayname.hpp" + +namespace DCCPlusPlus { + +constexpr std::array speedStepValues{28, 128}; + +Settings::Settings(Object& _parent, const std::string& parentPropertyName) + : SubObject(_parent, parentPropertyName) + , useEx{this, "use_ex", true, PropertyFlags::ReadWrite | PropertyFlags::Store} + , speedSteps{this, "speed_steps", 128, PropertyFlags::ReadWrite | PropertyFlags::Store} + , startupDelay{this, "startup_delay", startupDelayDefault, PropertyFlags::ReadWrite | PropertyFlags::Store} + , debugLogRXTX{this, "debug_log", false, PropertyFlags::ReadWrite | PropertyFlags::Store} +{ + Attributes::addEnabled(useEx, false); // disable for now, only ex is currently supported + m_interfaceItems.add(useEx); + + Attributes::addDisplayName(speedSteps, DisplayName::Hardware::speedSteps); + Attributes::addValues(speedSteps, speedStepValues); + m_interfaceItems.add(speedSteps); + + Attributes::addMinMax(startupDelay, startupDelayMin, startupDelayMax); + m_interfaceItems.add(startupDelay); + + Attributes::addDisplayName(debugLogRXTX, DisplayName::Hardware::debugLogRXTX); + m_interfaceItems.add(debugLogRXTX); +} + +Config Settings::config() const +{ + Config config; + config.useEx = useEx; + config.speedSteps = speedSteps; + config.startupDelay = startupDelay; + config.debugLogRXTX = debugLogRXTX; + return config; +} + +} diff --git a/server/src/hardware/protocol/dccplusplus/settings.hpp b/server/src/hardware/protocol/dccplusplus/settings.hpp new file mode 100644 index 00000000..2496e302 --- /dev/null +++ b/server/src/hardware/protocol/dccplusplus/settings.hpp @@ -0,0 +1,54 @@ +/** + * server/src/hardware/protocol/dccplusplus/settings.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_DCCPLUSPLUS_SETTINGS_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_DCCPLUSPLUS_SETTINGS_HPP + +#include "../../../core/subobject.hpp" +#include "../../../core/property.hpp" +#include "config.hpp" + +namespace DCCPlusPlus { + +class Settings : public SubObject +{ + CLASS_ID("dccplusplus_settings") + + private: + static constexpr uint16_t startupDelayMin = 0; + static constexpr uint16_t startupDelayDefault = 2'500; + static constexpr uint16_t startupDelayMax = 60'000; + + public: + Property useEx; + Property speedSteps; + Property startupDelay; + Property debugLogRXTX; + + Settings(Object& _parent, const std::string& parentPropertyName); + + Config config() const; +}; + +} + +#endif diff --git a/server/src/utils/string.hpp b/server/src/hardware/protocol/ecos/config.hpp similarity index 73% rename from server/src/utils/string.hpp rename to server/src/hardware/protocol/ecos/config.hpp index 58c279db..d6c19093 100644 --- a/server/src/utils/string.hpp +++ b/server/src/hardware/protocol/ecos/config.hpp @@ -1,9 +1,9 @@ /** - * server/src/utils/string.hpp + * server/src/hardware/protocol/ecos/config.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,14 +20,16 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef TRAINTASTIC_SERVER_UTILS_STRING_HPP -#define TRAINTASTIC_SERVER_UTILS_STRING_HPP +#ifndef TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_ECOS_CONFIG_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_ECOS_CONFIG_HPP -#include +namespace ECoS { -constexpr bool startsWith(std::string_view s, std::string_view prefix) +struct Config { - return s.rfind(prefix, 0) == 0; + bool debugLogRXTX; +}; + } #endif diff --git a/server/src/hardware/protocol/ecos/iohandler/iohandler.cpp b/server/src/hardware/protocol/ecos/iohandler/iohandler.cpp new file mode 100644 index 00000000..57ad7635 --- /dev/null +++ b/server/src/hardware/protocol/ecos/iohandler/iohandler.cpp @@ -0,0 +1,103 @@ +/** + * server/src/hardware/protocol/ecos/iohandler/iohandler.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 "iohandler.hpp" +#include "../kernel.hpp" + +namespace ECoS { + +IOHandler::IOHandler(Kernel& kernel) + : m_kernel{kernel} + , m_readBufferOffset{0} + , m_readPos{0} + , m_writeBufferOffset{0} +{ +} + +bool IOHandler::send(std::string_view message) +{ + if(m_writeBufferOffset + message.size() > m_writeBuffer.size()) + return false; + + const bool wasEmpty = m_writeBufferOffset == 0; + memcpy(m_writeBuffer.data() + m_writeBufferOffset, message.data(), message.size()); + m_writeBufferOffset += message.size(); + + if(wasEmpty) + write(); + + return true; +} + +void IOHandler::processRead(size_t bytesTransferred) +{ + static constexpr std::string_view typeReply{"REPLY"}; + static constexpr std::string_view typeEvent{"EVENT"}; + static constexpr size_t typeLength = 5; + + std::string_view buffer{m_readBuffer.data(), m_readBufferOffset + bytesTransferred}; + + //! @todo this can be a bit optimized by remembering the "state" when a message in not yet complete. + while(m_readPos != buffer.size()) + { + m_readPos = buffer.find('<', m_readPos); + if(m_readPos != std::string_view::npos) + { + if((buffer.size() - m_readPos) >= typeLength) + { + std::string_view type{m_readBuffer.data() + m_readPos + 1, typeLength}; + if(type == typeReply || type == typeEvent) + { + size_t pos = buffer.find(std::string_view{"', pos); + if(end != buffer.size()) + { + m_kernel.receive(std::string_view{m_readBuffer.data() + m_readPos, end - m_readPos + 1}); + m_readPos = end + 1; + } + else + break; + } + else + break; + } + } + else + break; + } + else + m_readPos = buffer.size(); + } + + if(m_readPos > 0) + { + assert(m_readPos <= buffer.size()); + m_readBufferOffset = buffer.size() - m_readPos; + if(m_readBufferOffset > 0) + memmove(m_readBuffer.data(), m_readBuffer.data() + m_readPos, m_readBufferOffset); + m_readPos = 0; + } +} + +} diff --git a/server/src/hardware/protocol/ecos/iohandler/iohandler.hpp b/server/src/hardware/protocol/ecos/iohandler/iohandler.hpp new file mode 100644 index 00000000..1fe4d7f4 --- /dev/null +++ b/server/src/hardware/protocol/ecos/iohandler/iohandler.hpp @@ -0,0 +1,63 @@ +/** + * server/src/hardware/protocol/ecos/iohandler/iohandler.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_ECOS_IOHANDLER_IOHANDLER_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_ECOS_IOHANDLER_IOHANDLER_HPP + +#include +#include +#include + +namespace ECoS { + +class Kernel; + +class IOHandler +{ + protected: + Kernel& m_kernel; + std::array m_readBuffer; + size_t m_readBufferOffset; + size_t m_readPos; + std::array m_writeBuffer; + size_t m_writeBufferOffset; + + IOHandler(Kernel& kernel); + + void processRead(size_t bytesTransferred); + virtual void write() = 0; + + public: + IOHandler(const IOHandler&) = delete; + IOHandler& operator =(const IOHandler&) = delete; + + virtual ~IOHandler() = default; + + virtual void start() = 0; + virtual void stop() = 0; + + bool send(std::string_view message); +}; + +} + +#endif diff --git a/server/src/hardware/protocol/ecos/iohandler/tcpiohandler.cpp b/server/src/hardware/protocol/ecos/iohandler/tcpiohandler.cpp new file mode 100644 index 00000000..c097aa0b --- /dev/null +++ b/server/src/hardware/protocol/ecos/iohandler/tcpiohandler.cpp @@ -0,0 +1,117 @@ +/** + * server/src/hardware/protocol/ecos/iohandler/serialiohandler.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 "tcpiohandler.hpp" +#include "../kernel.hpp" +#include "../../../../core/eventloop.hpp" +#include "../../../../log/log.hpp" +#include "../../../../log/logmessageexception.hpp" + +namespace ECoS { + +TCPIOHandler::TCPIOHandler(Kernel& kernel, const std::string& hostname, uint16_t port) + : IOHandler(kernel) + , m_socket{m_kernel.ioContext()} +{ + boost::system::error_code ec; + + m_endpoint.port(port); + m_endpoint.address(boost::asio::ip::make_address(hostname, ec)); + if(ec) + throw LogMessageException(LogMessage::E2003_MAKE_ADDRESS_FAILED_X, ec); + + m_socket.connect(m_endpoint, ec); + if(ec) + throw LogMessageException(LogMessage::E2005_SOCKET_CONNECT_FAILED_X, ec); + + + m_socket.set_option(boost::asio::socket_base::linger(true, 0)); + m_socket.set_option(boost::asio::ip::tcp::no_delay(true)); +} + +TCPIOHandler::~TCPIOHandler() +{ + if(m_socket.is_open()) + m_socket.close(); +} + +void TCPIOHandler::start() +{ + read(); +} + +void TCPIOHandler::stop() +{ + m_socket.close(); +} + +void TCPIOHandler::read() +{ + m_socket.async_read_some(boost::asio::buffer(m_readBuffer.data() + m_readBufferOffset, m_readBuffer.size() - m_readBufferOffset), + [this](const boost::system::error_code& ec, std::size_t bytesTransferred) + { + if(!ec) + { + processRead(bytesTransferred); + read(); + } + else if(ec != boost::asio::error::operation_aborted) + { + EventLoop::call( + [this, ec]() + { + Log::log(m_kernel.logId(), LogMessage::E2008_SOCKET_READ_FAILED_X, ec); + // TODO interface status -> error + }); + } + }); +} + +void TCPIOHandler::write() +{ + m_socket.async_write_some(boost::asio::buffer(m_writeBuffer.data(), m_writeBufferOffset), + [this](const boost::system::error_code& ec, std::size_t bytesTransferred) + { + if(!ec) + { + if(bytesTransferred < m_writeBufferOffset) + { + m_writeBufferOffset -= bytesTransferred; + memmove(m_writeBuffer.data(), m_writeBuffer.data() + bytesTransferred, m_writeBufferOffset); + write(); + } + else + m_writeBufferOffset = 0; + } + else if(ec != boost::asio::error::operation_aborted) + { + EventLoop::call( + [this, ec]() + { + Log::log(m_kernel.logId(), LogMessage::E2007_SOCKET_WRITE_FAILED_X, ec); + // TODO interface status -> error + }); + } + }); +} + +} diff --git a/server/src/hardware/protocol/ecos/iohandler/tcpiohandler.hpp b/server/src/hardware/protocol/ecos/iohandler/tcpiohandler.hpp new file mode 100644 index 00000000..024a0466 --- /dev/null +++ b/server/src/hardware/protocol/ecos/iohandler/tcpiohandler.hpp @@ -0,0 +1,53 @@ +/** + * server/src/hardware/protocol/ecos/iohandler/serialiohandler.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_ECOS_IOHANDLER_TCPIOHANDLER_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_ECOS_IOHANDLER_TCPIOHANDLER_HPP + +#include "iohandler.hpp" +#include + +namespace ECoS { + +class TCPIOHandler final : public IOHandler +{ + private: + boost::asio::ip::tcp::socket m_socket; + boost::asio::ip::tcp::endpoint m_endpoint; + + void read(); + + protected: + void write() final; + + public: + TCPIOHandler(Kernel& kernel, const std::string& hostname, uint16_t port = 15471); + ~TCPIOHandler() final; + + void start() final; + void stop() final; +}; + +} + +#endif + diff --git a/server/src/hardware/protocol/ecos/kernel.cpp b/server/src/hardware/protocol/ecos/kernel.cpp new file mode 100644 index 00000000..c9f80744 --- /dev/null +++ b/server/src/hardware/protocol/ecos/kernel.cpp @@ -0,0 +1,238 @@ +/** + * server/src/hardware/protocol/ecos/kernel.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 "kernel.hpp" +#include +#include "messages.hpp" +#include "object/ecos.hpp" +#include "object/locomotivemanager.hpp" +#include "object/switchmanager.hpp" +#include "object/feedbackmanager.hpp" +#include "../../decoder/decoder.hpp" +#include "../../decoder/decoderchangeflags.hpp" +#include "../../../utils/setthreadname.hpp" +#include "../../../utils/startswith.hpp" +#include "../../../utils/rtrim.hpp" +#include "../../../core/eventloop.hpp" +#include "../../../log/log.hpp" + +namespace ECoS { + +Kernel::Kernel(const Config& config) + : m_ioContext{1} + , m_decoderController{nullptr} + , m_inputController{nullptr} + , m_outputController{nullptr} + , m_config{config} +#ifndef NDEBUG + , m_started{false} +#endif +{ +} + +void Kernel::setConfig(const Config& config) +{ + m_ioContext.post( + [this, newConfig=config]() + { + m_config = newConfig; + }); +} + +void Kernel::setOnStarted(std::function callback) +{ + assert(!m_started); + m_onStarted = std::move(callback); +} + +void Kernel::setOnEmergencyStop(std::function callback) +{ + assert(!m_started); + m_onEmergencyStop = std::move(callback); +} + +void Kernel::setOnGo(std::function callback) +{ + assert(!m_started); + m_onGo = std::move(callback); +} + +void Kernel::setDecoderController(DecoderController* decoderController) +{ + assert(!m_started); + m_decoderController = decoderController; +} + +void Kernel::setInputController(InputController* inputController) +{ + assert(!m_started); + m_inputController = inputController; +} + +void Kernel::setOutputController(OutputController* outputController) +{ + assert(!m_started); + m_outputController = outputController; +} + +void Kernel::start() +{ + assert(m_ioHandler); + assert(!m_started); + assert(m_objects.empty()); + + m_thread = std::thread( + [this]() + { + setThreadName("ecos"); + auto work = std::make_shared(m_ioContext); + m_ioContext.run(); + }); + + m_ioContext.post( + [this]() + { + m_ioHandler->start(); + + m_objects.add(std::make_unique(*this)); + m_objects.add(std::make_unique(*this)); + m_objects.add(std::make_unique(*this)); + m_objects.add(std::make_unique(*this)); + + if(m_onStarted) + EventLoop::call( + [this]() + { + m_onStarted(); + }); + }); + +#ifndef NDEBUG + m_started = true; +#endif +} + +void Kernel::stop() +{ + m_ioContext.post( + [this]() + { + m_ioHandler->stop(); + }); + + m_ioContext.stop(); + + m_thread.join(); + + m_objects.clear(); + +#ifndef NDEBUG + m_started = false; +#endif +} + +void Kernel::receive(std::string_view message) +{ + if(m_config.debugLogRXTX) + { + std::string msg{rtrim(message, {'\r', '\n'})}; + std::replace_if(msg.begin(), msg.end(), [](char c){ return c == '\r' || c == '\n'; }, ';'); + EventLoop::call([this, msg](){ Log::log(m_logId, LogMessage::D2002_RX_X, msg); }); + } + + if(Reply reply; parseReply(message, reply)) + { + auto it = m_objects.find(reply.objectId); + if(it != m_objects.end()) + it->second->receiveReply(reply); + } + else if(Event event; parseEvent(message, event)) + { + auto it = m_objects.find(event.objectId); + if(it != m_objects.end()) + it->second->receiveEvent(event); + } + else + {}// EventLoop::call([this]() { Log::log(m_logId, LogMessage::E2018_ParseError); }); +} + +ECoS& Kernel::ecos() +{ + return static_cast(*m_objects[ObjectId::ecos]); +} + +void Kernel::ecosGoChanged(TriState value) +{ + if(value == TriState::False && m_onEmergencyStop) + EventLoop::call([this]() { m_onEmergencyStop(); }); + else if(value == TriState::True && m_onGo) + EventLoop::call([this]() { m_onGo(); }); +} + +void Kernel::emergencyStop() +{ + m_ioContext.post([this]() { ecos().stop(); }); +} + +void Kernel::go() +{ + m_ioContext.post([this]() { ecos().go(); }); +} + +void Kernel::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber) +{ + (void)(decoder); + (void)(changes); + (void)(functionNumber); +} + +bool Kernel::setOutput(uint16_t address, bool value) +{ + (void)(address); + (void)(value); + + return false; +} + +void Kernel::setIOHandler(std::unique_ptr handler) +{ + assert(handler); + assert(!m_ioHandler); + m_ioHandler = std::move(handler); +} + +void Kernel::send(std::string_view message) +{ + if(m_ioHandler->send(message)) + { + if(m_config.debugLogRXTX) + EventLoop::call( + [this, msg=std::string(rtrim(message, '\n'))]() + { + Log::log(m_logId, LogMessage::D2001_TX_X, msg); + }); + } + else + {} // log message and go to error state +} + +} diff --git a/server/src/hardware/protocol/ecos/kernel.hpp b/server/src/hardware/protocol/ecos/kernel.hpp new file mode 100644 index 00000000..14f6ece7 --- /dev/null +++ b/server/src/hardware/protocol/ecos/kernel.hpp @@ -0,0 +1,248 @@ +/** + * server/src/hardware/protocol/ecos/kernel.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_ECOS_KERNEL_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_ECOS_KERNEL_HPP + +#include +#include +#include +#include "config.hpp" +#include "iohandler/iohandler.hpp" +#include "object/object.hpp" + +class Decoder; +enum class DecoderChangeFlags; +class DecoderController; +class InputController; +class OutputController; + +namespace ECoS { + +class ECoS; + +class Kernel +{ + friend class Object; + friend class ECoS; + + private: + class Objects : public std::unordered_map> + { + public: + template + inline void add(std::unique_ptr object) + { + const auto id = object->id(); + emplace(id, std::move(object)); + } + }; + + boost::asio::io_context m_ioContext; + std::unique_ptr m_ioHandler; + std::thread m_thread; + std::string m_logId; + std::function m_onStarted; + + Objects m_objects; + + std::function m_onGo; + std::function m_onEmergencyStop; + + DecoderController* m_decoderController; + InputController* m_inputController; + OutputController* m_outputController; + + Config m_config; +#ifndef NDEBUG + bool m_started; +#endif + + Kernel(const Config& config); + + void setIOHandler(std::unique_ptr handler); + + ECoS& ecos(); + void ecosGoChanged(TriState value); + + public:// REMOVE!! just for testing + void postSend(const std::string& message) + { + m_ioContext.post( + [this, message]() + { + send(message); + }); + } + + void send(std::string_view message); + + public: + Kernel(const Kernel&) = delete; + Kernel& operator =(const Kernel&) = delete; + + /** + * @brief IO context for ECoS kernel and IO handler + * @return The IO context + */ + boost::asio::io_context& ioContext() { return m_ioContext; } + + /** + * @brief Create kernel and IO handler + * @param[in] config LocoNet configuration + * @param[in] args IO handler arguments + * @return The kernel instance + */ + template + static std::unique_ptr create(const Config& config, Args... args) + { + static_assert(std::is_base_of_v); + std::unique_ptr kernel{new Kernel(config)}; + kernel->setIOHandler(std::make_unique(*kernel, std::forward(args)...)); + return kernel; + } + + /** + * @brief Access the IO handler + * @return The IO handler + * @note The IO handler runs in the kernel's IO context, not all functions can be called safely! + */ + template + T& ioHandler() + { + assert(dynamic_cast(m_ioHandler.get())); + return static_cast(*m_ioHandler); + } + + /** + * @brief Get object id used for log messages + * @return The object id + */ + inline const std::string& logId() + { + return m_logId; + } + + /** + * @brief Set object id used for log messages + * @param[in] value The object id + */ + void setLogId(std::string value) { m_logId = std::move(value); } + + /** + * @brief Set ECoS configuration + * @param[in] config The LocoNet configuration + */ + void setConfig(const Config& config); + + /** + * @brief ... + * @param[in] callback ... + * @note This function may not be called when the kernel is running. + */ + void setOnStarted(std::function callback); + + /** + * @brief ... + * @param[in] callback ... + * @note This function may not be called when the kernel is running. + */ + void setOnEmergencyStop(std::function callback); + + /** + * @brief ... + * @param[in] callback ... + * @note This function may not be called when the kernel is running. + */ + void setOnGo(std::function callback); + + /** + * @brief Set the decoder controller + * @param[in] decoderController The decoder controller + * @note This function may not be called when the kernel is running. + */ + void setDecoderController(DecoderController* decoderController); + + /** + * @brief Set the input controller + * @param[in] inputController The input controller + * @note This function may not be called when the kernel is running. + */ + void setInputController(InputController* inputController); + + /** + * @brief Set the output controller + * @param[in] outputController The output controller + * @note This function may not be called when the kernel is running. + */ + void setOutputController(OutputController* outputController); + + /** + * @brief Start the kernel and IO handler + */ + void start(); + + /** + * @brief Stop the kernel and IO handler + */ + void stop(); + + /** + * @brief ... + * + * This must be called by the IO handler whenever a ECoS message is received. + * + * @param[in] message The received ECoS message + * @note This function must run in the kernel's IO context + */ + void receive(std::string_view message); + + /** + * @brief ... + */ + void emergencyStop(); + + /** + * @brief ... + */ + void go(); + + /** + * @brief ... + * @param[in] decoder ... + * @param[in] changes ... + * @param[in] functionNumber ... + */ + void decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber); + + /** + * @brief ... + * @param[in] address Output address + * @param[in] value Output value: \c true is on, \c false is off. + * @return \c true if send successful, \c false otherwise. + */ + bool setOutput(uint16_t address, bool value); +}; + +} + +#endif diff --git a/server/src/hardware/protocol/ecos/messages.cpp b/server/src/hardware/protocol/ecos/messages.cpp new file mode 100644 index 00000000..f4a264e3 --- /dev/null +++ b/server/src/hardware/protocol/ecos/messages.cpp @@ -0,0 +1,172 @@ +/** + * server/src/hardware/protocol/ecos/messages.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 "messages.hpp" +#include +#include +#include "../../../utils/startswith.hpp" +#include "../../../utils/fromchars.hpp" + +namespace ECoS { + +static const std::string_view endDelimiter = " n) + reply.options.emplace_back(&message[n], pos - n); + if(message[pos] == ')') + break; + n = pos + 1; + } + if(pos == std::string_view::npos || pos + 1 >= message.size() || message[pos + 1] != '>') + return false; + + // advance to next line + n = pos + 2; + while((message[n] == '\n' || message[n] == '\r') && n < message.size()) + n++; + if(n >= message.size()) + return false; + + // find end + size_t end; + if((end = message.find(endDelimiter, n)) == std::string_view::npos) + return false; + + // read lines + if(end > n) + { + while((pos = message.find('\n', n)) < end) + { + reply.lines.emplace_back(&message[n], pos - n); + n = pos + 1; + } + } + + // read status code + std::underlying_type_t status; + r = std::from_chars(&message[end + endDelimiter.size()], &message[message.size() - 1], status); + if(r.ec != std::errc()) + return false; + reply.status = static_cast(status); + n = r.ptr - message.data(); + + // read status message + while(message[n] != '(' && message[n] != '\n' && message[n] != '\r' && n < message.size()) + n++; + if(n >= message.size() || message[n] != '(') + return false; + + pos = ++n; + while(message[pos] != ')' && message[pos] != '\n' && message[pos] != '\r' && n < message.size()) + pos++; + if(pos >= message.size() || message[pos] != ')') + return false; + + reply.statusMessage = message.substr(n, pos - n); + + return true; +} + +bool parseEvent(std::string_view message, Event& event) +{ + static const std::string_view startDelimiter = "= text.size()) + return false; + const bool quoted = (text[n] == '"'); + pos = quoted ? text.find("\"]", ++n) : text.find(']', n); + if(pos == std::string_view::npos) + return false; + line.values.emplace(key, std::string_view{text.data() + n, pos - n}); + n = pos + (quoted ? 2 : 1); + } + else + n = pos; + } + } + + return true; +} + +} diff --git a/server/src/hardware/protocol/ecos/messages.hpp b/server/src/hardware/protocol/ecos/messages.hpp new file mode 100644 index 00000000..d4b4b183 --- /dev/null +++ b/server/src/hardware/protocol/ecos/messages.hpp @@ -0,0 +1,156 @@ +/** + * server/src/hardware/protocol/ecos/messages.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_ECOS_MESSAGES_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_ECOS_MESSAGES_HPP + +#include +#include +#include + +namespace ECoS { + +struct Command +{ + static constexpr std::string_view queryObjects = "queryObjects"; + static constexpr std::string_view set = "set"; + static constexpr std::string_view get = "get"; + static constexpr std::string_view create = "create"; + static constexpr std::string_view delete_ = "delete"; + static constexpr std::string_view request = "request"; + static constexpr std::string_view release = "release"; +}; + +struct ObjectId +{ + static constexpr uint16_t ecos = 1; + static constexpr uint16_t programmingTrack = 5; + static constexpr uint16_t locomotiveManager = 10; + static constexpr uint16_t switchManager = 11; + static constexpr uint16_t shuttleTrainControl = 12; + static constexpr uint16_t deviceManager = 20; + static constexpr uint16_t sniffer = 25; + static constexpr uint16_t feedbackManager = 26; + static constexpr uint16_t booster = 27; + static constexpr uint16_t controlDesk = 31; + static constexpr uint16_t s88 = 100; + static constexpr uint16_t ecosDetector = 200; +}; + +struct Option +{ + static constexpr std::string_view addr = "addr"; + static constexpr std::string_view dir = "dir"; + static constexpr std::string_view duration = "duration"; + static constexpr std::string_view go = "go"; + static constexpr std::string_view info = "info"; + static constexpr std::string_view mode = "mode"; + static constexpr std::string_view name = "name"; + static constexpr std::string_view ports = "ports"; + static constexpr std::string_view protocol = "protocol"; + static constexpr std::string_view speedStep = "speedstep"; + static constexpr std::string_view state = "state"; + static constexpr std::string_view status = "status"; + static constexpr std::string_view stop = "stop"; + static constexpr std::string_view view = "view"; +}; + +enum class Status : uint32_t +{ + Ok = 0, +}; + +struct Reply +{ + std::string_view command; + uint16_t objectId; + std::vector options; + std::vector lines; + Status status; + std::string_view statusMessage; +}; + +struct Event +{ + uint16_t objectId; +}; + +struct Line +{ + uint16_t objectId; + std::unordered_map values; +}; + +inline std::string buildCommand(std::string_view command, uint16_t objectId, std::initializer_list options) +{ + std::string s(command); + s.append("(").append(std::to_string(objectId)); + for(auto option : options) + s.append(", ").append(option); + s.append(")\n"); + return s; +} + +inline std::string queryObjects(uint16_t objectId, std::initializer_list options = {}) +{ + return buildCommand(Command::queryObjects, objectId, options); +} + +inline std::string set(uint16_t objectId, std::initializer_list options) +{ + return buildCommand(Command::set, objectId, options); +} + +inline std::string get(uint16_t objectId, std::initializer_list options) +{ + return buildCommand(Command::get, objectId, options); +} + +inline std::string create(uint16_t objectId, std::initializer_list options) +{ + return buildCommand(Command::create, objectId, options); +} + +inline std::string delete_(uint16_t objectId, std::initializer_list options) +{ + return buildCommand(Command::delete_, objectId, options); +} + +inline std::string request(uint16_t objectId, std::initializer_list options) +{ + return buildCommand(Command::request, objectId, options); +} + +inline std::string release(uint16_t objectId, std::initializer_list options) +{ + return buildCommand(Command::release, objectId, options); +} + +bool parseReply(std::string_view message, Reply& reply); +bool parseEvent(std::string_view message, Event& event); + +bool parseId(std::string_view line, uint16_t& id); +bool parseLine(std::string_view text, Line& line); + +} + +#endif diff --git a/server/src/hardware/protocol/ecos/object/ecos.cpp b/server/src/hardware/protocol/ecos/object/ecos.cpp new file mode 100644 index 00000000..ac7c2fc0 --- /dev/null +++ b/server/src/hardware/protocol/ecos/object/ecos.cpp @@ -0,0 +1,96 @@ +/** + * server/src/hardware/protocol/ecos/object/ecos.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 "ecos.hpp" +#include +#include "../kernel.hpp" +#include "../messages.hpp" + +namespace ECoS { + +ECoS::ECoS(Kernel& kernel) + : Object(kernel, ObjectId::ecos) +{ + requestView(); + send(get(m_id, {Option::info})); +} + +bool ECoS::receiveReply(const Reply& reply) +{ + assert(reply.objectId == m_id); + + if(reply.command == Command::set) + { + if(reply.options.size() == 1) + { + if(reply.options[0] == Option::stop) + { + if(m_go != TriState::False) + { + m_go = TriState::False; + m_kernel.ecosGoChanged(m_go); + } + } + else if(reply.options[0] == Option::go) + { + if(m_go != TriState::True) + { + m_go = TriState::True; + m_kernel.ecosGoChanged(m_go); + } + } + } + } + else if(reply.command == Command::get) + { + if(reply.options.size() == 1) + { + if(reply.options[0] == Option::info) + { + //! @todo + } + } + } + + return Object::receiveReply(reply); +} + +bool ECoS::receiveEvent(const Event& event) +{ + assert(event.objectId == m_id); + + return Object::receiveEvent(event); +} + +void ECoS::go() +{ + if(m_go != TriState::True) + send(set(m_id, {Option::go})); +} + +void ECoS::stop() +{ + if(m_go != TriState::False) + send(set(m_id, {Option::stop})); +} + +} diff --git a/server/src/hardware/protocol/loconet/loconetlist.hpp b/server/src/hardware/protocol/ecos/object/ecos.hpp similarity index 59% rename from server/src/hardware/protocol/loconet/loconetlist.hpp rename to server/src/hardware/protocol/ecos/object/ecos.hpp index e4fff0d4..90815576 100644 --- a/server/src/hardware/protocol/loconet/loconetlist.hpp +++ b/server/src/hardware/protocol/ecos/object/ecos.hpp @@ -1,9 +1,9 @@ /** - * server/src/hardware/protocol/loconet/loconetlist.hpp + * server/src/hardware/protocol/ecos/object/ecos.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,31 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_LOCONET_LOCONETLIST_HPP -#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_LOCONET_LOCONETLIST_HPP +#ifndef TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_ECOS_OBJECT_ECOS_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_ECOS_OBJECT_ECOS_HPP -#include "../../../core/objectlist.hpp" -#include "loconet.hpp" +#include "object.hpp" +#include -class LocoNetList : public ObjectList +namespace ECoS { + +class Kernel; + +class ECoS final : public Object { - protected: - bool isListedProperty(const std::string& name) final; + private: + TriState m_go = TriState::Undefined; public: - CLASS_ID("loconet_list") + ECoS(Kernel& kernel); - LocoNetList(Object& _parent, const std::string& parentPropertyName); + bool receiveReply(const Reply& reply) final; + bool receiveEvent(const Event& event) final; - TableModelPtr getModel() final; + void go(); + void stop(); }; +} + #endif diff --git a/server/src/hardware/protocol/ecos/object/feedback.cpp b/server/src/hardware/protocol/ecos/object/feedback.cpp new file mode 100644 index 00000000..ed5a7a43 --- /dev/null +++ b/server/src/hardware/protocol/ecos/object/feedback.cpp @@ -0,0 +1,60 @@ +/** + * server/src/hardware/protocol/ecos/object/feedback.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 "feedback.hpp" +#include +#include "../messages.hpp" + +namespace ECoS { + +const std::initializer_list Feedback::options = {Option::ports, Option::state}; + +Feedback::Feedback(Kernel& kernel, uint16_t id) + : Object(kernel, id) +{ +} + +Feedback::Feedback(Kernel& kernel, const Line& data) + : Feedback(kernel, data.objectId) +{ + const auto values = data.values; + if(auto ports = values.find(Option::ports); ports != values.end()) + {} + if(auto state = values.find(Option::state); state != values.end()) + {} +} + +bool Feedback::receiveReply(const Reply& reply) +{ + assert(reply.objectId == m_id); + + return Object::receiveReply(reply); +} + +bool Feedback::receiveEvent(const Event& event) +{ + assert(event.objectId == m_id); + + return Object::receiveEvent(event); +} + +} diff --git a/server/src/hardware/protocol/ecos/object/feedback.hpp b/server/src/hardware/protocol/ecos/object/feedback.hpp new file mode 100644 index 00000000..4bdccbb0 --- /dev/null +++ b/server/src/hardware/protocol/ecos/object/feedback.hpp @@ -0,0 +1,48 @@ +/** + * server/src/hardware/protocol/ecos/object/feedback.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_ECOS_OBJECT_FEEDBACK_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_ECOS_OBJECT_FEEDBACK_HPP + +#include "object.hpp" +#include "../messages.hpp" + +namespace ECoS { + +class Kernel; +struct Line; + +class Feedback final : public Object +{ + public: + static const std::initializer_list options; + + Feedback(Kernel& kernel, uint16_t id); + Feedback(Kernel& kernel, const Line& data); + + bool receiveReply(const Reply& reply) final; + bool receiveEvent(const Event& event) final; +}; + +} + +#endif \ No newline at end of file diff --git a/server/src/hardware/protocol/ecos/object/feedbackmanager.cpp b/server/src/hardware/protocol/ecos/object/feedbackmanager.cpp new file mode 100644 index 00000000..5bfa450f --- /dev/null +++ b/server/src/hardware/protocol/ecos/object/feedbackmanager.cpp @@ -0,0 +1,64 @@ +/** + * server/src/hardware/protocol/ecos/object/feedbackmanager.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 "feedbackmanager.hpp" +#include +#include "feedback.hpp" +#include "../messages.hpp" + +namespace ECoS { + +FeedbackManager::FeedbackManager(Kernel& kernel) + : Object(kernel, ObjectId::feedbackManager) +{ + requestView(); + send(queryObjects(m_id, Feedback::options)); + send(request(ObjectId::s88, {Option::view})); + send(request(ObjectId::ecosDetector, {Option::view})); +} + +bool FeedbackManager::receiveReply(const Reply& reply) +{ + assert(reply.objectId == m_id); + + if(reply.command == Command::queryObjects) + { + for(std::string_view line : reply.lines) + { + Line data; + if(parseLine(line, data) && !objectExists(data.objectId)) + addObject(std::make_unique(m_kernel, data)); + } + return true; + } + + return Object::receiveReply(reply); +} + +bool FeedbackManager::receiveEvent(const Event& event) +{ + assert(event.objectId == m_id); + + return Object::receiveEvent(event); +} + +} diff --git a/server/src/hardware/protocol/ecos/object/feedbackmanager.hpp b/server/src/hardware/protocol/ecos/object/feedbackmanager.hpp new file mode 100644 index 00000000..2277d8cf --- /dev/null +++ b/server/src/hardware/protocol/ecos/object/feedbackmanager.hpp @@ -0,0 +1,43 @@ +/** + * server/src/hardware/protocol/ecos/object/feedbackmanager.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_ECOS_OBJECT_FEEDBACKMANAGER_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_ECOS_OBJECT_FEEDBACKMANAGER_HPP + +#include "object.hpp" + +namespace ECoS { + +class Kernel; + +class FeedbackManager final : public Object +{ + public: + FeedbackManager(Kernel& kernel); + + bool receiveReply(const Reply& reply) final; + bool receiveEvent(const Event& event) final; +}; + +} + +#endif \ No newline at end of file diff --git a/server/src/hardware/protocol/ecos/object/locomotive.cpp b/server/src/hardware/protocol/ecos/object/locomotive.cpp new file mode 100644 index 00000000..5e4d8b1e --- /dev/null +++ b/server/src/hardware/protocol/ecos/object/locomotive.cpp @@ -0,0 +1,89 @@ +/** + * server/src/hardware/protocol/ecos/object/locomotive.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 "locomotive.hpp" +#include +#include "../messages.hpp" +#include "../../../../utils/fromchars.hpp" + +namespace ECoS { + +const std::initializer_list Locomotive::options = {Option::addr, Option::protocol, Option::dir, Option::speedStep}; + +static bool fromString(std::string_view text, Locomotive::Protocol& protocol) +{ + if(text == "MM14") + protocol = Locomotive::Protocol::MM14; + else if(text == "MM27") + protocol = Locomotive::Protocol::MM27; + else if(text == "MM28") + protocol = Locomotive::Protocol::MM28; + else if(text == "DCC14") + protocol = Locomotive::Protocol::DCC14; + else if(text == "DCC28") + protocol = Locomotive::Protocol::DCC28; + else if(text == "DCC128") + protocol = Locomotive::Protocol::DCC128; + else if(text == "SX32") + protocol = Locomotive::Protocol::SX32; + else if(text == "MMFKT") + protocol = Locomotive::Protocol::MMFKT; + else + return false; + return true; +} + +Locomotive::Locomotive(Kernel& kernel, uint16_t id) + : Object(kernel, id) +{ + requestView(); +} + +Locomotive::Locomotive(Kernel& kernel, const Line& data) + : Locomotive(kernel, data.objectId) +{ + const auto values = data.values; + if(auto addr = values.find(Option::addr); addr != values.end()) + fromChars(addr->second, m_address); + if(auto protocol = values.find(Option::protocol); protocol != values.end()) + fromString(protocol->second, m_protocol); + if(auto dir = values.find(Option::dir); dir != values.end()) + {} + if(auto speedStep = values.find(Option::speedStep); speedStep != values.end()) + {} +} + +bool Locomotive::receiveReply(const Reply& reply) +{ + assert(reply.objectId == m_id); + + return Object::receiveReply(reply); +} + +bool Locomotive::receiveEvent(const Event& event) +{ + assert(event.objectId == m_id); + + return Object::receiveEvent(event); +} + +} diff --git a/server/src/hardware/protocol/ecos/object/locomotive.hpp b/server/src/hardware/protocol/ecos/object/locomotive.hpp new file mode 100644 index 00000000..ac30be73 --- /dev/null +++ b/server/src/hardware/protocol/ecos/object/locomotive.hpp @@ -0,0 +1,69 @@ +/** + * server/src/hardware/protocol/ecos/object/locomotive.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_ECOS_OBJECT_LOCOMOTIVE_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_ECOS_OBJECT_LOCOMOTIVE_HPP + +#include "object.hpp" +#include "../messages.hpp" + +namespace ECoS { + +class Kernel; +struct Line; + +class Locomotive final : public Object +{ + public: + enum class Protocol + { + Unknown = 0, + MM14 = 1, + MM27 = 2, + MM28 = 3, + DCC14 = 4, + DCC28 = 5, + DCC128 = 6, + SX32 = 7, + MMFKT = 8, + }; + + private: + uint16_t m_address = 0; + Protocol m_protocol = Protocol::Unknown; + + public: + static const std::initializer_list options; + + Locomotive(Kernel& kernel, uint16_t id); + Locomotive(Kernel& kernel, const Line& data); + + bool receiveReply(const Reply& reply) final; + bool receiveEvent(const Event& event) final; + + uint16_t address() const { return m_address; } + Protocol protocol() const { return m_protocol; } +}; + +} + +#endif \ No newline at end of file diff --git a/server/src/hardware/protocol/ecos/object/locomotivemanager.cpp b/server/src/hardware/protocol/ecos/object/locomotivemanager.cpp new file mode 100644 index 00000000..4aa5c08b --- /dev/null +++ b/server/src/hardware/protocol/ecos/object/locomotivemanager.cpp @@ -0,0 +1,62 @@ +/** + * server/src/hardware/protocol/ecos/object/locomotivemanager.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 "locomotivemanager.hpp" +#include +#include "locomotive.hpp" +#include "../messages.hpp" + +namespace ECoS { + +LocomotiveManager::LocomotiveManager(Kernel& kernel) + : Object(kernel, ObjectId::locomotiveManager) +{ + requestView(); + send(queryObjects(m_id, Locomotive::options)); +} + +bool LocomotiveManager::receiveReply(const Reply& reply) +{ + assert(reply.objectId == m_id); + + if(reply.command == Command::queryObjects) + { + for(std::string_view line : reply.lines) + { + Line data; + if(parseLine(line, data) && !objectExists(data.objectId)) + addObject(std::make_unique(m_kernel, data)); + } + return true; + } + + return Object::receiveReply(reply); +} + +bool LocomotiveManager::receiveEvent(const Event& event) +{ + assert(event.objectId == m_id); + + return Object::receiveEvent(event); +} + +} diff --git a/server/src/hardware/protocol/ecos/object/locomotivemanager.hpp b/server/src/hardware/protocol/ecos/object/locomotivemanager.hpp new file mode 100644 index 00000000..d96837e0 --- /dev/null +++ b/server/src/hardware/protocol/ecos/object/locomotivemanager.hpp @@ -0,0 +1,43 @@ +/** + * server/src/hardware/protocol/ecos/object/locomotivemanager.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_ECOS_OBJECT_LOCOMOTIVEMANAGER_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_ECOS_OBJECT_LOCOMOTIVEMANAGER_HPP + +#include "object.hpp" + +namespace ECoS { + +class Kernel; + +class LocomotiveManager final : public Object +{ + public: + LocomotiveManager(Kernel& kernel); + + bool receiveReply(const Reply& reply) final; + bool receiveEvent(const Event& event) final; +}; + +} + +#endif \ No newline at end of file diff --git a/server/src/hardware/protocol/ecos/object/object.cpp b/server/src/hardware/protocol/ecos/object/object.cpp new file mode 100644 index 00000000..7296c0bc --- /dev/null +++ b/server/src/hardware/protocol/ecos/object/object.cpp @@ -0,0 +1,75 @@ +/** + * server/src/hardware/protocol/ecos/object/object.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 "object.hpp" +#include +#include "../messages.hpp" +#include "../kernel.hpp" + +namespace ECoS { + +Object::Object(Kernel& kernel, uint16_t id) + : m_kernel{kernel} + , m_id{id} +{ +} + +bool Object::receiveReply(const Reply& reply) +{ + assert(reply.objectId == m_id); + + (void)(reply); + + return false; +} + +bool Object::receiveEvent(const Event& event) +{ + assert(event.objectId == m_id); + + (void)(event); + + return false; +} + +void Object::requestView() +{ + if(!m_isViewActive) + send(request(m_id, {Option::view})); +} + +void Object::send(std::string_view message) +{ + m_kernel.send(message); +} + +bool Object::objectExists(uint16_t objectId) const +{ + return m_kernel.m_objects.find(objectId) != m_kernel.m_objects.end(); +} + +void Object::addObject(std::unique_ptr object) +{ + m_kernel.m_objects.add(std::move(object)); +} + +} diff --git a/server/src/hardware/protocol/ecos/object/object.hpp b/server/src/hardware/protocol/ecos/object/object.hpp new file mode 100644 index 00000000..7b25529e --- /dev/null +++ b/server/src/hardware/protocol/ecos/object/object.hpp @@ -0,0 +1,66 @@ +/** + * server/src/hardware/protocol/ecos/object/object.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_ECOS_OBJECT_OBJECT_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_ECOS_OBJECT_OBJECT_HPP + +#include +#include +#include + +namespace ECoS { + +class Kernel; +struct Reply; +struct Event; + +class Object +{ + protected: + Kernel& m_kernel; + const uint16_t m_id; + bool m_isViewActive = false; + + void send(std::string_view message); + + bool objectExists(uint16_t objectId) const; + void addObject(std::unique_ptr object); + + public: + Object(const Object&) = delete; + Object& operator =(const Object&) = delete; + + Object(Kernel& kernel, uint16_t id); + virtual ~Object() = default; + + virtual bool receiveReply(const Reply& reply); + virtual bool receiveEvent(const Event& event); + + inline uint16_t id() const { return m_id; } + inline bool isViewActive() const { return m_isViewActive; } + + void requestView(); +}; + +} + +#endif diff --git a/server/src/hardware/protocol/ecos/object/switch.cpp b/server/src/hardware/protocol/ecos/object/switch.cpp new file mode 100644 index 00000000..5a44905f --- /dev/null +++ b/server/src/hardware/protocol/ecos/object/switch.cpp @@ -0,0 +1,94 @@ +/** + * server/src/hardware/protocol/ecos/object/switch.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 "switch.hpp" +#include +#include "../messages.hpp" +#include "../../../../utils/fromchars.hpp" + +namespace ECoS { + +const std::initializer_list Switch::options = {Option::addr, Option::protocol, Option::state, Option::mode, Option::duration}; + +static bool fromString(std::string_view text, Switch::Protocol& protocol) +{ + if(text == "MM") + protocol = Switch::Protocol::MM; + else if(text == "DCC") + protocol = Switch::Protocol::DCC; + else + return false; + return true; +} + +static bool fromString(std::string_view text, Switch::Mode& mode) +{ + if(text == "SWITCH") + mode = Switch::Mode::Switch; + else if(text == "PULSE") + mode = Switch::Mode::Pulse; + else + return false; + return true; +} + +Switch::Switch(Kernel& kernel, uint16_t id) + : Object(kernel, id) +{ + requestView(); +} + +Switch::Switch(Kernel& kernel, const Line& data) + : Switch(kernel, data.objectId) +{ + const auto values = data.values; + if(auto addr = values.find(Option::addr); addr != values.end()) + fromChars(addr->second, m_address); + if(auto protocol = values.find(Option::protocol); protocol != values.end()) + fromString(protocol->second, m_protocol); + if(auto state = values.find(Option::state); state != values.end()) + {} + if(auto mode = values.find(Option::mode); mode != values.end()) + fromString(mode->second, m_mode); + if(auto duration = values.find(Option::duration); duration != values.end()) + fromChars(duration->second, m_duration); +} + +bool Switch::receiveReply(const Reply& reply) +{ + assert(reply.objectId == m_id); + + (void)(reply); + + return Object::receiveReply(reply); +} + +bool Switch::receiveEvent(const Event& event) +{ + assert(event.objectId == m_id); + + (void)(event); + + return Object::receiveEvent(event); +} + +} diff --git a/server/src/hardware/protocol/ecos/object/switch.hpp b/server/src/hardware/protocol/ecos/object/switch.hpp new file mode 100644 index 00000000..f6434ff8 --- /dev/null +++ b/server/src/hardware/protocol/ecos/object/switch.hpp @@ -0,0 +1,74 @@ +/** + * server/src/hardware/protocol/ecos/object/switch.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_ECOS_OBJECT_SWITCH_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_ECOS_OBJECT_SWITCH_HPP + +#include "object.hpp" +#include "../messages.hpp" + +namespace ECoS { + +class Kernel; +struct Line; + +class Switch final : public Object +{ + public: + enum class Protocol + { + Unknown = 0, + DCC = 1, + MM = 2, + }; + + enum class Mode + { + Unknown = 0, + Switch = 1, + Pulse = 2, + }; + + private: + uint16_t m_address = 0; + Protocol m_protocol = Protocol::Unknown; + Mode m_mode = Mode::Unknown; + uint16_t m_duration = 0; + + public: + static const std::initializer_list options; + + Switch(Kernel& kernel, uint16_t id); + Switch(Kernel& kernel, const Line& data); + + bool receiveReply(const Reply& reply) final; + bool receiveEvent(const Event& event) final; + + uint16_t address() const { return m_address; } + Protocol protocol() const { return m_protocol; } + Mode mode() const { return m_mode; } + uint16_t duration() const { return m_duration; } +}; + +} + +#endif \ No newline at end of file diff --git a/server/src/hardware/protocol/ecos/object/switchmanager.cpp b/server/src/hardware/protocol/ecos/object/switchmanager.cpp new file mode 100644 index 00000000..4b55c342 --- /dev/null +++ b/server/src/hardware/protocol/ecos/object/switchmanager.cpp @@ -0,0 +1,63 @@ +/** + * server/src/hardware/protocol/ecos/object/switchmanager.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 "switchmanager.hpp" +#include +#include "switch.hpp" +#include "../messages.hpp" + +namespace ECoS { + +SwitchManager::SwitchManager(Kernel& kernel) + : Object(kernel, ObjectId::switchManager) +{ + requestView(); + send(queryObjects(m_id, Switch::options)); +} + +bool SwitchManager::receiveReply(const Reply& reply) +{ + assert(reply.objectId == m_id); + + if(reply.command == Command::queryObjects) + { + for(std::string_view line : reply.lines) + { + Line data; + if(parseLine(line, data) && !objectExists(data.objectId)) + addObject(std::make_unique(m_kernel, data)); + } + return true; + } + + return Object::receiveReply(reply); +} + +bool SwitchManager::receiveEvent(const Event& event) +{ + assert(event.objectId == m_id); + + + return Object::receiveEvent(event); +} + +} diff --git a/server/src/hardware/protocol/ecos/object/switchmanager.hpp b/server/src/hardware/protocol/ecos/object/switchmanager.hpp new file mode 100644 index 00000000..b231ef93 --- /dev/null +++ b/server/src/hardware/protocol/ecos/object/switchmanager.hpp @@ -0,0 +1,43 @@ +/** + * server/src/hardware/protocol/ecos/object/switchmanager.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_ECOS_OBJECT_SWITCHMANAGER_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_ECOS_OBJECT_SWITCHMANAGER_HPP + +#include "object.hpp" + +namespace ECoS { + +class Kernel; + +class SwitchManager final : public Object +{ + public: + SwitchManager(Kernel& kernel); + + bool receiveReply(const Reply& reply) final; + bool receiveEvent(const Event& event) final; +}; + +} + +#endif diff --git a/server/src/hardware/protocol/ecos/settings.cpp b/server/src/hardware/protocol/ecos/settings.cpp new file mode 100644 index 00000000..078eb73a --- /dev/null +++ b/server/src/hardware/protocol/ecos/settings.cpp @@ -0,0 +1,47 @@ +/** + * server/src/hardware/protocol/ecos/settings.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 "settings.hpp" +#include "../../../core/attributes.hpp" +#include "../../../utils/displayname.hpp" + +namespace ECoS { + +Settings::Settings(Object& _parent, const std::string& parentPropertyName) + : SubObject(_parent, parentPropertyName) + , debugLogRXTX{this, "debug_log_rx_tx", false, PropertyFlags::ReadWrite | PropertyFlags::Store} +{ + Attributes::addDisplayName(debugLogRXTX, DisplayName::Hardware::debugLogRXTX); + //Attributes::addGroup(debugLogRXTX, Group::debug); + m_interfaceItems.add(debugLogRXTX); +} + +Config Settings::config() const +{ + Config config; + + config.debugLogRXTX = debugLogRXTX; + + return config; +} + +} diff --git a/server/src/hardware/protocol/ecos/settings.hpp b/server/src/hardware/protocol/ecos/settings.hpp new file mode 100644 index 00000000..4b3c448a --- /dev/null +++ b/server/src/hardware/protocol/ecos/settings.hpp @@ -0,0 +1,46 @@ +/** + * server/src/hardware/protocol/ecos/settings.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_ECOS_SETTINGS_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_ECOS_SETTINGS_HPP + +#include "../../../core/subobject.hpp" +#include "../../../core/property.hpp" +#include "config.hpp" + +namespace ECoS { + +class Settings final : public SubObject +{ + public: + CLASS_ID("ecos_settings") + + Property debugLogRXTX; + + Settings(Object& _parent, const std::string& parentPropertyName); + + Config config() const; +}; + +} + +#endif diff --git a/server/src/hardware/protocol/loconet/config.hpp b/server/src/hardware/protocol/loconet/config.hpp new file mode 100644 index 00000000..c639d9a4 --- /dev/null +++ b/server/src/hardware/protocol/loconet/config.hpp @@ -0,0 +1,42 @@ +/** + * server/src/hardware/protocol/loconet/config.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_LOCONET_CONFIG_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_LOCONET_CONFIG_HPP + +#include "../../../enum/loconetcommandstation.hpp" + +namespace LocoNet { + +struct Config +{ + bool fastClockSyncEnabled; + uint8_t fastClockSyncInterval; //!< Fast clock sync interval in seconds + + bool debugLogInput; + bool debugLogOutput; + bool debugLogRXTX; +}; + +} + +#endif diff --git a/server/src/hardware/input/monitor/loconetinputmonitor.hpp b/server/src/hardware/protocol/loconet/iohandler/iohandler.hpp similarity index 56% rename from server/src/hardware/input/monitor/loconetinputmonitor.hpp rename to server/src/hardware/protocol/loconet/iohandler/iohandler.hpp index c8e6e0c3..569f3927 100644 --- a/server/src/hardware/input/monitor/loconetinputmonitor.hpp +++ b/server/src/hardware/protocol/loconet/iohandler/iohandler.hpp @@ -1,9 +1,9 @@ /** - * server/src/hardware/input/monitor/loconetinputmonitor.hpp + * server/src/hardware/protocol/loconet/iohandler/iohandler.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,36 @@ * 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 - -#include "inputmonitor.hpp" +#ifndef TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_LOCONET_IOHANDLER_IOHANDLER_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_LOCONET_IOHANDLER_IOHANDLER_HPP namespace LocoNet { - class LocoNet; -} -class LocoNetInputMonitor final : public InputMonitor +class Kernel; +struct Message; + +class IOHandler { protected: - std::shared_ptr m_loconet; + Kernel& m_kernel; + + IOHandler(Kernel& kernel) + : m_kernel{kernel} + { + } public: - CLASS_ID("input_monitor.loconet") + IOHandler(const IOHandler&) = delete; + IOHandler& operator =(const IOHandler&) = delete; - LocoNetInputMonitor(std::shared_ptr loconet); - ~LocoNetInputMonitor() final; + virtual ~IOHandler() = default; - std::string getObjectId() const final { return ""; } + virtual void start() = 0; + virtual void stop() = 0; - std::vector getInputInfo() const final; + virtual bool send(const Message& message) = 0; }; +} + #endif diff --git a/server/src/hardware/protocol/loconet/iohandler/lbserveriohandler.cpp b/server/src/hardware/protocol/loconet/iohandler/lbserveriohandler.cpp new file mode 100644 index 00000000..40b5ff2f --- /dev/null +++ b/server/src/hardware/protocol/loconet/iohandler/lbserveriohandler.cpp @@ -0,0 +1,181 @@ +/** + * server/src/hardware/protocol/loconet/iohandler/lbserveriohandler.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 "lbserveriohandler.hpp" +#include +#include "../kernel.hpp" +#include "../messages.hpp" +#include "../../../../utils/startswith.hpp" +#include "../../../../utils/tohex.hpp" + +static std::string formatSend(const LocoNet::Message& message) +{ + std::string send{"SEND"}; + send.reserve(5 + 3 * message.size()); + for(uint8_t i = 0; i < message.size(); i++) + send.append(" ").append(toHex(*(reinterpret_cast(&message) + i))); + send.append("\n"); + return send; +} + +static constexpr bool isHexDigit(char c) +{ + return + (c >= '0' && c <= '9') || + (c >= 'A' && c <= 'F') || + (c >= 'a' && c <= 'f'); +} + +static constexpr uint8_t hexDigitValue(char c) +{ + if(c >= '0' && c <= '9') + return static_cast(c - '0'); + else if(c >= 'A' && c <= 'F') + return static_cast(c - 'A' + 10); + else if(c >= 'a' && c <= 'f') + return static_cast(c - 'a' + 10); + else + return 0xFF; +} + +static std::vector readHexBytes(std::string_view text) +{ + std::vector bytes; + size_t i = 0; + while(i < text.size() - 1) + { + if(isHexDigit(text[i]) && isHexDigit(text[i + 1])) + { + bytes.emplace_back(static_cast((hexDigitValue(text[i]) * 16) | hexDigitValue(text[i + 1]))); + i += 2; + } + else + i++; + } + return bytes; +} + +namespace LocoNet { + +LBServerIOHandler::LBServerIOHandler(Kernel& kernel, const std::string& hostname, uint16_t port) + : TCPIOHandler(kernel, hostname, port) + , m_readBufferOffset{0} +{ +} + +LBServerIOHandler::~LBServerIOHandler() +{ +} + +void LBServerIOHandler::start() +{ + read(); +} + +void LBServerIOHandler::stop() +{ +} + +bool LBServerIOHandler::send(const Message& message) +{ + const bool wasEmpty = m_writeQueue.empty(); + m_writeQueue.emplace(formatSend(message)); + + if(wasEmpty) + write(); + + return true; +} + +void LBServerIOHandler::read() +{ + m_socket.async_read_some(boost::asio::buffer(m_readBuffer.data() + m_readBufferOffset, m_readBuffer.size() - m_readBufferOffset), + [this](const boost::system::error_code& ec, std::size_t bytesTransferred) + { + if(!ec) + { + const char* pos = m_readBuffer.data(); + bytesTransferred += m_readBufferOffset; + const char* end = pos + bytesTransferred; + + while(bytesTransferred > 0) + { + const char* eol = pos; + while(*eol != '\n' && *eol != '\r' && eol < end) + eol++; + + if(eol == end) + break; // no newline found, wait for more data + + std::string_view line{pos, static_cast(eol - pos)}; + + if(startsWith(line, "RECEIVE ")) + { + std::vector bytes = readHexBytes(line.substr(8)); + m_kernel.receive(*reinterpret_cast(bytes.data())); + } + else if(startsWith(line, "SENT OK")) + { + m_writeQueue.pop(); + if(!m_writeQueue.empty()) + write(); + } + else if(startsWith(line, "VERSION ")) + { + m_version = line.substr(7); + } + + pos += line.size(); + bytesTransferred -= line.size(); + } + + if(bytesTransferred != 0) + memmove(m_readBuffer.data(), pos, bytesTransferred); + m_readBufferOffset = bytesTransferred; + + read(); + } + else{} + //EventLoop::call( + // [this, ec]() + // { + //Log::log(*this, LogMessage::E2002_SERIAL_READ_FAILED_X, ec); + //online = false; + //}); + }); +} + +void LBServerIOHandler::write() +{ + assert(!m_writeQueue.empty()); + const std::string& message = m_writeQueue.front(); + boost::asio::async_write(m_socket, boost::asio::buffer(message.data(), message.size()), + [this](const boost::system::error_code& ec, std::size_t /*bytesTransferred*/) + { + if(ec != boost::asio::error::operation_aborted) + { + // LogMessage::E1006_SOCKET_WRITE_FAILED_X, ec + } + }); +} + +} diff --git a/server/src/hardware/protocol/loconet/iohandler/lbserveriohandler.hpp b/server/src/hardware/protocol/loconet/iohandler/lbserveriohandler.hpp new file mode 100644 index 00000000..00445b17 --- /dev/null +++ b/server/src/hardware/protocol/loconet/iohandler/lbserveriohandler.hpp @@ -0,0 +1,57 @@ +/** + * server/src/hardware/protocol/loconet/iohandler/lbserveriohandler.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_LOCONET_IOHANDLER_LBSERVERIOHANDLER_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_LOCONET_IOHANDLER_LBSERVERIOHANDLER_HPP + +#include "tcpiohandler.hpp" +#include +#include + +namespace LocoNet { + +class LBServerIOHandler : public TCPIOHandler +{ + private: + std::array m_readBuffer; + size_t m_readBufferOffset; + std::queue m_writeQueue; + std::string m_version; + + void read(); + void write(); + + public: + LBServerIOHandler(Kernel& kernel, const std::string& hostname, uint16_t port); + ~LBServerIOHandler() final; + + void start() final; + void stop() final; + + bool send(const Message& message) final; + + const std::string& version() const { return m_version; } +}; + +} + +#endif \ No newline at end of file diff --git a/server/src/hardware/protocol/loconet/iohandler/serialiohandler.cpp b/server/src/hardware/protocol/loconet/iohandler/serialiohandler.cpp new file mode 100644 index 00000000..80695e7c --- /dev/null +++ b/server/src/hardware/protocol/loconet/iohandler/serialiohandler.cpp @@ -0,0 +1,150 @@ +/** + * server/src/hardware/protocol/loconet/iohandler/serialiohandler.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 "serialiohandler.hpp" +#include "../kernel.hpp" +#include "../messages.hpp" +#include "../../../../utils/serialport.hpp" + +namespace LocoNet { + +SerialIOHandler::SerialIOHandler(Kernel& kernel, const std::string& device, uint32_t baudrate, SerialFlowControl flowControl) + : IOHandler(kernel) + , m_serialPort{m_kernel.ioContext()} + , m_readBufferOffset{0} + , m_writeBufferOffset{0} +{ + SerialPort::open(m_serialPort, device, baudrate, 8, SerialParity::None, SerialStopBits::One, flowControl); +} + +SerialIOHandler::~SerialIOHandler() +{ + if(m_serialPort.is_open()) + m_serialPort.close(); +} + +void SerialIOHandler::start() +{ + read(); +} + +void SerialIOHandler::stop() +{ + m_serialPort.close(); +} + +bool SerialIOHandler::send(const Message& message) +{ + if(m_writeBufferOffset + message.size() > m_writeBuffer.size()) + return false; + + const bool wasEmpty = m_writeBufferOffset == 0; + memcpy(m_writeBuffer.data() + m_writeBufferOffset, &message, message.size()); + m_writeBufferOffset += message.size(); + + if(wasEmpty) + write(); + + return true; +} + +void SerialIOHandler::read() +{ + m_serialPort.async_read_some(boost::asio::buffer(m_readBuffer.data() + m_readBufferOffset, m_readBuffer.size() - m_readBufferOffset), + [this](const boost::system::error_code& ec, std::size_t bytesTransferred) + { + if(!ec) + { + const std::byte* pos = m_readBuffer.data(); + bytesTransferred += m_readBufferOffset; + + while(bytesTransferred > 1) + { + const Message* message = reinterpret_cast(pos); + + size_t drop = 0; + while((message->size() == 0 || (message->size() <= bytesTransferred && !isValid(*message))) && bytesTransferred > 0) + { + drop++; + pos++; + bytesTransferred--; + message = reinterpret_cast(pos); + } + + if(drop != 0) + { + //EventLoop::call( + // [this, drop]() + // { + //Log::log(*this, LogMessage::W2001_RECEIVED_MALFORMED_DATA_DROPPED_X_BYTES, drop); + // }); + } + else if(message->size() <= bytesTransferred) + { + m_kernel.receive(*message); + pos += message->size(); + bytesTransferred -= message->size(); + } + else + break; + } + + if(bytesTransferred != 0) + memmove(m_readBuffer.data(), pos, bytesTransferred); + m_readBufferOffset = bytesTransferred; + + read(); + } + else{} + //EventLoop::call( + // [this, ec]() + // { + //Log::log(*this, LogMessage::E2002_SERIAL_READ_FAILED_X, ec); + //online = false; + //}); + }); +} + +void SerialIOHandler::write() +{ + m_serialPort.async_write_some(boost::asio::buffer(m_writeBuffer.data(), m_writeBufferOffset), + [this](const boost::system::error_code& ec, std::size_t bytesTransferred) + { + if(!ec) + { + if(bytesTransferred < m_writeBufferOffset) + { + m_writeBufferOffset -= bytesTransferred; + memmove(m_writeBuffer.data(), m_writeBuffer.data() + bytesTransferred, m_writeBufferOffset); + write(); + } + else + m_writeBufferOffset = 0; + } + else if(ec != boost::asio::error::operation_aborted) + { + // LogMessage::E1006_SOCKET_WRITE_FAILED_X, ec + } + }); +} + +} diff --git a/server/src/hardware/protocol/loconet/iohandler/serialiohandler.hpp b/server/src/hardware/protocol/loconet/iohandler/serialiohandler.hpp new file mode 100644 index 00000000..ceca284c --- /dev/null +++ b/server/src/hardware/protocol/loconet/iohandler/serialiohandler.hpp @@ -0,0 +1,57 @@ +/** + * server/src/hardware/protocol/loconet/iohandler/serialiohandler.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_LOCONET_IOHANDLER_SERIALIOHANDLER_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_LOCONET_IOHANDLER_SERIALIOHANDLER_HPP + +#include "iohandler.hpp" +#include +#include "../../../../enum/serialflowcontrol.hpp" + +namespace LocoNet { + +class SerialIOHandler final : public IOHandler +{ + private: + boost::asio::serial_port m_serialPort; + std::array m_readBuffer; + size_t m_readBufferOffset; + std::array m_writeBuffer; + size_t m_writeBufferOffset; + + void read(); + void write(); + + public: + SerialIOHandler(Kernel& kernel, const std::string& device, uint32_t baudrate, SerialFlowControl flowControl); + ~SerialIOHandler() final; + + void start() final; + void stop() final; + + bool send(const Message& message) final; +}; + +} + +#endif + diff --git a/server/src/hardware/protocol/loconet/iohandler/tcpbinaryiohandler.cpp b/server/src/hardware/protocol/loconet/iohandler/tcpbinaryiohandler.cpp new file mode 100644 index 00000000..2abd4719 --- /dev/null +++ b/server/src/hardware/protocol/loconet/iohandler/tcpbinaryiohandler.cpp @@ -0,0 +1,145 @@ +/** + * server/src/hardware/protocol/loconet/iohandler/tcpbinaryiohandler.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 "tcpbinaryiohandler.hpp" +#include +#include "../kernel.hpp" +#include "../messages.hpp" + +namespace LocoNet { + +TCPBinaryIOHandler::TCPBinaryIOHandler(Kernel& kernel, const std::string& hostname, uint16_t port) + : TCPIOHandler(kernel, hostname, port) + , m_readBufferOffset{0} + , m_writeBufferOffset{0} +{ +} + +TCPBinaryIOHandler::~TCPBinaryIOHandler() +{ +} + +void TCPBinaryIOHandler::start() +{ + read(); +} + +void TCPBinaryIOHandler::stop() +{ +} + +bool TCPBinaryIOHandler::send(const Message& message) +{ + if(m_writeBufferOffset + message.size() > m_writeBuffer.size()) + return false; + + const bool wasEmpty = m_writeBufferOffset == 0; + memcpy(m_writeBuffer.data() + m_writeBufferOffset, &message, message.size()); + m_writeBufferOffset += message.size(); + + if(wasEmpty) + write(); + + return true; +} + +void TCPBinaryIOHandler::read() +{ + m_socket.async_read_some(boost::asio::buffer(m_readBuffer.data() + m_readBufferOffset, m_readBuffer.size() - m_readBufferOffset), + [this](const boost::system::error_code& ec, std::size_t bytesTransferred) + { + if(!ec) + { + const std::byte* pos = m_readBuffer.data(); + bytesTransferred += m_readBufferOffset; + + while(bytesTransferred > 1) + { + const Message* message = reinterpret_cast(pos); + + size_t drop = 0; + while((message->size() == 0 || (message->size() <= bytesTransferred && !isValid(*message))) && bytesTransferred > 0) + { + drop++; + pos++; + bytesTransferred--; + message = reinterpret_cast(pos); + } + + if(drop != 0) + { + //EventLoop::call( + // [this, drop]() + // { + //Log::log(*this, LogMessage::W2001_RECEIVED_MALFORMED_DATA_DROPPED_X_BYTES, drop); + // }); + } + else if(message->size() <= bytesTransferred) + { + m_kernel.receive(*message); + pos += message->size(); + bytesTransferred -= message->size(); + } + else + break; + } + + if(bytesTransferred != 0) + memmove(m_readBuffer.data(), pos, bytesTransferred); + m_readBufferOffset = bytesTransferred; + + read(); + } + else{} + //EventLoop::call( + // [this, ec]() + // { + //Log::log(*this, LogMessage::E2002_SERIAL_READ_FAILED_X, ec); + //online = false; + //}); + }); +} + +void TCPBinaryIOHandler::write() +{ + m_socket.async_write_some(boost::asio::buffer(m_writeBuffer.data(), m_writeBufferOffset), + [this](const boost::system::error_code& ec, std::size_t bytesTransferred) + { + if(!ec) + { + if(bytesTransferred < m_writeBufferOffset) + { + m_writeBufferOffset -= bytesTransferred; + memmove(m_writeBuffer.data(), m_writeBuffer.data() + bytesTransferred, m_writeBufferOffset); + write(); + } + else + m_writeBufferOffset = 0; + } + else if(ec != boost::asio::error::operation_aborted) + { + // LogMessage::E1006_SOCKET_WRITE_FAILED_X, ec + } + }); +} + +} diff --git a/server/src/hardware/protocol/loconet/iohandler/tcpbinaryiohandler.hpp b/server/src/hardware/protocol/loconet/iohandler/tcpbinaryiohandler.hpp new file mode 100644 index 00000000..61957a31 --- /dev/null +++ b/server/src/hardware/protocol/loconet/iohandler/tcpbinaryiohandler.hpp @@ -0,0 +1,55 @@ +/** + * server/src/hardware/protocol/loconet/iohandler/tcpbinaryiohandler.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_LOCONET_IOHANDLER_TCPBINARYIOHANDLER_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_LOCONET_IOHANDLER_TCPBINARYIOHANDLER_HPP + +#include "tcpiohandler.hpp" + +namespace LocoNet { + +class TCPBinaryIOHandler : public TCPIOHandler +{ + private: + std::array m_readBuffer; + size_t m_readBufferOffset; + std::array m_writeBuffer; + size_t m_writeBufferOffset; + + void read(); + void write(); + + public: + TCPBinaryIOHandler(Kernel& kernel, const std::string& hostname, uint16_t port); + ~TCPBinaryIOHandler() final; + + void start() final; + void stop() final; + + bool send(const Message& message) final; +}; + +} + +#endif + diff --git a/server/src/hardware/protocol/loconet/iohandler/tcpiohandler.cpp b/server/src/hardware/protocol/loconet/iohandler/tcpiohandler.cpp new file mode 100644 index 00000000..8a48b270 --- /dev/null +++ b/server/src/hardware/protocol/loconet/iohandler/tcpiohandler.cpp @@ -0,0 +1,53 @@ +/** + * server/src/hardware/protocol/loconet/iohandler/tcpiohandler.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 "tcpiohandler.hpp" +#include "../kernel.hpp" +#include "../../../../log/logmessageexception.hpp" + +namespace LocoNet { + +TCPIOHandler::TCPIOHandler(Kernel& kernel, const std::string& hostname, uint16_t port) + : IOHandler(kernel) + , m_socket{m_kernel.ioContext()} +{ + boost::system::error_code ec; + + m_endpoint.port(port); + m_endpoint.address(boost::asio::ip::make_address(hostname, ec)); + if(ec) + throw LogMessageException(LogMessage::E2003_MAKE_ADDRESS_FAILED_X, ec); + + m_socket.connect(m_endpoint, ec); + if(ec) + throw LogMessageException(LogMessage::E2005_SOCKET_CONNECT_FAILED_X, ec); + + + m_socket.set_option(boost::asio::socket_base::linger(true, 0)); + m_socket.set_option(boost::asio::ip::tcp::no_delay(true)); +} + +TCPIOHandler::~TCPIOHandler() +{ +} + +} diff --git a/server/src/hardware/protocol/xpressnet/xpressnetlist.hpp b/server/src/hardware/protocol/loconet/iohandler/tcpiohandler.hpp similarity index 61% rename from server/src/hardware/protocol/xpressnet/xpressnetlist.hpp rename to server/src/hardware/protocol/loconet/iohandler/tcpiohandler.hpp index d4499812..af3c9921 100644 --- a/server/src/hardware/protocol/xpressnet/xpressnetlist.hpp +++ b/server/src/hardware/protocol/loconet/iohandler/tcpiohandler.hpp @@ -1,5 +1,5 @@ /** - * server/src/hardware/protocol/xpressnet/xpressnetlist.hpp + * server/src/hardware/protocol/loconet/iohandler/tcpiohandler.hpp * * This file is part of the traintastic source code. * @@ -20,23 +20,26 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_XPRESSNET_XPRESSNETLIST_HPP -#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_XPRESSNET_XPRESSNETLIST_HPP +#ifndef TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_LOCONET_IOHANDLER_TCPIOHANDLER_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_LOCONET_IOHANDLER_TCPIOHANDLER_HPP -#include "../../../core/objectlist.hpp" -#include "xpressnet.hpp" +#include "iohandler.hpp" +#include -class XpressNetList : public ObjectList +namespace LocoNet { + +class TCPIOHandler : public IOHandler { protected: - bool isListedProperty(const std::string& name) final; + boost::asio::ip::tcp::socket m_socket; + boost::asio::ip::tcp::endpoint m_endpoint; + + TCPIOHandler(Kernel& kernel, const std::string& hostname, uint16_t port); public: - CLASS_ID("xpressnet_list") - - XpressNetList(Object& _parent, const std::string& parentPropertyName); - - TableModelPtr getModel() final; + ~TCPIOHandler() override; }; +} + #endif diff --git a/server/src/hardware/protocol/loconet/iohandler/z21iohandler.cpp b/server/src/hardware/protocol/loconet/iohandler/z21iohandler.cpp new file mode 100644 index 00000000..af7f4111 --- /dev/null +++ b/server/src/hardware/protocol/loconet/iohandler/z21iohandler.cpp @@ -0,0 +1,186 @@ +/** + * server/src/hardware/protocol/loconet/iohandler/z21iohandler.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 "z21iohandler.hpp" +#include +#include "../kernel.hpp" +#include "../messages.hpp" +#include "../../z21/messages.hpp" +#include "../../../../core/eventloop.hpp" +#include "../../../../log/log.hpp" +#include "../../../../log/logmessageexception.hpp" + +namespace LocoNet { + +Z21IOHandler::Z21IOHandler(Kernel& kernel, const std::string& hostname, uint16_t port) + : IOHandler(kernel) + , m_socket{m_kernel.ioContext()} + , m_sendBufferOffset{0} +{ + boost::system::error_code ec; + + m_remoteEndpoint.port(port); + m_remoteEndpoint.address(boost::asio::ip::make_address(hostname, ec)); + if(ec) + throw LogMessageException(LogMessage::E2003_MAKE_ADDRESS_FAILED_X, ec); + + if(m_socket.open(boost::asio::ip::udp::v4(), ec)) + throw LogMessageException(LogMessage::E2004_SOCKET_OPEN_FAILED_X, ec); + + if(m_socket.bind(boost::asio::ip::udp::endpoint(boost::asio::ip::address_v4::any(), 0), ec)) + { + m_socket.close(); + throw LogMessageException(LogMessage::E2006_SOCKET_BIND_FAILED_X, ec); + } +} + +void Z21IOHandler::start() +{ + receive(); + + send(Z21::LanSetBroadcastFlags(Z21::BroadcastFlags::LocoNet)); +} + +void Z21IOHandler::stop() +{ + send(Z21::LanLogoff()); + + m_socket.close(); +} + +bool Z21IOHandler::send(const Message& message) +{ + const uint16_t dataLen = sizeof(Z21::Message) + message.size(); + + if(m_sendBufferOffset + dataLen > m_sendBuffer.size()) + return false; + + const bool wasEmpty = m_sendBufferOffset == 0; + + // Z21 header: + const Z21::Message header{dataLen, Z21::LAN_LOCONET_FROM_LAN}; + memcpy(m_sendBuffer.data() + m_sendBufferOffset, &header, sizeof(header)); + m_sendBufferOffset += sizeof(header); + + // LocoNet message: + memcpy(m_sendBuffer.data() + m_sendBufferOffset, &message, message.size()); + m_sendBufferOffset += message.size(); + + if(wasEmpty) + send(); + + return true; +} + +bool Z21IOHandler::send(const Z21::Message& message) +{ + if(m_sendBufferOffset + message.dataLen() > m_sendBuffer.size()) + return false; + + const bool wasEmpty = m_sendBufferOffset == 0; + + memcpy(m_sendBuffer.data() + m_sendBufferOffset, &message, message.dataLen()); + m_sendBufferOffset += message.dataLen(); + + if(wasEmpty) + send(); + + return true; +} + +void Z21IOHandler::receive() +{ + m_socket.async_receive_from(boost::asio::buffer(m_receiveBuffer), m_receiveEndpoint, + [this](const boost::system::error_code& ec, std::size_t bytesReceived) + { + if(!ec) + { + const std::byte* pos = m_receiveBuffer.data(); + while(bytesReceived >= sizeof(Message)) + { + const Z21::Message& message = *reinterpret_cast(pos); + switch(message.header()) + { + case Z21::LAN_LOCONET_Z21_RX: + case Z21::LAN_LOCONET_Z21_TX: + case Z21::LAN_LOCONET_FROM_LAN: + m_kernel.receive(*reinterpret_cast(pos + sizeof(Z21::Message))); + break; + + default: + break; + } + + pos += message.dataLen(); + bytesReceived -= message.dataLen(); + } + receive(); + } + else + { + EventLoop::call( + [this, ec]() + { + Log::log(m_kernel.logId(), LogMessage::E2009_SOCKET_RECEIVE_FAILED_X, ec); + }); + } + }); +} + +void Z21IOHandler::send() +{ + m_socket.async_send_to(boost::asio::buffer(m_sendBuffer.data(), m_sendBufferOffset), m_remoteEndpoint, + [this](const boost::system::error_code& ec, std::size_t bytesTransferred) + { + if(!ec) + { + // echo back to kernel, kernel expects echo but Z21 doesn't send it: + const std::byte* pos = m_sendBuffer.data(); + const std::byte* end = pos + bytesTransferred; + while(pos < end) + { + if(reinterpret_cast(pos)->header() == Z21::LAN_LOCONET_FROM_LAN) + m_kernel.receive(*reinterpret_cast(pos + sizeof(Z21::Message))); + pos += reinterpret_cast(pos)->dataLen(); + } + + m_sendBufferOffset -= bytesTransferred; + + if(m_sendBufferOffset > 0) + { + memmove(m_sendBuffer.data(), m_sendBuffer.data() + bytesTransferred, m_sendBufferOffset); + send(); + } + } + else if(ec != boost::asio::error::operation_aborted) + { + EventLoop::call( + [this, ec]() + { + Log::log(m_kernel.logId(), LogMessage::E2011_SOCKET_SEND_FAILED_X, ec); + // TODO interface status -> error + }); + } + }); +} + +} diff --git a/server/src/hardware/protocol/loconet/iohandler/z21iohandler.hpp b/server/src/hardware/protocol/loconet/iohandler/z21iohandler.hpp new file mode 100644 index 00000000..d70fc6ae --- /dev/null +++ b/server/src/hardware/protocol/loconet/iohandler/z21iohandler.hpp @@ -0,0 +1,62 @@ +/** + * server/src/hardware/protocol/loconet/iohandler/z21iohandler.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_LOCONET_IOHANDLER_Z21IOHANDLER_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_LOCONET_IOHANDLER_Z21IOHANDLER_HPP + +#include "iohandler.hpp" +#include + +namespace Z21 { + struct Message; +} + +namespace LocoNet { + +class Z21IOHandler final : public IOHandler +{ + private: + static constexpr size_t payloadSizeMax = 1500 - 20 - 8; ///< Ethernet MTU - IPv4 header - UDP header + + boost::asio::ip::udp::socket m_socket; + boost::asio::ip::udp::endpoint m_remoteEndpoint; + boost::asio::ip::udp::endpoint m_receiveEndpoint; + std::array m_receiveBuffer; + std::array m_sendBuffer; + size_t m_sendBufferOffset; + + void receive(); + void send(); + bool send(const Z21::Message& message); + + public: + Z21IOHandler(Kernel& kernel, const std::string& hostname, uint16_t port = 21105); + + void start() final; + void stop() final; + + bool send(const Message& message) final; +}; + +} + +#endif diff --git a/server/src/hardware/protocol/loconet/kernel.cpp b/server/src/hardware/protocol/loconet/kernel.cpp new file mode 100644 index 00000000..2e111cc8 --- /dev/null +++ b/server/src/hardware/protocol/loconet/kernel.cpp @@ -0,0 +1,903 @@ +/** + * server/src/hardware/protocol/loconet/loconet.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 "kernel.hpp" +#include "iohandler/iohandler.hpp" +#include "messages.hpp" +#include "../../decoder/decoder.hpp" +#include "../../decoder/decoderchangeflags.hpp" +#include "../../decoder/decodercontroller.hpp" +#include "../../input/inputcontroller.hpp" +#include "../../output/outputcontroller.hpp" +#include "../../../utils/setthreadname.hpp" +#include "../../../utils/inrange.hpp" +#include "../../../core/eventloop.hpp" +#include "../../../log/log.hpp" +#include "../dcc/dcc.hpp" + +namespace LocoNet { + +static void updateDecoderSpeed(const std::shared_ptr& decoder, uint8_t speed) +{ + decoder->emergencyStop.setValueInternal(speed == SPEED_ESTOP); + + if(speed == SPEED_STOP || speed == SPEED_ESTOP) + decoder->throttle.setValueInternal(Decoder::throttleStop); + else + decoder->throttle.setValueInternal(Decoder::speedStepToThrottle(speed - 1, SPEED_MAX - 1)); +} + +Kernel::Kernel(const Config& config) + : m_ioContext{1} + , m_waitingForEcho{false} + , m_waitingForResponse{false} + , m_fastClockSyncTimer(m_ioContext) + , m_decoderController{nullptr} + , m_inputController{nullptr} + , m_outputController{nullptr} + , m_config{config} +#ifndef NDEBUG + , m_started{false} +#endif +{ +} + +void Kernel::setConfig(const Config& config) +{ + m_ioContext.post( + [this, newConfig=config]() + { + if(!m_config.fastClockSyncEnabled && newConfig.fastClockSyncEnabled) + { + send(RequestSlotData(SLOT_FAST_CLOCK)); + startFastClockSyncTimer(); + } + else if(m_config.fastClockSyncEnabled && !newConfig.fastClockSyncEnabled) + { + stopFastClockSyncTimer(); + } + m_config = newConfig; + }); +} + +void Kernel::setOnStarted(std::function callback) +{ + assert(!m_started); + m_onStarted = std::move(callback); +} + +void Kernel::setOnGlobalPowerChanged(std::function callback) +{ + assert(!m_started); + m_onGlobalPowerChanged = std::move(callback); +} + +void Kernel::setOnIdle(std::function callback) +{ + assert(!m_started); + m_onIdle = std::move(callback); +} + +void Kernel::setDecoderController(DecoderController* decoderController) +{ + assert(!m_started); + m_decoderController = decoderController; +} + +void Kernel::setInputController(InputController* inputController) +{ + assert(!m_started); + m_inputController = inputController; +} + +void Kernel::setOutputController(OutputController* outputController) +{ + assert(!m_started); + m_outputController = outputController; +} + +void Kernel::start() +{ + assert(m_ioHandler); + assert(!m_started); + + // reset all state values + m_globalPower = TriState::Undefined; + m_emergencyStop = TriState::Undefined; + m_addressToSlot.clear(); + m_slots.clear(); + m_pendingSlotMessages.clear(); + m_inputValues.fill(TriState::Undefined); + m_outputValues.fill(TriState::Undefined); + + m_thread = std::thread( + [this]() + { + setThreadName("loconet"); + auto work = std::make_shared(m_ioContext); + m_ioContext.run(); + }); + + m_ioContext.post( + [this]() + { + m_ioHandler->start(); + + if(m_config.fastClockSyncEnabled) + startFastClockSyncTimer(); + + for(uint8_t slot = SLOT_LOCO_MIN; slot <= SLOT_LOCO_MAX; slot++) + send(RequestSlotData(slot), LowPriority); + + if(m_onStarted) + EventLoop::call( + [this]() + { + m_onStarted(); + }); + }); + +#ifndef NDEBUG + m_started = true; +#endif +} + +void Kernel::stop() +{ + m_ioContext.post( + [this]() + { + m_fastClockSyncTimer.cancel(); + m_ioHandler->stop(); + }); + + m_ioContext.stop(); + + m_thread.join(); + +#ifndef NDEBUG + m_started = false; +#endif +} + +void Kernel::receive(const Message& message) +{ + if(m_config.debugLogRXTX) + EventLoop::call([this, msg=toString(message)](){ Log::log(m_logId, LogMessage::D2002_RX_X, msg); }); + + bool isResponse = false; + if(m_waitingForEcho && message == m_sendQueue[m_sentMessagePriority].front()) + { + m_waitingForEcho = false; + if(!m_waitingForResponse) + { + m_sendQueue[m_sentMessagePriority].pop(); + sendNextMessage(); + } + } + else if(m_waitingForResponse) + { + isResponse = isValidResponse(m_sendQueue[m_sentMessagePriority].front(), message); + } + + switch(message.opCode) + { + case OPC_GPON: + if(m_globalPower != TriState::True) + { + m_globalPower = TriState::True; + if(m_onGlobalPowerChanged) + EventLoop::call( + [this]() + { + m_onGlobalPowerChanged(true); + }); + } + break; + + case OPC_GPOFF: + if(m_globalPower != TriState::False) + { + m_globalPower = TriState::False; + if(m_onGlobalPowerChanged) + EventLoop::call( + [this]() + { + m_onGlobalPowerChanged(false); + }); + } + break; + + case OPC_IDLE: + if(m_emergencyStop != TriState::True) + { + m_emergencyStop = TriState::True; + if(m_onIdle) + EventLoop::call( + [this]() + { + m_onIdle(); + }); + } + break; + + case OPC_LOCO_SPD: + if(m_decoderController) + { + const LocoSpd& locoSpd = static_cast(message); + if(LocoSlot* slot = getLocoSlot(locoSpd.slot)) + { + const bool changed = slot->addressValid && (!slot->speedValid || slot->speed != locoSpd.speed); + slot->speed = locoSpd.speed; + slot->speedValid = true; + + if(changed) + EventLoop::call( + [this, address=slot->address, speed=slot->speed]() + { + if(auto decoder = getDecoder(address)) + updateDecoderSpeed(decoder, speed); + }); + } + } + break; + + case OPC_LOCO_DIRF: // direction and F0-F4 + if(m_decoderController) + { + const LocoDirF& locoDirF = static_cast(message); + if(LocoSlot* slot = getLocoSlot(locoDirF.slot)) + { + const bool changed = slot->addressValid && (!slot->dirf0f4Valid || slot->dirf0f4 != locoDirF.dirf); + slot->dirf0f4 = locoDirF.dirf; + slot->dirf0f4Valid = true; + + if(changed) + EventLoop::call( + [this, address=slot->address, dirf0f4=slot->dirf0f4]() + { + if(auto decoder = getDecoder(address)) + { + decoder->direction.setValueInternal((dirf0f4 & SL_DIR) ? Direction::Forward : Direction::Reverse); + decoder->setFunctionValue(0, dirf0f4 & SL_F0); + decoder->setFunctionValue(1, dirf0f4 & SL_F1); + decoder->setFunctionValue(2, dirf0f4 & SL_F2); + decoder->setFunctionValue(3, dirf0f4 & SL_F3); + decoder->setFunctionValue(4, dirf0f4 & SL_F4); + } + }); + } + } + break; + + case OPC_LOCO_SND: // F5-F8 + if(m_decoderController) + { + const LocoSnd& locoSnd = static_cast(message); + if(LocoSlot* slot = getLocoSlot(locoSnd.slot)) + { + const bool changed = slot->addressValid && (!slot->f5f8Valid || slot->f5f8 != locoSnd.snd); + slot->f5f8 = locoSnd.snd; + slot->f5f8Valid = true; + + if(changed) + EventLoop::call( + [this, address=slot->address, f5f8=slot->f5f8]() + { + if(auto decoder = getDecoder(address)) + { + decoder->setFunctionValue(5, f5f8 & SL_F5); + decoder->setFunctionValue(6, f5f8 & SL_F6); + decoder->setFunctionValue(7, f5f8 & SL_F7); + decoder->setFunctionValue(8, f5f8 & SL_F8); + } + }); + } + } + break; + + case OPC_LOCO_F9F12: + if(m_decoderController) + { + const LocoF9F12& locoF9F12 = static_cast(message); + if(LocoSlot* slot = getLocoSlot(locoF9F12.slot)) + { + const bool changed = slot->addressValid && (!slot->f9f12Valid || slot->f9f12 != locoF9F12.function); + slot->f9f12 = locoF9F12.function; + slot->f9f12Valid = true; + + if(changed) + EventLoop::call( + [this, address=slot->address, f9f12=slot->f9f12]() + { + if(auto decoder = getDecoder(address)) + { + decoder->setFunctionValue(9, f9f12 & SL_F9); + decoder->setFunctionValue(10, f9f12 & SL_F10); + decoder->setFunctionValue(11, f9f12 & SL_F11); + decoder->setFunctionValue(12, f9f12 & SL_F12); + } + }); + } + } + break; + + case OPC_INPUT_REP: + if(m_inputController) + { + const auto& inputRep = static_cast(message); + const auto value = toTriState(inputRep.value()); + if(m_inputValues[inputRep.fullAddress()] != value) + { + if(m_config.debugLogInput) + EventLoop::call( + [this, address=1 + inputRep.fullAddress(), value=inputRep.value()]() + { + Log::log(m_logId, LogMessage::D2007_INPUT_X_IS_X, address, value ? std::string_view{"1"} : std::string_view{"0"}); + }); + + m_inputValues[inputRep.fullAddress()] = value; + + EventLoop::call( + [this, address=1 + inputRep.fullAddress(), value]() + { + m_inputController->updateInputValue(address, value); + }); + } + } + break; + + case OPC_SW_REQ: + if(m_outputController) + { + const auto& switchRequest = static_cast(message); + const auto on = toTriState(switchRequest.on()); + if(m_outputValues[switchRequest.fullAddress()] != on) + { + if(m_config.debugLogOutput) + EventLoop::call( + [this, address=1 + switchRequest.fullAddress(), on=switchRequest.on()]() + { + Log::log(m_logId, LogMessage::D2008_OUTPUT_X_IS_X, address, on ? std::string_view{"1"} : std::string_view{"0"}); + }); + + m_outputValues[switchRequest.fullAddress()] = on; + + EventLoop::call( + [this, address=1 + switchRequest.fullAddress(), on]() + { + m_outputController->updateOutputValue(address, on); + }); + } + } + break; + + case OPC_SW_REP: + break; // unimplemented + + case OPC_SL_RD_DATA: + { + const uint8_t slot = *(reinterpret_cast(&message) + 2); + if(m_decoderController && isLocoSlot(slot)) + { + const SlotReadData& slotReadData = static_cast(message); + LocoSlot* locoSlot = getLocoSlot(slotReadData.slot, false); + assert(locoSlot); + + if(!locoSlot->addressValid) + m_addressToSlot[slotReadData.address()] = slot; + + bool changed = locoSlot->addressValid && locoSlot->address != slotReadData.address(); + if(changed) + { + if(auto it = m_addressToSlot.find(locoSlot->address); it != m_addressToSlot.end() && it->second == slotReadData.slot) + m_addressToSlot.erase(locoSlot->address); + locoSlot->invalidate(); + } + locoSlot->address = slotReadData.address(); + locoSlot->addressValid = true; + + changed |= (!locoSlot->dirf0f4Valid || locoSlot->dirf0f4 != slotReadData.dirf); + locoSlot->dirf0f4 = slotReadData.dirf; + locoSlot->dirf0f4Valid = true; + + changed |= (!locoSlot->f5f8Valid || locoSlot->f5f8 != slotReadData.snd); + locoSlot->f5f8 = slotReadData.snd; + locoSlot->f5f8Valid = true; + + if(changed) + EventLoop::call( + [this, address=locoSlot->address, speed=locoSlot->speed, dirf0f4=locoSlot->dirf0f4, f5f8=locoSlot->f5f8]() + { + if(auto decoder = getDecoder(address)) + { + updateDecoderSpeed(decoder, speed); + decoder->direction.setValueInternal((dirf0f4 & SL_DIR) ? Direction::Forward : Direction::Reverse); + decoder->setFunctionValue(0, dirf0f4 & SL_F0); + decoder->setFunctionValue(1, dirf0f4 & SL_F1); + decoder->setFunctionValue(2, dirf0f4 & SL_F2); + decoder->setFunctionValue(3, dirf0f4 & SL_F3); + decoder->setFunctionValue(4, dirf0f4 & SL_F4); + decoder->setFunctionValue(5, f5f8 & SL_F5); + decoder->setFunctionValue(6, f5f8 & SL_F6); + decoder->setFunctionValue(7, f5f8 & SL_F7); + decoder->setFunctionValue(8, f5f8 & SL_F8); + } + }); + + // check if there are pending slot messages + if(auto it = m_pendingSlotMessages.find(locoSlot->address); it != m_pendingSlotMessages.end()) + { + std::byte* p = it->second.data(); + size_t size = it->second.size(); + while(size > 0) + { + Message& slotMassage = *reinterpret_cast(p); + setSlot(slotMassage, slot); + updateChecksum(slotMassage); + send(slotMassage); + p += slotMassage.size(); + size -= slotMassage.size(); + } + m_pendingSlotMessages.erase(it); + } + } + else if(slot == SLOT_FAST_CLOCK) + { + // todo + } + else if(slot == SLOT_PROGRAMMING_TRACK) + { + // todo + } + break; + } + case OPC_BUSY: + break; // unimplemented + + case OPC_LONG_ACK: + { + const auto& longAck = static_cast(message); + if(longAck.respondingOpCode() == OPC_LOCO_ADR && longAck.ack1 == 0) + { + EventLoop::call( + [this]() + { + Log::log(m_logId, LogMessage::C2004_CANT_GET_FREE_SLOT); + }); + } + break; + } + case OPC_SLOT_STAT1: + break; // unimplemented + + case OPC_CONSIST_FUNC: + break; // unimplemented + + case OPC_UNLINK_SLOTS: + break; // unimplemented + + case OPC_LINK_SLOTS: + break; // unimplemented + + case OPC_MOVE_SLOTS: + break; // unimplemented + + case OPC_RQ_SL_DATA: + break; // unimplemented + + case OPC_SW_STATE: + break; // unimplemented + + case OPC_SW_ACK: + break; // unimplemented + + case OPC_LOCO_ADR: + break; // unimplemented + + case OPC_MULTI_SENSE: + break; // unimplemented + + case OPC_D4: + if(m_decoderController) + { + const uint8_t* bytes = reinterpret_cast(&message); + if(bytes[1] == 0x20) + { + switch(bytes[3]) + { + case 0x08: + { + const LocoF13F19& locoF13F19 = static_cast(message); + if(LocoSlot* slot = getLocoSlot(locoF13F19.slot)) + { + const bool changed = slot->addressValid && (!slot->f13f19Valid || slot->f13f19 != locoF13F19.function); + slot->f13f19 = locoF13F19.function; + slot->f13f19Valid = true; + + if(changed) + EventLoop::call( + [this, address=slot->address, f13f19=slot->f13f19]() + { + if(auto decoder = getDecoder(address)) + { + decoder->setFunctionValue(13, f13f19 & SL_F13); + decoder->setFunctionValue(14, f13f19 & SL_F14); + decoder->setFunctionValue(15, f13f19 & SL_F15); + decoder->setFunctionValue(16, f13f19 & SL_F16); + decoder->setFunctionValue(17, f13f19 & SL_F17); + decoder->setFunctionValue(18, f13f19 & SL_F18); + decoder->setFunctionValue(19, f13f19 & SL_F19); + } + }); + } + break; + } + case 0x05: + { + const LocoF20F28& locoF20F28 = static_cast(message); + if(LocoSlot* slot = getLocoSlot(locoF20F28.slot)) + { + const bool changed = slot->addressValid && (!slot->f20f28Valid || slot->f20f28 != locoF20F28.function); + slot->f20f28 = locoF20F28.function; + slot->f20f28Valid = true; + + if(changed) + EventLoop::call( + [this, address=slot->address, f20f28=slot->f20f28]() + { + if(auto decoder = getDecoder(address)) + { + decoder->setFunctionValue(20, f20f28 & SL_F20); + decoder->setFunctionValue(28, f20f28 & SL_F28); + } + }); + } + break; + } + case 0x09: + { + const LocoF21F27& locoF21F27 = static_cast(message); + if(LocoSlot* slot = getLocoSlot(locoF21F27.slot)) + { + const bool changed = slot->addressValid && (!slot->f21f27Valid || slot->f21f27 != locoF21F27.function); + slot->f21f27 = locoF21F27.function; + slot->f21f27Valid = true; + + if(changed) + EventLoop::call( + [this, address=slot->address, f21f27=slot->f21f27]() + { + if(auto decoder = getDecoder(address)) + { + decoder->setFunctionValue(21, f21f27 & SL_F21); + decoder->setFunctionValue(22, f21f27 & SL_F22); + decoder->setFunctionValue(23, f21f27 & SL_F23); + decoder->setFunctionValue(24, f21f27 & SL_F24); + decoder->setFunctionValue(25, f21f27 & SL_F25); + decoder->setFunctionValue(26, f21f27 & SL_F26); + decoder->setFunctionValue(27, f21f27 & SL_F27); + } + }); + } + break; + } + } + } + } + break; + + case OPC_MULTI_SENSE_LONG: + break; // unimplemented + + case OPC_PEER_XFER: + break; // unimplemented + + case OPC_IMM_PACKET: + break; // unimplemented + + case OPC_WR_SL_DATA: + break; // unimplemented + } + + if(m_waitingForResponse && isResponse) + { + m_waitingForResponse = false; + m_sendQueue[m_sentMessagePriority].pop(); + sendNextMessage(); + } +} + +void Kernel::setPowerOn(bool value) +{ + if(value) + { + m_ioContext.post( + [this]() + { + if(m_globalPower != TriState::True) + send(GlobalPowerOn(), HighPriority); + }); + } + else + { + m_ioContext.post( + [this]() + { + if(m_globalPower != TriState::False) + send(GlobalPowerOff(), HighPriority); + }); + } +} + +void Kernel::emergencyStop() +{ + m_ioContext.post( + [this]() + { + if(m_emergencyStop != TriState::True) + send(Idle(), HighPriority); + }); +} + +void Kernel::resume() +{ + m_ioContext.post( + [this]() + { + if(m_emergencyStop != TriState::False) + { + m_emergencyStop = TriState::False; + + // TODO: restore speeds + } + }); +} + +void Kernel::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber) +{ + if(has(changes, DecoderChangeFlags::EmergencyStop | DecoderChangeFlags::Throttle)) + { + const uint8_t speedStep = Decoder::throttleToSpeedStep(decoder.throttle, SPEED_MAX - 1); + if(m_emergencyStop == TriState::False || decoder.emergencyStop || speedStep == SPEED_STOP) + { + // only send speed updates if bus estop isn't active, except for speed STOP and ESTOP + LocoSpd message{static_cast(decoder.emergencyStop ? SPEED_ESTOP : (speedStep > 0 ? 1 + speedStep : SPEED_STOP))}; + send(decoder.address, message); + } + } + + if(has(changes, DecoderChangeFlags::FunctionValue | DecoderChangeFlags::Direction)) + { + if(functionNumber <= 4 || has(changes, DecoderChangeFlags::Direction)) + { + LocoDirF message{ + decoder.direction, + decoder.getFunctionValue(0), + decoder.getFunctionValue(1), + decoder.getFunctionValue(2), + decoder.getFunctionValue(3), + decoder.getFunctionValue(4)}; + send(decoder.address, message); + } + else if(functionNumber <= 8) + { + LocoSnd message{ + decoder.getFunctionValue(5), + decoder.getFunctionValue(6), + decoder.getFunctionValue(7), + decoder.getFunctionValue(8)}; + send(decoder.address, message); + } + else if(functionNumber <= 12) + { + LocoF9F12 message{ + decoder.getFunctionValue(9), + decoder.getFunctionValue(10), + decoder.getFunctionValue(11), + decoder.getFunctionValue(12)}; + send(decoder.address, message); + } + else if(functionNumber <= 19) + { + LocoF13F19 message{ + decoder.getFunctionValue(13), + decoder.getFunctionValue(14), + decoder.getFunctionValue(15), + decoder.getFunctionValue(16), + decoder.getFunctionValue(17), + decoder.getFunctionValue(18), + decoder.getFunctionValue(19)}; + send(decoder.address, message); + } + else if(functionNumber == 20 || functionNumber == 28) + { + LocoF20F28 message{ + decoder.getFunctionValue(20), + decoder.getFunctionValue(28)}; + send(decoder.address, message); + } + else if(functionNumber <= 27) + { + LocoF21F27 message{ + decoder.getFunctionValue(21), + decoder.getFunctionValue(22), + decoder.getFunctionValue(23), + decoder.getFunctionValue(24), + decoder.getFunctionValue(25), + decoder.getFunctionValue(26), + decoder.getFunctionValue(27)}; + send(decoder.address, message); + } + } +} + +bool Kernel::setOutput(uint16_t address, bool value) +{ + if(!inRange(address, outputAddressMin, outputAddressMax)) + return false; + + m_ioContext.post( + [this, address, value]() + { + send(SwitchRequest(address - 1, value)); + }); + + return true; +} + +Kernel::LocoSlot* Kernel::getLocoSlot(uint8_t slot, bool sendSlotDataRequestIfNew) +{ + if(!isLocoSlot(slot)) + return nullptr; + + auto it = m_slots.find(slot); + if(it == m_slots.end()) + { + if(sendSlotDataRequestIfNew) + send(RequestSlotData(slot)); + it = m_slots.emplace(slot, LocoSlot()).first; + } + + return &it->second; +} + +std::shared_ptr Kernel::getDecoder(uint16_t address) +{ + return m_decoderController->getDecoder(DecoderProtocol::DCC, address, DCC::isLongAddress(address), true); +} + +void Kernel::setIOHandler(std::unique_ptr handler) +{ + assert(handler); + assert(!m_ioHandler); + m_ioHandler = std::move(handler); +} + +void Kernel::send(const Message& message, Priority priority) +{ + if(!m_sendQueue[priority].append(message)) + { + // TODO: log message + return; + } + + if(!m_waitingForEcho && !m_waitingForResponse) + sendNextMessage(); +} + +void Kernel::send(uint16_t address, Message& message, uint8_t& slot) +{ + if(auto addressToSlot = m_addressToSlot.find(address); addressToSlot != m_addressToSlot.end()) + { + slot = addressToSlot->second; + updateChecksum(message); + send(message); + } + else // try get a slot + { + std::byte* ptr = reinterpret_cast(&message); + + auto pendingSlotMessage = m_pendingSlotMessages.find(address); + if(pendingSlotMessage == m_pendingSlotMessages.end()) + { + m_pendingSlotMessages[address].assign(ptr, ptr + message.size()); + send(LocoAdr{address}, HighPriority); + } + else + pendingSlotMessage->second.insert(pendingSlotMessage->second.end(), ptr, ptr + message.size()); + } +} + +void Kernel::sendNextMessage() +{ + for(int priority = HighPriority; priority <= LowPriority; priority++) + { + if(!m_sendQueue[priority].empty()) + { + const Message& message = m_sendQueue[priority].front(); + + if(m_config.debugLogRXTX) + EventLoop::call([this, msg=toString(message)](){ Log::log(m_logId, LogMessage::D2001_TX_X, msg); }); + + if(m_ioHandler->send(message)) + { + m_sentMessagePriority = static_cast(priority); + m_waitingForEcho = true; + m_waitingForResponse = hasResponse(message); + } + else + {} // log message and go to error state + return; + } + } +} + +void Kernel::startFastClockSyncTimer() +{ + assert(m_config.fastClockSyncInterval > 0); + m_fastClockSyncTimer.expires_after(boost::asio::chrono::seconds(m_config.fastClockSyncInterval)); + m_fastClockSyncTimer.async_wait(std::bind(&Kernel::fastClockSyncTimerExpired, this, std::placeholders::_1)); +} + +void Kernel::stopFastClockSyncTimer() +{ + m_fastClockSyncTimer.cancel(); +} + +void Kernel::fastClockSyncTimerExpired(const boost::system::error_code& ec) +{ + if(ec || !m_config.fastClockSyncEnabled) + return; + + send(RequestSlotData(SLOT_FAST_CLOCK)); + + startFastClockSyncTimer(); +} + + +bool Kernel::SendQueue::append(const Message& message) +{ + const uint8_t messageSize = message.size(); + if(m_bytes + messageSize > threshold()) + return false; + + memcpy(m_front + m_bytes, &message, messageSize); + m_bytes += messageSize; + + return true; +} + +void Kernel::SendQueue::pop() +{ + const uint8_t messageSize = front().size(); + m_front += messageSize; + m_bytes -= messageSize; + + if(static_cast(m_front - m_buffer.data()) >= threshold()) + { + memmove(m_buffer.data(), m_front, m_bytes); + m_front = m_buffer.data(); + } +} + +} diff --git a/server/src/hardware/protocol/loconet/kernel.hpp b/server/src/hardware/protocol/loconet/kernel.hpp new file mode 100644 index 00000000..b5b8a5e2 --- /dev/null +++ b/server/src/hardware/protocol/loconet/kernel.hpp @@ -0,0 +1,347 @@ +/** + * server/src/hardware/protocol/loconet/kernel.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_PROTOCOL_LOCONET_KERNEL_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_LOCONET_KERNEL_HPP + +#include +#include +#include +#include +#include +#include "config.hpp" +#include "iohandler/iohandler.hpp" + +class Decoder; +enum class DecoderChangeFlags; +class DecoderController; +class InputController; +class OutputController; + +namespace LocoNet { + +struct Message; + +class Kernel +{ + private: + enum Priority + { + HighPriority = 0, + NormalPriority = 1, + LowPriority = 2, + }; + + class SendQueue + { + private: + std::array m_buffer; + std::byte* m_front; + std::size_t m_bytes; + + constexpr std::size_t threshold() const noexcept { return m_buffer.size() / 2; } + + public: + SendQueue() + : m_front{m_buffer.data()} + , m_bytes{0} + { + } + + inline bool empty() const + { + return m_bytes == 0; + } + + inline const Message& front() const + { + return *reinterpret_cast(m_front); + } + + bool append(const Message& message); + + void pop(); + }; + + struct LocoSlot + { + uint16_t address = 0; + bool addressValid = false; + uint8_t speed = 0; + bool speedValid = false; + uint8_t dirf0f4 = 0; + bool dirf0f4Valid = false; + uint8_t f5f8 = 0; + bool f5f8Valid = false; + uint8_t f9f12 = 0; + bool f9f12Valid = false; + uint8_t f13f19 = 0; + bool f13f19Valid = false; + uint8_t f21f27 = 0; + bool f21f27Valid = false; + uint8_t f20f28 = 0; + bool f20f28Valid = false; + + void invalidate() + { + addressValid = false; + speedValid = false; + dirf0f4Valid = false; + f5f8Valid = false; + f9f12Valid = false; + f13f19Valid = false; + f20f28Valid = false; + f21f27Valid = false; + } + }; + + boost::asio::io_context m_ioContext; + std::unique_ptr m_ioHandler; + std::thread m_thread; + std::string m_logId; + std::function m_onStarted; + + std::array m_sendQueue; + Priority m_sentMessagePriority; + bool m_waitingForEcho; + bool m_waitingForResponse; + + TriState m_globalPower; + std::function m_onGlobalPowerChanged; + + TriState m_emergencyStop; + std::function m_onIdle; + + boost::asio::steady_timer m_fastClockSyncTimer; + + DecoderController* m_decoderController; + std::unordered_map m_addressToSlot; + std::unordered_map m_slots; + std::unordered_map> m_pendingSlotMessages; + + InputController* m_inputController; + std::array m_inputValues; + + OutputController* m_outputController; + std::array m_outputValues; + + Config m_config; +#ifndef NDEBUG + bool m_started; +#endif + + Kernel(const Config& config); + + LocoSlot* getLocoSlot(uint8_t slot, bool sendSlotDataRequestIfNew = true); + + std::shared_ptr getDecoder(uint16_t address); + + void setIOHandler(std::unique_ptr handler); + + void send(const Message& message, Priority priority = NormalPriority); + void send(uint16_t address, Message& message, uint8_t& slot); + template + inline void send(uint16_t address, T& message) + { + send(address, message, message.slot); + } + void sendNextMessage(); + + void startFastClockSyncTimer(); + void stopFastClockSyncTimer(); + void fastClockSyncTimerExpired(const boost::system::error_code& ec); + + public: + static constexpr uint16_t inputAddressMin = 1; + static constexpr uint16_t inputAddressMax = 4096; + static constexpr uint16_t outputAddressMin = 1; + static constexpr uint16_t outputAddressMax = 4096; + + Kernel(const Kernel&) = delete; + Kernel& operator =(const Kernel&) = delete; + + /** + * @brief IO context for LocoNet kernel and IO handler + * + * @return The IO context + */ + boost::asio::io_context& ioContext() { return m_ioContext; } + + /** + * @brief Create kernel and IO handler + * + * @param[in] config LocoNet configuration + * @param[in] args IO handler arguments + * @return The kernel instance + */ + template + static std::unique_ptr create(const Config& config, Args... args) + { + static_assert(std::is_base_of_v); + std::unique_ptr kernel{new Kernel(config)}; + kernel->setIOHandler(std::make_unique(*kernel, std::forward(args)...)); + return kernel; + } + + /** + * @brief Access the IO handler + * + * @return The IO handler + * @note The IO handler runs in the kernel's IO context, not all functions can be called safely! + */ + template + T& ioHandler() + { + assert(dynamic_cast(m_ioHandler.get())); + return static_cast(*m_ioHandler); + } + + /// @brief Get object id used for log messages + /// @return The object id + inline const std::string& logId() + { + return m_logId; + } + + /** + * @brief Set object id used for log messages + * + * @param[in] value The object id + */ + void setLogId(std::string value) { m_logId = std::move(value); } + + /** + * @brief Set LocoNet configuration + * + * @param[in] config The LocoNet configuration + */ + void setConfig(const Config& config); + + /** + * @brief ... + * + * @param[in] callback ... + * @note This function may not be called when the kernel is running. + */ + void setOnStarted(std::function callback); + + /** + * @brief ... + * + * @param[in] callback ... + * @note This function may not be called when the kernel is running. + */ + void setOnGlobalPowerChanged(std::function callback); + + /** + * @brief ... + * + * @param[in] callback ... + * @note This function may not be called when the kernel is running. + */ + void setOnIdle(std::function callback); + + /** + * @brief Set the decoder controller + * + * @param[in] decoderController The decoder controller + * @note This function may not be called when the kernel is running. + */ + void setDecoderController(DecoderController* decoderController); + + /** + * @brief Set the input controller + * + * @param[in] inputController The input controller + * @note This function may not be called when the kernel is running. + */ + void setInputController(InputController* inputController); + + /** + * @brief Set the output controller + * + * @param[in] outputController The output controller + * @note This function may not be called when the kernel is running. + */ + void setOutputController(OutputController* outputController); + + /** + * @brief Start the kernel and IO handler + */ + void start(); + + /** + * @brief Stop the kernel and IO handler + */ + void stop(); + + /** + * @brief ... + * + * This must be called by the IO handler whenever a LocoNet message is received. + * + * @param[in] message The received LocoNet message + * @note This function must run in the kernel's IO context + */ + void receive(const Message& message); + + /** + * + * + */ + void setPowerOn(bool value); + + /** + * + * + */ + void emergencyStop(); + + + /** + * + */ + void resume(); + + //TriState getInput(uint16_t address) const; + + //TriState getOutput(uint16_t address) const; + + /** + * + * + */ + void decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber); + + /** + * + * @param[in] address Output address, 1..4096 + * @param[in] value Output value: \c true is on, \c false is off. + * @return \c true if send successful, \c false otherwise. + */ + bool setOutput(uint16_t address, bool value); +}; + +} + +#endif + diff --git a/server/src/hardware/protocol/loconet/loconet.cpp b/server/src/hardware/protocol/loconet/loconet.cpp deleted file mode 100644 index 61319f39..00000000 --- a/server/src/hardware/protocol/loconet/loconet.cpp +++ /dev/null @@ -1,601 +0,0 @@ -/** - * server/src/hardware/protocol/loconet.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 "loconet.hpp" -#include -#include -#include "../../../core/eventloop.hpp" -#include "../../../core/traintastic.hpp" -#include "../../commandstation/commandstation.hpp" -#include "../../input/loconetinput.hpp" -#include "../../output/loconetoutput.hpp" -#include "../../../core/attributes.hpp" -#include "../../../world/getworld.hpp" -#include "loconetlisttablemodel.hpp" -#include "../../../log/log.hpp" -#include "../../../utils/displayname.hpp" - -namespace LocoNet { - -void updateDecoderSpeed(const std::shared_ptr& decoder, uint8_t speed) -{ - decoder->emergencyStop.setValueInternal(speed == SPEED_ESTOP); - - if(speed == SPEED_STOP || speed == SPEED_ESTOP) - decoder->throttle.setValueInternal(Decoder::throttleStop); - else - decoder->throttle.setValueInternal(Decoder::speedStepToThrottle(speed - 1, SPEED_MAX - 1)); -} - -std::shared_ptr LocoNet::create(Object& _parent, const std::string& parentPropertyName, std::function send) -{ - std::shared_ptr object{std::make_shared(_parent, parentPropertyName, std::move(send), Private())}; - if(auto w = getWorld(&_parent)) - w->loconets->addObject(object); - return object; -} - -LocoNet::LocoNet(Object& _parent, const std::string& parentPropertyName, std::function send, Private) : - SubObject(_parent, parentPropertyName), - m_commandStation{dynamic_cast(&_parent)}, - m_send{std::move(send)}, - m_debugLogRXTX{false}, - m_queryLocoSlots{SLOT_UNKNOWN}, - commandStation{this, "command_station", LocoNetCommandStation::Custom, PropertyFlags::ReadWrite | PropertyFlags::Store, - [this](LocoNetCommandStation value) - { - switch(value) - { - case LocoNetCommandStation::Custom: - break; - - case LocoNetCommandStation::DigikeijsDR5000: - break; - - case LocoNetCommandStation::UhlenbrockIntellibox: - break; - } - }}, - debugLogInput{this, "debug_log_input", false, PropertyFlags::ReadWrite | PropertyFlags::Store}, - debugLogOutput{this, "debug_log_output", false, PropertyFlags::ReadWrite | PropertyFlags::Store}, - debugLogRXTX{this, "debug_log_rx_tx", m_debugLogRXTX, PropertyFlags::ReadWrite | PropertyFlags::Store, - [this](bool value) - { - m_debugLogRXTX = value; - }}, - inputMonitor{*this, "input_monitor", - [this]() - { - return std::make_shared(shared_ptr()); - }}, - outputKeyboard{*this, "output_keyboard", - [this]() - { - return std::make_shared(shared_ptr()); - }} -{ - assert(m_send); - - Attributes::addEnabled(commandStation, m_commandStation && !m_commandStation->online); - Attributes::addValues(commandStation, LocoNetCommandStationValues); - m_interfaceItems.add(commandStation); - //Attributes::addGroup(debugLogInput); - m_interfaceItems.add(debugLogInput); - //Attributes::addGroup(debugLogOuput); - m_interfaceItems.add(debugLogOutput); - //Attributes::addGroup(debugLogRXTX); - m_interfaceItems.add(debugLogRXTX); - - Attributes::addDisplayName(inputMonitor, DisplayName::Hardware::inputMonitor); - m_interfaceItems.add(inputMonitor); - - Attributes::addDisplayName(outputKeyboard, DisplayName::Hardware::outputKeyboard); - m_interfaceItems.add(outputKeyboard); -} - -bool LocoNet::send(const Message& message) -{ - if(m_debugLogRXTX) - Log::log(*this, LogMessage::D2001_TX_X, message); - assert(isValid(message)); - return m_send(message); -} - -void LocoNet::send(uint16_t address, Message& message, uint8_t& slot) -{ - if((slot = m_slots.getSlot(address)) != SLOT_UNKNOWN) - { - updateChecksum(message); - send(message); - } - else // try get a slot - { - std::byte* ptr = reinterpret_cast(&message); - - auto it = m_slotRequests.find(address); - if(it == m_slotRequests.end()) - { - m_slotRequests[address].assign(ptr, ptr + message.size()); - send(LocoAdr{address}); - } - else - it->second.insert(it->second.end(), ptr, ptr + message.size()); - } -} - -void LocoNet::receive(const Message& message) -{ - // NOTE: this function is called async! - - assert(isValid(message)); - - if(m_debugLogRXTX) - EventLoop::call([this, msg=toString(message)](){ Log::log(*this, LogMessage::D2002_RX_X, msg); }); - - switch(message.opCode) - { - case OPC_GPON: - EventLoop::call( - [this]() - { - if(m_commandStation) - { - m_commandStation->emergencyStop.setValueInternal(false); - m_commandStation->powerOn.setValueInternal(true); - } - }); - break; - - case OPC_GPOFF: - EventLoop::call( - [this]() - { - if(m_commandStation) - m_commandStation->powerOn.setValueInternal(false); - }); - break; - - case OPC_IDLE: - EventLoop::call( - [this]() - { - if(m_commandStation) - m_commandStation->emergencyStop.setValueInternal(true); - }); - break; - - case OPC_LOCO_SPD: - EventLoop::call( - [this, locoSpd=*static_cast(&message)]() - { - if(auto decoder = getDecoder(locoSpd.slot)) - updateDecoderSpeed(decoder, locoSpd.speed); - }); - break; - - case OPC_LOCO_DIRF: - EventLoop::call( - [this, locoDirF=*static_cast(&message)]() - { - if(auto decoder = getDecoder(locoDirF.slot)) - { - decoder->direction.setValueInternal(locoDirF.direction()); - decoder->setFunctionValue(0, locoDirF.f0()); - decoder->setFunctionValue(1, locoDirF.f1()); - decoder->setFunctionValue(2, locoDirF.f2()); - decoder->setFunctionValue(3, locoDirF.f3()); - decoder->setFunctionValue(4, locoDirF.f4()); - } - }); - break; - - case OPC_LOCO_SND: - EventLoop::call( - [this, locoSnd=*static_cast(&message)]() - { - if(auto decoder = getDecoder(locoSnd.slot)) - { - decoder->setFunctionValue(5, locoSnd.f5()); - decoder->setFunctionValue(6, locoSnd.f6()); - decoder->setFunctionValue(7, locoSnd.f7()); - decoder->setFunctionValue(8, locoSnd.f8()); - } - }); - break; - - case OPC_LOCO_F9F12: - EventLoop::call( - [this, locoF9F12=*static_cast(&message)]() - { - if(auto decoder = getDecoder(locoF9F12.slot)) - { - decoder->setFunctionValue(9, locoF9F12.f9()); - decoder->setFunctionValue(10, locoF9F12.f10()); - decoder->setFunctionValue(11, locoF9F12.f11()); - decoder->setFunctionValue(12, locoF9F12.f12()); - } - }); - break; - - case OPC_INPUT_REP: - EventLoop::call( - [this, inputRep=*static_cast(&message)]() - { - const uint16_t address = 1 + inputRep.fullAddress(); - - if(debugLogInput) - Log::log(*this, LogMessage::D2007_INPUT_X_IS_X, address, inputRep.value() ? std::string_view{"1"} : std::string_view{"0"}); - - auto it = m_inputs.find(address); - if(it != m_inputs.end()) - it->second->updateValue(toTriState(inputRep.value())); - else - inputMonitorValueChanged(address, toTriState(inputRep.value())); - }); - break; - - case OPC_SW_REQ: - EventLoop::call( - [this, switchRequest=*static_cast(&message)]() - { - const uint16_t address = 1 + switchRequest.fullAddress(); - - if(debugLogOutput) - Log::log(*this, LogMessage::D2008_OUTPUT_X_IS_X, address, switchRequest.on() ? std::string_view{"1"} : std::string_view{"0"}); - - auto it = m_outputs.find(address); - if(it != m_outputs.end()) - it->second->updateValue(toTriState(switchRequest.on())); - else - outputKeyboardValueChanged(address, toTriState(switchRequest.on())); - }); - break; - - case OPC_SL_RD_DATA: - EventLoop::call( - [this, slotReadData=*static_cast(&message)]() - { - if(m_queryLocoSlots == slotReadData.slot) - { - m_queryLocoSlots++; - if(m_queryLocoSlots <= SLOT_LOCO_MAX) - send(RequestSlotData(m_queryLocoSlots)); - else - m_queryLocoSlots = SLOT_UNKNOWN; // done - } - - if(slotReadData.isBusy() || slotReadData.isActive()) - { - m_slots.set(slotReadData.address(), slotReadData.slot); - - Log::log(*this, LogMessage::D2009_SLOT_X_IS_X, slotReadData.slot, slotReadData.address()); - - if(auto decoder = getDecoder(slotReadData.slot, false)) - { - updateDecoderSpeed(decoder, slotReadData.spd); - decoder->direction.setValueInternal(slotReadData.direction()); - decoder->setFunctionValue(0, slotReadData.f0()); - decoder->setFunctionValue(1, slotReadData.f1()); - decoder->setFunctionValue(2, slotReadData.f2()); - decoder->setFunctionValue(3, slotReadData.f3()); - decoder->setFunctionValue(4, slotReadData.f4()); - decoder->setFunctionValue(5, slotReadData.f5()); - decoder->setFunctionValue(6, slotReadData.f6()); - decoder->setFunctionValue(7, slotReadData.f7()); - decoder->setFunctionValue(8, slotReadData.f8()); - } - } - else - Log::log(*this, LogMessage::D2010_SLOT_X_IS_FREE, slotReadData.slot); - }); - break; - - case OPC_BUSY: - case OPC_SW_REP: - case OPC_LONG_ACK: - case OPC_SLOT_STAT1: - case OPC_CONSIST_FUNC: - case OPC_UNLINK_SLOTS: - case OPC_LINK_SLOTS: - case OPC_MOVE_SLOTS: - case OPC_RQ_SL_DATA: - case OPC_SW_STATE: - case OPC_SW_ACK: - case OPC_LOCO_ADR: - case OPC_MULTI_SENSE: - case OPC_D4: - case OPC_MULTI_SENSE_LONG: - case OPC_PEER_XFER: - case OPC_IMM_PACKET: - case OPC_WR_SL_DATA: - // unimplemented - break; - } -} - -void LocoNet::emergencyStopChanged(bool value) -{ - if(value) - send(Idle()); - else if(m_commandStation && m_commandStation->powerOn) - send(GlobalPowerOn()); -} - -void LocoNet::powerOnChanged(bool value) -{ - if(value) - send(GlobalPowerOn()); - else - send(GlobalPowerOff()); -} - -void LocoNet::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber) -{ - if(has(changes, DecoderChangeFlags::EmergencyStop | DecoderChangeFlags::Throttle)) - { - const bool emergencyStop = decoder.emergencyStop || (m_commandStation && m_commandStation->emergencyStop); - const uint8_t speedStep = Decoder::throttleToSpeedStep(decoder.throttle, SPEED_MAX - 1); - LocoSpd message{static_cast(emergencyStop ? SPEED_ESTOP : (speedStep > 0 ? 1 + speedStep : SPEED_STOP))}; - send(decoder.address, message); - } - - if(has(changes, DecoderChangeFlags::FunctionValue | DecoderChangeFlags::Direction)) - { - if(functionNumber <= 4 || has(changes, DecoderChangeFlags::Direction)) - { - LocoDirF message{ - decoder.direction, - decoder.getFunctionValue(0), - decoder.getFunctionValue(1), - decoder.getFunctionValue(2), - decoder.getFunctionValue(3), - decoder.getFunctionValue(4)}; - send(decoder.address, message); - } - else if(functionNumber <= 8) - { - LocoSnd message{ - decoder.getFunctionValue(5), - decoder.getFunctionValue(6), - decoder.getFunctionValue(7), - decoder.getFunctionValue(8)}; - send(decoder.address, message); - } - else if(functionNumber <= 12) - { - LocoF9F12 message{ - decoder.getFunctionValue(9), - decoder.getFunctionValue(10), - decoder.getFunctionValue(11), - decoder.getFunctionValue(12)}; - send(decoder.address, message); - } - else if(functionNumber <= 19) - { - LocoF13F19 message{ - decoder.getFunctionValue(13), - decoder.getFunctionValue(14), - decoder.getFunctionValue(15), - decoder.getFunctionValue(16), - decoder.getFunctionValue(17), - decoder.getFunctionValue(18), - decoder.getFunctionValue(19)}; - send(decoder.address, message); - } - else if(functionNumber == 20 || functionNumber == 28) - { - LocoF20F28 message{ - decoder.getFunctionValue(20), - decoder.getFunctionValue(28)}; - send(decoder.address, message); - } - else if(functionNumber <= 27) - { - LocoF21F27 message{ - decoder.getFunctionValue(21), - decoder.getFunctionValue(22), - decoder.getFunctionValue(23), - decoder.getFunctionValue(24), - decoder.getFunctionValue(25), - decoder.getFunctionValue(26), - decoder.getFunctionValue(27)}; - send(decoder.address, message); - } - } -} - -void LocoNet::queryLocoSlots() -{ - m_queryLocoSlots = SLOT_LOCO_MIN; - send(RequestSlotData(m_queryLocoSlots)); -} - -uint16_t LocoNet::getUnusedInputAddress() const -{ - const auto end = m_inputs.cend(); - for(uint16_t address = LocoNetInput::addressMin; address < LocoNetInput::addressMax; address++) - if(m_inputs.find(address) == end) - return address; - return LocoNetInput::addressInvalid; -} - -uint16_t LocoNet::getUnusedOutputAddress() const -{ - const auto end = m_outputs.cend(); - for(uint16_t address = LocoNetOutput::addressMin; address < LocoNetOutput::addressMax; address++) - if(m_outputs.find(address) == end) - return address; - return LocoNetOutput::addressInvalid; -} - -void LocoNet::loaded() -{ - SubObject::loaded(); - - m_debugLogRXTX = debugLogRXTX; -} - -std::shared_ptr LocoNet::getDecoder(uint8_t slot, bool request) -{ - if(slot < SLOT_LOCO_MIN || slot > SLOT_LOCO_MAX) - return nullptr; - - if(m_commandStation) - { - const uint16_t address = m_slots.getAddress(slot); - if(address != 0) - { - auto decoder = m_commandStation->getDecoder(DecoderProtocol::DCC, address, isLongAddress(address)); - if(!decoder) - decoder = m_commandStation->getDecoder(DecoderProtocol::Auto, address); - return decoder; - } - else if(request) - send(RequestSlotData(slot)); - } - - return nullptr; -} - -bool LocoNet::isInputAddressAvailable(uint16_t address) const -{ - return m_inputs.find(address) == m_inputs.end(); -} - -bool LocoNet::changeInputAddress(LocoNetInput& input, uint16_t newAddress) -{ - assert(input.loconet.value().get() == this); - - if(!isInputAddressAvailable(newAddress)) - return false; - - auto node = m_inputs.extract(input.address); // old address - node.key() = newAddress; - m_inputs.insert(std::move(node)); - inputMonitorIdChanged(input.address, {}); - inputMonitorIdChanged(newAddress, input.id.value()); - input.updateValue(TriState::Undefined); - - return true; -} - -bool LocoNet::addInput(LocoNetInput& input) -{ - if(isInputAddressAvailable(input.address)) - { - m_inputs.insert({input.address, input.shared_ptr()}); - inputMonitorIdChanged(input.address, input.id.value()); - input.updateValue(TriState::Undefined); - // TODO: request state! - return true; - } - else - return false; -} - -void LocoNet::removeInput(LocoNetInput& input) -{ - assert(input.loconet.value().get() == this); - const uint16_t address = input.address; - auto it = m_inputs.find(input.address); - if(it != m_inputs.end() && it->second.get() == &input) - m_inputs.erase(it); - input.updateValue(TriState::Undefined); - inputMonitorIdChanged(address, {}); -} - -void LocoNet::inputMonitorIdChanged(const uint32_t address, std::string_view value) -{ - for(auto* item : m_inputMonitors) - if(item->inputIdChanged) - item->inputIdChanged(*item, address, value); -} - -void LocoNet::inputMonitorValueChanged(const uint32_t address, const TriState value) -{ - for(auto* item : m_inputMonitors) - if(item->inputValueChanged) - item->inputValueChanged(*item, address, value); -} - -bool LocoNet::isOutputAddressAvailable(uint16_t address) const -{ - return m_outputs.find(address) == m_outputs.end(); -} - -bool LocoNet::changeOutputAddress(LocoNetOutput& output, uint16_t newAddress) -{ - assert(output.loconet.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)); - outputKeyboardIdChanged(output.address, {}); - outputKeyboardIdChanged(newAddress, output.id.value()); - output.value.setValueInternal(TriState::Undefined); - // TODO: request state! - - return true; -} - -bool LocoNet::addOutput(LocoNetOutput& output) -{ - if(isOutputAddressAvailable(output.address)) - { - m_outputs.insert({output.address, output.shared_ptr()}); - outputKeyboardIdChanged(output.address, output.id.value()); - output.value.setValueInternal(TriState::Undefined); - return true; - } - else - return false; -} - -void LocoNet::removeOutput(LocoNetOutput& output) -{ - assert(output.loconet.value().get() == this); - const uint16_t address = output.address; - 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); - outputKeyboardIdChanged(address, {}); -} - -void LocoNet::outputKeyboardIdChanged(const uint32_t address, std::string_view value) -{ - for(auto* item : m_outputKeyboards) - if(item->outputIdChanged) - item->outputIdChanged(*item, address, value); -} - -void LocoNet::outputKeyboardValueChanged(const uint32_t address, const TriState value) -{ - for(auto* item : m_outputKeyboards) - if(item->outputValueChanged) - item->outputValueChanged(*item, address, value); -} - -} diff --git a/server/src/hardware/protocol/loconet/loconet.hpp b/server/src/hardware/protocol/loconet/loconet.hpp deleted file mode 100644 index 4a671b3d..00000000 --- a/server/src/hardware/protocol/loconet/loconet.hpp +++ /dev/null @@ -1,171 +0,0 @@ -/** - * server/src/hardware/protocol/loconet.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. - */ - -/** - * Portions Copyright (C) Digitrax Inc. - * - * LocoNet is a registered trademark of DigiTrax, Inc. - */ - -#ifndef TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_LOCONET_LOCONET_HPP -#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_LOCONET_LOCONET_HPP - -#include -#include "../../../core/subobject.hpp" -#include "../../../core/property.hpp" -#include "../../../core/method.hpp" -#include -#include "../../../enum/loconetcommandstation.hpp" -//#include -//#include -#include "../../../hardware/decoder/decoderchangeflags.hpp" -#include "messages.hpp" -#include "../../input/monitor/loconetinputmonitor.hpp" -#include "../../output/keyboard/loconetoutputkeyboard.hpp" - -class CommandStation; -class Decoder; -class LocoNetInput; -class LocoNetInputMonitor; -class LocoNetOutput; - -namespace LocoNet { - -class LocoNet : public SubObject -{ - //friend class LocoNetInput; - friend class ::LocoNetInputMonitor; - friend class ::LocoNetOutputKeyboard; - - private: - struct Private - { - }; - - protected: - static constexpr bool isLongAddress(uint16_t address) - { - return address > 127; - } - - public: - - protected: - class Slots - { - private: - std::unordered_map m_addressToSlot; - std::unordered_map m_slotToAddress; - - public: - uint8_t getSlot(uint16_t address) const - { - auto it = m_addressToSlot.find(address); - return it != m_addressToSlot.end() ? it->second : SLOT_UNKNOWN; - } - - uint16_t getAddress(uint8_t slot) const - { - auto it = m_slotToAddress.find(slot); - return it != m_slotToAddress.end() ? it->second : 0; - } - - void set(uint16_t address, uint8_t slot) - { - m_addressToSlot[address] = slot; - m_slotToAddress[slot] = address; - } - - void clear() - { - m_addressToSlot.clear(); - m_slotToAddress.clear(); - } - }; - - CommandStation* const m_commandStation; // valid if parent is command station, else nullptr - std::function m_send; - std::atomic_bool m_debugLogRXTX; - Slots m_slots; - std::unordered_map> m_slotRequests; - uint8_t m_queryLocoSlots; - std::unordered_map> m_inputs; - std::vector m_inputMonitors; - std::unordered_map> m_outputs; - std::vector m_outputKeyboards; - - void loaded() override; - - std::shared_ptr getDecoder(uint8_t slot, bool request = true); - - void send(uint16_t address, Message& message, uint8_t& slot); - template - inline void send(uint16_t address, T& message) - { - send(address, message, message.slot); - } - - public://protected: - [[nodiscard]] bool isInputAddressAvailable(uint16_t address) const; - [[nodiscard]] bool changeInputAddress(LocoNetInput& input, uint16_t newAddress); - [[nodiscard]] bool addInput(LocoNetInput& input); - void removeInput(LocoNetInput& input); - void inputMonitorIdChanged(uint32_t address, std::string_view value); - void inputMonitorValueChanged(uint32_t address, TriState value); - - [[nodiscard]] bool isOutputAddressAvailable(uint16_t address) const; - [[nodiscard]] bool changeOutputAddress(LocoNetOutput& output, uint16_t newAddress); - [[nodiscard]] bool addOutput(LocoNetOutput& output); - void removeOutput(LocoNetOutput& output); - void outputKeyboardIdChanged(uint32_t address, std::string_view value); - void outputKeyboardValueChanged(uint32_t address, TriState value); - - public: - CLASS_ID("protocol.loconet") - - Property commandStation; - Property debugLogInput; - Property debugLogOutput; - Property debugLogRXTX; - Method()> inputMonitor; - Method()> outputKeyboard; - - static std::shared_ptr create(Object& _parent, const std::string& parentPropertyName, std::function send); - - LocoNet(Object& _parent, const std::string& parentPropertyName, std::function send, Private); - - bool send(const Message& message); - void receive(const Message& message); - - void emergencyStopChanged(bool value); - void powerOnChanged(bool value); - void decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber); - - void queryLocoSlots(); - - uint16_t getUnusedInputAddress() const; - uint16_t getUnusedOutputAddress() const; -}; - -} - -#endif diff --git a/server/src/hardware/protocol/loconet/loconetlisttablemodel.cpp b/server/src/hardware/protocol/loconet/loconetlisttablemodel.cpp deleted file mode 100644 index efb9f5d5..00000000 --- a/server/src/hardware/protocol/loconet/loconetlisttablemodel.cpp +++ /dev/null @@ -1,66 +0,0 @@ -/** - * server/src/hardware/input/loconetlisttablemodel.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 "loconetlisttablemodel.hpp" -#include "loconetlist.hpp" -#include "../../../utils/displayname.hpp" - -constexpr uint32_t columnId = 0; - -bool LocoNetListTableModel::isListedProperty(const std::string& name) -{ - return name == "id"; -} - -LocoNetListTableModel::LocoNetListTableModel(LocoNetList& list) : - ObjectListTableModel(list) -{ - setColumnHeaders({ - DisplayName::Object::id, - }); -} - -std::string LocoNetListTableModel::getText(uint32_t column, uint32_t row) const -{ - if(row < rowCount()) - { - const LocoNet::LocoNet& loconet = getItem(row); - - switch(column) - { - case columnId: - return loconet.getObjectId(); - - default: - assert(false); - break; - } - } - - return ""; -} - -void LocoNetListTableModel::propertyChanged(BaseProperty& property, uint32_t row) -{ - if(property.name() == "id") - changed(row, columnId); -} diff --git a/server/src/hardware/protocol/loconet/loconetlisttablemodel.hpp b/server/src/hardware/protocol/loconet/loconetlisttablemodel.hpp deleted file mode 100644 index 0da18bee..00000000 --- a/server/src/hardware/protocol/loconet/loconetlisttablemodel.hpp +++ /dev/null @@ -1,48 +0,0 @@ -/** - * server/src/hardware/protocol/loconet/loconetlisttablemodel.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_PROTOCOL_LOCONET_LOCONETLISTTABLEMODEL_HPP -#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_LOCONET_LOCONETLISTTABLEMODEL_HPP - -#include "../../../core/objectlisttablemodel.hpp" -#include "loconet.hpp" - -class LocoNetList; - -class LocoNetListTableModel : public ObjectListTableModel -{ - friend class LocoNetList; - - protected: - void propertyChanged(BaseProperty& property, uint32_t row) final; - - public: - CLASS_ID("loconet_list_table_model") - - static bool isListedProperty(const std::string& name); - - LocoNetListTableModel(LocoNetList& list); - - std::string getText(uint32_t column, uint32_t row) const final; -}; - -#endif diff --git a/server/src/hardware/protocol/loconet/messages.cpp b/server/src/hardware/protocol/loconet/messages.cpp index 7cfd7773..04eb671a 100644 --- a/server/src/hardware/protocol/loconet/messages.cpp +++ b/server/src/hardware/protocol/loconet/messages.cpp @@ -21,6 +21,7 @@ */ #include "messages.hpp" +#include "../../../utils/inrange.hpp" #include "../../../utils/tohex.hpp" namespace LocoNet { @@ -56,6 +57,133 @@ bool isValid(const Message& message) return true; } +bool isLocoSlot(uint8_t slot) +{ + return inRange(slot, SLOT_LOCO_MIN, SLOT_LOCO_MAX); +} + +void setSlot(Message& message, uint8_t slot) +{ + assert(slot < 128); + + switch(message.opCode) + { + case OPC_LOCO_SPD: + case OPC_LOCO_DIRF: + case OPC_LOCO_SND: + case OPC_LOCO_F9F12: + static_cast(message).slot = slot; + return; + + case OPC_RQ_SL_DATA: + static_cast(message).slot = slot; + return; + + case OPC_D4: + { + uint8_t* bytes = reinterpret_cast(&message); + if(bytes[1] == 0x20 && (bytes[3] == 0x08 || bytes[3] == 0x05 || bytes[3] == 0x09)) // LocoF13F19 or LocoF20F28 or LocoF21F27 + { + bytes[2] = slot; + return; + } + break; + } + case OPC_GPON: + case OPC_GPOFF: + case OPC_IDLE: + case OPC_BUSY: + case OPC_INPUT_REP: + case OPC_SW_REQ: + case OPC_MULTI_SENSE: + case OPC_MULTI_SENSE_LONG: + case OPC_SW_REP: + case OPC_LONG_ACK: + case OPC_SLOT_STAT1: + case OPC_CONSIST_FUNC: + case OPC_UNLINK_SLOTS: + case OPC_LINK_SLOTS: + case OPC_MOVE_SLOTS: + case OPC_SW_STATE: + case OPC_SW_ACK: + case OPC_LOCO_ADR: + case OPC_PEER_XFER: + case OPC_SL_RD_DATA: + case OPC_IMM_PACKET: + case OPC_WR_SL_DATA: + break; // no slot or not yet implemented + } + assert(false); +} + +bool isValidResponse(const Message& request, const Message& response) +{ + switch(request.opCode) + { + case OPC_UNLINK_SLOTS: + assert(false); //! @todo implement + return false; + + case OPC_LINK_SLOTS: + assert(false); //! @todo implement + return false; + + case OPC_MOVE_SLOTS: + assert(false); //! @todo implement + return false; + + case OPC_RQ_SL_DATA: + return + response.opCode == OPC_SL_RD_DATA && + static_cast(request).slot == static_cast(response).slot; + + case OPC_SW_STATE: + assert(false); //! @todo implement + return false; + + case OPC_SW_ACK: + assert(false); //! @todo implement + return false; + + case OPC_LOCO_ADR: + return + response.opCode == OPC_SL_RD_DATA && + static_cast(request).addressLow == static_cast(response).adr && + static_cast(request).addressHigh == static_cast(response).adr2; + + case OPC_IMM_PACKET: + assert(false); //! @todo implement + return false; + + case OPC_WR_SL_DATA: + assert(false); //! @todo implement + return false; + + case OPC_BUSY: + case OPC_GPOFF: + case OPC_GPON: + case OPC_IDLE: + case OPC_LOCO_SPD: + case OPC_LOCO_DIRF: + case OPC_LOCO_SND: + case OPC_LOCO_F9F12: + case OPC_SW_REQ: + case OPC_SW_REP: + case OPC_INPUT_REP: + case OPC_LONG_ACK: + case OPC_SLOT_STAT1: + case OPC_CONSIST_FUNC: + case OPC_MULTI_SENSE: + case OPC_D4: + case OPC_MULTI_SENSE_LONG: + case OPC_PEER_XFER: + case OPC_SL_RD_DATA: + assert(!hasResponse(request)); + break; + } + return false; +} + std::string toString(const Message& message, bool raw) { std::string s; diff --git a/server/src/hardware/protocol/loconet/messages.hpp b/server/src/hardware/protocol/loconet/messages.hpp index 14ccc0c8..e3394f36 100644 --- a/server/src/hardware/protocol/loconet/messages.hpp +++ b/server/src/hardware/protocol/loconet/messages.hpp @@ -29,6 +29,7 @@ #ifndef TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_LOCONET_MESSAGES_HPP #define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_LOCONET_MESSAGES_HPP +#include #include #include #include "opcode.hpp" @@ -41,8 +42,15 @@ uint8_t calcChecksum(const Message& msmessageg); void updateChecksum(Message& message); bool isChecksumValid(const Message& message); bool isValid(const Message& message); + +bool isLocoSlot(uint8_t slot); +void setSlot(Message& message, uint8_t slot); + +bool isValidResponse(const Message& request, const Message& response); + std::string toString(const Message& message, bool raw = false); +constexpr uint8_t SLOT_DISPATCH = 0; constexpr uint8_t SLOT_LOCO_MIN = 1; constexpr uint8_t SLOT_LOCO_MAX = 119; constexpr uint8_t SLOT_FAST_CLOCK = 123; @@ -534,6 +542,27 @@ struct InputRep : Message }; static_assert(sizeof(InputRep) == 4); +struct LongAck : Message +{ + uint8_t lpoc; + uint8_t ack1; + uint8_t checksum; + + LongAck() : + Message{OPC_LONG_ACK}, + lpoc{0}, + ack1{0}, + checksum{0} + { + } + + OpCode respondingOpCode() const + { + return static_cast(0x80 | lpoc); + } +}; +static_assert(sizeof(LongAck) == 4); + struct SwitchRequest : Message { uint8_t sw1; @@ -1037,10 +1066,20 @@ struct MultiSenseLongTransponder : MultiSenseLong static_assert(sizeof(MultiSenseLongTransponder) == 9); // OPC_SL_RD_DATA [E7 0E 1F 13 6F 01 30 07 08 19 00 00 00 52] -struct SlotReadData : Message +struct SlotReadDataBase : Message { uint8_t len; uint8_t slot; + + SlotReadDataBase() : + Message(OPC_SL_RD_DATA), + len{14} + { + } +}; + +struct SlotReadData : SlotReadDataBase +{ uint8_t stat; uint8_t adr; uint8_t spd; @@ -1053,12 +1092,6 @@ struct SlotReadData : Message uint8_t id2; uint8_t checksum; - SlotReadData() : - Message(OPC_SL_RD_DATA), - len{14} - { - } - bool isBusy() const { return stat & SL_BUSY; @@ -1215,6 +1248,23 @@ struct SlotReadData : Message } }; static_assert(sizeof(SlotReadData) == 14); + +struct FastClockSlotReadData : SlotReadDataBase +{ + uint8_t clk_rate; + uint8_t frac_minsl; + uint8_t frac_minsh; + uint8_t mins_60; + uint8_t trk; + uint8_t hrs_24; + uint8_t days; + uint8_t clk_cntrl; + uint8_t id1; + uint8_t id2; + uint8_t checksum; +}; +static_assert(sizeof(FastClockSlotReadData) == 14); + /* struct ImmediatePacket : Message { @@ -1363,6 +1413,16 @@ struct ImmediatePacketF21F28 : ImmediatePacketLoco static_assert(sizeof(ImmediatePacketF21F28) == sizeof(ImmediatePacketLoco)); */ +constexpr bool hasResponse(const Message& message) +{ + return (message.opCode & 0x08); +} + +} + +inline bool operator ==(const LocoNet::Message& lhs, const LocoNet::Message& rhs) +{ + return lhs.size() == rhs.size() && std::memcmp(&lhs, &rhs, lhs.size()) == 0; } #endif diff --git a/server/src/hardware/protocol/loconet/settings.cpp b/server/src/hardware/protocol/loconet/settings.cpp new file mode 100644 index 00000000..b38da2f4 --- /dev/null +++ b/server/src/hardware/protocol/loconet/settings.cpp @@ -0,0 +1,101 @@ +/** + * server/src/hardware/protocol/loconet/settings.cpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2019-2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "settings.hpp" +#include "../../../core/attributes.hpp" +#include "../../../utils/displayname.hpp" + +namespace LocoNet { + +Settings::Settings(Object& _parent, const std::string& parentPropertyName) + : SubObject(_parent, parentPropertyName) + , commandStation{this, "command_station", LocoNetCommandStation::Custom, PropertyFlags::ReadWrite | PropertyFlags::Store, + [this](LocoNetCommandStation value) + { + switch(value) + { + case LocoNetCommandStation::Custom: + break; + + case LocoNetCommandStation::DigikeijsDR5000: + break; + + case LocoNetCommandStation::UhlenbrockIntellibox: + break; + } + }} + , fastClockSyncEnabled{this, "fast_clock_sync_enabled", false, PropertyFlags::ReadWrite | PropertyFlags::Store, + [this](bool value) + { + Attributes::setEnabled(fastClockSyncInterval, value); + }} + , fastClockSyncInterval{this, "fast_clock_sync_interval", 60, PropertyFlags::ReadWrite | PropertyFlags::Store} + , debugLogInput{this, "debug_log_input", false, PropertyFlags::ReadWrite | PropertyFlags::Store} + , debugLogOutput{this, "debug_log_output", false, PropertyFlags::ReadWrite | PropertyFlags::Store} + , debugLogRXTX{this, "debug_log_rx_tx", false, PropertyFlags::ReadWrite | PropertyFlags::Store} +{ + Attributes::addDisplayName(commandStation, DisplayName::Hardware::commandStation); + Attributes::addValues(commandStation, LocoNetCommandStationValues); + m_interfaceItems.add(commandStation); + + //Attributes::addGroup(fastClockSyncEnabled, Group::fastClockSync); + m_interfaceItems.add(fastClockSyncEnabled); + + Attributes::addEnabled(fastClockSyncInterval, fastClockSyncEnabled); + //Attributes::addGroup(fastClockSyncInterval, Group::fastClockSync); + m_interfaceItems.add(fastClockSyncInterval); + + Attributes::addDisplayName(debugLogInput, DisplayName::Hardware::debugLogInput); + //Attributes::addGroup(debugLogInput, Group::debug); + m_interfaceItems.add(debugLogInput); + + Attributes::addDisplayName(debugLogOutput, DisplayName::Hardware::debugLogOutput); + //Attributes::addGroup(debugLogOuput, Group::debug); + m_interfaceItems.add(debugLogOutput); + + Attributes::addDisplayName(debugLogRXTX, DisplayName::Hardware::debugLogRXTX); + //Attributes::addGroup(debugLogRXTX, Group::debug); + m_interfaceItems.add(debugLogRXTX); +} + +Config Settings::config() const +{ + Config config; + + config.fastClockSyncEnabled = fastClockSyncEnabled; + config.fastClockSyncInterval = fastClockSyncInterval; + + config.debugLogInput = debugLogInput; + config.debugLogOutput = debugLogOutput; + config.debugLogRXTX = debugLogRXTX; + + return config; +} + +void Settings::loaded() +{ + SubObject::loaded(); + + Attributes::setEnabled(fastClockSyncInterval, fastClockSyncEnabled); +} + +} diff --git a/server/src/hardware/output/keyboard/loconetoutputkeyboard.hpp b/server/src/hardware/protocol/loconet/settings.hpp similarity index 54% rename from server/src/hardware/output/keyboard/loconetoutputkeyboard.hpp rename to server/src/hardware/protocol/loconet/settings.hpp index 5b699fbe..07b6c748 100644 --- a/server/src/hardware/output/keyboard/loconetoutputkeyboard.hpp +++ b/server/src/hardware/protocol/loconet/settings.hpp @@ -1,5 +1,5 @@ /** - * server/src/hardware/output/keyboard/loconetoutputkeyboard.hpp + * server/src/hardware/protocol/loconet/settings.hpp * * This file is part of the traintastic source code. * @@ -20,30 +20,35 @@ * 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_PROTOCOL_LOCONET_SETTINGS_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_LOCONET_SETTINGS_HPP -#include "outputkeyboard.hpp" +#include "../../../core/subobject.hpp" +#include "../../../core/property.hpp" +#include "config.hpp" namespace LocoNet { - class LocoNet; -} -class LocoNetOutputKeyboard final : public OutputKeyboard +class Settings final : public SubObject { protected: - std::shared_ptr m_loconet; + void loaded() final; public: - CLASS_ID("output_keyboard.loconet") + CLASS_ID("loconet_settings") - LocoNetOutputKeyboard(std::shared_ptr loconet); - ~LocoNetOutputKeyboard() final; + Property commandStation; + Property fastClockSyncEnabled; + Property fastClockSyncInterval; //!< Fast clock sync interval in seconds + Property debugLogInput; + Property debugLogOutput; + Property debugLogRXTX; - std::string getObjectId() const final { return ""; } + Settings(Object& _parent, const std::string& parentPropertyName); - std::vector getOutputInfo() const final; - void setOutputValue(uint32_t address, bool value) final; + Config config() const; }; +} + #endif diff --git a/server/src/hardware/input/inputs.cpp b/server/src/hardware/protocol/xpressnet/config.hpp similarity index 66% rename from server/src/hardware/input/inputs.cpp rename to server/src/hardware/protocol/xpressnet/config.hpp index 7dc73088..09533b13 100644 --- a/server/src/hardware/input/inputs.cpp +++ b/server/src/hardware/protocol/xpressnet/config.hpp @@ -1,9 +1,9 @@ /** - * server/src/hardware/input/inputs.cpp + * server/src/hardware/protocol/xpressnet/config.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,13 +20,20 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "inputs.hpp" -#include "../../utils/ifclassidcreate.hpp" -#include "../../world/world.hpp" +#ifndef TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_XPRESSNET_CONFIG_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_XPRESSNET_CONFIG_HPP -std::shared_ptr Inputs::create(const std::shared_ptr& world, std::string_view classId, std::string_view id) +namespace XpressNet { + +struct Config { - IF_CLASSID_CREATE(LocoNetInput) - IF_CLASSID_CREATE(XpressNetInput) - return std::shared_ptr(); + bool useEmergencyStopLocomotiveCommand; + bool useRocoF13F20Command; + + bool debugLogInput; + bool debugLogRXTX; +}; + } + +#endif diff --git a/server/src/hardware/protocol/xpressnet/iohandler/iohandler.cpp b/server/src/hardware/protocol/xpressnet/iohandler/iohandler.cpp new file mode 100644 index 00000000..e7d71434 --- /dev/null +++ b/server/src/hardware/protocol/xpressnet/iohandler/iohandler.cpp @@ -0,0 +1,130 @@ +/** + * server/src/hardware/protocol/xpressnet/iohandler/iohandler.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 "iohandler.hpp" +#include "../kernel.hpp" +#include "../messages.hpp" +#include "../../../../core/eventloop.hpp" +#include "../../../../log/log.hpp" + +namespace XpressNet { + +IOHandler::IOHandler(Kernel& kernel) + : m_kernel{kernel} + , m_readBufferOffset{0} + , m_writeBufferOffset{0} + , m_extraHeader{false} +{ +} + +bool IOHandler::send(const Message& message) +{ + if(m_writeBufferOffset + message.size() > m_writeBuffer.size()) + return false; + + const bool wasEmpty = m_writeBufferOffset == 0; + + if(m_extraHeader) + { + m_writeBuffer[m_writeBufferOffset++] = std::byte{0xFF}; + m_writeBuffer[m_writeBufferOffset++] = std::byte{0xFE}; + } + + memcpy(m_writeBuffer.data() + m_writeBufferOffset, &message, message.size()); + m_writeBufferOffset += message.size(); + + if(wasEmpty) + write(); + + return true; +} + +void IOHandler::processRead(size_t bytesTransferred) +{ + const std::byte* pos = m_readBuffer.data(); + bytesTransferred += m_readBufferOffset; + + while(bytesTransferred > (m_extraHeader ? 3 : 1)) + { + const Message* message = nullptr; + size_t drop = 0; + + if(m_extraHeader) // each message is prepended by [FF FE] or [FF FD] + { + while(drop < bytesTransferred - 2) + { + message = reinterpret_cast(pos + 2); + if(pos[0] == std::byte{0xFF} && + (pos[1] == std::byte{0xFE} || pos[1] == std::byte{0xFD}) && + message->size() <= bytesTransferred && + isChecksumValid(*message)) + { + pos += 2; + bytesTransferred -= 2; + break; + } + + drop++; + pos++; + bytesTransferred--; + } + } + else + { + while(drop < bytesTransferred) + { + message = reinterpret_cast(pos); + if(message->size() <= bytesTransferred && isChecksumValid(*message)) + break; + + drop++; + pos++; + bytesTransferred--; + } + } + + if(drop != 0) + { + EventLoop::call( + [this, drop]() + { + Log::log(m_kernel.logId(), LogMessage::W2001_RECEIVED_MALFORMED_DATA_DROPPED_X_BYTES, drop); + }); + } + + assert(message); + if(message->size() <= bytesTransferred) + { + m_kernel.receive(*message); + pos += message->size(); + bytesTransferred -= message->size(); + } + else + break; + } + + if(bytesTransferred != 0) + memmove(m_readBuffer.data(), pos, bytesTransferred); + m_readBufferOffset = bytesTransferred; +} + +} diff --git a/server/src/hardware/protocol/xpressnet/iohandler/iohandler.hpp b/server/src/hardware/protocol/xpressnet/iohandler/iohandler.hpp new file mode 100644 index 00000000..6e1b0318 --- /dev/null +++ b/server/src/hardware/protocol/xpressnet/iohandler/iohandler.hpp @@ -0,0 +1,63 @@ +/** + * server/src/hardware/protocol/xpressnet/iohandler/iohandler.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_XPRESSNET_IOHANDLER_IOHANDLER_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_XPRESSNET_IOHANDLER_IOHANDLER_HPP + +#include +#include + +namespace XpressNet { + +class Kernel; +struct Message; + +class IOHandler +{ + protected: + Kernel& m_kernel; + std::array m_readBuffer; + size_t m_readBufferOffset; + std::array m_writeBuffer; + size_t m_writeBufferOffset; + bool m_extraHeader; //!< every message is prepended by [FF FD] or [FF FE] + + IOHandler(Kernel& kernel); + + void processRead(size_t bytesTransferred); + virtual void write() = 0; + + public: + IOHandler(const IOHandler&) = delete; + IOHandler& operator =(const IOHandler&) = delete; + + virtual ~IOHandler() = default; + + virtual void start() = 0; + virtual void stop() = 0; + + bool send(const Message& message); +}; + +} + +#endif diff --git a/server/src/hardware/protocol/xpressnet/iohandler/liusbiohandler.hpp b/server/src/hardware/protocol/xpressnet/iohandler/liusbiohandler.hpp new file mode 100644 index 00000000..63f375c2 --- /dev/null +++ b/server/src/hardware/protocol/xpressnet/iohandler/liusbiohandler.hpp @@ -0,0 +1,38 @@ +/** + * server/src/hardware/protocol/xpressnet/iohandler/liusbiohandler.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_XPRESSNET_IOHANDLER_LIUSBIOHANDLER_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_XPRESSNET_IOHANDLER_LIUSBIOHANDLER_HPP + +#include "serialiohandler.hpp" + +namespace XpressNet { + +class LIUSBIOHandler final : public SerialIOHandler +{ + public: + LIUSBIOHandler(Kernel& kernel, const std::string& device, uint32_t baudrate, SerialFlowControl flowControl); +}; + +} + +#endif diff --git a/server/src/hardware/protocol/xpressnet/iohandler/luisbiohandler.cpp b/server/src/hardware/protocol/xpressnet/iohandler/luisbiohandler.cpp new file mode 100644 index 00000000..5e04131d --- /dev/null +++ b/server/src/hardware/protocol/xpressnet/iohandler/luisbiohandler.cpp @@ -0,0 +1,33 @@ +/** + * server/src/hardware/protocol/xpressnet/iohandler/luisbiohandler.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 "liusbiohandler.hpp" + +namespace XpressNet { + +LIUSBIOHandler::LIUSBIOHandler(Kernel& kernel, const std::string& device, uint32_t baudrate, SerialFlowControl flowControl) + : SerialIOHandler(kernel, device, baudrate, flowControl) +{ + m_extraHeader = true; +} + +} diff --git a/server/src/hardware/protocol/xpressnet/iohandler/rosofts88xpressnetliiohandler.cpp b/server/src/hardware/protocol/xpressnet/iohandler/rosofts88xpressnetliiohandler.cpp new file mode 100644 index 00000000..541f37f9 --- /dev/null +++ b/server/src/hardware/protocol/xpressnet/iohandler/rosofts88xpressnetliiohandler.cpp @@ -0,0 +1,43 @@ +/** + * server/src/hardware/protocol/xpressnet/iohandler/rosofts88xpressnetliiohandler.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 "rosofts88xpressnetliiohandler.hpp" +#include "../messages.hpp" + +namespace XpressNet { + +RoSoftS88XPressNetLIIOHandler::RoSoftS88XPressNetLIIOHandler(Kernel& kernel, const std::string& device, uint32_t baudrate, SerialFlowControl flowControl, uint8_t s88StartAddress, uint8_t s88ModuleCount) + : SerialIOHandler(kernel, device, baudrate, flowControl) + , m_s88StartAddress{s88StartAddress} + , m_s88ModuleCount{s88ModuleCount} +{ +} + +void RoSoftS88XPressNetLIIOHandler::start() +{ + SerialIOHandler::start(); + + send(RoSoftS88XpressNetLI::S88StartAddress(m_s88StartAddress)); + send(RoSoftS88XpressNetLI::S88ModuleCount(m_s88ModuleCount)); +} + +} diff --git a/server/src/hardware/protocol/xpressnet/iohandler/rosofts88xpressnetliiohandler.hpp b/server/src/hardware/protocol/xpressnet/iohandler/rosofts88xpressnetliiohandler.hpp new file mode 100644 index 00000000..3d80eea4 --- /dev/null +++ b/server/src/hardware/protocol/xpressnet/iohandler/rosofts88xpressnetliiohandler.hpp @@ -0,0 +1,44 @@ +/** + * server/src/hardware/protocol/xpressnet/iohandler/rosofts88xpressnetliiohandler.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_XPRESSNET_IOHANDLER_ROSOFTS88XPRESSNETLIIOHANDLER_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_XPRESSNET_IOHANDLER_ROSOFTS88XPRESSNETLIIOHANDLER_HPP + +#include "serialiohandler.hpp" + +namespace XpressNet { + +class RoSoftS88XPressNetLIIOHandler final : public SerialIOHandler +{ + private: + const uint8_t m_s88StartAddress; + const uint8_t m_s88ModuleCount; + + public: + RoSoftS88XPressNetLIIOHandler(Kernel& kernel, const std::string& device, uint32_t baudrate, SerialFlowControl flowControl, uint8_t s88StartAddress, uint8_t s88ModuleCount); + + void start() final; +}; + +} + +#endif diff --git a/server/src/hardware/protocol/xpressnet/iohandler/serialiohandler.cpp b/server/src/hardware/protocol/xpressnet/iohandler/serialiohandler.cpp new file mode 100644 index 00000000..aea3124c --- /dev/null +++ b/server/src/hardware/protocol/xpressnet/iohandler/serialiohandler.cpp @@ -0,0 +1,105 @@ +/** + * server/src/hardware/protocol/xpressnet/iohandler/serialiohandler.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 "serialiohandler.hpp" +#include "../kernel.hpp" +#include "../messages.hpp" +#include "../../../../core/eventloop.hpp" +#include "../../../../log/log.hpp" +#include "../../../../utils/serialport.hpp" + +namespace XpressNet { + +SerialIOHandler::SerialIOHandler(Kernel& kernel, const std::string& device, uint32_t baudrate, SerialFlowControl flowControl) + : IOHandler(kernel) + , m_serialPort{m_kernel.ioContext()} +{ + SerialPort::open(m_serialPort, device, baudrate, 8, SerialParity::None, SerialStopBits::One, flowControl); +} + +SerialIOHandler::~SerialIOHandler() +{ + if(m_serialPort.is_open()) + m_serialPort.close(); +} + +void SerialIOHandler::start() +{ + read(); +} + +void SerialIOHandler::stop() +{ + m_serialPort.close(); +} + +void SerialIOHandler::read() +{ + m_serialPort.async_read_some(boost::asio::buffer(m_readBuffer.data() + m_readBufferOffset, m_readBuffer.size() - m_readBufferOffset), + [this](const boost::system::error_code& ec, std::size_t bytesTransferred) + { + if(!ec) + { + processRead(bytesTransferred); + read(); + } + else if(ec != boost::asio::error::operation_aborted) + { + EventLoop::call( + [this, ec]() + { + Log::log(m_kernel.logId(), LogMessage::E2002_SERIAL_READ_FAILED_X, ec); + // TODO interface status -> error + }); + } + }); +} + +void SerialIOHandler::write() +{ + m_serialPort.async_write_some(boost::asio::buffer(m_writeBuffer.data(), m_writeBufferOffset), + [this](const boost::system::error_code& ec, std::size_t bytesTransferred) + { + if(!ec) + { + if(bytesTransferred < m_writeBufferOffset) + { + m_writeBufferOffset -= bytesTransferred; + memmove(m_writeBuffer.data(), m_writeBuffer.data() + bytesTransferred, m_writeBufferOffset); + write(); + } + else + m_writeBufferOffset = 0; + } + else if(ec != boost::asio::error::operation_aborted) + { + EventLoop::call( + [this, ec]() + { + Log::log(m_kernel.logId(), LogMessage::E2001_SERIAL_WRITE_FAILED_X, ec); + // TODO interface status -> error + }); + } + }); +} + +} diff --git a/server/src/hardware/protocol/xpressnet/iohandler/serialiohandler.hpp b/server/src/hardware/protocol/xpressnet/iohandler/serialiohandler.hpp new file mode 100644 index 00000000..cfa0dcf8 --- /dev/null +++ b/server/src/hardware/protocol/xpressnet/iohandler/serialiohandler.hpp @@ -0,0 +1,50 @@ +/** + * server/src/hardware/protocol/xpressnet/iohandler/serialiohandler.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_XPRESSNET_IOHANDLER_SERIALIOHANDLER_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_XPRESSNET_IOHANDLER_SERIALIOHANDLER_HPP + +#include "iohandler.hpp" +#include +#include "../../../../enum/serialflowcontrol.hpp" + +namespace XpressNet { + +class SerialIOHandler : public IOHandler +{ + private: + boost::asio::serial_port m_serialPort; + + void read(); + void write() final; + + public: + SerialIOHandler(Kernel& kernel, const std::string& device, uint32_t baudrate, SerialFlowControl flowControl); + ~SerialIOHandler() override; + + void start() override; + void stop() final; +}; + +} + +#endif diff --git a/server/src/hardware/protocol/xpressnet/iohandler/tcpiohandler.cpp b/server/src/hardware/protocol/xpressnet/iohandler/tcpiohandler.cpp new file mode 100644 index 00000000..205dfbcc --- /dev/null +++ b/server/src/hardware/protocol/xpressnet/iohandler/tcpiohandler.cpp @@ -0,0 +1,116 @@ +/** + * server/src/hardware/protocol/xpressnet/iohandler/tcpiohandler.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 "tcpiohandler.hpp" +#include +#include "../kernel.hpp" +#include "../messages.hpp" +#include "../../../../core/eventloop.hpp" +#include "../../../../log/log.hpp" +#include "../../../../log/logmessageexception.hpp" + +namespace XpressNet { + +TCPIOHandler::TCPIOHandler(Kernel& kernel, const std::string& hostname, uint16_t port) + : IOHandler(kernel) + , m_socket{m_kernel.ioContext()} +{ + m_extraHeader = true; + + boost::system::error_code ec; + + m_endpoint.port(port); + m_endpoint.address(boost::asio::ip::make_address(hostname, ec)); + if(ec) + throw LogMessageException(LogMessage::E2003_MAKE_ADDRESS_FAILED_X, ec); + + m_socket.connect(m_endpoint, ec); + if(ec) + throw LogMessageException(LogMessage::E2005_SOCKET_CONNECT_FAILED_X, ec); + + m_socket.set_option(boost::asio::socket_base::linger(true, 0)); + m_socket.set_option(boost::asio::ip::tcp::no_delay(true)); +} + +TCPIOHandler::~TCPIOHandler() +{ +} + +void TCPIOHandler::start() +{ + read(); +} + +void TCPIOHandler::stop() +{ +} + +void TCPIOHandler::read() +{ + m_socket.async_read_some(boost::asio::buffer(m_readBuffer.data() + m_readBufferOffset, m_readBuffer.size() - m_readBufferOffset), + [this](const boost::system::error_code& ec, std::size_t bytesTransferred) + { + if(!ec) + { + processRead(bytesTransferred); + read(); + } + else if(ec != boost::asio::error::operation_aborted) + { + EventLoop::call( + [this, ec]() + { + Log::log(m_kernel.logId(), LogMessage::E1007_SOCKET_READ_FAILED_X, ec); + // TODO interface status -> error + }); + } + }); +} + +void TCPIOHandler::write() +{ + m_socket.async_write_some(boost::asio::buffer(m_writeBuffer.data(), m_writeBufferOffset), + [this](const boost::system::error_code& ec, std::size_t bytesTransferred) + { + if(!ec) + { + m_writeBufferOffset -= bytesTransferred; + + if(m_writeBufferOffset > 0) + { + memmove(m_writeBuffer.data(), m_writeBuffer.data() + bytesTransferred, m_writeBufferOffset); + write(); + } + } + else if(ec != boost::asio::error::operation_aborted) + { + EventLoop::call( + [this, ec]() + { + Log::log(m_kernel.logId(), LogMessage::E1006_SOCKET_WRITE_FAILED_X, ec); + // TODO interface status -> error + }); + } + }); +} + +} diff --git a/server/src/hardware/input/monitor/xpressnetinputmonitor.hpp b/server/src/hardware/protocol/xpressnet/iohandler/tcpiohandler.hpp similarity index 57% rename from server/src/hardware/input/monitor/xpressnetinputmonitor.hpp rename to server/src/hardware/protocol/xpressnet/iohandler/tcpiohandler.hpp index fbf8e773..38c6b603 100644 --- a/server/src/hardware/input/monitor/xpressnetinputmonitor.hpp +++ b/server/src/hardware/protocol/xpressnet/iohandler/tcpiohandler.hpp @@ -1,5 +1,5 @@ /** - * server/src/hardware/input/monitor/xpressnetinputmonitor.hpp + * server/src/hardware/protocol/xpressnet/iohandler/tcpiohandler.hpp * * This file is part of the traintastic source code. * @@ -20,29 +20,32 @@ * 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_HARDWARE_PROTOCOL_XPRESSNET_IOHANDLER_TCPIOHANDLER_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_XPRESSNET_IOHANDLER_TCPIOHANDLER_HPP -#include "inputmonitor.hpp" +#include "iohandler.hpp" +#include namespace XpressNet { - class XpressNet; -} -class XpressNetInputMonitor final : public InputMonitor +class TCPIOHandler : public IOHandler { - protected: - std::shared_ptr m_xpressnet; + private: + boost::asio::ip::tcp::socket m_socket; + boost::asio::ip::tcp::endpoint m_endpoint; + + void read(); + void write() final; public: - CLASS_ID("input_monitor.xpressnet") + TCPIOHandler(Kernel& kernel, const std::string& hostname, uint16_t port); + ~TCPIOHandler() final; - XpressNetInputMonitor(std::shared_ptr xpressnet); - ~XpressNetInputMonitor() final; - - std::string getObjectId() const final { return ""; } - - std::vector getInputInfo() const final; + void start() final; + void stop() final; }; +} + #endif + diff --git a/server/src/hardware/protocol/xpressnet/kernel.cpp b/server/src/hardware/protocol/xpressnet/kernel.cpp new file mode 100644 index 00000000..ec34c8ac --- /dev/null +++ b/server/src/hardware/protocol/xpressnet/kernel.cpp @@ -0,0 +1,411 @@ +/** + * server/src/hardware/protocol/xpressnet/kernel.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 "kernel.hpp" +#include "messages.hpp" +#include "../../decoder/decoder.hpp" +#include "../../decoder/decoderchangeflags.hpp" +#include "../../input/inputcontroller.hpp" +#include "../../../utils/setthreadname.hpp" +#include "../../../core/eventloop.hpp" +#include "../../../log/log.hpp" + +namespace XpressNet { + +Kernel::Kernel(const Config& config) + : m_ioContext{1} + , m_decoderController{nullptr} + , m_inputController{nullptr} + , m_outputController{nullptr} + , m_config{config} +#ifndef NDEBUG + , m_started{false} +#endif +{ +} + +void Kernel::setConfig(const Config& config) +{ + m_ioContext.post( + [this, newConfig=config]() + { + m_config = newConfig; + }); +} + +void Kernel::start() +{ + assert(m_ioHandler); + assert(!m_started); + + // reset all state values + m_trackPowerOn = TriState::Undefined; + m_emergencyStop = TriState::Undefined; + m_inputValues.fill(TriState::Undefined); + + m_thread = std::thread( + [this]() + { + setThreadName("xpressnet"); + auto work = std::make_shared(m_ioContext); + m_ioContext.run(); + }); + + m_ioContext.post( + [this]() + { + m_ioHandler->start(); + + if(m_onStarted) + EventLoop::call( + [this]() + { + m_onStarted(); + }); + }); + +#ifndef NDEBUG + m_started = true; +#endif +} + +void Kernel::stop() +{ + m_ioContext.post( + [this]() + { + m_ioHandler->stop(); + }); + + m_ioContext.stop(); + + m_thread.join(); + +#ifndef NDEBUG + m_started = false; +#endif +} + +void Kernel::receive(const Message& message) +{ + if(m_config.debugLogRXTX) + EventLoop::call( + [this, msg=toString(message)]() + { + Log::log(m_logId, LogMessage::D2002_RX_X, msg); + }); + + switch(message.identification()) + { + case idFeedbackBroadcast: + { + const FeedbackBroadcast* feedback = static_cast(&message); + + for(uint8_t i = 0; i < feedback->pairCount(); i++) + { + const FeedbackBroadcast::Pair& pair = feedback->pair(i); + switch(pair.type()) + { + case FeedbackBroadcast::Pair::Type::AccessoryDecoderWithoutFeedback: + break; // not yet implemented + + case FeedbackBroadcast::Pair::Type::AccessoryDecoderWithFeedback: + break; // not yet implemented + + case FeedbackBroadcast::Pair::Type::FeedbackModule: + if(m_inputController) + { + const uint16_t baseAddress = pair.groupAddress() << 2; + + for(uint16_t j = 0; j < 4; j++) + { + const uint16_t fullAddress = baseAddress + j; + const TriState value = toTriState((pair.statusNibble() & (1 << j)) != 0); + if(m_inputValues[fullAddress] != value) + { + if(m_config.debugLogInput) + EventLoop::call( + [this, address=1 + fullAddress, value]() + { + Log::log(m_logId, LogMessage::D2007_INPUT_X_IS_X, address, value == TriState::True ? std::string_view{"1"} : std::string_view{"0"}); + }); + + m_inputValues[fullAddress] = value; + + EventLoop::call( + [this, address=1 + fullAddress, value]() + { + m_inputController->updateInputValue(address, value); + }); + } + } + } + break; + + case FeedbackBroadcast::Pair::Type::ReservedForFutureUse: + break; + } + } + + break; + } + case 0x60: + if(message == NormalOperationResumed()) + { + if(m_trackPowerOn != TriState::True || m_emergencyStop != TriState::False) + { + m_trackPowerOn = TriState::True; + m_emergencyStop = TriState::False; + + if(m_onNormalOperationResumed) + EventLoop::call( + [this]() + { + m_onNormalOperationResumed(); + }); + } + } + else if(message == TrackPowerOff()) + { + if(m_trackPowerOn != TriState::False) + { + m_trackPowerOn = TriState::False; + + if(m_onTrackPowerOff) + EventLoop::call( + [this]() + { + m_onTrackPowerOff(); + }); + } + } + break; + + case 0x80: + if(message == EmergencyStop()) + { + if(m_emergencyStop != TriState::True) + { + m_emergencyStop = TriState::True; + + if(m_onEmergencyStop) + EventLoop::call( + [this]() + { + m_onEmergencyStop(); + }); + } + } + break; + } +} + +void Kernel::normalOperationsResumed() +{ + m_ioContext.post( + [this]() + { + if(m_trackPowerOn != TriState::True || m_emergencyStop != TriState::False) + send(NormalOperationResumed()); + }); +} + +void Kernel::trackPowerOff() +{ + m_ioContext.post( + [this]() + { + if(m_trackPowerOn != TriState::False) + send(TrackPowerOff()); + }); +} + +void Kernel::emergencyStop() +{ + m_ioContext.post( + [this]() + { + if(m_emergencyStop != TriState::True) + send(EmergencyStop()); + }); +} + +void Kernel::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber) +{ + if(m_config.useEmergencyStopLocomotiveCommand && changes == DecoderChangeFlags::EmergencyStop && decoder.emergencyStop) + { + postSend(EmergencyStopLocomotive(decoder.address, decoder.longAddress)); + } + else if(has(changes, DecoderChangeFlags::EmergencyStop | DecoderChangeFlags::Direction | DecoderChangeFlags::Throttle | DecoderChangeFlags::SpeedSteps)) + { + switch(decoder.speedSteps) + { + case 14: + postSend(SpeedAndDirectionInstruction14( + decoder.address, + decoder.longAddress, + decoder.emergencyStop, + decoder.direction, + Decoder::throttleToSpeedStep(decoder.throttle, 14), + decoder.getFunctionValue(0))); + break; + + case 27: + postSend(SpeedAndDirectionInstruction27( + decoder.address, + decoder.longAddress, + decoder.emergencyStop, + decoder.direction, + Decoder::throttleToSpeedStep(decoder.throttle, 27))); + break; + + case 28: + postSend(SpeedAndDirectionInstruction28( + decoder.address, + decoder.longAddress, + decoder.emergencyStop, + decoder.direction, + Decoder::throttleToSpeedStep(decoder.throttle, 28))); + break; + + case 126: + case 128: + default: + postSend(SpeedAndDirectionInstruction128( + decoder.address, + decoder.longAddress, + decoder.emergencyStop, + decoder.direction, + Decoder::throttleToSpeedStep(decoder.throttle, 126))); + break; + } + } + else if(has(changes, DecoderChangeFlags::FunctionValue)) + { + if(functionNumber <= 4) + { + postSend(FunctionInstructionGroup1( + decoder.address, + decoder.longAddress, + decoder.getFunctionValue(0), + decoder.getFunctionValue(1), + decoder.getFunctionValue(2), + decoder.getFunctionValue(3), + decoder.getFunctionValue(4))); + } + else if(functionNumber <= 8) + { + postSend(FunctionInstructionGroup2( + decoder.address, + decoder.longAddress, + decoder.getFunctionValue(5), + decoder.getFunctionValue(6), + decoder.getFunctionValue(7), + decoder.getFunctionValue(8))); + } + else if(functionNumber <= 12) + { + postSend(FunctionInstructionGroup3( + decoder.address, + decoder.longAddress, + decoder.getFunctionValue(9), + decoder.getFunctionValue(10), + decoder.getFunctionValue(11), + decoder.getFunctionValue(12))); + } + else if(functionNumber <= 20) + { + if(m_config.useRocoF13F20Command) + { + postSend(RocoMultiMAUS::FunctionInstructionF13F20( + decoder.address, + decoder.longAddress, + decoder.getFunctionValue(13), + decoder.getFunctionValue(14), + decoder.getFunctionValue(15), + decoder.getFunctionValue(16), + decoder.getFunctionValue(17), + decoder.getFunctionValue(18), + decoder.getFunctionValue(19), + decoder.getFunctionValue(20))); + } + else + { + postSend(FunctionInstructionGroup4( + decoder.address, + decoder.longAddress, + decoder.getFunctionValue(13), + decoder.getFunctionValue(14), + decoder.getFunctionValue(15), + decoder.getFunctionValue(16), + decoder.getFunctionValue(17), + decoder.getFunctionValue(18), + decoder.getFunctionValue(19), + decoder.getFunctionValue(20))); + } + } + else if(functionNumber <= 28) + { + postSend(FunctionInstructionGroup5( + decoder.address, + decoder.longAddress, + decoder.getFunctionValue(21), + decoder.getFunctionValue(22), + decoder.getFunctionValue(23), + decoder.getFunctionValue(24), + decoder.getFunctionValue(25), + decoder.getFunctionValue(26), + decoder.getFunctionValue(27), + decoder.getFunctionValue(28))); + } + } +} + +bool Kernel::setOutput(uint16_t address, bool value) +{ + postSend(AccessoryDecoderOperationRequest(address - 1, value)); + return true; +} + +void Kernel::setIOHandler(std::unique_ptr handler) +{ + assert(handler); + assert(!m_ioHandler); + m_ioHandler = std::move(handler); +} + +void Kernel::send(const Message& message) +{ + if(m_ioHandler->send(message)) + { + if(m_config.debugLogRXTX) + EventLoop::call( + [this, msg=toString(message)]() + { + Log::log(m_logId, LogMessage::D2001_TX_X, msg); + }); + } + else + {} // log message and go to error state +} + +} diff --git a/server/src/hardware/protocol/xpressnet/kernel.hpp b/server/src/hardware/protocol/xpressnet/kernel.hpp new file mode 100644 index 00000000..09bbace9 --- /dev/null +++ b/server/src/hardware/protocol/xpressnet/kernel.hpp @@ -0,0 +1,293 @@ +/** + * server/src/hardware/protocol/xpressnet/kernel.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_PROTOCOL_XPRESSNET_KERNEL_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_XPRESSNET_KERNEL_HPP + +#include +#include +#include +#include +#include +#include "config.hpp" +#include "iohandler/iohandler.hpp" + +class Decoder; +enum class DecoderChangeFlags; +class DecoderController; +class InputController; +class OutputController; + +namespace XpressNet { + +struct Message; + +class Kernel +{ + private: + boost::asio::io_context m_ioContext; + std::unique_ptr m_ioHandler; + std::thread m_thread; + std::string m_logId; + std::function m_onStarted; + + TriState m_trackPowerOn; + TriState m_emergencyStop; + std::function m_onNormalOperationResumed; + std::function m_onTrackPowerOff; + std::function m_onEmergencyStop; + + DecoderController* m_decoderController; + + InputController* m_inputController; + std::array m_inputValues; + + OutputController* m_outputController; + //std::array m_outputValues; + + Config m_config; +#ifndef NDEBUG + bool m_started; +#endif + + Kernel(const Config& config); + + void setIOHandler(std::unique_ptr handler); + + template + void postSend(const T& message) + { + m_ioContext.post( + [this, message]() + { + send(message); + }); + } + + void send(const Message& message); + + public: + static constexpr uint16_t ioAddressMin = 1; + static constexpr uint16_t ioAddressMax = 2048; + + Kernel(const Kernel&) = delete; + Kernel& operator =(const Kernel&) = delete; + + /** + * @brief IO context for XpressNet kernel and IO handler + * + * @return The IO context + */ + boost::asio::io_context& ioContext() { return m_ioContext; } + + /** + * @brief Create kernel and IO handler + * + * @param[in] config XpressNet configuration + * @param[in] args IO handler arguments + * @return The kernel instance + */ + template + static std::unique_ptr create(const Config& config, Args... args) + { + static_assert(std::is_base_of_v); + std::unique_ptr kernel{new Kernel(config)}; + kernel->setIOHandler(std::make_unique(*kernel, std::forward(args)...)); + return kernel; + } + + /** + * @brief Access the IO handler + * + * @return The IO handler + * @note The IO handler runs in the kernel's IO context, not all functions can be called safely! + */ + template + T& ioHandler() + { + assert(dynamic_cast(m_ioHandler.get())); + return static_cast(*m_ioHandler); + } + + /** + * + * + */ + inline const std::string& logId() { return m_logId; } + + /** + * @brief Set object id used for log messages + * + * @param[in] value The object id + */ + inline void setLogId(std::string value) + { + m_logId = std::move(value); + } + + /** + * @brief Set XpressNet configuration + * + * @param[in] config The XpressNet configuration + */ + void setConfig(const Config& config); + + /** + * @brief ... + * + * @param[in] callback ... + * @note This function may not be called when the kernel is running. + */ + inline void setOnStarted(std::function callback) + { + assert(!m_started); + m_onStarted = std::move(callback); + } + + /** + * @brief ... + * + * @param[in] callback ... + * @note This function may not be called when the kernel is running. + */ + inline void setOnNormalOperationResumed(std::function callback) + { + assert(!m_started); + m_onNormalOperationResumed = std::move(callback); + } + + /** + * @brief ... + * + * @param[in] callback ... + * @note This function may not be called when the kernel is running. + */ + inline void setOnTrackPowerOff(std::function callback) + { + assert(!m_started); + m_onTrackPowerOff = std::move(callback); + } + + /** + * @brief ... + * + * @param[in] callback ... + * @note This function may not be called when the kernel is running. + */ + inline void setOnEmergencyStop(std::function callback) + { + assert(!m_started); + m_onEmergencyStop = std::move(callback); + } + + /** + * @brief Set the decoder controller + * + * @param[in] decoderController The decoder controller + * @note This function may not be called when the kernel is running. + */ + inline void setDecoderController(DecoderController* decoderController) + { + assert(!m_started); + m_decoderController = decoderController; + } + + /** + * @brief Set the input controller + * + * @param[in] inputController The input controller + * @note This function may not be called when the kernel is running. + */ + inline void setInputController(InputController* inputController) + { + assert(!m_started); + m_inputController = inputController; + } + + /** + * @brief Set the output controller + * + * @param[in] outputController The output controller + * @note This function may not be called when the kernel is running. + */ + inline void setOutputController(OutputController* outputController) + { + assert(!m_started); + m_outputController = outputController; + } + + /** + * @brief Start the kernel and IO handler + */ + void start(); + + /** + * @brief Stop the kernel and IO handler + */ + void stop(); + + /** + * @brief ... + * + * This must be called by the IO handler whenever a XpressNet message is received. + * + * @param[in] message The received XpressNet message + * @note This function must run in the kernel's IO context + */ + void receive(const Message& message); + + /** + * + * + */ + void normalOperationsResumed(); + + /** + * + * + */ + void trackPowerOff(); + + /** + * + * + */ + void emergencyStop(); + + /** + * + * + */ + void decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber); + + /** + * + * @param[in] address Output address, 1..2048 + * @param[in] value Output value: \c true is on, \c false is off. + * @return \c true if send successful, \c false otherwise. + */ + bool setOutput(uint16_t address, bool value); +}; + +} + +#endif diff --git a/server/src/hardware/protocol/xpressnet/messages.hpp b/server/src/hardware/protocol/xpressnet/messages.hpp index b5179c38..6a568ae3 100644 --- a/server/src/hardware/protocol/xpressnet/messages.hpp +++ b/server/src/hardware/protocol/xpressnet/messages.hpp @@ -120,16 +120,21 @@ struct FeedbackBroadcast : Message uint8_t address; uint8_t data; + constexpr uint16_t groupAddress() const + { + return (static_cast(address) << 1) | ((data & 0x10) ? 1 : 0); + } + + constexpr bool switchingCommandCompleted() const + { + return (data & 0x80); + } + constexpr Type type() const { return static_cast((data & 0x60) >> 5); } - constexpr bool isHighNibble() const - { - return (data & 0x10); - } - constexpr uint8_t statusNibble() const { return (data & 0x0F); @@ -161,8 +166,8 @@ struct EmergencyStopLocomotive : Message if(longAddress) { assert(address >= 1 && address <= 9999); - addressHigh = 0x00; - addressLow = address & 0x7f; + addressHigh = 0xC0 | address >> 8; + addressLow = address & 0xff; } else { @@ -404,6 +409,25 @@ struct setFunctionStateGroup : LocomotiveInstruction } __attribute__((packed)); */ +struct AccessoryDecoderOperationRequest : Message +{ + uint8_t address = 0x00; + uint8_t data = 0x80; + uint8_t checksum; + + AccessoryDecoderOperationRequest(uint16_t fullAddress, bool value) + : Message(0x52) + { + assert(fullAddress < 2048); + address = static_cast(fullAddress >> 3); + data |= static_cast(fullAddress & 0x07); + if(value) + data |= 0x40; + checksum = calcChecksum(*this); + } +}; +static_assert(sizeof(AccessoryDecoderOperationRequest) == 4); + namespace RocoMultiMAUS { struct FunctionInstructionF13F20 : LocomotiveInstruction diff --git a/server/src/hardware/protocol/xpressnet/settings.cpp b/server/src/hardware/protocol/xpressnet/settings.cpp new file mode 100644 index 00000000..3f286ddb --- /dev/null +++ b/server/src/hardware/protocol/xpressnet/settings.cpp @@ -0,0 +1,96 @@ +/** + * server/src/hardware/protocol/xpressnet/settings.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 "settings.hpp" +#include "../../../core/attributes.hpp" +#include "../../../utils/displayname.hpp" + +namespace XpressNet { + +Settings::Settings(Object& _parent, const std::string& parentPropertyName) + : SubObject(_parent, parentPropertyName) + , commandStation{this, "command_station", XpressNetCommandStation::Custom, PropertyFlags::ReadWrite | PropertyFlags::Store, std::bind(&Settings::commandStationChanged, this, std::placeholders::_1)} + , useEmergencyStopLocomotiveCommand{this, "use_emergency_stop_locomotive_command", false, PropertyFlags::ReadWrite | PropertyFlags::Store, std::bind(&Settings::setCommandStationCustom, this)} + , useRocoF13F20Command{this, "use_roco_f13_f20_command", false, PropertyFlags::ReadWrite | PropertyFlags::Store, std::bind(&Settings::setCommandStationCustom, this)} + , debugLogInput{this, "debug_log_input", false, PropertyFlags::ReadWrite | PropertyFlags::Store} + , debugLogRXTX{this, "debug_log_rx_tx", false, PropertyFlags::ReadWrite | PropertyFlags::Store} +{ + Attributes::addDisplayName(commandStation, DisplayName::Hardware::commandStation); + Attributes::addValues(commandStation, XpressNetCommandStationValues); + m_interfaceItems.add(commandStation); + + m_interfaceItems.add(useEmergencyStopLocomotiveCommand); + + m_interfaceItems.add(useRocoF13F20Command); + + Attributes::addDisplayName(debugLogInput, DisplayName::Hardware::debugLogInput); + m_interfaceItems.add(debugLogInput); + + Attributes::addDisplayName(debugLogRXTX, DisplayName::Hardware::debugLogRXTX); + m_interfaceItems.add(debugLogRXTX); +} + +Config Settings::config() const +{ + Config config; + + config.useEmergencyStopLocomotiveCommand = useEmergencyStopLocomotiveCommand; + config.useRocoF13F20Command = useRocoF13F20Command; + + config.debugLogInput = debugLogInput; + config.debugLogRXTX = debugLogRXTX; + + return config; +} + +void Settings::loaded() +{ + SubObject::loaded(); + + commandStationChanged(commandStation); +} + +void Settings::commandStationChanged(XpressNetCommandStation value) +{ + switch(value) + { + case XpressNetCommandStation::Custom: + break; + + case XpressNetCommandStation::Roco10764: + useEmergencyStopLocomotiveCommand.setValueInternal(false); + useRocoF13F20Command.setValueInternal(true); + break; + + case XpressNetCommandStation::DigikeijsDR5000: + useEmergencyStopLocomotiveCommand.setValueInternal(false); + useRocoF13F20Command.setValueInternal(false); + break; + } +} + +void Settings::setCommandStationCustom() +{ + commandStation.setValueInternal(XpressNetCommandStation::Custom); +} + +} diff --git a/server/src/hardware/protocol/xpressnet/settings.hpp b/server/src/hardware/protocol/xpressnet/settings.hpp new file mode 100644 index 00000000..e871d586 --- /dev/null +++ b/server/src/hardware/protocol/xpressnet/settings.hpp @@ -0,0 +1,57 @@ +/** + * server/src/hardware/protocol/xpressnet/settings.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_PROTOCOL_XPRESSNET_SETTINGS_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_XPRESSNET_SETTINGS_HPP + +#include "../../../core/subobject.hpp" +#include "../../../core/property.hpp" +#include "../../../enum/xpressnetcommandstation.hpp" +#include "config.hpp" + +namespace XpressNet { + +class Settings final : public SubObject +{ + protected: + void loaded() final; + + void commandStationChanged(XpressNetCommandStation value); + void setCommandStationCustom(); + + public: + CLASS_ID("xpressnet_settings") + + Property commandStation; + Property useEmergencyStopLocomotiveCommand; + Property useRocoF13F20Command; + Property debugLogInput; + Property debugLogRXTX; + + Settings(Object& _parent, const std::string& parentPropertyName); + + Config config() const; +}; + +} + +#endif diff --git a/server/src/hardware/protocol/xpressnet/xpressnet.cpp b/server/src/hardware/protocol/xpressnet/xpressnet.cpp deleted file mode 100644 index f00a4eca..00000000 --- a/server/src/hardware/protocol/xpressnet/xpressnet.cpp +++ /dev/null @@ -1,393 +0,0 @@ -/** - * server/src/hardware/protocol/xpressnet/xpressnet.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 "xpressnet.hpp" -#include "../../../core/traintastic.hpp" -#include "../../../core/eventloop.hpp" -#include "../../decoder/decoder.hpp" -#include "../../../core/attributes.hpp" -#include "../../input/xpressnetinput.hpp" -#include "../../../log/log.hpp" -#include "../../../utils/displayname.hpp" - -namespace XpressNet { - -XpressNet::XpressNet(Object& _parent, const std::string& parentPropertyName, std::function send) : - SubObject(_parent, parentPropertyName), - m_commandStation{dynamic_cast(&_parent)}, - m_send{std::move(send)}, - m_debugLog{true}, - commandStation{this, "command_station", XpressNetCommandStation::Custom, PropertyFlags::ReadWrite | PropertyFlags::Store, - [this](XpressNetCommandStation value) - { - switch(value) - { - case XpressNetCommandStation::Custom: - break; - - case XpressNetCommandStation::Roco10764: - useEmergencyStopLocomotiveCommand.setValueInternal(false); - //useFunctionStateCommands.setValueInternal(false); - useRocoF13F20Command.setValueInternal(true); - break; - - case XpressNetCommandStation::DigikeijsDR5000: - useEmergencyStopLocomotiveCommand.setValueInternal(false); // ????? - //useFunctionStateCommands.setValueInternal(false); - useRocoF13F20Command.setValueInternal(true); - break; - } - // TODO: updateEnabled(); - }}, - useEmergencyStopLocomotiveCommand{this, "use_emergency_stop_locomotive_command", false, PropertyFlags::ReadWrite | PropertyFlags::Store, - [this](bool) - { - commandStation = XpressNetCommandStation::Custom; - }}, - //useFunctionStateCommands{this, "use_function_state_commands", false, PropertyFlags::ReadWrite | PropertyFlags::Store, - // [this](bool) - // { - // commandStation = XpressNetCommandStation::Custom; - // }}, - useRocoF13F20Command{this, "use_roco_f13_f20_command", false, PropertyFlags::ReadWrite | PropertyFlags::Store, - [this](bool) - { - commandStation = XpressNetCommandStation::Custom; - }}, - debugLog{this, "debug_log", m_debugLog, PropertyFlags::ReadWrite | PropertyFlags::Store, - [this](bool value) - { - m_debugLog = value; - }}, - inputMonitor{*this, "input_monitor", - [this]() - { - return std::make_shared(shared_ptr()); - }} -{ - Attributes::addEnabled(commandStation, false); - Attributes::addValues(commandStation, XpressNetCommandStationValues); - m_interfaceItems.add(commandStation); - Attributes::addEnabled(useEmergencyStopLocomotiveCommand, false); - m_interfaceItems.add(useEmergencyStopLocomotiveCommand); - //Attributes::addEnabled(useFunctionStateCommands, false); - //m_interfaceItems.add(useFunctionStateCommands); - Attributes::addEnabled(useRocoF13F20Command, false); - m_interfaceItems.add(useRocoF13F20Command); - m_interfaceItems.add(debugLog); - - Attributes::addDisplayName(inputMonitor, DisplayName::Hardware::inputMonitor); - m_interfaceItems.add(inputMonitor); -} - -void XpressNet::worldEvent(WorldState state, WorldEvent event) -{ - SubObject::worldEvent(state, event); - - const bool editable = contains(state, WorldState::Edit); - - Attributes::setEnabled(commandStation, editable); - Attributes::setEnabled(useEmergencyStopLocomotiveCommand, editable); - //Attributes::setEnabled(useFunctionStateCommands, editable); - Attributes::setEnabled(useRocoF13F20Command, editable); -} - -void XpressNet::receive(const Message& message) -{ - // NOTE: this runs outside the event loop !!! - - assert(isChecksumValid(message)); - - if(m_debugLog) - EventLoop::call([this, data=toString(message)](){ Log::log(*this, LogMessage::D2002_RX_X, data); }); - - if(m_commandStation) - { - switch(message.identification()) - { - case idFeedbackBroadcast: - { - const FeedbackBroadcast* fb = static_cast(&message); - - for(uint8_t i = 0; i < fb->pairCount(); i++) - { - const FeedbackBroadcast::Pair& p = fb->pair(i); - if(p.type() == FeedbackBroadcast::Pair::Type::FeedbackModule) - { - const uint16_t baseAddress = 1 + (static_cast(p.address) << 3) + (p.isHighNibble() ? 4 : 0); - EventLoop::call( - [this, baseAddress, status=p.statusNibble()]() - { - for(uint16_t j = 0; j < 4; j++) - { - TriState v = toTriState((status & (1 << j)) != 0); - auto it = m_inputs.find(baseAddress + j); - if(it != m_inputs.end()) - it->second->updateValue(v); - else - inputMonitorValueChanged(baseAddress + j, v); - } - }); - } - } - break; - } - case 0x60: - if(message == NormalOperationResumed()) - { - EventLoop::call( - [cs=m_commandStation->shared_ptr()]() - { - cs->emergencyStop.setValueInternal(false); - cs->powerOn.setValueInternal(true); - }); - } - else if(message == TrackPowerOff()) - { - EventLoop::call( - [cs=m_commandStation->shared_ptr()]() - { - cs->powerOn.setValueInternal(false); - }); - } - break; - - case 0x80: - if(message == EmergencyStop()) - { - EventLoop::call( - [cs=m_commandStation->shared_ptr()]() - { - cs->emergencyStop.setValueInternal(true); - }); - } - break; - } - } -} - -void XpressNet::emergencyStopChanged(bool value) -{ - if(value) - send(EmergencyStop()); - else if(m_commandStation && m_commandStation->powerOn) - send(NormalOperationResumed()); -} - -void XpressNet::powerOnChanged(bool value) -{ - if(value) - send(NormalOperationResumed()); - else - send(TrackPowerOff()); -} - -void XpressNet::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber) -{ - if(useEmergencyStopLocomotiveCommand && changes == DecoderChangeFlags::EmergencyStop && decoder.emergencyStop) - send(EmergencyStopLocomotive( - decoder.address, - decoder.longAddress)); - else if(has(changes, DecoderChangeFlags::EmergencyStop | DecoderChangeFlags::Direction | DecoderChangeFlags::Throttle | DecoderChangeFlags::SpeedSteps)) - { - switch(decoder.speedSteps) - { - case 14: - send(SpeedAndDirectionInstruction14( - decoder.address, - decoder.longAddress, - decoder.emergencyStop, - decoder.direction, - Decoder::throttleToSpeedStep(decoder.throttle, 14), - decoder.getFunctionValue(0))); - break; - - case 27: - send(SpeedAndDirectionInstruction27( - decoder.address, - decoder.longAddress, - decoder.emergencyStop, - decoder.direction, - Decoder::throttleToSpeedStep(decoder.throttle, 27))); - break; - - case 28: - send(SpeedAndDirectionInstruction28( - decoder.address, - decoder.longAddress, - decoder.emergencyStop, - decoder.direction, - Decoder::throttleToSpeedStep(decoder.throttle, 28))); - break; - - case 126: - case 128: - default: - send(SpeedAndDirectionInstruction128( - decoder.address, - decoder.longAddress, - decoder.emergencyStop, - decoder.direction, - Decoder::throttleToSpeedStep(decoder.throttle, 126))); - break; - } - } - else if(has(changes, DecoderChangeFlags::FunctionValue)) - { - if(functionNumber <= 4) - send(FunctionInstructionGroup1( - decoder.address, - decoder.longAddress, - decoder.getFunctionValue(0), - decoder.getFunctionValue(1), - decoder.getFunctionValue(2), - decoder.getFunctionValue(3), - decoder.getFunctionValue(4))); - else if(functionNumber <= 8) - send(FunctionInstructionGroup2( - decoder.address, - decoder.longAddress, - decoder.getFunctionValue(5), - decoder.getFunctionValue(6), - decoder.getFunctionValue(7), - decoder.getFunctionValue(8))); - else if(functionNumber <= 12) - send(FunctionInstructionGroup3( - decoder.address, - decoder.longAddress, - decoder.getFunctionValue(9), - decoder.getFunctionValue(10), - decoder.getFunctionValue(11), - decoder.getFunctionValue(12))); - else if(functionNumber <= 20) - { - if(useRocoF13F20Command) - send(RocoMultiMAUS::FunctionInstructionF13F20( - decoder.address, - decoder.longAddress, - decoder.getFunctionValue(13), - decoder.getFunctionValue(14), - decoder.getFunctionValue(15), - decoder.getFunctionValue(16), - decoder.getFunctionValue(17), - decoder.getFunctionValue(18), - decoder.getFunctionValue(19), - decoder.getFunctionValue(20))); - else - send(FunctionInstructionGroup4( - decoder.address, - decoder.longAddress, - decoder.getFunctionValue(13), - decoder.getFunctionValue(14), - decoder.getFunctionValue(15), - decoder.getFunctionValue(16), - decoder.getFunctionValue(17), - decoder.getFunctionValue(18), - decoder.getFunctionValue(19), - decoder.getFunctionValue(20))); - } - else if(functionNumber <= 28) - send(FunctionInstructionGroup5( - decoder.address, - decoder.longAddress, - decoder.getFunctionValue(21), - decoder.getFunctionValue(22), - decoder.getFunctionValue(23), - decoder.getFunctionValue(24), - decoder.getFunctionValue(25), - decoder.getFunctionValue(26), - decoder.getFunctionValue(27), - decoder.getFunctionValue(28))); - } -} - -bool XpressNet::isInputAddressAvailable(uint16_t address) const -{ - return m_inputs.find(address) == m_inputs.end(); -} - -uint16_t XpressNet::getUnusedInputAddress() const -{ - const auto end = m_inputs.cend(); - for(uint16_t address = XpressNetInput::addressMin; address < XpressNetInput::addressMax; address++) - if(m_inputs.find(address) == end) - return address; - return XpressNetInput::addressInvalid; -} - -bool XpressNet::changeInputAddress(XpressNetInput& input, uint16_t newAddress) -{ - assert(input.xpressnet.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)); - inputMonitorIdChanged(input.address, {}); - inputMonitorIdChanged(newAddress, input.id.value()); - input.updateValue(TriState::Undefined); - - return true; -} - -bool XpressNet::addInput(XpressNetInput& input) -{ - if(isInputAddressAvailable(input.address)) - { - m_inputs.insert({input.address, input.shared_ptr()}); - inputMonitorIdChanged(input.address, input.id.value()); - input.updateValue(TriState::Undefined); - // TODO: request state! - return true; - } - else - return false; -} - -void XpressNet::removeInput(XpressNetInput& input) -{ - assert(input.xpressnet.value().get() == this); - const uint16_t address = input.address; - auto it = m_inputs.find(input.address); - if(it != m_inputs.end() && it->second.get() == &input) - m_inputs.erase(it); - input.updateValue(TriState::Undefined); - inputMonitorIdChanged(address, {}); -} - -void XpressNet::inputMonitorIdChanged(const uint32_t address, std::string_view value) -{ - for(auto* item : m_inputMonitors) - if(item->inputIdChanged) - item->inputIdChanged(*item, address, value); -} - -void XpressNet::inputMonitorValueChanged(const uint32_t address, const TriState value) -{ - for(auto* item : m_inputMonitors) - if(item->inputValueChanged) - item->inputValueChanged(*item, address, value); -} - -} diff --git a/server/src/hardware/protocol/xpressnet/xpressnet.hpp b/server/src/hardware/protocol/xpressnet/xpressnet.hpp deleted file mode 100644 index 16c84eb8..00000000 --- a/server/src/hardware/protocol/xpressnet/xpressnet.hpp +++ /dev/null @@ -1,86 +0,0 @@ -/** - * server/src/hardware/protocol/xpressnet/xpressnet.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_PROTOCOL_XPRESSNET_XPRESSNET_HPP -#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_XPRESSNET_XPRESSNET_HPP - -#include "../../../core/subobject.hpp" -#include "../../../core/property.hpp" -#include "../../../core/method.hpp" -#include "../../../enum/xpressnetcommandstation.hpp" -#include "../../../hardware/decoder/decoderchangeflags.hpp" -#include "messages.hpp" -#include "../../input/monitor/xpressnetinputmonitor.hpp" - -class CommandStation; -class Decoder; -class XpressNetInput; -class XpressNetInputMonitor; - -namespace XpressNet { - -class XpressNet : public SubObject -{ - friend class ::XpressNetInputMonitor; - - protected: - static bool getFunctionValue(const Decoder& decoder, uint32_t number); - - CommandStation* const m_commandStation; // valid if parent is command station, else nullptr - std::function m_send; - std::atomic_bool m_debugLog; - std::unordered_map> m_inputs; - std::vector m_inputMonitors; - - void worldEvent(WorldState state, WorldEvent event) final; - - public: - CLASS_ID("protocol.xpressnet") - - Property commandStation; - Property useEmergencyStopLocomotiveCommand; - //Property useFunctionStateCommands; - Property useRocoF13F20Command; - Property debugLog; - Method()> inputMonitor; - - XpressNet(Object& _parent, const std::string& parentPropertyName, std::function send); - - bool send(const Message& msg) { return m_send(msg); } - void receive(const Message& msg); - - void emergencyStopChanged(bool value); - void powerOnChanged(bool value); - void decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber); - - [[nodiscard]] bool isInputAddressAvailable(uint16_t address) const; - uint16_t getUnusedInputAddress() const; - [[nodiscard]] bool changeInputAddress(XpressNetInput& input, uint16_t newAddress); - [[nodiscard]] bool addInput(XpressNetInput& input); - void removeInput(XpressNetInput& input); - void inputMonitorIdChanged(uint32_t address, std::string_view value); - void inputMonitorValueChanged(uint32_t address, TriState value); -}; - -} - -#endif diff --git a/server/src/hardware/protocol/z21/clientkernel.cpp b/server/src/hardware/protocol/z21/clientkernel.cpp new file mode 100644 index 00000000..47a0bcb6 --- /dev/null +++ b/server/src/hardware/protocol/z21/clientkernel.cpp @@ -0,0 +1,345 @@ +/** + * server/src/hardware/protocol/z21/clientkernel.cpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021-2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "clientkernel.hpp" +#include "messages.hpp" +#include "../xpressnet/messages.hpp" +#include "../../decoder/decoder.hpp" +#include "../../decoder/decoderchangeflags.hpp" +#include "../../input/inputcontroller.hpp" +#include "../../../core/eventloop.hpp" +#include "../../../log/log.hpp" + +namespace Z21 { + +ClientKernel::ClientKernel(const ClientConfig& config) + : Kernel() + , m_keepAliveTimer(m_ioContext) + , m_config{config} +{ +} + +void ClientKernel::setConfig(const ClientConfig& config) +{ + m_ioContext.post( + [this, newConfig=config]() + { + m_config = newConfig; + }); +} + +void ClientKernel::receive(const Message& message) +{ + if(m_config.debugLogRXTX) + EventLoop::call( + [this, msg=toString(message)]() + { + Log::log(m_logId, LogMessage::D2002_RX_X, msg); + }); + + switch(message.header()) + { + case LAN_X: + { + const auto& lanX = static_cast(message); + + if(!XpressNet::isChecksumValid(*reinterpret_cast(&lanX.xheader))) + break; + + switch(lanX.xheader) + { + case 0x61: + if(message == LanXBCTrackPowerOff()) + { + if(m_trackPowerOn != TriState::False) + { + m_trackPowerOn = TriState::False; + + if(m_onTrackPowerOnChanged) + EventLoop::call( + [this]() + { + m_onTrackPowerOnChanged(false); + }); + } + } + else if(message == LanXBCTrackPowerOn()) + { + if(m_trackPowerOn != TriState::True) + { + m_trackPowerOn = TriState::True; + + if(m_onTrackPowerOnChanged) + EventLoop::call( + [this]() + { + m_onTrackPowerOnChanged(true); + }); + } + } + break; + + case 0x81: + if(message == LanXBCStopped()) + { + if(m_emergencyStop != TriState::True) + { + m_emergencyStop = TriState::True; + + if(m_onEmergencyStop) + EventLoop::call( + [this]() + { + m_onEmergencyStop(); + }); + } + } + break; + } + break; + } + case LAN_GET_SERIAL_NUMBER: + if(message.dataLen() == sizeof(LanGetSerialNumberReply)) + { + const auto& reply = static_cast(message); + + if(m_serialNumber != reply.serialNumber()) + { + m_serialNumber = reply.serialNumber(); + if(m_onSerialNumberChanged) + { + EventLoop::call( + [this, serialNumber=m_serialNumber]() + { + m_onSerialNumberChanged(serialNumber); + }); + } + } + } + break; + + case LAN_GET_HWINFO: + if(message.dataLen() == sizeof(LanGetHardwareInfoReply)) + { + const auto& reply = static_cast(message); + + if(m_hardwareType != reply.hardwareType() || + m_firmwareVersionMajor != reply.firmwareVersionMajor() || + m_firmwareVersionMinor != reply.firmwareVersionMinor()) + { + m_hardwareType = reply.hardwareType(); + m_firmwareVersionMajor = reply.firmwareVersionMajor(); + m_firmwareVersionMinor = reply.firmwareVersionMinor(); + + if(m_onHardwareInfoChanged) + { + EventLoop::call( + [this, hardwareType=m_hardwareType, firmwareVersionMajor=m_firmwareVersionMajor, firmwareVersionMinor=m_firmwareVersionMinor]() + { + m_onHardwareInfoChanged(hardwareType, firmwareVersionMajor, firmwareVersionMinor); + }); + } + } + } + break; + + case LAN_GET_CODE: + case LAN_LOGOFF: + case LAN_SET_BROADCASTFLAGS: + case LAN_GET_BROADCASTFLAGS: + case LAN_GET_LOCO_MODE: + case LAN_SET_LOCO_MODE: + case LAN_GET_TURNOUTMODE: + case LAN_SET_TURNOUTMODE: + case LAN_RMBUS_DATACHANGED: + case LAN_RMBUS_GETDATA: + case LAN_RMBUS_PROGRAMMODULE: + case LAN_SYSTEMSTATE_DATACHANGED: + case LAN_SYSTEMSTATE_GETDATA: + case LAN_RAILCOM_DATACHANGED: + case LAN_RAILCOM_GETDATA: + case LAN_LOCONET_Z21_RX: + case LAN_LOCONET_Z21_TX: + case LAN_LOCONET_FROM_LAN: + case LAN_LOCONET_DISPATCH_ADDR: + case LAN_LOCONET_DETECTOR: + case LAN_CAN_DETECTOR: + break; // not (yet) supported + } +} + +void ClientKernel::trackPowerOn() +{ + m_ioContext.post( + [this]() + { + if(m_trackPowerOn != TriState::True || m_emergencyStop != TriState::False) + send(LanXSetTrackPowerOn()); + }); +} + +void ClientKernel::trackPowerOff() +{ + m_ioContext.post( + [this]() + { + if(m_trackPowerOn != TriState::False) + send(LanXSetTrackPowerOff()); + }); +} + +void ClientKernel::emergencyStop() +{ + m_ioContext.post( + [this]() + { + if(m_emergencyStop != TriState::True) + send(LanXSetStop()); + }); +} + +void ClientKernel::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber) +{ + if(has(changes, DecoderChangeFlags::EmergencyStop | DecoderChangeFlags::Direction | DecoderChangeFlags::Throttle | DecoderChangeFlags::SpeedSteps)) + { + LanXSetLocoDrive cmd; + cmd.setAddress(decoder.address, decoder.longAddress); + + switch(decoder.speedSteps) + { + case 14: + { + const uint8_t speedStep = Decoder::throttleToSpeedStep(decoder.throttle, 14); + cmd.db0 = 0x10; + if(decoder.emergencyStop) + cmd.speedAndDirection = 0x01; + else if(speedStep > 0) + cmd.speedAndDirection = speedStep + 1; + break; + } + case 28: + { + uint8_t speedStep = Decoder::throttleToSpeedStep(decoder.throttle, 28); + cmd.db0 = 0x12; + if(decoder.emergencyStop) + cmd.speedAndDirection = 0x01; + else if(speedStep > 0) + { + speedStep++; + cmd.speedAndDirection = ((speedStep & 0x01) << 4) | (speedStep >> 1); + } + break; + } + case 126: + case 128: + default: + { + const uint8_t speedStep = Decoder::throttleToSpeedStep(decoder.throttle, 126); + cmd.db0 = 0x13; + if(decoder.emergencyStop) + cmd.speedAndDirection = 0x01; + else if(speedStep > 0) + cmd.speedAndDirection = speedStep + 1; + break; + } + } + + if(decoder.direction.value() == Direction::Forward) + cmd.speedAndDirection |= 0x80; + + cmd.checksum = XpressNet::calcChecksum(*reinterpret_cast(&cmd.xheader)); + postSend(cmd); + } + else if(has(changes, DecoderChangeFlags::FunctionValue)) + { + if(functionNumber <= LanXSetLocoFunction::functionNumberMax) + if(const auto& f = decoder.getFunction(functionNumber)) + postSend(LanXSetLocoFunction( + decoder.address, decoder.longAddress, + static_cast(functionNumber), + f->value ? LanXSetLocoFunction::SwitchType::On : LanXSetLocoFunction::SwitchType::Off)); + } +} + +void ClientKernel::onStart() +{ + // reset all state values + m_serialNumber = 0; + m_hardwareType = HWT_UNKNOWN; + m_firmwareVersionMajor = 0; + m_firmwareVersionMinor = 0; + m_trackPowerOn = TriState::Undefined; + m_emergencyStop = TriState::Undefined; + + send(LanGetSerialNumber()); + send(LanGetHardwareInfo()); + + send(LanSetBroadcastFlags( + BroadcastFlags::PowerLocoTurnoutChanges | + BroadcastFlags::SystemStatusChanges | + BroadcastFlags::AllLocoChanges)); // seems not to work with DR5000 + + send(LanGetBroadcastFlags()); + + send(LanSystemStateGetData()); + + startKeepAliveTimer(); +} + +void ClientKernel::onStop() +{ + send(LanLogoff()); +} + +void ClientKernel::send(const Message& message) +{ + if(m_ioHandler->send(message)) + { + if(m_config.debugLogRXTX) + EventLoop::call( + [this, msg=toString(message)]() + { + Log::log(m_logId, LogMessage::D2001_TX_X, msg); + }); + } + else + {} // log message and go to error state +} + +void ClientKernel::startKeepAliveTimer() +{ + assert(m_config.keepAliveInterval > 0); + m_keepAliveTimer.expires_after(boost::asio::chrono::seconds(m_config.keepAliveInterval)); + m_keepAliveTimer.async_wait(std::bind(&ClientKernel::keepAliveTimerExpired, this, std::placeholders::_1)); +} + +void ClientKernel::keepAliveTimerExpired(const boost::system::error_code& ec) +{ + if(ec) + return; + + send(LanSystemStateGetData()); + + startKeepAliveTimer(); +} + +} diff --git a/server/src/hardware/protocol/z21/clientkernel.hpp b/server/src/hardware/protocol/z21/clientkernel.hpp new file mode 100644 index 00000000..d68a2cc9 --- /dev/null +++ b/server/src/hardware/protocol/z21/clientkernel.hpp @@ -0,0 +1,178 @@ +/** + * server/src/hardware/protocol/z21/clientkernel.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021-2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_Z21_CLIENTKERNEL_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_Z21_CLIENTKERNEL_HPP + +#include "kernel.hpp" +#include +#include "../../../enum/tristate.hpp" + +namespace Z21 { + +class ClientKernel final : public Kernel +{ + private: + boost::asio::steady_timer m_keepAliveTimer; + + uint32_t m_serialNumber; + std::function m_onSerialNumberChanged; + + HardwareType m_hardwareType; + uint8_t m_firmwareVersionMajor; + uint8_t m_firmwareVersionMinor; + std::function m_onHardwareInfoChanged; + + TriState m_trackPowerOn; + TriState m_emergencyStop; + std::function m_onTrackPowerOnChanged; + std::function m_onEmergencyStop; + + DecoderController* m_decoderController = nullptr; + + ClientConfig m_config; + + ClientKernel(const ClientConfig& config); + + void onStart() final; + void onStop() final; + + template + void postSend(const T& message) + { + m_ioContext.post( + [this, message]() + { + send(message); + }); + } + + void send(const Message& message); + + void startKeepAliveTimer(); + void keepAliveTimerExpired(const boost::system::error_code& ec); + + public: + /** + * @brief Create kernel and IO handler + * @param[in] config Z21 client configuration + * @param[in] args IO handler arguments + * @return The kernel instance + */ + template + static std::unique_ptr create(const ClientConfig& config, Args... args) + { + static_assert(std::is_base_of_v); + std::unique_ptr kernel{new ClientKernel(config)}; + kernel->setIOHandler(std::make_unique(*kernel, std::forward(args)...)); + return kernel; + } + + /** + * @brief Set Z21 client configuration + * @param[in] config The Z21 client configuration + */ + void setConfig(const ClientConfig& config); + + /** + * @brief Incoming message handler + * This method must be called by the IO handler whenever a Z21 message is received. + * @param[in] message The received Z21 message + * @note This function must run in the kernel's IO context + */ + void receive(const Message& message); + + /** + * @brief ... + * @param[in] callback ... + * @note This function may not be called when the kernel is running. + */ + inline void setOnSerialNumberChanged(std::function callback) + { + assert(!m_started); + m_onSerialNumberChanged = std::move(callback); + } + + /** + * @brief ... + * @param[in] callback ... + * @note This function may not be called when the kernel is running. + */ + inline void setOnHardwareInfoChanged(std::function callback) + { + assert(!m_started); + m_onHardwareInfoChanged = std::move(callback); + } + + /** + * @brief ... + * @param[in] callback ... + * @note This function may not be called when the kernel is running. + */ + inline void setOnTrackPowerOnChanged(std::function callback) + { + assert(!m_started); + m_onTrackPowerOnChanged = std::move(callback); + } + + /** + */ + void trackPowerOn(); + + /** + */ + void trackPowerOff(); + + /** + */ + void emergencyStop(); + + /** + * @brief ... + * @param[in] callback ... + * @note This function may not be called when the kernel is running. + */ + inline void setOnEmergencyStop(std::function callback) + { + assert(!m_started); + m_onEmergencyStop = std::move(callback); + } + + /** + * @brief Set the decoder controller + * @param[in] decoderController The decoder controller + * @note This function may not be called when the kernel is running. + */ + inline void setDecoderController(DecoderController* decoderController) + { + assert(!m_started); + m_decoderController = decoderController; + } + + /** + */ + void decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber); +}; + +} + +#endif diff --git a/server/src/hardware/commandstation/virtualcommandstation.cpp b/server/src/hardware/protocol/z21/clientsettings.cpp similarity index 63% rename from server/src/hardware/commandstation/virtualcommandstation.cpp rename to server/src/hardware/protocol/z21/clientsettings.cpp index 5d5b912e..90e7f2ef 100644 --- a/server/src/hardware/commandstation/virtualcommandstation.cpp +++ b/server/src/hardware/protocol/z21/clientsettings.cpp @@ -1,9 +1,9 @@ /** - * hardware/commandstation/virtualcommandstation.cpp + * server/src/hardware/protocol/z21/clientsettings.cpp * - * This file is part of the traintastic source code + * This file is part of the traintastic source code. * - * Copyright (C) 2019-2020 Reinder Feenstra + * Copyright (C) 2021-2022 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -20,15 +20,22 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "virtualcommandstation.hpp" +#include "clientsettings.hpp" -VirtualCommandStation::VirtualCommandStation(const std::weak_ptr& world, std::string_view _id) : - CommandStation(world, _id) +namespace Z21 { + +ClientSettings::ClientSettings(Object& _parent, const std::string& parentPropertyName) + : Settings(_parent, parentPropertyName) { - name = "Virtual"; } -bool VirtualCommandStation::setOnline(bool& /*value*/) +ClientConfig ClientSettings::config() const { - return true; + ClientConfig config; + + getConfig(config); + + return config; +} + } diff --git a/server/src/hardware/protocol/z21/clientsettings.hpp b/server/src/hardware/protocol/z21/clientsettings.hpp new file mode 100644 index 00000000..517e912d --- /dev/null +++ b/server/src/hardware/protocol/z21/clientsettings.hpp @@ -0,0 +1,42 @@ +/** + * server/src/hardware/protocol/z21/clientsettings.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021-2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_Z21_CLIENTSETTINGS_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_Z21_CLIENTSETTINGS_HPP + +#include "settings.hpp" + +namespace Z21 { + +class ClientSettings final : public Settings +{ + CLASS_ID("z21_settings.client") + + public: + ClientSettings(Object& _parent, const std::string& parentPropertyName); + + ClientConfig config() const; +}; + +} + +#endif diff --git a/server/src/hardware/protocol/z21/config.hpp b/server/src/hardware/protocol/z21/config.hpp new file mode 100644 index 00000000..dc7a6c83 --- /dev/null +++ b/server/src/hardware/protocol/z21/config.hpp @@ -0,0 +1,58 @@ +/** + * server/src/hardware/protocol/z21/config.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021-2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_Z21_CONFIG_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_Z21_CONFIG_HPP + +#include "messages.hpp" + +namespace Z21 { + +struct Config +{ + bool debugLogRXTX; +}; + +struct ClientConfig : Config +{ + static constexpr uint16_t keepAliveInterval = 15; //!< sec +}; + +struct ServerConfig : Config +{ + static constexpr CommandStationId commandStationId = CommandStationId::Z21; + static constexpr uint8_t firmwareVersionMajor = 1; + static constexpr uint8_t firmwareVersionMinor = 30; + static constexpr HardwareType hardwareType = HWT_Z21_START; + static constexpr uint16_t inactiveClientPurgeTime = 60; ///< sec + static constexpr uint32_t serialNumber = 123456789; + static constexpr size_t subscriptionMax = 16; + static constexpr uint8_t xBusVersion = 30; + + bool allowEmergencyStop; + bool allowTrackPowerOff; + bool allowTrackPowerOnReleaseEmergencyStop; +}; + +} + +#endif diff --git a/server/src/hardware/protocol/z21/iohandler/iohandler.hpp b/server/src/hardware/protocol/z21/iohandler/iohandler.hpp new file mode 100644 index 00000000..eccf1617 --- /dev/null +++ b/server/src/hardware/protocol/z21/iohandler/iohandler.hpp @@ -0,0 +1,63 @@ +/** + * server/src/hardware/protocol/z21/iohandler/iohandler.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021-2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_Z21_IOHANDLER_IOHANDLER_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_Z21_IOHANDLER_IOHANDLER_HPP + +#include +#include + +namespace Z21 { + +class Kernel; +struct Message; + +class IOHandler +{ + protected: + Kernel& m_kernel; + + IOHandler(Kernel& kernel) + : m_kernel{kernel} + { + } + + public: + using ClientId = size_t; + + IOHandler(const IOHandler&) = delete; + IOHandler& operator =(const IOHandler&) = delete; + + virtual ~IOHandler() = default; + + virtual void start() = 0; + virtual void stop() = 0; + + virtual bool send(const Message& /*message*/) { return false; } + virtual bool sendTo(const Message& /*message*/, ClientId /*id*/) { return false; } + + virtual void purgeClient(ClientId /*id*/) {} +}; + +} + +#endif diff --git a/server/src/hardware/protocol/z21/iohandler/udpclientiohandler.cpp b/server/src/hardware/protocol/z21/iohandler/udpclientiohandler.cpp new file mode 100644 index 00000000..a7792701 --- /dev/null +++ b/server/src/hardware/protocol/z21/iohandler/udpclientiohandler.cpp @@ -0,0 +1,101 @@ +/** + * server/src/hardware/protocol/z21/iohandler/udpclientiohandler.cpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021-2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "udpclientiohandler.hpp" +#include "../clientkernel.hpp" +#include "../messages.hpp" +#include "../../../../core/eventloop.hpp" +#include "../../../../log/log.hpp" +#include "../../../../log/logmessageexception.hpp" + +namespace Z21 { + +UDPClientIOHandler::UDPClientIOHandler(ClientKernel& kernel, const std::string& hostname, uint16_t port) + : UDPIOHandler(kernel) + , m_sendBufferOffset{0} +{ + boost::system::error_code ec; + + m_remoteEndpoint.port(port); + m_remoteEndpoint.address(boost::asio::ip::make_address(hostname, ec)); + if(ec) + throw LogMessageException(LogMessage::E2003_MAKE_ADDRESS_FAILED_X, ec); + + if(m_socket.open(boost::asio::ip::udp::v4(), ec)) + throw LogMessageException(LogMessage::E2004_SOCKET_OPEN_FAILED_X, ec); + + if(m_socket.bind(boost::asio::ip::udp::endpoint(boost::asio::ip::address_v4::any(), 0), ec)) + { + m_socket.close(); + throw LogMessageException(LogMessage::E2006_SOCKET_BIND_FAILED_X, ec); + } +} + +bool UDPClientIOHandler::send(const Message& message) +{ + if(m_sendBufferOffset + message.dataLen() > m_sendBuffer.size()) + return false; + + const bool wasEmpty = m_sendBufferOffset == 0; + + memcpy(m_sendBuffer.data() + m_sendBufferOffset, &message, message.dataLen()); + m_sendBufferOffset += message.dataLen(); + + if(wasEmpty) + send(); + + return true; +} + +void UDPClientIOHandler::receive(const Message& message, const boost::asio::ip::udp::endpoint& /*remoteEndpoint*/) +{ + static_cast(m_kernel).receive(message); +} + +void UDPClientIOHandler::send() +{ + m_socket.async_send_to(boost::asio::buffer(m_sendBuffer.data(), m_sendBufferOffset), m_remoteEndpoint, + [this](const boost::system::error_code& ec, std::size_t bytesTransferred) + { + if(!ec) + { + m_sendBufferOffset -= bytesTransferred; + + if(m_sendBufferOffset > 0) + { + memmove(m_sendBuffer.data(), m_sendBuffer.data() + bytesTransferred, m_sendBufferOffset); + send(); + } + } + else if(ec != boost::asio::error::operation_aborted) + { + EventLoop::call( + [this, ec]() + { + Log::log(m_kernel.logId(), LogMessage::E2011_SOCKET_SEND_FAILED_X, ec); + // TODO interface status -> error + }); + } + }); +} + +} diff --git a/server/src/hardware/protocol/z21/iohandler/udpclientiohandler.hpp b/server/src/hardware/protocol/z21/iohandler/udpclientiohandler.hpp new file mode 100644 index 00000000..d338efeb --- /dev/null +++ b/server/src/hardware/protocol/z21/iohandler/udpclientiohandler.hpp @@ -0,0 +1,52 @@ +/** + * server/src/hardware/protocol/z21/iohandler/udpclientiohandler.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021-2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_Z21_IOHANDLER_UDPCLIENTIOHANDLER_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_Z21_IOHANDLER_UDPCLIENTIOHANDLER_HPP + +#include "udpiohandler.hpp" + +namespace Z21 { + +class ClientKernel; + +class UDPClientIOHandler final : public UDPIOHandler +{ + private: + boost::asio::ip::udp::endpoint m_remoteEndpoint; + std::array m_sendBuffer; + size_t m_sendBufferOffset; + + void send(); + + protected: + void receive(const Message& message, const boost::asio::ip::udp::endpoint& remoteEndpoint) final; + + public: + UDPClientIOHandler(ClientKernel& kernel, const std::string& hostname, uint16_t port); + + bool send(const Message& message) final; +}; + +} + +#endif diff --git a/server/src/hardware/protocol/z21/iohandler/udpiohandler.cpp b/server/src/hardware/protocol/z21/iohandler/udpiohandler.cpp new file mode 100644 index 00000000..49434959 --- /dev/null +++ b/server/src/hardware/protocol/z21/iohandler/udpiohandler.cpp @@ -0,0 +1,77 @@ +/** + * server/src/hardware/protocol/z21/iohandler/udpiohandler.cpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021-2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "udpiohandler.hpp" +#include "../kernel.hpp" +#include "../messages.hpp" +#include "../../../../core/eventloop.hpp" +#include "../../../../log/log.hpp" +#include "../../../../log/logmessageexception.hpp" + +namespace Z21 { + +UDPIOHandler::UDPIOHandler(Kernel& kernel) + : IOHandler(kernel) + , m_socket{m_kernel.ioContext()} +{ +} + +void UDPIOHandler::start() +{ + receive(); +} + +void UDPIOHandler::stop() +{ +} + +void UDPIOHandler::receive() +{ + m_socket.async_receive_from(boost::asio::buffer(m_receiveBuffer), m_receiveEndpoint, + [this](const boost::system::error_code& ec, std::size_t bytesReceived) + { + if(!ec) + { + const std::byte* pos = m_receiveBuffer.data(); + while(bytesReceived >= sizeof(Message)) + { + const Message& message = *reinterpret_cast(pos); + if(message.dataLen() < sizeof(Message)) + break; + receive(message, m_receiveEndpoint); + pos += message.dataLen(); + bytesReceived -= message.dataLen(); + } + receive(); + } + else + { + EventLoop::call( + [this, ec]() + { + Log::log(m_kernel.logId(), LogMessage::E2009_SOCKET_RECEIVE_FAILED_X, ec); + }); + } + }); +} + +} diff --git a/server/src/hardware/protocol/z21/iohandler/udpiohandler.hpp b/server/src/hardware/protocol/z21/iohandler/udpiohandler.hpp new file mode 100644 index 00000000..0d5e9f5c --- /dev/null +++ b/server/src/hardware/protocol/z21/iohandler/udpiohandler.hpp @@ -0,0 +1,58 @@ +/** + * server/src/hardware/protocol/z21/iohandler/udpiohandler.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021-2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_Z21_IOHANDLER_UDPIOHANDLER_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_Z21_IOHANDLER_UDPIOHANDLER_HPP + +#include "iohandler.hpp" +#include + +namespace Z21 { + +class UDPIOHandler : public IOHandler +{ + public: + static constexpr size_t payloadSizeMax = 1500 - 20 - 8; ///< Ethernet MTU - IPv4 header - UDP header + + private: + boost::asio::ip::udp::endpoint m_receiveEndpoint; + std::array m_receiveBuffer; + + void receive(); + + protected: + boost::asio::ip::udp::socket m_socket; + + virtual void receive(const Message& message, const boost::asio::ip::udp::endpoint& remoteEndpoint) = 0; + + public: + static constexpr uint16_t defaultPort = 21105; + + UDPIOHandler(Kernel& kernel); + + void start() override; + void stop() override; +}; + +} + +#endif diff --git a/server/src/hardware/protocol/z21/iohandler/udpserveriohandler.cpp b/server/src/hardware/protocol/z21/iohandler/udpserveriohandler.cpp new file mode 100644 index 00000000..6cfa91bb --- /dev/null +++ b/server/src/hardware/protocol/z21/iohandler/udpserveriohandler.cpp @@ -0,0 +1,87 @@ +/** + * server/src/hardware/protocol/z21/iohandler/udpclientiohandler.cpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021-2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "udpserveriohandler.hpp" +//#include +#include "../serverkernel.hpp" +#include "../messages.hpp" +#include "../../../../log/logmessageexception.hpp" + +namespace Z21 { + +UDPServerIOHandler::UDPServerIOHandler(ServerKernel& kernel) + : UDPIOHandler(kernel) +{ + boost::system::error_code ec; + + if(m_socket.open(boost::asio::ip::udp::v4(), ec)) + throw LogMessageException(LogMessage::E2004_SOCKET_OPEN_FAILED_X, ec); + + if(m_socket.bind(boost::asio::ip::udp::endpoint(boost::asio::ip::address_v4::any(), defaultPort), ec)) + { + m_socket.close(); + throw LogMessageException(LogMessage::E2006_SOCKET_BIND_FAILED_X, ec); + } +} + +bool UDPServerIOHandler::sendTo(const Message& message, ClientId id) +{ + //! \todo use async_send_to() + if(auto it = m_clients.find(id); it != m_clients.end()) + { + m_socket.send_to(boost::asio::buffer(&message, message.dataLen()), it->second); + return true; + } + + return false; +} + +void UDPServerIOHandler::purgeClient(ClientId id) +{ + m_clients.erase(id); +} + +void UDPServerIOHandler::receive(const Message& message, const boost::asio::ip::udp::endpoint& remoteEndpoint) +{ + ClientId clientId; + + if(auto it = std::find_if(m_clients.begin(), m_clients.end(), [&remoteEndpoint](auto n) { return n.second == remoteEndpoint; }); it != m_clients.end()) + { + clientId = it->first; + } + else // new client + { + do + { + m_lastClientId++; + } + while(m_clients.find(m_lastClientId) != m_clients.end()); + + m_clients.emplace(m_lastClientId, remoteEndpoint); + + clientId = m_lastClientId; + } + + static_cast(m_kernel).receiveFrom(message, clientId); +} + +} diff --git a/server/src/hardware/protocol/z21/iohandler/udpserveriohandler.hpp b/server/src/hardware/protocol/z21/iohandler/udpserveriohandler.hpp new file mode 100644 index 00000000..324afbea --- /dev/null +++ b/server/src/hardware/protocol/z21/iohandler/udpserveriohandler.hpp @@ -0,0 +1,52 @@ +/** + * server/src/hardware/protocol/z21/iohandler/udpserveriohandler.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021-2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_Z21_IOHANDLER_UDPSERVERIOHANDLER_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_Z21_IOHANDLER_UDPSERVERIOHANDLER_HPP + +#include "udpiohandler.hpp" +#include + +namespace Z21 { + +class ServerKernel; + +class UDPServerIOHandler final : public UDPIOHandler +{ + private: + ClientId m_lastClientId = 0; + std::unordered_map m_clients; + + protected: + void receive(const Message& message, const boost::asio::ip::udp::endpoint& remoteEndpoint) final; + + public: + UDPServerIOHandler(ServerKernel& kernel); + + bool sendTo(const Message& message, ClientId id) final; + + void purgeClient(ClientId id) final; +}; + +} + +#endif diff --git a/server/src/hardware/protocol/z21/kernel.cpp b/server/src/hardware/protocol/z21/kernel.cpp new file mode 100644 index 00000000..1b93201b --- /dev/null +++ b/server/src/hardware/protocol/z21/kernel.cpp @@ -0,0 +1,102 @@ +/** + * server/src/hardware/protocol/z21/kernel.cpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2019-2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "kernel.hpp" +#include "messages.hpp" +#include "../xpressnet/messages.hpp" +#include "../../decoder/decoder.hpp" +#include "../../decoder/decoderchangeflags.hpp" +#include "../../input/inputcontroller.hpp" +#include "../../../utils/setthreadname.hpp" +#include "../../../core/eventloop.hpp" +#include "../../../log/log.hpp" + +namespace Z21 { + +Kernel::Kernel() + : m_ioContext{1} +#ifndef NDEBUG + , m_started{false} +#endif +{ +} + +void Kernel::start() +{ + assert(m_ioHandler); + assert(!m_started); + + m_thread = std::thread( + [this]() + { + setThreadName("z21"); + auto work = std::make_shared(m_ioContext); + m_ioContext.run(); + }); + + m_ioContext.post( + [this]() + { + m_ioHandler->start(); + + onStart(); + + if(m_onStarted) + EventLoop::call( + [this]() + { + m_onStarted(); + }); + }); + +#ifndef NDEBUG + m_started = true; +#endif +} + +void Kernel::stop() +{ + m_ioContext.post( + [this]() + { + onStop(); + + m_ioHandler->stop(); + }); + + m_ioContext.stop(); + + m_thread.join(); + +#ifndef NDEBUG + m_started = false; +#endif +} + +void Kernel::setIOHandler(std::unique_ptr handler) +{ + assert(handler); + assert(!m_ioHandler); + m_ioHandler = std::move(handler); +} + +} diff --git a/server/src/hardware/protocol/z21/kernel.hpp b/server/src/hardware/protocol/z21/kernel.hpp new file mode 100644 index 00000000..dd7fec70 --- /dev/null +++ b/server/src/hardware/protocol/z21/kernel.hpp @@ -0,0 +1,118 @@ +/** + * server/src/hardware/protocol/z21/kernel.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2019-2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_Z21_KERNEL_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_Z21_KERNEL_HPP + +#include +#include +#include + +#include +#include "config.hpp" +#include "iohandler/iohandler.hpp" + +class Decoder; +enum class DecoderChangeFlags; +class DecoderController; + +namespace Z21 { + +struct Message; +enum HardwareType : uint32_t; + +class Kernel +{ + private: + std::thread m_thread; + std::function m_onStarted; + + protected: + boost::asio::io_context m_ioContext; + std::unique_ptr m_ioHandler; + std::string m_logId; +#ifndef NDEBUG + bool m_started; +#endif + + Kernel(); + virtual ~Kernel() = default; + + void setIOHandler(std::unique_ptr handler); + + virtual void onStart() {} + virtual void onStop() {} + + public: + Kernel(const Kernel&) = delete; + Kernel& operator =(const Kernel&) = delete; + + /** + * @brief IO context for Z21 kernel and IO handler + * + * @return The IO context + */ + boost::asio::io_context& ioContext() { return m_ioContext; } + + /** + * @brief Get object id used for log messages + * @return The object id + */ + inline const std::string& logId() + { + return m_logId; + } + + /** + * @brief Set object id used for log messages + * @param[in] value The object id + */ + inline void setLogId(std::string value) + { + m_logId = std::move(value); + } + + /** + * @brief ... + * @param[in] callback ... + * @note This function may not be called when the kernel is running. + */ + inline void setOnStarted(std::function callback) + { + assert(!m_started); + m_onStarted = std::move(callback); + } + + /** + * @brief Start the kernel and IO handler + */ + virtual void start(); + + /** + * @brief Stop the kernel and IO handler + */ + virtual void stop(); +}; + +} + +#endif diff --git a/server/src/hardware/protocol/z21/messages.cpp b/server/src/hardware/protocol/z21/messages.cpp index 92f4813f..c182815e 100644 --- a/server/src/hardware/protocol/z21/messages.cpp +++ b/server/src/hardware/protocol/z21/messages.cpp @@ -3,7 +3,7 @@ * * This file is part of the traintastic source code. * - * Copyright (C) 2019-2020 Reinder Feenstra + * Copyright (C) 2019-2022 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -31,6 +31,7 @@ static std::string_view toString(Header header) switch(header) { case LAN_GET_SERIAL_NUMBER: return "LAN_GET_SERIAL_NUMBER"; + case LAN_GET_CODE: return "LAN_GET_CODE"; case LAN_GET_HWINFO: return "LAN_GET_HWINFO"; case LAN_LOGOFF: return "LAN_LOGOFF"; case LAN_X: return "LAN_X"; @@ -38,10 +39,21 @@ static std::string_view toString(Header header) case LAN_GET_BROADCASTFLAGS: return "LAN_GET_BROADCASTFLAGS"; case LAN_GET_LOCO_MODE: return "LAN_GET_LOCO_MODE"; case LAN_SET_LOCO_MODE: return "LAN_SET_LOCO_MODE"; + case LAN_GET_TURNOUTMODE: return "LAN_GET_TURNOUTMODE"; + case LAN_SET_TURNOUTMODE: return "LAN_SET_TURNOUTMODE"; + case LAN_RMBUS_DATACHANGED: return "LAN_RMBUS_DATACHANGED"; + case LAN_RMBUS_GETDATA: return "LAN_RMBUS_GETDATA"; + case LAN_RMBUS_PROGRAMMODULE: return "LAN_RMBUS_PROGRAMMODULE"; case LAN_SYSTEMSTATE_DATACHANGED: return "LAN_SYSTEMSTATE_DATACHANGED"; case LAN_SYSTEMSTATE_GETDATA: return "LAN_SYSTEMSTATE_GETDATA"; + case LAN_RAILCOM_DATACHANGED: return "LAN_RAILCOM_DATACHANGED"; + case LAN_RAILCOM_GETDATA: return "LAN_RAILCOM_GETDATA"; case LAN_LOCONET_Z21_RX: return "LAN_LOCONET_Z21_RX"; case LAN_LOCONET_Z21_TX: return "LAN_LOCONET_Z21_TX"; + case LAN_LOCONET_FROM_LAN: return "LAN_LOCONET_FROM_LAN"; + case LAN_LOCONET_DISPATCH_ADDR: return "LAN_LOCONET_DISPATCH_ADDR"; + case LAN_LOCONET_DETECTOR: return "LAN_LOCONET_DETECTOR"; + case LAN_CAN_DETECTOR: return "LAN_CAN_DETECTOR"; } return {}; } @@ -65,8 +77,23 @@ std::string toString(const Message& message, bool raw) switch(static_cast(message).xheader) { case 0x21: - if(message == LanXGetStatus()) + if(message == LanXGetVersion()) + s = "LAN_X_GET_VERSION"; + else if(message == LanXGetStatus()) s = "LAN_X_GET_STATUS"; + else if(message == LanXSetTrackPowerOff()) + s = "LAN_X_SET_TRACK_POWER_OFF"; + else if(message == LanXSetTrackPowerOn()) + s = "LAN_X_SET_TRACK_POWER_ON"; + else + raw = true; + break; + + case 0x61: + if(message == LanXBCTrackPowerOff()) + s = "LAN_X_BC_TRACK_POWER_OFF"; + else if(message == LanXBCTrackPowerOn()) + s = "LAN_X_BC_TRACK_POWER_ON"; else raw = true; break; @@ -84,28 +111,126 @@ std::string toString(const Message& message, bool raw) raw = true; break; + case 0x80: + if(message == LanXSetStop()) + s = "LAN_X_SET_STOP"; + else + raw = true; + break; + + case 0xE3: + if(const auto& getLocoInfo = static_cast(message); getLocoInfo.db0 == 0xF0) + { + s = "LAN_X_GET_LOCO_INFO"; + s.append(" address=").append(std::to_string(getLocoInfo.address())); + if(getLocoInfo.isLongAddress()) + s.append(" (long)"); + } + else + raw = true; + break; + + case 0xE4: + if(const auto& setLocoDrive = static_cast(message); + setLocoDrive.db0 >= 0x10 && setLocoDrive.db0 <= 0x13) + { + s = "LAN_X_SET_LOCO_DRIVE"; + s.append(" address=").append(std::to_string(setLocoDrive.address())); + if(setLocoDrive.isLongAddress()) + s.append("/long"); + s.append(" direction=").append(setLocoDrive.direction() == Direction::Forward ? "fwd" : "rev"); + s.append(" speed="); + if(setLocoDrive.isEmergencyStop()) + s.append("estop"); + else + s.append(std::to_string(setLocoDrive.speedStep())).append("/").append(std::to_string(setLocoDrive.speedSteps())); + } + else if(const auto& setLocoFunction = static_cast(message); + setLocoFunction.db0 == 0xF8) + { + s = "LAN_X_SET_LOCO_FUNCTION"; + s.append(" address=").append(std::to_string(setLocoFunction.address())); + if(setLocoFunction.isLongAddress()) + s.append("/long"); + s.append(" function=").append(std::to_string(setLocoFunction.functionIndex())); + s.append(" state=").append(toString(setLocoFunction.switchType())); + } + else + raw = true; + break; + + case 0xEF: + { + const auto& locoInfo = static_cast(message); + s = "LAN_X_LOCO_INFO"; + s.append(" address=").append(std::to_string(locoInfo.address())); + if(locoInfo.isLongAddress()) + s.append("/long"); + s.append(" direction=").append(locoInfo.direction() == Direction::Forward ? "fwd" : "rev"); + s.append(" speed="); + if(locoInfo.isEmergencyStop()) + s.append("estop"); + else + s.append(std::to_string(locoInfo.speedStep())).append("/").append(std::to_string(locoInfo.speedSteps())); + for(uint8_t i = 0; i <= LanXLocoInfo::functionIndexMax; i++) + s.append(" f").append(std::to_string(i)).append("=").append(locoInfo.getFunction(i) ? "1" : "0"); + s.append(" busy=").append(locoInfo.isBusy() ? "1" : "0"); + break; + } + case 0xF1: + if(message == LanXGetFirmwareVersion()) + s = "LAN_X_GET_FIRMWARE_VERSION"; + else + raw = true; + break; + + case 0xF3: + if(message.dataLen() == sizeof(LanXGetFirmwareVersionReply)) + { + const auto& getFirmwareVersion = static_cast(message); + s = "LAN_X_GET_FIRMWARE_VERSION"; + s.append(" version=").append(std::to_string(getFirmwareVersion.versionMajor())).append(".").append(std::to_string(getFirmwareVersion.versionMinor())); + } + else + raw = true; + break; + default: raw = true; break; } break; - case Z21::LAN_GET_BROADCASTFLAGS: - if(message.dataLen() == sizeof(Z21::LanGetBroadcastFlags)) + case LAN_GET_BROADCASTFLAGS: + if(message == LanGetBroadcastFlags()) s = "LAN_GET_BROADCASTFLAGS"; - //else if(message.dataLen() == sizeof(Z21::LanGetBroadcastFlagsReply)) - // s = "LAN_GET_BROADCASTFLAGS flags=0x" + toHex(static_cast(message).broadcastFlags())); else raw = true; break; - case Z21::LAN_SET_BROADCASTFLAGS: + case LAN_SET_BROADCASTFLAGS: if(message.dataLen() == sizeof(Z21::LanSetBroadcastFlags)) - s = "LAN_SET_BROADCASTFLAGS flags=0x" + toHex(static_cast(message).broadcastFlags()); + { + s = "LAN_SET_BROADCASTFLAGS"; + s.append(" flags=0x").append(toHex(static_cast>(static_cast(message).broadcastFlags()))); + } else raw = true; break; + case LAN_SYSTEMSTATE_DATACHANGED: + { + const auto& systemState = static_cast(message); + s.append(" mainCurrent=").append(std::to_string(systemState.mainCurrent)); + s.append(" progCurrent=").append(std::to_string(systemState.progCurrent)); + s.append(" filteredMainCurrent=").append(std::to_string(systemState.filteredMainCurrent)); + s.append(" temperature=").append(std::to_string(systemState.temperature)); + s.append(" supplyVoltage=").append(std::to_string(systemState.supplyVoltage)); + s.append(" vccVoltage=").append(std::to_string(systemState.vccVoltage)); + s.append(" centralState=0x").append(toHex(systemState.centralState)); + s.append(" centralStateEx=0x").append(toHex(systemState.centralStateEx)); + break; + } default: raw = true; break; diff --git a/server/src/hardware/protocol/z21/messages.hpp b/server/src/hardware/protocol/z21/messages.hpp index 900e8382..31d99a85 100644 --- a/server/src/hardware/protocol/z21/messages.hpp +++ b/server/src/hardware/protocol/z21/messages.hpp @@ -3,7 +3,7 @@ * * This file is part of the traintastic source code. * - * Copyright (C) 2019-2020 Reinder Feenstra + * Copyright (C) 2019-2022 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -42,6 +42,7 @@ std::string toString(const Message& message, bool raw = false); enum Header : uint16_t { LAN_GET_SERIAL_NUMBER = 0x10, + LAN_GET_CODE = 0x18, LAN_GET_HWINFO = 0x1A, LAN_LOGOFF = 0x30, LAN_X = 0x40, @@ -49,26 +50,90 @@ enum Header : uint16_t LAN_GET_BROADCASTFLAGS = 0x51, LAN_GET_LOCO_MODE = 0x60, LAN_SET_LOCO_MODE = 0x61, + LAN_GET_TURNOUTMODE = 0x70, + LAN_SET_TURNOUTMODE = 0x71, + LAN_RMBUS_DATACHANGED = 0x80, + LAN_RMBUS_GETDATA = 0x81, + LAN_RMBUS_PROGRAMMODULE = 0x82, LAN_SYSTEMSTATE_DATACHANGED = 0x84, LAN_SYSTEMSTATE_GETDATA = 0x85, + LAN_RAILCOM_DATACHANGED = 0x88, + LAN_RAILCOM_GETDATA = 0x89, LAN_LOCONET_Z21_RX = 0xA0, LAN_LOCONET_Z21_TX = 0xA1, + LAN_LOCONET_FROM_LAN = 0xA2, + LAN_LOCONET_DISPATCH_ADDR = 0xA3, + LAN_LOCONET_DETECTOR = 0xA4, + LAN_CAN_DETECTOR = 0xC4, }; -enum BroadcastFlags : uint32_t +enum class BroadcastFlags : uint32_t { - /** - * Broadcasts and info messages concerning driving and switching are delivered to the registered clients automatically. - * The following messages are concerned: - * 2.7 LAN_X_BC_TRACK_POWER_OFF - * 2.8 LAN_X_BC_TRACK_POWER_ON - * 2.9 LAN_X_BC_PROGRAMMING_MODE - * 2.10 LAN_X_BC_TRACK_SHORT_CIRCUIT - * 2.14 LAN_X_BC_STOPPED - * 4.4 LAN_X_LOCO_INFO (loco address must be subscribed too) - * 5.3 LAN_X_TURNOUT_INFO - */ - PowerLocoTurnout = 0x00000001, + None = 0, + + /// Broadcasts and info messages concerning driving and switching are delivered to the registered clients automatically. + /// The following messages are concerned: + /// 2.7 LAN_X_BC_TRACK_POWER_OFF + /// 2.8 LAN_X_BC_TRACK_POWER_ON + /// 2.9 LAN_X_BC_PROGRAMMING_MODE + /// 2.10 LAN_X_BC_TRACK_SHORT_CIRCUIT + /// 2.14 LAN_X_BC_STOPPED + /// 4.4 LAN_X_LOCO_INFO (loco address must be subscribed too) + /// 5.3 LAN_X_TURNOUT_INFO + PowerLocoTurnoutChanges = 0x00000001, + + /// Changes of the feedback devices on the R-Bus are sent automatically. + /// Z21 Broadcast messages see 7.1 LAN_RMBUS_DATACHANGED + RBusChanges = 0x00000002, + + /// Changes of RailCom data of subscribed locomotives are sent automatically. + /// Z21 Broadcast messages see 8.1 LAN_RAILCOM_DATACHANGED + RailCOMChanges = 0x00000004, + + /// Changes of the Z21 system status are sent automatically. + /// Z21 Broadcast messages see 2.18 LAN_SYSTEMSTATE_DATACHANGED + SystemStatusChanges = 0x00000100, + + /// Extends flag 0x00000001; client now gets LAN_X_LOCO_INFO LAN_X_LOCO_INFO + /// without having to subscribe to the corresponding locomotive addresses, i.e. for all + /// controlled locomotives! + /// Due to the high network traffic, this flag may only be used by adequate PC railroad + /// automation software and is NOT intended for mobile hand controllers under any + /// circumstances. + /// From FW V1.20 bis V1.23: LAN_X_LOCO_INFO is sent for all locomotives. + /// From FW V1.24: LAN_X_LOCO_INFO is sent for all modified locomotives. + AllLocoChanges = 0x00010000, + + /// Forwarding messages from LocoNet bus to LAN client without locos and switches. + LocoNetWithoutLocoAndSwitches = 0x01000000, + + /// Forwarding locomotive-specific LocoNet messages to LAN Client: + /// OPC_LOCO_SPD, OPC_LOCO_DIRF, OPC_LOCO_SND, OPC_LOCO_F912, OPC_EXP_CMD + LocoNetLoco = 0x02000000, + + /// Forwarding switch-specific LocoNet messages to LAN client: + /// OPC_SW_REQ, OPC_SW_REP, OPC_SW_ACK, OPC_SW_STATE + LocoNetSwitch = 0x04000000, + + /// Sending status changes of LocoNet track occupancy detectors to the LAN client. + /// See 9.5 LAN_LOCONET_DETECTOR + LocoNetDetector = 0x08000000, + + /// + LocoNet = LocoNetWithoutLocoAndSwitches | LocoNetLoco | LocoNetSwitch | LocoNetDetector, + + /// Version 1.29: + /// Sending changes of RailCom data to the LAN Client. + /// Client gets LAN_RAILCOM_DATACHANGED without having to subscribe to the + /// corresponding locomotive addresses, i.e. for all controlled locomotives! Due to the high + /// network traffic, this flag may only be used by adequate PC railroad automation software + /// and is NOT intended for mobile hand controllers under any circumstances. + /// Z21 Broadcast messages see 8.1 LAN_RAILCOM_DATACHANGED + RailComDataChanged = 0x00040000, + + /// Sending status changes of CAN-Bus track occupancy detectors to the LAN client. + /// See 10.1 LAN_CAN_DETECTOR + CANDetector = 0x00080000, }; enum LocoMode : uint8_t @@ -92,6 +157,7 @@ static constexpr uint8_t LAN_X_LOCO_INFO = 0xEF; enum HardwareType : uint32_t { + HWT_UNKNOWN = 0, HWT_Z21_OLD = 0x00000200, //!< „black Z21” (hardware variant from 2012) HWT_Z21_NEW = 0x00000201, //!< „black Z21”(hardware variant from 2013) HWT_SMARTRAIL = 0x00000202, //!< SmartRail (from 2012) @@ -99,6 +165,37 @@ enum HardwareType : uint32_t HWT_Z21_START = 0x00000204, //!< „z21 start” starter set variant (from 2016) }; +constexpr std::string_view toString(HardwareType value) +{ + switch(value) + { + case HWT_Z21_OLD: + return "Black Z21 (hardware variant from 2012)"; + + case HWT_Z21_NEW: + return "Black Z21 (hardware variant from 2013)"; + + case HWT_SMARTRAIL: + return "SmartRail (from 2012)"; + + case HWT_Z21_SMALL: + return "White Z21 (starter set variant from 2013)"; + + case HWT_Z21_START : + return "Z21 start (starter set variant from 2016)"; + + case HWT_UNKNOWN: + break; + } + + return {}; +} + +enum class CommandStationId : uint8_t +{ + Z21 = 0x12, +}; + #define Z21_CENTRALSTATE_EMERGENCYSTOP 0x01 //!< The emergency stop is switched on #define Z21_CENTRALSTATE_TRACKVOLTAGEOFF 0x02 //!< The track voltage is switched off #define Z21_CENTRALSTATE_SHORTCIRCUIT 0x04 //!< Short circuit @@ -195,6 +292,19 @@ struct LanLogoff : Message static_assert(sizeof(LanLogoff) == 4); // LAN_X_GET_VERSION +struct LanXGetVersion : LanX +{ + uint8_t db0 = 0x21; + uint8_t checksum = 0x00; + + LanXGetVersion() : + LanX(sizeof(LanXGetVersion), 0x21) + { + } +} ATTRIBUTE_PACKED; +static_assert(sizeof(LanXGetVersion) == 7); + +// LAN_X_GET_FIRMWARE_VERSION struct LanXGetFirmwareVersion : LanX { uint8_t db0 = 0x0A; @@ -313,9 +423,6 @@ static_assert(sizeof(LanXGetLocoInfo) == 9); // LAN_X_SET_LOCO_DRIVE struct LanXSetLocoDrive : LanX { - //static constexpr uint8_t directionFlag = 0x80; - - //uint8_t xheader = 0xe4; uint8_t db0; uint8_t addressHigh; uint8_t addressLow; @@ -337,6 +444,12 @@ struct LanXSetLocoDrive : LanX return (addressHigh & 0xC0) == 0xC0; } + inline void setAddress(uint16_t address, bool longAddress) + { + addressHigh = longAddress ? (0xC0 | (address >> 8)) : 0x00; + addressLow = longAddress ? address & 0xFF : address & 0x7F; + } + inline uint8_t speedSteps() const { switch(db0 & 0x0F) @@ -391,10 +504,15 @@ struct LanXSetLocoFunction : LanX Invalid = 3, }; + static constexpr uint8_t functionNumberMax = 28; + static constexpr uint8_t functionNumberMask = 0x3F; + static constexpr uint8_t switchTypeMask = 0xC0; + static constexpr uint8_t switchTypeShift = 6; + uint8_t db0 = 0xf8; uint8_t addressHigh; uint8_t addressLow; - uint8_t db3; + uint8_t db3 = 0; uint8_t checksum; LanXSetLocoFunction() : @@ -402,6 +520,15 @@ struct LanXSetLocoFunction : LanX { } + LanXSetLocoFunction(uint16_t address, bool longAddress, uint8_t functionIndex, SwitchType value) + : LanXSetLocoFunction() + { + setAddress(address, longAddress); + setFunctionIndex(functionIndex); + setSwitchType(value); + calcChecksum(); + } + inline uint16_t address() const { return (static_cast(addressHigh & 0x3F) << 8) | addressLow; @@ -412,14 +539,31 @@ struct LanXSetLocoFunction : LanX return (addressHigh & 0xC0) == 0xC0; } + inline void setAddress(uint16_t address, bool longAddress) + { + addressHigh = longAddress ? (0xC0 | (address >> 8)) : 0x00; + addressLow = longAddress ? address & 0xFF : address & 0x7F; + } + inline SwitchType switchType() const { - return static_cast(db3 >> 6); + return static_cast(db3 >> switchTypeShift); + } + + inline void setSwitchType(SwitchType value) + { + db3 = (db3 & functionNumberMask) | (static_cast(value) << switchTypeShift); } inline uint8_t functionIndex() const { - return db3 & 0x3F; + return db3 & functionNumberMask; + } + + inline void setFunctionIndex(uint8_t value) + { + assert(value <= functionNumberMax); + db3 = (db3 & switchTypeMask) | (value & functionNumberMask); } } ATTRIBUTE_PACKED; static_assert(sizeof(LanXSetLocoFunction) == 10); @@ -443,7 +587,7 @@ struct LanSetBroadcastFlags : Message { BroadcastFlags broadcastFlagsLE; // LE - LanSetBroadcastFlags(uint32_t _broadcastFlags = 0) : + LanSetBroadcastFlags(BroadcastFlags _broadcastFlags = BroadcastFlags::None) : Message(sizeof(LanSetBroadcastFlags), LAN_SET_BROADCASTFLAGS), broadcastFlagsLE{host_to_le(_broadcastFlags)} { @@ -453,7 +597,6 @@ struct LanSetBroadcastFlags : Message { return le_to_host(broadcastFlagsLE); } - } ATTRIBUTE_PACKED; static_assert(sizeof(LanSetBroadcastFlags) == 8); @@ -569,6 +712,40 @@ struct LanGetSerialNumberReply : Message } ATTRIBUTE_PACKED; static_assert(sizeof(LanGetSerialNumberReply) == 8); +// Reply to LAN_X_GET_VERSION +struct LanXGetVersionReply : LanX +{ + uint8_t db0 = 0x21; + uint8_t xBusVersionBCD; + CommandStationId commandStationId; + uint8_t checksum; + + LanXGetVersionReply() + : LanX(sizeof(LanXGetVersionReply), 0x63) + { + } + + LanXGetVersionReply(uint8_t _xBusVersion, CommandStationId _commandStationId) + : LanXGetVersionReply() + { + setXBusVersion(_xBusVersion); + commandStationId = _commandStationId; + calcChecksum(); + } + + inline uint8_t xBusVersion() const + { + return Utils::fromBCD(xBusVersionBCD); + } + + inline void setXBusVersion(uint8_t value) + { + assert(value < 100); + xBusVersionBCD = Utils::toBCD(value); + } +}; +static_assert(sizeof(LanXGetVersionReply) == 9); + // Reply to LAN_GET_CODE struct LanXGetFirmwareVersionReply : LanX { @@ -724,6 +901,7 @@ struct LanXLocoInfo : LanX static constexpr uint8_t directionFlag = 0x80; static constexpr uint8_t speedStepMask = 0x7F; static constexpr uint8_t flagF0 = 0x10; + static constexpr uint8_t functionIndexMax = 28; uint8_t addressHigh = 0; uint8_t addressLow = 0; @@ -825,7 +1003,7 @@ struct LanXLocoInfo : LanX Z21::Utils::setSpeedStep(speedAndDirection, speedSteps(), value); } - bool getFunction(uint8_t index) + bool getFunction(uint8_t index) const { if(index == 0) return db4 & flagF0; @@ -965,6 +1143,22 @@ static_assert(sizeof(LanSystemStateDataChanged) == 20); PRAGMA_PACK_POP +constexpr std::string_view toString(LanXSetLocoFunction::SwitchType value) +{ + switch(value) + { + case LanXSetLocoFunction::SwitchType::Off: + return "off"; + case LanXSetLocoFunction::SwitchType::On: + return "on"; + case LanXSetLocoFunction::SwitchType::Toggle: + return "toggle"; + case LanXSetLocoFunction::SwitchType::Invalid: + return "invalid"; + } + return {}; +} + } inline bool operator ==(const Z21::Message& lhs, const Z21::Message& rhs) @@ -972,4 +1166,18 @@ inline bool operator ==(const Z21::Message& lhs, const Z21::Message& rhs) return lhs.dataLen() == rhs.dataLen() && std::memcmp(&lhs, &rhs, lhs.dataLen()) == 0; } +constexpr Z21::BroadcastFlags operator |(Z21::BroadcastFlags lhs, Z21::BroadcastFlags rhs) +{ + return static_cast( + static_cast>(lhs) | + static_cast>(rhs)); +} + +constexpr Z21::BroadcastFlags operator &(Z21::BroadcastFlags lhs, Z21::BroadcastFlags rhs) +{ + return static_cast( + static_cast>(lhs) & + static_cast>(rhs)); +} + #endif diff --git a/server/src/hardware/protocol/z21/serverkernel.cpp b/server/src/hardware/protocol/z21/serverkernel.cpp new file mode 100644 index 00000000..ec0254e9 --- /dev/null +++ b/server/src/hardware/protocol/z21/serverkernel.cpp @@ -0,0 +1,439 @@ +/** + * server/src/hardware/protocol/z21/serverkernel.cpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2019-2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "serverkernel.hpp" +#include "messages.hpp" +#include "../xpressnet/messages.hpp" +#include "../../decoder/decoderlist.hpp" +#include "../../../core/eventloop.hpp" +#include "../../../log/log.hpp" + +namespace Z21 { + +ServerKernel::ServerKernel(const ServerConfig& config, std::shared_ptr decoderList) + : Kernel() + , m_inactiveClientPurgeTimer{m_ioContext} + , m_config{config} + , m_decoderList{std::move(decoderList)} +{ +} + +void ServerKernel::setConfig(const ServerConfig& config) +{ + m_ioContext.post( + [this, newConfig=config]() + { + m_config = newConfig; + }); +} + +void ServerKernel::setState(bool trackPowerOn, bool emergencyStop) +{ + m_ioContext.post( + [this, trackPowerOn, emergencyStop]() + { + const auto trackPowerOnTri = toTriState(trackPowerOn); + const auto emergencyStopTri = toTriState(emergencyStop); + + const bool trackPowerOnChanged = m_trackPowerOn != trackPowerOnTri; + const bool emergencyStopChanged = m_emergencyStop != emergencyStopTri; + + m_trackPowerOn = trackPowerOnTri; + m_emergencyStop = emergencyStopTri; + + if(emergencyStopChanged && m_emergencyStop == TriState::True) + sendTo(LanXBCStopped(), BroadcastFlags::PowerLocoTurnoutChanges); + + if(trackPowerOnChanged && m_trackPowerOn == TriState::False) + sendTo(LanXBCTrackPowerOff(), BroadcastFlags::PowerLocoTurnoutChanges); + + if((trackPowerOnChanged || emergencyStopChanged) && m_trackPowerOn == TriState::True && m_emergencyStop == TriState::False) + sendTo(LanXBCTrackPowerOn(), BroadcastFlags::PowerLocoTurnoutChanges); + + if(trackPowerOnChanged || emergencyStopChanged) + sendTo(getLanSystemStateDataChanged(), BroadcastFlags::PowerLocoTurnoutChanges); + }); +} + +void ServerKernel::receiveFrom(const Message& message, IOHandler::ClientId clientId) +{ + if(m_config.debugLogRXTX) + EventLoop::call( + [this, clientId, msg=toString(message)]() + { + Log::log(m_logId, LogMessage::D2005_X_RX_X, clientId, msg); + }); + + m_clients[clientId].lastSeen = std::chrono::steady_clock::now(); + + switch(message.header()) + { + case LAN_X: + { + const auto& lanX = static_cast(message); + + if(!XpressNet::isChecksumValid(*reinterpret_cast(&lanX.xheader))) + break; + + switch(lanX.xheader) + { + case 0x21: + if(message == LanXGetVersion()) + { + sendTo(LanXGetVersionReply(ServerConfig::xBusVersion, ServerConfig::commandStationId), clientId); + } + else if(message == LanXGetStatus()) + { + LanXStatusChanged response; + if(m_emergencyStop != TriState::False) + response.db1 |= Z21_CENTRALSTATE_EMERGENCYSTOP; + if(m_trackPowerOn != TriState::True) + response.db1 |= Z21_CENTRALSTATE_TRACKVOLTAGEOFF; + response.calcChecksum(); + sendTo(response, clientId); + } + else if(message == LanXSetTrackPowerOn()) + { + if(m_config.allowTrackPowerOnReleaseEmergencyStop && (m_trackPowerOn != TriState::True || m_emergencyStop != TriState::False) && m_onTrackPowerOn) + { + EventLoop::call( + [this]() + { + m_onTrackPowerOn(); + }); + } + } + else if(message == LanXSetTrackPowerOff()) + { + if(m_config.allowTrackPowerOff && m_trackPowerOn != TriState::False && m_onTrackPowerOff) + { + EventLoop::call( + [this]() + { + m_onTrackPowerOff(); + }); + } + } + break; + + case 0x80: + if(message == LanXSetStop()) + { + if(m_config.allowEmergencyStop && m_emergencyStop != TriState::True && m_onEmergencyStop) + { + EventLoop::call( + [this]() + { + m_onEmergencyStop(); + }); + } + } + break; + + case 0xE3: + if(const auto& getLocoInfo = static_cast(message); + getLocoInfo.db0 == 0xF0) + { + subscribe(clientId, getLocoInfo.address(), getLocoInfo.isLongAddress()); + + EventLoop::call( + [this, getLocoInfo, clientId]() + { + if(auto decoder = getDecoder(getLocoInfo.address(), getLocoInfo.isLongAddress())) + postSendTo(LanXLocoInfo(*decoder), clientId); + }); + } + break; + + case 0xE4: + if(const auto& setLocoDrive = static_cast(message); + setLocoDrive.db0 >= 0x10 && setLocoDrive.db0 <= 0x13) + { + subscribe(clientId, setLocoDrive.address(), setLocoDrive.isLongAddress()); + + EventLoop::call( + [this, setLocoDrive]() + { + if(auto decoder = getDecoder(setLocoDrive.address(), setLocoDrive.isLongAddress())) + { + decoder->direction = setLocoDrive.direction(); + decoder->emergencyStop = setLocoDrive.isEmergencyStop(); + decoder->throttle = Decoder::speedStepToThrottle(setLocoDrive.speedStep(), setLocoDrive.speedSteps()); + } + //else + // Log::log(*this, LogMessage::I2001_UNKNOWN_LOCO_ADDRESS_X, setLocoDrive.address()); + }); + } + else if(const auto& setLocoFunction = static_cast(message); + setLocoFunction.db0 == 0xF8 && + setLocoFunction.switchType() != LanXSetLocoFunction::SwitchType::Invalid) + { + subscribe(clientId, setLocoFunction.address(), setLocoFunction.isLongAddress()); + + EventLoop::call( + [this, setLocoFunction]() + { + if(auto decoder = getDecoder(setLocoFunction.address(), setLocoFunction.isLongAddress())) + { + if(auto function = decoder->getFunction(setLocoFunction.functionIndex())) + { + switch(setLocoFunction.switchType()) + { + case LanXSetLocoFunction::SwitchType::Off: + function->value = false; + break; + + case LanXSetLocoFunction::SwitchType::On: + function->value = true; + break; + + case LanXSetLocoFunction::SwitchType::Toggle: + function->value = !function->value; + break; + + case LanXSetLocoFunction::SwitchType::Invalid: + assert(false); + break; + } + } + } + }); + } + break; + + case 0xF1: + if(message == LanXGetFirmwareVersion()) + sendTo(LanXGetFirmwareVersionReply(ServerConfig::firmwareVersionMajor, ServerConfig::firmwareVersionMinor), clientId); + break; + } + break; + } + case LAN_GET_LOCO_MODE: + if(message.dataLen() == sizeof(LanGetLocoMode)) + sendTo(LanGetLocoModeReply(static_cast(message).address(), LocoMode::DCC), clientId); + break; + + case LAN_SET_LOCO_MODE: + // ignore, we always report DCC + break; + + case LAN_GET_SERIAL_NUMBER: + if(message.dataLen() == sizeof(LanGetSerialNumber)) + sendTo(LanGetSerialNumberReply(ServerConfig::serialNumber), clientId); + break; + + case LAN_GET_HWINFO: + if(message.dataLen() == sizeof(LanGetHardwareInfo)) + sendTo(LanGetHardwareInfoReply(ServerConfig::hardwareType, ServerConfig::firmwareVersionMajor, ServerConfig::firmwareVersionMinor), clientId); + break; + + case LAN_GET_BROADCASTFLAGS: + if(message == LanGetBroadcastFlags()) + sendTo(LanSetBroadcastFlags(m_clients[clientId].broadcastFlags), clientId); + break; + + case LAN_SET_BROADCASTFLAGS: + if(message.dataLen() == sizeof(LanSetBroadcastFlags)) + m_clients[clientId].broadcastFlags = static_cast(message).broadcastFlags(); + break; + + case LAN_SYSTEMSTATE_GETDATA: + if(message == LanSystemStateGetData()) + sendTo(getLanSystemStateDataChanged(), clientId); + break; + + case LAN_LOGOFF: + if(message == LanLogoff()) + m_clients.erase(clientId); + break; + + case LAN_GET_CODE: + case LAN_GET_TURNOUTMODE: + case LAN_SET_TURNOUTMODE: + case LAN_RMBUS_DATACHANGED: + case LAN_RMBUS_GETDATA: + case LAN_RMBUS_PROGRAMMODULE: + case LAN_SYSTEMSTATE_DATACHANGED: + case LAN_RAILCOM_DATACHANGED: + case LAN_RAILCOM_GETDATA: + case LAN_LOCONET_Z21_RX: + case LAN_LOCONET_Z21_TX: + case LAN_LOCONET_FROM_LAN: + case LAN_LOCONET_DISPATCH_ADDR: + case LAN_LOCONET_DETECTOR: + case LAN_CAN_DETECTOR: + break; // not (yet) supported + } +} + +void ServerKernel::onStart() +{ + startInactiveClientPurgeTimer(); +} + +void ServerKernel::onStop() +{ + m_inactiveClientPurgeTimer.cancel(); +} + +void ServerKernel::sendTo(const Message& message, IOHandler::ClientId clientId) +{ + if(m_ioHandler->sendTo(message, clientId)) + { + if(m_config.debugLogRXTX) + EventLoop::call( + [this, clientId, msg=toString(message)]() + { + Log::log(m_logId, LogMessage::D2004_X_TX_X, clientId, msg); + }); + } + else + {} // log message and go to error state +} + +void ServerKernel::sendTo(const Message& message, BroadcastFlags broadcastFlags) +{ + for(const auto& client : m_clients) + if((client.second.broadcastFlags & broadcastFlags) != BroadcastFlags::None) + sendTo(message, client.first); +} + +LanSystemStateDataChanged ServerKernel::getLanSystemStateDataChanged() const +{ + LanSystemStateDataChanged message; + + if(m_emergencyStop != TriState::False) + message.centralState |= Z21_CENTRALSTATE_EMERGENCYSTOP; + if(m_trackPowerOn != TriState::True) + message.centralState |= Z21_CENTRALSTATE_TRACKVOLTAGEOFF; + + return message; +} + +std::shared_ptr ServerKernel::getDecoder(uint16_t address, bool longAddress) const +{ + auto decoder = m_decoderList->getDecoder(DecoderProtocol::DCC, address, longAddress); + if(!decoder) + decoder = m_decoderList->getDecoder(DecoderProtocol::Auto, address); + if(!decoder) + decoder = m_decoderList->getDecoder(address); + return decoder; +} + +void ServerKernel::removeClient(IOHandler::ClientId clientId) +{ + auto& subscriptions = m_clients[clientId].subscriptions; + while(!subscriptions.empty()) + unsubscribe(clientId, *subscriptions.begin()); + m_clients.erase(clientId); + m_ioHandler->purgeClient(clientId); +} + +void ServerKernel::subscribe(IOHandler::ClientId clientId, uint16_t address, bool longAddress) +{ + auto& subscriptions = m_clients[clientId].subscriptions; + const std::pair key{address, longAddress}; + if(std::find(subscriptions.begin(), subscriptions.end(), key) != subscriptions.end()) + return; + subscriptions.emplace_back(address, longAddress); + if(subscriptions.size() > ServerConfig::subscriptionMax) + unsubscribe(clientId, *subscriptions.begin()); + + EventLoop::call( + [this, key]() + { + if(auto it = m_decoderSubscriptions.find(key); it == m_decoderSubscriptions.end()) + { + if(auto decoder = getDecoder(key.first, key.second)) + m_decoderSubscriptions.emplace(key, DecoderSubscription{decoder->decoderChanged.connect(std::bind(&ServerKernel::decoderChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)), 1}); + } + else + { + it->second.count++; + } + }); +} + +void ServerKernel::unsubscribe(IOHandler::ClientId clientId, std::pair key) +{ + { + auto& subscriptions = m_clients[clientId].subscriptions; + auto it = std::find(subscriptions.begin(), subscriptions.end(), key); + if(it != subscriptions.end()) + subscriptions.erase(it); + } + + EventLoop::call( + [this, key]() + { + if(auto it = m_decoderSubscriptions.find(key); it != m_decoderSubscriptions.end()) + { + assert(it->second.count > 0); + if(--it->second.count == 0) + m_decoderSubscriptions.erase(it); + } + }); +} + +void ServerKernel::decoderChanged(const Decoder& decoder, DecoderChangeFlags /*changes*/, uint32_t /*functionNumber*/) +{ + const std::pair key(decoder.address, decoder.longAddress); + const LanXLocoInfo message(decoder); + + EventLoop::call( + [this, key, message]() + { + for(auto it : m_clients) + if((it.second.broadcastFlags & BroadcastFlags::PowerLocoTurnoutChanges) == BroadcastFlags::PowerLocoTurnoutChanges) + { + auto& subscriptions = it.second.subscriptions; + if(std::find(subscriptions.begin(), subscriptions.end(), key) != subscriptions.end()) + sendTo(message, it.first); + } + }); +} + +void ServerKernel::startInactiveClientPurgeTimer() +{ + assert(m_config.inactiveClientPurgeTime > 0); + m_inactiveClientPurgeTimer.expires_after(boost::asio::chrono::seconds(std::max(1, m_config.inactiveClientPurgeTime / 4))); + m_inactiveClientPurgeTimer.async_wait(std::bind(&ServerKernel::inactiveClientPurgeTimerExpired, this, std::placeholders::_1)); +} + +void ServerKernel::inactiveClientPurgeTimerExpired(const boost::system::error_code& ec) +{ + if(ec) + return; + + std::vector clientsToRemove; + const auto purgeTime = std::chrono::steady_clock::now() - std::chrono::seconds(ServerConfig::inactiveClientPurgeTime); + for(const auto& it : m_clients) + if(it.second.lastSeen < purgeTime) + clientsToRemove.emplace_back(it.first); + + for(const auto& clientId : clientsToRemove) + removeClient(clientId); + + startInactiveClientPurgeTimer(); +} + +} diff --git a/server/src/hardware/protocol/z21/serverkernel.hpp b/server/src/hardware/protocol/z21/serverkernel.hpp new file mode 100644 index 00000000..2aead98f --- /dev/null +++ b/server/src/hardware/protocol/z21/serverkernel.hpp @@ -0,0 +1,169 @@ +/** + * server/src/hardware/protocol/z21/serverkernel.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2019-2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_Z21_SERVERKERNEL_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_Z21_SERVERKERNEL_HPP + +#include "kernel.hpp" +#include +#include +#include +#include +#include +#include "messages.hpp" + +class DecoderList; + +namespace Z21 { + +class ServerKernel final : public Kernel +{ + private: + struct Client + { + std::chrono::time_point lastSeen; + BroadcastFlags broadcastFlags = BroadcastFlags::None; + std::list> subscriptions; + }; + + struct DecoderSubscription + { + boost::signals2::connection connection; + size_t count; //!< number of clients subscribed to the decoder + }; + + boost::asio::steady_timer m_inactiveClientPurgeTimer; + ServerConfig m_config; + std::shared_ptr m_decoderList; + std::unordered_map m_clients; + std::map, DecoderSubscription> m_decoderSubscriptions; + TriState m_trackPowerOn = TriState::Undefined; + std::function m_onTrackPowerOff; + std::function m_onTrackPowerOn; + TriState m_emergencyStop = TriState::Undefined; + std::function m_onEmergencyStop; + + ServerKernel(const ServerConfig& config, std::shared_ptr decoderList); + + void onStart() final; + void onStop() final; + + template + void postSendTo(const T& message, IOHandler::ClientId clientId) + { + m_ioContext.post( + [this, message, clientId]() + { + sendTo(message, clientId); + }); + } + + void sendTo(const Message& message, IOHandler::ClientId clientId); + void sendTo(const Message& message, BroadcastFlags broadcastFlags); + + LanSystemStateDataChanged getLanSystemStateDataChanged() const; + + std::shared_ptr getDecoder(uint16_t address, bool longAddress) const; + + void removeClient(IOHandler::ClientId clientId); + void subscribe(IOHandler::ClientId clientId, uint16_t address, bool longAddress); + void unsubscribe(IOHandler::ClientId clientId, std::pair key); + void decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber); + + void startInactiveClientPurgeTimer(); + void inactiveClientPurgeTimerExpired(const boost::system::error_code& ec); + + public: + /** + * @brief Create kernel and IO handler + * @param[in] config Z21 server configuration + * @param[in] args IO handler arguments + * @return The kernel instance + */ + template + static std::unique_ptr create(const ServerConfig& config, std::shared_ptr decoderList, Args... args) + { + static_assert(std::is_base_of_v); + std::unique_ptr kernel{new ServerKernel(config, std::move(decoderList))}; + kernel->setIOHandler(std::make_unique(*kernel, std::forward(args)...)); + return kernel; + } + + /** + * @brief ... + * @param[in] callback ... + * @note This function may not be called when the kernel is running. + */ + inline void setOnTrackPowerOff(std::function callback) + { + assert(!m_started); + m_onTrackPowerOff = std::move(callback); + } + + /** + * @brief ... + * @param[in] callback ... + * @note This function may not be called when the kernel is running. + */ + inline void setOnTrackPowerOn(std::function callback) + { + assert(!m_started); + m_onTrackPowerOn = std::move(callback); + } + + /** + * @brief ... + * @param[in] callback ... + * @note This function may not be called when the kernel is running. + */ + inline void setOnEmergencyStop(std::function callback) + { + assert(!m_started); + m_onEmergencyStop = std::move(callback); + } + + /** + * @brief Set Z21 server configuration + * @param[in] config The Z21 server configuration + */ + void setConfig(const ServerConfig& config); + + /** + * @brief Set Z21 state + * @param[in] trackPowerOn \c true if track power is on, \c false if track power is off + * @param[in] emergencyStop \c true if emergency stop is active, \c false if emergency stop isn't active + */ + void setState(bool trackPowerOn, bool emergencyStop); + + /** + * @brief Incoming message handler + * This method must be called by the IO handler whenever a Z21 message is received. + * @param[in] message The received Z21 message + * @param[in] cliendId The client who sent the message + * @note This function must run in the kernel's IO context + */ + void receiveFrom(const Message& message, IOHandler::ClientId clientId); +}; + +} + +#endif diff --git a/server/src/hardware/protocol/z21/serversettings.cpp b/server/src/hardware/protocol/z21/serversettings.cpp new file mode 100644 index 00000000..af6052a3 --- /dev/null +++ b/server/src/hardware/protocol/z21/serversettings.cpp @@ -0,0 +1,51 @@ +/** + * server/src/hardware/protocol/z21/serversettings.cpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021-2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "serversettings.hpp" + +namespace Z21 { + +ServerSettings::ServerSettings(Object& _parent, const std::string& parentPropertyName) + : Settings(_parent, parentPropertyName) + , allowEmergencyStop{this, "allow_emergency_stop", true, PropertyFlags::ReadWrite | PropertyFlags::Store} + , allowTrackPowerOff{this, "allow_track_power_off", true, PropertyFlags::ReadWrite | PropertyFlags::Store} + , allowTrackPowerOnReleaseEmergencyStop{this, "allow_track_power_on_release_emergency_stop", false, PropertyFlags::ReadWrite | PropertyFlags::Store} +{ + m_interfaceItems.insertBefore(allowEmergencyStop, debugLogRXTX); + m_interfaceItems.insertBefore(allowTrackPowerOff, debugLogRXTX); + m_interfaceItems.insertBefore(allowTrackPowerOnReleaseEmergencyStop, debugLogRXTX); +} + +ServerConfig ServerSettings::config() const +{ + ServerConfig config; + + getConfig(config); + + config.allowEmergencyStop = allowEmergencyStop; + config.allowTrackPowerOff = allowTrackPowerOff; + config.allowTrackPowerOnReleaseEmergencyStop = allowTrackPowerOnReleaseEmergencyStop; + + return config; +} + +} diff --git a/server/src/hardware/protocol/z21/serversettings.hpp b/server/src/hardware/protocol/z21/serversettings.hpp new file mode 100644 index 00000000..c5d90677 --- /dev/null +++ b/server/src/hardware/protocol/z21/serversettings.hpp @@ -0,0 +1,46 @@ +/** + * server/src/hardware/protocol/z21/serversettings.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021-2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_Z21_SERVERSETTINGS_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_Z21_SERVERSETTINGS_HPP + +#include "settings.hpp" + +namespace Z21 { + +class ServerSettings final : public Settings +{ + CLASS_ID("z21_settings.server") + + public: + Property allowEmergencyStop; + Property allowTrackPowerOff; + Property allowTrackPowerOnReleaseEmergencyStop; + + ServerSettings(Object& _parent, const std::string& parentPropertyName); + + ServerConfig config() const; +}; + +} + +#endif diff --git a/server/src/hardware/protocol/loconet/loconetlist.cpp b/server/src/hardware/protocol/z21/settings.cpp similarity index 55% rename from server/src/hardware/protocol/loconet/loconetlist.cpp rename to server/src/hardware/protocol/z21/settings.cpp index 18dfd6f4..acdcb878 100644 --- a/server/src/hardware/protocol/loconet/loconetlist.cpp +++ b/server/src/hardware/protocol/z21/settings.cpp @@ -1,9 +1,9 @@ /** - * server/src/hardware/protocol/loconet/loconetlist.cpp + * server/src/hardware/protocol/z21/settings.cpp * * This file is part of the traintastic source code. * - * Copyright (C) 2019-2020 Reinder Feenstra + * Copyright (C) 2019-2022 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -20,20 +20,23 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "loconetlist.hpp" -#include "loconetlisttablemodel.hpp" +#include "settings.hpp" +#include "../../../core/attributes.hpp" +#include "../../../utils/displayname.hpp" -LocoNetList::LocoNetList(Object& _parent, const std::string& parentPropertyName) : - ObjectList(_parent, parentPropertyName) +namespace Z21 { + +Settings::Settings(Object& _parent, const std::string& parentPropertyName) + : SubObject(_parent, parentPropertyName) + , debugLogRXTX{this, "debug_log_rx_tx", false, PropertyFlags::ReadWrite | PropertyFlags::Store} { + Attributes::addDisplayName(debugLogRXTX, DisplayName::Hardware::debugLogRXTX); + m_interfaceItems.add(debugLogRXTX); } -TableModelPtr LocoNetList::getModel() +void Settings::getConfig(Config& config) const { - return std::make_shared(*this); + config.debugLogRXTX = debugLogRXTX; } -bool LocoNetList::isListedProperty(const std::string& name) -{ - return LocoNetListTableModel::isListedProperty(name); } diff --git a/server/src/hardware/commandstation/virtualcommandstation.hpp b/server/src/hardware/protocol/z21/settings.hpp similarity index 60% rename from server/src/hardware/commandstation/virtualcommandstation.hpp rename to server/src/hardware/protocol/z21/settings.hpp index 461b2969..f9e8070f 100644 --- a/server/src/hardware/commandstation/virtualcommandstation.hpp +++ b/server/src/hardware/protocol/z21/settings.hpp @@ -1,9 +1,9 @@ /** - * server/src/hardware/commandstation/virtualcommandstation.hpp + * server/src/hardware/protocol/z21/settings.hpp * * This file is part of the traintastic source code. * - * Copyright (C) 2019-2020 Reinder Feenstra + * Copyright (C) 2019-2022 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -20,21 +20,26 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef TRAINTASTIC_SERVER_HARDWARE_COMMANDSTATION_VIRTUALCOMMANDSTATION_HPP -#define TRAINTASTIC_SERVER_HARDWARE_COMMANDSTATION_VIRTUALCOMMANDSTATION_HPP +#ifndef TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_Z21_SETTINGS_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_Z21_SETTINGS_HPP -#include "commandstation.hpp" +#include "../../../core/subobject.hpp" +#include "../../../core/property.hpp" +#include "config.hpp" -class VirtualCommandStation : public CommandStation +namespace Z21 { + +class Settings : public SubObject { protected: - bool setOnline(bool& value) final; + Settings(Object& _parent, const std::string& parentPropertyName); + + void getConfig(Config& config) const; public: - CLASS_ID("command_station.virtual") - CREATE(VirtualCommandStation) - - VirtualCommandStation(const std::weak_ptr& world, std::string_view _id); + Property debugLogRXTX; }; +} + #endif diff --git a/server/src/hardware/protocol/z21/utils.hpp b/server/src/hardware/protocol/z21/utils.hpp index d033cc16..7ef253a6 100644 --- a/server/src/hardware/protocol/z21/utils.hpp +++ b/server/src/hardware/protocol/z21/utils.hpp @@ -3,7 +3,7 @@ * * This file is part of the traintastic source code. * - * Copyright (C) 2019-2020 Reinder Feenstra + * Copyright (C) 2019-2022 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -63,6 +63,9 @@ constexpr void setEmergencyStop(uint8_t& db) constexpr uint8_t getSpeedStep(uint8_t db, uint8_t speedSteps) { + if(isEmergencyStop(db, speedSteps)) + return 0; + switch(speedSteps) { case 126: @@ -70,7 +73,7 @@ constexpr uint8_t getSpeedStep(uint8_t db, uint8_t speedSteps) break; case 28: - db = ((db & 0x0F) << 1) | ((db & 0x10) >> 4); + db = ((db & 0x0F) << 1) | ((db & 0x10) >> 4); //! @todo check break; case 14: diff --git a/server/src/log/appendarguments.hpp b/server/src/log/appendarguments.hpp new file mode 100644 index 00000000..3f5bc654 --- /dev/null +++ b/server/src/log/appendarguments.hpp @@ -0,0 +1,56 @@ +/** + * server/src/log/appendarguments.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_LOG_APPENDARGUMENTS_HPP +#define TRAINTASTIC_SERVER_LOG_APPENDARGUMENTS_HPP + +#include +#include +#include +#include +#include +#include +#include "../core/object.hpp" + +template +inline static void appendArguments(std::vector& list, const T& value, const Ts&... others) +{ + if constexpr(std::is_same_v || std::is_same_v || std::is_same_v) + list.emplace_back(value); + else if constexpr((std::is_integral_v && !std::is_enum_v) || std::is_floating_point_v) + list.emplace_back(std::to_string(value)); + else if constexpr(std::is_same_v || std::is_same_v) + list.emplace_back(value.message()); + else if constexpr(std::is_same_v) + list.emplace_back(value.string()); + else if constexpr(std::is_base_of_v) + list.emplace_back(value.getObjectId()); + else if constexpr(std::is_base_of_v) + list.emplace_back(value.what()); + else + list.emplace_back(toString(value)); + + if constexpr(sizeof...(Ts) > 0) + appendArguments(list, std::forward(others)...); +} + +#endif diff --git a/server/src/log/log.hpp b/server/src/log/log.hpp index 4ce455e0..7e6d049c 100644 --- a/server/src/log/log.hpp +++ b/server/src/log/log.hpp @@ -25,11 +25,8 @@ #include #include -#include -#include #include -#include -#include "../core/object.hpp" +#include "appendarguments.hpp" class Logger; class MemoryLogger; @@ -41,28 +38,6 @@ class Log Log() = default; - template - inline static void append(std::vector& list, const T& value, const Ts&... others) - { - if constexpr(std::is_same_v || std::is_same_v || std::is_same_v) - list.emplace_back(value); - else if constexpr((std::is_integral_v && !std::is_enum_v) || std::is_floating_point_v) - list.emplace_back(std::to_string(value)); - else if constexpr(std::is_same_v || std::is_same_v) - list.emplace_back(value.message()); - else if constexpr(std::is_same_v) - list.emplace_back(value.string()); - else if constexpr(std::is_base_of_v) - list.emplace_back(value.getObjectId()); - else if constexpr(std::is_base_of_v) - list.emplace_back(value.what()); - else - list.emplace_back(toString(value)); - - if constexpr(sizeof...(Ts) > 0) - append(list, std::forward(others)...); - } - template inline static T* get() { @@ -111,12 +86,27 @@ class Log log(object.getObjectId(), message); } + inline static void log(std::string objectId, LogMessage message, const std::vector& args) + { + logFormatted(std::move(objectId), message, args); + } + + inline static void log(std::string_view objectId, LogMessage message, const std::vector& args) + { + logFormatted(std::string{objectId}, message, args); + } + + inline static void log(const Object& object, LogMessage message, const std::vector& args) + { + logFormatted(object.getObjectId(), message, args); + } + template static void log(std::string objectId, LogMessage message, const Args&... args) { std::vector list; list.reserve(sizeof...(Args)); - append(list, std::forward(args)...); + appendArguments(list, std::forward(args)...); logFormatted(std::move(objectId), message, std::move(list)); } @@ -125,7 +115,7 @@ class Log { std::vector list; list.reserve(sizeof...(Args)); - append(list, std::forward(args)...); + appendArguments(list, std::forward(args)...); logFormatted(std::string{objectId}, message, std::move(list)); } diff --git a/server/src/log/logmessageexception.hpp b/server/src/log/logmessageexception.hpp index 4784b9d1..48a469a7 100644 --- a/server/src/log/logmessageexception.hpp +++ b/server/src/log/logmessageexception.hpp @@ -25,11 +25,13 @@ #include #include +#include "appendarguments.hpp" class LogMessageException : public std::exception { private: LogMessage m_message; + std::vector m_args; std::string m_what; public: @@ -41,12 +43,22 @@ class LogMessageException : public std::exception m_what.append(std::to_string(logMessageNumber(message))); } + template + LogMessageException(LogMessage message, const Args&... args) + : m_message{message} + , m_what{"message:"} + { + m_args.reserve(sizeof...(Args)); + appendArguments(m_args, std::forward(args)...); + } + const char* what() const noexcept override { return m_what.c_str(); } - inline LogMessage message() const { return m_message; } + inline LogMessage message() const noexcept { return m_message; } + inline const std::vector& args() const noexcept { return m_args; } }; #endif diff --git a/server/src/lua/class.cpp b/server/src/lua/class.cpp index 52825416..6e1d9780 100644 --- a/server/src/lua/class.cpp +++ b/server/src/lua/class.cpp @@ -3,7 +3,7 @@ * * This file is part of the traintastic source code. * - * Copyright (C) 2021 Reinder Feenstra + * Copyright (C) 2021-2022 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -55,30 +55,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" @@ -86,24 +62,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" @@ -187,34 +157,16 @@ void Class::registerValues(lua_State* L) registerValue(L, "CLOCK"); - registerValue(L, "WLANMAUS_CONTROLLER"); - registerValue(L, "CONTROLLER_LIST"); -#ifdef USB_XPRESSNET - registerValue(L, "USB_XPRESSNET_CONTROLLER"); -#endif - - registerValue(L, "Z21_COMMAND_STATION"); - registerValue(L, "VIRTUAL_COMMAND_STATION"); - registerValue(L, "LOCONET_TCP_BINARY_COMMAND_STATION"); -#ifdef USB_XPRESSNET - registerValue(L, "USB_XPRESSNET_INTERFACE"); -#endif - registerValue(L, "XPRESSNET_SERIAL_COMMAND_STATION"); - registerValue(L, "DCCPLUSPLUS_SERIAL_COMMAND_STATION"); - registerValue(L, "COMMAND_STATION_LIST"); - registerValue(L, "LOCONET_SERIAL_COMMAND_STATION"); - registerValue(L, "DECODER_FUNCTION"); registerValue(L, "DECODER_LIST"); registerValue(L, "DECODER"); registerValue(L, "DECODER_FUNCTIONS"); - registerValue(L, "LOCONET_INPUT"); - registerValue(L, "XPRESSNET_INPUT"); + registerValue(L, "INPUT"); registerValue(L, "INPUT_LIST"); + registerValue(L, "OUTPUT"); registerValue(L, "OUTPUT_LIST"); - registerValue(L, "LOCONET_OUTPUT"); registerValue(L, "RAIL_VEHICLE_LIST"); registerValue(L, "LOCOMOTIVE"); diff --git a/server/src/utils/displayname.hpp b/server/src/utils/displayname.hpp index 066f421e..334f6f69 100644 --- a/server/src/utils/displayname.hpp +++ b/server/src/utils/displayname.hpp @@ -3,7 +3,7 @@ * * This file is part of the traintastic source code. * - * Copyright (C) 2021 Reinder Feenstra + * Copyright (C) 2021-2022 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -31,12 +31,6 @@ namespace DisplayName { constexpr std::string_view outputMap = "board_tile:output_map"; } - namespace CommandStation - { - constexpr std::string_view emergencyStop = "command_station:emergency_stop"; - constexpr std::string_view online = "command_station:online"; - constexpr std::string_view powerOn = "command_station:power_on"; - } namespace Controller { constexpr std::string_view active = "controller:active"; @@ -46,11 +40,27 @@ namespace DisplayName { constexpr std::string_view address = "hardware:address"; constexpr std::string_view commandStation = "hardware:command_station"; + constexpr std::string_view dccplusplus = "hardware:dccplusplus"; + constexpr std::string_view debugLogInput = "hardware:debug_log_input"; + constexpr std::string_view debugLogOutput = "hardware:debug_log_output"; + constexpr std::string_view debugLogRXTX = "hardware:debug_log_rx_tx"; + constexpr std::string_view decoders = "hardware:decoders"; + constexpr std::string_view ecos = "hardware:ecos"; 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"; + constexpr std::string_view z21 = "hardware:z21"; + } + namespace Interface + { + constexpr std::string_view online = "interface:online"; + constexpr std::string_view status = "interface:status"; + constexpr std::string_view type = "interface:type"; } namespace IP { @@ -72,9 +82,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/hardware/output/outputs.cpp b/server/src/utils/endswith.hpp similarity index 68% rename from server/src/hardware/output/outputs.cpp rename to server/src/utils/endswith.hpp index 152d6dad..fdd57072 100644 --- a/server/src/hardware/output/outputs.cpp +++ b/server/src/utils/endswith.hpp @@ -1,9 +1,9 @@ /** - * server/src/hardware/output/outputs.cpp + * server/src/utils/endswith.hpp * * This file is part of the traintastic source code. * - * Copyright (C) 2019-2020 Reinder Feenstra + * Copyright (C) 2022 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -20,12 +20,14 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "outputs.hpp" +#ifndef TRAINTASTIC_SERVER_UTILS_ENDSWITH_HPP +#define TRAINTASTIC_SERVER_UTILS_ENDSWITH_HPP -std::shared_ptr Outputs::create(const std::weak_ptr& world, std::string_view classId, std::string_view id) +#include + +constexpr bool endsWith(std::string_view sv, std::string_view suffix) { - if(classId == LocoNetOutput::classId) - return LocoNetOutput::create(world, id); - else - return std::shared_ptr(); + return sv.size() >= suffix.size() && std::equal(suffix.rbegin(), suffix.rend(), sv.rbegin()); } + +#endif diff --git a/server/src/utils/fromchars.hpp b/server/src/utils/fromchars.hpp new file mode 100644 index 00000000..d55f0915 --- /dev/null +++ b/server/src/utils/fromchars.hpp @@ -0,0 +1,35 @@ +/** + * server/src/utils/fromchars.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_FROMCHARS_HPP +#define TRAINTASTIC_SERVER_UTILS_FROMCHARS_HPP + +#include +#include + +template +inline auto fromChars(std::string_view sv, T& value, int base = 10) +{ + return std::from_chars(sv.data(), sv.data() + sv.size() - 1, value, base); +} + +#endif diff --git a/server/src/hardware/protocol/xpressnet/xpressnetlist.cpp b/server/src/utils/inrange.hpp similarity index 62% rename from server/src/hardware/protocol/xpressnet/xpressnetlist.cpp rename to server/src/utils/inrange.hpp index 860e9062..e7032606 100644 --- a/server/src/hardware/protocol/xpressnet/xpressnetlist.cpp +++ b/server/src/utils/inrange.hpp @@ -1,5 +1,5 @@ /** - * server/src/hardware/protocol/xpressnet/xpressnetlist.cpp + * server/src/utils/inrange.hpp * * This file is part of the traintastic source code. * @@ -20,20 +20,21 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "xpressnetlist.hpp" -#include "xpressnetlisttablemodel.hpp" +#ifndef TRAINTASTIC_SERVER_UTILS_INRANGE_HPP +#define TRAINTASTIC_SERVER_UTILS_INRANGE_HPP -XpressNetList::XpressNetList(Object& _parent, const std::string& parentPropertyName) : - ObjectList(_parent, parentPropertyName) +#include + +template +constexpr bool inRange(const T value, const T min, const T max) { + return value >= min && value <= max; } -TableModelPtr XpressNetList::getModel() +template +constexpr bool inRange(const T value, const std::pair& range) { - return std::make_shared(*this); + return inRange(value, range.first, range.second); } -bool XpressNetList::isListedProperty(const std::string& name) -{ - return XpressNetListTableModel::isListedProperty(name); -} +#endif diff --git a/server/src/utils/rtrim.hpp b/server/src/utils/rtrim.hpp new file mode 100644 index 00000000..d476ed4b --- /dev/null +++ b/server/src/utils/rtrim.hpp @@ -0,0 +1,64 @@ +/** + * server/src/utils/rtrim.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_RTRIM_HPP +#define TRAINTASTIC_SERVER_UTILS_RTRIM_HPP + +#include +#include + +constexpr std::string_view rtrim(std::string_view s, char c) +{ + if(s.empty()) + return {}; + + size_t size = s.size() - 1; + while(s.data()[size] == c) + { + if(size == 0) + return {}; + size--; + } + return {s.data(), size + 1}; +} + +#if __cplusplus >= 202002L +constexpr +#else +inline +#endif +std::string_view rtrim(std::string_view s, std::initializer_list c) +{ + if(s.empty()) + return {}; + + size_t size = s.size() - 1; + while(std::any_of(c.begin(), c.end(), [c1=s.data()[size]](char c2) { return c1 == c2; })) + { + if(size == 0) + return {}; + size--; + } + return {s.data(), size + 1}; +} + +#endif diff --git a/server/src/utils/serialport.cpp b/server/src/utils/serialport.cpp new file mode 100644 index 00000000..7bf57713 --- /dev/null +++ b/server/src/utils/serialport.cpp @@ -0,0 +1,70 @@ +/** + * server/src/utils/serialport.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 "serialport.hpp" +#include "../log/logmessageexception.hpp" + +namespace SerialPort { + +void open(boost::asio::serial_port& serialPort, const std::string& device, uint32_t baudrate, uint8_t characterSize, SerialParity parity, SerialStopBits stopBits, SerialFlowControl flowControl) +{ + boost::system::error_code ec; + + serialPort.open(device, ec); + if(ec) + throw LogMessageException(LogMessage::E2010_SERIAL_PORT_OPEN_FAILED_X, ec); + + serialPort.set_option(boost::asio::serial_port_base::baud_rate(baudrate), ec); + if(ec) + throw LogMessageException(LogMessage::E2013_SERIAL_PORT_SET_BAUDRATE_FAILED_X, ec); + + serialPort.set_option(boost::asio::serial_port_base::character_size(characterSize), ec); + if(ec) + throw LogMessageException(LogMessage::E2014_SERIAL_PORT_SET_DATA_BITS_FAILED_X, ec); + + static_cast(parity); // silence unused warning if assertions are off + assert(parity == SerialParity::None); + serialPort.set_option(boost::asio::serial_port_base::parity(boost::asio::serial_port_base::parity::none), ec); + if(ec) + throw LogMessageException(LogMessage::E2016_SERIAL_PORT_SET_PARITY_FAILED_X, ec); + + static_cast(stopBits); // silence unused warning if assertions are off + assert(stopBits == SerialStopBits::One); + serialPort.set_option(boost::asio::serial_port_base::stop_bits(boost::asio::serial_port_base::stop_bits::one), ec); + if(ec) + throw LogMessageException(LogMessage::E2015_SERIAL_PORT_SET_STOP_BITS_FAILED_X, ec); + + switch(flowControl) + { + case SerialFlowControl::None: + serialPort.set_option(boost::asio::serial_port_base::flow_control(boost::asio::serial_port_base::flow_control::none)); + break; + + case SerialFlowControl::Hardware: + serialPort.set_option(boost::asio::serial_port_base::flow_control(boost::asio::serial_port_base::flow_control::hardware)); + break; + } + if(ec) + throw LogMessageException(LogMessage::E2017_SERIAL_PORT_SET_FLOW_CONTROL_FAILED_X, ec); +} + +} diff --git a/server/src/utils/serialport.hpp b/server/src/utils/serialport.hpp new file mode 100644 index 00000000..be9cb882 --- /dev/null +++ b/server/src/utils/serialport.hpp @@ -0,0 +1,48 @@ +/** + * server/src/utils/serialport.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_SERIALPORT_HPP +#define TRAINTASTIC_SERVER_UTILS_SERIALPORT_HPP + +#include +#include +#include +#include +#include +#include + +namespace SerialPort { + +constexpr uint32_t baudrateMin = 50; +constexpr uint32_t baudrateMax = 3'000'000; +constexpr std::array baudrateValues = {{110, 300, 600, 1'200, 2'400, 4'800, 9'600, 14'400, 19'200, 38'400, 57'600, 115'200, 128'000, 256'000}}; + +/** + * @brief Open serial port + * + * @note Throws LogMessageException on failure + */ +void open(boost::asio::serial_port& serialPort, const std::string& device, uint32_t baudrate, uint8_t characterSize, SerialParity parity, SerialStopBits stopBits, SerialFlowControl flowControl); + +} + +#endif diff --git a/server/src/utils/setthreadname.hpp b/server/src/utils/setthreadname.hpp new file mode 100644 index 00000000..9d4f9c0b --- /dev/null +++ b/server/src/utils/setthreadname.hpp @@ -0,0 +1,45 @@ +/** + * server/src/utils/setthreadname.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_SETTHREADNAME_HPP +#define TRAINTASTIC_SERVER_UTILS_SETTHREADNAME_HPP + +#include +#include +#if __has_include() + #include +#endif + +inline void setThreadName(const char* name) +{ +#if __has_include() + if constexpr(std::is_same_v) + pthread_setname_np(pthread_self(), name); +#endif +#ifdef WIN32 + // TODO: + //if constexpr(std::is_same_v) + // SetThreadDescriptionA(GetCurrentThread(), name); +#endif +} + +#endif diff --git a/server/src/utils/startswith.hpp b/server/src/utils/startswith.hpp new file mode 100644 index 00000000..72a58165 --- /dev/null +++ b/server/src/utils/startswith.hpp @@ -0,0 +1,33 @@ +/** + * server/src/utils/startswith.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_STARTSWITH_HPP +#define TRAINTASTIC_SERVER_UTILS_STARTSWITH_HPP + +#include + +constexpr bool startsWith(std::string_view sv, std::string_view prefix) +{ + return sv.size() >= prefix.size() && std::equal(prefix.begin(), prefix.end(), sv.begin()); +} + +#endif diff --git a/server/src/utils/stripsuffix.hpp b/server/src/utils/stripsuffix.hpp new file mode 100644 index 00000000..c3bd607d --- /dev/null +++ b/server/src/utils/stripsuffix.hpp @@ -0,0 +1,37 @@ +/** + * server/src/utils/stripsuffix.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef TRAINTASTIC_SERVER_UTILS_STRIPSUFFIX_HPP +#define TRAINTASTIC_SERVER_UTILS_STRIPSUFFIX_HPP + +#include +#include +#include "endswith.hpp" + +constexpr std::string_view stripSuffix(std::string_view sv, std::string_view suffix) +{ + if(endsWith(sv, suffix)) + sv.remove_suffix(suffix.size()); + return sv; +} + +#endif diff --git a/server/src/world/world.cpp b/server/src/world/world.cpp index 3325ef6b..31a98392 100644 --- a/server/src/world/world.cpp +++ b/server/src/world/world.cpp @@ -3,7 +3,7 @@ * * This file is part of the traintastic source code. * - * Copyright (C) 2019-2021 Reinder Feenstra + * Copyright (C) 2019-2022 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -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 | PropertyFlags::ScriptReadOnly}, scale{this, "scale", WorldScale::H0, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::ScriptReadOnly, [this](WorldScale /*value*/){ updateScaleRatio(); }}, scaleRatio{this, "scale_ratio", 87, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::ScriptReadOnly}, - 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 | PropertyFlags::ScriptReadOnly}, clock{this, "clock", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore}, trains{this, "trains", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly}, @@ -117,14 +118,14 @@ World::World(Private) : powerOff{*this, "power_off", MethodFlags::ScriptCallable, [this]() { - Log::log(*this, LogMessage::N1014_POWER_ON); + Log::log(*this, LogMessage::N1015_POWER_OFF); state.setValueInternal(state.value() - WorldState::PowerOn); event(WorldEvent::PowerOff); }}, powerOn{*this, "power_on", [this]() { - Log::log(*this, LogMessage::N1015_POWER_OFF); + Log::log(*this, LogMessage::N1014_POWER_ON); state.setValueInternal(state.value() + WorldState::PowerOn); event(WorldEvent::PowerOn); }}, @@ -243,20 +244,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 6707a77c..545c1f73 100644 --- a/server/src/world/world.hpp +++ b/server/src/world/world.hpp @@ -3,7 +3,7 @@ * * This file is part of the traintastic source code. * - * Copyright (C) 2019-2021 Reinder Feenstra + * Copyright (C) 2019-2022 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -26,6 +26,7 @@ #include "../core/object.hpp" #include "../core/property.hpp" #include "../core/objectproperty.hpp" +#include "../core/controllerlist.hpp" #include "../core/event.hpp" #include #include @@ -35,13 +36,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 @@ -86,13 +87,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..51e594ba 100644 --- a/server/src/world/worldloader.cpp +++ b/server/src/world/worldloader.cpp @@ -26,17 +26,15 @@ #include #include #include "world.hpp" -#include "../utils/string.hpp" +#include "../utils/startswith.hpp" +#include "../utils/stripsuffix.hpp" #include "ctwreader.hpp" #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 +152,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 +167,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)) @@ -204,6 +200,61 @@ void WorldLoader::createObject(ObjectData& objectData) else if(classId == Lua::Script::classId) objectData.object = Lua::Script::create(m_world, id); #endif + // backwards compatibility < 0.1 + else if(classId == "command_station.dccplusplus_serial") + { + objectData.object = DCCPlusPlusInterface::create(m_world, id); + objectData.json["device"] = objectData.json["port"]; + objectData.json.erase("port"); + } + else if(classId == "command_station.loconet_serial") + { + auto object = LocoNetInterface::create(m_world, id); + object->type = LocoNetInterfaceType::Serial; + objectData.object = object; + objectData.json["device"] = objectData.json["port"]; + objectData.json.erase("port"); + } + else if(classId == "command_station.loconet_tcp_binary") + { + auto object = LocoNetInterface::create(m_world, id); + object->type = LocoNetInterfaceType::TCPBinary; + objectData.object = object; + } + else if(classId == "command_station.xpressnet_serial") + { + auto object = XpressNetInterface::create(m_world, id); + object->type = XpressNetInterfaceType::Serial; + objectData.object = object; + objectData.json["device"] = objectData.json["port"]; + objectData.json.erase("port"); + } + else if(classId == "command_station.z21") + { + objectData.object = Z21Interface::create(m_world, id); + } + else if(classId == "controller.wlanmaus") + { + objectData.object = WlanMausInterface::create(m_world, id); + } + else if(classId == "input.loconet") + { + objectData.object = Input::create(m_world, id); + objectData.json["interface"] = stripSuffix(objectData.json["loconet"], ".loconet"); + objectData.json.erase("loconet"); + } + else if(classId == "output.loconet") + { + objectData.object = Output::create(m_world, id); + objectData.json["interface"] = stripSuffix(objectData.json["loconet"], ".loconet"); + objectData.json.erase("loconet"); + } + else if(classId == "input.xpressnet") + { + objectData.object = Input::create(m_world, id); + objectData.json["interface"] = stripSuffix(objectData.json["xpressnet"], ".xpressnet"); + objectData.json.erase("xpressnet"); + } else assert(false); 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/shared/src/traintastic/enum/interfacestatus.hpp b/shared/src/traintastic/enum/interfacestatus.hpp new file mode 100644 index 00000000..cbd7f4f1 --- /dev/null +++ b/shared/src/traintastic/enum/interfacestatus.hpp @@ -0,0 +1,47 @@ +/** + * shared/src/enum/interfacestatus.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_SHARED_TRAINTASTIC_ENUM_INTERFACESTATUS_HPP +#define TRAINTASTIC_SHARED_TRAINTASTIC_ENUM_INTERFACESTATUS_HPP + +#include +#include "enum.hpp" + +enum class InterfaceStatus : uint8_t +{ + Offline = 0, + Initializing = 1, + Online = 2, + Error = 255, +}; + +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/src/traintastic/enum/loconetinterfacetype.hpp b/shared/src/traintastic/enum/loconetinterfacetype.hpp new file mode 100644 index 00000000..b57a5d9e --- /dev/null +++ b/shared/src/traintastic/enum/loconetinterfacetype.hpp @@ -0,0 +1,47 @@ +/** + * shared/src/traintastic/enum/loconetinterfacetype.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_SHARED_TRAINTASTIC_ENUM_LOCONETINTERFACETYPE_HPP +#define TRAINTASTIC_SHARED_TRAINTASTIC_ENUM_LOCONETINTERFACETYPE_HPP + +#include +#include "enum.hpp" + +enum class LocoNetInterfaceType : uint16_t +{ + Serial = 0, + TCPBinary = 1, + LBServer = 2, + Z21 = 3, +}; + +ENUM_NAME(LocoNetInterfaceType, "loconet_interface_type") + +ENUM_VALUES(LocoNetInterfaceType, 4, +{ + {LocoNetInterfaceType::Serial, "serial"}, + {LocoNetInterfaceType::TCPBinary, "tcp_binary"}, + {LocoNetInterfaceType::LBServer, "lbserver"}, + {LocoNetInterfaceType::Z21, "z21"}, +}) + +#endif diff --git a/shared/src/traintastic/enum/logmessage.hpp b/shared/src/traintastic/enum/logmessage.hpp index cc005816..c6cb790d 100644 --- a/shared/src/traintastic/enum/logmessage.hpp +++ b/shared/src/traintastic/enum/logmessage.hpp @@ -70,6 +70,8 @@ enum class LogMessage : uint32_t I1004_CONNECTION_LOST = LogMessageOffset::info + 1004, I1005_BUILDING_WORLD_INDEX = LogMessageOffset::info + 1005, I2001_UNKNOWN_LOCO_ADDRESS_X = LogMessageOffset::info + 2001, + I2002_HARDWARE_TYPE_X = LogMessageOffset::info + 2002, + I2003_FIRMWARE_VERSION_X = LogMessageOffset::info + 2003, I9999_X = LogMessageOffset::info + 9999, // Notice: @@ -126,6 +128,12 @@ enum class LogMessage : uint32_t E2010_SERIAL_PORT_OPEN_FAILED_X = LogMessageOffset::error + 2010, E2011_SOCKET_SEND_FAILED_X = LogMessageOffset::error + 2011, E2012_FUNCTION_NUMBER_ALREADY_IN_USE = LogMessageOffset::error + 2012, + E2013_SERIAL_PORT_SET_BAUDRATE_FAILED_X = LogMessageOffset::error + 2013, + E2014_SERIAL_PORT_SET_DATA_BITS_FAILED_X = LogMessageOffset::error + 2014, + E2015_SERIAL_PORT_SET_STOP_BITS_FAILED_X = LogMessageOffset::error + 2015, + E2016_SERIAL_PORT_SET_PARITY_FAILED_X = LogMessageOffset::error + 2016, + E2017_SERIAL_PORT_SET_FLOW_CONTROL_FAILED_X = LogMessageOffset::error + 2017, + E9999_X = LogMessageOffset::error + 9999, // Critical: @@ -137,8 +145,9 @@ enum class LogMessage : uint32_t C1006_CREATING_WORLD_BACKUP_FAILED_X = LogMessageOffset::critical + 1006, C1007_CREATING_WORLD_BACKUP_DIRECTORY_FAILED_X = LogMessageOffset::critical + 1007, C2001_ADDRESS_ALREADY_USED_AT_X = LogMessageOffset::critical + 2001, - C2002_DCCPLUSPLUS_ONLY_SUPPORTS_THE_DCC_PROTOCOL = LogMessageOffset::critical + 2001, - C2003_DCCPLUSPLUS_DOESNT_SUPPORT_DCC_LONG_ADDRESSES_BELOW_128 = LogMessageOffset::critical + 2002, + C2002_DCCPLUSPLUS_ONLY_SUPPORTS_THE_DCC_PROTOCOL = LogMessageOffset::critical + 2002, + C2003_DCCPLUSPLUS_DOESNT_SUPPORT_DCC_LONG_ADDRESSES_BELOW_128 = LogMessageOffset::critical + 2003, + C2004_CANT_GET_FREE_SLOT = LogMessageOffset::critical + 2004, C9999_X = LogMessageOffset::critical + 9999, // Fatal: diff --git a/shared/src/traintastic/enum/serialparity.hpp b/shared/src/traintastic/enum/serialparity.hpp new file mode 100644 index 00000000..a12a1c0d --- /dev/null +++ b/shared/src/traintastic/enum/serialparity.hpp @@ -0,0 +1,33 @@ +/** + * shared/src/enum/serialparity.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_SHARED_TRAINTASTIC_ENUM_SERIALPARITY_HPP +#define TRAINTASTIC_SHARED_TRAINTASTIC_ENUM_SERIALPARITY_HPP + +#include + +enum class SerialParity : uint8_t +{ + None = 0, +}; + +#endif diff --git a/shared/src/traintastic/enum/serialstopbits.hpp b/shared/src/traintastic/enum/serialstopbits.hpp new file mode 100644 index 00000000..ef19ac50 --- /dev/null +++ b/shared/src/traintastic/enum/serialstopbits.hpp @@ -0,0 +1,33 @@ +/** + * shared/src/enum/serialstopbits.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_SHARED_TRAINTASTIC_ENUM_SERIALSTOPBITS_HPP +#define TRAINTASTIC_SHARED_TRAINTASTIC_ENUM_SERIALSTOPBITS_HPP + +#include + +enum class SerialStopBits : uint8_t +{ + One = 1, +}; + +#endif diff --git a/shared/src/traintastic/enum/xpressnetinterfacetype.hpp b/shared/src/traintastic/enum/xpressnetinterfacetype.hpp new file mode 100644 index 00000000..b634f2ac --- /dev/null +++ b/shared/src/traintastic/enum/xpressnetinterfacetype.hpp @@ -0,0 +1,43 @@ +/** + * shared/src/traintastic/enum/xpressnetinterfacetype.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_SHARED_TRAINTASTIC_ENUM_XPRESSNETINTERFACETYPE_HPP +#define TRAINTASTIC_SHARED_TRAINTASTIC_ENUM_XPRESSNETINTERFACETYPE_HPP + +#include +#include "enum.hpp" + +enum class XpressNetInterfaceType : uint8_t +{ + Serial = 0, + Network = 1, +}; + +ENUM_NAME(XpressNetInterfaceType, "xpressnet_interface_type") + +ENUM_VALUES(XpressNetInterfaceType, 2, +{ + {XpressNetInterfaceType::Serial, "serial"}, + {XpressNetInterfaceType::Network, "network"}, +}) + +#endif diff --git a/shared/src/traintastic/enum/xpressnetserialinterface.hpp b/shared/src/traintastic/enum/xpressnetserialinterfacetype.hpp similarity index 56% rename from shared/src/traintastic/enum/xpressnetserialinterface.hpp rename to shared/src/traintastic/enum/xpressnetserialinterfacetype.hpp index 09ecf23d..ba558f00 100644 --- a/shared/src/traintastic/enum/xpressnetserialinterface.hpp +++ b/shared/src/traintastic/enum/xpressnetserialinterfacetype.hpp @@ -1,9 +1,9 @@ /** - * shared/src/enum/xpressnetserialinterface.hpp + * shared/src/enum/xpressnetserialinterfacetype.hpp * * This file is part of the traintastic source code. * - * Copyright (C) 2019-2020 Reinder Feenstra + * 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 @@ -20,34 +20,32 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef TRAINTASTIC_SHARED_TRAINTASTIC_ENUM_XPRESSNETSERIALINTERFACE_HPP -#define TRAINTASTIC_SHARED_TRAINTASTIC_ENUM_XPRESSNETSERIALINTERFACE_HPP +#ifndef TRAINTASTIC_SHARED_TRAINTASTIC_ENUM_XPRESSNETSERIALINTERFACETYPE_HPP +#define TRAINTASTIC_SHARED_TRAINTASTIC_ENUM_XPRESSNETSERIALINTERFACETYPE_HPP #include #include "enum.hpp" -enum class XpressNetSerialInterface : uint16_t +enum class XpressNetSerialInterfaceType : uint16_t { - Custom = 0, - LenzLI100 = 1, - LenzLI100F = 2, - LenzLI101F = 3, + LenzLI100 = 0, + LenzLI100F = 1, + LenzLI101F = 2, + LenzLIUSB = 3, RoSoftS88XPressNetLI = 4, + DigikeijsDR5000 = 5, }; -template<> -struct EnumName -{ - static constexpr char const* value = "xpressnet_serial_interface"; -}; +ENUM_NAME(XpressNetSerialInterfaceType, "xpressnet_serial_interface_type") -ENUM_VALUES(XpressNetSerialInterface, 5, +ENUM_VALUES(XpressNetSerialInterfaceType, 6, { - {XpressNetSerialInterface::Custom, "custom"}, - {XpressNetSerialInterface::LenzLI100, "lenz_li100"}, - {XpressNetSerialInterface::LenzLI100F, "lenz_li100f"}, - {XpressNetSerialInterface::LenzLI101F, "lenz_li101f"}, - {XpressNetSerialInterface::RoSoftS88XPressNetLI, "rosoft_s88xpressnetli"}, + {XpressNetSerialInterfaceType::LenzLI100, "lenz_li100"}, + {XpressNetSerialInterfaceType::LenzLI100F, "lenz_li100f"}, + {XpressNetSerialInterfaceType::LenzLI101F, "lenz_li101f"}, + {XpressNetSerialInterfaceType::RoSoftS88XPressNetLI, "rosoft_s88xpressnetli"}, + {XpressNetSerialInterfaceType::LenzLIUSB, "lenz_liusb"}, + {XpressNetSerialInterfaceType::DigikeijsDR5000, "digikeijs_dr5000"}, }) #endif diff --git a/shared/translations/en-us.txt b/shared/translations/en-us.txt index d0bc9b15..934df23b 100644 --- a/shared/translations/en-us.txt +++ b/shared/translations/en-us.txt @@ -43,16 +43,12 @@ class_id:board_tile.rail.turnout_right_90=Turnout right 90° class_id:board_tile.rail.turnout_right_curved=Turnout right curved class_id:board_tile.rail.turnout_singleslip=Single slip class_id:board_tile.rail.turnout_wye=Turnout wye -class_id:command_station.dccplusplus_serial=DCC++ (serial) -class_id:command_station.loconet_serial=LocoNet (serial) -class_id:command_station.loconet_tcp_binary=LocoNet (TCP binary) -class_id:command_station.virtual=Virtual -class_id:command_station.xpressnet_serial=XpressNet (serial) -class_id:command_station.z21=Z21 -class_id:controller.wlanmaus=WLANmaus -class_id:input.loconet=LocoNet input -class_id:input.xpressnet=XpressNet input -class_id:output.loconet=LocoNet output +class_id:interface.dccplusplus=DCC++ +class_id:interface.ecos=ECoS +class_id:interface.loconet=LocoNet +class_id:interface.wlanmaus=WLANmaus +class_id:interface.xpressnet=XpressNet +class_id:interface.z21=Z21 class_id:vehicle.rail.freight_car=Freight car class_id:vehicle.rail.locomotive=Locomotive @@ -63,37 +59,8 @@ clock:month=Month clock:multiplier=Multiplier clock:year=Year -command_station.dccplusplus_serial:dcc_plus_plus=DCC++(EX) - -command_station.loconet_serial:interface=Interface - -command_station.xpressnet_serial:interface=Interface -command_station.xpressnet_serial:s88_module_count=S88 module count -command_station.xpressnet_serial:s88_start_address=S88 start address - -command_station.z21:filtered_main_current=Filtered main current -command_station.z21:firmware_version=Firmware verion -command_station.z21:hardware_type=Hardware type -command_station.z21:high_temperature=High temperature -command_station.z21:main_current=Main current -command_station.z21:power_lost=Power lost -command_station.z21:prog_current=Prog current -command_station.z21:programming_mode_active=Programming mode active -command_station.z21:serial_number=Serial number -command_station.z21:short_circuit=Short circuit -command_station.z21:short_circut_external=Short circuit external -command_station.z21:short_circut_internal=Short circuit internal -command_station.z21:supply_voltage=Supply voltage -command_station.z21:temperature=Temperature -command_station.z21:vcc_voltage=VCC voltage - -command_station:emergency_stop=Emergency stop -command_station:online=Online -command_station:power_on=Power on - -controller.wlanmaus:debug_log=Enable command logging (debug) - -controller:active=Active +dccplusplus_settings:startup_delay=Startup delay +dccplusplus_settings:use_ex=Use DCC++EX commands decoder:functions=Functions decoder:long_address=DCC long address @@ -124,11 +91,21 @@ direction:reverse=Reverse hardware:address=Address hardware:command_station=Command station +hardware:dccplusplus=DCC++ +hardware:debug_log_input=Log input changes +hardware:debug_log_output=Log output changes +hardware:debug_log_rx_tx=Log all communication +hardware:decoders=Decoders +hardware:ecos=ECoS 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 +hardware:z21=Z21 input_list:bus=Bus @@ -136,6 +113,22 @@ input_map_item.block:input=Input input_map_item.block:invert=Invert input_map_item.block:type=Type +interface.dccplusplus:dcc_plus_plus=DCC++(EX) + +interface.loconet:interface=Interface + +interface.xpressnet:interface=Interface +interface.xpressnet:s88_module_count=S88 module count +interface.xpressnet:s88_start_address=S88 start address + +interface.z21:firmware_version=Firmware version +interface.z21:hardware_type=Hardware type +interface.z21:serial_number=Serial number + +interface:online=Online +interface:status=Status +interface:type=Type + ip:hostname=Hostname ip:port=Port @@ -160,10 +153,19 @@ loconet_command_station:custom=Custom loconet_command_station:digikeijs_dr5000=Digikeijs DR5000 loconet_command_station:uhlenbrock_intellibox=Uhlenbrock Intellibox +loconet_interface_type:lbserver=LBServer +loconet_interface_type:serial=Serial +loconet_interface_type:tcp_binary=TCP binary +loconet_interface_type:z21=Z21 + loconet_serial_interface:custom=Custom loconet_serial_interface:digikeijs_dr5000=Digikeijs DR5000 loconet_serial_interface:rosoft_loconet_interface=RoSoft LocoNet interface +loconet_settings:command_station=Command station +loconet_settings:fast_clock_sync_enabled=Fast clock sync enabled +loconet_settings:fast_clock_sync_interval=Fast clock sync interval + lua.script:start=Start lua.script:stop=Stop @@ -229,6 +231,9 @@ message:I1003=Client connected message:I1004=Connection lost message:I1005=Building world index message:I2001=Unknown loco address: %1 +message:I2002=Hardware type: %1 +message:I2003=Firmware version: %1 +message:I2004=Hardware version: %1 message:I9999=%1 message:N1001=Received signal: %1 message:N1002=Created new world @@ -277,20 +282,6 @@ output_map.turnout:key=Position output_map:use=Use -protocol.dccplusplus:debug_log=Enable command logging (debug) -protocol.dccplusplus:use_ex=Use DCC++EX commands - -protocol.loconet:command_station=Command station -protocol.loconet:debug_log_input=Enable input logging -protocol.loconet:debug_log_output=Enable output logging -protocol.loconet:debug_log_rx_tx=Enable RX/TX logging - -protocol.xpressnet:command_station=Command station -protocol.xpressnet:debug_log=Enable logging (debug) -protocol.xpressnet:use_emergency_stop_locomotive_command=Use short emergency stop locomotive command -protocol.xpressnet:use_function_state_commands=Use function state commands -protocol.xpressnet:use_roco_f13_f20_command=Use Roco F13-F20 command - qtapp.connect_dialog:connect=Connect qtapp.connect_dialog:connect_to_server=Connect to server qtapp.connect_dialog:connected=Connected @@ -375,6 +366,7 @@ sensor_type:occupy_detector=Occupy detector sensor_type:reed_switch=Reed switch serial:baudrate=Baudrate +serial:device=Device serial:flow_control=Flow control serial:port=Port @@ -433,11 +425,10 @@ weight_unit:ton=ton world:boards=Boards world:clock=Clock -world:command_stations=Command stations -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 @@ -464,8 +455,20 @@ xpressnet_command_station:custom=Custom xpressnet_command_station:digikeijs_dr5000=Digikeijs DR5000 xpressnet_command_station:roco_10764=Roco 10764 -xpressnet_serial_interface:custom=Custom -xpressnet_serial_interface:lenz_li100=Lenz LI100 -xpressnet_serial_interface:lenz_li100f=Lenz LI100F -xpressnet_serial_interface:lenz_li101f=Lenz LI101F -xpressnet_serial_interface:rosoft_s88xpressnetli=RoSoft s88XpressNetLI +xpressnet_interface_type:network=Network +xpressnet_interface_type:serial=Serial + +xpressnet_serial_interface_type:custom=Custom +xpressnet_serial_interface_type:digikeijs_dr5000=Digikeijs DR5000 +xpressnet_serial_interface_type:lenz_li100=Lenz LI100 +xpressnet_serial_interface_type:lenz_li100f=Lenz LI100F +xpressnet_serial_interface_type:lenz_li101f=Lenz LI101F +xpressnet_serial_interface_type:lenz_liusb=Lenz LIUSB +xpressnet_serial_interface_type:rosoft_s88xpressnetli=RoSoft s88XpressNetLI + +xpressnet_settings:use_emergency_stop_locomotive_command=Use short emergency stop locomotive command +xpressnet_settings:use_roco_f13_f20_command=Use Roco F13-F20 command + +z21_settings.server:allow_emergency_stop=Allow emergency stop +z21_settings.server:allow_track_power_off=Allow power off +z21_settings.server:allow_track_power_on_release_emergency_stop=Allow power on/emergency stop release