Merge pull request #77 from traintastic/11-märklin-cs2cs3-hardware-support

Märklin CS2/CS3 hardware support
Dieser Commit ist enthalten in:
Reinder Feenstra 2023-09-16 08:31:07 +02:00 committet von GitHub
Commit 3041751f6c
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: 4AEE18F83AFDEB23
89 geänderte Dateien mit 8971 neuen und 101 gelöschten Zeilen

Datei anzeigen

@ -66,6 +66,8 @@ file(GLOB SOURCES
"src/utils/*.cpp"
"src/widget/*.hpp"
"src/widget/*.cpp"
"src/widget/list/*.hpp"
"src/widget/list/*.cpp"
"src/widget/object/*.hpp"
"src/widget/object/*.cpp"
"src/widget/objectlist/*.hpp"

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2021 Reinder Feenstra
* Copyright (C) 2019-2021,2023 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -67,6 +67,11 @@ QString TableModel::getRowObjectId(int row) const
return m_texts.value(ColumnRow(0, row));
}
QString TableModel::getValue(int column, int row) const
{
return m_texts.value(ColumnRow(column, row));
}
void TableModel::setRegion(int columnMin, int columnMax, int rowMin, int rowMax)
{
if(m_region.columnMin != columnMin ||

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2021 Reinder Feenstra
* Copyright (C) 2019-2021,2023 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -69,6 +69,7 @@ class TableModel : public QAbstractTableModel
QVariant data(const QModelIndex& index, int role) const final;
QString getRowObjectId(int row) const;
QString getValue(int column, int row) const;
void setRegion(int columnMin, int columnMax, int rowMin, int rowMax);
};

Datei anzeigen

@ -36,6 +36,7 @@
#include <traintastic/enum/loconetinterfacetype.hpp>
#include <traintastic/enum/loconetcommandstation.hpp>
#include <traintastic/enum/loconetserialinterface.hpp>
#include <traintastic/enum/marklincaninterfacetype.hpp>
#include <traintastic/enum/opcmultisensedirection.hpp>
#include <traintastic/enum/outputaction.hpp>
#include <traintastic/enum/pcapoutput.hpp>
@ -97,6 +98,7 @@ QString translateEnum(const QString& enumName, qint64 value)
TRANSLATE_ENUM(LocoNetCommandStation)
TRANSLATE_ENUM(LocoNetInterfaceType)
TRANSLATE_ENUM(LocoNetSerialInterface)
TRANSLATE_ENUM(MarklinCANInterfaceType)
TRANSLATE_ENUM(OPCMultiSenseDirection)
TRANSLATE_ENUM(OutputAction)
TRANSLATE_ENUM(PCAPOutput)

Datei anzeigen

@ -21,6 +21,7 @@
*/
#include "createwidget.hpp"
#include "list/marklincanlocomotivelistwidget.hpp"
#include "objectlist/throttleobjectlistwidget.hpp"
#include "object/luascripteditwidget.hpp"
#include "object/objecteditwidget.hpp"
@ -58,6 +59,10 @@ QWidget* createWidgetIfCustom(const ObjectPtr& object, QWidget* parent)
return new OutputMapWidget(outputMap, parent);
else if(classId == "input_map.block" || classId == "decoder_functions")
return new ItemsEditWidget(object, parent);
else if(classId == "marklin_can_node_list")
return new ListWidget(object, parent);
else if(classId == "marklin_can_locomotive_list")
return new MarklinCANLocomotiveListWidget(object, parent);
else
return nullptr;
}

Datei anzeigen

@ -0,0 +1,80 @@
/**
* client/src/widget/list/istwidget.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2023 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 "listwidget.hpp"
#include <QVBoxLayout>
#include <QtWaitingSpinner/waitingspinnerwidget.h>
#include "../alertwidget.hpp"
#include "../tablewidget.hpp"
#include "../../network/connection.hpp"
#include "../../network/object.hpp"
#include "../../theme/theme.hpp"
#include "../../network/utils.hpp"
ListWidget::ListWidget(const ObjectPtr& object, QWidget* parent)
: QWidget(parent)
, m_object{object}
, m_tableWidget{new TableWidget(this)}
{
setWindowIcon(Theme::getIconForClassId(m_object->classId()));
m_tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
QVBoxLayout* layout = new QVBoxLayout();
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(m_tableWidget);
setLayout(layout);
auto* spinner = new WaitingSpinnerWidget(this, true, false);
spinner->start();
m_requestId = m_object->connection()->getTableModel(m_object,
[this, spinner](const TableModelPtr& tableModel, Message::ErrorCode ec)
{
if(tableModel)
{
m_requestId = Connection::invalidRequestId;
m_tableWidget->setTableModel(tableModel);
connect(m_tableWidget, &TableWidget::doubleClicked, this,
[this](const QModelIndex& index)
{
tableDoubleClicked(index);
});
connect(m_tableWidget->selectionModel(), &QItemSelectionModel::selectionChanged, this,
[this](const QItemSelection&, const QItemSelection&)
{
tableSelectionChanged();
});
tableSelectionChanged();
delete spinner;
}
else
static_cast<QVBoxLayout*>(this->layout())->insertWidget(0, AlertWidget::error(errorCodeToText(ec)));
});
}
ListWidget::~ListWidget()
{
object()->connection()->cancelRequest(m_requestId);
}

Datei anzeigen

@ -0,0 +1,50 @@
/**
* client/src/widget/list/listwidget.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2023 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef TRAINTASTIC_CLIENT_WIDGET_LIST_LISTWIDGET_HPP
#define TRAINTASTIC_CLIENT_WIDGET_LIST_LISTWIDGET_HPP
#include <QWidget>
#include "../../network/objectptr.hpp"
class TableWidget;
class ListWidget : public QWidget
{
private:
ObjectPtr m_object;
int m_requestId;
protected:
TableWidget* m_tableWidget;
const ObjectPtr& object() { return m_object; }
virtual void tableSelectionChanged() {}
virtual void tableDoubleClicked(const QModelIndex& /*index*/) {}
public:
explicit ListWidget(const ObjectPtr& object, QWidget* parent = nullptr);
~ListWidget() override;
};
#endif

Datei anzeigen

