Merge pull request #77 from traintastic/11-märklin-cs2cs3-hardware-support
Märklin CS2/CS3 hardware support
Dieser Commit ist enthalten in:
Commit
3041751f6c
@ -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"
|
||||
|
||||
@ -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 ||
|
||||
|
||||
@ -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);
|
||||
};
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
80
client/src/widget/list/listwidget.cpp
Normale Datei
80
client/src/widget/list/listwidget.cpp
Normale Datei
@ -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);
|
||||
}
|
||||
50
client/src/widget/list/listwidget.hpp
Normale Datei
50
client/src/widget/list/listwidget.hpp
Normale Datei
@ -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
|
||||
77
client/src/widget/list/marklincanlocomotivelistwidget.cpp
Normale Datei
77
client/src/widget/list/marklincanlocomotivelistwidget.cpp
Normale Datei
@ -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);
|
||||
}
|
||||
46
client/src/widget/list/marklincanlocomotivelistwidget.hpp
Normale Datei
46
client/src/widget/list/marklincanlocomotivelistwidget.hpp
Normale Datei
@ -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
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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,
|
||||
};
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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,
|
||||
|
||||
194
server/src/hardware/interface/marklincan/marklincanlocomotivelist.cpp
Normale Datei
194
server/src/hardware/interface/marklincan/marklincanlocomotivelist.cpp
Normale Datei
@ -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);
|
||||
}
|
||||
@ -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
|
||||
@ -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 {};
|
||||
}
|
||||
@ -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
|
||||
71
server/src/hardware/interface/marklincan/marklincannodelist.cpp
Normale Datei
71
server/src/hardware/interface/marklincan/marklincannodelist.cpp
Normale Datei
@ -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);
|
||||
}
|
||||
}
|
||||
53
server/src/hardware/interface/marklincan/marklincannodelist.hpp
Normale Datei
53
server/src/hardware/interface/marklincan/marklincannodelist.hpp
Normale Datei
@ -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
|
||||
@ -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 {};
|
||||
}
|
||||
@ -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
|
||||
358
server/src/hardware/interface/marklincaninterface.cpp
Normale Datei
358
server/src/hardware/interface/marklincaninterface.cpp
Normale Datei
@ -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);
|
||||
}
|
||||
96
server/src/hardware/interface/marklincaninterface.hpp
Normale Datei
96
server/src/hardware/interface/marklincaninterface.hpp
Normale Datei
@ -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
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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()]()
|
||||
{
|
||||
|
||||
@ -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
|
||||
|
||||
40
server/src/hardware/protocol/marklincan/config.hpp
Normale Datei
40
server/src/hardware/protocol/marklincan/config.hpp
Normale Datei
@ -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
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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
|
||||
61
server/src/hardware/protocol/marklincan/iohandler/iohandler.hpp
Normale Datei
61
server/src/hardware/protocol/marklincan/iohandler/iohandler.hpp
Normale Datei
@ -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
|
||||
@ -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)));
|
||||
}
|
||||
|
||||
}
|
||||
@ -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
|
||||
110
server/src/hardware/protocol/marklincan/iohandler/serialiohandler.cpp
Normale Datei
110
server/src/hardware/protocol/marklincan/iohandler/serialiohandler.cpp
Normale Datei
@ -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();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@ -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
|
||||
@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@ -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
|
||||
167
server/src/hardware/protocol/marklincan/iohandler/socketcaniohandler.cpp
Normale Datei
167
server/src/hardware/protocol/marklincan/iohandler/socketcaniohandler.cpp
Normale Datei
@ -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();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@ -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
|
||||
122
server/src/hardware/protocol/marklincan/iohandler/tcpiohandler.cpp
Normale Datei
122
server/src/hardware/protocol/marklincan/iohandler/tcpiohandler.cpp
Normale Datei
@ -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();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
52
server/src/hardware/protocol/marklincan/iohandler/tcpiohandler.hpp
Normale Datei
52
server/src/hardware/protocol/marklincan/iohandler/tcpiohandler.hpp
Normale Datei
@ -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
|
||||
134
server/src/hardware/protocol/marklincan/iohandler/udpiohandler.cpp
Normale Datei
134
server/src/hardware/protocol/marklincan/iohandler/udpiohandler.cpp
Normale Datei
@ -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();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
54
server/src/hardware/protocol/marklincan/iohandler/udpiohandler.hpp
Normale Datei
54
server/src/hardware/protocol/marklincan/iohandler/udpiohandler.hpp
Normale Datei
@ -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
|
||||
992
server/src/hardware/protocol/marklincan/kernel.cpp
Normale Datei
992
server/src/hardware/protocol/marklincan/kernel.cpp
Normale Datei
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
337
server/src/hardware/protocol/marklincan/kernel.hpp
Normale Datei
337
server/src/hardware/protocol/marklincan/kernel.hpp
Normale Datei
@ -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
|
||||
248
server/src/hardware/protocol/marklincan/locomotivelist.cpp
Normale Datei
248
server/src/hardware/protocol/marklincan/locomotivelist.cpp
Normale Datei
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
84
server/src/hardware/protocol/marklincan/locomotivelist.hpp
Normale Datei
84
server/src/hardware/protocol/marklincan/locomotivelist.hpp
Normale Datei
@ -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
|
||||
77
server/src/hardware/protocol/marklincan/message/configdata.cpp
Normale Datei
77
server/src/hardware/protocol/marklincan/message/configdata.cpp
Normale Datei
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
97
server/src/hardware/protocol/marklincan/message/configdata.hpp
Normale Datei
97
server/src/hardware/protocol/marklincan/message/configdata.hpp
Normale Datei
@ -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
|
||||
186
server/src/hardware/protocol/marklincan/message/statusdataconfig.cpp
Normale Datei
186
server/src/hardware/protocol/marklincan/message/statusdataconfig.cpp
Normale Datei
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
187
server/src/hardware/protocol/marklincan/message/statusdataconfig.hpp
Normale Datei
187
server/src/hardware/protocol/marklincan/message/statusdataconfig.hpp
Normale Datei
@ -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
|
||||
353
server/src/hardware/protocol/marklincan/messages.cpp
Normale Datei
353
server/src/hardware/protocol/marklincan/messages.cpp
Normale Datei
@ -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 {};
|
||||
}
|
||||
|
||||
}
|
||||
780
server/src/hardware/protocol/marklincan/messages.hpp
Normale Datei
780
server/src/hardware/protocol/marklincan/messages.hpp
Normale Datei
@ -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
|
||||
51
server/src/hardware/protocol/marklincan/node.hpp
Normale Datei
51
server/src/hardware/protocol/marklincan/node.hpp
Normale Datei
@ -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
|
||||
71
server/src/hardware/protocol/marklincan/settings.cpp
Normale Datei
71
server/src/hardware/protocol/marklincan/settings.cpp
Normale Datei
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
55
server/src/hardware/protocol/marklincan/settings.hpp
Normale Datei
55
server/src/hardware/protocol/marklincan/settings.hpp
Normale Datei
@ -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
|
||||
48
server/src/hardware/protocol/marklincan/uid.cpp
Normale Datei
48
server/src/hardware/protocol/marklincan/uid.cpp
Normale Datei
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
78
server/src/hardware/protocol/marklincan/uid.hpp
Normale Datei
78
server/src/hardware/protocol/marklincan/uid.hpp
Normale Datei
@ -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
|
||||
@ -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:
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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());
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
52
server/src/utils/random.hpp
Normale Datei
@ -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
|
||||
67
server/src/utils/writefile.cpp
Normale Datei
67
server/src/utils/writefile.cpp
Normale Datei
@ -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;
|
||||
}
|
||||
46
server/src/utils/writefile.hpp
Normale Datei
46
server/src/utils/writefile.hpp
Normale Datei
@ -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
47
server/src/utils/zlib.cpp
Normale Datei
@ -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
41
server/src/utils/zlib.hpp
Normale Datei
@ -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
|
||||
@ -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;
|
||||
|
||||
@ -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
75
server/thirdparty/zlib/bin/zlib1.def
vendored
Normale Datei
@ -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
3
server/thirdparty/zlib/bin/zlib1.dll
vendored
Normale Datei
@ -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
332
server/thirdparty/zlib/include/zconf.h
vendored
Normale Datei
@ -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
1357
server/thirdparty/zlib/include/zlib.h
vendored
Normale Datei
Datei-Diff unterdrückt, da er zu groß ist
Diff laden
@ -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
|
||||
|
||||
@ -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:
|
||||
|
||||
58
shared/src/traintastic/enum/marklincaninterfacetype.hpp
Normale Datei
58
shared/src/traintastic/enum/marklincaninterfacetype.hpp
Normale Datei
@ -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
|
||||
@ -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"
|
||||
}
|
||||
]
|
||||
@ -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"
|
||||
}
|
||||
]
|
||||
Laden…
x
In neuem Issue referenzieren
Einen Benutzer sperren