@ -0,0 +1,77 @@
/**
* client/src/widget/list/marklincanlocomotivelistwidget.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 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 "marklincanlocomotivelistwidget.hpp"
#include "../tablewidget.hpp"
#include "../../misc/methodaction.hpp"
#include "../../network/object.hpp"
#include "../../network/method.hpp"
#include "../../network/tablemodel.hpp"
#include "../../network/callmethod.hpp"
#include "../../theme/theme.hpp"
#include <QVBoxLayout>
#include <QToolBar>
MarklinCANLocomotiveListWidget::MarklinCANLocomotiveListWidget(const ObjectPtr& object, QWidget* parent)
: ListWidget(object, parent)
, m_toolbar{new QToolBar(this)}
{
static_cast<QVBoxLayout*>(this->layout())->insertWidget(0, m_toolbar);
if(Method* method = object->getMethod("import_or_sync"))
{
m_importOrSyncAction = new MethodAction(Theme::getIcon("import_or_sync"), *method,
[this]()
{
if(auto* model = m_tableWidget->selectionModel(); model && model->hasSelection())
{
if(const auto rows = model->selectedRows(); !rows.empty())
{
const auto name = static_cast<TableModel*>(m_tableWidget->model())->getValue(columnName, rows[0].row());
callMethod(m_importOrSyncAction->method(), nullptr, name);
}
}
});
m_toolbar->addAction(m_importOrSyncAction);
}
if(Method* method = object->getMethod("import_or_sync_all"))
{
m_toolbar->addAction(new MethodAction(Theme::getIcon("import_or_sync_all"), *method));
}
if(Method* method = object->getMethod("reload"))
{
auto* spacer = new QWidget(this);
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
spacer->show();
m_toolbar->addWidget(spacer);
m_toolbar->addAction(new MethodAction(Theme::getIcon("update"), *method));
}
}
void MarklinCANLocomotiveListWidget::tableSelectionChanged()
{
const bool hasSelection = m_tableWidget->selectionModel() && m_tableWidget->selectionModel()->hasSelection();
m_importOrSyncAction->setForceDisabled(!hasSelection);
}

Datei anzeigen

@ -0,0 +1,46 @@
/**
* client/src/widget/list/marklincanlocomotivelistwidget.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef TRAINTASTIC_CLIENT_WIDGET_LIST_MARKLINCANLOCOMOTIVELISTWIDGET_HPP
#define TRAINTASTIC_CLIENT_WIDGET_LIST_MARKLINCANLOCOMOTIVELISTWIDGET_HPP
#include "listwidget.hpp"
class QToolBar;
class MethodAction;
class MarklinCANLocomotiveListWidget : public ListWidget
{
private:
static constexpr int columnName = 0;
QToolBar* m_toolbar;
MethodAction* m_importOrSyncAction;
protected:
void tableSelectionChanged() final;
public:
explicit MarklinCANLocomotiveListWidget(const ObjectPtr& object, QWidget* parent = nullptr);
};
#endif

Datei anzeigen

@ -24,15 +24,12 @@
#include <QVBoxLayout>
#include <QToolBar>
#include <QTableView>
#include <QtWaitingSpinner/waitingspinnerwidget.h>
#include <traintastic/locale/locale.hpp>
#include "../tablewidget.hpp"
#include "../../network/connection.hpp"
#include "../../network/object.hpp"
#include "../../network/method.hpp"
#include "../../network/utils.hpp"
#include "../../network/callmethod.hpp"
#include "../alertwidget.hpp"
#include "../../theme/theme.hpp"
#include "../../misc/methodaction.hpp"
#include "../../dialog/objectselectlistdialog.hpp"
@ -47,11 +44,10 @@
ObjectListWidget::ObjectListWidget(const ObjectPtr& object, QWidget* parent) :
QWidget(parent),
ObjectListWidget::ObjectListWidget(const ObjectPtr& object_, QWidget* parent) :
ListWidget(object_, parent),
m_requestIdInputMonitor{Connection::invalidRequestId},
m_requestIdOutputKeyboard{Connection::invalidRequestId},
m_object{object},
m_toolbar{new QToolBar()},
m_buttonCreate{nullptr},
m_actionCreate{nullptr},
@ -60,45 +56,11 @@ ObjectListWidget::ObjectListWidget(const ObjectPtr& object, QWidget* parent) :
m_actionInputMonitor{nullptr},
m_actionInputMonitorChannel{nullptr},
m_actionOutputKeyboard{nullptr},
m_actionOutputKeyboardChannel{nullptr},
m_tableWidget{new TableWidget()}
m_actionOutputKeyboardChannel{nullptr}
{
setWindowIcon(Theme::getIconForClassId(m_object->classId()));
static_cast<QVBoxLayout*>(this->layout())->insertWidget(0, m_toolbar);
m_tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
QVBoxLayout* layout = new QVBoxLayout();
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(m_toolbar);
layout->addWidget(m_tableWidget);
setLayout(layout);
auto* spinner = new WaitingSpinnerWidget(this, true, false);
spinner->start();
m_requestId = m_object->connection()->getTableModel(m_object,
[this, spinner](const TableModelPtr& tableModel, Message::ErrorCode ec)
{
if(tableModel)
{
m_requestId = Connection::invalidRequestId;
m_tableWidget->setTableModel(tableModel);
connect(m_tableWidget, &TableWidget::doubleClicked, this, &ObjectListWidget::tableDoubleClicked);
connect(m_tableWidget->selectionModel(), &QItemSelectionModel::selectionChanged, this,
[this](const QItemSelection&, const QItemSelection&)
{
tableSelectionChanged();
});
tableSelectionChanged();
delete spinner;
}
else
static_cast<QVBoxLayout*>(this->layout())->insertWidget(0, AlertWidget::error(errorCodeToText(ec)));
});
if(Method* method = m_object->getMethod("create");
if(Method* method = object()->getMethod("create");
method && method->resultType() == ValueType::Object)
{
if(method->argumentTypes().size() == 0) // Create method witout argument
@ -107,7 +69,7 @@ ObjectListWidget::ObjectListWidget(const ObjectPtr& object, QWidget* parent) :
[this, method]()
{
if(m_requestIdCreate != Connection::invalidRequestId)
m_object->connection()->cancelRequest(m_requestIdCreate);
object()->connection()->cancelRequest(m_requestIdCreate);
m_requestIdCreate = method->call(
[this](const ObjectPtr& addedObject, Message::ErrorCode /*ec*/)
@ -146,7 +108,7 @@ ObjectListWidget::ObjectListWidget(const ObjectPtr& object, QWidget* parent) :
[this, method, action]()
{
if(m_requestIdCreate != Connection::invalidRequestId)
m_object->connection()->cancelRequest(m_requestIdCreate);
object()->connection()->cancelRequest(m_requestIdCreate);
m_requestIdCreate = method->call(action->data().toString(),
[this](const ObjectPtr& addedObject, Message::ErrorCode /*ec*/)
@ -177,7 +139,7 @@ ObjectListWidget::ObjectListWidget(const ObjectPtr& object, QWidget* parent) :
Q_ASSERT(false); // unsupported method prototype
}
if(Method* method = m_object->getMethod("add");
if(Method* method = object()->getMethod("add");
method &&
method->argumentTypes().size() == 1 && method->argumentTypes()[0] == ValueType::Object &&
method->resultType() == ValueType::Invalid)
@ -204,7 +166,7 @@ ObjectListWidget::ObjectListWidget(const ObjectPtr& object, QWidget* parent) :
});
m_actionEdit->setEnabled(false);
if(Method* method = m_object->getMethod("remove"))
if(Method* method = object()->getMethod("remove"))
{
m_actionRemove = new MethodAction(Theme::getIcon("remove"), *method,
[this]()
@ -216,7 +178,7 @@ ObjectListWidget::ObjectListWidget(const ObjectPtr& object, QWidget* parent) :
m_toolbar->addAction(m_actionRemove);
}
if(Method* method = m_object->getMethod("delete"))
if(Method* method = object()->getMethod("delete"))
{
m_actionDelete = new MethodAction(Theme::getIcon("delete"), *method,
[this]()
@ -228,7 +190,7 @@ ObjectListWidget::ObjectListWidget(const ObjectPtr& object, QWidget* parent) :
m_toolbar->addAction(m_actionDelete);
}
if(Method* move = m_object->getMethod("move"))
if(Method* move = object()->getMethod("move"))
{
m_actionMoveUp = new MethodAction(Theme::getIcon("up"), *move,
[this]()
@ -265,12 +227,12 @@ ObjectListWidget::ObjectListWidget(const ObjectPtr& object, QWidget* parent) :
m_actionMoveDown->setForceDisabled(true);
}
if(Method* method = m_object->getMethod("reverse"))
if(Method* method = object()->getMethod("reverse"))
{
m_actionReverse = new MethodAction(Theme::getIcon("reverse"), *method);
}
if(Method* method = m_object->getMethod("input_monitor"))
if(Method* method = object()->getMethod("input_monitor"))
{
m_actionInputMonitor = new MethodAction(Theme::getIcon("input_monitor"), *method,
[this]()
@ -284,7 +246,7 @@ ObjectListWidget::ObjectListWidget(const ObjectPtr& object, QWidget* parent) :
});
}
if(Method* method = m_object->getMethod("input_monitor_channel"))
if(Method* method = object()->getMethod("input_monitor_channel"))
{
if(const auto values = method->getAttribute(AttributeName::Values, QVariant()).toList(); !values.isEmpty())
{
@ -321,7 +283,7 @@ ObjectListWidget::ObjectListWidget(const ObjectPtr& object, QWidget* parent) :
}
}
if(Method* method = m_object->getMethod("output_keyboard"))
if(Method* method = object()->getMethod("output_keyboard"))
{
m_actionOutputKeyboard = new MethodAction(Theme::getIcon("output_keyboard"), *method,
[this]()
@ -338,7 +300,7 @@ ObjectListWidget::ObjectListWidget(const ObjectPtr& object, QWidget* parent) :
});
}
if(Method* method = m_object->getMethod("output_keyboard_channel"))
if(Method* method = object()->getMethod("output_keyboard_channel"))
{
if(const auto values = method->getAttribute(AttributeName::Values, QVariant()).toList(); !values.isEmpty())
{
@ -413,11 +375,11 @@ ObjectListWidget::ObjectListWidget(const ObjectPtr& object, QWidget* parent) :
{
QAction* startAll = nullptr;
if(Method* method = m_object->getMethod("start_all"))
if(Method* method = object()->getMethod("start_all"))
startAll = new MethodAction(Theme::getIcon("run"), *method);
QAction* stopAll = nullptr;
if(Method* method = m_object->getMethod("stop_all"))
if(Method* method = object()->getMethod("stop_all"))
stopAll = new MethodAction(Theme::getIcon("stop"), *method);
if(startAll || stopAll)
@ -433,13 +395,15 @@ ObjectListWidget::ObjectListWidget(const ObjectPtr& object, QWidget* parent) :
ObjectListWidget::~ObjectListWidget()
{
m_object->connection()->cancelRequest(m_requestId);
object()->connection()->cancelRequest(m_requestIdCreate);
object()->connection()->cancelRequest(m_requestIdInputMonitor);
object()->connection()->cancelRequest(m_requestIdOutputKeyboard);
}
void ObjectListWidget::objectDoubleClicked(const QString& id)
{
SubWindowType type = SubWindowType::Object;
if(m_object->classId() == "list.board")
if(object()->classId() == "list.board")
type = SubWindowType::Board;
MainWindow::instance->showObject(id, "", type);
}

Datei anzeigen

@ -23,24 +23,21 @@
#ifndef TRAINTASTIC_CLIENT_WIDGET_OBJECTLIST_OBJECTLISTWIDGET_HPP
#define TRAINTASTIC_CLIENT_WIDGET_OBJECTLIST_OBJECTLISTWIDGET_HPP
#include <QWidget>
#include "../list/listwidget.hpp"
#include "../../network/objectptr.hpp"
class QToolBar;
class QToolButton;
class TableWidget;
class MethodAction;
class ObjectListWidget : public QWidget
class ObjectListWidget : public ListWidget
{
Q_OBJECT
private:
int m_requestId;
int m_requestIdCreate;
int m_requestIdInputMonitor;
int m_requestIdOutputKeyboard;
ObjectPtr m_object;
QToolBar* m_toolbar;
QToolButton* m_buttonCreate;
QAction* m_actionCreate;
@ -55,17 +52,13 @@ class ObjectListWidget : public QWidget
MethodAction* m_actionInputMonitorChannel;
MethodAction* m_actionOutputKeyboard;
MethodAction* m_actionOutputKeyboardChannel;
TableWidget* m_tableWidget;
void tableSelectionChanged();
private slots:
void tableDoubleClicked(const QModelIndex& index);
protected:
const ObjectPtr& object() { return m_object; }
QToolBar* toolbar() { return m_toolbar; }
void tableSelectionChanged() override;
void tableDoubleClicked(const QModelIndex& index) override;
virtual void tableSelectionChanged(bool hasSelection);
virtual void objectDoubleClicked(const QString& id);
QStringList getSelectedObjectIds() const;

Datei anzeigen

@ -51,6 +51,7 @@ Name: "firewall_wlanmaus"; Description: "{cm:FirewallAllowWLANmausZ21}"; GroupDe
Source: "..\..\server\build\{#ServerExeName}"; DestDir: "{app}\server"; Flags: ignoreversion; Check: InstallServer
Source: "..\..\server\thirdparty\lua5.3\bin\win64\lua53.dll"; DestDir: "{app}\server"; Flags: ignoreversion; Check: InstallServer
Source: "..\..\server\thirdparty\libarchive\bin\archive.dll"; DestDir: "{app}\server"; Flags: ignoreversion; Check: InstallServer
Source: "..\..\server\thirdparty\zlib\bin\zlib1.dll"; DestDir: "{app}\server"; Flags: ignoreversion; Check: InstallServer
; Client
Source: "..\..\client\build\Release\{#ClientExeName}"; DestDir: "{app}\client"; Flags: ignoreversion; Check: InstallClient
Source: "..\..\client\build\Release\*.dll"; DestDir: "{app}\client"; Flags: ignoreversion; Check: InstallClient

Datei anzeigen

@ -84,6 +84,8 @@ file(GLOB SOURCES
"src/hardware/input/monitor/*.cpp"
"src/hardware/interface/*.hpp"
"src/hardware/interface/*.cpp"
"src/hardware/interface/marklincan/*.hpp"
"src/hardware/interface/marklincan/*.cpp"
"src/hardware/output/*.hpp"
"src/hardware/output/*.cpp"
"src/hardware/output/keyboard/*.hpp"
@ -112,6 +114,12 @@ file(GLOB SOURCES
"src/hardware/protocol/loconet/message/uhlenbrock/*.cpp"
"src/hardware/protocol/loconet/iohandler/*.hpp"
"src/hardware/protocol/loconet/iohandler/*.cpp"
"src/hardware/protocol/marklincan/*.hpp"
"src/hardware/protocol/marklincan/*.cpp"
"src/hardware/protocol/marklincan/iohandler/*.hpp"
"src/hardware/protocol/marklincan/iohandler/*.cpp"
"src/hardware/protocol/marklincan/message/*.hpp"
"src/hardware/protocol/marklincan/message/*.cpp"
"src/hardware/protocol/traintasticdiy/*.hpp"
"src/hardware/protocol/traintasticdiy/*.cpp"
"src/hardware/protocol/traintasticdiy/iohandler/*.hpp"
@ -194,6 +202,10 @@ if(LINUX)
target_link_libraries(traintastic-server PRIVATE PkgConfig::LIBSYSTEMD)
target_link_libraries(traintastic-server-test PRIVATE PkgConfig::LIBSYSTEMD)
endif()
else()
# socket CAN is only available on linux:
file(GLOB SOCKET_CAN_SOURCES "src/hardware/protocol/marklincan/iohandler/socketcaniohandler.*")
list(REMOVE_ITEM SOURCES ${SOCKET_CAN_SOURCES})
endif()
if(WIN32)
@ -273,6 +285,32 @@ else()
list(APPEND SOURCES ${SOURCES_BOOST})
endif()
# zlib
if(WIN32)
set(ZLIB_INCLUDE_DIRS "thirdparty/zlib/include")
if(MSVC)
set(ZLIB_LIBRARIES zlib1)
add_custom_command(TARGET traintastic-server PRE_LINK
COMMAND lib "/def:${PROJECT_SOURCE_DIR}/thirdparty/zlib/bin/zlib1.def" /out:zlib1.lib /machine:x64)
add_custom_command(TARGET traintastic-server-test PRE_LINK
COMMAND lib "/def:${PROJECT_SOURCE_DIR}/thirdparty/zlib/bin/zlib1.def" /out:zlib1.lib /machine:x64)
else()
# MinGW can directly link .dll without import lib
set(ZLIB_LIBRARIES "${PROJECT_SOURCE_DIR}/thirdparty/zlib/bin/zlib1.dll")
endif()
# copy zlib1.dll to build directory:
add_custom_command(TARGET traintastic-server POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy "${PROJECT_SOURCE_DIR}/thirdparty/zlib/bin/zlib1.dll" .)
else()
find_package(ZLIB REQUIRED)
endif()
target_include_directories(traintastic-server PRIVATE ${ZLIB_INCLUDE_DIRS})
target_link_libraries(traintastic-server PRIVATE ${ZLIB_LIBRARIES})
target_include_directories(traintastic-server-test PRIVATE ${ZLIB_INCLUDE_DIRS})
target_link_libraries(traintastic-server-test PRIVATE ${ZLIB_LIBRARIES})
# libarchive
if(WIN32)
set(LibArchive_INCLUDE_DIRS "thirdparty/libarchive/include")

Datei anzeigen

@ -177,6 +177,12 @@ struct Attributes
item.setAttribute(AttributeName::Visible, value);
}
static inline void setVisible(std::initializer_list<std::reference_wrapper<InterfaceItem>> items, bool value)
{
for(auto& item : items)
item.get().setAttribute(AttributeName::Visible, value);
}
static inline void addObjectEditor(InterfaceItem& item, bool value)
{
item.addAttribute(AttributeName::ObjectEditor, value);

Datei anzeigen

@ -75,6 +75,7 @@ Decoder::Decoder(World& world, std::string_view _id) :
updateEditable();
}},
address{this, "address", 0, PropertyFlags::ReadWrite | PropertyFlags::Store},
mfxUID{this, "mfx_uid", 0, PropertyFlags::ReadWrite | PropertyFlags::Store},
emergencyStop{this, "emergency_stop", false, PropertyFlags::ReadWrite,
[this](const bool& /*value*/)
{
@ -134,6 +135,10 @@ Decoder::Decoder(World& world, std::string_view _id) :
Attributes::addVisible(address, false);
m_interfaceItems.add(address);
Attributes::addEnabled(mfxUID, false);
Attributes::addVisible(mfxUID, false);
m_interfaceItems.add(mfxUID);
Attributes::addObjectEditor(emergencyStop, false);
m_interfaceItems.add(emergencyStop);
@ -356,6 +361,12 @@ void Decoder::protocolChanged()
else
Attributes::setMinMax(address, std::pair<uint16_t, uint16_t>(0, 0));
if(protocol == DecoderProtocol::MFX)
address = 0;
// MFX:
Attributes::setVisible(mfxUID, protocol == DecoderProtocol::MFX);
// speed steps:
const auto values = interface->decoderSpeedSteps(protocol);
Attributes::setVisible(speedSteps, !values.empty());
@ -370,6 +381,7 @@ void Decoder::protocolChanged()
else
{
Attributes::setVisible(address, false);
Attributes::setVisible(mfxUID, false);
Attributes::setVisible(speedSteps, false);
}
}

Datei anzeigen

@ -83,17 +83,19 @@ class Decoder : public IdObject
static constexpr float throttleStop = throttleMin;
static constexpr float throttleMax = 1;
inline static uint8_t throttleToSpeedStep(float throttle, uint8_t speedStepMax)
template<class T, std::enable_if_t<std::is_unsigned<T>::value, bool> = true>
inline static T throttleToSpeedStep(float throttle, T speedStepMax)
{
return static_cast<uint8_t>(std::lround(std::clamp(throttle, throttleMin, throttleMax) * speedStepMax));
return static_cast<T>(std::lround(std::clamp(throttle, throttleMin, throttleMax) * speedStepMax));
}
inline static float speedStepToThrottle(uint8_t speedStep, uint8_t speedStepMax)
template<class T, std::enable_if_t<std::is_unsigned<T>::value, bool> = true>
inline static float speedStepToThrottle(T speedStep, T speedStepMax)
{
if(speedStepMax != 0)
return static_cast<float>(std::clamp<uint8_t>(speedStep, 0, speedStepMax)) / speedStepMax;
else
return 0;
return static_cast<float>(std::clamp<T>(speedStep, 0, speedStepMax)) / speedStepMax;
return 0;
}
static const std::shared_ptr<Decoder> null;
@ -102,6 +104,7 @@ class Decoder : public IdObject
ObjectProperty<DecoderController> interface;
Property<DecoderProtocol> protocol;
Property<uint16_t> address;
Property<uint32_t> mfxUID;
Property<bool> emergencyStop;
Property<Direction> direction;
Method<void()> toggleDirection;

Datei anzeigen

@ -57,6 +57,7 @@ std::pair<uint16_t, uint16_t> DecoderController::decoderAddressMinMax(DecoderPro
case DecoderProtocol::Selectrix:
return {1, 112};
case DecoderProtocol::MFX: // no address -> MFX UID is used
case DecoderProtocol::None:
return noAddressMinMax;
}
@ -69,6 +70,7 @@ tcb::span<const uint8_t> DecoderController::decoderSpeedSteps(DecoderProtocol pr
static constexpr std::array<uint8_t, 3> dccSpeedSteps{{14, 28, 128}};
static constexpr std::array<uint8_t, 3> motorolaSpeedSteps{{14, 27, 28}};
static constexpr std::array<uint8_t, 1> selectrixSpeedSteps{{32}};
static constexpr std::array<uint8_t, 1> mfxSpeedSteps{{126}};
switch(protocol)
{
@ -82,6 +84,9 @@ tcb::span<const uint8_t> DecoderController::decoderSpeedSteps(DecoderProtocol pr
case DecoderProtocol::Selectrix:
return selectrixSpeedSteps;
case DecoderProtocol::MFX:
return mfxSpeedSteps;
case DecoderProtocol::None:
return {};
}
@ -141,11 +146,18 @@ void DecoderController::destroying()
DecoderController::DecoderVector::iterator DecoderController::findDecoder(const Decoder& decoder)
{
if(decoder.protocol == DecoderProtocol::MFX)
{
return findDecoderMFX(decoder.mfxUID);
}
return findDecoder(decoder.protocol, decoder.address);
}
DecoderController::DecoderVector::iterator DecoderController::findDecoder(DecoderProtocol protocol, uint16_t address)
{
if(protocol == DecoderProtocol::MFX)
return m_decoders.end();
return std::find_if(m_decoders.begin(), m_decoders.end(),
[protocol, address](const auto& it)
{
@ -153,6 +165,18 @@ DecoderController::DecoderVector::iterator DecoderController::findDecoder(Decode
});
}
DecoderController::DecoderVector::iterator DecoderController::findDecoderMFX(uint32_t mfxUID)
{
if(mfxUID == 0)
return m_decoders.end();
return std::find_if(m_decoders.begin(), m_decoders.end(),
[mfxUID](const auto& it)
{
return it->protocol == DecoderProtocol::MFX && it->mfxUID == mfxUID;
});
}
void DecoderController::restoreDecoderSpeed()
{
for(const auto& decoder : m_decoders)

Datei anzeigen

@ -58,6 +58,7 @@ class DecoderController
DecoderVector::iterator findDecoder(const Decoder& decoder);
DecoderVector::iterator findDecoder(DecoderProtocol protocol, uint16_t address);
DecoderVector::iterator findDecoderMFX(uint32_t mfxUID);
/// \brief restore speed of all decoders that are not (emergency) stopped
void restoreDecoderSpeed();

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2022 Reinder Feenstra
* Copyright (C) 2022-2023 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -32,12 +32,14 @@ enum class DecoderListColumn
Name = 1 << 1,
Interface = 1 << 2,
Address = 1 << 3,
Protocol = 1 << 4,
};
constexpr std::array<DecoderListColumn, 4> decoderListColumnValues = {
constexpr std::array<DecoderListColumn, 5> decoderListColumnValues = {
DecoderListColumn::Id,
DecoderListColumn::Name,
DecoderListColumn::Interface,
DecoderListColumn::Protocol,
DecoderListColumn::Address,
};

Datei anzeigen

@ -38,6 +38,9 @@ static std::string_view displayName(DecoderListColumn column)
case DecoderListColumn::Interface:
return DisplayName::Hardware::interface;
case DecoderListColumn::Protocol:
return "decoder:protocol";
case DecoderListColumn::Address:
return DisplayName::Hardware::address;
}
@ -51,6 +54,7 @@ bool DecoderListTableModel::isListedProperty(std::string_view name)
name == "id" ||
name == "name" ||
name == "interface" ||
name == "protocol" ||
name == "address";
}
@ -96,8 +100,16 @@ std::string DecoderListTableModel::getText(uint32_t column, uint32_t row) const
}
return "";
case DecoderListColumn::Protocol:
if(const auto* it = EnumValues<DecoderProtocol>::value.find(decoder.protocol); it != EnumValues<DecoderProtocol>::value.end())
return std::string("$").append(EnumName<DecoderProtocol>::value).append(":").append(it->second).append("$");
break;
case DecoderListColumn::Address:
return decoder.address.toString();
if(hasAddress(decoder.protocol.value()))
return decoder.address.toString();
else
return {};
default:
assert(false);
@ -118,6 +130,8 @@ void DecoderListTableModel::propertyChanged(BaseProperty& property, uint32_t row
changed(row, DecoderListColumn::Name);
else if(name == "interface")
changed(row, DecoderListColumn::Interface);
else if(name == "protocol")
changed(row, DecoderListColumn::Protocol);
else if(name == "address")
changed(row, DecoderListColumn::Address);
}

Datei anzeigen

@ -39,7 +39,7 @@
#include "../../world/world.hpp"
#include "../../world/worldloader.hpp"
constexpr auto decoderListColumns = DecoderListColumn::Id | DecoderListColumn::Name | DecoderListColumn::Address;
constexpr auto decoderListColumns = DecoderListColumn::Id | DecoderListColumn::Name | DecoderListColumn::Protocol | DecoderListColumn::Address;
constexpr auto inputListColumns = InputListColumn::Id | InputListColumn::Name | InputListColumn::Channel | InputListColumn::Address;
constexpr auto outputListColumns = OutputListColumn::Id | OutputListColumn::Name | OutputListColumn::Channel | OutputListColumn::Address;

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2021-2022 Reinder Feenstra
* Copyright (C) 2021-2023 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -30,6 +30,7 @@ std::shared_ptr<Interface> Interfaces::create(World& world, std::string_view cla
IF_CLASSID_CREATE(ECoSInterface)
IF_CLASSID_CREATE(HSI88Interface)
IF_CLASSID_CREATE(LocoNetInterface)
IF_CLASSID_CREATE(MarklinCANInterface)
IF_CLASSID_CREATE(TraintasticDIYInterface)
IF_CLASSID_CREATE(WiThrottleInterface)
IF_CLASSID_CREATE(WlanMausInterface)

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2021-2022 Reinder Feenstra
* Copyright (C) 2021-2023 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -30,6 +30,7 @@
#include "ecosinterface.hpp"
#include "hsi88.hpp"
#include "loconetinterface.hpp"
#include "marklincaninterface.hpp"
#include "traintasticdiyinterface.hpp"
#include "withrottleinterface.hpp"
#include "wlanmausinterface.hpp"
@ -45,6 +46,7 @@ struct Interfaces
ECoSInterface::classId,
HSI88Interface::classId,
LocoNetInterface::classId,
MarklinCANInterface::classId,
TraintasticDIYInterface::classId,
WiThrottleInterface::classId,
WlanMausInterface::classId,

Datei anzeigen

@ -0,0 +1,194 @@
/**
* server/src/hardware/interface/marklincan/marklincanlocomotivelist.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 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 "marklincanlocomotivelist.hpp"
#include "marklincanlocomotivelisttablemodel.hpp"
#include "../marklincaninterface.hpp"
#include "../../decoder/list/decoderlist.hpp"
#include "../../input/list/inputlist.hpp"
#include "../../output/list/outputlist.hpp"
#include "../../../core/attributes.hpp"
#include "../../../core/method.tpp"
#include "../../../core/objectproperty.tpp"
#include "../../../world/getworld.hpp"
#include "../../../world/world.hpp"
MarklinCANLocomotiveList::MarklinCANLocomotiveList(Object& parent_, std::string_view parentPropertyName)
: SubObject(parent_, parentPropertyName)
, importOrSync{*this, "import_or_sync",
[this](const std::string& name)
{
if(!m_data)
return;
auto it = std::find_if(m_data->begin(), m_data->end(),
[&name](auto item)
{
return item.name == name;
});
if(it != m_data->end())
{
import(*it);
}
}}
, importOrSyncAll{*this, "import_or_sync_all",
[this]()
{
if(!m_data)
return;
for(const auto& locomotive : *m_data)
{
import(locomotive);
}
}}
, reload{*this, "reload",
[this]()
{
if(const auto& kernel = interface().m_kernel)
{
kernel->getLocomotiveList();
}
}}
{
Attributes::addEnabled(importOrSync, false);
m_interfaceItems.add(importOrSync);
Attributes::addEnabled(importOrSyncAll, false);
m_interfaceItems.add(importOrSyncAll);
Attributes::addEnabled(reload, false);
m_interfaceItems.add(reload);
}
void MarklinCANLocomotiveList::setData(std::shared_ptr<MarklinCAN::LocomotiveList> value)
{
m_data = std::move(value);
const uint32_t rowCount = m_data ? static_cast<uint32_t>(m_data->size()) : 0;
for(auto& model : m_models)
{
model->setRowCount(rowCount);
}
}
TableModelPtr MarklinCANLocomotiveList::getModel()
{
return std::make_shared<MarklinCANLocomotiveListTableModel>(*this);
}
void MarklinCANLocomotiveList::worldEvent(WorldState state, WorldEvent event)
{
SubObject::worldEvent(state, event);
updateEnabled();
}
void MarklinCANLocomotiveList::clear()
{
m_data.reset();
for(auto& model : m_models)
{
model->setRowCount(0);
}
updateEnabled();
}
MarklinCANInterface& MarklinCANLocomotiveList::interface()
{
assert(dynamic_cast<MarklinCANInterface*>(&parent()));
return dynamic_cast<MarklinCANInterface&>(parent());
}
void MarklinCANLocomotiveList::import(const MarklinCAN::LocomotiveList::Locomotive& locomotive)
{
auto& decoders = *interface().decoders;
std::shared_ptr<Decoder> decoder;
// 1. try to find by MFX UID:
if(locomotive.protocol == DecoderProtocol::MFX && locomotive.mfxUID != 0)
{
auto it = std::find_if(decoders.begin(), decoders.end(),
[&locomotive](const auto& item)
{
return item->protocol == DecoderProtocol::MFX && item->mfxUID == locomotive.mfxUID;
});
if(it != decoders.end())
decoder = *it;
}
// 2. try to find by name:
if(!decoder)
{
auto it = std::find_if(decoders.begin(), decoders.end(),
[&locomotive](const auto& item)
{
return item->name.value() == locomotive.name;
});
if(it != decoders.end())
decoder = *it;
}
// 3. try to find by protocol / address (only: motorola or DCC)
if(!decoder && locomotive.protocol != DecoderProtocol::MFX)
{
auto it = std::find_if(decoders.begin(), decoders.end(),
[&locomotive](const auto& item)
{
return item->protocol == locomotive.protocol && item->address == locomotive.address;
});
if(it != decoders.end())
decoder = *it;
}
if(!decoder) // not found, create a new one
{
decoder = decoders.create();
}
// update it:
decoder->name = locomotive.name;
decoder->protocol = locomotive.protocol;
if(decoder->protocol == DecoderProtocol::MFX)
{
decoder->address = 0;
decoder->mfxUID = locomotive.mfxUID;
}
else // motorola or DCC
{
decoder->address = locomotive.address;
}
//! \todo create/update locomotive
}
void MarklinCANLocomotiveList::updateEnabled()
{
const auto worldState = getWorld(parent()).state.value();
const bool enabled = m_data && !m_data->empty() && contains(worldState, WorldState::Edit) && !contains(worldState, WorldState::Run);
Attributes::setEnabled({importOrSync, importOrSyncAll}, enabled);
}

Datei anzeigen

@ -0,0 +1,70 @@
/**
* server/src/hardware/interface/marklincan/marklincanlocomotivelist.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 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_MARKLINCAN_MARKLINCANLOCOMOTIVELIST_HPP
#define TRAINTASTIC_SERVER_HARDWARE_INTERFACE_MARKLINCAN_MARKLINCANLOCOMOTIVELIST_HPP
#include "../../../core/subobject.hpp"
#include "../../../core/table.hpp"
#include "../../../core/method.hpp"
#include "../../protocol/marklincan/locomotivelist.hpp"
#include <memory>
class MarklinCANInterface;
class MarklinCANLocomotiveListTableModel;
class MarklinCANLocomotiveList : public SubObject, public Table
{
CLASS_ID("marklin_can_locomotive_list")
friend class MarklinCANInterface;
friend class MarklinCANLocomotiveListTableModel;
private:
std::shared_ptr<MarklinCAN::LocomotiveList> m_data;
std::vector<MarklinCANLocomotiveListTableModel*> m_models;
void worldEvent(WorldState state, WorldEvent event) override;
void clear();
MarklinCANInterface& interface();
void import(const MarklinCAN::LocomotiveList::Locomotive& locomotive);
void updateEnabled();
public:
Method<void(const std::string&)> importOrSync;
Method<void()> importOrSyncAll;
Method<void()> reload;
MarklinCANLocomotiveList(Object& parent_, std::string_view parentPropertyName);
const std::shared_ptr<MarklinCAN::LocomotiveList>& data() const
{
return m_data;
}
void setData(std::shared_ptr<MarklinCAN::LocomotiveList> value);
TableModelPtr getModel() final;
};
#endif

Datei anzeigen

@ -0,0 +1,86 @@
/**
* server/src/hardware/interface/marklincan/marklincanlocomotivelisttablemodel.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 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 "marklincanlocomotivelisttablemodel.hpp"
#include "marklincanlocomotivelist.hpp"
#include "../../protocol/marklincan/locomotivelist.hpp"
#include "../../../utils/displayname.hpp"
#include "../../../utils/tohex.hpp"
constexpr uint32_t columnName = 0;
constexpr uint32_t columnProtocol = 1;
constexpr uint32_t columnAddress = 2;
constexpr uint32_t columnMFXUID = 3;
MarklinCANLocomotiveListTableModel::MarklinCANLocomotiveListTableModel(MarklinCANLocomotiveList& list)
: m_list{list.shared_ptr<MarklinCANLocomotiveList>()}
{
assert(m_list);
m_list->m_models.push_back(this);
setColumnHeaders({
DisplayName::Object::name,
std::string_view{"decoder:protocol"},
DisplayName::Hardware::address,
std::string_view{"decoder:mfx_uid"}
});
setRowCount(m_list->data() ? m_list->data()->size() : 0);
}
MarklinCANLocomotiveListTableModel::~MarklinCANLocomotiveListTableModel()
{
auto it = std::find(m_list->m_models.begin(), m_list->m_models.end(), this);
assert(it != m_list->m_models.end());
m_list->m_models.erase(it);
}
std::string MarklinCANLocomotiveListTableModel::getText(uint32_t column, uint32_t row) const
{
if(m_list->data() && row < m_list->data()->size())
{
const auto& locomotive = m_list->data()->operator[](row);
switch(column)
{
case columnName:
return locomotive.name;
case columnProtocol:
return
std::string("$")
.append(EnumName<DecoderProtocol>::value)
.append(":")
.append(EnumValues<DecoderProtocol>::value.at(locomotive.protocol))
.append("$");
case columnAddress:
return std::to_string(locomotive.protocol == DecoderProtocol::MFX ? locomotive.sid : locomotive.address);
case columnMFXUID:
if(locomotive.protocol == DecoderProtocol::MFX)
return toHex(locomotive.mfxUID);
return {};
}
}
return {};
}

Datei anzeigen

@ -0,0 +1,46 @@
/**
* server/src/hardware/interface/marklincan/marklincanlocomotivelisttablemodel.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 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_MARKLINCAN_MARKLINCANLOCOMOTIVELISTTABLEMODEL_HPP
#define TRAINTASTIC_SERVER_HARDWARE_INTERFACE_MARKLINCAN_MARKLINCANLOCOMOTIVELISTTABLEMODEL_HPP
#include "../../../core/tablemodel.hpp"
class MarklinCANLocomotiveList;
class MarklinCANLocomotiveListTableModel : public TableModel
{
CLASS_ID("table_model.marklin_can_locomotive_list")
friend class MarklinCANLocomotiveList;
private:
std::shared_ptr<MarklinCANLocomotiveList> m_list;
public:
MarklinCANLocomotiveListTableModel(MarklinCANLocomotiveList& list);
~MarklinCANLocomotiveListTableModel() override;
std::string getText(uint32_t column, uint32_t row) const final;
};
#endif

Datei anzeigen

@ -0,0 +1,71 @@
/**
* server/src/hardware/interface/marklincan/marklincannodelist.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 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 "marklincannodelist.hpp"
#include "marklincannodelisttablemodel.hpp"
MarklinCANNodeList::MarklinCANNodeList(Object& parent_, std::string_view parentPropertyName)
: SubObject(parent_, parentPropertyName)
{
}
TableModelPtr MarklinCANNodeList::getModel()
{
return std::make_shared<MarklinCANNodeListTableModel>(*this);
}
void MarklinCANNodeList::update(const MarklinCAN::Node& node)
{
auto it = std::find_if(m_nodes.begin(), m_nodes.end(),
[uid=node.uid](const auto& item)
{
return item.uid == uid;
});
if(it == m_nodes.end()) // add
{
m_nodes.push_back(node);
const uint32_t rowCount = m_nodes.size();
for(auto& model : m_models)
{
model->setRowCount(rowCount);
}
}
else // update
{
*it = node;
const uint32_t row = it - m_nodes.begin();
for(auto& model : m_models)
{
model->rowsChanged(row, row);
}
}
}
void MarklinCANNodeList::clear()
{
m_nodes.clear();
for(auto& model : m_models)
{
model->setRowCount(0);
}
}

Datei anzeigen

@ -0,0 +1,53 @@
/**
* server/src/hardware/interface/marklincan/marklincannodelist.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 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_MARKLINCAN_MARKLINCANNODELIST_HPP
#define TRAINTASTIC_SERVER_HARDWARE_INTERFACE_MARKLINCAN_MARKLINCANNODELIST_HPP
#include "../../../core/subobject.hpp"
#include "../../../core/table.hpp"
#include "../../protocol/marklincan/node.hpp"
class MarklinCANInterface;
class MarklinCANNodeListTableModel;
class MarklinCANNodeList : public SubObject, public Table
{
CLASS_ID("marklin_can_node_list")
friend class MarklinCANInterface;
friend class MarklinCANNodeListTableModel;
private:
std::vector<MarklinCAN::Node> m_nodes;
std::vector<MarklinCANNodeListTableModel*> m_models;
void update(const MarklinCAN::Node& node);
void clear();
public:
MarklinCANNodeList(Object& parent_, std::string_view parentPropertyName);
TableModelPtr getModel() final;
};
#endif

Datei anzeigen

@ -0,0 +1,90 @@
/**
* server/src/hardware/interface/marklincan/marklincannodelisttablemodel.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 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 "marklincannodelisttablemodel.hpp"
#include "marklincannodelist.hpp"
#include "../../../utils/displayname.hpp"
#include "../../../utils/tohex.hpp"
constexpr uint32_t columnUID = 0;
constexpr uint32_t columnName = 1;
constexpr uint32_t columnArticleNumber = 2;
constexpr uint32_t columnSerialNumber = 3;
constexpr uint32_t columnSoftwareVersion = 4;
constexpr uint32_t columnDeviceId = 5;
MarklinCANNodeListTableModel::MarklinCANNodeListTableModel(MarklinCANNodeList& list)
: m_list{list.shared_ptr<MarklinCANNodeList>()}
{
assert(m_list);
m_list->m_models.push_back(this);
setColumnHeaders({
std::string_view{"table_model.marklin_can_node_list:uid"},
std::string_view{"table_model.marklin_can_node_list:device_name"},
std::string_view{"table_model.marklin_can_node_list:article_number"},
std::string_view{"table_model.marklin_can_node_list:serial_number"},
std::string_view{"table_model.marklin_can_node_list:software_version"},
std::string_view{"table_model.marklin_can_node_list:device_id"}
});
setRowCount(m_list->m_nodes.size());
}
MarklinCANNodeListTableModel::~MarklinCANNodeListTableModel()
{
auto it = std::find(m_list->m_models.begin(), m_list->m_models.end(), this);
assert(it != m_list->m_models.end());
m_list->m_models.erase(it);
}
std::string MarklinCANNodeListTableModel::getText(uint32_t column, uint32_t row) const
{
if(row < m_list->m_nodes.size())
{
const auto& node = m_list->m_nodes[row];
switch(column)
{
case columnUID:
return toHex(node.uid);
case columnName:
return node.deviceName;
case columnArticleNumber:
return node.articleNumber;
case columnSerialNumber:
if(node.serialNumber == 0)
return {};
return std::to_string(node.serialNumber);
case columnSoftwareVersion:
return std::to_string(node.softwareVersionMajor).append(".").append(std::to_string(node.softwareVersionMinor));
case columnDeviceId:
return toHex(static_cast<uint16_t>(node.deviceId));
}
}
return {};
}

Datei anzeigen

@ -0,0 +1,46 @@
/**
* server/src/hardware/interface/marklincan/marklincannodelisttablemodel.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 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_MARKLINCAN_MARKLINCANNODELISTTABLEMODEL_HPP
#define TRAINTASTIC_SERVER_HARDWARE_INTERFACE_MARKLINCAN_MARKLINCANNODELISTTABLEMODEL_HPP
#include "../../../core/tablemodel.hpp"
class MarklinCANNodeList;
class MarklinCANNodeListTableModel : public TableModel
{
CLASS_ID("table_model.marklin_can_node_list")
friend class MarklinCANNodeList;
private:
std::shared_ptr<MarklinCANNodeList> m_list;
public:
MarklinCANNodeListTableModel(MarklinCANNodeList& list);
~MarklinCANNodeListTableModel() override;
std::string getText(uint32_t column, uint32_t row) const final;
};
#endif

Datei anzeigen

@ -0,0 +1,358 @@
/**
* server/src/hardware/interface/marklincaninterface.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 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 "marklincaninterface.hpp"
#include "../decoder/list/decoderlist.hpp" // ????
#include "../decoder/list/decoderlisttablemodel.hpp"
#include "../input/input.hpp"
#include "../input/list/inputlist.hpp"
#include "../output/list/outputlist.hpp"
#include "../protocol/marklincan/iohandler/simulationiohandler.hpp"
#include "../protocol/marklincan/iohandler/tcpiohandler.hpp"
#include "../protocol/marklincan/iohandler/udpiohandler.hpp"
#ifdef __linux__
#include "../protocol/marklincan/iohandler/socketcaniohandler.hpp"
#endif
#include "../protocol/marklincan/iohandler/serialiohandler.hpp"
#include "../protocol/marklincan/kernel.hpp"
#include "../protocol/marklincan/settings.hpp"
#include "../../core/attributes.hpp"
#include "../../core/objectproperty.tpp"
#include "../../log/log.hpp"
#include "../../log/logmessageexception.hpp"
#include "../../utils/displayname.hpp"
#include "../../utils/inrange.hpp"
constexpr auto decoderListColumns = DecoderListColumn::Id | DecoderListColumn::Name | DecoderListColumn::Protocol | DecoderListColumn::Address;
constexpr auto inputListColumns = InputListColumn::Id | InputListColumn::Name | InputListColumn::Address;
constexpr auto outputListColumns = OutputListColumn::Id | OutputListColumn::Name | OutputListColumn::Channel | OutputListColumn::Address;
MarklinCANInterface::MarklinCANInterface(World& world, std::string_view _id)
: Interface(world, _id)
, DecoderController(*this, decoderListColumns)
, InputController(static_cast<IdObject&>(*this))
, OutputController(static_cast<IdObject&>(*this))
, type{this, "type", MarklinCANInterfaceType::NetworkTCP, PropertyFlags::ReadWrite | PropertyFlags::Store,
[this](MarklinCANInterfaceType /*value*/)
{
typeChanged();
}}
, hostname{this, "hostname", "", PropertyFlags::ReadWrite | PropertyFlags::Store}
, interface{this, "interface", "", PropertyFlags::ReadWrite | PropertyFlags::Store}
, device{this, "device", "", PropertyFlags::ReadWrite | PropertyFlags::Store}
, baudrate{this, "baudrate", 115'200, PropertyFlags::ReadWrite | PropertyFlags::Store}
, flowControl{this, "flow_control", SerialFlowControl::None, PropertyFlags::ReadWrite | PropertyFlags::Store}
, marklinCAN{this, "marklin_can", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject}
, marklinCANNodeList{this, "marklin_can_node_list", nullptr, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::SubObject}
, marklinCANLocomotiveList{this, "marklin_can_locomotive_list", nullptr, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::SubObject}
{
name = "M\u00E4rklin CAN";
marklinCAN.setValueInternal(std::make_shared<MarklinCAN::Settings>(*this, marklinCAN.name()));
marklinCANNodeList.setValueInternal(std::make_shared<MarklinCANNodeList>(*this, marklinCANNodeList.name()));
marklinCANLocomotiveList.setValueInternal(std::make_shared<MarklinCANLocomotiveList>(*this, marklinCANLocomotiveList.name()));
Attributes::addDisplayName(type, DisplayName::Interface::type);
Attributes::addEnabled(type, !online);
Attributes::addValues(type, marklinCANInterfaceTypeValues);
m_interfaceItems.insertBefore(type, notes);
Attributes::addDisplayName(hostname, DisplayName::IP::hostname);
Attributes::addEnabled(hostname, !online);
Attributes::addVisible(hostname, false);
m_interfaceItems.insertBefore(hostname, notes);
Attributes::addDisplayName(interface, DisplayName::Hardware::interface);
Attributes::addEnabled(interface, !online);
Attributes::addVisible(interface, false);
m_interfaceItems.insertBefore(interface, notes);
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(marklinCAN, DisplayName::Hardware::marklinCAN);
m_interfaceItems.insertBefore(marklinCAN, notes);
m_interfaceItems.insertBefore(marklinCANNodeList, notes);
m_interfaceItems.insertBefore(marklinCANLocomotiveList, notes);
m_interfaceItems.insertBefore(decoders, notes);
m_interfaceItems.insertBefore(inputs, notes);
m_interfaceItems.insertBefore(outputs, notes);
typeChanged();
}
tcb::span<const DecoderProtocol> MarklinCANInterface::decoderProtocols() const
{
static constexpr std::array<DecoderProtocol, 4> protocols{DecoderProtocol::DCCShort, DecoderProtocol::DCCLong, DecoderProtocol::MFX, DecoderProtocol::Motorola};
return tcb::span<const DecoderProtocol>{protocols.data(), protocols.size()};
}
tcb::span<const uint8_t> MarklinCANInterface::decoderSpeedSteps(DecoderProtocol protocol) const
{
static constexpr std::array<uint8_t, 4> dccLongSpeedSteps{{28, 128}}; // 14 not supported for long addresses
switch(protocol)
{
case DecoderProtocol::DCCLong:
return dccLongSpeedSteps;
default:
return DecoderController::decoderSpeedSteps(protocol);
}
}
void MarklinCANInterface::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber)
{
if(m_kernel)
m_kernel->decoderChanged(decoder, changes, functionNumber);
}
std::pair<uint32_t, uint32_t> MarklinCANInterface::inputAddressMinMax(uint32_t /*channel*/) const
{
return {MarklinCAN::Kernel::s88AddressMin, MarklinCAN::Kernel::s88AddressMax};
}
const std::vector<uint32_t>* MarklinCANInterface::outputChannels() const
{
return &MarklinCAN::Kernel::outputChannels;
}
const std::vector<std::string_view>* MarklinCANInterface::outputChannelNames() const
{
return &MarklinCAN::Kernel::outputChannelNames;
}
std::pair<uint32_t, uint32_t> MarklinCANInterface::outputAddressMinMax(uint32_t channel) const
{
using namespace MarklinCAN;
switch(channel)
{
case Kernel::OutputChannel::motorola:
return {Kernel::outputMotorolaAddressMin, Kernel::outputMotorolaAddressMax};
case Kernel::OutputChannel::dcc:
return {Kernel::outputDCCAddressMin, Kernel::outputDCCAddressMax};
case Kernel::OutputChannel::sx1:
return {Kernel::outputSX1AddressMin, Kernel::outputSX1AddressMax};
}
assert(false);
return {0, 0};
}
bool MarklinCANInterface::setOutputValue(uint32_t channel, uint32_t address, bool value)
{
return
m_kernel &&
inRange(address, outputAddressMinMax(channel)) &&
m_kernel->setOutput(channel, static_cast<uint16_t>(address), value);
}
bool MarklinCANInterface::setOnline(bool& value, bool simulation)
{
if(!m_kernel && value)
{
try
{
if(simulation)
{
m_kernel = MarklinCAN::Kernel::create<MarklinCAN::SimulationIOHandler>(marklinCAN->config());
}
else
{
switch(type.value())
{
case MarklinCANInterfaceType::NetworkTCP:
m_kernel = MarklinCAN::Kernel::create<MarklinCAN::TCPIOHandler>(marklinCAN->config(), hostname.value());
break;
case MarklinCANInterfaceType::NetworkUDP:
m_kernel = MarklinCAN::Kernel::create<MarklinCAN::UDPIOHandler>(marklinCAN->config(), hostname.value());
break;
case MarklinCANInterfaceType::SocketCAN:
#ifdef __linux__
m_kernel = MarklinCAN::Kernel::create<MarklinCAN::SocketCANIOHandler>(marklinCAN->config(), interface.value());
break;
#else
setState(InterfaceState::Error);
Log::log(*this, LogMessage::C2005_SOCKETCAN_IS_ONLY_AVAILABLE_ON_LINUX);
return false;
#endif
case MarklinCANInterfaceType::Serial:
m_kernel = MarklinCAN::Kernel::create<MarklinCAN::SerialIOHandler>(marklinCAN->config(), device.value(), baudrate.value(), flowControl.value());
break;
}
}
assert(m_kernel);
setState(InterfaceState::Initializing);
m_kernel->setLogId(id.value());
m_kernel->setOnStarted(
[this]()
{
setState(InterfaceState::Online);
Attributes::setEnabled(marklinCANLocomotiveList->reload, true);
});
m_kernel->setOnError(
[this]()
{
setState(InterfaceState::Error);
online = false; // communication no longer possible
});
m_kernel->setOnNodeChanged(
[this](const MarklinCAN::Node& node)
{
marklinCANNodeList->update(node);
});
m_kernel->setOnLocomotiveListChanged(
[this](const std::shared_ptr<MarklinCAN::LocomotiveList>& list)
{
marklinCANLocomotiveList->setData(list);
});
m_kernel->setDecoderController(this);
m_kernel->setInputController(this);
m_kernel->setOutputController(this);
m_kernel->start();
m_marklinCANPropertyChanged = marklinCAN->propertyChanged.connect(
[this](BaseProperty& /*property*/)
{
m_kernel->setConfig(marklinCAN->config());
});
Attributes::setEnabled({type, hostname, interface, device, baudrate, flowControl}, false);
}
catch(const LogMessageException& e)
{
setState(InterfaceState::Offline);
Log::log(*this, e.message(), e.args());
return false;
}
}
else if(m_kernel && !value)
{
Attributes::setEnabled({type, hostname, interface, device, baudrate, flowControl}, true);
Attributes::setEnabled(marklinCANLocomotiveList->reload, false);
marklinCANNodeList->clear();
marklinCANLocomotiveList->clear();
m_marklinCANPropertyChanged.disconnect();
m_kernel->stop();
m_kernel.reset();
if(status->state != InterfaceState::Error)
setState(InterfaceState::Offline);
}
return true;
}
void MarklinCANInterface::addToWorld()
{
Interface::addToWorld();
DecoderController::addToWorld();
InputController::addToWorld(inputListColumns);
OutputController::addToWorld(outputListColumns);
}
void MarklinCANInterface::loaded()
{
Interface::loaded();
typeChanged();
}
void MarklinCANInterface::destroying()
{
OutputController::destroying();
InputController::destroying();
DecoderController::destroying();
Interface::destroying();
}
void MarklinCANInterface::worldEvent(WorldState state, WorldEvent event)
{
Interface::worldEvent(state, event);
if(m_kernel)
{
switch(event)
{
case WorldEvent::PowerOff:
m_kernel->systemStop();
break;
case WorldEvent::PowerOn:
m_kernel->systemGo();
m_kernel->systemHalt();
break;
case WorldEvent::Stop:
m_kernel->systemHalt();
break;
case WorldEvent::Run:
m_kernel->systemGo();
break;
default:
break;
}
}
}
void MarklinCANInterface::idChanged(const std::string& newId)
{
if(m_kernel)
m_kernel->setLogId(newId);
}
void MarklinCANInterface::typeChanged()
{
Attributes::setVisible(hostname, isNetwork(type));
Attributes::setVisible(interface, type == MarklinCANInterfaceType::SocketCAN);
Attributes::setVisible({device, baudrate, flowControl}, type == MarklinCANInterfaceType::Serial);
}

Datei anzeigen

@ -0,0 +1,96 @@
/**
* server/src/hardware/interface/marklincaninterface.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 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_MARKLINCANINTERFACE_HPP
#define TRAINTASTIC_SERVER_HARDWARE_INTERFACE_MARKLINCANINTERFACE_HPP
#include "interface.hpp"
#include "marklincan/marklincannodelist.hpp"
#include "marklincan/marklincanlocomotivelist.hpp"
#include <traintastic/enum/marklincaninterfacetype.hpp>
#include "../decoder/decodercontroller.hpp"
#include "../input/inputcontroller.hpp"
#include "../output/outputcontroller.hpp"
#include "../protocol/marklincan/kernel.hpp"
#include "../protocol/marklincan/settings.hpp"
#include "../../core/serialdeviceproperty.hpp"
#include "../../enum/serialflowcontrol.hpp"
/**
* @brief Märklin CAN hardware interface
*/
class MarklinCANInterface final
: public Interface
, public DecoderController
, public InputController
, public OutputController
{
CLASS_ID("interface.marklin_can")
CREATE(MarklinCANInterface)
DEFAULT_ID("marklin_can")
friend class MarklinCANLocomotiveList;
private:
std::unique_ptr<MarklinCAN::Kernel> m_kernel;
boost::signals2::connection m_marklinCANPropertyChanged;
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, bool simulation) final;
public:
Property<MarklinCANInterfaceType> type;
Property<std::string> hostname;
Property<std::string> interface;
SerialDeviceProperty device;
Property<uint32_t> baudrate;
Property<SerialFlowControl> flowControl;
ObjectProperty<MarklinCAN::Settings> marklinCAN;
ObjectProperty<MarklinCANNodeList> marklinCANNodeList;
ObjectProperty<MarklinCANLocomotiveList> marklinCANLocomotiveList;
MarklinCANInterface(World& world, std::string_view _id);
// DecoderController:
tcb::span<const DecoderProtocol> decoderProtocols() const final;
tcb::span<const uint8_t> decoderSpeedSteps(DecoderProtocol protocol) const final;
void decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber) final;
// InputController:
std::pair<uint32_t, uint32_t> inputAddressMinMax(uint32_t /*channel*/) const final;
// OutputController:
const std::vector<uint32_t>* outputChannels() const final;
const std::vector<std::string_view>* outputChannelNames() const final;
std::pair<uint32_t, uint32_t> outputAddressMinMax(uint32_t channel) const final;
[[nodiscard]] bool setOutputValue(uint32_t channel, uint32_t address, bool value) final;
};
#endif

Datei anzeigen

@ -40,7 +40,7 @@
#include "../../utils/inrange.hpp"
#include "../../world/world.hpp"
constexpr auto decoderListColumns = DecoderListColumn::Id | DecoderListColumn::Name | DecoderListColumn::Address;
constexpr auto decoderListColumns = DecoderListColumn::Id | DecoderListColumn::Name | DecoderListColumn::Protocol | DecoderListColumn::Address;
constexpr auto inputListColumns = InputListColumn::Id | InputListColumn::Name | InputListColumn::Channel | InputListColumn::Address;
constexpr auto outputListColumns = OutputListColumn::Id | OutputListColumn::Name | OutputListColumn::Address;

Datei anzeigen

@ -282,7 +282,7 @@ void Kernel::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes,
{
if(has(changes, DecoderChangeFlags::EmergencyStop | DecoderChangeFlags::Throttle | DecoderChangeFlags::Direction))
{
const uint8_t speed = Decoder::throttleToSpeedStep(decoder.throttle, 126);
const uint8_t speed = Decoder::throttleToSpeedStep<uint8_t>(decoder.throttle, 126);
m_ioContext.post(
[this, address=decoder.address.value(), emergencyStop=decoder.emergencyStop.value(), speed, direction=decoder.direction.value()]()
{

Datei anzeigen

@ -53,9 +53,9 @@ static void updateDecoderSpeed(const std::shared_ptr<Decoder>& decoder, uint8_t
else
{
speed--; // decrement one for ESTOP: 2..127 -> 1..126
const uint8_t currentStep = Decoder::throttleToSpeedStep(decoder->throttle.value(), SPEED_MAX - 1);
const uint8_t currentStep = Decoder::throttleToSpeedStep<uint8_t>(decoder->throttle.value(), SPEED_MAX - 1);
if(currentStep != speed) // only update trottle if it is a different step
decoder->throttle.setValueInternal(Decoder::speedStepToThrottle(speed, SPEED_MAX - 1));
decoder->throttle.setValueInternal(Decoder::speedStepToThrottle<uint8_t>(speed, SPEED_MAX - 1));
}
}
@ -992,7 +992,7 @@ void Kernel::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes,
if(has(changes, DecoderChangeFlags::EmergencyStop | DecoderChangeFlags::Throttle))
{
const uint8_t speedStep = Decoder::throttleToSpeedStep(decoder.throttle, SPEED_MAX - 1);
const uint8_t speedStep = Decoder::throttleToSpeedStep<uint8_t>(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

Datei anzeigen

@ -0,0 +1,40 @@
/**
* server/src/hardware/protocol/marklincan/config.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 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_MARKLINCAN_CONFIG_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_MARKLINCAN_CONFIG_HPP
namespace MarklinCAN {
struct Config
{
uint32_t defaultSwitchTime; //!< Default switch time in ms
uint32_t nodeUID;
uint32_t nodeSerialNumber;
bool debugLogRXTX;
bool debugStatusDataConfig;
bool debugConfigStream;
};
}
#endif

Datei anzeigen

@ -0,0 +1,64 @@
/**
* server/src/hardware/protocol/marklincan/configdatastreamcollector.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 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 "configdatastreamcollector.hpp"
namespace MarklinCAN {
ConfigDataStreamCollector::ConfigDataStreamCollector(std::string name_)
: name{std::move(name_)}
{
}
ConfigDataStreamCollector::Status ConfigDataStreamCollector::process(const ConfigDataStream& message)
{
if(message.isData() && m_crc != 0x0000)
{
if(m_offset >= m_data.size()) /*[[unlikely]]*/
return ErrorToMuchData;
if(m_offset + 8 >= m_data.size()) // last message
{
std::memcpy(m_data.data() + m_offset, message.data, m_data.size() - m_offset);
m_offset = m_data.size();
if(crc16(m_data) != m_crc)
return ErrorInvalidCRC;
return Complete;
}
std::memcpy(m_data.data() + m_offset, message.data, 8);
m_offset += 8;
return Collecting;
}
else if(message.isStart() && m_crc == 0x0000)
{
m_data.resize(message.length());
m_crc = message.crc();
return Collecting;
}
return ErrorInvalidMessage;
}
}

Datei anzeigen

@ -0,0 +1,78 @@
/**
* server/src/hardware/protocol/marklincan/configdatastreamcollector.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 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_MARKLINCAN_CONFIGDATASTREAMCOLLECTOR_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_MARKLINCAN_CONFIGDATASTREAMCOLLECTOR_HPP
#include <cstddef>
#include <vector>
#include "message/configdata.hpp"
namespace MarklinCAN {
class ConfigDataStreamCollector
{
private:
uint16_t m_crc = 0;
std::vector<std::byte> m_data;
size_t m_offset = 0;
public:
enum Status
{
Collecting,
Complete,
ErrorInvalidMessage,
ErrorToMuchData,
ErrorInvalidCRC,
};
const std::string name;
ConfigDataStreamCollector(std::string name_);
const std::byte* data() const
{
return m_data.data();
}
size_t dataSize() const
{
return m_data.size();
}
const std::vector<std::byte>& bytes() const
{
return m_data;
}
Status process(const ConfigDataStream& message);
std::vector<std::byte>&& releaseData()
{
return std::move(m_data);
}
};
}
#endif

Datei anzeigen

@ -0,0 +1,61 @@
/**
* server/src/hardware/protocol/marklincan/iohandler/iohandler.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 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_MARKLINCAN_IOHANDLER_IOHANDLER_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_MARKLINCAN_IOHANDLER_IOHANDLER_HPP
namespace MarklinCAN {
class Kernel;
struct Message;
class IOHandler
{
protected:
Kernel& m_kernel;
IOHandler(Kernel& kernel)
: m_kernel{kernel}
{
}
public:
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) = 0;
};
template<class T>
constexpr bool isSimulation()
{
return false;
}
}
#endif

Datei anzeigen

@ -0,0 +1,64 @@
/**
* server/src/hardware/protocol/marklincan/iohandler/networkiohandler.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 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 "networkiohandler.hpp"
#include "../../../../utils/endian.hpp"
namespace MarklinCAN {
void NetworkIOHandler::start()
{
read();
}
bool NetworkIOHandler::send(const Message& message)
{
if(m_writeBufferOffset + sizeof(NetworkMessage) > m_writeBuffer.size())
return false;
const bool wasEmpty = m_writeBufferOffset == 0;
toNetworkMessage(message, *reinterpret_cast<NetworkMessage*>(m_writeBuffer.data() + m_writeBufferOffset));
m_writeBufferOffset += sizeof(NetworkMessage);
if(wasEmpty)
write();
return true;
}
Message NetworkIOHandler::toMessage(const NetworkMessage& networkMessage)
{
Message message;
message.id = be_to_host(networkMessage.idBE);
message.dlc = networkMessage.dlc;
memcpy(message.data, networkMessage.data, std::min(sizeof(message.data), sizeof(networkMessage.data)));
return message;
}
void NetworkIOHandler::toNetworkMessage(const Message& message, NetworkMessage& networkMessage)
{
networkMessage.idBE = host_to_be(message.id);
networkMessage.dlc = message.dlc;
memcpy(networkMessage.data, message.data, std::min(sizeof(networkMessage.data), sizeof(message.data)));
}
}

Datei anzeigen

@ -0,0 +1,69 @@
/**
* server/src/hardware/protocol/marklincan/iohandler/networkiohandler.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 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_MARKLINCAN_IOHANDLER_NETWORKIOHANDLER_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_MARKLINCAN_IOHANDLER_NETWORKIOHANDLER_HPP
#include "iohandler.hpp"
#include <array>
#include "../messages.hpp"
#include "../../../../utils/packed.hpp"
namespace MarklinCAN {
class NetworkIOHandler : public IOHandler
{
protected:
PRAGMA_PACK_PUSH_1
struct NetworkMessage
{
uint32_t idBE;
uint8_t dlc;
uint8_t data[8];
} ATTRIBUTE_PACKED;
PRAGMA_PACK_POP
static_assert(sizeof(NetworkMessage) == 13);
std::array<std::byte, 1500> m_writeBuffer;
size_t m_writeBufferOffset;
NetworkIOHandler(Kernel& kernel)
: IOHandler(kernel)
, m_writeBufferOffset{0}
{
}
virtual void read() = 0;
virtual void write() = 0;
static Message toMessage(const NetworkMessage& networkMessage);
static void toNetworkMessage(const Message& message, NetworkMessage& networkMessage);
public:
void start() final;
bool send(const Message& message) final;
};
}
#endif

Datei anzeigen

@ -0,0 +1,110 @@
/**
* server/src/hardware/protocol/marklincan/iohandler/serialiohandler.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 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 "../../../../log/logmessageexception.hpp"
#include "../../../../utils/serialport.hpp"
namespace MarklinCAN {
SerialIOHandler::SerialIOHandler(Kernel& kernel, const std::string& device, uint32_t baudrate, SerialFlowControl flowControl)
: NetworkIOHandler(kernel)
, m_serialPort{m_kernel.ioContext()}
, m_readBufferOffset{0}
{
SerialPort::open(m_serialPort, device, baudrate, 8, SerialParity::None, SerialStopBits::One, flowControl);
}
void SerialIOHandler::stop()
{
m_serialPort.cancel();
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)
{
const std::byte* pos = m_readBuffer.data();
bytesTransferred += m_readBufferOffset;
while(bytesTransferred >= sizeof(NetworkMessage))
{
m_kernel.receive(toMessage(*reinterpret_cast<const NetworkMessage*>(pos)));
pos += sizeof(NetworkMessage);
bytesTransferred -= sizeof(NetworkMessage);
}
if(bytesTransferred != 0)
memmove(m_readBuffer.data(), pos, bytesTransferred);
m_readBufferOffset = 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);
m_kernel.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);
m_kernel.error();
});
}
});
}
}

Datei anzeigen

@ -0,0 +1,50 @@
/**
* server/src/hardware/protocol/marklincan/iohandler/serialiohandler.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 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_MARKLINCAN_IOHANDLER_SERIALIOHANDLER_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_MARKLINCAN_IOHANDLER_SERIALIOHANDLER_HPP
#include "networkiohandler.hpp"
#include <boost/asio/serial_port.hpp>
#include "../../../../enum/serialflowcontrol.hpp"
namespace MarklinCAN {
class SerialIOHandler final : public NetworkIOHandler
{
private:
boost::asio::serial_port m_serialPort;
std::array<std::byte, 1500> m_readBuffer;
size_t m_readBufferOffset;
void read() final;
void write() final;
public:
SerialIOHandler(Kernel& kernel, const std::string& device, uint32_t baudrate, SerialFlowControl flowControl);
void stop() final;
};
}
#endif

Datei anzeigen

@ -0,0 +1,336 @@
/**
* server/src/hardware/protocol/marklincan/iohandler/simulationiohandler.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 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 "simulationiohandler.hpp"
#include "../kernel.hpp"
#include "../message/statusdataconfig.hpp"
#include "../../../../utils/random.hpp"
#include "../../../../utils/zlib.hpp"
namespace MarklinCAN {
SimulationIOHandler::SimulationIOHandler(Kernel& kernel)
: IOHandler(kernel)
, m_pingTimer{kernel.ioContext()}
, m_bootloaderCANTimer{kernel.ioContext()}
, m_delayedMessageTimer{kernel.ioContext()}
{
}
void SimulationIOHandler::start()
{
using namespace std::chrono_literals;
auto expireAfter = std::chrono::milliseconds(Random::value<int>(0, bootstrapCANInterval.count()));
startBootloaderCANTimer(expireAfter);
startPingTimer(expireAfter + 1s);
}
void SimulationIOHandler::startBootloaderCANTimer(std::chrono::milliseconds expireAfter)
{
m_bootloaderCANTimer.expires_after(expireAfter);
m_bootloaderCANTimer.async_wait(
[this](const boost::system::error_code& ec)
{
if(!ec)
{
reply(BootloaderCAN(guiUID));
}
if(ec != boost::asio::error::operation_aborted)
{
startBootloaderCANTimer();
}
});
}
void SimulationIOHandler::startPingTimer(std::chrono::milliseconds expireAfter)
{
m_pingTimer.expires_after(expireAfter);
m_pingTimer.async_wait(
[this](const boost::system::error_code& ec)
{
if(!ec)
{
reply(Ping(guiUID));
replyPing();
}
if(ec != boost::asio::error::operation_aborted)
{
startPingTimer();
}
});
}
void SimulationIOHandler::stop()
{
while(!m_delayedMessages.empty())
m_delayedMessages.pop();
m_bootloaderCANTimer.cancel();
m_pingTimer.cancel();
m_delayedMessageTimer.cancel();
}
bool SimulationIOHandler::send(const Message& message)
{
switch(message.command())
{
case Command::System:
{
const auto& system = static_cast<const SystemMessage&>(message);
switch(system.subCommand())
{
case SystemSubCommand::SystemStop:
case SystemSubCommand::SystemGo:
case SystemSubCommand::SystemHalt:
case SystemSubCommand::LocomotiveEmergencyStop:
case SystemSubCommand::LocomotiveCycleEnd:
// not (yet) implemented
break;
case SystemSubCommand::AccessorySwitchTime:
if(!message.isResponse() && message.dlc == 7)
{
auto response = static_cast<const AccessorySwitchTime&>(message);
if(response.switchTime() == 0)
m_switchTime = defaultSwitchTime;
else
m_switchTime = std::chrono::milliseconds(response.switchTime() * 10);
response.setResponse(true);
reply(response);
}
break;
case SystemSubCommand::Overload:
case SystemSubCommand::Status:
case SystemSubCommand::ModelClock:
case SystemSubCommand::MFXSeek:
// not (yet) implemented
break;
}
break;
}
case Command::Discovery:
case Command::Bind:
case Command::Verify:
case Command::LocomotiveSpeed:
case Command::LocomotiveDirection:
case Command::LocomotiveFunction:
case Command::ReadConfig:
case Command::WriteConfig:
// not (yet) implemented
break;
case Command::AccessoryControl:
if(!message.isResponse() && (message.dlc == 6 || message.dlc == 8))
{
// confirm switch command:
AccessoryControl response{static_cast<const AccessoryControl&>(message)};
response.setResponse(true);
reply(response);
// handle switch time:
if(response.current() != 0 && (response.isDefaultSwitchTime() || response.switchTime() > 0))
{
const auto switchTime = response.isDefaultSwitchTime() ? m_switchTime : std::chrono::milliseconds(response.switchTime() * 10);
response.setResponse(false);
response.setCurrent(0);
reply(response, switchTime);
response.setResponse(true);
reply(response, switchTime + std::chrono::milliseconds(1));
}
}
break;
case Command::AccessoryConfig:
case Command::S88Polling:
case Command::FeedbackEvent:
case Command::SX1Event:
// not (yet) implemented
break;
case Command::Ping:
if(message.dlc == 0 && !message.isResponse())
{
replyPing();
reply(PingReply(guiUID, 4, 3, DeviceId::CS2GUI));
}
else if(message.dlc == 8 && message.isResponse())
{
const auto& pingReply = static_cast<const PingReply&>(message);
if(m_knownPingUIDs.count(pingReply.uid()) == 0)
{
m_knownPingUIDs.emplace(pingReply.uid());
reply(StatusDataConfig(guiUID, pingReply.uid(), 0));
}
}
break;
case Command::Update:
case Command::ReadConfigData:
case Command::BootloaderCAN:
case Command::BootloaderTrack:
// not (yet) implemented
break;
case Command::StatusDataConfig:
if(message.dlc == 5 && !message.isResponse())
{
const uint32_t uid = static_cast<const UidMessage&>(message).uid();
const uint8_t index = message.data[4];
switch(index)
{
case 0x00:
{
StatusData::DeviceDescription desc;
switch(uid)
{
case gfpUID:
desc.numberOfReadings = 4;
desc.numberOfConfigurationChannels = 2;
desc.serialNumber = 12345;
desc.articleNumber[0] = '6';
desc.articleNumber[1] = '0';
desc.articleNumber[2] = '2';
desc.articleNumber[3] = '1';
desc.articleNumber[4] = '4';
desc.deviceName = "Central Station 2";
break;
case linkS88UID:
desc.numberOfReadings = 0;
desc.numberOfConfigurationChannels = 12;
desc.serialNumber = 1234;
desc.articleNumber[0] = '6';
desc.articleNumber[1] = '0';
desc.articleNumber[2] = '8';
desc.articleNumber[3] = '8';
desc.articleNumber[4] = '3';
desc.deviceName = "Link S88";
break;
case guiUID: // CS2 GUI doesn't respond to this request
default:
// no response
return true;
}
for(const auto& msg : statusDataConfigReply(uid, uid, index, desc))
reply(msg);
break;
}
}
}
break;
case Command::ConfigData:
if(message.dlc == 8 && !message.isResponse())
{
const auto& configData = static_cast<const ConfigData&>(message);
if(configData.name() == ConfigDataName::loks)
{
static constexpr std::string_view emptyLoks = "[lokomotive]\nversion\n .minor=4\nsession\n .id=13749\n";
// compress:
std::vector<std::byte> data;
data.resize(emptyLoks.size());
if(!ZLib::compressString(emptyLoks, data))
data.resize(data.size() * 2); // retry with larger buffer
if(!ZLib::compressString(emptyLoks, data))
break;
// prepend uncompressed size (big endian):
uint32_t uncompressedSize = host_to_be<uint32_t>(emptyLoks.size());
for(int i = sizeof(uncompressedSize) - 1; i >= 0; i--)
data.insert(data.begin(), reinterpret_cast<const std::byte*>(&uncompressedSize)[i]);
reply(ConfigData(guiUID, configData.name(), true));
reply(ConfigDataStream(guiUID, data.size(), crc16(data)));
const std::byte* end = data.data() + data.size();
for(std::byte* p = data.data(); p < end; p += 8)
{
reply(ConfigDataStream(guiUID, p, std::min<size_t>(end - p, 8)));
}
}
}
break;
case Command::ConfigDataStream:
// not (yet) implemented
break;
}
return true;
}
void SimulationIOHandler::reply(const Message& message)
{
// post the reply, so it has some delay
m_kernel.ioContext().post(
[this, message]()
{
m_kernel.receive(message);
});
}
void SimulationIOHandler::reply(const Message& message, std::chrono::milliseconds delay)
{
const auto time = std::chrono::steady_clock::now() + delay;
const bool restartTimer = m_delayedMessages.empty() || m_delayedMessages.top().time > time;
m_delayedMessages.push({time, message});
if(restartTimer)
restartDelayedMessageTimer();
}
void SimulationIOHandler::replyPing()
{
reply(PingReply(gfpUID, 3, 85, DeviceId::GleisFormatProzessorOrBooster));
reply(PingReply(linkS88UID, 1, 1, static_cast<DeviceId>(64)));
}
void SimulationIOHandler::restartDelayedMessageTimer()
{
assert(!m_delayedMessages.empty());
m_delayedMessageTimer.cancel();
m_delayedMessageTimer.expires_after(m_delayedMessages.top().time - std::chrono::steady_clock::now());
m_delayedMessageTimer.async_wait(
[this](const boost::system::error_code& ec)
{
if(!ec)
{
while(!m_delayedMessages.empty() && m_delayedMessages.top().time <= std::chrono::steady_clock::now())
{
reply(m_delayedMessages.top().message);
m_delayedMessages.pop();
}
}
if(ec != boost::asio::error::operation_aborted && !m_delayedMessages.empty())
restartDelayedMessageTimer();
});
}
}

Datei anzeigen

@ -0,0 +1,93 @@
/**
* server/src/hardware/protocol/marklincan/iohandler/simulationiohandler.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 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_MARKLINCAN_IOHANDLER_SIMULATIONIOHANDLER_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_MARKLINCAN_IOHANDLER_SIMULATIONIOHANDLER_HPP
#include "iohandler.hpp"
#include <chrono>
#include <queue>
#include <set>
#include <boost/asio/steady_timer.hpp>
#include "../messages.hpp"
namespace MarklinCAN {
class SimulationIOHandler final : public IOHandler
{
private:
static constexpr auto bootstrapCANInterval = std::chrono::milliseconds(11500);
static constexpr auto pingInterval = std::chrono::milliseconds(11500);
static constexpr auto defaultSwitchTime = std::chrono::milliseconds(1000);
static constexpr uint32_t gfpUID = 0x4353A442;
static constexpr uint32_t guiUID = gfpUID + 1;
static constexpr uint32_t linkS88UID = 0x5339BBD1;
struct DelayedMessage
{
std::chrono::steady_clock::time_point time;
Message message;
};
class DelayedMessageCompare
{
public:
bool operator()(const DelayedMessage& a, const DelayedMessage& b)
{
return a.time >= b.time;
}
};
std::chrono::milliseconds m_switchTime = defaultSwitchTime;
std::priority_queue<DelayedMessage, std::vector<DelayedMessage>, DelayedMessageCompare> m_delayedMessages;
boost::asio::steady_timer m_pingTimer;
boost::asio::steady_timer m_bootloaderCANTimer;
boost::asio::steady_timer m_delayedMessageTimer;
std::set<uint32_t> m_knownPingUIDs;
void reply(const Message& message);
void reply(const Message& message, std::chrono::milliseconds delay);
void replyPing();
void startBootloaderCANTimer(std::chrono::milliseconds expireAfter = bootstrapCANInterval);
void startPingTimer(std::chrono::milliseconds expireAfter = pingInterval);
void restartDelayedMessageTimer();
public:
SimulationIOHandler(Kernel& kernel);
void start() final;
void stop() final;
bool send(const Message& message) final;
};
template<>
constexpr bool isSimulation<SimulationIOHandler>()
{
return true;
}
}
#endif

Datei anzeigen

@ -0,0 +1,167 @@
/**
* server/src/hardware/protocol/marklincan/iohandler/socketcaniohandler.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 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 "socketcaniohandler.hpp"
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <linux/can/raw.h>
#include "../kernel.hpp"
#include "../messages.hpp"
#include "../../../../core/eventloop.hpp"
#include "../../../../log/log.hpp"
#include "../../../../log/logmessageexception.hpp"
namespace MarklinCAN {
SocketCANIOHandler::SocketCANIOHandler(Kernel& kernel, const std::string& interface)
: IOHandler(kernel)
, m_stream{kernel.ioContext()}
{
int fd = socket(PF_CAN, SOCK_RAW, CAN_RAW);
if(fd < 0)
throw LogMessageException(LogMessage::E2022_SOCKET_CREATE_FAILED_X, std::string_view(strerror(errno)));
struct ifreq ifr;
std::strncpy(ifr.ifr_name, interface.c_str(), IFNAMSIZ - 1);
ifr.ifr_name[IFNAMSIZ - 1] = '\0';
if(ioctl(fd, SIOCGIFINDEX, &ifr) < 0)
{
close(fd);
throw LogMessageException(LogMessage::E2023_SOCKET_IOCTL_FAILED_X, std::string_view(strerror(errno)));
}
struct sockaddr_can addr;
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
if(bind(fd, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)) < 0)
{
close(fd);
throw LogMessageException(LogMessage::E2006_SOCKET_BIND_FAILED_X, std::string_view(strerror(errno)));
}
m_stream.assign(fd);
}
void SocketCANIOHandler::start()
{
read();
}
void SocketCANIOHandler::stop()
{
m_stream.cancel();
m_stream.close();
}
bool SocketCANIOHandler::send(const Message& message)
{
if(m_writeBufferOffset + 1 > m_writeBuffer.size())
return false;
const bool wasEmpty = m_writeBufferOffset == 0;
auto& frame = m_writeBuffer[m_writeBufferOffset];
frame.can_id = CAN_EFF_FLAG | (message.id & CAN_EFF_MASK);
frame.can_dlc = message.dlc;
std::memcpy(frame.data, message.data, message.dlc);
m_writeBufferOffset++;
if(wasEmpty)
write();
return true;
}
void SocketCANIOHandler::read()
{
m_stream.async_read_some(boost::asio::buffer(m_readBuffer.data() + m_readBufferOffset * frameSize, (m_readBuffer.size() - m_readBufferOffset) * frameSize),
[this](const boost::system::error_code& ec, std::size_t bytesTransferred)
{
if(!ec)
{
auto framesTransferred = bytesTransferred / frameSize;
const auto* frame = reinterpret_cast<const struct can_frame*>(m_readBuffer.data());
framesTransferred += m_readBufferOffset;
while(framesTransferred > 0)
{
Message message;
message.id = frame->can_id & CAN_EFF_MASK;
message.dlc = frame->can_dlc;
std::memcpy(message.data, frame->data, message.dlc);
m_kernel.receive(message);
frame++;
framesTransferred--;
}
if(framesTransferred != 0) /*[[unlikely]]*/
memmove(m_readBuffer.data(), frame, framesTransferred * frameSize);
m_readBufferOffset = framesTransferred;
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);
m_kernel.error();
});
}
});
}
void SocketCANIOHandler::write()
{
m_stream.async_write_some(boost::asio::buffer(m_writeBuffer.data(), m_writeBufferOffset * frameSize),
[this](const boost::system::error_code& ec, std::size_t bytesTransferred)
{
if(!ec)
{
const auto framesTransferred = bytesTransferred / frameSize;
if(framesTransferred < m_writeBufferOffset)
{
m_writeBufferOffset -= framesTransferred;
memmove(m_writeBuffer.data(), m_writeBuffer.data() + framesTransferred * frameSize, 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);
m_kernel.error();
});
}
});
}
}

Datei anzeigen

@ -0,0 +1,58 @@
/**
* server/src/hardware/protocol/marklincan/iohandler/socketcaniohandler.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 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_MARKLINCAN_IOHANDLER_SOCKETCANIOHANDLER_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_MARKLINCAN_IOHANDLER_SOCKETCANIOHANDLER_HPP
#include "iohandler.hpp"
#include <string>
#include <linux/can.h>
#include <boost/asio/posix/stream_descriptor.hpp>
namespace MarklinCAN {
class SocketCANIOHandler : public IOHandler
{
private:
static constexpr size_t frameSize = sizeof(struct can_frame);
boost::asio::posix::stream_descriptor m_stream;
std::array<struct can_frame, 32> m_readBuffer;
size_t m_readBufferOffset = 0;
std::array<struct can_frame, 32> m_writeBuffer;
size_t m_writeBufferOffset = 0;
void read();
void write();
public:
SocketCANIOHandler(Kernel& kernel, const std::string& interface);
void start() final;
void stop() final;
bool send(const Message& message) final;
};
}
#endif

Datei anzeigen

@ -0,0 +1,122 @@
/**
* server/src/hardware/protocol/marklincan/iohandler/tcpiohandler.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 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 "../messages.hpp"
#include "../../../../core/eventloop.hpp"
#include "../../../../log/log.hpp"
#include "../../../../log/logmessageexception.hpp"
namespace MarklinCAN {
TCPIOHandler::TCPIOHandler(Kernel& kernel, const std::string& hostname)
: NetworkIOHandler(kernel)
, m_socket{m_kernel.ioContext()}
, m_readBufferOffset{0}
{
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));
}
void TCPIOHandler::stop()
{
m_socket.cancel();
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)
{
const std::byte* pos = m_readBuffer.data();
bytesTransferred += m_readBufferOffset;
while(bytesTransferred >= sizeof(NetworkMessage))
{
m_kernel.receive(toMessage(*reinterpret_cast<const NetworkMessage*>(pos)));
pos += sizeof(NetworkMessage);
bytesTransferred -= sizeof(NetworkMessage);
}
if(bytesTransferred != 0)
memmove(m_readBuffer.data(), pos, bytesTransferred);
m_readBufferOffset = 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);
m_kernel.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);
m_kernel.error();
});
}
});
}
}

Datei anzeigen

@ -0,0 +1,52 @@
/**
* server/src/hardware/protocol/marklincan/iohandler/tcpiohandler.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 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_MARKLINCAN_IOHANDLER_TCPIOHANDLER_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_MARKLINCAN_IOHANDLER_TCPIOHANDLER_HPP
#include "networkiohandler.hpp"
#include <boost/asio/ip/tcp.hpp>
namespace MarklinCAN {
class TCPIOHandler final : public NetworkIOHandler
{
private:
static constexpr uint16_t port = 15731;
boost::asio::ip::tcp::socket m_socket;
boost::asio::ip::tcp::endpoint m_endpoint;
std::array<std::byte, 1500> m_readBuffer;
size_t m_readBufferOffset;
void read() final;
void write() final;
public:
TCPIOHandler(Kernel& kernel, const std::string& hostname);
void stop() final;
};
}
#endif

Datei anzeigen

@ -0,0 +1,134 @@
/**
* server/src/hardware/protocol/marklincan/iohandler/udpiohandler.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 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 "../../../../core/eventloop.hpp"
#include "../../../../log/log.hpp"
#include "../../../../log/logmessageexception.hpp"
namespace MarklinCAN {
UDPIOHandler::UDPIOHandler(Kernel& kernel, const std::string& hostname)
: NetworkIOHandler(kernel)
, m_readSocket{m_kernel.ioContext()}
, m_writeSocket{m_kernel.ioContext()}
{
boost::system::error_code ec;
// read:
if(m_readSocket.open(boost::asio::ip::udp::v4(), ec))
throw LogMessageException(LogMessage::E2004_SOCKET_OPEN_FAILED_X, ec);
m_readSocket.set_option(boost::asio::ip::udp::socket::reuse_address(true));
if(m_readSocket.bind(boost::asio::ip::udp::endpoint(boost::asio::ip::address_v4::any(), localPort), ec))
{
m_readSocket.close();
throw LogMessageException(LogMessage::E2006_SOCKET_BIND_FAILED_X, ec);
}
// write:
m_writeEndpoint.port(remotePort);
m_writeEndpoint.address(boost::asio::ip::make_address(hostname, ec));
if(ec)
throw LogMessageException(LogMessage::E2003_MAKE_ADDRESS_FAILED_X, ec);
if(m_writeSocket.open(boost::asio::ip::udp::v4(), ec))
throw LogMessageException(LogMessage::E2004_SOCKET_OPEN_FAILED_X, ec);
m_writeSocket.set_option(boost::asio::ip::udp::socket::reuse_address(true));
if(m_writeSocket.bind(boost::asio::ip::udp::endpoint(boost::asio::ip::address_v4::any(), localPort), ec))
{
m_writeSocket.close();
throw LogMessageException(LogMessage::E2006_SOCKET_BIND_FAILED_X, ec);
}
}
void UDPIOHandler::stop()
{
m_readSocket.cancel();
m_writeSocket.cancel();
m_readSocket.close();
m_writeSocket.close();
}
void UDPIOHandler::read()
{
m_readSocket.async_receive_from(boost::asio::buffer(m_readBuffer), m_readEndpoint,
[this](const boost::system::error_code& ec, std::size_t bytesReceived)
{
if(!ec)
{
if(bytesReceived == sizeof(NetworkMessage))
{
m_kernel.receive(toMessage(*reinterpret_cast<const NetworkMessage*>(m_readBuffer.data())));
}
read();
}
else if(ec != boost::asio::error::operation_aborted)
{
EventLoop::call(
[this, ec]()
{
Log::log(m_kernel.logId(), LogMessage::E2009_SOCKET_RECEIVE_FAILED_X, ec);
m_kernel.error();
});
}
});
}
void UDPIOHandler::write()
{
assert(m_writeBufferOffset >= sizeof(NetworkMessage));
assert(m_writeBufferOffset % sizeof(NetworkMessage) == 0);
m_writeSocket.async_send_to(boost::asio::buffer(m_writeBuffer.data(), sizeof(NetworkMessage)), m_writeEndpoint,
[this](const boost::system::error_code& ec, std::size_t bytesTransferred)
{
if(!ec)
{
assert(bytesTransferred == sizeof(NetworkMessage));
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::E2011_SOCKET_SEND_FAILED_X, ec);
m_kernel.error();
});
}
});
}
}

Datei anzeigen

@ -0,0 +1,54 @@
/**
* server/src/hardware/protocol/marklincan/iohandler/udpiohandler.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 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_MARKLINCAN_IOHANDLER_UDPIOHANDLER_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_MARKLINCAN_IOHANDLER_UDPIOHANDLER_HPP
#include "networkiohandler.hpp"
#include <boost/asio/ip/udp.hpp>
namespace MarklinCAN {
class UDPIOHandler final : public NetworkIOHandler
{
private:
static constexpr uint16_t localPort = 15730;
static constexpr uint16_t remotePort = 15731;
boost::asio::ip::udp::socket m_readSocket;
boost::asio::ip::udp::endpoint m_readEndpoint;
std::array<std::byte, 1500> m_readBuffer;
boost::asio::ip::udp::socket m_writeSocket;
boost::asio::ip::udp::endpoint m_writeEndpoint;
void read() final;
void write() final;
public:
UDPIOHandler(Kernel& kernel, const std::string& hostname);
void stop() final;
};
}
#endif

Datei anzeigen

@ -0,0 +1,992 @@
/**
* server/src/hardware/protocol/marklincan/kernel.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 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 <nlohmann/json.hpp>
#include <version.hpp>
#include "messages.hpp"
#include "message/configdata.hpp"
#include "message/statusdataconfig.hpp"
#include "locomotivelist.hpp"
#include "uid.hpp"
#include "../dcc/dcc.hpp"
#include "../../decoder/decoder.hpp"
#include "../../decoder/decoderchangeflags.hpp"
#include "../../decoder/decodercontroller.hpp"
#include "../../input/inputcontroller.hpp"
#include "../../output/outputcontroller.hpp"
#include "../../../core/eventloop.hpp"
#include "../../../log/log.hpp"
#include "../../../traintastic/traintastic.hpp"
#include "../../../utils/inrange.hpp"
#include "../../../utils/setthreadname.hpp"
#include "../../../utils/tohex.hpp"
#include "../../../utils/writefile.hpp"
#include "../../../utils/zlib.hpp"
namespace MarklinCAN {
static std::tuple<bool, DecoderProtocol, uint16_t> uidToProtocolAddress(uint32_t uid)
{
if(inRange(uid, UID::Range::locomotiveMotorola))
return {true, DecoderProtocol::Motorola, uid - UID::Range::locomotiveMotorola.first};
if(inRange(uid, UID::Range::locomotiveMFX))
return {true, DecoderProtocol::MFX, uid - UID::Range::locomotiveMFX.first};
if(inRange(uid, UID::Range::locomotiveDCC))
{
//! \todo Handle long address < 128
const uint16_t address = uid - UID::Range::locomotiveDCC.first;
if(address <= DCC::addressShortMax)
return {true, DecoderProtocol::DCCShort, address};
else
return {true, DecoderProtocol::DCCLong, address};
}
return {false, DecoderProtocol::None, 0};
}
Kernel::Kernel(const Config& config, bool simulation)
: m_ioContext{1}
, m_simulation{simulation}
, m_statusDataConfigRequestTimer{m_ioContext}
, m_debugDir{Traintastic::instance->debugDir()}
, m_config{config}
#ifndef NDEBUG
, m_started{false}
#endif
{
assert(isEventLoopThread());
(void)m_simulation;
}
void Kernel::setConfig(const Config& config)
{
assert(isEventLoopThread());
m_ioContext.post(
[this, newConfig=config]()
{
if(m_config.defaultSwitchTime != newConfig.defaultSwitchTime)
send(AccessorySwitchTime(newConfig.defaultSwitchTime / 10));
m_config = newConfig;
});
}
void Kernel::setOnStarted(std::function<void()> callback)
{
assert(isEventLoopThread());
assert(!m_started);
m_onStarted = std::move(callback);
}
void Kernel::setOnError(std::function<void()> callback)
{
assert(isEventLoopThread());
assert(!m_started);
m_onError = std::move(callback);
}
void Kernel::setOnLocomotiveListChanged(std::function<void(const std::shared_ptr<LocomotiveList>&)> callback)
{
assert(isEventLoopThread());
assert(!m_started);
m_onLocomotiveListChanged = std::move(callback);
}
void Kernel::setOnNodeChanged(std::function<void(const Node& node)> callback)
{
assert(isEventLoopThread());
assert(!m_started);
m_onNodeChanged = std::move(callback);
}
void Kernel::setDecoderController(DecoderController* decoderController)
{
assert(isEventLoopThread());
assert(!m_started);
m_decoderController = decoderController;
}
void Kernel::setInputController(InputController* inputController)
{
assert(isEventLoopThread());
assert(!m_started);
m_inputController = inputController;
}
void Kernel::setOutputController(OutputController* outputController)
{
assert(isEventLoopThread());
assert(!m_started);
m_outputController = outputController;
}
void Kernel::start()
{
assert(isEventLoopThread());
assert(m_ioHandler);
assert(!m_started);
// reset all state values
m_inputValues.fill(TriState::Undefined);
m_outputValuesMotorola.fill(TriState::Undefined);
m_outputValuesDCC.fill(TriState::Undefined);
m_outputValuesSX1.fill(TriState::Undefined);
m_thread = std::thread(
[this]()
{
setThreadName("marklin_can");
auto work = std::make_shared<boost::asio::io_context::work>(m_ioContext);
m_ioContext.run();
});
m_ioContext.post(
[this]()
{
m_ioHandler->start();
// add Traintastic to the node list
{
Node node;
node.uid = m_config.nodeUID;
node.deviceName = nodeDeviceName;
node.articleNumber = nodeArticleNumber;
node.serialNumber = m_config.nodeSerialNumber;
node.softwareVersionMajor = TRAINTASTIC_VERSION_MAJOR;
node.softwareVersionMinor = TRAINTASTIC_VERSION_MINOR;
node.deviceId = DeviceId::Traintastic;
nodeChanged(node);
m_nodes.emplace(m_config.nodeUID, node);
}
nextState();
});
#ifndef NDEBUG
m_started = true;
#endif
}
void Kernel::stop()
{
assert(isEventLoopThread());
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)
{
assert(isKernelThread());
if(m_config.debugLogRXTX)
EventLoop::call([this, msg=toString(message)](){ Log::log(m_logId, LogMessage::D2002_RX_X, msg); });
switch(message.command())
{
case Command::System:
{
const auto& system = static_cast<const SystemMessage&>(message);
switch(system.subCommand())
{
case SystemSubCommand::SystemStop:
case SystemSubCommand::SystemGo:
case SystemSubCommand::SystemHalt:
// not (yet) implemented
break;
case SystemSubCommand::LocomotiveEmergencyStop:
if(m_decoderController && system.isResponse())
{
auto [success, protocol, address] = uidToProtocolAddress(system.uid());
if(success)
{
EventLoop::call(
[this, protocol=protocol, address=address]()
{
if(const auto& decoder = m_decoderController->getDecoder(protocol, address))
decoder->emergencyStop.setValueInternal(true);
});
}
}
break;
case SystemSubCommand::LocomotiveCycleEnd:
// not (yet) implemented
break;
case SystemSubCommand::AccessorySwitchTime:
if(message.isResponse() && m_state == State::SetAccessorySwitchTime)
nextState();
break;
case SystemSubCommand::Overload:
case SystemSubCommand::Status:
case SystemSubCommand::ModelClock:
case SystemSubCommand::MFXSeek:
// not (yet) implemented
break;
}
break;
}
case Command::Discovery:
case Command::Bind:
case Command::Verify:
// not (yet) implemented
break;
case Command::LocomotiveSpeed:
if(m_decoderController)
{
const auto& locomotiveSpeed = static_cast<const LocomotiveSpeed&>(message);
if(locomotiveSpeed.isResponse() && locomotiveSpeed.hasSpeed())
{
auto [success, protocol, address] = uidToProtocolAddress(locomotiveSpeed.uid());
if(success)
{
EventLoop::call(
[this, protocol=protocol, address=address, throttle=Decoder::speedStepToThrottle(locomotiveSpeed.speed(), LocomotiveSpeed::speedMax)]()
{
if(const auto& decoder = m_decoderController->getDecoder(protocol, address))
{
decoder->emergencyStop.setValueInternal(false);
decoder->throttle.setValueInternal(throttle);
}
});
}
}
}
break;
case Command::LocomotiveDirection:
if(m_decoderController)
{
const auto& locomotiveDirection = static_cast<const LocomotiveDirection&>(message);
if(locomotiveDirection.isResponse() && locomotiveDirection.hasDirection())
{
auto [success, protocol, address] = uidToProtocolAddress(locomotiveDirection.uid());
if(success)
{
Direction direction = Direction::Unknown;
switch(locomotiveDirection.direction())
{
case LocomotiveDirection::Direction::Forward:
direction = Direction::Forward;
break;
case LocomotiveDirection::Direction::Reverse:
direction = Direction::Reverse;
break;
case LocomotiveDirection::Direction::Same:
case LocomotiveDirection::Direction::Inverse:
break;
}
EventLoop::call(
[this, protocol=protocol, address=address, direction]()
{
if(const auto& decoder = m_decoderController->getDecoder(protocol, address))
decoder->direction.setValueInternal(direction);
});
}
}
}
break;
case Command::LocomotiveFunction:
if(m_decoderController)
{
const auto& locomotiveFunction = static_cast<const LocomotiveFunction&>(message);
if(locomotiveFunction.isResponse() && locomotiveFunction.hasValue())
{
auto [success, protocol, address] = uidToProtocolAddress(locomotiveFunction.uid());
if(success)
{
EventLoop::call(
[this, protocol=protocol, address=address, number=locomotiveFunction.number(), value=locomotiveFunction.isOn()]()
{
if(const auto& decoder = m_decoderController->getDecoder(protocol, address))
decoder->setFunctionValue(number, value);
});
}
}
}
break;
case Command::ReadConfig:
case Command::WriteConfig:
// not (yet) implemented
break;
case Command::AccessoryControl:
if(message.isResponse() && (message.dlc == 6 || message.dlc == 8))
{
const auto& accessoryControl = static_cast<const AccessoryControl&>(message);
if(accessoryControl.position() != AccessoryControl::positionOff &&
accessoryControl.position() != AccessoryControl::positionOn)
break;
uint32_t channel = 0;
uint32_t address = accessoryControl.position() == AccessoryControl::positionOff ? 1 : 2;
const auto value = toTriState(accessoryControl.current() != 0);
if(inRange(accessoryControl.uid(), UID::Range::accessoryMotorola))
{
channel = OutputChannel::motorola;
address += (accessoryControl.uid() - UID::Range::accessoryMotorola.first) << 1;
if(address > m_outputValuesMotorola.size() || m_outputValuesMotorola[address - 1] == value)
break;
m_outputValuesMotorola[address - 1] = value;
}
else if(inRange(accessoryControl.uid(), UID::Range::accessoryDCC))
{
channel = OutputChannel::dcc;
address += (accessoryControl.uid() - UID::Range::accessoryDCC.first) << 1;
if(address > m_outputValuesDCC.size() || m_outputValuesDCC[address - 1] == value)
break;
m_outputValuesDCC[address - 1] = value;
}
else if(inRange(accessoryControl.uid(), UID::Range::accessorySX1))
{
channel = OutputChannel::sx1;
address += (accessoryControl.uid() - UID::Range::accessorySX1.first) << 1;
if(address > m_outputValuesSX1.size() || m_outputValuesSX1[address - 1] == value)
break;
m_outputValuesSX1[address - 1] = value;
}
if(channel != 0)
{
EventLoop::call(
[this, channel, address, value]()
{
m_outputController->updateOutputValue(channel, address, value);
});
}
}
break;
case Command::AccessoryConfig:
case Command::S88Polling:
// not (yet) implemented
break;
case Command::FeedbackEvent:
if(message.dlc == 8)
{
if(m_inputController)
{
const auto& feedbackState = static_cast<const FeedbackState&>(message);
if(feedbackState.deviceId() == 0) //! \todo what about other values?
{
const auto value = feedbackState.stateNew() == 0 ? TriState::False : TriState::True;
if(inRange(feedbackState.contactId(), s88AddressMin, s88AddressMax) && m_inputValues[feedbackState.contactId() - s88AddressMin] != value)
{
m_inputValues[feedbackState.contactId() - s88AddressMin] = value;
EventLoop::call(
[this, address=feedbackState.contactId(), value]()
{
m_inputController->updateInputValue(InputController::defaultInputChannel, address, value);
});
}
}
}
}
break;
case Command::SX1Event:
// not (yet) implemented
break;
case Command::Ping:
if(message.dlc == 0 && !message.isResponse())
{
send(PingReply(m_config.nodeUID, TRAINTASTIC_VERSION_MAJOR, TRAINTASTIC_VERSION_MINOR, DeviceId::Traintastic));
}
else if(message.dlc == 8 && message.isResponse())
{
const auto& pingReply = static_cast<const PingReply&>(message);
if(auto it = m_nodes.find(pingReply.uid()); it == m_nodes.end())
{
if(it == m_nodes.end()) // new node
{
Node node;
node.uid = pingReply.uid();
node.softwareVersionMajor = pingReply.softwareVersionMajor();
node.softwareVersionMinor = pingReply.softwareVersionMinor();
node.deviceId = pingReply.deviceId();
m_nodes.emplace(pingReply.uid(), node);
}
if(pingReply.uid() != m_config.nodeUID)
{
// queue the requests and wait for some time before sending them.
// multiple transfer don't work well at the same time.
m_statusDataConfigRequestQueue.emplace(pingReply.uid(), 0);
restartStatusDataConfigTimer();
}
}
}
break;
case Command::Update:
case Command::ReadConfigData:
case Command::BootloaderCAN:
case Command::BootloaderTrack:
// not (yet) implemented
break;
case Command::StatusDataConfig:
if(message.dlc == 5 && !message.isResponse() && static_cast<const UidMessage&>(message).uid() == m_config.nodeUID)
{
const uint32_t uid = static_cast<const UidMessage&>(message).uid();
const uint8_t index = message.data[4];
switch(index)
{
case 0x00:
{
StatusData::DeviceDescription desc;
desc.serialNumber = m_config.nodeSerialNumber;
memcpy(desc.articleNumber, nodeArticleNumber.data(), std::min(nodeArticleNumber.size(), sizeof(desc.articleNumber)));
desc.deviceName = nodeDeviceName;
for(const auto& reply : statusDataConfigReply(m_config.nodeUID, uid, index, desc))
send(reply);
break;
}
}
}
else if(message.dlc == 5 && message.isResponse())
{
const auto& configReply = static_cast<const StatusDataConfig&>(message);
receiveStatusDataConfig(configReply.uid(), configReply.index(), m_statusConfigData);
m_statusConfigData.clear();
}
else if(message.dlc == 6 && message.isResponse())
{
const auto& configReply = static_cast<const StatusDataConfigReply&>(message);
if(m_statusConfigData.size() / 8 == configReply.packetCount())
{
receiveStatusDataConfig(configReply.uid(), configReply.index(), m_statusConfigData);
}
m_statusConfigData.clear();
}
else if(message.dlc == 8 && message.isResponse())
{
const auto& configData = static_cast<const StatusDataConfigReplyData&>(message);
if(m_statusConfigData.empty() && configData.hash() == StatusDataConfigReplyData::startHash)
{
m_statusConfigData.resize(8);
std::memcpy(m_statusConfigData.data(), configData.data, 8);
}
else if((m_statusConfigData.size() / 8 + StatusDataConfigReplyData::startHash) == configData.hash())
{
m_statusConfigData.resize(m_statusConfigData.size() + 8);
std::memcpy(m_statusConfigData.data() + m_statusConfigData.size() - 8, configData.data, 8);
}
else
m_statusConfigData.clear(); // invalid data -> reset
}
break;
case Command::ConfigData:
if(message.isResponse() && message.dlc == 8)
{
m_configDataStreamCollector = std::make_unique<ConfigDataStreamCollector>(std::string{static_cast<const ConfigData&>(message).name()});
}
break;
case Command::ConfigDataStream:
if(m_configDataStreamCollector) /*[[likely]]*/
{
const auto status = m_configDataStreamCollector->process(static_cast<const ConfigDataStream&>(message));
if(status != ConfigDataStreamCollector::Collecting)
{
if(status == ConfigDataStreamCollector::Complete)
{
receiveConfigData(std::move(m_configDataStreamCollector));
}
else // error
{
m_configDataStreamCollector.reset();
}
}
}
break;
}
}
void Kernel::error()
{
assert(isEventLoopThread());
if(m_onError)
m_onError();
}
void Kernel::systemStop()
{
assert(isEventLoopThread());
m_ioContext.post(
[this]()
{
send(SystemStop());
});
}
void Kernel::systemGo()
{
assert(isEventLoopThread());
m_ioContext.post(
[this]()
{
send(SystemGo());
});
}
void Kernel::systemHalt()
{
assert(isEventLoopThread());
m_ioContext.post(
[this]()
{
send(SystemHalt());
});
}
void Kernel::getLocomotiveList()
{
assert(isEventLoopThread());
m_ioContext.post(
[this]()
{
send(ConfigData(m_config.nodeUID, ConfigDataName::loks));
});
}
void Kernel::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber)
{
assert(isEventLoopThread());
uint32_t uid = 0;
switch(decoder.protocol.value())
{
case DecoderProtocol::DCCShort:
case DecoderProtocol::DCCLong:
uid = UID::locomotiveDCC(decoder.address);
break;
case DecoderProtocol::Motorola:
uid = UID::locomotiveMotorola(decoder.address);
break;
case DecoderProtocol::MFX:
if(const auto& it = m_mfxUIDtoSID.find(decoder.mfxUID); it != m_mfxUIDtoSID.end())
{
uid = UID::locomotiveMFX(it->second);
}
else
{
Log::log(m_logId, LogMessage::E2024_UNKNOWN_LOCOMOTIVE_MFX_UID_X, toHex(decoder.mfxUID.value()));
}
break;
case DecoderProtocol::None:
case DecoderProtocol::Selectrix:
assert(false);
break;
}
if(uid == 0)
return;
if(has(changes, DecoderChangeFlags::Direction))
{
LocomotiveDirection::Direction direction = LocomotiveDirection::Direction::Same;
switch(decoder.direction.value())
{
case Direction::Forward:
direction = LocomotiveDirection::Direction::Forward;
break;
case Direction::Reverse:
direction = LocomotiveDirection::Direction::Reverse;
break;
case Direction::Unknown:
break;
}
if(direction != LocomotiveDirection::Direction::Same)
postSend(LocomotiveDirection(uid, direction));
}
if(has(changes, DecoderChangeFlags::EmergencyStop) && decoder.emergencyStop)
postSend(LocomotiveEmergencyStop(uid));
else if(has(changes, DecoderChangeFlags::Throttle | DecoderChangeFlags::EmergencyStop))
postSend(LocomotiveSpeed(uid, Decoder::throttleToSpeedStep(decoder.throttle, LocomotiveSpeed::speedMax)));
if(has(changes, DecoderChangeFlags::FunctionValue) && functionNumber <= std::numeric_limits<uint8_t>::max())
postSend(LocomotiveFunction(uid, functionNumber, decoder.getFunctionValue(functionNumber)));
}
bool Kernel::setOutput(uint32_t channel, uint16_t address, bool value)
{
assert(isEventLoopThread());
m_ioContext.post(
[this, channel, address, value]()
{
uint32_t uid = 0;
switch(channel)
{
case OutputChannel::motorola:
assert(inRange(address, outputMotorolaAddressMin, outputMotorolaAddressMax));
if(m_outputValuesMotorola[address - 1] == toTriState(value))
return;
uid = MarklinCAN::UID::accessoryMotorola((address + 1) >> 1);
break;
case OutputChannel::dcc:
assert(inRange(address, outputDCCAddressMin, outputDCCAddressMax));
if(m_outputValuesDCC[address - 1] == toTriState(value))
return;
uid = MarklinCAN::UID::accessoryDCC((address + 1) >> 1);
break;
case OutputChannel::sx1:
assert(inRange(address, outputSX1AddressMin, outputSX1AddressMax));
if(m_outputValuesSX1[address - 1] == toTriState(value))
return;
uid = MarklinCAN::UID::accessorySX1((address + 1) >> 1);
break;
default: /*[[unlikely]]*/
return;
}
assert(uid != 0);
MarklinCAN::AccessoryControl cmd(uid);
cmd.setPosition((address & 0x1) ? MarklinCAN::AccessoryControl::positionOff : MarklinCAN::AccessoryControl::positionOn);
cmd.setCurrent(value ? 1 : 0);
send(cmd);
});
return true;
}
void Kernel::setIOHandler(std::unique_ptr<IOHandler> handler)
{
assert(isEventLoopThread());
assert(handler);
assert(!m_ioHandler);
m_ioHandler = std::move(handler);
}
void Kernel::send(const Message& message)
{
assert(isKernelThread());
if(m_config.debugLogRXTX)
EventLoop::call([this, msg=toString(message)](){ Log::log(m_logId, LogMessage::D2001_TX_X, msg); });
m_ioHandler->send(message);
}
void Kernel::postSend(const Message& message)
{
m_ioContext.post(
[this, message]()
{
send(message);
});
}
void Kernel::receiveStatusDataConfig(uint32_t nodeUID, uint8_t index, const std::vector<std::byte>& statusConfigData)
{
auto it = m_nodes.find(nodeUID);
if(it == m_nodes.end())
return;
Node& node = it->second;
if(index == 0)
{
const auto devDesc = StatusData::DeviceDescription::decode(statusConfigData);
node.serialNumber = devDesc.serialNumber;
node.articleNumber.assign(devDesc.articleNumber, strnlen(devDesc.articleNumber, sizeof(devDesc.articleNumber)));
node.deviceName = devDesc.deviceName;
node.numberOfReadings = devDesc.numberOfReadings;
node.numberOfConfigurationChannels = devDesc.numberOfConfigurationChannels;
// schedule read of reading/configuration descriptions:
const uint8_t lastIndex = devDesc.numberOfReadings + devDesc.numberOfConfigurationChannels;
for(uint8_t i = 1; i <= lastIndex; i++)
m_statusDataConfigRequestQueue.emplace(node.uid, i);
}
else if(index <= node.numberOfReadings)
{
node.readings.emplace_back(StatusData::ReadingDescription::decode(statusConfigData));
}
else if(index <= node.numberOfReadings + node.numberOfConfigurationChannels)
{
node.configurations.emplace_back(StatusData::ConfigurationDescription::decode(statusConfigData));
}
if(index == node.numberOfReadings + node.numberOfConfigurationChannels)
{
nodeChanged(node);
}
if(!m_statusDataConfigRequestQueue.empty() && m_statusDataConfigRequestQueue.front().uid == nodeUID && m_statusDataConfigRequestQueue.front().index == index)
{
m_statusDataConfigRequestQueue.pop();
m_statusDataConfigRequestRetries = statusDataConfigRequestRetryCount;
if(!m_statusDataConfigRequestQueue.empty())
restartStatusDataConfigTimer();
else if(m_state == State::DiscoverNodes)
nextState();
}
}
void Kernel::receiveConfigData(std::unique_ptr<ConfigDataStreamCollector> configData)
{
const auto basename = m_debugDir / m_logId / "configstream" / configData->name;
if(m_config.debugConfigStream)
{
writeFile(std::filesystem::path(basename).concat(".bin"), configData->bytes());
}
if(configData->name == ConfigDataName::loks)
{
const size_t uncompressedSize = be_to_host(*reinterpret_cast<const uint32_t*>(configData->data()));
std::string locList;
if(ZLib::Uncompress::toString(configData->data() + sizeof(uint32_t), configData->dataSize() - sizeof(uint32_t), uncompressedSize, locList))
{
if(m_config.debugConfigStream)
{
writeFile(std::filesystem::path(basename).concat(".txt"), locList);
}
EventLoop::call(
[this, list=std::make_shared<LocomotiveList>(locList)]()
{
// update MFX UID to SID list:
m_mfxUIDtoSID.clear();
for(const auto& item : *list)
m_mfxUIDtoSID.emplace(item.mfxUID, item.sid);
if(m_onLocomotiveListChanged) /*[[likely]]*/
m_onLocomotiveListChanged(list);
});
}
if(m_state == State::DownloadLokList)
nextState();
}
}
void Kernel::restartStatusDataConfigTimer()
{
assert(!m_statusDataConfigRequestQueue.empty());
m_statusDataConfigRequestTimer.cancel();
m_statusDataConfigRequestTimer.expires_after(std::chrono::milliseconds(50));
m_statusDataConfigRequestTimer.async_wait(
[this](const boost::system::error_code& ec)
{
if(!ec)
{
if(!m_statusConfigData.empty())
return; // if in progress, don't request, when finished it will start the timer again
if(m_statusDataConfigRequestRetries > 0) /*[[likely]]*/
m_statusDataConfigRequestRetries--;
if(m_statusDataConfigRequestRetries == 0)
{
// give up, no response
if(auto it = m_nodes.find(m_statusDataConfigRequestQueue.front().uid); it != m_nodes.end()) /*[[likely]]*/
{
nodeChanged(it->second);
}
m_statusDataConfigRequestQueue.pop();
m_statusDataConfigRequestRetries = statusDataConfigRequestRetryCount;
}
else
{
const auto& request = m_statusDataConfigRequestQueue.front();
send(StatusDataConfig(m_config.nodeUID, request.uid, request.index));
}
}
if(ec != boost::asio::error::operation_aborted)
{
if(!m_statusDataConfigRequestQueue.empty())
restartStatusDataConfigTimer();
else if(m_state == State::DiscoverNodes)
nextState();
}
});
}
void Kernel::nodeChanged(const Node& node)
{
assert(isKernelThread());
if(m_onNodeChanged) /*[[likely]]*/
EventLoop::call(
[this, node=node]()
{
static_assert(!std::is_reference_v<decltype(node)>);
m_onNodeChanged(node);
});
if(m_config.debugStatusDataConfig)
{
using namespace nlohmann;
// serialize into JSON:
auto data = json::object();
data["uid"] = node.uid;
data["software_version"] = std::to_string(node.softwareVersionMajor).append(".").append(std::to_string(node.softwareVersionMinor));
data["device_id"] = node.deviceId;
if(!node.deviceName.empty() || node.serialNumber != 0 || !node.articleNumber.empty() || node.numberOfReadings != 0 || node.numberOfConfigurationChannels != 0)
{
data["serial_number"] = node.serialNumber;
data["article_number"] = node.articleNumber;
data["device_name"] = node.deviceName;
data["number_of_readings"] = node.numberOfReadings;
auto readings = json::array();
for(const auto& reading : node.readings)
{
auto readingData = json::object();
readingData["channel"] = reading.channel;
readingData["power"] = reading.power;
readingData["color"] = reading.color;
readingData["zero"] = reading.zero;
readingData["rangeEnd"] = reading.rangeEnd;
readingData["description"] = reading.description;
readingData["labelStart"] = reading.labelStart;
readingData["labelEnd"] = reading.labelEnd;
readingData["unit"] = reading.unit;
readings.emplace_back(readingData);
}
data["readings"] = readings;
data["number_of_configuration_channels"] = node.numberOfConfigurationChannels;
auto configurations = json::array();
for(const auto& configuration : node.configurations)
{
auto configurationData = json::object();
configurationData["channel"] = configuration.channel;
configurationData["description"] = configuration.description;
switch(configuration.type)
{
case StatusData::ConfigurationDescription::Type::List:
{
configurationData["type"] = "list";
configurationData["default"] = configuration.default_;
auto items = json::array();
for(const auto& item : configuration.listItems)
items.emplace_back(item);
configurationData["items"] = items;
break;
}
case StatusData::ConfigurationDescription::Type::Number:
configurationData["type"] = "number";
configurationData["value_min"] = configuration.valueMin;
configurationData["value_max"] = configuration.valueMax;
configurationData["value"] = configuration.value;
configurationData["label_start"] = configuration.labelStart;
configurationData["label_end"] = configuration.labelEnd;
configurationData["unit"] = configuration.unit;
break;
default:
configurationData["type"] = static_cast<uint8_t>(configuration.type);
assert(false);
break;
}
configurations.emplace_back(configurationData);
}
data["configurations"] = configurations;
}
writeFileJSON(m_debugDir / m_logId / "statusdataconfig" / toHex(node.uid).append(".json"), data);
}
}
void Kernel::changeState(State value)
{
assert(isKernelThread());
assert(m_state != value);
m_state = value;
switch(m_state)
{
case State::Initial:
break;
case State::DiscoverNodes:
send(Ping());
break;
case State::SetAccessorySwitchTime:
send(AccessorySwitchTime(m_config.defaultSwitchTime / 10));
break;
case State::DownloadLokList:
send(ConfigData(m_config.nodeUID, ConfigDataName::loks));
break;
case State::Started:
if(m_onStarted) /*[[likely]]*/
EventLoop::call(
[this]()
{
m_onStarted();
});
break;
}
}
}

Datei anzeigen

@ -0,0 +1,337 @@
/**
* server/src/hardware/protocol/marklincan/kernel.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 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_MARKLINCAN_KERNEL_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_MARKLINCAN_KERNEL_HPP
#include <memory>
#include <array>
#include <map>
#include <unordered_map>
#include <thread>
#include <filesystem>
#include <queue>
#include <boost/asio/io_context.hpp>
#include <boost/asio/steady_timer.hpp>
#include <traintastic/enum/tristate.hpp>
#include "config.hpp"
#include "node.hpp"
#include "iohandler/iohandler.hpp"
#include "configdatastreamcollector.hpp"
class Decoder;
enum class DecoderChangeFlags;
class DecoderController;
class InputController;
class OutputController;
namespace MarklinCAN {
struct Message;
class LocomotiveList;
class Kernel
{
public:
static constexpr std::string_view nodeDeviceName = "Traintastic";
static constexpr std::string_view nodeArticleNumber = "1";
static constexpr uint16_t s88AddressMin = 1;
static constexpr uint16_t s88AddressMax = 16384;
static constexpr uint16_t outputMotorolaAddressMin = 1;
static constexpr uint16_t outputMotorolaAddressMax = 1024 * 2;
static constexpr uint16_t outputDCCAddressMin = 1;
static constexpr uint16_t outputDCCAddressMax = 2048 * 2;
static constexpr uint16_t outputSX1AddressMin = 1;
static constexpr uint16_t outputSX1AddressMax = 1024 * 2;
struct OutputChannel
{
static constexpr uint32_t motorola = 1;
static constexpr uint32_t dcc = 2;
static constexpr uint32_t sx1 = 3;
};
inline static const std::vector<uint32_t> outputChannels = {
OutputChannel::motorola,
OutputChannel::dcc,
OutputChannel::sx1,
};
inline static const std::vector<std::string_view> outputChannelNames = {
"$hardware:motorola$",
"$hardware:dcc$",
"SX1",
};
private:
//! Startup states, executed in order.
enum class State
{
Initial, // must be first
DiscoverNodes,
SetAccessorySwitchTime,
DownloadLokList,
Started // must be last
};
struct StatusDataConfigRequest
{
uint32_t uid;
uint8_t index;
StatusDataConfigRequest(uint32_t uid_, uint8_t index_)
: uid{uid_}
, index{index_}
{
}
};
static constexpr int statusDataConfigRequestRetryCount = 3;
boost::asio::io_context m_ioContext;
std::unique_ptr<IOHandler> m_ioHandler;
const bool m_simulation;
std::thread m_thread;
std::string m_logId;
State m_state = State::Initial;
std::function<void()> m_onStarted;
std::function<void()> m_onError;
std::function<void(const Node& node)> m_onNodeChanged;
std::queue<StatusDataConfigRequest> m_statusDataConfigRequestQueue; //<! UID+index to request config data from
int m_statusDataConfigRequestRetries = statusDataConfigRequestRetryCount;
boost::asio::steady_timer m_statusDataConfigRequestTimer;
std::function<void(const std::shared_ptr<LocomotiveList>&)> m_onLocomotiveListChanged;
std::unordered_map<uint32_t, Node> m_nodes;
DecoderController* m_decoderController = nullptr;
std::map<uint32_t, uint16_t> m_mfxUIDtoSID;
InputController* m_inputController = nullptr;
std::array<TriState, s88AddressMax - s88AddressMin + 1> m_inputValues;
OutputController* m_outputController = nullptr;
std::array<TriState, outputMotorolaAddressMax - outputMotorolaAddressMin + 1> m_outputValuesMotorola;
std::array<TriState, outputDCCAddressMax - outputDCCAddressMin + 1> m_outputValuesDCC;
std::array<TriState, outputSX1AddressMax - outputSX1AddressMin + 1> m_outputValuesSX1;
std::vector<std::byte> m_statusConfigData;
std::unique_ptr<ConfigDataStreamCollector> m_configDataStreamCollector;
const std::filesystem::path m_debugDir;
Config m_config;
#ifndef NDEBUG
bool m_started;
#endif
Kernel(const Config& config, bool simulation);
void setIOHandler(std::unique_ptr<IOHandler> handler);
void send(const Message& message);
void postSend(const Message& message);
void receiveStatusDataConfig(uint32_t nodeUID, uint8_t index, const std::vector<std::byte>& statusConfigData);
void receiveConfigData(std::unique_ptr<ConfigDataStreamCollector> configData);
void restartStatusDataConfigTimer();
void nodeChanged(const Node& node);
void changeState(State value);
inline void nextState()
{
assert(m_state != State::Started);
changeState(static_cast<State>(static_cast<std::underlying_type_t<State>>(m_state) + 1));
}
public:
Kernel(const Kernel&) = delete;
Kernel& operator =(const Kernel&) = delete;
#ifndef NDEBUG
bool isKernelThread() const
{
return std::this_thread::get_id() == m_thread.get_id();
}
#endif
/**
* \brief IO context for 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 Configuration
* \param[in] args IO handler arguments
* \return The kernel instance
*/
template<class IOHandlerType, class... Args>
static std::unique_ptr<Kernel> create(const Config& config, Args... args)
{
static_assert(std::is_base_of_v<IOHandler, IOHandlerType>);
std::unique_ptr<Kernel> kernel{new Kernel(config, isSimulation<IOHandlerType>())};
kernel->setIOHandler(std::make_unique<IOHandlerType>(*kernel, std::forward<Args>(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<class T>
T& ioHandler()
{
assert(dynamic_cast<T*>(m_ioHandler.get()));
return static_cast<T&>(*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 configuration
*
* \param[in] config The 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<void()> callback);
/**
* \brief Register error handler
*
* Once this handler is called the LocoNet communication it stopped.
*
* \param[in] callback Handler to call in case of an error.
* \note This function may not be called when the kernel is running.
*/
void setOnError(std::function<void()> callback);
/**
*
*/
void setOnLocomotiveListChanged(std::function<void(const std::shared_ptr<LocomotiveList>&)> callback);
/**
*
*/
void setOnNodeChanged(std::function<void(const Node& node)> 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 Marklin CAN message is received.
*
* \param[in] message The received Marklin CAN message
* \note This function must run in the kernel's IO context
*/
void receive(const Message& message);
//! Must be called by the IO handler in case of a fatal error.
//! This will put the interface in error state
//! \note This function must run in the event loop thread
void error();
void systemStop();
void systemGo();
void systemHalt();
void getLocomotiveList();
void decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber);
/**
* \brief ...
* \param[in] channel Channel
* \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(uint32_t channel, uint16_t address, bool value);
};
}
#endif

Datei anzeigen

@ -0,0 +1,248 @@
/**
* server/src/hardware/protocol/marklincan/locomotivelist.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 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 "locomotivelist.hpp"
#include <traintastic/enum/decoderprotocol.hpp>
#include "../dcc/dcc.hpp"
#include "../../../utils/fromchars.hpp"
#include "../../../utils/startswith.hpp"
namespace MarklinCAN {
static bool readLine(std::string_view& src, std::string_view& line)
{
const size_t n = src.find('\n');
if(n == std::string_view::npos)
return false;
line = src.substr(0, n);
src = src.substr(n + 1);
return true;
}
//! \brief Function types used by CS2 (and CS3?)
//! \note Values based on tests with CS2
enum class FunctionType : uint8_t
{
Light = 1,
CabLight = 48,
F0 = 50,
F1 = 51,
F2 = 52,
F3 = 53,
F4 = 54,
F5 = 55,
F6 = 56,
F7 = 57,
F8 = 58,
F9 = 59,
F10 = 60,
F11 = 61,
F12 = 62,
F13 = 63,
F14 = 64,
F15 = 65,
F16 = 66,
F17 = 67,
F18 = 68,
F19 = 69,
F20 = 70,
F21 = 71,
F22 = 72,
F23 = 73,
F24 = 74,
F25 = 75,
F26 = 76,
F27 = 77,
F28 = 78,
Mute = 109,
};
constexpr DecoderFunctionFunction toDecoderFunctionFunction(uint8_t typ)
{
// lowest 7 bit only (at least for the CS2)
switch(static_cast<FunctionType>(typ & 0x7F))
{
case FunctionType::Light:
return DecoderFunctionFunction::Light;
case FunctionType::Mute:
return DecoderFunctionFunction::Mute;
case FunctionType::CabLight:
case FunctionType::F0:
case FunctionType::F1:
case FunctionType::F2:
case FunctionType::F3:
case FunctionType::F4:
case FunctionType::F5:
case FunctionType::F6:
case FunctionType::F7:
case FunctionType::F8:
case FunctionType::F9:
case FunctionType::F10:
case FunctionType::F11:
case FunctionType::F12:
case FunctionType::F13:
case FunctionType::F14:
case FunctionType::F15:
case FunctionType::F16:
case FunctionType::F17:
case FunctionType::F18:
case FunctionType::F19:
case FunctionType::F20:
case FunctionType::F21:
case FunctionType::F22:
case FunctionType::F23:
case FunctionType::F24:
case FunctionType::F25:
case FunctionType::F26:
case FunctionType::F27:
case FunctionType::F28:
break; // Generic
}
return DecoderFunctionFunction::Generic;
}
constexpr DecoderFunctionType toDecoderFunctionType(uint8_t typ)
{
// higest bit is used to indicate it is a momentary function (at least for the CS2)
return (typ & 0x80) ? DecoderFunctionType::Momentary : DecoderFunctionType::OnOff;
}
LocomotiveList::LocomotiveList(std::string_view list)
: m_locomotives{build(list)}
{
}
std::vector<LocomotiveList::Locomotive> LocomotiveList::build(std::string_view list)
{
std::string_view line;
if(list.empty() || !readLine(list, line) || line != "[lokomotive]")
return {};
std::vector<Locomotive> locomotives;
while(readLine(list, line))
{
if(line == "lokomotive")
{
Locomotive locomotive;
while(readLine(list, line))
{
if(startsWith(line, " ."))
{
line = line.substr(2);
if(startsWith(line, "name="))
{
locomotive.name = line.substr(5);
}
else if(startsWith(line, "adresse=0x"))
{
fromChars(line.substr(10), locomotive.address, 16);
}
else if(startsWith(line, "typ="))
{
std::string_view typ = line.substr(4);
if(typ == "mfx")
{
locomotive.protocol = DecoderProtocol::MFX;
}
else if(typ == "dcc")
{
locomotive.protocol = DecoderProtocol::DCCShort; // or DCCLong (handled later)
}
else if(typ == "mm2_dil8" || typ == "mm2_prg" || typ == "mm_prg")
{
locomotive.protocol = DecoderProtocol::Motorola;
}
}
else if(startsWith(line, "sid=0x"))
{
fromChars(line.substr(6), locomotive.sid, 16);
}
else if(startsWith(line, "mfxuid=0x"))
{
fromChars(line.substr(9), locomotive.mfxUID, 16);
}
else if(line == "funktionen" || line == "funktionen_2")
{
bool hasTyp = false; // if typ is missing the function is unused
Function function;
function.nr = 0xFF;
while(readLine(list, line))
{
if(startsWith(line, " .."))
{
line = line.substr(3);
if(startsWith(line, "nr="))
{
fromChars(line.substr(3), function.nr);
}
else if(startsWith(line, "typ="))
{
hasTyp = true;
uint8_t typ;
if(fromChars(line.substr(4), typ).ec == std::errc())
{
function.type = toDecoderFunctionType(typ);
function.function = toDecoderFunctionFunction(typ);
}
}
}
else
{
list = {line.data(), line.size() + 1 + list.size()}; // restore list for next readLine
break;
}
}
if(function.nr != 0xFF && hasTyp)
{
locomotive.functions.push_back(function);
}
}
}
else
{
list = {line.data(), line.size() + 1 + list.size()}; // restore list for next readLine
break;
}
}
if(locomotive.protocol == DecoderProtocol::DCCShort && DCC::isLongAddress(locomotive.address))
{
locomotive.protocol = DecoderProtocol::DCCLong;
}
locomotives.emplace_back(std::move(locomotive));
}
}
return locomotives;
}
}

Datei anzeigen

@ -0,0 +1,84 @@
/**
* server/src/hardware/protocol/marklincan/locomotivelist.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 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_MARKLINCAN_LOCOMOTIVELIST_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_MARKLINCAN_LOCOMOTIVELIST_HPP
#include <string_view>
#include <vector>
#include <memory>
#include <traintastic/enum/decoderprotocol.hpp>
#include <traintastic/enum/decoderfunctiontype.hpp>
#include <traintastic/enum/decoderfunctionfunction.hpp>
namespace MarklinCAN {
class LocomotiveList
{
public:
struct Function
{
uint8_t nr;
DecoderFunctionType type = DecoderFunctionType::OnOff;
DecoderFunctionFunction function = DecoderFunctionFunction::Generic;
};
struct Locomotive
{
std::string name;
uint16_t address = 0;
DecoderProtocol protocol = DecoderProtocol::None;
uint16_t sid = 0;
uint32_t mfxUID = 0;
std::vector<Function> functions;
};
private:
static std::vector<Locomotive> build(std::string_view list);
const std::vector<Locomotive> m_locomotives;
public:
LocomotiveList(std::string_view list = {});
auto begin() const { return m_locomotives.begin(); }
auto end() const { return m_locomotives.end(); }
bool empty() const
{
return m_locomotives.empty();
}
const Locomotive& operator [](size_t index) const
{
return m_locomotives[index];
}
inline size_t size() const
{
return m_locomotives.size();
}
};
}
#endif

Datei anzeigen

@ -0,0 +1,77 @@
/**
* server/src/hardware/protocol/marklincan/message/configdata.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 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 "configdata.hpp"
namespace MarklinCAN {
//! CRC algorithm as documented by Marklin in the CS2 CAN documentation
static uint16_t crc16(uint16_t crc, uint8_t value)
{
constexpr uint16_t poly = 0x1021;
// Create the CRC "dividend" for polynomial arithmetic (binary arithmetic with no carries)
crc = crc ^ (static_cast<uint16_t>(value) << 8);
// "Divide" the poly into the dividend using CRC XOR subtraction CRC_acc holds the
// "remainder" of each divide. Only complete this division for 8 bits since input is 1 byte
for (int i = 0; i < 8; i++)
{
// Check if the MSB is set (if MSB is 1, then the POLY can "divide" into the "dividend")
if((crc & 0x8000) == 0x8000)
{
// if so, shift the CRC value, and XOR "subtract" the poly
crc = crc << 1;
crc ^= poly;
}
else
{
// if not, just shift the CRC value
crc = crc << 1;
}
}
// Return the final remainder (CRC value)
return crc;
}
uint16_t crc16(const std::vector<std::byte>& data)
{
uint16_t crc = 0xFFFF;
for(auto value : data)
{
crc = crc16(crc, static_cast<uint8_t>(value));
}
if(data.size() % 8 != 0)
{
// unused nul bytes must be included in CRC:
const size_t n = 8 - (data.size() % 8);
for(size_t i = 0; i < n; i++)
crc = crc16(crc, 0x00);
}
return crc;
}
}

Datei anzeigen

@ -0,0 +1,97 @@
/**
* server/src/hardware/protocol/marklincan/message/configdata.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 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_MARKLINCAN_MESSAGE_CONFIGDATA_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_MARKLINCAN_MESSAGE_CONFIGDATA_HPP
#include <cstddef>
#include <vector>
#include "../messages.hpp"
namespace MarklinCAN {
namespace ConfigDataName {
constexpr std::string_view loks = "loks";
}
struct ConfigData : Message
{
ConfigData(uint32_t hashUID, std::string_view name_, bool response = false)
: Message(hashUID, Command::ConfigData, response)
{
assert(name_.size() <= 8);
dlc = 8;
std::memcpy(data, name_.data(), std::min<size_t>(name_.size(), 8));
}
std::string_view name() const
{
const char* str = reinterpret_cast<const char*>(data);
return {str, strnlen(str, dlc)};
}
};
struct ConfigDataStream : Message
{
ConfigDataStream(uint32_t hashUID, uint32_t length_, uint16_t crc_)
: Message(hashUID, Command::ConfigDataStream, false)
{
dlc = 6;
*reinterpret_cast<uint32_t*>(data) = host_to_be(length_);
*reinterpret_cast<uint16_t*>(data + sizeof(uint32_t)) = host_to_be(crc_);
}
ConfigDataStream(uint32_t hashUID, const void* buffer, size_t size)
: Message(hashUID, Command::ConfigDataStream, false)
{
dlc = 8;
std::memcpy(data, buffer, std::min<size_t>(size, dlc));
}
bool isStart() const
{
return dlc == 6 || dlc == 7;
}
uint32_t length() const
{
assert(isStart());
return be_to_host(*reinterpret_cast<const uint32_t*>(data));
}
uint16_t crc() const
{
assert(isStart());
return be_to_host(*reinterpret_cast<const uint16_t*>(data + sizeof(uint32_t)));
}
bool isData() const
{
return dlc == 8;
}
};
uint16_t crc16(const std::vector<std::byte>& data);
}
#endif

Datei anzeigen

@ -0,0 +1,186 @@
/**
* server/src/hardware/protocol/marklincan/message/statusdataconfig.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 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 "statusdataconfig.hpp"
namespace MarklinCAN {
std::vector<Message> statusDataConfigReply(uint32_t hashUID, uint32_t uid, uint8_t index, const std::vector<std::byte>& bytes)
{
const uint16_t dataMessageCount = (bytes.size() + 7) / 8;
std::vector<Message> messages;
messages.reserve(1 + dataMessageCount);
const std::byte* p = bytes.data();
size_t todo = bytes.size();
for(uint16_t i = 0; i < dataMessageCount; i++)
{
const uint8_t dlc = std::min<size_t>(todo, 8);
messages.push_back(StatusDataConfigReplyData(i, p, dlc));
p += dlc;
todo -= dlc;
}
assert(p == bytes.data() + bytes.size());
messages.push_back(StatusDataConfigReply(hashUID, uid, index, dataMessageCount));
return messages;
}
namespace StatusData
{
static void append(std::byte*& p, const void* buffer, const size_t size)
{
p = static_cast<std::byte*>(std::memcpy(p, buffer, size)) + size;
}
template<typename T>
static void append(std::byte*& p, T value)
{
append(p, &value, sizeof(value));
}
template<typename T>
static void read(const std::byte*& p, T& value)
{
if constexpr(std::is_trivially_copyable_v<T>)
{
std::memcpy(&value, p, sizeof(value));
p += sizeof(value);
}
else
static_assert(sizeof(T) != sizeof(T));
}
static void read(const std::byte*& p, std::string& value, size_t maxLen)
{
const char* c = reinterpret_cast<const char*>(p);
value.assign(c, strnlen(c, maxLen));
p += value.size() + 1;
}
std::vector<std::byte> DeviceDescription::toBytes() const
{
assert(nickname.size() <= 15); // max 16 byte including NUL
std::vector<std::byte> bytes;
bytes.resize(
sizeof(numberOfReadings) +
sizeof(numberOfConfigurationChannels) +
sizeof(userDefined) +
sizeof(serialNumber) +
sizeof(articleNumber) +
deviceName.size() + 1 +
nickname.size() + 1);
std::byte* p = bytes.data();
append(p, numberOfReadings);
append(p, numberOfConfigurationChannels);
append(p, host_to_be(userDefined));
append(p, host_to_be(serialNumber));
append(p, articleNumber, sizeof(articleNumber));
append(p, deviceName.c_str(), deviceName.size() + 1);
append(p, nickname.c_str(), nickname.size() + 1);
assert(p == bytes.data() + bytes.size());
return bytes;
}
void DeviceDescription::fromBytes(const std::vector<std::byte>& bytes)
{
const std::byte* p = bytes.data();
const std::byte* const end = p + bytes.size();
read(p, numberOfReadings);
read(p, numberOfConfigurationChannels);
read(p, userDefined);
userDefined = be_to_host(userDefined);
read(p, serialNumber);
serialNumber = be_to_host(serialNumber);
read(p, articleNumber);
read(p, deviceName, end - p);
read(p, nickname, end - p);
}
void ReadingDescription::fromBytes(const std::vector<std::byte>& bytes)
{
const std::byte* p = bytes.data();
const std::byte* const end = p + bytes.size();
read(p, channel);
read(p, power);
read(p, color);
read(p, zero);
zero = be_to_host(zero);
read(p, rangeEnd);
for(size_t i = 0; i < sizeof(rangeEnd) / sizeof(*rangeEnd); i++)
rangeEnd[i] = be_to_host(rangeEnd[i]);
read(p, description, end - p);
read(p, labelStart, end - p);
read(p, labelEnd, end - p);
read(p, unit, end - p);
}
void ConfigurationDescription::fromBytes(const std::vector<std::byte>& bytes)
{
const std::byte* p = bytes.data();
const std::byte* const end = p + bytes.size();
read(p, channel);
read(p, type);
switch(type)
{
case Type::List:
{
uint8_t itemCount;
read(p, itemCount);
read(p, default_);
uint32_t reserved;
read(p, reserved);
read(p, description, end - p);
for(uint8_t i = 0; i < itemCount; i++)
{
std::string item;
read(p, item, end - p);
listItems.emplace_back(item);
}
break;
}
case Type::Number:
read(p, valueMin);
valueMin = be_to_host(valueMin);
read(p, valueMax);
valueMax = be_to_host(valueMax);
read(p, value);
value = be_to_host(value);
read(p, description, end - p);
read(p, labelStart, end - p);
read(p, labelEnd, end - p);
read(p, unit, end - p);
break;
}
}
}
}

Datei anzeigen

@ -0,0 +1,187 @@
/**
* server/src/hardware/protocol/marklincan/message/statusdataconfig.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 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_MARKLINCAN_MESSAGE_STATUSDATACONFIG_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_MARKLINCAN_MESSAGE_STATUSDATACONFIG_HPP
#include "../messages.hpp"
#include <cstddef>
#include <vector>
namespace MarklinCAN {
struct StatusDataConfig : UidMessage
{
StatusDataConfig(uint32_t hashUID, uint32_t uid, uint8_t index_)
: UidMessage(hashUID, Command::StatusDataConfig, false, uid)
{
dlc = 5;
setIndex(index_);
}
uint8_t index() const
{
return data[4];
}
void setIndex(uint8_t value)
{
data[4] = value;
}
};
struct StatusDataConfigReply : UidMessage
{
StatusDataConfigReply(uint32_t hashUID, uint32_t uid, uint8_t index_, uint8_t packetCount_)
: UidMessage(hashUID, Command::StatusDataConfig, true, uid)
{
dlc = 6;
setIndex(index_);
setPacketCount(packetCount_);
}
uint8_t index() const
{
return data[4];
}
void setIndex(uint8_t value)
{
data[4] = value;
}
uint8_t packetCount() const
{
return data[5];
}
void setPacketCount(uint8_t value)
{
data[5] = value;
}
};
struct StatusDataConfigReplyData : Message
{
static constexpr uint16_t startHash = 0x301;
StatusDataConfigReplyData(uint16_t packetNumber, const void* bytes, uint8_t byteCount)
: Message(Command::StatusDataConfig, true)
{
assert(byteCount >= 1 && byteCount <= 8);
setHash(startHash + packetNumber);
dlc = 8;
std::memcpy(data, bytes, byteCount);
}
};
std::vector<Message> statusDataConfigReply(uint32_t hashUID, uint32_t uid, uint8_t index, const std::vector<std::byte>& bytes);
template<class T>
inline std::vector<Message> statusDataConfigReply(uint32_t hashUID, uint32_t uid, uint8_t index, const T& data)
{
return statusDataConfigReply(hashUID, uid, index, data.toBytes());
}
namespace StatusData
{
struct DeviceDescription
{
inline static DeviceDescription decode(const std::vector<std::byte>& bytes)
{
DeviceDescription desc;
desc.fromBytes(bytes);
return desc;
}
uint8_t numberOfReadings = 0;
uint8_t numberOfConfigurationChannels = 0;
uint16_t userDefined = 0;
uint32_t serialNumber = 0;
char articleNumber[8] = {0, 0, 0, 0, 0, 0, 0, 0};
std::string deviceName;
std::string nickname;
std::vector<std::byte> toBytes() const;
void fromBytes(const std::vector<std::byte>& bytes);
};
struct ReadingDescription
{
inline static ReadingDescription decode(const std::vector<std::byte>& bytes)
{
ReadingDescription desc;
desc.fromBytes(bytes);
return desc;
}
uint8_t channel;
int8_t power;
uint8_t color[4];
uint16_t zero;
uint16_t rangeEnd[4];
std::string description;
std::string labelStart;
std::string labelEnd;
std::string unit;
void fromBytes(const std::vector<std::byte>& bytes);
};
struct ConfigurationDescription
{
enum class Type : uint8_t
{
List = 1,
Number = 2,
};
inline static ConfigurationDescription decode(const std::vector<std::byte>& bytes)
{
ConfigurationDescription desc;
desc.fromBytes(bytes);
return desc;
}
uint8_t channel;
Type type;
std::string description;
// list:
uint8_t default_;
std::vector<std::string> listItems;
// number:
uint16_t valueMin;
uint16_t valueMax;
uint16_t value;
std::string labelStart;
std::string labelEnd;
std::string unit;
void fromBytes(const std::vector<std::byte>& bytes);
};
}
}
#endif

Datei anzeigen

@ -0,0 +1,353 @@
/**
* server/src/hardware/protocol/marklincan/messages.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 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 "uid.hpp"
#include "../../../utils/tohex.hpp"
namespace MarklinCAN {
std::string toString(const Message& message)
{
std::string s;
switch(message.command())
{
case Command::System:
{
const auto& system = static_cast<const SystemMessage&>(message);
switch(system.subCommand())
{
case SystemSubCommand::SystemStop:
{
s.append("SystemStop");
const auto& systemStop = static_cast<const SystemStop&>(message);
s.append(" ").append(UID::toString(systemStop.uid()));
break;
}
case SystemSubCommand::SystemGo:
{
s.append("SystemGo");
const auto& systemGo = static_cast<const SystemGo&>(message);
s.append(" ").append(UID::toString(systemGo.uid()));
break;
}
case SystemSubCommand::SystemHalt:
{
s.append("SystemHalt");
const auto& systemHalt = static_cast<const SystemHalt&>(message);
s.append(" ").append(UID::toString(systemHalt.uid()));
break;
}
case SystemSubCommand::LocomotiveEmergencyStop:
s.append("LocomotiveEmergencyStop");
break;
case SystemSubCommand::LocomotiveCycleEnd:
s.append("LocomotiveCycleEnd");
break;
case SystemSubCommand::AccessorySwitchTime:
{
s.append("AccessorySwitchTime");
const auto& accessorySwitchTime = static_cast<const AccessorySwitchTime&>(message);
s.append(" switch_time=").append(std::to_string(accessorySwitchTime.switchTime()));
if(accessorySwitchTime.switchTime() == 0)
s.append(" (default)");
else
s.append(" (").append(std::to_string(accessorySwitchTime.switchTime() * 10)).append("ms)");
break;
}
case SystemSubCommand::Overload:
s.append("Overload");
break;
case SystemSubCommand::Status:
{
s.append("Status");
const auto& systemStatus = static_cast<const SystemStatus&>(message);
s.append(" ").append(UID::toString(systemStatus.uid()));
s.append(" channel=").append(std::to_string(systemStatus.channel()));
if(message.dlc == 8)
s.append(" value=").append(std::to_string(static_cast<const SystemStatusResponse&>(message).value()));
break;
}
case SystemSubCommand::ModelClock:
s.append("ModelClock");
if(message.dlc == 8)
{
const auto& modelClock = static_cast<const ModelClock&>(message);
s.append(" ").append(UID::toString(modelClock.uid()));
s.append(" hour=").append(std::to_string(modelClock.hour()));
s.append(" minute=").append(std::to_string(modelClock.minute()));
s.append(" factor=").append(std::to_string(modelClock.factor()));
}
break;
case SystemSubCommand::MFXSeek:
s.append("MFXSeek");
break;
}
break;
}
case Command::Discovery:
s.append("Discovery");
break;
case Command::Bind:
s.append("Bind");
break;
case Command::Verify:
s.append("Verify");
break;
case Command::LocomotiveSpeed:
{
s.append("LocomotiveSpeed");
const auto& locomotiveSpeed = static_cast<const LocomotiveSpeed&>(message);
if(!locomotiveSpeed.hasSpeed() && !locomotiveSpeed.isResponse())
s.append(" get");
else if(locomotiveSpeed.hasSpeed() && !locomotiveSpeed.isResponse())
s.append(" set");
else if(locomotiveSpeed.hasSpeed() && locomotiveSpeed.isResponse())
s.append(" ack");
else
s.append(" ???");
s.append(" ").append(UID::toString(locomotiveSpeed.uid()));
if(locomotiveSpeed.dlc == 6)
s.append(" speed=").append(std::to_string(locomotiveSpeed.speed()));
break;
}
case Command::LocomotiveDirection:
{
s.append("LocomotiveDirection");
const auto& locomotiveDirection = static_cast<const LocomotiveDirection&>(message);
if(!locomotiveDirection.hasDirection() && !locomotiveDirection.isResponse())
s.append(" get");
else if(locomotiveDirection.hasDirection() && !locomotiveDirection.isResponse())
s.append(" set");
else if(locomotiveDirection.hasDirection() && locomotiveDirection.isResponse())
s.append(" ack");
else
s.append(" ???");
s.append(" ").append(UID::toString(locomotiveDirection.uid()));
if(locomotiveDirection.hasDirection())
{
s.append(" direction=");
switch(locomotiveDirection.direction())
{
case LocomotiveDirection::Direction::Same:
s.append("same");
break;
case LocomotiveDirection::Direction::Forward:
s.append("forward");
break;
case LocomotiveDirection::Direction::Reverse:
s.append("reverse");
break;
case LocomotiveDirection::Direction::Inverse:
s.append("inverse");
break;
}
}
break;
}
case Command::LocomotiveFunction:
{
s.append("LocomotiveFunction");
const auto& locomotiveFunction = static_cast<const LocomotiveFunction&>(message);
if(!locomotiveFunction.hasValue() && !locomotiveFunction.isResponse())
s.append(" get");
else if(locomotiveFunction.hasValue() && !locomotiveFunction.isResponse())
s.append(" set");
else if(locomotiveFunction.hasValue() && locomotiveFunction.isResponse())
s.append(" ack");
else
s.append(" ???");
s.append(" ").append(UID::toString(locomotiveFunction.uid()));
s.append(" number=").append(std::to_string(locomotiveFunction.number()));
if(locomotiveFunction.hasValue())
s.append(" value=").append(std::to_string(locomotiveFunction.value()));
break;
}
case Command::ReadConfig:
s.append("ReadConfig");
break;
case Command::WriteConfig:
s.append("WriteConfig");
break;
case Command::AccessoryControl:
{
s.append("AccessoryControl");
const auto& accessoryControl = static_cast<const AccessoryControl&>(message);
s.append(" ").append(UID::toString(accessoryControl.uid()));
s.append(" position=").append(std::to_string(accessoryControl.position()));
s.append(" current=").append(std::to_string(accessoryControl.current()));
s.append(" switch_time=");
if(accessoryControl.isDefaultSwitchTime())
s.append("default");
else
s.append(std::to_string(accessoryControl.switchTime()));
break;
}
case Command::AccessoryConfig:
s.append("AccessoryConfig");
break;
case Command::S88Polling:
s.append("S88Polling");
break;
case Command::FeedbackEvent:
{
s.append("FeedbackEvent");
const auto& feedbackEvent = static_cast<const FeedbackMessage&>(message);
s.append(" device_id=").append(std::to_string(feedbackEvent.deviceId()));
s.append(" contact_id=").append(std::to_string(feedbackEvent.contactId()));
if(message.dlc == 5)
{
const auto& feedbackStateParameter = static_cast<const FeedbackStateParameter&>(message);
s.append(" parameter=").append(std::to_string(feedbackStateParameter.parameter()));
}
else if(message.dlc == 8)
{
const auto& feedbackState = static_cast<const FeedbackState&>(message);
s.append(" state_old=").append(std::to_string(feedbackState.stateOld()));
s.append(" state_new=").append(std::to_string(feedbackState.stateNew()));
s.append(" time=").append(std::to_string(feedbackState.time()));
}
break;
}
case Command::SX1Event:
s.append("SX1Event");
break;
case Command::Ping:
s.append("Ping");
if(message.dlc == 8)
{
const auto& pingReply = static_cast<const PingReply&>(message);
s.append(" ").append(UID::toString(pingReply.uid()));
s.append(" software_version=").append(std::to_string(pingReply.softwareVersionMajor())).append(".").append(std::to_string(pingReply.softwareVersionMinor()));
s.append(" device_id=0x").append(toHex(static_cast<uint16_t>(pingReply.deviceId())));
s.append(" (").append(toString(pingReply.deviceId())).append(")");
}
break;
case Command::Update:
s.append("Update");
break;
case Command::ReadConfigData:
s.append("ReadConfigData");
break;
case Command::BootloaderCAN:
s.append("BootloaderCAN");
break;
case Command::BootloaderTrack:
s.append("BootloaderTrack");
break;
case Command::StatusDataConfig:
s.append("StatusDataConfig");
break;
case Command::ConfigData:
s.append("ConfigData");
break;
case Command::ConfigDataStream:
s.append("ConfigDataStream");
break;
}
// raw:
s.append(s.empty() ? "[" : " [").append(std::to_string(message.priority()));
s.append(" ").append(toHex(static_cast<uint8_t>(message.command())));
s.append(message.isResponse() ? " 1" : " 0");
s.append(" ").append(toHex(message.hash()));
s.append(" ").append(std::to_string(message.dlc));
if(message.dlc > 0)
s.append(" ").append(toHex(message.data, message.dlc));
s.append("]");
return s;
}
std::string_view toString(MarklinCAN::DeviceId value)
{
switch(value)
{
case DeviceId::GleisFormatProzessorOrBooster:
return "Gleis Format Prozessor 60213,60214 / Booster 60173, 60174";
case DeviceId::Gleisbox:
return "Gleisbox 60112 und 60113";
case DeviceId::Connect6021:
return "Connect 6021 Art-Nr.60128";
case DeviceId::MS2:
return "MS 2 60653, Txxxxx";
case DeviceId::Traintastic:
return "Traintastic";
case DeviceId::WirelessDevices:
return "Wireless Devices";
case DeviceId::CS2GUI:
return "CS2-GUI (Master)";
}
return {};
}
}

Datei anzeigen

@ -0,0 +1,780 @@
/**
* server/src/hardware/protocol/marklincan/messages.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 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_MARKLINCAN_MESSAGES_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_MARKLINCAN_MESSAGES_HPP
#include <cstdint>
#include <cassert>
#include <cstring>
#include <string>
#include <initializer_list>
#include "../../../utils/byte.hpp"
#include "../../../utils/endian.hpp"
namespace MarklinCAN {
struct Message;
constexpr uint16_t calcHash(uint32_t uid)
{
const uint16_t hash = high16(uid) ^ low16(uid);
return ((hash << 3) & 0xFC00) | 0x0300 | (hash & 0x007F);
}
std::string toString(const Message& message);
enum class Command : uint8_t
{
System = 0x00,
Discovery = 0x01,
Bind = 0x02,
Verify = 0x03,
LocomotiveSpeed = 0x04,
LocomotiveDirection = 0x05,
LocomotiveFunction = 0x06,
ReadConfig = 0x07,
WriteConfig = 0x08,
AccessoryControl = 0x0B,
AccessoryConfig = 0x0C,
S88Polling = 0x10,
FeedbackEvent = 0x11,
SX1Event = 0x12,
Ping = 0x18, // or SoftwareVersionRequest
Update = 0x19,
ReadConfigData = 0x1A,
BootloaderCAN = 0x1B,
BootloaderTrack = 0x1C,
StatusDataConfig = 0x1D,
ConfigData = 0x20,
ConfigDataStream = 0x21,
};
enum class SystemSubCommand : uint8_t
{
SystemStop = 0x00,
SystemGo = 0x01,
SystemHalt = 0x02,
LocomotiveEmergencyStop = 0x03,
LocomotiveCycleEnd = 0x04,
AccessorySwitchTime = 0x06,
//Neuanmeldezahler = 0x09,
Overload = 0x0A,
Status = 0x0B,
ModelClock = 0x20,
MFXSeek = 0x30,
};
enum class DeviceId : uint16_t
{
GleisFormatProzessorOrBooster= 0x0000, //!< Gleis Format Prozessor 60213,60214 / Booster 60173, 60174
Gleisbox = 0x0010, //!< Gleisbox 60112 und 60113
Connect6021 = 0x0020, //!< Connect 6021 Art-Nr.60128
MS2 = 0x0030, //!< MS 2 60653, Txxxxx
Traintastic = 0x5740, //!< Device id for Traintastic (unofficial)
WirelessDevices = 0xFFE0, //!< Wireless Devices
CS2GUI = 0xFFFF //!< CS2-GUI (Master)
};
struct Message
{
static constexpr uint32_t hashMask = 0x0000FFFF;
static constexpr uint32_t responseMask = 0x00010000;
uint32_t id = 0;
uint8_t data[8] = {0, 0, 0, 0, 0, 0, 0, 0};
uint8_t dlc = 0;
Message() = default;
Message(uint32_t hashUID, Command command, bool response)
{
id = (static_cast<uint32_t>(command) << 17) | (response ? responseMask : 0) | calcHash(hashUID);
}
Message(Command command, bool response, uint32_t uid = 0)
{
id = (static_cast<uint32_t>(command) << 17) | (response ? 0x00010000 : 0) | calcHash(uid);
}
Message(Command command, bool response, std::initializer_list<uint8_t> data_, uint32_t uid = 0)
: Message(command, response, uid)
{
assert(data_.size() <= sizeof(data));
dlc = data_.size();
if(data_.size() != 0)
std::memcpy(data, data_.begin(), data_.size());
}
uint8_t priority() const
{
return (id >> 25) & 0x0F;
}
Command command() const
{
return static_cast<Command>((id >> 17) & 0xFF);
}
bool isResponse() const
{
return id & responseMask;
}
void setResponse(bool value)
{
if(value)
id |= responseMask;
else
id &= ~responseMask;
}
uint16_t hash() const
{
return id & hashMask;
}
void setHash(uint16_t value)
{
id &= ~hashMask;
id |= value;
}
void setHashUID(uint32_t value)
{
setHash(calcHash(value));
}
};
struct UidMessage : Message
{
UidMessage(uint32_t hashUID, Command command, bool response, uint32_t uid)
: Message(hashUID, command, response)
{
dlc = 4;
*reinterpret_cast<uint32_t*>(&data[0]) = host_to_be(uid);
}
UidMessage(Command command, bool response, uint32_t uid)
: Message(command, response, uid)
{
dlc = 4;
*reinterpret_cast<uint32_t*>(&data[0]) = host_to_be(uid);
}
uint32_t uid() const
{
return be_to_host(*reinterpret_cast<const uint32_t*>(&data[0]));
}
void setUID(uint32_t value)
{
*reinterpret_cast<uint32_t*>(&data[0]) = host_to_be(value);
}
};
struct SystemMessage : UidMessage
{
SystemMessage(SystemSubCommand subCommand, uint32_t uid = 0)
: UidMessage{Command::System, false, uid}
{
dlc = 5;
data[4] = static_cast<uint8_t>(subCommand);
}
SystemSubCommand subCommand() const
{
return static_cast<SystemSubCommand>(data[4]);
}
};
struct SystemStop : SystemMessage
{
SystemStop(uint32_t uid = 0)
: SystemMessage(SystemSubCommand::SystemStop, uid)
{
}
};
struct SystemGo : SystemMessage
{
SystemGo(uint32_t uid = 0)
: SystemMessage(SystemSubCommand::SystemGo, uid)
{
}
};
struct SystemHalt : SystemMessage
{
SystemHalt(uint32_t uid = 0)
: SystemMessage(SystemSubCommand::SystemHalt, uid)
{
}
};
struct SystemStatus : SystemMessage
{
uint8_t channel() const
{
assert(dlc > 5);
return data[5];
}
void setChannel(uint8_t value)
{
assert(dlc > 5);
data[5] = value;
}
};
struct SystemStatusRequest : SystemStatus
{
};
struct SystemStatusResponse : SystemStatus
{
uint16_t value() const
{
assert(dlc == 8);
return to16(data[7], data[6]);
}
void setValue(uint16_t value_)
{
assert(dlc == 8);
data[6] = high8(value_);
data[7] = low8(value_);
}
};
struct ModelClock : SystemMessage
{
ModelClock(uint32_t uid, uint8_t hour_, uint8_t minute_, uint8_t factor_)
: SystemMessage(SystemSubCommand::ModelClock, uid)
{
dlc = 8;
setHour(hour_);
setMinute(minute_);
setFactor(factor_);
}
uint8_t hour() const
{
return data[5];
}
void setHour(uint8_t value)
{
assert(value < 24);
data[5] = value;
}
uint8_t minute() const
{
return data[6];
}
void setMinute(uint8_t value)
{
assert(value < 60);
data[6] = value;
}
uint8_t factor() const
{
return data[7];
}
void setFactor(uint8_t value)
{
assert(value <= 60);
data[7] = value;
}
};
struct LocomotiveEmergencyStop : SystemMessage
{
LocomotiveEmergencyStop(uint32_t uid)
: SystemMessage(SystemSubCommand::LocomotiveEmergencyStop, uid)
{
}
};
struct AccessorySwitchTime : SystemMessage
{
AccessorySwitchTime(uint16_t switchTime_, uint32_t uid = 0)
: SystemMessage(SystemSubCommand::AccessorySwitchTime, uid)
{
dlc = 7;
setSwitchTime(switchTime_);
}
uint16_t switchTime() const
{
return to16(data[6], data[5]);
}
void setSwitchTime(uint16_t value)
{
assert(value <= 16300); // 163s in 10ms steps
data[5] = high8(value);
data[6] = low8(value);
}
};
struct LocomotiveSpeed : UidMessage
{
static constexpr uint16_t speedMax = 1000;
LocomotiveSpeed(uint32_t uid)
: UidMessage(Command::LocomotiveSpeed, false, uid)
{
}
LocomotiveSpeed(uint32_t uid, uint16_t speed_, bool response = false)
: LocomotiveSpeed(uid)
{
setResponse(response);
dlc = 6;
setSpeed(speed_);
}
bool hasSpeed() const
{
return dlc == 6;
}
uint16_t speed() const
{
assert(hasSpeed());
return to16(data[5], data[4]);
}
void setSpeed(uint16_t value)
{
assert(hasSpeed());
assert(value <= speedMax);
data[4] = high8(value);
data[5] = low8(value);
}
};
struct LocomotiveDirection : UidMessage
{
enum class Direction : uint8_t
{
Same = 0,
Forward = 1,
Reverse = 2,
Inverse = 3,
};
LocomotiveDirection(uint32_t uid)
: UidMessage(Command::LocomotiveDirection, false, uid)
{
}
LocomotiveDirection(uint32_t uid, Direction direction_, bool response = false)
: LocomotiveDirection(uid)
{
setResponse(response);
dlc = 5;
setDirection(direction_);
}
bool hasDirection() const
{
return dlc == 5;
}
Direction direction() const
{
assert(hasDirection());
return static_cast<Direction>(data[4]);
}
void setDirection(Direction value)
{
assert(hasDirection());
data[4] = static_cast<uint8_t>(value);
}
};
struct LocomotiveFunction : UidMessage
{
static constexpr uint8_t numberMax = 31;
static constexpr uint8_t valueOff = 0;
static constexpr uint8_t valueOnMin = 1;
static constexpr uint8_t valueOnMax = 31;
LocomotiveFunction(uint32_t uid, uint8_t number_)
: UidMessage(Command::LocomotiveFunction, false, uid)
{
dlc = 5;
setNumber(number_);
}
LocomotiveFunction(uint32_t uid, uint8_t number_, uint8_t value_, bool response = false)
: LocomotiveFunction(uid, number_)
{
setResponse(response);
dlc = 6;
setNumber(value_);
}
LocomotiveFunction(uint32_t uid, uint8_t number_, bool value_, bool response = false)
: LocomotiveFunction(uid, number_)
{
setResponse(response);
dlc = 6;
setValue(value_);
}
uint8_t number() const
{
return data[4];
}
void setNumber(uint8_t value)
{
assert(value <= numberMax);
data[4] = value;
}
bool hasValue() const
{
return dlc == 6;
}
bool isOn() const
{
return value() != valueOff;
}
uint8_t value() const
{
assert(hasValue());
return data[5];
}
void setValue(bool value)
{
setValue(value ? valueOnMin : valueOff);
}
void setValue(uint8_t value)
{
assert(hasValue());
assert(value <= valueOnMax);
data[5] = value;
}
};
struct AccessoryControl : UidMessage
{
static constexpr uint8_t positionOff = 0;
static constexpr uint8_t positionOn = 1;
AccessoryControl(uint32_t uid_, bool response = false)
: UidMessage(Command::AccessoryControl, response, uid_)
{
dlc = 6;
}
uint8_t position() const
{
return data[4];
}
void setPosition(uint8_t value)
{
data[4] = value;
}
uint8_t current() const
{
return data[5];
}
void setCurrent(uint8_t value)
{
assert(value <= 31);
data[5] = value;
}
bool isDefaultSwitchTime() const
{
return dlc == 6;
}
void setDefaultSwitchTime()
{
dlc = 6;
}
uint16_t switchTime() const
{
assert(dlc == 8);
return to16(data[7], data[6]);
}
void setSwitchTime(uint16_t value)
{
dlc = 8;
data[6] = high8(value);
data[7] = low8(value);
}
};
struct S88ModuleCount : UidMessage
{
S88ModuleCount(uint32_t uid, uint8_t count_)
: UidMessage(Command::S88Polling, false, uid)
{
dlc = 5;
setCount(count_);
}
uint8_t count() const
{
return data[5];
}
void setCount(uint8_t value)
{
data[5] = value;
}
};
struct S88ModuleState : UidMessage
{
//! \todo verify state endianess, asume big endian for now.
S88ModuleState(uint32_t uid, uint8_t module_)
: UidMessage(Command::S88Polling, true, uid)
{
dlc = 7;
setModule(module_);
}
uint8_t module() const
{
return data[5];
}
void setModule(uint8_t value)
{
data[5] = value;
}
uint16_t state() const
{
return to16(data[7], data[6]);
}
void setState(uint16_t value)
{
data[6] = low8(value);
data[7] = high8(value);
}
bool getState(uint8_t index) const
{
assert(index < 16);
return state() & (1 << index);
}
void setState(uint8_t index, bool value)
{
assert(index < 16);
if(value)
setState(state() | (1 << index));
else
setState(state() & ~(1 << index));
}
};
struct FeedbackMessage : UidMessage
{
FeedbackMessage(uint16_t deviceId_, uint16_t contactId_, bool response)
: UidMessage(Command::FeedbackEvent, response, 0)
{
dlc = 4;
setDeviceId(deviceId_);
setContactId(contactId_);
}
uint16_t deviceId() const
{
return to16(data[1], data[0]);
}
void setDeviceId(uint16_t value)
{
data[0] = high8(value);
data[1] = low8(value);
}
uint16_t contactId() const
{
return to16(data[3], data[2]);
}
void setContactId(uint16_t value)
{
data[2] = high8(value);
data[3] = low8(value);
}
};
struct FeedbackStateRequest : FeedbackMessage
{
FeedbackStateRequest(uint16_t deviceId_, uint16_t contactId_)
: FeedbackMessage(deviceId_, contactId_, false)
{
}
};
struct FeedbackStateParameter : FeedbackMessage
{
FeedbackStateParameter(uint16_t deviceId_, uint16_t contactId_, uint8_t parameter_)
: FeedbackMessage(deviceId_, contactId_, false)
{
dlc = 5;
setParameter(parameter_);
}
uint8_t parameter() const
{
return data[4];
}
void setParameter(uint8_t value)
{
data[4] = value;
}
};
struct FeedbackState : FeedbackMessage
{
FeedbackState(uint16_t deviceId_, uint16_t contactId_)
: FeedbackMessage(deviceId_, contactId_, true)
{
dlc = 8;
}
uint8_t stateOld() const
{
return data[4];
}
void setStateOld(uint8_t value)
{
data[4] = value;
}
uint8_t stateNew() const
{
return data[5];
}
void setStateNew(uint8_t value)
{
data[5] = value;
}
uint16_t time() const
{
return to16(data[7], data[6]);
}
void setTime(uint16_t value)
{
data[6] = high8(value);
data[7] = low8(value);
}
};
struct Ping : Message
{
Ping()
: Message(Command::Ping, false)
{
}
Ping(uint32_t hashUID)
: Message(hashUID, Command::Ping, false)
{
}
};
struct PingReply : UidMessage
{
PingReply(uint32_t hashUID, uint8_t softwareVersionMajor_, uint8_t softwareVersionMinor_, DeviceId deviceId_)
: UidMessage(Command::Ping, true, hashUID)
{
dlc = 8;
setSoftwareVersion(softwareVersionMajor_, softwareVersionMinor_);
setDeviceId(deviceId_);
}
uint8_t softwareVersionMajor() const
{
return data[4];
}
uint8_t softwareVersionMinor() const
{
return data[5];
}
void setSoftwareVersion(uint8_t major, uint8_t minor)
{
data[4] = major;
data[5] = minor;
}
DeviceId deviceId() const
{
return static_cast<DeviceId>(to16(data[7], data[6]));
}
void setDeviceId(DeviceId value)
{
data[6] = high8(static_cast<uint16_t>(value));
data[7] = low8(static_cast<uint16_t>(value));
}
};
struct BootloaderCAN : Message
{
BootloaderCAN(uint32_t hashUID)
: Message(hashUID, Command::BootloaderCAN, false)
{
}
};
std::string_view toString(MarklinCAN::DeviceId value);
}
#endif

Datei anzeigen

@ -0,0 +1,51 @@
/**
* server/src/hardware/protocol/marklincan/node.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 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_MARKLINCAN_NODE_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_MARKLINCAN_NODE_HPP
#include <string>
#include <cstdint>
#include "message/statusdataconfig.hpp"
namespace MarklinCAN {
enum class DeviceId : uint16_t;
struct Node
{
uint32_t uid = 0;
uint8_t softwareVersionMajor = 0;
uint8_t softwareVersionMinor = 0;
DeviceId deviceId = static_cast<DeviceId>(0);
uint32_t serialNumber = 0;
std::string articleNumber;
std::string deviceName;
uint8_t numberOfReadings = 0;
std::vector<StatusData::ReadingDescription> readings;
uint8_t numberOfConfigurationChannels = 0;
std::vector<StatusData::ConfigurationDescription> configurations;
};
}
#endif

Datei anzeigen

@ -0,0 +1,71 @@
/**
* server/src/hardware/protocol/marklincan/settings.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 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 "uid.hpp"
#include "../../../core/attributes.hpp"
#include "../../../utils/displayname.hpp"
#include "../../../utils/random.hpp"
namespace MarklinCAN {
Settings::Settings(Object& _parent, std::string_view parentPropertyName)
: SubObject(_parent, parentPropertyName)
, defaultSwitchTime{this, "default_switch_time", 0, PropertyFlags::ReadWrite | PropertyFlags::Store}
, nodeUID{this, "node_uid", Random::value(UID::Range::manufacturer), PropertyFlags::ReadWrite | PropertyFlags::Store}
, nodeSerialNumber{this, "node_serial_number", Random::value(nodeSerialNumberRandomMin, nodeSerialNumberRandomMax), PropertyFlags::ReadWrite | PropertyFlags::Store}
, debugLogRXTX{this, "debug_log_rx_tx", false, PropertyFlags::ReadWrite | PropertyFlags::Store}
, debugStatusDataConfig{this, "debug_status_data_config", false, PropertyFlags::ReadWrite | PropertyFlags::Store}
, debugConfigStream{this, "debug_config_stream", false, PropertyFlags::ReadWrite | PropertyFlags::Store}
{
Attributes::addMinMax<uint32_t>(defaultSwitchTime, 0, 163'000);
//Attributes::addStep(defaultSwitchTime, 10);
m_interfaceItems.add(defaultSwitchTime);
Attributes::addMinMax(nodeUID, UID::Range::manufacturer);
m_interfaceItems.add(nodeUID);
m_interfaceItems.add(nodeSerialNumber);
Attributes::addDisplayName(debugLogRXTX, DisplayName::Hardware::debugLogRXTX);
m_interfaceItems.add(debugLogRXTX);
m_interfaceItems.add(debugStatusDataConfig);
m_interfaceItems.add(debugConfigStream);
}
Config Settings::config() const
{
Config config;
config.defaultSwitchTime = defaultSwitchTime;
config.nodeUID = nodeUID;
config.nodeSerialNumber = nodeSerialNumber;
config.debugLogRXTX = debugLogRXTX;
config.debugStatusDataConfig = debugStatusDataConfig;
config.debugConfigStream = debugConfigStream;
return config;
}
}

Datei anzeigen

@ -0,0 +1,55 @@
/**
* server/src/hardware/protocol/marklincan/settings.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 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_MARKLINCAN_SETTINGS_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_MARKLINCAN_SETTINGS_HPP
#include "../../../core/subobject.hpp"
#include "../../../core/property.hpp"
#include "config.hpp"
namespace MarklinCAN {
class Settings final : public SubObject
{
private:
static constexpr uint32_t nodeSerialNumberRandomMin = 1000;
static constexpr uint32_t nodeSerialNumberRandomMax = 9999;
public:
CLASS_ID("marklincan_settings")
Property<uint32_t> defaultSwitchTime;
Property<uint32_t> nodeUID;
Property<uint32_t> nodeSerialNumber;
Property<bool> debugLogRXTX;
Property<bool> debugStatusDataConfig;
Property<bool> debugConfigStream;
Settings(Object& _parent, std::string_view parentPropertyName);
Config config() const;
};
}
#endif

Datei anzeigen

@ -0,0 +1,48 @@
/**
* server/src/hardware/protocol/marklincan/uid.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 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 "uid.hpp"
#include "../../../utils/inrange.hpp"
#include "../../../utils/tohex.hpp"
namespace MarklinCAN::UID {
std::string toString(uint32_t uid)
{
if(inRange(uid, Range::locomotiveMotorola))
return std::string("Motorola(").append(std::to_string(uid - Range::locomotiveMotorola.first)).append(")");
if(inRange(uid, Range::locomotiveMFX))
return std::string("MFX(").append(std::to_string(uid - Range::locomotiveMFX.first)).append(")");
if(inRange<uint32_t>(uid, Range::locomotiveDCC))
return std::string("DCC(").append(std::to_string(uid - Range::locomotiveDCC.first)).append(")");
if(inRange(uid, Range::accessoryMotorola))
return std::string("Motorola(").append(std::to_string(uid - Range::accessoryMotorola.first + 1)).append(")");
if(inRange(uid, Range::accessoryDCC))
return std::string("DCC(").append(std::to_string(uid - Range::accessoryDCC.first + 1)).append(")");
if(inRange(uid, Range::accessorySX1))
return std::string("SX1(").append(std::to_string(uid - Range::accessorySX1.first + 1)).append(")");
return std::string("uid=").append(toHex(uid));
}
}

Datei anzeigen

@ -0,0 +1,78 @@
/**
* server/src/hardware/protocol/marklincan/uid.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 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_MARKLINCAN_UID_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_MARKLINCAN_UID_HPP
#include <cstdint>
#include <string>
#include <utility>
#include <traintastic/enum/decoderprotocol.hpp>
namespace MarklinCAN::UID {
namespace Range
{
constexpr std::pair<uint32_t, uint32_t> locomotiveMotorola{0x000, 0x03FF};
constexpr std::pair<uint32_t, uint32_t> accessorySX1{0x0800, 0x0BFF};
constexpr std::pair<uint32_t, uint32_t> manufacturer{0x1C00, 0x1FFF};
constexpr std::pair<uint32_t, uint32_t> accessoryMotorola{0x3000, 0x33FF};
constexpr std::pair<uint32_t, uint32_t> accessoryDCC{0x3800, 0x3FFF};
constexpr std::pair<uint32_t, uint32_t> locomotiveMFX{0x4000, 0x7FFF};
constexpr std::pair<uint32_t, uint32_t> locomotiveDCC{0xC000, 0xFFFF};
}
constexpr uint32_t locomotiveMotorola(uint16_t address)
{
return (Range::locomotiveMotorola.first | address);
}
constexpr uint32_t accessorySX1(uint16_t address)
{
return (Range::accessorySX1.first | (address - 1));
}
constexpr uint32_t accessoryMotorola(uint16_t address)
{
return (Range::accessoryMotorola.first | (address - 1));
}
constexpr uint32_t accessoryDCC(uint16_t address)
{
return (Range::accessoryDCC.first | (address - 1));
}
constexpr uint32_t locomotiveMFX(uint16_t address)
{
return (Range::locomotiveMFX.first | address);
}
constexpr uint32_t locomotiveDCC(uint16_t address)
{
return (Range::locomotiveDCC.first | address);
}
std::string toString(uint32_t uid);
}
#endif

Datei anzeigen

@ -279,7 +279,7 @@ void Kernel::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes,
decoder.address,
decoder.emergencyStop,
decoder.direction,
Decoder::throttleToSpeedStep(decoder.throttle, 14),
Decoder::throttleToSpeedStep<uint8_t>(decoder.throttle, 14),
decoder.getFunctionValue(0)));
break;
@ -288,7 +288,7 @@ void Kernel::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes,
decoder.address,
decoder.emergencyStop,
decoder.direction,
Decoder::throttleToSpeedStep(decoder.throttle, 27)));
Decoder::throttleToSpeedStep<uint8_t>(decoder.throttle, 27)));
break;
case 28:
@ -296,7 +296,7 @@ void Kernel::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes,
decoder.address,
decoder.emergencyStop,
decoder.direction,
Decoder::throttleToSpeedStep(decoder.throttle, 28)));
Decoder::throttleToSpeedStep<uint8_t>(decoder.throttle, 28)));
break;
case 128:
@ -304,7 +304,7 @@ void Kernel::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes,
decoder.address,
decoder.emergencyStop,
decoder.direction,
Decoder::throttleToSpeedStep(decoder.throttle, 126)));
Decoder::throttleToSpeedStep<uint8_t>(decoder.throttle, 126)));
break;
default:

Datei anzeigen

@ -334,7 +334,7 @@ void ClientKernel::decoderChanged(const Decoder& decoder, DecoderChangeFlags cha
{
case 14:
{
const uint8_t speedStep = Decoder::throttleToSpeedStep(decoder.throttle, 14);
const uint8_t speedStep = Decoder::throttleToSpeedStep<uint8_t>(decoder.throttle, 14);
cmd.db0 = 0x10;
if(decoder.emergencyStop)
cmd.speedAndDirection = 0x01;
@ -344,7 +344,7 @@ void ClientKernel::decoderChanged(const Decoder& decoder, DecoderChangeFlags cha
}
case 28:
{
uint8_t speedStep = Decoder::throttleToSpeedStep(decoder.throttle, 28);
uint8_t speedStep = Decoder::throttleToSpeedStep<uint8_t>(decoder.throttle, 28);
cmd.db0 = 0x12;
if(decoder.emergencyStop)
cmd.speedAndDirection = 0x01;
@ -359,7 +359,7 @@ void ClientKernel::decoderChanged(const Decoder& decoder, DecoderChangeFlags cha
case 128:
default:
{
const uint8_t speedStep = Decoder::throttleToSpeedStep(decoder.throttle, 126);
const uint8_t speedStep = Decoder::throttleToSpeedStep<uint8_t>(decoder.throttle, 126);
cmd.db0 = 0x13;
if(decoder.emergencyStop)
cmd.speedAndDirection = 0x01;

Datei anzeigen

@ -60,6 +60,7 @@
#include "../hardware/interface/ecosinterface.hpp"
#include "../hardware/interface/hsi88.hpp"
#include "../hardware/interface/loconetinterface.hpp"
#include "../hardware/interface/marklincaninterface.hpp"
#include "../hardware/interface/traintasticdiyinterface.hpp"
#include "../hardware/interface/withrottleinterface.hpp"
#include "../hardware/interface/wlanmausinterface.hpp"
@ -174,6 +175,7 @@ void Class::registerValues(lua_State* L)
registerValue<ECoSInterface>(L, "ECOS");
registerValue<HSI88Interface>(L, "HSI88");
registerValue<LocoNetInterface>(L, "LOCONET");
registerValue<MarklinCANInterface>(L, "MARKLIN_CAN");
registerValue<TraintasticDIYInterface>(L, "TRAINTASTIC_DIY");
registerValue<XpressNetInterface>(L, "XPRESSNET");
registerValue<WiThrottleInterface>(L, "WITHROTTLE");

Datei anzeigen

@ -26,6 +26,7 @@
#include <boost/uuid/string_generator.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <archive.h>
#include <zlib.h>
#include <version.hpp>
#include <traintastic/utils/str.hpp>
#include "../core/eventloop.hpp"
@ -175,6 +176,7 @@ Traintastic::RunStatus Traintastic::run(const std::string& worldUUID, bool simul
Log::log(*this, LogMessage::I1006_X, boostVersion);
Log::log(*this, LogMessage::I1007_X, std::string_view{"nlohmann::json " STR(NLOHMANN_JSON_VERSION_MAJOR) "." STR(NLOHMANN_JSON_VERSION_MINOR) "." STR(NLOHMANN_JSON_VERSION_PATCH)});
Log::log(*this, LogMessage::I1008_X, std::string_view{archive_version_details()});
Log::log(*this, LogMessage::I1009_ZLIB_X, std::string_view{zlibVersion()});
//! \todo Add tcb::span version when available, see https://github.com/tcbrindle/span/issues/33
Log::log(*this, LogMessage::I9002_X, Lua::getVersion());

Datei anzeigen

@ -40,4 +40,14 @@ constexpr uint16_t to16(const uint8_t valueLow, const uint8_t valueHigh)
return (static_cast<uint16_t>(valueHigh) << 8) | valueLow;
}
constexpr uint16_t high16(const uint32_t value)
{
return static_cast<uint16_t>(value >> 16);
}
constexpr uint16_t low16(const uint32_t value)
{
return static_cast<uint16_t>(value & 0xFFFF);
}
#endif

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2021-2022 Reinder Feenstra
* Copyright (C) 2021-2023 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -53,6 +53,7 @@ namespace DisplayName
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 marklinCAN = "hardware:marklin_can";
constexpr std::string_view outputKeyboard = "hardware:output_keyboard";
constexpr std::string_view outputs = "hardware:outputs";
constexpr std::string_view speedSteps = "hardware:speed_steps";

52
server/src/utils/random.hpp Normale Datei
Datei anzeigen

@ -0,0 +1,52 @@
/**
* server/src/utils/random.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 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_RANDOM_HPP
#define TRAINTASTIC_SERVER_UTILS_RANDOM_HPP
#include <ctime>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_int_distribution.hpp>
class Random
{
private:
inline static boost::random::mt19937 s_gen{static_cast<unsigned int>(time(nullptr))};
Random() = default;
public:
template<typename T>
static T value(T min, T max)
{
boost::random::uniform_int_distribution<T> dist(min, max);
return dist(s_gen);
}
template<typename T>
static T value(std::pair<T, T> range)
{
return value(range.first, range.second);
}
};
#endif

Datei anzeigen

@ -0,0 +1,67 @@
/**
* server/src/utils/writefile.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 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 "writefile.hpp"
#include <fstream>
//! \brief Try to create directories if they doesn't exist already
static bool createDirectories(const std::filesystem::path& path)
{
if(!std::filesystem::is_directory(path))
{
std::error_code ec;
std::filesystem::create_directories(path, ec);
if(ec)
return false;
}
return true;
}
bool writeFile(const std::filesystem::path& filename, const void* data, size_t dataSize)
{
if(!createDirectories(filename.parent_path()))
return false;
std::ofstream file;
file.open(filename, std::ios::binary | std::ios::out | std::ios::trunc);
if(!file.is_open())
return false;
file.write(reinterpret_cast<const char*>(data), dataSize);
return true;
}
bool writeFileJSON(const std::filesystem::path& filename, const nlohmann::json& data)
{
if(!createDirectories(filename.parent_path()))
return false;
std::ofstream file;
file.open(filename, std::ios::binary | std::ios::out | std::ios::trunc);
if(!file.is_open())
return false;
file << data.dump(2);
return true;
}

Datei anzeigen

@ -0,0 +1,46 @@
/**
* server/src/utils/writefile.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 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_WRITEFILE_HPP
#define TRAINTASTIC_SERVER_UTILS_WRITEFILE_HPP
#include <cstddef>
#include <vector>
#include <filesystem>
#include <nlohmann/json.hpp>
bool writeFile(const std::filesystem::path& filename, const void* data, size_t dataSize);
inline bool writeFile(const std::filesystem::path& filename, std::string_view data)
{
return writeFile(filename, data.data(), data.size());
}
template<typename T>
inline bool writeFile(const std::filesystem::path& filename, const std::vector<T>& data)
{
return writeFile(filename, data.data(), data.size() * sizeof(T));
}
bool writeFileJSON(const std::filesystem::path& filename, const nlohmann::json& data);
#endif

47
server/src/utils/zlib.cpp Normale Datei
Datei anzeigen

@ -0,0 +1,47 @@
/**
* server/src/utils/zlib.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 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 "zlib.hpp"
#include <zlib.h>
namespace ZLib {
bool compressString(std::string_view src, std::vector<std::byte>& out)
{
uLongf destLen = out.size();
const int r = compress(reinterpret_cast<Bytef*>(out.data()), &destLen, reinterpret_cast<const Bytef*>(src.data()), src.size());
out.resize(destLen);
return r == Z_OK;
}
namespace Uncompress {
bool toString(const void* src, size_t srcSize, size_t dstSize, std::string& out)
{
out.resize(dstSize);
uLongf outSize = out.size();
int r = uncompress(reinterpret_cast<Bytef*>(out.data()), &outSize, reinterpret_cast<const Bytef*>(src), srcSize);
out.resize(outSize);
return r == Z_OK;
}
}}

41
server/src/utils/zlib.hpp Normale Datei
Datei anzeigen

@ -0,0 +1,41 @@
/**
* server/src/utils/zlib.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 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_ZLIB_HPP
#define TRAINTASTIC_SERVER_UTILS_ZLIB_HPP
#include <cstddef>
#include <string>
#include <string_view>
#include <vector>
namespace ZLib {
bool compressString(std::string_view src, std::vector<std::byte>& out);
namespace Uncompress {
bool toString(const void* src, size_t srcSize, size_t dstSize, std::string& out);
}}
#endif

Datei anzeigen

@ -67,7 +67,7 @@
using nlohmann::json;
constexpr auto decoderListColumns = DecoderListColumn::Id | DecoderListColumn::Name | DecoderListColumn::Interface | DecoderListColumn::Address;
constexpr auto decoderListColumns = DecoderListColumn::Id | DecoderListColumn::Name | DecoderListColumn::Interface | DecoderListColumn::Protocol | DecoderListColumn::Address;
constexpr auto inputListColumns = InputListColumn::Id | InputListColumn::Name | InputListColumn::Interface | InputListColumn::Channel | InputListColumn::Address;
constexpr auto outputListColumns = OutputListColumn::Id | OutputListColumn::Name | OutputListColumn::Interface | OutputListColumn::Channel | OutputListColumn::Address;
constexpr auto identificationListColumns = IdentificationListColumn::Id | IdentificationListColumn::Name | IdentificationListColumn::Interface /*| IdentificationListColumn::Channel*/ | IdentificationListColumn::Address;

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic test suite.
*
* Copyright (C) 2022 Reinder Feenstra
* Copyright (C) 2022-2023 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -27,6 +27,7 @@
#include "../src/hardware/interface/ecosinterface.hpp"
#include "../src/hardware/interface/hsi88.hpp"
#include "../src/hardware/interface/loconetinterface.hpp"
#include "../src/hardware/interface/marklincaninterface.hpp"
#include "../src/hardware/interface/traintasticdiyinterface.hpp"
#include "../src/hardware/interface/withrottleinterface.hpp"
#include "../src/hardware/interface/wlanmausinterface.hpp"
@ -38,6 +39,7 @@
ECoSInterface, \
HSI88Interface, \
LocoNetInterface, \
MarklinCANInterface, \
TraintasticDIYInterface, \
WiThrottleInterface, \
WlanMausInterface, \
@ -48,6 +50,7 @@
DCCPlusPlusInterface, \
ECoSInterface, \
LocoNetInterface, \
MarklinCANInterface, \
XpressNetInterface, \
Z21Interface

75
server/thirdparty/zlib/bin/zlib1.def vendored Normale Datei
Datei anzeigen

@ -0,0 +1,75 @@
; h:\mingw\3.3.1\bin\dlltool.exe --export-all-symbols --output-def=zlib.def adler32.pic.o compress.pic.o crc32.pic.o gzio.pic.o uncompr.pic.o deflate.pic.o trees.pic.o zutil.pic.o inflate.pic.o infback.pic.o inftrees.pic.o inffast.pic.o zlib-dllversion.o zlib-dll-res.o
EXPORTS
DllGetVersion @ 1 ;
_dist_code @ 2 DATA ;
_length_code @ 3 DATA ;
_tr_align @ 4 ;
_tr_flush_block @ 5 ;
_tr_init @ 6 ;
_tr_stored_block @ 7 ;
_tr_tally @ 8 ;
adler32 @ 9 ;
adler32_combine @ 10 ;
compress @ 11 ;
compress2 @ 12 ;
compressBound @ 13 ;
crc32 @ 14 ;
crc32_combine @ 15 ;
deflate @ 16 ;
deflateBound @ 17 ;
deflateCopy @ 18 ;
deflateEnd @ 19 ;
deflateInit2_ @ 20 ;
deflateInit_ @ 21 ;
deflateParams @ 22 ;
deflatePrime @ 23 ;
deflateReset @ 24 ;
deflateSetDictionary @ 25 ;
deflateSetHeader @ 26 ;
deflateTune @ 27 ;
deflate_copyright @ 28 DATA ;
get_crc_table @ 29 ;
gzclearerr @ 30 ;
gzclose @ 31 ;
gzdirect @ 32 ;
gzdopen @ 33 ;
gzeof @ 34 ;
gzerror @ 35 ;
gzflush @ 36 ;
gzgetc @ 37 ;
gzgets @ 38 ;
gzopen @ 39 ;
gzprintf @ 40 ;
gzputc @ 41 ;
gzputs @ 42 ;
gzread @ 43 ;
gzrewind @ 44 ;
gzseek @ 45 ;
gzsetparams @ 46 ;
gztell @ 47 ;
gzungetc @ 48 ;
gzwrite @ 49 ;
inflate @ 50 ;
inflateBack @ 51 ;
inflateBackEnd @ 52 ;
inflateBackInit_ @ 53 ;
inflateCopy @ 54 ;
inflateEnd @ 55 ;
inflateGetHeader @ 56 ;
inflateInit2_ @ 57 ;
inflateInit_ @ 58 ;
inflatePrime @ 59 ;
inflateReset @ 60 ;
inflateSetDictionary @ 61 ;
inflateSync @ 62 ;
inflateSyncPoint @ 63 ;
inflate_copyright @ 64 DATA ;
inflate_fast @ 65 ;
inflate_table @ 66 ;
uncompress @ 67 ;
zError @ 68 ;
z_errmsg @ 69 DATA ;
zcalloc @ 70 ;
zcfree @ 71 ;
zlibCompileFlags @ 72 ;
zlibVersion @ 73 ;

3
server/thirdparty/zlib/bin/zlib1.dll vendored Normale Datei
Datei anzeigen

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4b08bebfac59f1c453668d1742df29fdbe1be195d3d73ab7a8851b9a52d2acfe
size 120302

332
server/thirdparty/zlib/include/zconf.h vendored Normale Datei
Datei anzeigen

@ -0,0 +1,332 @@
/* zconf.h -- configuration of the zlib compression library
* Copyright (C) 1995-2005 Jean-loup Gailly.
* For conditions of distribution and use, see copyright notice in zlib.h
*/
/* @(#) $Id$ */
#ifndef ZCONF_H
#define ZCONF_H
/*
* If you *really* need a unique prefix for all types and library functions,
* compile with -DZ_PREFIX. The "standard" zlib should be compiled without it.
*/
#ifdef Z_PREFIX
# define deflateInit_ z_deflateInit_
# define deflate z_deflate
# define deflateEnd z_deflateEnd
# define inflateInit_ z_inflateInit_
# define inflate z_inflate
# define inflateEnd z_inflateEnd
# define deflateInit2_ z_deflateInit2_
# define deflateSetDictionary z_deflateSetDictionary
# define deflateCopy z_deflateCopy
# define deflateReset z_deflateReset
# define deflateParams z_deflateParams
# define deflateBound z_deflateBound
# define deflatePrime z_deflatePrime
# define inflateInit2_ z_inflateInit2_
# define inflateSetDictionary z_inflateSetDictionary
# define inflateSync z_inflateSync
# define inflateSyncPoint z_inflateSyncPoint
# define inflateCopy z_inflateCopy
# define inflateReset z_inflateReset
# define inflateBack z_inflateBack
# define inflateBackEnd z_inflateBackEnd
# define compress z_compress
# define compress2 z_compress2
# define compressBound z_compressBound
# define uncompress z_uncompress
# define adler32 z_adler32
# define crc32 z_crc32
# define get_crc_table z_get_crc_table
# define zError z_zError
# define alloc_func z_alloc_func
# define free_func z_free_func
# define in_func z_in_func
# define out_func z_out_func
# define Byte z_Byte
# define uInt z_uInt
# define uLong z_uLong
# define Bytef z_Bytef
# define charf z_charf
# define intf z_intf
# define uIntf z_uIntf
# define uLongf z_uLongf
# define voidpf z_voidpf
# define voidp z_voidp
#endif
#if defined(__MSDOS__) && !defined(MSDOS)
# define MSDOS
#endif
#if (defined(OS_2) || defined(__OS2__)) && !defined(OS2)
# define OS2
#endif
#if defined(_WINDOWS) && !defined(WINDOWS)
# define WINDOWS
#endif
#if defined(_WIN32) || defined(_WIN32_WCE) || defined(__WIN32__)
# ifndef WIN32
# define WIN32
# endif
#endif
#if (defined(MSDOS) || defined(OS2) || defined(WINDOWS)) && !defined(WIN32)
# if !defined(__GNUC__) && !defined(__FLAT__) && !defined(__386__)
# ifndef SYS16BIT
# define SYS16BIT
# endif
# endif
#endif
/*
* Compile with -DMAXSEG_64K if the alloc function cannot allocate more
* than 64k bytes at a time (needed on systems with 16-bit int).
*/
#ifdef SYS16BIT
# define MAXSEG_64K
#endif
#ifdef MSDOS
# define UNALIGNED_OK
#endif
#ifdef __STDC_VERSION__
# ifndef STDC
# define STDC
# endif
# if __STDC_VERSION__ >= 199901L
# ifndef STDC99
# define STDC99
# endif
# endif
#endif
#if !defined(STDC) && (defined(__STDC__) || defined(__cplusplus))
# define STDC
#endif
#if !defined(STDC) && (defined(__GNUC__) || defined(__BORLANDC__))
# define STDC
#endif
#if !defined(STDC) && (defined(MSDOS) || defined(WINDOWS) || defined(WIN32))
# define STDC
#endif
#if !defined(STDC) && (defined(OS2) || defined(__HOS_AIX__))
# define STDC
#endif
#if defined(__OS400__) && !defined(STDC) /* iSeries (formerly AS/400). */
# define STDC
#endif
#ifndef STDC
# ifndef const /* cannot use !defined(STDC) && !defined(const) on Mac */
# define const /* note: need a more gentle solution here */
# endif
#endif
/* Some Mac compilers merge all .h files incorrectly: */
#if defined(__MWERKS__)||defined(applec)||defined(THINK_C)||defined(__SC__)
# define NO_DUMMY_DECL
#endif
/* Maximum value for memLevel in deflateInit2 */
#ifndef MAX_MEM_LEVEL
# ifdef MAXSEG_64K
# define MAX_MEM_LEVEL 8
# else
# define MAX_MEM_LEVEL 9
# endif
#endif
/* Maximum value for windowBits in deflateInit2 and inflateInit2.
* WARNING: reducing MAX_WBITS makes minigzip unable to extract .gz files
* created by gzip. (Files created by minigzip can still be extracted by
* gzip.)
*/
#ifndef MAX_WBITS
# define MAX_WBITS 15 /* 32K LZ77 window */
#endif
/* The memory requirements for deflate are (in bytes):
(1 << (windowBits+2)) + (1 << (memLevel+9))
that is: 128K for windowBits=15 + 128K for memLevel = 8 (default values)
plus a few kilobytes for small objects. For example, if you want to reduce
the default memory requirements from 256K to 128K, compile with
make CFLAGS="-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7"
Of course this will generally degrade compression (there's no free lunch).
The memory requirements for inflate are (in bytes) 1 << windowBits
that is, 32K for windowBits=15 (default value) plus a few kilobytes
for small objects.
*/
/* Type declarations */
#ifndef OF /* function prototypes */
# ifdef STDC
# define OF(args) args
# else
# define OF(args) ()
# endif
#endif
/* The following definitions for FAR are needed only for MSDOS mixed
* model programming (small or medium model with some far allocations).
* This was tested only with MSC; for other MSDOS compilers you may have
* to define NO_MEMCPY in zutil.h. If you don't need the mixed model,
* just define FAR to be empty.
*/
#ifdef SYS16BIT
# if defined(M_I86SM) || defined(M_I86MM)
/* MSC small or medium model */
# define SMALL_MEDIUM
# ifdef _MSC_VER
# define FAR _far
# else
# define FAR far
# endif
# endif
# if (defined(__SMALL__) || defined(__MEDIUM__))
/* Turbo C small or medium model */
# define SMALL_MEDIUM
# ifdef __BORLANDC__
# define FAR _far
# else
# define FAR far
# endif
# endif
#endif
#if defined(WINDOWS) || defined(WIN32)
/* If building or using zlib as a DLL, define ZLIB_DLL.
* This is not mandatory, but it offers a little performance increase.
*/
# ifdef ZLIB_DLL
# if defined(WIN32) && (!defined(__BORLANDC__) || (__BORLANDC__ >= 0x500))
# ifdef ZLIB_INTERNAL
# define ZEXTERN extern __declspec(dllexport)
# else
# define ZEXTERN extern __declspec(dllimport)
# endif
# endif
# endif /* ZLIB_DLL */
/* If building or using zlib with the WINAPI/WINAPIV calling convention,
* define ZLIB_WINAPI.
* Caution: the standard ZLIB1.DLL is NOT compiled using ZLIB_WINAPI.
*/
# ifdef ZLIB_WINAPI
# ifdef FAR
# undef FAR
# endif
# include <windows.h>
/* No need for _export, use ZLIB.DEF instead. */
/* For complete Windows compatibility, use WINAPI, not __stdcall. */
# define ZEXPORT WINAPI
# ifdef WIN32
# define ZEXPORTVA WINAPIV
# else
# define ZEXPORTVA FAR CDECL
# endif
# endif
#endif
#if defined (__BEOS__)
# ifdef ZLIB_DLL
# ifdef ZLIB_INTERNAL
# define ZEXPORT __declspec(dllexport)
# define ZEXPORTVA __declspec(dllexport)
# else
# define ZEXPORT __declspec(dllimport)
# define ZEXPORTVA __declspec(dllimport)
# endif
# endif
#endif
#ifndef ZEXTERN
# define ZEXTERN extern
#endif
#ifndef ZEXPORT
# define ZEXPORT
#endif
#ifndef ZEXPORTVA
# define ZEXPORTVA
#endif
#ifndef FAR
# define FAR
#endif
#if !defined(__MACTYPES__)
typedef unsigned char Byte; /* 8 bits */
#endif
typedef unsigned int uInt; /* 16 bits or more */
typedef unsigned long uLong; /* 32 bits or more */
#ifdef SMALL_MEDIUM
/* Borland C/C++ and some old MSC versions ignore FAR inside typedef */
# define Bytef Byte FAR
#else
typedef Byte FAR Bytef;
#endif
typedef char FAR charf;
typedef int FAR intf;
typedef uInt FAR uIntf;
typedef uLong FAR uLongf;
#ifdef STDC
typedef void const *voidpc;
typedef void FAR *voidpf;
typedef void *voidp;
#else
typedef Byte const *voidpc;
typedef Byte FAR *voidpf;
typedef Byte *voidp;
#endif
#if 0 /* HAVE_UNISTD_H -- this line is updated by ./configure */
# include <sys/types.h> /* for off_t */
# include <unistd.h> /* for SEEK_* and off_t */
# ifdef VMS
# include <unixio.h> /* for off_t */
# endif
# define z_off_t off_t
#endif
#ifndef SEEK_SET
# define SEEK_SET 0 /* Seek from beginning of file. */
# define SEEK_CUR 1 /* Seek from current position. */
# define SEEK_END 2 /* Set file pointer to EOF plus "offset" */
#endif
#ifndef z_off_t
# define z_off_t long
#endif
#if defined(__OS400__)
# define NO_vsnprintf
#endif
#if defined(__MVS__)
# define NO_vsnprintf
# ifdef FAR
# undef FAR
# endif
#endif
/* MVS linker does not support external names larger than 8 bytes */
#if defined(__MVS__)
# pragma map(deflateInit_,"DEIN")
# pragma map(deflateInit2_,"DEIN2")
# pragma map(deflateEnd,"DEEND")
# pragma map(deflateBound,"DEBND")
# pragma map(inflateInit_,"ININ")
# pragma map(inflateInit2_,"ININ2")
# pragma map(inflateEnd,"INEND")
# pragma map(inflateSync,"INSY")
# pragma map(inflateSetDictionary,"INSEDI")
# pragma map(compressBound,"CMBND")
# pragma map(inflate_table,"INTABL")
# pragma map(inflate_fast,"INFA")
# pragma map(inflate_copyright,"INCOPY")
#endif
#endif /* ZCONF_H */

1357
server/thirdparty/zlib/include/zlib.h vendored Normale Datei

Datei-Diff unterdrückt, da er zu groß ist Diff laden

Datei anzeigen

@ -31,19 +31,37 @@ enum class DecoderProtocol : uint8_t
None = 0,
DCCShort = 1,
Motorola = 2,
//MFX = 3,
MFX = 3,
Selectrix = 4,
//FMZ = 5,
DCCLong = 6,
};
TRAINTASTIC_ENUM(DecoderProtocol, "decoder_protocol", 5,
TRAINTASTIC_ENUM(DecoderProtocol, "decoder_protocol", 6,
{
{DecoderProtocol::None, "none"},
{DecoderProtocol::DCCShort, "dcc_short"},
{DecoderProtocol::Motorola, "motorola"},
{DecoderProtocol::MFX, "mfx"},
{DecoderProtocol::Selectrix, "selectrix"},
{DecoderProtocol::DCCLong, "dcc_long"},
});
constexpr bool hasAddress(DecoderProtocol value)
{
switch(value)
{
case DecoderProtocol::DCCShort:
case DecoderProtocol::DCCLong:
case DecoderProtocol::Motorola:
case DecoderProtocol::Selectrix:
return true;
case DecoderProtocol::None:
case DecoderProtocol::MFX:
return false;
}
return false;
}
#endif

Datei anzeigen

@ -78,6 +78,7 @@ enum class LogMessage : uint32_t
I1006_X = LogMessageOffset::info + 1006, //!< boost version
I1007_X = LogMessageOffset::info + 1007, //!< nlohmann::json version
I1008_X = LogMessageOffset::info + 1008, //!< LibArchive version
I1009_ZLIB_X = LogMessageOffset::info + 1009, //!< zlib version
I2001_UNKNOWN_LOCO_ADDRESS_X = LogMessageOffset::info + 2001,
I2002_HARDWARE_TYPE_X = LogMessageOffset::info + 2002,
I2003_FIRMWARE_VERSION_X = LogMessageOffset::info + 2003,
@ -172,6 +173,9 @@ enum class LogMessage : uint32_t
E2019_TIMEOUT_NO_RESPONSE_WITHIN_X_MS = LogMessageOffset::error + 2019,
E2020_TOTAL_NUMBER_OF_MODULES_MAY_NOT_EXCEED_X = LogMessageOffset::error + 2020,
E2021_STARTING_PCAP_LOG_FAILED_X = LogMessageOffset::error + 2021,
E2022_SOCKET_CREATE_FAILED_X = LogMessageOffset::error + 2022,
E2023_SOCKET_IOCTL_FAILED_X = LogMessageOffset::error + 2023,
E2024_UNKNOWN_LOCOMOTIVE_MFX_UID_X = LogMessageOffset::error + 2024,
E9001_X_DURING_EXECUTION_OF_X_EVENT_HANDLER = LogMessageOffset::error + 9001,
E9999_X = LogMessageOffset::error + 9999,
@ -191,6 +195,7 @@ enum class LogMessage : uint32_t
C1013_CANT_LOAD_WORLD_SAVED_WITH_NEWER_VERSION_REQUIRES_AT_LEAST_X = LogMessageOffset::critical + 1013,
C2001_ADDRESS_ALREADY_USED_AT_X = LogMessageOffset::critical + 2001,
C2004_CANT_GET_FREE_SLOT = LogMessageOffset::critical + 2004,
C2005_SOCKETCAN_IS_ONLY_AVAILABLE_ON_LINUX = LogMessageOffset::critical + 2005,
C9999_X = LogMessageOffset::critical + 9999,
// Fatal:

Datei anzeigen

@ -0,0 +1,58 @@
/**
* shared/src/traintastic/enum/marklincaninterfacetype.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 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_MARKLINCANINTERFACETYPE_HPP
#define TRAINTASTIC_SHARED_TRAINTASTIC_ENUM_MARKLINCANINTERFACETYPE_HPP
#include <cstdint>
#include <array>
#include "enum.hpp"
enum class MarklinCANInterfaceType : uint16_t
{
NetworkTCP = 0,
NetworkUDP = 1,
SocketCAN = 2,
Serial = 3,
};
TRAINTASTIC_ENUM(MarklinCANInterfaceType, "marklin_can_interface_type", 4,
{
{MarklinCANInterfaceType::NetworkTCP, "network_tcp"},
{MarklinCANInterfaceType::NetworkUDP, "network_udp"},
{MarklinCANInterfaceType::SocketCAN, "socket_can"},
{MarklinCANInterfaceType::Serial, "serial"},
});
inline constexpr std::array<MarklinCANInterfaceType, 4> marklinCANInterfaceTypeValues{{
MarklinCANInterfaceType::NetworkTCP,
MarklinCANInterfaceType::NetworkUDP,
MarklinCANInterfaceType::SocketCAN,
MarklinCANInterfaceType::Serial,
}};
constexpr bool isNetwork(MarklinCANInterfaceType value)
{
return value == MarklinCANInterfaceType::NetworkTCP || value == MarklinCANInterfaceType::NetworkUDP;
}
#endif

Datei anzeigen

@ -4222,5 +4222,93 @@
"term_plural": "",
"reference": "",
"comment": ""
},
{
"term": "marklin_can_interface_type:network_tcp",
"definition": "Network (TCP)"
},
{
"term": "marklin_can_interface_type:network_udp",
"definition": "Network (UDP)"
},
{
"term": "message:E2022",
"definition": "Socket create failed (%1)"
},
{
"term": "message:E2023",
"definition": "Socket ioctl failed (%1)"
},
{
"term": "message:C2005",
"definition": "SocketCAN is only available on Linux"
},
{
"term": "marklin_can_interface_type:socket_can",
"definition": "SocketCAN (Linux only)"
},
{
"term": "interface.marklin_can:marklin_can_locomotive_list",
"definition": "Märklin CAN: Locomotive list"
},
{
"term": "marklincan_settings:default_switch_time",
"definition": "Default switch time"
},
{
"term": "marklincan_settings:debug_config_stream",
"definition": "Debug: Config stream"
},
{
"term": "marklincan_settings:node_uid",
"definition": "Node UID"
},
{
"term": "marklincan_settings:node_serial_number",
"definition": "Node serial number"
},
{
"term": "marklin_can_locomotive_list:import_or_sync",
"definition": "Import/sync"
},
{
"term": "marklin_can_locomotive_list:import_or_sync_all",
"definition": "Import/sync all"
},
{
"term": "marklin_can_locomotive_list:reload",
"definition": "Reload"
},
{
"term": "interface.marklin_can:marklin_can_node_list",
"definition": "Märklin CAN: Node list"
},
{
"term": "table_model.marklin_can_node_list:device_name",
"definition": "Device name"
},
{
"term": "table_model.marklin_can_node_list:article_number",
"definition": "Article number"
},
{
"term": "table_model.marklin_can_node_list:serial_number",
"definition": "Serial number"
},
{
"term": "table_model.marklin_can_node_list:software_version",
"definition": "Software version"
},
{
"term": "table_model.marklin_can_node_list:device_id",
"definition": "Device Id"
},
{
"term": "marklincan_settings:debug_status_data_config",
"definition": "Debug: Status data config"
},
{
"term": "message:E2024",
"definition": "Unknown locomotive MFX UID: %1"
}
]

Datei anzeigen

@ -346,5 +346,33 @@
{
"term": "xpressnet_serial_interface:rosoft_s88xpressnetli",
"definition": "RoSoft s88XPressNetLI"
},
{
"term": "class_id:interface.marklin_can",
"definition": "Märklin CAN"
},
{
"term": "decoder_protocol:mfx",
"definition": "MFX"
},
{
"term": "decoder_protocol:selectrix",
"definition": "Selectrix"
},
{
"term": "hardware:marklin_can",
"definition": "Märklin CAN"
},
{
"term": "decoder:mfx_uid",
"definition": "MFX UID"
},
{
"term": "message:I1009",
"definition": "zlib %1"
},
{
"term": "table_model.marklin_can_node_list:uid",
"definition": "UID"
}
]