Merge branch 'interface-controller'
Dieser Commit ist enthalten in:
Commit
1966cf666b
14
.github/workflows/build.yml
vendored
14
.github/workflows/build.yml
vendored
@ -28,7 +28,7 @@ jobs:
|
||||
jobs: 2
|
||||
build_type: Release
|
||||
build_deb: true
|
||||
defines: "-DDEBIAN_PACKAGE_VERSION_EXTRA=~ubuntu~bionic~$CI_REF_NAME_SLUG~${{github.run_number}}~$CI_SHA_SHORT"
|
||||
defines: "-DCMAKE_CXX_COMPILER=g++-8 -DDEBIAN_PACKAGE_VERSION_EXTRA=~ubuntu~bionic~$CI_REF_NAME_SLUG~${{github.run_number}}~$CI_SHA_SHORT"
|
||||
|
||||
- name: "ubuntu_20.04"
|
||||
os: ubuntu-20.04
|
||||
@ -98,6 +98,11 @@ jobs:
|
||||
- name: Install packages
|
||||
if: startswith(matrix.config.os, 'ubuntu')
|
||||
run: sudo apt install qt5-default libqt5svg5-dev
|
||||
|
||||
# Ubuntu 18.04 only:
|
||||
- name: Install g++-8
|
||||
if: matrix.config.os == 'ubuntu-18.04'
|
||||
run: sudo apt install g++-8
|
||||
|
||||
# All:
|
||||
- name: Create Build Environment
|
||||
@ -173,7 +178,7 @@ jobs:
|
||||
jobs: 2
|
||||
build_type: Release
|
||||
build_deb: true
|
||||
defines: "-DDEBIAN_PACKAGE_VERSION_EXTRA=~ubuntu~bionic~$CI_REF_NAME_SLUG~${{github.run_number}}~$CI_SHA_SHORT"
|
||||
defines: "-DCMAKE_CXX_COMPILER=g++-8 -DDEBIAN_PACKAGE_VERSION_EXTRA=~ubuntu~bionic~$CI_REF_NAME_SLUG~${{github.run_number}}~$CI_SHA_SHORT"
|
||||
ccov: false
|
||||
|
||||
- name: "ubuntu_20.04"
|
||||
@ -242,6 +247,11 @@ jobs:
|
||||
if: startswith(matrix.config.os, 'ubuntu')
|
||||
run: sudo apt install liblua5.3-dev lcov libarchive-dev
|
||||
|
||||
# Ubuntu 18.04 only:
|
||||
- name: Install g++-8
|
||||
if: matrix.config.os == 'ubuntu-18.04'
|
||||
run: sudo apt install g++-8
|
||||
|
||||
# All:
|
||||
- name: Create Build Environment
|
||||
# Some projects don't allow in-source building, so create a separate build directory
|
||||
|
||||
@ -225,11 +225,10 @@ MainWindow::MainWindow(QWidget* parent) :
|
||||
|
||||
m_menuObjects = menuBar()->addMenu(Locale::tr("qtapp.mainmenu:objects"));
|
||||
menu = m_menuObjects->addMenu(Theme::getIcon("hardware"), Locale::tr("qtapp.mainmenu:hardware"));
|
||||
menu->addAction(Locale::tr("world:command_stations") + "...", [this](){ showObject("world.command_stations", Locale::tr("world:command_stations")); });
|
||||
menu->addAction(Locale::tr("world:interfaces") + "...", [this](){ showObject("world.interfaces", Locale::tr("world:interfaces")); });
|
||||
menu->addAction(Locale::tr("world:decoders") + "...", [this](){ showObject("world.decoders", Locale::tr("world:decoders")); });
|
||||
menu->addAction(Locale::tr("world:inputs") + "...", [this](){ showObject("world.inputs", Locale::tr("world:inputs")); });
|
||||
menu->addAction(Locale::tr("world:outputs") + "...", [this](){ showObject("world.outputs", Locale::tr("world:outputs")); });
|
||||
menu->addAction(Locale::tr("world:controllers") + "...", [this](){ showObject("world.controllers", Locale::tr("world:controllers")); });
|
||||
m_menuObjects->addAction(Locale::tr("world:boards") + "...", [this](){ showObject("world.boards", Locale::tr("world:boards")); });
|
||||
m_menuObjects->addAction(Locale::tr("world:clock") + "...", [this](){ showObject("world.clock", Locale::tr("world:clock")); });
|
||||
m_menuObjects->addAction(Locale::tr("world:trains") + "...", [this](){ showObject("world.trains", Locale::tr("world:trains")); });
|
||||
|
||||
@ -476,9 +476,9 @@ ObjectPtr Connection::readObject(const Message& message)
|
||||
else
|
||||
{
|
||||
const QString classId = QString::fromLatin1(message.read<QByteArray>());
|
||||
if(classId.startsWith(InputMonitor::classIdPrefix))
|
||||
if(classId == InputMonitor::classId)
|
||||
p = new InputMonitor(shared_from_this(), handle, classId);
|
||||
else if(classId.startsWith(OutputKeyboard::classIdPrefix))
|
||||
else if(classId == OutputKeyboard::classId)
|
||||
p = new OutputKeyboard(shared_from_this(), handle, classId);
|
||||
else if(classId.startsWith(OutputMap::classIdPrefix))
|
||||
p = new OutputMap(shared_from_this(), handle, classId);
|
||||
|
||||
@ -34,7 +34,7 @@ class InputMonitor final : public Object
|
||||
int m_requestId;
|
||||
|
||||
public:
|
||||
inline static const QString classIdPrefix = QStringLiteral("input_monitor.");
|
||||
inline static const QString classId = QStringLiteral("input_monitor");
|
||||
|
||||
InputMonitor(const std::shared_ptr<Connection>& connection, Handle handle, const QString& classId);
|
||||
~InputMonitor() final;
|
||||
|
||||
@ -34,7 +34,7 @@ class OutputKeyboard final : public Object
|
||||
int m_requestId;
|
||||
|
||||
public:
|
||||
inline static const QString classIdPrefix = QStringLiteral("output_keyboard.");
|
||||
inline static const QString classId = QStringLiteral("output_keyboard");
|
||||
|
||||
OutputKeyboard(std::shared_ptr<Connection> connection, Handle handle, const QString& classId);
|
||||
~OutputKeyboard() final;
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2021 Reinder Feenstra
|
||||
* Copyright (C) 2019-2022 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -28,6 +28,7 @@
|
||||
#include <traintastic/enum/decoderprotocol.hpp>
|
||||
#include <traintastic/enum/direction.hpp>
|
||||
#include <traintastic/enum/lengthunit.hpp>
|
||||
#include <traintastic/enum/loconetinterfacetype.hpp>
|
||||
#include <traintastic/enum/loconetcommandstation.hpp>
|
||||
#include <traintastic/enum/loconetserialinterface.hpp>
|
||||
#include <traintastic/enum/outputaction.hpp>
|
||||
@ -39,7 +40,8 @@
|
||||
#include <traintastic/enum/weightunit.hpp>
|
||||
#include <traintastic/enum/worldscale.hpp>
|
||||
#include <traintastic/enum/xpressnetcommandstation.hpp>
|
||||
#include <traintastic/enum/xpressnetserialinterface.hpp>
|
||||
#include <traintastic/enum/xpressnetinterfacetype.hpp>
|
||||
#include <traintastic/enum/xpressnetserialinterfacetype.hpp>
|
||||
#include "../settings/boardsettings.hpp"
|
||||
|
||||
#define GET_ENUM_VALUES(_type) \
|
||||
@ -73,6 +75,7 @@ QString translateEnum(const QString& enumName, qint64 value)
|
||||
TRANSLATE_ENUM(Direction)
|
||||
TRANSLATE_ENUM(LengthUnit)
|
||||
TRANSLATE_ENUM(LocoNetCommandStation)
|
||||
TRANSLATE_ENUM(LocoNetInterfaceType)
|
||||
TRANSLATE_ENUM(LocoNetSerialInterface)
|
||||
TRANSLATE_ENUM(OutputAction)
|
||||
TRANSLATE_ENUM(SensorType)
|
||||
@ -83,7 +86,8 @@ QString translateEnum(const QString& enumName, qint64 value)
|
||||
TRANSLATE_ENUM(WeightUnit)
|
||||
TRANSLATE_ENUM(WorldScale)
|
||||
TRANSLATE_ENUM(XpressNetCommandStation)
|
||||
TRANSLATE_ENUM(XpressNetSerialInterface)
|
||||
TRANSLATE_ENUM(XpressNetInterfaceType)
|
||||
TRANSLATE_ENUM(XpressNetSerialInterfaceType)
|
||||
TRANSLATE_ENUM(BoardSettings::ColorScheme)
|
||||
return enumName + "@" + QString::number(value);
|
||||
}
|
||||
|
||||
@ -42,8 +42,6 @@ QWidget* createWidgetIfCustom(const ObjectPtr& object, QWidget* parent)
|
||||
return new ObjectListWidget(object, parent); // todo remove
|
||||
else if(classId == "decoder_list")
|
||||
return new ThrottleObjectListWidget(object, parent); // todo remove
|
||||
else if(classId == "input_list")
|
||||
return new ObjectListWidget(object, parent); // todo remove
|
||||
else if(classId == "controller_list")
|
||||
return new ObjectListWidget(object, parent); // todo remove
|
||||
else if(classId == "rail_vehicle_list")
|
||||
@ -52,6 +50,8 @@ QWidget* createWidgetIfCustom(const ObjectPtr& object, QWidget* parent)
|
||||
return new ObjectListWidget(object, parent); // todo remove
|
||||
else if(classId == "world_list")
|
||||
return new ObjectListWidget(object, parent);
|
||||
else if(object->classId().startsWith("list."))
|
||||
return new ObjectListWidget(object, parent);
|
||||
else if(classId == "lua.script")
|
||||
return new LuaScriptEditWidget(object, parent);
|
||||
else if(auto outputMap = std::dynamic_pointer_cast<OutputMap>(object))
|
||||
@ -66,8 +66,6 @@ QWidget* createWidget(const ObjectPtr& object, QWidget* parent)
|
||||
{
|
||||
if(QWidget* widget = createWidgetIfCustom(object, parent))
|
||||
return widget;
|
||||
else if(object->classId().startsWith("list."))
|
||||
return new ObjectListWidget(object, parent);
|
||||
else if(auto inputMonitor = std::dynamic_pointer_cast<InputMonitor>(object))
|
||||
return new InputMonitorWidget(inputMonitor, parent);
|
||||
else if(auto outputKeyboard = std::dynamic_pointer_cast<OutputKeyboard>(object))
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2021 Reinder Feenstra
|
||||
* Copyright (C) 2019-2022 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -49,12 +49,16 @@
|
||||
|
||||
ObjectListWidget::ObjectListWidget(const ObjectPtr& object, QWidget* parent) :
|
||||
QWidget(parent),
|
||||
m_requestIdInputMonitor{Connection::invalidRequestId},
|
||||
m_requestIdOutputKeyboard{Connection::invalidRequestId},
|
||||
m_object{object},
|
||||
m_toolbar{new QToolBar()},
|
||||
m_buttonAdd{nullptr},
|
||||
m_actionAdd{nullptr},
|
||||
m_actionEdit{nullptr},
|
||||
m_actionDelete{nullptr},
|
||||
m_actionInputMonitor{nullptr},
|
||||
m_actionOutputKeyboard{nullptr},
|
||||
m_tableWidget{new TableWidget()}
|
||||
{
|
||||
m_tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
@ -188,6 +192,43 @@ ObjectListWidget::ObjectListWidget(const ObjectPtr& object, QWidget* parent) :
|
||||
m_actionDelete->setForceDisabled(true);
|
||||
m_toolbar->addAction(m_actionDelete);
|
||||
}
|
||||
|
||||
if(Method* method = m_object->getMethod("input_monitor"))
|
||||
{
|
||||
m_actionInputMonitor = new MethodAction(Theme::getIcon("input_monitor"), *method,
|
||||
[this]()
|
||||
{
|
||||
m_requestIdInputMonitor = m_actionInputMonitor->method().call(
|
||||
[this](const ObjectPtr& inputMonitor, Message::ErrorCode)
|
||||
{
|
||||
if(inputMonitor)
|
||||
MainWindow::instance->showObject(inputMonitor);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if(Method* method = m_object->getMethod("output_keyboard"))
|
||||
{
|
||||
m_actionOutputKeyboard = new MethodAction(Theme::getIcon("output_keyboard"), *method,
|
||||
[this]()
|
||||
{
|
||||
m_requestIdOutputKeyboard = m_actionOutputKeyboard->method().call(
|
||||
[this](const ObjectPtr& outputKeyboard, Message::ErrorCode)
|
||||
{
|
||||
if(outputKeyboard)
|
||||
MainWindow::instance->showObject(outputKeyboard);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if(m_actionInputMonitor || m_actionOutputKeyboard)
|
||||
{
|
||||
m_toolbar->addSeparator();
|
||||
if(m_actionInputMonitor)
|
||||
m_toolbar->addAction(m_actionInputMonitor);
|
||||
if(m_actionOutputKeyboard)
|
||||
m_toolbar->addAction(m_actionOutputKeyboard);
|
||||
}
|
||||
}
|
||||
|
||||
ObjectListWidget::~ObjectListWidget()
|
||||
|
||||
@ -36,15 +36,18 @@ class ObjectListWidget : public QWidget
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
//const QString m_id;
|
||||
int m_requestId;
|
||||
int m_requestIdAdd;
|
||||
int m_requestIdInputMonitor;
|
||||
int m_requestIdOutputKeyboard;
|
||||
ObjectPtr m_object;
|
||||
QToolBar* m_toolbar;
|
||||
QToolButton* m_buttonAdd;
|
||||
QAction* m_actionAdd;
|
||||
QAction* m_actionEdit;
|
||||
MethodAction* m_actionDelete;
|
||||
MethodAction* m_actionInputMonitor;
|
||||
MethodAction* m_actionOutputKeyboard;
|
||||
TableWidget* m_tableWidget;
|
||||
|
||||
void tableSelectionChanged();
|
||||
|
||||
@ -91,7 +91,7 @@ ThrottleWidget::ThrottleWidget(ObjectPtr object, QWidget* parent)
|
||||
});
|
||||
}
|
||||
|
||||
if(m_emergencyStop = m_object->getProperty("emergency_stop"))
|
||||
if((m_emergencyStop = m_object->getProperty("emergency_stop")))
|
||||
{
|
||||
m_speedoMeter->setEStop(m_emergencyStop->toBool());
|
||||
connect(m_emergencyStop, &AbstractProperty::valueChangedBool, m_speedoMeter, &SpeedoMeterWidget::setEStop);
|
||||
@ -100,7 +100,7 @@ ThrottleWidget::ThrottleWidget(ObjectPtr object, QWidget* parent)
|
||||
if(auto* p = dynamic_cast<ObjectProperty*>(m_object->getProperty("functions")))
|
||||
{
|
||||
m_functionsRequestId = m_object->connection()->getObject(p->objectId(),
|
||||
[this](const ObjectPtr& functions, Message::ErrorCode ec)
|
||||
[this](const ObjectPtr& functions, Message::ErrorCode /*ec*/)
|
||||
{
|
||||
m_functionsRequestId = Connection::invalidRequestId;
|
||||
|
||||
@ -113,7 +113,7 @@ ThrottleWidget::ThrottleWidget(ObjectPtr object, QWidget* parent)
|
||||
for(const QString& id : *items)
|
||||
{
|
||||
const int functionRequestId = functions->connection()->getObject(id,
|
||||
[this, i](const ObjectPtr& function, Message::ErrorCode ec)
|
||||
[this, i](const ObjectPtr& function, Message::ErrorCode /*ec*/)
|
||||
{
|
||||
m_functionRequestIds.erase(i);
|
||||
|
||||
@ -263,9 +263,7 @@ ThrottleFunctionButton* ThrottleWidget::getFunctionButton(DecoderFunctionFunctio
|
||||
auto it = std::find_if(m_functionButtons.begin(), m_functionButtons.end(),
|
||||
[function](const auto* btn)
|
||||
{
|
||||
auto n = btn->number();
|
||||
auto f = btn->function();
|
||||
return f == function;
|
||||
return btn->function() == function;
|
||||
});
|
||||
return (it != m_functionButtons.end()) ? *it : nullptr;
|
||||
}
|
||||
|
||||
@ -77,10 +77,6 @@ file(GLOB SOURCES
|
||||
"src/core/*.hpp"
|
||||
"src/core/*.cpp"
|
||||
"src/enum/*.hpp"
|
||||
"src/hardware/commandstation/*.hpp"
|
||||
"src/hardware/commandstation/*.cpp"
|
||||
"src/hardware/controller/*.hpp"
|
||||
"src/hardware/controller/*.cpp"
|
||||
"src/hardware/decoder/*.hpp"
|
||||
"src/hardware/decoder/*.cpp"
|
||||
"src/hardware/input/*.hpp"
|
||||
@ -91,6 +87,8 @@ file(GLOB SOURCES
|
||||
"src/hardware/input/map/*.cpp"
|
||||
"src/hardware/input/monitor/*.hpp"
|
||||
"src/hardware/input/monitor/*.cpp"
|
||||
"src/hardware/interface/*.hpp"
|
||||
"src/hardware/interface/*.cpp"
|
||||
"src/hardware/output/*.hpp"
|
||||
"src/hardware/output/*.cpp"
|
||||
"src/hardware/output/keyboard/*.hpp"
|
||||
@ -101,12 +99,26 @@ file(GLOB SOURCES
|
||||
"src/hardware/output/map/*.cpp"
|
||||
"src/hardware/protocol/dccplusplus/*.hpp"
|
||||
"src/hardware/protocol/dccplusplus/*.cpp"
|
||||
"src/hardware/protocol/dccplusplus/iohandler/*.hpp"
|
||||
"src/hardware/protocol/dccplusplus/iohandler/*.cpp"
|
||||
"src/hardware/protocol/ecos/*.hpp"
|
||||
"src/hardware/protocol/ecos/*.cpp"
|
||||
"src/hardware/protocol/ecos/iohandler/*.hpp"
|
||||
"src/hardware/protocol/ecos/iohandler/*.cpp"
|
||||
"src/hardware/protocol/ecos/object/*.hpp"
|
||||
"src/hardware/protocol/ecos/object/*.cpp"
|
||||
"src/hardware/protocol/loconet/*.hpp"
|
||||
"src/hardware/protocol/loconet/*.cpp"
|
||||
"src/hardware/protocol/loconet/iohandler/*.hpp"
|
||||
"src/hardware/protocol/loconet/iohandler/*.cpp"
|
||||
"src/hardware/protocol/xpressnet/*.hpp"
|
||||
"src/hardware/protocol/xpressnet/*.cpp"
|
||||
"src/hardware/protocol/xpressnet/iohandler/*.hpp"
|
||||
"src/hardware/protocol/xpressnet/iohandler/*.cpp"
|
||||
"src/hardware/protocol/z21/*.hpp"
|
||||
"src/hardware/protocol/z21/*.cpp"
|
||||
"src/hardware/protocol/z21/iohandler/*.hpp"
|
||||
"src/hardware/protocol/z21/iohandler/*.cpp"
|
||||
"src/log/*.hpp"
|
||||
"src/log/*.cpp"
|
||||
"src/train/*.hpp"
|
||||
@ -212,29 +224,6 @@ else()
|
||||
add_definitions(-DDISABLE_LUA_SCRIPTING)
|
||||
endif()
|
||||
|
||||
option(USB_XPRESSNET "USB XpressNet interface/controller support" ON)
|
||||
message(STATUS "USB XpressNet interface/controller support: ${USB_XPRESSNET}")
|
||||
if(ENABLE_USB_XPRESSNET_INTERFACE)
|
||||
#pkg_check_modules(USBXPRESSNET REQUIRED IMPORTED_TARGET usbxpressnet)
|
||||
find_path(USBXPRESSNET_INCLUDE_DIR usbxpressnet.h)
|
||||
#message(${USBXPRESSNET_INCLUDE_DIR})
|
||||
find_library(USBXPRESSNET_LIB usbxpressnet)
|
||||
#message(${USBXPRESSNET_LIB})
|
||||
#if(NOT USBXPRESSNET_LIB)
|
||||
# message(FATAL_ERROR "usbxpressnet library not found")
|
||||
#endif()
|
||||
|
||||
add_definitions(-DUSB_XPRESSNET)
|
||||
|
||||
target_include_directories(traintastic-server PRIVATE ${USBXPRESSNET_INCLUDE_DIR})
|
||||
target_link_libraries(traintastic-server PRIVATE ${USBXPRESSNET_LIB})
|
||||
|
||||
target_include_directories(traintastic-server-test PRIVATE ${USBXPRESSNET_INCLUDE_DIR})
|
||||
target_link_libraries(traintastic-server-test PRIVATE ${USBXPRESSNET_LIB})
|
||||
else()
|
||||
list(FILTER SOURCES EXCLUDE REGEX ".*usbxpressnet(interface|controller)\.(hpp|cpp)$")
|
||||
endif()
|
||||
|
||||
### OPTIONS END ###
|
||||
|
||||
target_sources(traintastic-server PRIVATE ${SOURCES})
|
||||
|
||||
52
server/simulate/ecossimulator.py
Ausführbare Datei
52
server/simulate/ecossimulator.py
Ausführbare Datei
@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import socket
|
||||
import re
|
||||
|
||||
ADDRESS = '127.0.0.1'
|
||||
PORT = 15471
|
||||
|
||||
OBJECTID_ECOS = 1
|
||||
|
||||
|
||||
def process(request):
|
||||
print('RX:\n' + request)
|
||||
m = re.match(r'^(queryObjects|set|get|create|delete|request|release)\(\s*([0-9]+)\s*(.*)\)$', request)
|
||||
if m is not None:
|
||||
command = m.group(1)
|
||||
objectId = int(m.group(2))
|
||||
args = [s for s in re.split(r'\s*,\s*', m.group(3)) if s]
|
||||
|
||||
if objectId == OBJECTID_ECOS:
|
||||
if command == 'get':
|
||||
if args[0] == 'info':
|
||||
return \
|
||||
'<REPLY get(1, info)>\n' + \
|
||||
'1 ECoS\n' + \
|
||||
'1 ProtocolVersion[0.1]\n' + \
|
||||
'1 ApplicationVersion[1.0.1]\n' + \
|
||||
'1 HardwareVersion[1.3]\n' + \
|
||||
'<END 0 (OK)>\n'
|
||||
|
||||
return None
|
||||
|
||||
|
||||
print('Traintastic ECoS simulator')
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
s.bind((ADDRESS, PORT))
|
||||
s.listen()
|
||||
print('Listening at {:s}:{:d}'.format(ADDRESS, PORT))
|
||||
conn, addr = s.accept()
|
||||
with conn:
|
||||
print('Connection from {:s}:{:d}'.format(addr[0], addr[1]))
|
||||
while True:
|
||||
try:
|
||||
for request in conn.recv(1024).decode('utf-8').split('\n'):
|
||||
if request != '':
|
||||
response = process(request)
|
||||
if response is not None:
|
||||
print('TX:\n' + response)
|
||||
conn.sendall(response.encode('utf-8'))
|
||||
except ConnectionError:
|
||||
print('Connection lost')
|
||||
break
|
||||
@ -62,6 +62,12 @@ struct Attributes
|
||||
item.setAttribute(AttributeName::Enabled, value);
|
||||
}
|
||||
|
||||
static inline void setEnabled(std::initializer_list<std::reference_wrapper<InterfaceItem>> items, bool value)
|
||||
{
|
||||
for(auto& item : items)
|
||||
item.get().setAttribute(AttributeName::Enabled, value);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static inline void addMinMax(Property<T>& property, T min, T max)
|
||||
{
|
||||
@ -106,6 +112,22 @@ struct Attributes
|
||||
property.setAttribute(AttributeName::Max, convertUnit(value, unit, property.unit()));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static inline void setMinMax(Property<T>& property, T min, T max)
|
||||
{
|
||||
static_assert(std::is_integral_v<T> || std::is_floating_point_v<T>);
|
||||
property.setAttribute(AttributeName::Min, min);
|
||||
property.setAttribute(AttributeName::Max, max);
|
||||
}
|
||||
|
||||
template<class T, class Unit>
|
||||
static inline void setMinMax(UnitProperty<T, Unit>& property, T min, T max, Unit unit)
|
||||
{
|
||||
static_assert(std::is_floating_point_v<T>);
|
||||
property.setAttribute(AttributeName::Min, convertUnit(min, unit, property.unit()));
|
||||
property.setAttribute(AttributeName::Max, convertUnit(max, unit, property.unit()));
|
||||
}
|
||||
|
||||
static inline void addVisible(InterfaceItem& item, bool value)
|
||||
{
|
||||
item.addAttribute(AttributeName::Visible, value);
|
||||
|
||||
@ -1,140 +0,0 @@
|
||||
/**
|
||||
* server/src/core/commandstationproperty.cpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2020 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "commandstationproperty.hpp"
|
||||
#include "../hardware/commandstation/commandstation.hpp"
|
||||
|
||||
using T = CommandStation;
|
||||
|
||||
CommandStationProperty::CommandStationProperty(Object* object, const std::string& name, const std::shared_ptr<T>& value, PropertyFlags flags) :
|
||||
AbstractObjectProperty(object, name, flags),
|
||||
m_value{value}
|
||||
{
|
||||
}
|
||||
|
||||
CommandStationProperty::CommandStationProperty(Object* object, const std::string& name, std::nullptr_t, PropertyFlags flags) :
|
||||
CommandStationProperty(object, name, std::shared_ptr<T>(), flags)
|
||||
{
|
||||
}
|
||||
|
||||
CommandStationProperty::CommandStationProperty(Object* object, const std::string& name, const std::shared_ptr<T>& value, PropertyFlags flags, OnSet onSet) :
|
||||
CommandStationProperty(object, name, value, flags)
|
||||
{
|
||||
m_onSet = onSet;
|
||||
}
|
||||
|
||||
CommandStationProperty::CommandStationProperty(Object* object, const std::string& name, std::nullptr_t, PropertyFlags flags, OnSet onSet) :
|
||||
CommandStationProperty(object, name, std::shared_ptr<T>(), flags, onSet)
|
||||
{
|
||||
}
|
||||
|
||||
const std::shared_ptr<T>& CommandStationProperty::value() const
|
||||
{
|
||||
return m_value;
|
||||
}
|
||||
|
||||
void CommandStationProperty::setValue(const std::shared_ptr<T>& value)
|
||||
{
|
||||
assert(isWriteable());
|
||||
if(m_value == value)
|
||||
return;
|
||||
else if(!isWriteable())
|
||||
throw not_writable_error();
|
||||
else if(!m_onSet || m_onSet(value))
|
||||
{
|
||||
m_value = value;
|
||||
changed();
|
||||
}
|
||||
else
|
||||
throw invalid_value_error();
|
||||
}
|
||||
|
||||
void CommandStationProperty::setValueInternal(const std::shared_ptr<T>& value)
|
||||
{
|
||||
if(m_value != value)
|
||||
{
|
||||
m_value = value;
|
||||
changed();
|
||||
}
|
||||
}
|
||||
|
||||
const T* CommandStationProperty::operator ->() const
|
||||
{
|
||||
return m_value.get();
|
||||
}
|
||||
|
||||
T* CommandStationProperty::operator ->()
|
||||
{
|
||||
return m_value.get();
|
||||
}
|
||||
|
||||
const T& CommandStationProperty::operator *() const
|
||||
{
|
||||
return *m_value;
|
||||
}
|
||||
|
||||
T& CommandStationProperty::operator *()
|
||||
{
|
||||
return *m_value;
|
||||
}
|
||||
|
||||
CommandStationProperty::operator bool()
|
||||
{
|
||||
return m_value.operator bool();
|
||||
}
|
||||
|
||||
CommandStationProperty& CommandStationProperty::operator =(const std::shared_ptr<T>& value)
|
||||
{
|
||||
setValue(value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
ObjectPtr CommandStationProperty::toObject() const
|
||||
{
|
||||
return std::dynamic_pointer_cast<Object>(m_value);
|
||||
}
|
||||
|
||||
void CommandStationProperty::fromObject(const ObjectPtr& value)
|
||||
{
|
||||
if(value)
|
||||
{
|
||||
if(std::shared_ptr<T> v = std::dynamic_pointer_cast<T>(value))
|
||||
setValue(v);
|
||||
else
|
||||
throw conversion_error();
|
||||
}
|
||||
else
|
||||
setValue(nullptr);
|
||||
}
|
||||
|
||||
void CommandStationProperty::load(const ObjectPtr& value)
|
||||
{
|
||||
if(value)
|
||||
{
|
||||
if(std::shared_ptr<T> v = std::dynamic_pointer_cast<T>(value))
|
||||
m_value = v;
|
||||
else
|
||||
throw conversion_error();
|
||||
}
|
||||
else
|
||||
m_value.reset();
|
||||
}
|
||||
@ -1,68 +0,0 @@
|
||||
/**
|
||||
* server/src/core/commandstationproperty.hpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2020 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef TRAINTASTIC_SERVER_CORE_COMMANDSTATIONPROPERTY_HPP
|
||||
#define TRAINTASTIC_SERVER_CORE_COMMANDSTATIONPROPERTY_HPP
|
||||
|
||||
#include "abstractobjectproperty.hpp"
|
||||
#include <functional>
|
||||
|
||||
class CommandStation;
|
||||
|
||||
//! workaround for ObjectProperty<CommandStation>
|
||||
class CommandStationProperty : public AbstractObjectProperty
|
||||
{
|
||||
public:
|
||||
using OnSet = std::function<bool(const std::shared_ptr<CommandStation>& value)>;
|
||||
|
||||
protected:
|
||||
std::shared_ptr<CommandStation> m_value;
|
||||
OnSet m_onSet;
|
||||
|
||||
public:
|
||||
CommandStationProperty(Object* object, const std::string& name, const std::shared_ptr<CommandStation>& value, PropertyFlags flags);
|
||||
CommandStationProperty(Object* object, const std::string& name, std::nullptr_t, PropertyFlags flags);
|
||||
CommandStationProperty(Object* object, const std::string& name, const std::shared_ptr<CommandStation>& value, PropertyFlags flags, OnSet onSet);
|
||||
CommandStationProperty(Object* object, const std::string& name, std::nullptr_t, PropertyFlags flags, OnSet onSet);
|
||||
|
||||
const std::shared_ptr<CommandStation>& value() const;
|
||||
|
||||
void setValue(const std::shared_ptr<CommandStation>& value);
|
||||
void setValueInternal(const std::shared_ptr<CommandStation>& value);
|
||||
|
||||
const CommandStation* operator ->() const;
|
||||
CommandStation* operator ->();
|
||||
|
||||
const CommandStation& operator *() const;
|
||||
CommandStation& operator *();
|
||||
|
||||
operator bool();
|
||||
|
||||
CommandStationProperty& operator =(const std::shared_ptr<CommandStation>& value);
|
||||
|
||||
ObjectPtr toObject() const final;
|
||||
void fromObject(const ObjectPtr& value) final;
|
||||
|
||||
void load(const ObjectPtr& value) final;
|
||||
};
|
||||
|
||||
#endif
|
||||
51
server/src/core/controllerlist.hpp
Normale Datei
51
server/src/core/controllerlist.hpp
Normale Datei
@ -0,0 +1,51 @@
|
||||
/**
|
||||
* server/src/core/controllerlist.hpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef TRAINTASTIC_SERVER_CORE_CONTROLLERLIST_HPP
|
||||
#define TRAINTASTIC_SERVER_CORE_CONTROLLERLIST_HPP
|
||||
|
||||
#include "controllerlistbase.hpp"
|
||||
|
||||
template<class T>
|
||||
class ControllerList : public ControllerListBase
|
||||
{
|
||||
public:
|
||||
ControllerList(Object& _parent, const std::string& parentPropertyName)
|
||||
: ControllerListBase(_parent, parentPropertyName)
|
||||
{
|
||||
}
|
||||
|
||||
inline void add(const std::shared_ptr<T>& controller)
|
||||
{
|
||||
if(ObjectPtr object = std::dynamic_pointer_cast<Object>(controller))
|
||||
ControllerListBase::add(std::move(object));
|
||||
else
|
||||
assert(false);
|
||||
}
|
||||
|
||||
inline void remove(const std::shared_ptr<T>& controller)
|
||||
{
|
||||
ControllerListBase::remove(std::dynamic_pointer_cast<Object>(controller));
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
85
server/src/core/controllerlistbase.cpp
Normale Datei
85
server/src/core/controllerlistbase.cpp
Normale Datei
@ -0,0 +1,85 @@
|
||||
/**
|
||||
* server/src/core/controllerlistbase.cpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2021-2022 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "controllerlistbase.hpp"
|
||||
#include "controllerlistbasetablemodel.hpp"
|
||||
|
||||
ControllerListBase::ControllerListBase(Object& _parent, const std::string& parentPropertyName)
|
||||
: AbstractObjectList(_parent, parentPropertyName)
|
||||
, length{this, "length", 0, PropertyFlags::ReadOnly}
|
||||
{
|
||||
}
|
||||
|
||||
TableModelPtr ControllerListBase::getModel()
|
||||
{
|
||||
return std::make_shared<ControllerListBaseTableModel>(*this);
|
||||
}
|
||||
|
||||
ObjectPtr ControllerListBase::getObject(uint32_t index)
|
||||
{
|
||||
assert(index < m_items.size());
|
||||
return std::static_pointer_cast<Object>(m_items[index]);
|
||||
}
|
||||
|
||||
void ControllerListBase::add(ObjectPtr controller)
|
||||
{
|
||||
assert(controller);
|
||||
m_propertyChanged.emplace(controller.get(), controller->propertyChanged.connect(
|
||||
[this](BaseProperty& property)
|
||||
{
|
||||
if(!m_models.empty() && ControllerListBaseTableModel::isListedProperty(property.name()))
|
||||
{
|
||||
ObjectPtr obj = property.object().shared_from_this();
|
||||
const uint32_t rows = static_cast<uint32_t>(m_items.size());
|
||||
for(uint32_t row = 0; row < rows; row++)
|
||||
if(m_items[row] == obj)
|
||||
{
|
||||
for(auto& model : m_models)
|
||||
model->propertyChanged(property, row);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}));
|
||||
m_items.emplace_back(std::move(controller));
|
||||
rowCountChanged();
|
||||
}
|
||||
|
||||
void ControllerListBase::remove(const ObjectPtr& controller)
|
||||
{
|
||||
assert(controller);
|
||||
auto it = std::find(m_items.begin(), m_items.end(), controller);
|
||||
if(it != m_items.end())
|
||||
{
|
||||
m_propertyChanged.erase(controller.get());
|
||||
m_items.erase(it);
|
||||
rowCountChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void ControllerListBase::rowCountChanged()
|
||||
{
|
||||
const auto size = m_items.size();
|
||||
length.setValueInternal(static_cast<uint32_t>(size));
|
||||
for(auto& model : m_models)
|
||||
model->setRowCount(static_cast<uint32_t>(size));
|
||||
}
|
||||
|
||||
61
server/src/core/controllerlistbase.hpp
Normale Datei
61
server/src/core/controllerlistbase.hpp
Normale Datei
@ -0,0 +1,61 @@
|
||||
/**
|
||||
* server/src/core/controllerlistbase.hpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef TRAINTASTIC_SERVER_CORE_CONTROLLERLISTBASE_HPP
|
||||
#define TRAINTASTIC_SERVER_CORE_CONTROLLERLISTBASE_HPP
|
||||
|
||||
#include "abstractobjectlist.hpp"
|
||||
#include "property.hpp"
|
||||
|
||||
class ControllerListBaseTableModel;
|
||||
|
||||
class ControllerListBase : public AbstractObjectList
|
||||
{
|
||||
friend class ControllerListBaseTableModel;
|
||||
|
||||
CLASS_ID("list.controller")
|
||||
|
||||
private:
|
||||
std::vector<ObjectPtr> m_items;
|
||||
std::unordered_map<Object*, boost::signals2::connection> m_propertyChanged;
|
||||
std::vector<ControllerListBaseTableModel*> m_models;
|
||||
|
||||
void rowCountChanged();
|
||||
|
||||
protected:
|
||||
std::vector<ObjectPtr> getItems() const final { return m_items; }
|
||||
void setItems(const std::vector<ObjectPtr>& items) final { m_items = items; }
|
||||
|
||||
void add(ObjectPtr object);
|
||||
void remove(const ObjectPtr& object);
|
||||
|
||||
public:
|
||||
Property<uint32_t> length;
|
||||
|
||||
ControllerListBase(Object& _parent, const std::string& parentPropertyName);
|
||||
|
||||
TableModelPtr getModel() final;
|
||||
|
||||
ObjectPtr getObject(uint32_t index) final;
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -1,5 +1,5 @@
|
||||
/**
|
||||
* server/src/hardware/input/xpressnetlisttablemodel.cpp
|
||||
* server/src/core/controllerlistbasetablemodel.cpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
@ -20,35 +20,48 @@
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "xpressnetlisttablemodel.hpp"
|
||||
#include "xpressnetlist.hpp"
|
||||
#include "../../../utils/displayname.hpp"
|
||||
#include "controllerlistbasetablemodel.hpp"
|
||||
#include "controllerlistbase.hpp"
|
||||
#include "../utils/displayname.hpp"
|
||||
|
||||
constexpr uint32_t columnId = 0;
|
||||
constexpr uint32_t columnName = 1;
|
||||
|
||||
bool XpressNetListTableModel::isListedProperty(const std::string& name)
|
||||
bool ControllerListBaseTableModel::isListedProperty(const std::string& name)
|
||||
{
|
||||
return name == "id";
|
||||
return
|
||||
name == "id" ||
|
||||
name == "name";
|
||||
}
|
||||
|
||||
XpressNetListTableModel::XpressNetListTableModel(XpressNetList& list) :
|
||||
ObjectListTableModel<XpressNet::XpressNet>(list)
|
||||
ControllerListBaseTableModel::ControllerListBaseTableModel(ControllerListBase& list)
|
||||
: TableModel()
|
||||
, m_list{list.shared_ptr<ControllerListBase>()}
|
||||
{
|
||||
list.m_models.push_back(this);
|
||||
setRowCount(static_cast<uint32_t>(list.m_items.size()));
|
||||
|
||||
setColumnHeaders({
|
||||
DisplayName::Object::id
|
||||
DisplayName::Object::id,
|
||||
DisplayName::Object::name,
|
||||
});
|
||||
}
|
||||
|
||||
std::string XpressNetListTableModel::getText(uint32_t column, uint32_t row) const
|
||||
std::string ControllerListBaseTableModel::getText(uint32_t column, uint32_t row) const
|
||||
{
|
||||
if(row < rowCount())
|
||||
{
|
||||
const XpressNet::XpressNet& xpressnet = getItem(row);
|
||||
const Object& object = *m_list->m_items[row];
|
||||
|
||||
switch(column)
|
||||
{
|
||||
case columnId:
|
||||
return xpressnet.getObjectId();
|
||||
return object.getObjectId();
|
||||
|
||||
case columnName:
|
||||
if(const auto* property = object.getProperty("name"))
|
||||
return property->toString();
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(false);
|
||||
@ -59,8 +72,10 @@ std::string XpressNetListTableModel::getText(uint32_t column, uint32_t row) cons
|
||||
return "";
|
||||
}
|
||||
|
||||
void XpressNetListTableModel::propertyChanged(BaseProperty& property, uint32_t row)
|
||||
void ControllerListBaseTableModel::propertyChanged(BaseProperty& property, uint32_t row)
|
||||
{
|
||||
if(property.name() == "id")
|
||||
changed(row, columnId);
|
||||
else if(property.name() == "name")
|
||||
changed(row, columnName);
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
/**
|
||||
* server/src/hardware/protocol/xpressnet/xpressnetlisttablemodel.hpp
|
||||
* server/src/core/controllerlistbasetablemodel.hpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
@ -20,27 +20,28 @@
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_XPRESSNET_XPRESSNETLISTTABLEMODEL_HPP
|
||||
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_XPRESSNET_XPRESSNETLISTTABLEMODEL_HPP
|
||||
#ifndef TRAINTASTIC_SERVER_CORE_CONTROLLERLISTBASETABLEMODEL_HPP
|
||||
#define TRAINTASTIC_SERVER_CORE_CONTROLLERLISTBASETABLEMODEL_HPP
|
||||
|
||||
#include "../../../core/objectlisttablemodel.hpp"
|
||||
#include "xpressnet.hpp"
|
||||
#include "tablemodel.hpp"
|
||||
|
||||
class XpressNetList;
|
||||
class ControllerListBase;
|
||||
|
||||
class XpressNetListTableModel : public ObjectListTableModel<XpressNet::XpressNet>
|
||||
class ControllerListBaseTableModel final : public TableModel
|
||||
{
|
||||
friend class XpressNetList;
|
||||
CLASS_ID("table_model.controller_list")
|
||||
|
||||
protected:
|
||||
void propertyChanged(BaseProperty& property, uint32_t row) final;
|
||||
friend class ControllerListBase;
|
||||
|
||||
private:
|
||||
std::shared_ptr<ControllerListBase> m_list;
|
||||
|
||||
void propertyChanged(BaseProperty& property, uint32_t row);
|
||||
|
||||
public:
|
||||
CLASS_ID("xpressnet_list_table_model")
|
||||
|
||||
static bool isListedProperty(const std::string& name);
|
||||
|
||||
XpressNetListTableModel(XpressNetList& list);
|
||||
ControllerListBaseTableModel(ControllerListBase& list);
|
||||
|
||||
std::string getText(uint32_t column, uint32_t row) const final;
|
||||
};
|
||||
@ -45,21 +45,41 @@ void Object::destroy()
|
||||
}
|
||||
}
|
||||
|
||||
const InterfaceItem* Object::getItem(std::string_view name) const
|
||||
{
|
||||
return m_interfaceItems.find(name);
|
||||
}
|
||||
|
||||
InterfaceItem* Object::getItem(std::string_view name)
|
||||
{
|
||||
return m_interfaceItems.find(name);
|
||||
}
|
||||
|
||||
const AbstractMethod* Object::getMethod(std::string_view name) const
|
||||
{
|
||||
return dynamic_cast<const AbstractMethod*>(getItem(name));
|
||||
}
|
||||
|
||||
AbstractMethod* Object::getMethod(std::string_view name)
|
||||
{
|
||||
return dynamic_cast<AbstractMethod*>(getItem(name));
|
||||
}
|
||||
|
||||
const AbstractProperty* Object::getProperty(std::string_view name) const
|
||||
{
|
||||
return dynamic_cast<const AbstractProperty*>(getItem(name));
|
||||
}
|
||||
|
||||
AbstractProperty* Object::getProperty(std::string_view name)
|
||||
{
|
||||
return dynamic_cast<AbstractProperty*>(getItem(name));
|
||||
}
|
||||
|
||||
const AbstractVectorProperty* Object::getVectorProperty(std::string_view name) const
|
||||
{
|
||||
return dynamic_cast<const AbstractVectorProperty*>(getItem(name));
|
||||
}
|
||||
|
||||
AbstractVectorProperty* Object::getVectorProperty(std::string_view name)
|
||||
{
|
||||
return dynamic_cast<AbstractVectorProperty*>(getItem(name));
|
||||
|
||||
@ -99,9 +99,13 @@ class Object : public std::enable_shared_from_this<Object>
|
||||
|
||||
const InterfaceItems& interfaceItems() const { return m_interfaceItems; }
|
||||
|
||||
const InterfaceItem* getItem(std::string_view name) const;
|
||||
InterfaceItem* getItem(std::string_view name);
|
||||
const AbstractMethod* getMethod(std::string_view name) const;
|
||||
AbstractMethod* getMethod(std::string_view name);
|
||||
const AbstractProperty* getProperty(std::string_view name) const;
|
||||
AbstractProperty* getProperty(std::string_view name);
|
||||
const AbstractVectorProperty* getVectorProperty(std::string_view name) const;
|
||||
AbstractVectorProperty* getVectorProperty(std::string_view name);
|
||||
};
|
||||
|
||||
|
||||
@ -35,6 +35,8 @@ class ObjectListTableModel : public TableModel
|
||||
std::shared_ptr<ObjectList<T>> m_list;
|
||||
|
||||
protected:
|
||||
static constexpr uint32_t invalidColumn = std::numeric_limits<uint32_t>::max();
|
||||
|
||||
const T& getItem(uint32_t row) const { return *m_list->m_items[row]; }
|
||||
//T& getItem(uint32_t row) { return *m_list.m_items[row]; }
|
||||
virtual void propertyChanged(BaseProperty& property, uint32_t row) = 0;
|
||||
|
||||
@ -49,7 +49,6 @@
|
||||
|
||||
#include "settings.hpp"
|
||||
|
||||
#include "../hardware/commandstation/commandstationlist.hpp"
|
||||
#include "../hardware/decoder/decoderlist.hpp"
|
||||
|
||||
#include <iostream>
|
||||
@ -563,13 +562,13 @@ void Session::writeObject(Message& message, const ObjectPtr& object)
|
||||
|
||||
if(auto* inputMonitor = dynamic_cast<InputMonitor*>(object.get()))
|
||||
{
|
||||
inputMonitor->inputIdChanged = std::bind(&Session::inputMonitorInputIdChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
|
||||
inputMonitor->inputValueChanged = std::bind(&Session::inputMonitorInputValueChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
|
||||
m_objectSignals.emplace(handle, inputMonitor->inputIdChanged.connect(std::bind(&Session::inputMonitorInputIdChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)));
|
||||
m_objectSignals.emplace(handle, inputMonitor->inputValueChanged.connect(std::bind(&Session::inputMonitorInputValueChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)));
|
||||
}
|
||||
else if(auto* outputKeyboard = dynamic_cast<OutputKeyboard*>(object.get()))
|
||||
{
|
||||
outputKeyboard->outputIdChanged = std::bind(&Session::outputKeyboardOutputIdChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
|
||||
outputKeyboard->outputValueChanged = std::bind(&Session::outputKeyboardOutputValueChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
|
||||
m_objectSignals.emplace(handle, outputKeyboard->outputIdChanged.connect(std::bind(&Session::outputKeyboardOutputIdChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)));
|
||||
m_objectSignals.emplace(handle, outputKeyboard->outputValueChanged.connect(std::bind(&Session::outputKeyboardOutputValueChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)));
|
||||
}
|
||||
else if(auto* board = dynamic_cast<Board*>(object.get()))
|
||||
{
|
||||
|
||||
35
server/src/enum/interfacestatus.hpp
Normale Datei
35
server/src/enum/interfacestatus.hpp
Normale Datei
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* server/src/enum/interfacestatus.hpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef TRAINTASTIC_SERVER_ENUM_INTERFACESTATUS_HPP
|
||||
#define TRAINTASTIC_SERVER_ENUM_INTERFACESTATUS_HPP
|
||||
|
||||
#include <traintastic/enum/interfacestatus.hpp>
|
||||
|
||||
inline constexpr std::array<InterfaceStatus, 4> interfaceStatusValues{{
|
||||
InterfaceStatus::Offline,
|
||||
InterfaceStatus::Initializing,
|
||||
InterfaceStatus::Online,
|
||||
InterfaceStatus::Error,
|
||||
}};
|
||||
|
||||
#endif
|
||||
49
server/src/enum/loconetinterfacetype.hpp
Normale Datei
49
server/src/enum/loconetinterfacetype.hpp
Normale Datei
@ -0,0 +1,49 @@
|
||||
/**
|
||||
* server/src/enum/loconetinterface.hpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef TRAINTASTIC_SERVER_ENUM_LOCONETINTERFACETYPE_HPP
|
||||
#define TRAINTASTIC_SERVER_ENUM_LOCONETINTERFACETYPE_HPP
|
||||
|
||||
#include <traintastic/enum/loconetinterfacetype.hpp>
|
||||
#include <array>
|
||||
|
||||
inline constexpr std::array<LocoNetInterfaceType, 4> locoNetInterfaceTypeValues{{
|
||||
LocoNetInterfaceType::Serial,
|
||||
LocoNetInterfaceType::TCPBinary,
|
||||
LocoNetInterfaceType::LBServer,
|
||||
LocoNetInterfaceType::Z21,
|
||||
}};
|
||||
|
||||
constexpr bool isSerial(LocoNetInterfaceType value)
|
||||
{
|
||||
return (value == LocoNetInterfaceType::Serial);
|
||||
}
|
||||
|
||||
constexpr bool isNetwork(LocoNetInterfaceType value)
|
||||
{
|
||||
return
|
||||
value == LocoNetInterfaceType::TCPBinary ||
|
||||
value == LocoNetInterfaceType::LBServer ||
|
||||
value == LocoNetInterfaceType::Z21;
|
||||
}
|
||||
|
||||
#endif
|
||||
34
server/src/enum/xpressnetinterfacetype.hpp
Normale Datei
34
server/src/enum/xpressnetinterfacetype.hpp
Normale Datei
@ -0,0 +1,34 @@
|
||||
/**
|
||||
* server/src/enum/xpressnetinterface.hpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef TRAINTASTIC_SERVER_ENUM_XPRESSNETINTERFACETYPE_HPP
|
||||
#define TRAINTASTIC_SERVER_ENUM_XPRESSNETINTERFACETYPE_HPP
|
||||
|
||||
#include <traintastic/enum/xpressnetinterfacetype.hpp>
|
||||
#include <array>
|
||||
|
||||
inline constexpr std::array<XpressNetInterfaceType, 2> xpressNetInterfaceTypeValues{{
|
||||
XpressNetInterfaceType::Serial,
|
||||
XpressNetInterfaceType::Network,
|
||||
}};
|
||||
|
||||
#endif
|
||||
@ -1,37 +0,0 @@
|
||||
/**
|
||||
* server/src/enum/xpressnetserialinterface.hpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2020 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef TRAINTASTIC_SERVER_ENUM_XPRESSNETSERIALINTERFACE_HPP
|
||||
#define TRAINTASTIC_SERVER_ENUM_XPRESSNETSERIALINTERFACE_HPP
|
||||
|
||||
#include <traintastic/enum/xpressnetserialinterface.hpp>
|
||||
#include <array>
|
||||
|
||||
inline constexpr std::array<XpressNetSerialInterface, 5> XpressNetSerialInterfaceValues{{
|
||||
XpressNetSerialInterface::Custom,
|
||||
XpressNetSerialInterface::LenzLI100,
|
||||
XpressNetSerialInterface::LenzLI100F,
|
||||
XpressNetSerialInterface::LenzLI101F,
|
||||
XpressNetSerialInterface::RoSoftS88XPressNetLI,
|
||||
}};
|
||||
|
||||
#endif
|
||||
@ -1,5 +1,5 @@
|
||||
/**
|
||||
* server/src/hardware/input/inputs.hpp
|
||||
* server/src/enum/xpressnetserialinterfacetype.hpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
@ -20,25 +20,19 @@
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef TRAINTASTIC_SERVER_HARDWARE_INPUT_INPUTS_HPP
|
||||
#define TRAINTASTIC_SERVER_HARDWARE_INPUT_INPUTS_HPP
|
||||
#ifndef TRAINTASTIC_SERVER_ENUM_XPRESSNETSERIALINTERFACETYPE_HPP
|
||||
#define TRAINTASTIC_SERVER_ENUM_XPRESSNETSERIALINTERFACETYPE_HPP
|
||||
|
||||
#include "input.hpp"
|
||||
#include "../../utils/makearray.hpp"
|
||||
#include <traintastic/enum/xpressnetserialinterfacetype.hpp>
|
||||
#include <array>
|
||||
|
||||
#include "loconetinput.hpp"
|
||||
#include "xpressnetinput.hpp"
|
||||
constexpr std::array<XpressNetSerialInterfaceType, 6> XpressNetSerialInterfaceTypeValues{{
|
||||
XpressNetSerialInterfaceType::DigikeijsDR5000,
|
||||
XpressNetSerialInterfaceType::LenzLI100,
|
||||
XpressNetSerialInterfaceType::LenzLI100F,
|
||||
XpressNetSerialInterfaceType::LenzLI101F,
|
||||
XpressNetSerialInterfaceType::LenzLIUSB,
|
||||
XpressNetSerialInterfaceType::RoSoftS88XPressNetLI,
|
||||
}};
|
||||
|
||||
struct Inputs
|
||||
{
|
||||
static constexpr std::string_view classIdPrefix = "input.";
|
||||
|
||||
static constexpr auto classList = makeArray(
|
||||
LocoNetInput::classId,
|
||||
XpressNetInput::classId
|
||||
);
|
||||
|
||||
static std::shared_ptr<Input> create(const std::shared_ptr<World>& world, std::string_view classId, std::string_view id);
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@ -1,204 +0,0 @@
|
||||
/**
|
||||
* server/src/hardware/commandstation/commandstation.cpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "commandstation.hpp"
|
||||
#include "commandstationlist.hpp"
|
||||
#include "commandstationlisttablemodel.hpp"
|
||||
#include <functional>
|
||||
#include "../../world/world.hpp"
|
||||
#include "../decoder/decoder.hpp"
|
||||
#include "../decoder/decoderlist.hpp"
|
||||
#include "../../core/attributes.hpp"
|
||||
#include "../../utils/displayname.hpp"
|
||||
#include "../../utils/almostzero.hpp"
|
||||
|
||||
CommandStation::CommandStation(const std::weak_ptr<World>& world, std::string_view _id) :
|
||||
IdObject(world, _id),
|
||||
name{this, "name", "", PropertyFlags::ReadWrite | PropertyFlags::Store},
|
||||
online{this, "online", false, PropertyFlags::ReadWrite | PropertyFlags::NoStore,
|
||||
[this](bool value)
|
||||
{
|
||||
Attributes::setEnabled(emergencyStop, value);
|
||||
Attributes::setEnabled(powerOn, value);
|
||||
},
|
||||
std::bind(&CommandStation::setOnline, this, std::placeholders::_1)},
|
||||
//status{this, "status", CommandStationStatus::Offline, PropertyFlags::ReadOnly},
|
||||
emergencyStop{this, "emergency_stop", true, PropertyFlags::ReadWrite | PropertyFlags::NoStore,
|
||||
[this](bool value)
|
||||
{
|
||||
emergencyStopChanged(value);
|
||||
}},
|
||||
powerOn{this, "power_on", false, PropertyFlags::ReadWrite | PropertyFlags::NoStore,
|
||||
[this](bool value)
|
||||
{
|
||||
powerOnChanged(value);
|
||||
}},
|
||||
decoders{this, "decoders", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject},
|
||||
controllers{this, "controllers", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject},
|
||||
notes{this, "notes", "", PropertyFlags::ReadWrite | PropertyFlags::Store}
|
||||
{
|
||||
decoders.setValueInternal(std::make_shared<DecoderList>(*this, decoders.name()));
|
||||
controllers.setValueInternal(std::make_shared<ControllerList>(*this, controllers.name()));
|
||||
|
||||
auto w = world.lock();
|
||||
const bool editable = w && contains(w->state.value(), WorldState::Edit);
|
||||
|
||||
Attributes::addDisplayName(name, DisplayName::Object::name);
|
||||
Attributes::addEnabled(name, editable);
|
||||
m_interfaceItems.add(name);
|
||||
|
||||
Attributes::addDisplayName(online, DisplayName::CommandStation::online);
|
||||
m_interfaceItems.add(online);
|
||||
|
||||
Attributes::addDisplayName(emergencyStop, DisplayName::CommandStation::emergencyStop);
|
||||
Attributes::addEnabled(emergencyStop, online);
|
||||
Attributes::addObjectEditor(emergencyStop, false);
|
||||
m_interfaceItems.insertBefore(emergencyStop, notes);
|
||||
|
||||
Attributes::addDisplayName(powerOn, DisplayName::CommandStation::powerOn);
|
||||
Attributes::addEnabled(powerOn, online);
|
||||
Attributes::addObjectEditor(powerOn, false);
|
||||
m_interfaceItems.insertBefore(powerOn, notes);
|
||||
|
||||
Attributes::addDisplayName(decoders, DisplayName::World::decoders);
|
||||
m_interfaceItems.add(decoders);
|
||||
|
||||
Attributes::addDisplayName(controllers, DisplayName::World::controllers);
|
||||
m_interfaceItems.add(controllers);
|
||||
|
||||
Attributes::addDisplayName(notes, DisplayName::Object::notes);
|
||||
m_interfaceItems.add(notes);
|
||||
}
|
||||
|
||||
void CommandStation::loaded()
|
||||
{
|
||||
IdObject::loaded();
|
||||
|
||||
checkAllDecoders();
|
||||
}
|
||||
|
||||
void CommandStation::addToWorld()
|
||||
{
|
||||
IdObject::addToWorld();
|
||||
|
||||
if(auto world = m_world.lock())
|
||||
world->commandStations->addObject(shared_ptr<CommandStation>());
|
||||
}
|
||||
|
||||
void CommandStation::destroying()
|
||||
{
|
||||
for(const auto& decoder : *decoders)
|
||||
{
|
||||
assert(decoder->commandStation.value() == shared_ptr<CommandStation>());
|
||||
decoder->commandStation = nullptr;
|
||||
}
|
||||
if(auto world = m_world.lock())
|
||||
world->commandStations->removeObject(shared_ptr<CommandStation>());
|
||||
IdObject::destroying();
|
||||
}
|
||||
|
||||
void CommandStation::worldEvent(WorldState state, WorldEvent event)
|
||||
{
|
||||
IdObject::worldEvent(state, event);
|
||||
|
||||
Attributes::setEnabled(name, contains(state, WorldState::Edit));
|
||||
|
||||
try
|
||||
{
|
||||
switch(event)
|
||||
{
|
||||
case WorldEvent::Offline:
|
||||
online = false;
|
||||
break;
|
||||
|
||||
case WorldEvent::Online:
|
||||
online = true;
|
||||
break;
|
||||
|
||||
case WorldEvent::PowerOff:
|
||||
powerOn = false;
|
||||
break;
|
||||
|
||||
case WorldEvent::PowerOn:
|
||||
powerOn = true;
|
||||
if(!emergencyStop)
|
||||
restoreSpeed();
|
||||
break;
|
||||
|
||||
case WorldEvent::Stop:
|
||||
emergencyStop = true;
|
||||
break;
|
||||
|
||||
case WorldEvent::Run:
|
||||
emergencyStop = false;
|
||||
if(powerOn)
|
||||
restoreSpeed();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
const std::shared_ptr<::Decoder>& CommandStation::getDecoder(DecoderProtocol protocol, uint16_t address, bool longAddress) const
|
||||
{
|
||||
auto it = std::find_if(decoders->begin(), decoders->end(), [=](auto& decoder){ return decoder->protocol.value() == protocol && decoder->address.value() == address && decoder->longAddress == longAddress; });
|
||||
if(it != decoders->end())
|
||||
return *it;
|
||||
return Decoder::null;
|
||||
}
|
||||
|
||||
void CommandStation::emergencyStopChanged(bool value)
|
||||
{
|
||||
for(auto& controller : *controllers)
|
||||
controller->emergencyStopChanged(value);
|
||||
}
|
||||
|
||||
void CommandStation::powerOnChanged(bool value)
|
||||
{
|
||||
for(auto& controller : *controllers)
|
||||
controller->powerOnChanged(value);
|
||||
}
|
||||
|
||||
void CommandStation::checkAllDecoders() const
|
||||
{
|
||||
for(const auto& decoder : *decoders)
|
||||
checkDecoder(*decoder);
|
||||
}
|
||||
|
||||
void CommandStation::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber)
|
||||
{
|
||||
for(auto& controller : *controllers)
|
||||
controller->decoderChanged(decoder, changes, functionNumber);
|
||||
}
|
||||
|
||||
//! \brief restore speed of all decoders that are not (emergency) stopped
|
||||
void CommandStation::restoreSpeed()
|
||||
{
|
||||
for(const auto& decoder : *decoders)
|
||||
if(!decoder->emergencyStop && !almostZero(decoder->throttle.value()))
|
||||
decoderChanged(*decoder, DecoderChangeFlags::Throttle, 0);
|
||||
}
|
||||
@ -1,68 +0,0 @@
|
||||
/**
|
||||
* server/src/hardware/commandstation/commandstation.hpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef TRAINTASTIC_SERVER_HARDWARE_COMMANDSTATION_COMMANDSTATION_HPP
|
||||
#define TRAINTASTIC_SERVER_HARDWARE_COMMANDSTATION_COMMANDSTATION_HPP
|
||||
|
||||
#include "../../core/idobject.hpp"
|
||||
#include "../../core/objectproperty.hpp"
|
||||
#include "../../enum/commandstationstatus.hpp"
|
||||
#include "../decoder/decoderlist.hpp"
|
||||
#include "../controller/controllerlist.hpp"
|
||||
|
||||
class Decoder;
|
||||
enum class DecoderChangeFlags;
|
||||
|
||||
class CommandStation : public IdObject
|
||||
{
|
||||
friend class ::Decoder;
|
||||
|
||||
protected:
|
||||
void loaded() override;
|
||||
void addToWorld() final;
|
||||
void destroying() override;
|
||||
void worldEvent(WorldState state, WorldEvent event) override;
|
||||
|
||||
virtual bool setOnline(bool& value) = 0;
|
||||
virtual void emergencyStopChanged(bool value);
|
||||
virtual void powerOnChanged(bool value);
|
||||
void checkAllDecoders() const;
|
||||
virtual void checkDecoder(const Decoder& /*decoder*/) const {}
|
||||
virtual void decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber);
|
||||
|
||||
void restoreSpeed();
|
||||
|
||||
public:
|
||||
CommandStation(const std::weak_ptr<World>& world, std::string_view _id);
|
||||
|
||||
Property<std::string> name;
|
||||
Property<bool> online;
|
||||
Property<bool> emergencyStop;
|
||||
Property<bool> powerOn;
|
||||
ObjectProperty<DecoderList> decoders;
|
||||
ObjectProperty<ControllerList> controllers;
|
||||
Property<std::string> notes;
|
||||
|
||||
const std::shared_ptr<Decoder>& getDecoder(DecoderProtocol protocol, uint16_t address, bool longAddress = false) const;
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -1,46 +0,0 @@
|
||||
/**
|
||||
* server/src/hardware/commandstation/commandstationlist.hpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2020 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef TRAINTASTIC_SERVER_HARDWARE_COMMANDSTATION_COMMANDSTATIONLIST_HPP
|
||||
#define TRAINTASTIC_SERVER_HARDWARE_COMMANDSTATION_COMMANDSTATIONLIST_HPP
|
||||
|
||||
#include "../../core/objectlist.hpp"
|
||||
#include "commandstation.hpp"
|
||||
|
||||
class CommandStationList : public ObjectList<CommandStation>
|
||||
{
|
||||
protected:
|
||||
void worldEvent(WorldState state, WorldEvent event) final;
|
||||
bool isListedProperty(const std::string& name) final;
|
||||
|
||||
public:
|
||||
CLASS_ID("command_station_list")
|
||||
|
||||
Method<std::shared_ptr<CommandStation>(std::string_view)> add;
|
||||
Method<void(const std::shared_ptr<CommandStation>&)> remove;
|
||||
|
||||
CommandStationList(Object& _parent, const std::string& parentPropertyName);
|
||||
|
||||
TableModelPtr getModel() final;
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -1,100 +0,0 @@
|
||||
/**
|
||||
* server/src/core/
|
||||
*
|
||||
* This file is part of the traintastic source code
|
||||
*
|
||||
* Copyright (C) 2019-2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "commandstationlisttablemodel.hpp"
|
||||
#include "commandstationlist.hpp"
|
||||
#include "../../utils/utf8.hpp"
|
||||
#include "../../utils/displayname.hpp"
|
||||
|
||||
constexpr uint32_t columnId = 0;
|
||||
constexpr uint32_t columnName = 1;
|
||||
constexpr uint32_t columnOnline = 2;
|
||||
constexpr uint32_t columnEmergencyStop = 3;
|
||||
constexpr uint32_t columnTrackPower = 4;
|
||||
|
||||
bool CommandStationListTableModel::isListedProperty(const std::string& name)
|
||||
{
|
||||
return
|
||||
name == "id" ||
|
||||
name == "name" ||
|
||||
name == "online" ||
|
||||
name == "emergency_stop" ||
|
||||
name == "power_on";
|
||||
}
|
||||
|
||||
CommandStationListTableModel::CommandStationListTableModel(CommandStationList& list) :
|
||||
ObjectListTableModel<CommandStation>(list)
|
||||
{
|
||||
setColumnHeaders({
|
||||
DisplayName::Object::id,
|
||||
DisplayName::Object::name,
|
||||
DisplayName::CommandStation::online,
|
||||
DisplayName::CommandStation::emergencyStop,
|
||||
DisplayName::CommandStation::powerOn,
|
||||
});
|
||||
}
|
||||
|
||||
std::string CommandStationListTableModel::getText(uint32_t column, uint32_t row) const
|
||||
{
|
||||
if(row < rowCount())
|
||||
{
|
||||
const CommandStation& cs = getItem(row);
|
||||
|
||||
switch(column)
|
||||
{
|
||||
case columnId:
|
||||
return cs.id;
|
||||
|
||||
case columnName:
|
||||
return cs.name;
|
||||
|
||||
case columnOnline:
|
||||
return cs.online ? "\u2022" : "";
|
||||
|
||||
case columnEmergencyStop:
|
||||
return cs.emergencyStop ? "\u2022" : "";
|
||||
|
||||
case columnTrackPower:
|
||||
return cs.powerOn ? UTF8_CHECKMARK : UTF8_BALLOT_X;
|
||||
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
void CommandStationListTableModel::propertyChanged(BaseProperty& property, uint32_t row)
|
||||
{
|
||||
if(property.name() == "id")
|
||||
changed(row, columnId);
|
||||
else if(property.name() == "name")
|
||||
changed(row, columnName);
|
||||
else if(property.name() == "online")
|
||||
changed(row, columnOnline);
|
||||
else if(property.name() == "emergency_stop")
|
||||
changed(row, columnEmergencyStop);
|
||||
else if(property.name() == "track_voltage_off")
|
||||
changed(row, columnTrackPower);
|
||||
}
|
||||
@ -1,48 +0,0 @@
|
||||
/**
|
||||
* server/src/hardware/commandstation/commandstationlisttablemodel.hpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef TRAINTASTIC_SERVER_HARDWARE_COMMANDSTATION_COMMANDSTATIONLISTTABLEMODEL_HPP
|
||||
#define TRAINTASTIC_SERVER_HARDWARE_COMMANDSTATION_COMMANDSTATIONLISTTABLEMODEL_HPP
|
||||
|
||||
#include "../../core/objectlisttablemodel.hpp"
|
||||
#include "commandstationlist.hpp"
|
||||
|
||||
class CommandStationList;
|
||||
|
||||
class CommandStationListTableModel : public ObjectListTableModel<CommandStation>
|
||||
{
|
||||
friend class CommandStationList;
|
||||
|
||||
protected:
|
||||
void propertyChanged(BaseProperty& property, uint32_t row) final;
|
||||
|
||||
public:
|
||||
CLASS_ID("command_station_list_table_model")
|
||||
|
||||
static bool isListedProperty(const std::string& name);
|
||||
|
||||
CommandStationListTableModel(CommandStationList& commandStationList);
|
||||
|
||||
std::string getText(uint32_t column, uint32_t row) const final;
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -1,45 +0,0 @@
|
||||
/**
|
||||
* server/src/hardware/commandstation/commandstations.cpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "commandstations.hpp"
|
||||
|
||||
std::shared_ptr<CommandStation> CommandStations::create(const std::weak_ptr<World>& world, std::string_view classId, std::string_view id)
|
||||
{
|
||||
if(classId == DCCPlusPlusSerial::classId)
|
||||
return DCCPlusPlusSerial::create(world, id);
|
||||
if(classId == LocoNetSerial::classId)
|
||||
return LocoNetSerial::create(world, id);
|
||||
else if(classId == LocoNetTCPBinary::classId)
|
||||
return LocoNetTCPBinary::create(world, id);
|
||||
#ifdef USB_XPRESSNET
|
||||
else if(classId == USBXpressNetInterface::classId)
|
||||
return USBXpressNetInterface::create(world, id);
|
||||
#endif
|
||||
else if(classId == XpressNetSerial::classId)
|
||||
return XpressNetSerial::create(world, id);
|
||||
else if(classId == RocoZ21::classId)
|
||||
return RocoZ21::create(world, id);
|
||||
else if(classId == VirtualCommandStation::classId)
|
||||
return VirtualCommandStation::create(world, id);
|
||||
else
|
||||
return std::shared_ptr<CommandStation>();
|
||||
}
|
||||
@ -1,58 +0,0 @@
|
||||
/**
|
||||
* server/src/hardware/commandstation/create.hpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef TRAINTASTIC_SERVER_HARDWARE_COMMANDSTATION_COMMANDSTATIONS_HPP
|
||||
#define TRAINTASTIC_SERVER_HARDWARE_COMMANDSTATION_COMMANDSTATIONS_HPP
|
||||
|
||||
#include "commandstation.hpp"
|
||||
#include "../../utils/makearray.hpp"
|
||||
|
||||
#include "dccplusplusserial.hpp"
|
||||
#include "loconetserial.hpp"
|
||||
#include "loconettcpbinary.hpp"
|
||||
#ifdef USB_XPRESSNET
|
||||
#include "usbxpressnetinterface.hpp"
|
||||
#endif
|
||||
#include "xpressnetserial.hpp"
|
||||
#include "rocoz21.hpp"
|
||||
#include "virtualcommandstation.hpp"
|
||||
|
||||
struct CommandStations
|
||||
{
|
||||
static constexpr std::string_view classIdPrefix = "command_station.";
|
||||
|
||||
static constexpr auto classList = makeArray(
|
||||
DCCPlusPlusSerial::classId,
|
||||
LocoNetSerial::classId,
|
||||
LocoNetTCPBinary::classId,
|
||||
#ifdef USB_XPRESSNET
|
||||
USBXpressNetInterface::classId,
|
||||
#endif
|
||||
XpressNetSerial::classId,
|
||||
RocoZ21::classId,
|
||||
VirtualCommandStation::classId
|
||||
);
|
||||
|
||||
static std::shared_ptr<CommandStation> create(const std::weak_ptr<World>& world, std::string_view classId, std::string_view id);
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -1,133 +0,0 @@
|
||||
/**
|
||||
* server/src/hardware/commandstation/dccplusplusserial.cpp
|
||||
*
|
||||
* This file is part of the traintastic source code
|
||||
*
|
||||
* Copyright (C) 2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "dccplusplusserial.hpp"
|
||||
#include "../protocol/dccplusplus/messages.hpp"
|
||||
#include "../../core/eventloop.hpp"
|
||||
#include "../../log/log.hpp"
|
||||
|
||||
DCCPlusPlusSerial::DCCPlusPlusSerial(const std::weak_ptr<World>& world, std::string_view _id) :
|
||||
SerialCommandStation(world, _id),
|
||||
dccPlusPlus{this, "dcc_plus_plus", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject}
|
||||
{
|
||||
name = "DCC++ (serial)";
|
||||
baudrate = 115200;
|
||||
dccPlusPlus.setValueInternal(std::make_shared<DCCPlusPlus::DCCPlusPlus>(*this, dccPlusPlus.name(), std::bind(&DCCPlusPlusSerial::send, this, std::placeholders::_1)));
|
||||
|
||||
m_interfaceItems.insertBefore(dccPlusPlus, notes);
|
||||
}
|
||||
|
||||
void DCCPlusPlusSerial::emergencyStopChanged(bool value)
|
||||
{
|
||||
CommandStation::emergencyStopChanged(value);
|
||||
|
||||
if(online)
|
||||
dccPlusPlus->emergencyStopChanged(value);
|
||||
}
|
||||
|
||||
void DCCPlusPlusSerial::powerOnChanged(bool value)
|
||||
{
|
||||
CommandStation::powerOnChanged(value);
|
||||
|
||||
if(online)
|
||||
dccPlusPlus->powerOnChanged(value);
|
||||
}
|
||||
|
||||
void DCCPlusPlusSerial::checkDecoder(const Decoder& decoder) const
|
||||
{
|
||||
CommandStation::checkDecoder(decoder);
|
||||
dccPlusPlus->checkDecoder(decoder);
|
||||
}
|
||||
|
||||
void DCCPlusPlusSerial::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber)
|
||||
{
|
||||
CommandStation::decoderChanged(decoder, changes, functionNumber);
|
||||
|
||||
if(online)
|
||||
dccPlusPlus->decoderChanged(decoder, changes, functionNumber);
|
||||
}
|
||||
|
||||
bool DCCPlusPlusSerial::send(std::string_view message)
|
||||
{
|
||||
if(!m_serialPort.is_open())
|
||||
return false;
|
||||
|
||||
// for now a blocking implementation, will be replaced by async.
|
||||
|
||||
boost::system::error_code ec;
|
||||
size_t todo = message.size();
|
||||
while(todo > 0)
|
||||
{
|
||||
todo -= m_serialPort.write_some(boost::asio::buffer(message.data(), message.size()), ec);
|
||||
if(ec)
|
||||
{
|
||||
Log::log(*this, LogMessage::E2001_SERIAL_WRITE_FAILED_X, ec);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void DCCPlusPlusSerial::started()
|
||||
{
|
||||
send(DCCPlusPlus::Ex::setSpeedSteps(dccPlusPlus->speedSteps));
|
||||
}
|
||||
|
||||
void DCCPlusPlusSerial::read()
|
||||
{
|
||||
m_serialPort.async_read_some(boost::asio::buffer(m_readBuffer.data() + m_readBufferOffset, m_readBuffer.size() - m_readBufferOffset),
|
||||
[this](const boost::system::error_code& ec, std::size_t bytesTransferred)
|
||||
{
|
||||
if(!ec)
|
||||
{
|
||||
const char* pos = reinterpret_cast<const char*>(m_readBuffer.data());
|
||||
bytesTransferred += m_readBufferOffset;
|
||||
|
||||
size_t i = 0;
|
||||
while(i < bytesTransferred)
|
||||
{
|
||||
if(*(pos + i) == '\n')
|
||||
{
|
||||
dccPlusPlus->receive(std::string_view{pos, i + 1});
|
||||
pos += i + 1;
|
||||
bytesTransferred -= i + 1;
|
||||
i = 0;
|
||||
}
|
||||
else
|
||||
i++;
|
||||
}
|
||||
|
||||
if(bytesTransferred != 0)
|
||||
memmove(m_readBuffer.data(), pos, bytesTransferred);
|
||||
m_readBufferOffset = bytesTransferred;
|
||||
|
||||
read();
|
||||
}
|
||||
else
|
||||
EventLoop::call(
|
||||
[this, ec]()
|
||||
{
|
||||
Log::log(*this, LogMessage::E2002_SERIAL_READ_FAILED_X, ec);
|
||||
online = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -1,162 +0,0 @@
|
||||
/**
|
||||
* hardware/commandstation/loconetserial.cpp
|
||||
*
|
||||
* This file is part of the traintastic source code
|
||||
*
|
||||
* Copyright (C) 2019-2021 Reinder Feenstra <reinderfeenstra@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "loconetserial.hpp"
|
||||
#include "../../core/traintastic.hpp"
|
||||
#include "../../core/eventloop.hpp"
|
||||
#include "../../core/attributes.hpp"
|
||||
#include "../../log/log.hpp"
|
||||
#include "../../utils/displayname.hpp"
|
||||
|
||||
LocoNetSerial::LocoNetSerial(const std::weak_ptr<World>& world, std::string_view _id) :
|
||||
SerialCommandStation(world, _id),
|
||||
interface{this, "interface", LocoNetSerialInterface::Custom, PropertyFlags::ReadWrite | PropertyFlags::Store,
|
||||
[this](LocoNetSerialInterface value)
|
||||
{
|
||||
switch(value)
|
||||
{
|
||||
case LocoNetSerialInterface::Custom:
|
||||
break;
|
||||
|
||||
case LocoNetSerialInterface::DigikeijsDR5000:
|
||||
baudrate = 115200;
|
||||
flowControl = SerialFlowControl::Hardware;
|
||||
break;
|
||||
|
||||
case LocoNetSerialInterface::RoSoftLocoNetInterface:
|
||||
baudrate = 19200;
|
||||
flowControl = SerialFlowControl::Hardware;
|
||||
break;
|
||||
}
|
||||
}},
|
||||
loconet{this, "loconet", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject}
|
||||
{
|
||||
name = "LocoNet (serial)";
|
||||
loconet.setValueInternal(LocoNet::LocoNet::create(*this, loconet.name(), std::bind(&LocoNetSerial::send, this, std::placeholders::_1)));
|
||||
|
||||
Attributes::addEnabled(interface, !online);
|
||||
Attributes::addValues(interface, LocoNetSerialInterfaceValues);
|
||||
m_interfaceItems.insertBefore(interface, baudrate);
|
||||
|
||||
Attributes::addDisplayName(loconet, DisplayName::Hardware::loconet);
|
||||
m_interfaceItems.insertBefore(loconet, notes);
|
||||
}
|
||||
|
||||
void LocoNetSerial::emergencyStopChanged(bool value)
|
||||
{
|
||||
CommandStation::emergencyStopChanged(value);
|
||||
|
||||
if(online)
|
||||
loconet->emergencyStopChanged(value);
|
||||
}
|
||||
|
||||
void LocoNetSerial::powerOnChanged(bool value)
|
||||
{
|
||||
CommandStation::powerOnChanged(value);
|
||||
|
||||
if(online)
|
||||
loconet->powerOnChanged(value);
|
||||
}
|
||||
|
||||
void LocoNetSerial::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber)
|
||||
{
|
||||
CommandStation::decoderChanged(decoder, changes, functionNumber);
|
||||
|
||||
if(online)
|
||||
loconet->decoderChanged(decoder, changes, functionNumber);
|
||||
}
|
||||
|
||||
bool LocoNetSerial::send(const LocoNet::Message& message)
|
||||
{
|
||||
if(!m_serialPort.is_open())
|
||||
return false;
|
||||
boost::system::error_code ec;
|
||||
m_serialPort.write_some(boost::asio::buffer(static_cast<const void*>(&message), message.size()), ec); // TODO async
|
||||
if(ec)
|
||||
{
|
||||
Log::log(*this, LogMessage::E2001_SERIAL_WRITE_FAILED_X, ec);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void LocoNetSerial::started()
|
||||
{
|
||||
loconet->queryLocoSlots();
|
||||
}
|
||||
|
||||
void LocoNetSerial::read()
|
||||
{
|
||||
m_serialPort.async_read_some(boost::asio::buffer(m_readBuffer.data() + m_readBufferOffset, m_readBuffer.size() - m_readBufferOffset),
|
||||
[this](const boost::system::error_code& ec, std::size_t bytesTransferred)
|
||||
{
|
||||
if(!ec)
|
||||
{
|
||||
const uint8_t* pos = m_readBuffer.data();
|
||||
bytesTransferred += m_readBufferOffset;
|
||||
|
||||
while(bytesTransferred > 1)
|
||||
{
|
||||
const LocoNet::Message* message = reinterpret_cast<const LocoNet::Message*>(pos);
|
||||
|
||||
size_t drop = 0;
|
||||
while((message->size() == 0 || (message->size() <= bytesTransferred && !LocoNet::isValid(*message))) && drop < bytesTransferred)
|
||||
{
|
||||
drop++;
|
||||
pos++;
|
||||
bytesTransferred--;
|
||||
message = reinterpret_cast<const LocoNet::Message*>(pos);
|
||||
}
|
||||
|
||||
if(drop != 0)
|
||||
{
|
||||
EventLoop::call(
|
||||
[this, drop]()
|
||||
{
|
||||
Log::log(*this, LogMessage::W2001_RECEIVED_MALFORMED_DATA_DROPPED_X_BYTES, drop);
|
||||
});
|
||||
}
|
||||
else if(message->size() <= bytesTransferred)
|
||||
{
|
||||
loconet->receive(*message);
|
||||
pos += message->size();
|
||||
bytesTransferred -= message->size();
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
if(bytesTransferred != 0)
|
||||
memmove(m_readBuffer.data(), pos, bytesTransferred);
|
||||
m_readBufferOffset = bytesTransferred;
|
||||
|
||||
read();
|
||||
}
|
||||
else
|
||||
EventLoop::call(
|
||||
[this, ec]()
|
||||
{
|
||||
Log::log(*this, LogMessage::E2002_SERIAL_READ_FAILED_X, ec);
|
||||
online = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -1,52 +0,0 @@
|
||||
/**
|
||||
* server/src/hardware/commandstation/loconetserial.hpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef TRAINTASTIC_SERVER_HARDWARE_COMMANDSTATION_LOCONETSERIAL_HPP
|
||||
#define TRAINTASTIC_SERVER_HARDWARE_COMMANDSTATION_LOCONETSERIAL_HPP
|
||||
|
||||
#include "serialcommandstation.hpp"
|
||||
#include "../protocol/loconet/loconet.hpp"
|
||||
#include "../../enum/loconetserialinterface.hpp"
|
||||
|
||||
class LocoNetSerial : public SerialCommandStation
|
||||
{
|
||||
protected:
|
||||
void emergencyStopChanged(bool value) final;
|
||||
void powerOnChanged(bool value) final;
|
||||
void decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber) final;
|
||||
|
||||
bool start();
|
||||
bool send(const LocoNet::Message& msg);
|
||||
void started() final;
|
||||
void read() final;
|
||||
|
||||
public:
|
||||
CLASS_ID("command_station.loconet_serial")
|
||||
CREATE(LocoNetSerial)
|
||||
|
||||
Property<LocoNetSerialInterface> interface;
|
||||
ObjectProperty<LocoNet::LocoNet> loconet;
|
||||
|
||||
LocoNetSerial(const std::weak_ptr<World>& world, std::string_view _id);
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -1,192 +0,0 @@
|
||||
/**
|
||||
* hardware/commandstation/loconettcpbinary.cpp
|
||||
*
|
||||
* This file is part of the traintastic source code
|
||||
*
|
||||
* Copyright (C) 2021 Reinder Feenstra <reinderfeenstra@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "loconettcpbinary.hpp"
|
||||
#include "../../core/traintastic.hpp"
|
||||
#include "../../core/eventloop.hpp"
|
||||
#include "../../core/attributes.hpp"
|
||||
#include "../../log/log.hpp"
|
||||
#include "../../utils/displayname.hpp"
|
||||
|
||||
LocoNetTCPBinary::LocoNetTCPBinary(const std::weak_ptr<World>& world, std::string_view _id) :
|
||||
CommandStation(world, _id),
|
||||
m_socket{Traintastic::instance->ioContext()},
|
||||
hostname{this, "hostname", "", PropertyFlags::ReadWrite | PropertyFlags::Store},
|
||||
port{this, "port", 5550, PropertyFlags::ReadWrite | PropertyFlags::Store},
|
||||
loconet{this, "loconet", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject}
|
||||
{
|
||||
name = "LocoNet (TCP binary)";
|
||||
loconet.setValueInternal(LocoNet::LocoNet::create(*this, loconet.name(), std::bind(&LocoNetTCPBinary::send, this, std::placeholders::_1)));
|
||||
|
||||
Attributes::addDisplayName(hostname, DisplayName::IP::hostname);
|
||||
Attributes::addEnabled(hostname, true);
|
||||
m_interfaceItems.insertBefore(hostname, notes);
|
||||
|
||||
Attributes::addDisplayName(port, DisplayName::IP::port);
|
||||
Attributes::addEnabled(port, true);
|
||||
m_interfaceItems.insertBefore(port, notes);
|
||||
|
||||
Attributes::addDisplayName(loconet, DisplayName::Hardware::loconet);
|
||||
m_interfaceItems.insertBefore(loconet, notes);
|
||||
}
|
||||
|
||||
bool LocoNetTCPBinary::setOnline(bool& value)
|
||||
{
|
||||
if(!m_socket.is_open() && value)
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
|
||||
boost::asio::ip::tcp::endpoint endpoint;
|
||||
endpoint.port(port);
|
||||
endpoint.address(boost::asio::ip::make_address(hostname, ec));
|
||||
if(ec)
|
||||
{
|
||||
Log::log(*this, LogMessage::E2003_MAKE_ADDRESS_FAILED_X, ec);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_socket.connect(endpoint, ec);
|
||||
if(ec)
|
||||
{
|
||||
Log::log(*this, LogMessage::E2005_SOCKET_CONNECT_FAILED_X, ec);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_socket.set_option(boost::asio::socket_base::linger(true, 0));
|
||||
m_socket.set_option(boost::asio::ip::tcp::no_delay(true));
|
||||
|
||||
receive(); // start receiving messages
|
||||
|
||||
Attributes::setEnabled(hostname, false);
|
||||
Attributes::setEnabled(port, false);
|
||||
}
|
||||
else if(m_socket.is_open() && !value)
|
||||
{
|
||||
Attributes::setEnabled(hostname, true);
|
||||
Attributes::setEnabled(port, true);
|
||||
|
||||
m_socket.close();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void LocoNetTCPBinary::emergencyStopChanged(bool value)
|
||||
{
|
||||
CommandStation::emergencyStopChanged(value);
|
||||
|
||||
if(online)
|
||||
loconet->emergencyStopChanged(value);
|
||||
}
|
||||
|
||||
void LocoNetTCPBinary::powerOnChanged(bool value)
|
||||
{
|
||||
CommandStation::powerOnChanged(value);
|
||||
|
||||
if(online)
|
||||
loconet->powerOnChanged(value);
|
||||
}
|
||||
|
||||
void LocoNetTCPBinary::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber)
|
||||
{
|
||||
CommandStation::decoderChanged(decoder, changes, functionNumber);
|
||||
|
||||
if(online)
|
||||
loconet->decoderChanged(decoder, changes, functionNumber);
|
||||
}
|
||||
|
||||
void LocoNetTCPBinary::receive()
|
||||
{
|
||||
m_socket.async_read_some(boost::asio::buffer(m_readBuffer.data() + m_readBufferOffset, m_readBuffer.size() - m_readBufferOffset),
|
||||
[this](const boost::system::error_code& ec, std::size_t bytesTransferred)
|
||||
{
|
||||
if(!ec)
|
||||
{
|
||||
const uint8_t* pos = m_readBuffer.data();
|
||||
bytesTransferred += m_readBufferOffset;
|
||||
|
||||
while(bytesTransferred > 1)
|
||||
{
|
||||
const LocoNet::Message* message = reinterpret_cast<const LocoNet::Message*>(pos);
|
||||
|
||||
size_t drop = 0;
|
||||
while((message->size() == 0 || (message->size() <= bytesTransferred && !LocoNet::isValid(*message))) && drop < bytesTransferred)
|
||||
{
|
||||
drop++;
|
||||
pos++;
|
||||
bytesTransferred--;
|
||||
message = reinterpret_cast<const LocoNet::Message*>(pos);
|
||||
}
|
||||
|
||||
if(drop != 0)
|
||||
{
|
||||
EventLoop::call(
|
||||
[this, drop]()
|
||||
{
|
||||
Log::log(*this, LogMessage::W2001_RECEIVED_MALFORMED_DATA_DROPPED_X_BYTES, drop);
|
||||
});
|
||||
}
|
||||
else if(message->size() <= bytesTransferred)
|
||||
{
|
||||
loconet->receive(*message);
|
||||
pos += message->size();
|
||||
bytesTransferred -= message->size();
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
if(bytesTransferred != 0)
|
||||
memmove(m_readBuffer.data(), pos, bytesTransferred);
|
||||
m_readBufferOffset = bytesTransferred;
|
||||
|
||||
receive();
|
||||
}
|
||||
else
|
||||
EventLoop::call(
|
||||
[this, ec]()
|
||||
{
|
||||
Log::log(*this, LogMessage::E2008_SOCKET_READ_FAILED_X, ec);
|
||||
online = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
bool LocoNetTCPBinary::send(const LocoNet::Message& message)
|
||||
{
|
||||
if(!m_socket.is_open())
|
||||
return false;
|
||||
|
||||
// for now a blocking implementation, will be replaced by async.
|
||||
|
||||
boost::system::error_code ec;
|
||||
size_t todo = message.size();
|
||||
while(todo > 0)
|
||||
{
|
||||
todo -= m_socket.write_some(boost::asio::buffer(static_cast<const void*>(&message), message.size()), ec);
|
||||
if(ec)
|
||||
{
|
||||
Log::log(*this, LogMessage::E2007_SOCKET_WRITE_FAILED_X, ec);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -1,56 +0,0 @@
|
||||
/**
|
||||
* server/src/hardware/commandstation/loconettcpbinary.hpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef TRAINTASTIC_SERVER_HARDWARE_COMMANDSTATION_LOCONETTCPBINARY_HPP
|
||||
#define TRAINTASTIC_SERVER_HARDWARE_COMMANDSTATION_LOCONETTCPBINARY_HPP
|
||||
|
||||
#include "commandstation.hpp"
|
||||
#include <boost/asio.hpp>
|
||||
#include "../protocol/loconet/loconet.hpp"
|
||||
|
||||
class LocoNetTCPBinary : public CommandStation
|
||||
{
|
||||
protected:
|
||||
boost::asio::ip::tcp::socket m_socket;
|
||||
std::array<uint8_t, 1024> m_readBuffer;
|
||||
uint16_t m_readBufferOffset;
|
||||
|
||||
bool setOnline(bool& value) final;
|
||||
void emergencyStopChanged(bool value) final;
|
||||
void powerOnChanged(bool value) final;
|
||||
void decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber) final;
|
||||
|
||||
void receive();
|
||||
bool send(const LocoNet::Message& msg);
|
||||
|
||||
public:
|
||||
CLASS_ID("command_station.loconet_tcp_binary")
|
||||
CREATE(LocoNetTCPBinary)
|
||||
|
||||
Property<std::string> hostname;
|
||||
Property<uint16_t> port;
|
||||
ObjectProperty<LocoNet::LocoNet> loconet;
|
||||
|
||||
LocoNetTCPBinary(const std::weak_ptr<World>& world, std::string_view _id);
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -1,524 +0,0 @@
|
||||
/**
|
||||
* server/src/hardware/commandstation/rocoz21.cpp
|
||||
*
|
||||
* This file is part of the traintastic source code
|
||||
*
|
||||
* Copyright (C) 2019-2021 Reinder Feenstra <reinderfeenstra@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "rocoz21.hpp"
|
||||
#include "../../core/traintastic.hpp"
|
||||
#include "../../world/world.hpp"
|
||||
#include "../../core/eventloop.hpp"
|
||||
#include "../../core/attributes.hpp"
|
||||
#include "../decoder/decoderchangeflags.hpp"
|
||||
#include "../protocol/xpressnet/messages.hpp"
|
||||
#include "../protocol/z21/messages.hpp"
|
||||
#include "../../utils/tohex.hpp"
|
||||
#include "../../utils/category.hpp"
|
||||
#include "../../log/log.hpp"
|
||||
#include "../../utils/displayname.hpp"
|
||||
|
||||
#define SET_ADDRESS \
|
||||
if(decoder.longAddress) \
|
||||
{ \
|
||||
cmd.addressHigh = 0xc0 | (decoder.address >> 8); \
|
||||
cmd.addressLow = decoder.address & 0xff; \
|
||||
} \
|
||||
else \
|
||||
{ \
|
||||
cmd.addressHigh = 0; \
|
||||
cmd.addressLow = decoder.address; \
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
RocoZ21::RocoZ21(const std::weak_ptr<World>& world, std::string_view _id) :
|
||||
CommandStation(world, _id),
|
||||
m_socket{Traintastic::instance->ioContext()},
|
||||
hostname{this, "hostname", "", PropertyFlags::ReadWrite | PropertyFlags::Store},
|
||||
port{this, "port", 21105, PropertyFlags::ReadWrite | PropertyFlags::Store},
|
||||
loconet{this, "loconet", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject},
|
||||
serialNumber{this, "serial_number", "", PropertyFlags::ReadOnly},
|
||||
hardwareType{this, "hardware_type", "", PropertyFlags::ReadOnly},
|
||||
firmwareVersion{this, "firmware_version", "", PropertyFlags::ReadOnly},
|
||||
mainCurrent{this, "main_current", std::numeric_limits<float>::quiet_NaN(), PropertyFlags::ReadOnly},
|
||||
progCurrent{this, "prog_current", std::numeric_limits<float>::quiet_NaN(), PropertyFlags::ReadOnly},
|
||||
filteredMainCurrent{this, "filtered_main_current", std::numeric_limits<float>::quiet_NaN(), PropertyFlags::ReadOnly},
|
||||
temperature{this, "temperature", std::numeric_limits<float>::quiet_NaN(), PropertyFlags::ReadOnly},
|
||||
supplyVoltage{this, "supply_voltage", std::numeric_limits<float>::quiet_NaN(), PropertyFlags::ReadOnly},
|
||||
vccVoltage{this, "vcc_voltage", std::numeric_limits<float>::quiet_NaN(), PropertyFlags::ReadOnly},
|
||||
shortCircuit{this, "short_circuit", false, PropertyFlags::ReadOnly},
|
||||
programmingModeActive{this, "programming_mode_active", false, PropertyFlags::ReadOnly},
|
||||
highTemperature{this, "high_temperature", false, PropertyFlags::ReadOnly},
|
||||
powerLost{this, "power_lost", false, PropertyFlags::ReadOnly},
|
||||
shortCircutInternal{this, "short_circut_internal", false, PropertyFlags::ReadOnly},
|
||||
shortCircutExternal{this, "short_circut_external", false, PropertyFlags::ReadOnly}
|
||||
{
|
||||
name = "Z21";
|
||||
loconet.setValueInternal(LocoNet::LocoNet::create(*this, loconet.name(),
|
||||
[/*this*/](const ::LocoNet::Message& /*msg*/)
|
||||
{
|
||||
return false;
|
||||
}));
|
||||
|
||||
Attributes::addDisplayName(hostname, DisplayName::IP::hostname);
|
||||
Attributes::addEnabled(hostname, true);
|
||||
m_interfaceItems.insertBefore(hostname, notes);
|
||||
|
||||
Attributes::addDisplayName(port, DisplayName::IP::port);
|
||||
Attributes::addEnabled(port, true);
|
||||
m_interfaceItems.insertBefore(port, notes);
|
||||
|
||||
Attributes::addDisplayName(loconet, DisplayName::Hardware::loconet);
|
||||
m_interfaceItems.insertBefore(loconet, notes);
|
||||
|
||||
Attributes::addCategory(serialNumber, Category::info);
|
||||
m_interfaceItems.insertBefore(serialNumber, notes);
|
||||
Attributes::addCategory(hardwareType, Category::info);
|
||||
m_interfaceItems.insertBefore(hardwareType, notes);
|
||||
Attributes::addCategory(firmwareVersion, Category::info);
|
||||
m_interfaceItems.insertBefore(firmwareVersion, notes);
|
||||
Attributes::addCategory(mainCurrent, Category::info);
|
||||
m_interfaceItems.insertBefore(mainCurrent, notes);
|
||||
Attributes::addCategory(progCurrent, Category::info);
|
||||
m_interfaceItems.insertBefore(progCurrent, notes);
|
||||
Attributes::addCategory(filteredMainCurrent, Category::info);
|
||||
m_interfaceItems.insertBefore(filteredMainCurrent, notes);
|
||||
Attributes::addCategory(temperature, Category::info);
|
||||
m_interfaceItems.insertBefore(temperature, notes);
|
||||
Attributes::addCategory(supplyVoltage, Category::info);
|
||||
m_interfaceItems.insertBefore(supplyVoltage, notes);
|
||||
Attributes::addCategory(vccVoltage, Category::info);
|
||||
m_interfaceItems.insertBefore(vccVoltage, notes);
|
||||
Attributes::addCategory(shortCircuit, Category::info);
|
||||
m_interfaceItems.insertBefore(shortCircuit, notes);
|
||||
Attributes::addCategory(programmingModeActive, Category::info);
|
||||
m_interfaceItems.insertBefore(programmingModeActive, notes);
|
||||
Attributes::addCategory(highTemperature, Category::info);
|
||||
m_interfaceItems.insertBefore(highTemperature, notes);
|
||||
Attributes::addCategory(powerLost, Category::info);
|
||||
m_interfaceItems.insertBefore(powerLost, notes);
|
||||
Attributes::addCategory(shortCircutInternal, Category::info);
|
||||
m_interfaceItems.insertBefore(shortCircutInternal, notes);
|
||||
Attributes::addCategory(shortCircutExternal, Category::info);
|
||||
m_interfaceItems.insertBefore(shortCircutExternal, notes);
|
||||
}
|
||||
|
||||
void RocoZ21::emergencyStopChanged(bool value)
|
||||
{
|
||||
if(online && value)
|
||||
send(Z21::LanXSetStop());
|
||||
}
|
||||
|
||||
void RocoZ21::powerOnChanged(bool value)
|
||||
{
|
||||
if(online)
|
||||
{
|
||||
if(value)
|
||||
send(Z21::LanXSetTrackPowerOn());
|
||||
else
|
||||
send(Z21::LanXSetTrackPowerOff());
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
bool RocoZ21::isDecoderSupported(Decoder& decoder) const
|
||||
{
|
||||
return
|
||||
decoder.protocol == DecoderProtocol::DCC &&
|
||||
decoder.address >= 1 &&
|
||||
decoder.address <= (decoder.longAddress ? 9999 : 127);
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
void RocoZ21::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber)
|
||||
{
|
||||
if(has(changes, DecoderChangeFlags::EmergencyStop | DecoderChangeFlags::Direction | DecoderChangeFlags::Throttle | DecoderChangeFlags::SpeedSteps))
|
||||
{
|
||||
Z21::LanXSetLocoDrive cmd;
|
||||
//cmd.dataLen = sizeof(cmd);
|
||||
//cmd.header = Z21::LAN_X;
|
||||
SET_ADDRESS;
|
||||
|
||||
switch(decoder.speedSteps)
|
||||
{
|
||||
case 14:
|
||||
{
|
||||
const uint8_t speedStep = Decoder::throttleToSpeedStep(decoder.throttle, 14);
|
||||
cmd.db0 = 0x10;
|
||||
if(decoder.emergencyStop)
|
||||
cmd.speedAndDirection = 0x01;
|
||||
else if(speedStep > 0)
|
||||
cmd.speedAndDirection = speedStep + 1;
|
||||
break;
|
||||
}
|
||||
case 28:
|
||||
{
|
||||
uint8_t speedStep = Decoder::throttleToSpeedStep(decoder.throttle, 28);
|
||||
cmd.db0 = 0x12;
|
||||
if(decoder.emergencyStop)
|
||||
cmd.speedAndDirection = 0x01;
|
||||
else if(speedStep > 0)
|
||||
{
|
||||
speedStep++;
|
||||
cmd.speedAndDirection = ((speedStep & 0x01) << 4) | (speedStep >> 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 126:
|
||||
case 128:
|
||||
default:
|
||||
{
|
||||
const uint8_t speedStep = Decoder::throttleToSpeedStep(decoder.throttle, 126);
|
||||
cmd.db0 = 0x13;
|
||||
if(decoder.emergencyStop)
|
||||
cmd.speedAndDirection = 0x01;
|
||||
else if(speedStep > 0)
|
||||
cmd.speedAndDirection = speedStep + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(decoder.direction.value() == Direction::Forward)
|
||||
cmd.speedAndDirection |= 0x80;
|
||||
|
||||
cmd.checksum = XpressNet::calcChecksum(*reinterpret_cast<const XpressNet::Message*>(&cmd.xheader));
|
||||
send(cmd);
|
||||
}
|
||||
else if(has(changes, DecoderChangeFlags::FunctionValue))
|
||||
{
|
||||
if(functionNumber <= 28)
|
||||
{
|
||||
if(const auto& f = decoder.getFunction(functionNumber))
|
||||
{
|
||||
Z21::LanXSetLocoFunction cmd;
|
||||
//cmd.dataLen = sizeof(cmd);
|
||||
//cmd.header = Z21_LAN_X;
|
||||
SET_ADDRESS;
|
||||
cmd.db3 = (f->value ? 0x40 : 0x00) | static_cast<uint8_t>(functionNumber);
|
||||
cmd.checksum = XpressNet::calcChecksum(*reinterpret_cast<const XpressNet::Message*>(&cmd.xheader));
|
||||
send(cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool RocoZ21::setOnline(bool& value)
|
||||
{
|
||||
if(!m_socket.is_open() && value)
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
|
||||
m_remoteEndpoint.port(port);
|
||||
m_remoteEndpoint.address(boost::asio::ip::make_address(hostname, ec));
|
||||
if(ec)
|
||||
{
|
||||
Log::log(*this, LogMessage::E2003_MAKE_ADDRESS_FAILED_X, ec);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(m_socket.open(boost::asio::ip::udp::v4(), ec))
|
||||
{
|
||||
Log::log(*this, LogMessage::E2004_SOCKET_OPEN_FAILED_X, ec);
|
||||
return false;
|
||||
}
|
||||
else if(m_socket.bind(boost::asio::ip::udp::endpoint(boost::asio::ip::address_v4::any(), port), ec))
|
||||
{
|
||||
m_socket.close();
|
||||
Log::log(*this, LogMessage::E2006_SOCKET_BIND_FAILED_X, ec);
|
||||
return false;
|
||||
}
|
||||
|
||||
receive(); // start receiving messages
|
||||
|
||||
send(Z21::LanSetBroadcastFlags(0x07000000 | /*0x00010000 |*/ 0x00000100 | 0x00000001));
|
||||
|
||||
// try to communicate with Z21
|
||||
|
||||
send(Z21::LanGetSerialNumber());
|
||||
send(Z21::LanGetHardwareInfo());
|
||||
send(Z21::LanGetBroadcastFlags());
|
||||
send(Z21::LanSystemStateGetData());
|
||||
for(auto& decoder : *decoders)
|
||||
send(Z21::LanXGetLocoInfo(decoder->address, decoder->longAddress));
|
||||
|
||||
Attributes::setEnabled(hostname, false);
|
||||
Attributes::setEnabled(port, false);
|
||||
}
|
||||
else if(m_socket.is_open() && !value)
|
||||
{
|
||||
send(Z21::LanLogoff());
|
||||
|
||||
serialNumber.setValueInternal("");
|
||||
hardwareType.setValueInternal("");
|
||||
firmwareVersion.setValueInternal("");
|
||||
|
||||
Attributes::setEnabled(hostname, true);
|
||||
Attributes::setEnabled(port, true);
|
||||
|
||||
m_socket.close();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void RocoZ21::receive()
|
||||
{
|
||||
m_socket.async_receive_from(boost::asio::buffer(m_receiveBuffer), m_receiveEndpoint,
|
||||
[this](const boost::system::error_code& ec, std::size_t bytesReceived)
|
||||
{
|
||||
if(!ec)
|
||||
{
|
||||
if((bytesReceived >= sizeof(Z21::Message)))
|
||||
{
|
||||
const Z21::Message* message = reinterpret_cast<const Z21::Message*>(m_receiveBuffer.data());
|
||||
//const z21_lan_header* cmd = reinterpret_cast<const z21_lan_header*>(m_receiveBuffer.data());
|
||||
switch(message->header())
|
||||
{
|
||||
case Z21::LAN_GET_SERIAL_NUMBER:
|
||||
if(message->dataLen() == sizeof(Z21::LanGetSerialNumberReply))
|
||||
{
|
||||
EventLoop::call(
|
||||
[this, value=std::to_string(static_cast<const Z21::LanGetSerialNumberReply*>(message)->serialNumber())]()
|
||||
{
|
||||
serialNumber.setValueInternal(value);
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case Z21::LAN_GET_HWINFO:
|
||||
{
|
||||
const Z21::LanGetHardwareInfoReply* reply = static_cast<const Z21::LanGetHardwareInfoReply*>(message);
|
||||
|
||||
std::string hwType;
|
||||
switch(reply->hardwareType())
|
||||
{
|
||||
case Z21::HWT_Z21_OLD:
|
||||
hwType = "Black Z21 (hardware variant from 2012)";
|
||||
break;
|
||||
case Z21::HWT_Z21_NEW:
|
||||
hwType = "Black Z21 (hardware variant from 2013)";
|
||||
break;
|
||||
case Z21::HWT_SMARTRAIL:
|
||||
hwType = "SmartRail (from 2012)";
|
||||
break;
|
||||
case Z21::HWT_Z21_SMALL:
|
||||
hwType = "White Z21 (starter set variant from 2013)";
|
||||
break;
|
||||
case Z21::HWT_Z21_START :
|
||||
hwType = "Z21 start (starter set variant from 2016)";
|
||||
break;
|
||||
default:
|
||||
hwType = "0x" + toHex(reply->hardwareType());
|
||||
break;
|
||||
}
|
||||
|
||||
const std::string fwVersion = std::to_string(reply->firmwareVersionMajor()) + "." + std::to_string(reply->firmwareVersionMinor());
|
||||
|
||||
EventLoop::call(
|
||||
[this, hwType, fwVersion]()
|
||||
{
|
||||
hardwareType.setValueInternal(hwType);
|
||||
firmwareVersion.setValueInternal(fwVersion);
|
||||
});
|
||||
break;
|
||||
}
|
||||
case Z21::LAN_X:
|
||||
{
|
||||
// TODO check XOR
|
||||
const uint8_t xheader = static_cast<const Z21::LanX*>(message)->xheader;
|
||||
switch(xheader)
|
||||
{
|
||||
case Z21::LAN_X_LOCO_INFO:
|
||||
{
|
||||
const Z21::LanXLocoInfo* info = static_cast<const Z21::LanXLocoInfo*>(message);
|
||||
const uint16_t address = (static_cast<uint16_t>(info->addressHigh) << 8) | info->addressLow;
|
||||
const uint8_t speedStepMode = info->db2 & 0x07;
|
||||
const Direction direction = (info->speedAndDirection & 0x80) ? Direction::Forward : Direction::Reverse;
|
||||
const uint8_t speedStep = info->speedAndDirection & 0x7f;
|
||||
const uint32_t functions =
|
||||
((info->db4 & 0x10) >> 4) |
|
||||
((info->db4 & 0x0f) << 1) |
|
||||
(static_cast<uint32_t>(info->f5f12) << 5) |
|
||||
(static_cast<uint32_t>(info->f13f20) << 13) |
|
||||
(static_cast<uint32_t>(info->f21f28) << 21);
|
||||
|
||||
EventLoop::call(
|
||||
[this, address, speedStepMode, direction, speedStep, functions]()
|
||||
{
|
||||
const std::shared_ptr<Decoder>& decoder = getDecoder(DecoderProtocol::DCC, address & 0x3fff, address & 0xc000);
|
||||
if(decoder)
|
||||
{
|
||||
decoder->direction = direction;
|
||||
switch(speedStepMode)
|
||||
{
|
||||
case 0:
|
||||
decoder->throttle.setValueInternal(Decoder::speedStepToThrottle(speedStep, 14));
|
||||
break;
|
||||
|
||||
case 2:
|
||||
decoder->throttle.setValueInternal(Decoder::speedStepToThrottle(speedStep, 28));
|
||||
break;
|
||||
|
||||
case 4:
|
||||
decoder->throttle.setValueInternal(Decoder::speedStepToThrottle(speedStep, 126));
|
||||
break;
|
||||
}
|
||||
|
||||
for(auto& function : *decoder->functions)
|
||||
{
|
||||
const uint8_t number = function->number;
|
||||
if(number <= 28)
|
||||
function->value.setValueInternal(functions & (1 << number));
|
||||
}
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
case Z21::LAN_X_BC:
|
||||
{
|
||||
|
||||
break;
|
||||
}
|
||||
case Z21::LAN_X_BC_STOPPED:
|
||||
EventLoop::call(
|
||||
[this]()
|
||||
{
|
||||
emergencyStop.setValueInternal(true);
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
EventLoop::call([this, xheader](){ Log::log(*this, LogMessage::D2003_UNKNOWN_XHEADER_0XX, toHex(xheader)); });
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Z21::LAN_SYSTEMSTATE_DATACHANGED:
|
||||
{
|
||||
/*
|
||||
const Z21::LanSystemStateDataChanged state = *reinterpret_cast<const Z21::LanSystemStateDataChanged*>(m_receiveBuffer.data());
|
||||
EventLoop::call(
|
||||
[this, state]()
|
||||
{
|
||||
mainCurrent.setValueInternal(state.mainCurrent / 1e3); //!< Current on the main track in mA
|
||||
progCurrent.setValueInternal(state.progCurrent / 1e3); //!< Current on programming track in mA;
|
||||
filteredMainCurrent.setValueInternal(state.filteredMainCurrent / 1e3); //!< Smoothed current on the main track in mA
|
||||
temperature.setValueInternal(state.temperature); //!< Command station internal temperature in °C
|
||||
supplyVoltage.setValueInternal(state.supplyVoltage / 1e3); //!< Supply voltage in mV
|
||||
vccVoltage.setValueInternal(state.vccVoltage / 1e3); //!< Internal voltage, identical to track voltage in mV
|
||||
emergencyStop.setValueInternal(state.centralState & Z21_CENTRALSTATE_EMERGENCYSTOP);
|
||||
trackVoltageOff.setValueInternal(state.centralState & Z21_CENTRALSTATE_TRACKVOLTAGEOFF);
|
||||
shortCircuit.setValueInternal(state.centralState & Z21_CENTRALSTATE_SHORTCIRCUIT);
|
||||
programmingModeActive.setValueInternal(state.centralState & Z21_CENTRALSTATE_PROGRAMMINGMODEACTIVE);
|
||||
highTemperature.setValueInternal(state.centralStateEx & Z21_CENTRALSTATEEX_HIGHTEMPERATURE);
|
||||
powerLost.setValueInternal(state.centralStateEx & Z21_CENTRALSTATEEX_POWERLOST);
|
||||
shortCircutInternal.setValueInternal(state.centralStateEx & Z21_CENTRALSTATEEX_SHORTCIRCUITEXTERNAL);
|
||||
shortCircutExternal.setValueInternal(state.centralStateEx & Z21_CENTRALSTATEEX_SHORTCIRCUITINTERNAL);
|
||||
});
|
||||
*/
|
||||
break;
|
||||
}
|
||||
case Z21::LAN_LOCONET_Z21_RX:
|
||||
//case Z21_LAN_LOCONET_Z21_TX:
|
||||
//case Z21_LAN_LOCONET_Z21_LAN:
|
||||
loconet->receive(*reinterpret_cast<const ::LocoNet::Message*>(m_receiveBuffer.data() + sizeof(Z21::Message)));
|
||||
break;
|
||||
/*
|
||||
|
||||
using LocoNet = LocoNet;
|
||||
|
||||
const LocoNet::Header* message = reinterpret_cast<const LocoNet::Header*>(m_receiveBuffer.data() + sizeof(z21_lan_header));
|
||||
|
||||
switch(message->opCode)
|
||||
{
|
||||
case LocoNet::OPC_INPUT_REP:
|
||||
{
|
||||
const LocoNet::InputRep* inputRep = static_cast<const LocoNet::InputRep*>(message);
|
||||
|
||||
//if(debugEnabled)
|
||||
{
|
||||
const std::string message = "loconet rx OPC_INPUT_REP:"
|
||||
" address=" + std::to_string(inputRep->address()) +
|
||||
" input=" + (inputRep->isAuxInput() ? "aux" : "switch") +
|
||||
" value=" + (inputRep->value() ? "high" : "low");
|
||||
}
|
||||
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
default:
|
||||
//if(debugEnabled)
|
||||
{
|
||||
std::string message = "unknown loconet message: ";
|
||||
for(int i = 4; i < cmd->dataLen; i++)
|
||||
message += toHex(reinterpret_cast<const uint8_t*>(cmd)[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
*/
|
||||
|
||||
default:
|
||||
//if(debugEnabled)
|
||||
{
|
||||
std::string data = "dataLen=0x" + toHex(message->dataLen()) + ", header=0x" + toHex(message->header());
|
||||
if(message->dataLen() > 4)
|
||||
{
|
||||
data += ", data=";
|
||||
for(int i = sizeof(Z21::Message); i < message->dataLen(); i++)
|
||||
data += toHex(reinterpret_cast<const uint8_t*>(message)[i]);
|
||||
}
|
||||
EventLoop::call([this, data](){ Log::log(*this, LogMessage::D2006_UNKNOWN_MESSAGE_X, data); });
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
receive();
|
||||
}
|
||||
else
|
||||
EventLoop::call([this, ec](){ Log::log(*this, LogMessage::E2009_SOCKET_RECEIVE_FAILED_X, ec); });
|
||||
});
|
||||
}
|
||||
|
||||
void RocoZ21::send(const Z21::Message& message)
|
||||
{
|
||||
// TODO async
|
||||
|
||||
// TODO: add to queue, send async
|
||||
|
||||
boost::system::error_code ec;
|
||||
m_socket.send_to(boost::asio::buffer(&message, message.dataLen()), m_remoteEndpoint, 0, ec);
|
||||
if(ec)
|
||||
Log::log(*this, LogMessage::E2011_SOCKET_SEND_FAILED_X, ec);
|
||||
/*
|
||||
m_socket.send_to(boost::asio:buffer(&message, message.dataLen()), 0, m_remoteEndpoint);,
|
||||
[this](const boost::system::error_code& ec, std::size_t)
|
||||
{
|
||||
if(ec)
|
||||
EventLoop::call([this, ec](){ Log::log(*this, LogMessage::E2011_SOCKET_SEND_FAILED_X, ec); });
|
||||
});*/
|
||||
}
|
||||
@ -1,84 +0,0 @@
|
||||
/**
|
||||
* server/src/hardware/commandstation/rocoz21.hpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef TRAINTASTIC_SERVER_HARDWARE_COMMANDSTATION_ROCOZ21_HPP
|
||||
#define TRAINTASTIC_SERVER_HARDWARE_COMMANDSTATION_ROCOZ21_HPP
|
||||
|
||||
#include "commandstation.hpp"
|
||||
#include <boost/asio.hpp>
|
||||
#include "../../core/objectproperty.hpp"
|
||||
#include "../protocol/loconet/loconet.hpp"
|
||||
|
||||
struct z21_lan_header;
|
||||
|
||||
namespace Z21 {
|
||||
class Message;
|
||||
}
|
||||
|
||||
class RocoZ21 : public CommandStation
|
||||
{
|
||||
protected:
|
||||
boost::asio::ip::udp::socket m_socket;
|
||||
boost::asio::ip::udp::endpoint m_remoteEndpoint;
|
||||
boost::asio::ip::udp::endpoint m_receiveEndpoint;
|
||||
std::array<uint8_t,64> m_receiveBuffer;
|
||||
|
||||
bool setOnline(bool& value) final;
|
||||
//bool isDecoderSupported(Decoder& decoder) const final;
|
||||
void emergencyStopChanged(bool value) final;
|
||||
void powerOnChanged(bool value) final;
|
||||
void decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber) final;
|
||||
|
||||
void receive();
|
||||
void send(const Z21::Message& message);
|
||||
void send(const z21_lan_header* msg);
|
||||
inline void send(const z21_lan_header& msg) { send(&msg); }
|
||||
|
||||
public:
|
||||
CLASS_ID("command_station.z21")
|
||||
CREATE(RocoZ21)
|
||||
|
||||
RocoZ21(const std::weak_ptr<World>& world, std::string_view _id);
|
||||
|
||||
Property<std::string> hostname;
|
||||
Property<uint16_t> port;
|
||||
ObjectProperty<LocoNet::LocoNet> loconet;
|
||||
Property<std::string> serialNumber;
|
||||
Property<std::string> hardwareType;
|
||||
Property<std::string> firmwareVersion;
|
||||
Property<float> mainCurrent;
|
||||
Property<float> progCurrent;
|
||||
Property<float> filteredMainCurrent;
|
||||
Property<float> temperature;
|
||||
Property<float> supplyVoltage;
|
||||
Property<float> vccVoltage;
|
||||
//Property<bool> emergencyStop;
|
||||
//Property<bool> trackVoltageOff;
|
||||
Property<bool> shortCircuit;
|
||||
Property<bool> programmingModeActive;
|
||||
Property<bool> highTemperature;
|
||||
Property<bool> powerLost;
|
||||
Property<bool> shortCircutInternal;
|
||||
Property<bool> shortCircutExternal;
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -1,133 +0,0 @@
|
||||
/**
|
||||
* server/src/hardware/commandstation/serialcommandstation.hpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "serialcommandstation.hpp"
|
||||
#include "../../core/traintastic.hpp"
|
||||
#include "../../core/eventloop.hpp"
|
||||
#include "../../core/attributes.hpp"
|
||||
#include "../../log/log.hpp"
|
||||
#include "../../utils/displayname.hpp"
|
||||
|
||||
SerialCommandStation::SerialCommandStation(const std::weak_ptr<World>& world, std::string_view _id) :
|
||||
CommandStation(world, _id),
|
||||
m_serialPort{Traintastic::instance->ioContext()},
|
||||
m_readBufferOffset{0},
|
||||
port{this, "port", "/dev/ttyUSB0", PropertyFlags::ReadWrite | PropertyFlags::Store},
|
||||
baudrate{this, "baudrate", 19200, PropertyFlags::ReadWrite | PropertyFlags::Store,
|
||||
[this](uint32_t)
|
||||
{
|
||||
//interface = LocoNetSerialInterface::Custom;
|
||||
}},
|
||||
flowControl{this, "flow_control", SerialFlowControl::None, PropertyFlags::ReadWrite | PropertyFlags::Store,
|
||||
[this](SerialFlowControl)
|
||||
{
|
||||
//interface = LocoNetSerialInterface::Custom;
|
||||
}}
|
||||
{
|
||||
Attributes::addDisplayName(port, DisplayName::Serial::port);
|
||||
Attributes::addEnabled(port, !online);
|
||||
m_interfaceItems.insertBefore(port, notes);
|
||||
|
||||
Attributes::addDisplayName(baudrate, DisplayName::Serial::baudrate);
|
||||
Attributes::addEnabled(baudrate, !online);
|
||||
m_interfaceItems.insertBefore(baudrate, notes);
|
||||
|
||||
Attributes::addDisplayName(flowControl, DisplayName::Serial::flowControl);
|
||||
Attributes::addEnabled(flowControl, !online);
|
||||
Attributes::addValues(flowControl, SerialFlowControlValues);
|
||||
m_interfaceItems.insertBefore(flowControl, notes);
|
||||
}
|
||||
|
||||
void SerialCommandStation::loaded()
|
||||
{
|
||||
CommandStation::loaded();
|
||||
updateEnabled();
|
||||
}
|
||||
|
||||
void SerialCommandStation::worldEvent(WorldState state, WorldEvent event)
|
||||
{
|
||||
CommandStation::worldEvent(state, event);
|
||||
if(event == WorldEvent::EditEnabled || event == WorldEvent::EditDisabled)
|
||||
updateEnabled();
|
||||
}
|
||||
|
||||
bool SerialCommandStation::setOnline(bool& value)
|
||||
{
|
||||
if(!m_serialPort.is_open() && value)
|
||||
{
|
||||
if(!start())
|
||||
{
|
||||
value = false;
|
||||
return false;
|
||||
}
|
||||
m_readBufferOffset = 0;
|
||||
read();
|
||||
started();
|
||||
}
|
||||
else if(m_serialPort.is_open() && !value)
|
||||
stop();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SerialCommandStation::start()
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
m_serialPort.open(port, ec);
|
||||
if(ec)
|
||||
{
|
||||
Log::log(*this, LogMessage::E2010_SERIAL_PORT_OPEN_FAILED_X, ec);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_serialPort.set_option(boost::asio::serial_port_base::baud_rate(baudrate));
|
||||
m_serialPort.set_option(boost::asio::serial_port_base::character_size(8));
|
||||
m_serialPort.set_option(boost::asio::serial_port_base::stop_bits(boost::asio::serial_port_base::stop_bits::one));
|
||||
m_serialPort.set_option(boost::asio::serial_port_base::parity(boost::asio::serial_port_base::parity::none));
|
||||
switch(flowControl)
|
||||
{
|
||||
case SerialFlowControl::None:
|
||||
m_serialPort.set_option(boost::asio::serial_port_base::flow_control(boost::asio::serial_port_base::flow_control::none));
|
||||
break;
|
||||
|
||||
case SerialFlowControl::Hardware:
|
||||
m_serialPort.set_option(boost::asio::serial_port_base::flow_control(boost::asio::serial_port_base::flow_control::hardware));
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void SerialCommandStation::stop()
|
||||
{
|
||||
// TODO: send power off cmd??
|
||||
m_serialPort.close();
|
||||
}
|
||||
|
||||
void SerialCommandStation::updateEnabled()
|
||||
{
|
||||
auto w = m_world.lock();
|
||||
bool enabled = w && contains(w->state.value(), WorldState::Edit) && !online;
|
||||
|
||||
Attributes::setEnabled(port, enabled);
|
||||
Attributes::setEnabled(baudrate, enabled);
|
||||
Attributes::setEnabled(flowControl, enabled);
|
||||
}
|
||||
@ -1,59 +0,0 @@
|
||||
/**
|
||||
* server/src/hardware/commandstation/serialcommandstation.hpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef TRAINTASTIC_SERVER_HARDWARE_COMMANDSTATION_SERIALCOMMANDSTATION_HPP
|
||||
#define TRAINTASTIC_SERVER_HARDWARE_COMMANDSTATION_SERIALCOMMANDSTATION_HPP
|
||||
|
||||
#include "commandstation.hpp"
|
||||
#include "../../enum/serialflowcontrol.hpp"
|
||||
#include <boost/asio/serial_port.hpp>
|
||||
|
||||
class SerialCommandStation : public CommandStation
|
||||
{
|
||||
private:
|
||||
void updateEnabled();
|
||||
|
||||
protected:
|
||||
boost::asio::serial_port m_serialPort;
|
||||
std::array<uint8_t, 1024> m_readBuffer;
|
||||
uint16_t m_readBufferOffset;
|
||||
|
||||
void loaded() override;
|
||||
void worldEvent(WorldState state, WorldEvent event) override;
|
||||
|
||||
bool setOnline(bool& value) final;
|
||||
|
||||
bool start();
|
||||
virtual void stop();
|
||||
|
||||
virtual void started() {}
|
||||
virtual void read() = 0;
|
||||
|
||||
public:
|
||||
Property<std::string> port;
|
||||
Property<uint32_t> baudrate;
|
||||
Property<SerialFlowControl> flowControl;
|
||||
|
||||
SerialCommandStation(const std::weak_ptr<World>& world, std::string_view _id);
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -1,112 +0,0 @@
|
||||
/**
|
||||
* server/src/hardware/commandstation/usbxpressnetinterface.cpp
|
||||
*
|
||||
* Copyright (C) 2019-2021 Reinder Feenstra <reinderfeenstra@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "usbxpressnetinterface.hpp"
|
||||
#include "../../core/traintastic.hpp"
|
||||
#include "../../world/world.hpp"
|
||||
|
||||
USBXpressNetInterface::USBXpressNetInterface(const std::weak_ptr<World>& world, std::string_view _id) :
|
||||
CommandStation(world, _id),
|
||||
m_handle{nullptr},
|
||||
serial{this, "serial", "", PropertyFlags::ReadWrite | PropertyFlags::Store},
|
||||
address{this, "address", 31, PropertyFlags::ReadWrite | PropertyFlags::Store},
|
||||
xpressnet{this, "xpressnet", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject}
|
||||
{
|
||||
name = "USB XpressNet interface";
|
||||
xpressnet.setValueInternal(std::make_shared<XpressNet::XpressNet>(*this, xpressnet.name(), std::bind(&USBXpressNetInterface::send, this, std::placeholders::_1)));
|
||||
|
||||
m_interfaceItems.insertBefore(serial, notes);
|
||||
m_interfaceItems.insertBefore(address, notes);
|
||||
m_interfaceItems.insertBefore(xpressnet, notes);
|
||||
|
||||
usbxpressnet_init();
|
||||
}
|
||||
|
||||
USBXpressNetInterface::~USBXpressNetInterface()
|
||||
{
|
||||
if(m_handle)
|
||||
{
|
||||
usbxpressnet_reset(m_handle);
|
||||
usbxpressnet_close(m_handle);
|
||||
}
|
||||
usbxpressnet_fini();
|
||||
}
|
||||
|
||||
bool USBXpressNetInterface::setOnline(bool& value)
|
||||
{
|
||||
if(!m_handle && value)
|
||||
{
|
||||
usbxpressnet_status status = usbxpressnet_open(!serial.value().empty() ? serial.value().c_str() : nullptr, &m_handle);
|
||||
if(status != USBXPRESSNET_STATUS_SUCCESS)
|
||||
{
|
||||
// \todo Log::log(*this, LogMessage::E0001_X, std::string("usbxpressnet_open: ") + usbxpressnet_status_str(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
status = usbxpressnet_reset(m_handle);
|
||||
if(status != USBXPRESSNET_STATUS_SUCCESS)
|
||||
{
|
||||
// \todo Log::log(*this, LogMessage::E0001_X, std::string("usbxpressnet_reset: ") + usbxpressnet_status_str(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
status = usbxpressnet_set_mode(m_handle, USBXPRESSNET_MODE_DEVICE, address);
|
||||
if(status != USBXPRESSNET_STATUS_SUCCESS)
|
||||
{
|
||||
// \todo Log::log(*this, LogMessage::E0001_X, std::string("usbxpressnet_set_mode: ") + usbxpressnet_status_str(status));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if(m_handle && !value)
|
||||
{
|
||||
usbxpressnet_close(m_handle);
|
||||
m_handle = nullptr;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void USBXpressNetInterface::emergencyStopChanged(bool value)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void USBXpressNetInterface::powerOnChanged(bool value)
|
||||
{
|
||||
}
|
||||
|
||||
void USBXpressNetInterface::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber)
|
||||
{
|
||||
if(online)
|
||||
xpressnet->decoderChanged(decoder, changes, functionNumber);
|
||||
}
|
||||
|
||||
bool USBXpressNetInterface::send(const XpressNet::Message& msg)
|
||||
{
|
||||
assert(XpressNet::isChecksumValid(msg));
|
||||
if(!m_handle)
|
||||
return false;
|
||||
usbxpressnet_status status;
|
||||
if((status = usbxpressnet_send_message(m_handle, &msg)) != USBXPRESSNET_STATUS_SUCCESS)
|
||||
{
|
||||
Traintastic::instance->console->critical(id, std::string("usbxpressnet_send_message: ") + usbxpressnet_status_str(status));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -1,57 +0,0 @@
|
||||
/**
|
||||
* server/src/hardware/commandstation/usbxpressnetinterface.hpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef TRAINTASTIC_SERVER_HARDWARE_COMMANDSTATION_USBXPRESSNETINTERFACE_HPP
|
||||
#define TRAINTASTIC_SERVER_HARDWARE_COMMANDSTATION_USBXPRESSNETINTERFACE_HPP
|
||||
|
||||
#include "commandstation.hpp"
|
||||
#include "../protocol/xpressnet/xpressnet.hpp"
|
||||
#include <usbxpressnet.h>
|
||||
|
||||
class USBXpressNetInterface : public CommandStation
|
||||
{
|
||||
protected:
|
||||
static const uint8_t addressMin = 1;
|
||||
static const uint8_t addressMax = 31;
|
||||
|
||||
usbxpressnet_handle m_handle;
|
||||
|
||||
bool setOnline(bool& value) final;
|
||||
void emergencyStopChanged(bool value) final;
|
||||
void powerOnChanged(bool value) final;
|
||||
void decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber) final;
|
||||
|
||||
bool send(const XpressNet::Message& msg);
|
||||
|
||||
public:
|
||||
CLASS_ID("command_station.usb_xpressnet_interface")
|
||||
CREATE(USBXpressNetInterface)
|
||||
|
||||
Property<std::string> serial;
|
||||
Property<uint8_t> address;
|
||||
ObjectProperty<XpressNet::XpressNet> xpressnet;
|
||||
|
||||
USBXpressNetInterface(const std::weak_ptr<World>& world, std::string_view _id);
|
||||
~USBXpressNetInterface() final;
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -1,273 +0,0 @@
|
||||
/**
|
||||
* hardware/commandstation/xpressnetserial.cpp
|
||||
*
|
||||
* This file is part of the traintastic source code
|
||||
*
|
||||
* Copyright (C) 2019-2021 Reinder Feenstra <reinderfeenstra@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "xpressnetserial.hpp"
|
||||
#include "../../world/world.hpp"
|
||||
#include "../../core/traintastic.hpp"
|
||||
#include "../../core/eventloop.hpp"
|
||||
#include "../../core/attributes.hpp"
|
||||
#include "../../log/log.hpp"
|
||||
#include "../../utils/displayname.hpp"
|
||||
|
||||
XpressNetSerial::XpressNetSerial(const std::weak_ptr<World>& world, std::string_view _id) :
|
||||
SerialCommandStation(world, _id),
|
||||
interface{this, "interface", XpressNetSerialInterface::LenzLI100, PropertyFlags::ReadWrite | PropertyFlags::Store,
|
||||
[this](XpressNetSerialInterface value)
|
||||
{
|
||||
switch(value)
|
||||
{
|
||||
case XpressNetSerialInterface::Custom:
|
||||
break;
|
||||
|
||||
case XpressNetSerialInterface::LenzLI100:
|
||||
case XpressNetSerialInterface::RoSoftS88XPressNetLI:
|
||||
baudrate.setValueInternal(9600);
|
||||
flowControl.setValueInternal(SerialFlowControl::Hardware);
|
||||
break;
|
||||
|
||||
case XpressNetSerialInterface::LenzLI100F:
|
||||
baudrate.setValueInternal(19200);
|
||||
flowControl.setValueInternal(SerialFlowControl::Hardware);
|
||||
break;
|
||||
|
||||
case XpressNetSerialInterface::LenzLI101F:
|
||||
baudrate.setValueInternal(19200);
|
||||
flowControl.setValueInternal(SerialFlowControl::Hardware);
|
||||
break;
|
||||
}
|
||||
updateEnabled();
|
||||
updateVisible();
|
||||
}},
|
||||
xpressnet{this, "xpressnet", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject},
|
||||
s88StartAddress{this, "s88_start_address", XpressNet::RoSoftS88XpressNetLI::S88StartAddress::startAddressDefault, PropertyFlags::ReadWrite | PropertyFlags::Store},
|
||||
s88ModuleCount{this, "s88_module_count", XpressNet::RoSoftS88XpressNetLI::S88ModuleCount::moduleCountDefault, PropertyFlags::ReadWrite | PropertyFlags::Store}
|
||||
{
|
||||
name = "XpressNet (serial)";
|
||||
xpressnet.setValueInternal(std::make_shared<XpressNet::XpressNet>(*this, xpressnet.name(), std::bind(&XpressNetSerial::send, this, std::placeholders::_1)));
|
||||
|
||||
Attributes::addValues(interface, XpressNetSerialInterfaceValues);
|
||||
Attributes::addEnabled(interface, !online);
|
||||
m_interfaceItems.insertBefore(interface, baudrate);
|
||||
|
||||
Attributes::addDisplayName(xpressnet, DisplayName::Hardware::xpressnet);
|
||||
m_interfaceItems.insertBefore(xpressnet, notes);
|
||||
|
||||
Attributes::addMinMax(s88StartAddress, XpressNet::RoSoftS88XpressNetLI::S88StartAddress::startAddressMin, XpressNet::RoSoftS88XpressNetLI::S88StartAddress::startAddressMax);
|
||||
Attributes::addEnabled(s88StartAddress, !online);
|
||||
Attributes::addVisible(s88StartAddress, false);
|
||||
m_interfaceItems.insertBefore(s88StartAddress, notes);
|
||||
|
||||
Attributes::addMinMax(s88ModuleCount, XpressNet::RoSoftS88XpressNetLI::S88ModuleCount::moduleCountMin, XpressNet::RoSoftS88XpressNetLI::S88ModuleCount::moduleCountMax);
|
||||
Attributes::addEnabled(s88ModuleCount, !online);
|
||||
Attributes::addVisible(s88ModuleCount, false);
|
||||
m_interfaceItems.insertBefore(s88ModuleCount, notes);
|
||||
|
||||
updateEnabled();
|
||||
updateVisible();
|
||||
}
|
||||
|
||||
void XpressNetSerial::loaded()
|
||||
{
|
||||
SerialCommandStation::loaded();
|
||||
updateEnabled();
|
||||
updateVisible();
|
||||
}
|
||||
|
||||
void XpressNetSerial::worldEvent(WorldState state, WorldEvent event)
|
||||
{
|
||||
SerialCommandStation::worldEvent(state, event);
|
||||
if(event == WorldEvent::EditEnabled || event == WorldEvent::EditDisabled)
|
||||
updateEnabled();
|
||||
}
|
||||
|
||||
void XpressNetSerial::emergencyStopChanged(bool value)
|
||||
{
|
||||
CommandStation::emergencyStopChanged(value);
|
||||
|
||||
if(online)
|
||||
xpressnet->emergencyStopChanged(value);
|
||||
}
|
||||
|
||||
void XpressNetSerial::powerOnChanged(bool value)
|
||||
{
|
||||
CommandStation::powerOnChanged(value);
|
||||
|
||||
if(online)
|
||||
xpressnet->powerOnChanged(value);
|
||||
}
|
||||
|
||||
void XpressNetSerial::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber)
|
||||
{
|
||||
CommandStation::decoderChanged(decoder, changes, functionNumber);
|
||||
|
||||
if(online)
|
||||
xpressnet->decoderChanged(decoder, changes, functionNumber);
|
||||
}
|
||||
|
||||
bool XpressNetSerial::send(const XpressNet::Message& msg)
|
||||
{
|
||||
assert(XpressNet::isChecksumValid(msg));
|
||||
if(!m_serialPort.is_open())
|
||||
return false;
|
||||
boost::system::error_code ec;
|
||||
m_serialPort.write_some(boost::asio::buffer(static_cast<const void*>(&msg), msg.size()), ec); // TODO async
|
||||
if(ec)
|
||||
{
|
||||
Log::log(*this, LogMessage::E2001_SERIAL_WRITE_FAILED_X, ec);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void XpressNetSerial::stop()
|
||||
{
|
||||
SerialCommandStation::stop();
|
||||
updateEnabled();
|
||||
}
|
||||
|
||||
void XpressNetSerial::started()
|
||||
{
|
||||
SerialCommandStation::started();
|
||||
|
||||
updateEnabled();
|
||||
|
||||
switch(interface.value())
|
||||
{
|
||||
case XpressNetSerialInterface::RoSoftS88XPressNetLI:
|
||||
{
|
||||
send(XpressNet::RoSoftS88XpressNetLI::S88StartAddress(s88StartAddress));
|
||||
send(XpressNet::RoSoftS88XpressNetLI::S88ModuleCount(s88ModuleCount));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void XpressNetSerial::read()
|
||||
{
|
||||
m_serialPort.async_read_some(boost::asio::buffer(m_readBuffer.data() + m_readBufferOffset, m_readBuffer.size() - m_readBufferOffset),
|
||||
[this](const boost::system::error_code& ec, std::size_t bytesTransferred)
|
||||
{
|
||||
if(!ec)
|
||||
{
|
||||
const uint8_t* pos = m_readBuffer.data();
|
||||
bytesTransferred += m_readBufferOffset;
|
||||
|
||||
while(bytesTransferred > 1)
|
||||
{
|
||||
const XpressNet::Message* message = reinterpret_cast<const XpressNet::Message*>(pos);
|
||||
|
||||
size_t drop = 0;
|
||||
while(message->size() <= bytesTransferred && !XpressNet::isChecksumValid(*message) && drop < bytesTransferred)
|
||||
{
|
||||
drop++;
|
||||
pos++;
|
||||
bytesTransferred--;
|
||||
message = reinterpret_cast<const XpressNet::Message*>(pos);
|
||||
}
|
||||
|
||||
if(drop != 0)
|
||||
{
|
||||
EventLoop::call(
|
||||
[this, drop]()
|
||||
{
|
||||
Log::log(*this, LogMessage::W2001_RECEIVED_MALFORMED_DATA_DROPPED_X_BYTES, drop);
|
||||
});
|
||||
}
|
||||
|
||||
if(message->size() <= bytesTransferred)
|
||||
{
|
||||
bool handled = false;
|
||||
|
||||
if(message->header == 0xF2)
|
||||
{
|
||||
switch(*(pos + 1))
|
||||
{
|
||||
case 0xF1:
|
||||
{
|
||||
using XpressNet::RoSoftS88XpressNetLI::S88StartAddress;
|
||||
EventLoop::call(
|
||||
[this, msg = *static_cast<const S88StartAddress*>(message)]()
|
||||
{
|
||||
assert(msg.startAddress >= S88StartAddress::startAddressMin && msg.startAddress <= S88StartAddress::startAddressMax);
|
||||
s88StartAddress.setValueInternal(msg.startAddress);
|
||||
});
|
||||
handled = true;
|
||||
break;
|
||||
}
|
||||
case 0xF2:
|
||||
{
|
||||
using XpressNet::RoSoftS88XpressNetLI::S88ModuleCount;
|
||||
EventLoop::call(
|
||||
[this, msg = *static_cast<const S88ModuleCount*>(message)]()
|
||||
{
|
||||
assert(msg.moduleCount >= S88ModuleCount::moduleCountMin && msg.moduleCount <= S88ModuleCount::moduleCountMax);
|
||||
s88ModuleCount.setValueInternal(msg.moduleCount);
|
||||
});
|
||||
handled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!handled)
|
||||
xpressnet->receive(*message);
|
||||
pos += message->size();
|
||||
bytesTransferred -= message->size();
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
if(bytesTransferred != 0)
|
||||
memmove(m_readBuffer.data(), pos, bytesTransferred);
|
||||
m_readBufferOffset = bytesTransferred;
|
||||
|
||||
read();
|
||||
}
|
||||
else
|
||||
EventLoop::call(
|
||||
[this, ec]()
|
||||
{
|
||||
Log::log(*this, LogMessage::E2002_SERIAL_READ_FAILED_X, ec);
|
||||
online = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void XpressNetSerial::updateEnabled()
|
||||
{
|
||||
auto w = m_world.lock();
|
||||
const bool enabled = w && contains(w->state.value(), WorldState::Edit) && !online;
|
||||
|
||||
Attributes::setEnabled(interface, enabled);
|
||||
Attributes::setEnabled(s88StartAddress, enabled);
|
||||
Attributes::setEnabled(s88ModuleCount, enabled);
|
||||
}
|
||||
|
||||
void XpressNetSerial::updateVisible()
|
||||
{
|
||||
const bool visible = interface == XpressNetSerialInterface::RoSoftS88XPressNetLI;
|
||||
Attributes::setVisible(s88StartAddress, visible);
|
||||
Attributes::setVisible(s88ModuleCount, visible);
|
||||
}
|
||||
@ -1,61 +0,0 @@
|
||||
/**
|
||||
* server/src/hardware/commandstation/xpressnetserial.hpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef TRAINTASTIC_SERVER_HARDWARE_COMMANDSTATION_XPRESSNETSERIAL_HPP
|
||||
#define TRAINTASTIC_SERVER_HARDWARE_COMMANDSTATION_XPRESSNETSERIAL_HPP
|
||||
|
||||
#include "serialcommandstation.hpp"
|
||||
#include "../../enum/xpressnetserialinterface.hpp"
|
||||
#include "../protocol/xpressnet/xpressnet.hpp"
|
||||
|
||||
class XpressNetSerial : public SerialCommandStation
|
||||
{
|
||||
private:
|
||||
void updateEnabled();
|
||||
void updateVisible();
|
||||
|
||||
protected:
|
||||
void loaded() final;
|
||||
void worldEvent(WorldState state, WorldEvent event) final;
|
||||
|
||||
void emergencyStopChanged(bool value) final;
|
||||
void powerOnChanged(bool value) final;
|
||||
void decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber) final;
|
||||
|
||||
bool send(const XpressNet::Message& msg);
|
||||
void stop() final;
|
||||
void started() final;
|
||||
void read() final;
|
||||
|
||||
public:
|
||||
CLASS_ID("command_station.xpressnet_serial")
|
||||
CREATE(XpressNetSerial)
|
||||
|
||||
Property<XpressNetSerialInterface> interface;
|
||||
ObjectProperty<XpressNet::XpressNet> xpressnet;
|
||||
Property<uint8_t> s88StartAddress;
|
||||
Property<uint8_t> s88ModuleCount;
|
||||
|
||||
XpressNetSerial(const std::weak_ptr<World>& world, std::string_view _id);
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -1,114 +0,0 @@
|
||||
/**
|
||||
* server/src/hardware/controller/controller.cpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "controller.hpp"
|
||||
#include "controllerlist.hpp"
|
||||
#include "controllerlisttablemodel.hpp"
|
||||
#include "../commandstation/commandstation.hpp"
|
||||
#include "../../world/getworld.hpp"
|
||||
#include "../../core/attributes.hpp"
|
||||
#include "../../utils/displayname.hpp"
|
||||
|
||||
Controller::Controller(const std::weak_ptr<World>& _world, std::string_view _id) :
|
||||
IdObject(_world, _id),
|
||||
name{this, "name", "", PropertyFlags::ReadWrite | PropertyFlags::Store},
|
||||
commandStation{this, "command_station", nullptr, PropertyFlags::ReadWrite | PropertyFlags::Store,
|
||||
[this](const std::shared_ptr<CommandStation>& value)
|
||||
{
|
||||
std::shared_ptr<Controller> controller = shared_ptr<Controller>();
|
||||
assert(controller);
|
||||
|
||||
if(commandStation)
|
||||
commandStation->controllers->removeObject(controller);
|
||||
|
||||
if(value)
|
||||
value->controllers->addObject(controller);
|
||||
|
||||
return true;
|
||||
}},
|
||||
active{this, "active", false, PropertyFlags::ReadWrite | PropertyFlags::StoreState, nullptr,
|
||||
std::bind(&Controller::setActive, this, std::placeholders::_1)},
|
||||
notes{this, "notes", "", PropertyFlags::ReadWrite | PropertyFlags::Store}
|
||||
{
|
||||
auto world = _world.lock();
|
||||
const bool editable = world && contains(world->state.value(), WorldState::Edit);
|
||||
|
||||
Attributes::addDisplayName(name, DisplayName::Object::name);
|
||||
m_interfaceItems.add(name);
|
||||
|
||||
Attributes::addDisplayName(commandStation, DisplayName::Hardware::commandStation);
|
||||
Attributes::addEnabled(commandStation, editable);
|
||||
Attributes::addObjectList(commandStation, world->commandStations);
|
||||
m_interfaceItems.add(commandStation);
|
||||
|
||||
Attributes::addDisplayName(active, DisplayName::Controller::active);
|
||||
m_interfaceItems.add(active);
|
||||
|
||||
Attributes::addDisplayName(notes, DisplayName::Object::notes);
|
||||
m_interfaceItems.add(notes);
|
||||
}
|
||||
|
||||
void Controller::addToWorld()
|
||||
{
|
||||
IdObject::addToWorld();
|
||||
|
||||
if(auto world = m_world.lock())
|
||||
world->controllers->addObject(shared_ptr<Controller>());
|
||||
}
|
||||
|
||||
void Controller::destroying()
|
||||
{
|
||||
if(commandStation.value())
|
||||
commandStation = nullptr;
|
||||
if(auto world = m_world.lock())
|
||||
world->controllers->removeObject(shared_ptr<Controller>());
|
||||
IdObject::destroying();
|
||||
}
|
||||
|
||||
void Controller::worldEvent(WorldState state, WorldEvent event)
|
||||
{
|
||||
IdObject::worldEvent(state, event);
|
||||
|
||||
const bool editable = contains(state, WorldState::Edit);
|
||||
|
||||
Attributes::setEnabled(commandStation, editable);
|
||||
|
||||
try
|
||||
{
|
||||
switch(event)
|
||||
{
|
||||
case WorldEvent::Offline:
|
||||
active = false;
|
||||
break;
|
||||
|
||||
case WorldEvent::Online:
|
||||
active = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -1,59 +0,0 @@
|
||||
/**
|
||||
* server/src/hardware/controller/controller.hpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef TRAINTASTIC_SERVER_HARDWARE_CONTROLLER_CONTROLLER_HPP
|
||||
#define TRAINTASTIC_SERVER_HARDWARE_CONTROLLER_CONTROLLER_HPP
|
||||
|
||||
#include "../../core/idobject.hpp"
|
||||
#include "../../core/property.hpp"
|
||||
#include "../../core/objectproperty.hpp"
|
||||
#include "../../core/commandstationproperty.hpp"
|
||||
|
||||
class Decoder;
|
||||
enum class DecoderChangeFlags;
|
||||
|
||||
class Controller : public IdObject
|
||||
{
|
||||
friend class CommandStation;
|
||||
|
||||
protected:
|
||||
void addToWorld() final;
|
||||
void destroying() final;
|
||||
void worldEvent(WorldState state, WorldEvent event) override;
|
||||
|
||||
virtual bool setActive(bool& value) = 0;
|
||||
|
||||
virtual void emergencyStopChanged(bool value) = 0;
|
||||
virtual void powerOnChanged(bool value) = 0;
|
||||
virtual void decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber) = 0;
|
||||
|
||||
public:
|
||||
Property<std::string> name;
|
||||
CommandStationProperty commandStation;
|
||||
Property<bool> active;
|
||||
Property<std::string> notes;
|
||||
|
||||
Controller(const std::weak_ptr<World>& _world, std::string_view _id);
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -1,85 +0,0 @@
|
||||
/**
|
||||
* server/src/hardware/controller/controllerlist.cpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2020 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "controllerlist.hpp"
|
||||
#include "controllerlisttablemodel.hpp"
|
||||
#include "controllers.hpp"
|
||||
#include "../commandstation/commandstation.hpp"
|
||||
#include "../../world/getworld.hpp"
|
||||
#include "../../core/attributes.hpp"
|
||||
#include "../../utils/displayname.hpp"
|
||||
|
||||
ControllerList::ControllerList(Object& _parent, const std::string& parentPropertyName) :
|
||||
ObjectList<Controller>(_parent, parentPropertyName),
|
||||
add{*this, "add",
|
||||
[this](std::string_view controllerClassId)
|
||||
{
|
||||
auto world = getWorld(&this->parent());
|
||||
if(!world)
|
||||
return std::shared_ptr<Controller>();
|
||||
auto controller = Controllers::create(world, controllerClassId, world->getUniqueId("controller"));
|
||||
if(auto* cs = dynamic_cast<CommandStation*>(&this->parent()))
|
||||
controller->commandStation = cs->shared_ptr<CommandStation>();
|
||||
//else if(world->commandStations->length() == 1)
|
||||
// decoder->commandStation = cs->shared_ptr<CommandStation>();
|
||||
return controller;
|
||||
}}
|
||||
, remove{*this, "remove",
|
||||
[this](const std::shared_ptr<Controller>& controller)
|
||||
{
|
||||
if(containsObject(controller))
|
||||
controller->destroy();
|
||||
assert(!containsObject(controller));
|
||||
}}
|
||||
{
|
||||
auto world = getWorld(&_parent);
|
||||
const bool editable = world && contains(world->state.value(), WorldState::Edit);
|
||||
|
||||
Attributes::addDisplayName(add, DisplayName::List::add);
|
||||
Attributes::addEnabled(add, editable);
|
||||
Attributes::addClassList(add, Controllers::classList);
|
||||
m_interfaceItems.add(add);
|
||||
|
||||
Attributes::addDisplayName(remove, DisplayName::List::remove);
|
||||
Attributes::addEnabled(remove, editable);
|
||||
m_interfaceItems.add(remove);
|
||||
}
|
||||
|
||||
TableModelPtr ControllerList::getModel()
|
||||
{
|
||||
return std::make_shared<ControllerListTableModel>(*this);
|
||||
}
|
||||
|
||||
void ControllerList::worldEvent(WorldState state, WorldEvent event)
|
||||
{
|
||||
ObjectList<Controller>::worldEvent(state, event);
|
||||
|
||||
const bool editable = contains(state, WorldState::Edit);
|
||||
|
||||
Attributes::setEnabled(add, editable);
|
||||
Attributes::setEnabled(remove, editable);
|
||||
}
|
||||
|
||||
bool ControllerList::isListedProperty(const std::string& name)
|
||||
{
|
||||
return ControllerListTableModel::isListedProperty(name);
|
||||
}
|
||||
@ -1,96 +0,0 @@
|
||||
/**
|
||||
* server/src/hardware/controller/usbxpressnetcontroller.cpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2020 Reinder Feenstra <reinderfeenstra@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "usbxpressnetcontroller.hpp"
|
||||
#include "../../core/traintastic.hpp"
|
||||
#include "../../world/world.hpp"
|
||||
#include "../../core/attributes.hpp"
|
||||
|
||||
USBXpressNetController::USBXpressNetController(const std::weak_ptr<World>& world, std::string_view _id) :
|
||||
Controller(world, _id),
|
||||
m_handle{nullptr},
|
||||
serial{this, "serial", "", PropertyFlags::ReadWrite | PropertyFlags::Store},
|
||||
mode{this, "mode", USBXpressNetControllerMode::Direct, PropertyFlags::ReadWrite | PropertyFlags::Store}
|
||||
{
|
||||
name = "USB XpressNet controller";
|
||||
|
||||
m_interfaceItems.insertBefore(serial, notes);
|
||||
Attributes::addValues(mode, USBXpressNetControllerModeValues);
|
||||
m_interfaceItems.insertBefore(mode, notes);
|
||||
|
||||
usbxpressnet_init();
|
||||
}
|
||||
|
||||
USBXpressNetController::~USBXpressNetController()
|
||||
{
|
||||
if(m_handle)
|
||||
{
|
||||
usbxpressnet_reset(m_handle);
|
||||
usbxpressnet_close(m_handle);
|
||||
}
|
||||
usbxpressnet_fini();
|
||||
}
|
||||
|
||||
bool USBXpressNetController::setActive(bool& value)
|
||||
{
|
||||
if(!m_handle && value)
|
||||
{
|
||||
usbxpressnet_status status = usbxpressnet_open(!serial.value().empty() ? serial.value().c_str() : nullptr, &m_handle);
|
||||
if(status != USBXPRESSNET_STATUS_SUCCESS)
|
||||
{
|
||||
// \todo Log::log(*this, LogMessage::E0001_X, std::string("usbxpressnet_open: ") + usbxpressnet_status_str(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
status = usbxpressnet_reset(m_handle);
|
||||
if(status != USBXPRESSNET_STATUS_SUCCESS)
|
||||
{
|
||||
// \todo Log::log(*this, LogMessage::E0001_X, std::string("usbxpressnet_reset: ") + usbxpressnet_status_str(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
status = usbxpressnet_set_mode(m_handle, USBXPRESSNET_MODE_STATION, 0);
|
||||
if(status != USBXPRESSNET_STATUS_SUCCESS)
|
||||
{
|
||||
// \todo Log::log(*this, LogMessage::E0001_X, std::string("usbxpressnet_set_mode: ") + usbxpressnet_status_str(status));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if(m_handle && !value)
|
||||
{
|
||||
usbxpressnet_close(m_handle);
|
||||
m_handle = nullptr;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void USBXpressNetController::emergencyStopChanged(bool value)
|
||||
{
|
||||
}
|
||||
|
||||
void USBXpressNetController::trackPowerChanged(bool value)
|
||||
{
|
||||
}
|
||||
|
||||
void USBXpressNetController::decoderChanged(const Decoder& decoder, DecoderChangeFlags, uint32_t)
|
||||
{
|
||||
}
|
||||
@ -1,52 +0,0 @@
|
||||
/**
|
||||
* server/src/hardware/controller/usbxpressnetcontroller.hpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2020 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef TRAINTASTIC_SERVER_HARDWARE_CONTROLLER_USBXPRESSNETCONTROLLER_HPP
|
||||
#define TRAINTASTIC_SERVER_HARDWARE_CONTROLLER_USBXPRESSNETCONTROLLER_HPP
|
||||
|
||||
#include "controller.hpp"
|
||||
#include <usbxpressnet.h>
|
||||
#include "../../enum/usbxpressnetcontrollermode.hpp"
|
||||
|
||||
class USBXpressNetController : public Controller
|
||||
{
|
||||
protected:
|
||||
usbxpressnet_handle m_handle;
|
||||
|
||||
bool setActive(bool& value) final;
|
||||
|
||||
void emergencyStopChanged(bool value) final;
|
||||
void trackPowerChanged(bool value) final;
|
||||
void decoderChanged(const Decoder& decoder, DecoderChangeFlags, uint32_t) final;
|
||||
|
||||
public:
|
||||
CLASS_ID("controller.usb_xpressnet_controller")
|
||||
CREATE(USBXpressNetController)
|
||||
|
||||
Property<std::string> serial;
|
||||
Property<USBXpressNetControllerMode> mode;
|
||||
|
||||
USBXpressNetController(const std::weak_ptr<World>& world, std::string_view _id);
|
||||
~USBXpressNetController() final;
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -1,437 +0,0 @@
|
||||
/**
|
||||
* server/src/hardware/controller/wlanmaus.cpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "wlanmaus.hpp"
|
||||
#include "../../core/traintastic.hpp"
|
||||
#include "../../core/eventloop.hpp"
|
||||
#include "../commandstation/commandstation.hpp"
|
||||
#include "../decoder/decoder.hpp"
|
||||
#include "../protocol/z21/messages.hpp"
|
||||
#include "../../utils/tohex.hpp"
|
||||
#include "../../core/attributes.hpp"
|
||||
#include "../../log/log.hpp"
|
||||
#include "../../utils/displayname.hpp"
|
||||
|
||||
static std::string toString(const boost::asio::ip::udp::endpoint& endpoint)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << endpoint;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
WLANmaus::WLANmaus(const std::weak_ptr<World> world, std::string_view _id) :
|
||||
Controller(world, _id),
|
||||
m_socket{Traintastic::instance->ioContext()},
|
||||
m_blockLocoInfo{nullptr},
|
||||
m_debugLog{false},
|
||||
port{this, "port", 21105, PropertyFlags::ReadWrite | PropertyFlags::Store},
|
||||
debugLog{this, "debug_log", m_debugLog, PropertyFlags::ReadWrite | PropertyFlags::Store,
|
||||
[this](bool value)
|
||||
{
|
||||
m_debugLog = value;
|
||||
}}
|
||||
{
|
||||
Attributes::addDisplayName(port, DisplayName::IP::port);
|
||||
Attributes::addEnabled(port, !active);
|
||||
m_interfaceItems.add(port);
|
||||
|
||||
m_interfaceItems.add(debugLog);
|
||||
}
|
||||
|
||||
bool WLANmaus::setActive(bool& value)
|
||||
{
|
||||
if(!m_socket.is_open() && value)
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
|
||||
if(m_socket.open(boost::asio::ip::udp::v4(), ec))
|
||||
{
|
||||
Log::log(*this, LogMessage::E2004_SOCKET_OPEN_FAILED_X, ec);
|
||||
return false;
|
||||
}
|
||||
else if(m_socket.bind(boost::asio::ip::udp::endpoint(boost::asio::ip::address_v4::any(), port), ec))
|
||||
{
|
||||
m_socket.close();
|
||||
Log::log(*this, LogMessage::E2006_SOCKET_BIND_FAILED_X, ec);
|
||||
return false;
|
||||
}
|
||||
|
||||
receive(); // start receiving messages
|
||||
|
||||
// TODO: send message were alive ??
|
||||
|
||||
Attributes::setEnabled(port, false);
|
||||
}
|
||||
else if(m_socket.is_open() && !value)
|
||||
{
|
||||
Attributes::setEnabled(port, true);
|
||||
|
||||
m_socket.close();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void WLANmaus::emergencyStopChanged(bool value)
|
||||
{
|
||||
if(value)
|
||||
{
|
||||
for(auto it : m_clients)
|
||||
if(it.second.broadcastFlags & Z21::PowerLocoTurnout)
|
||||
sendTo(Z21::LanXBCStopped(), it.first);
|
||||
}
|
||||
else if(commandStation && commandStation->powerOn) // send z21_lan_x_bc_track_power_on if power is on
|
||||
{
|
||||
for(auto it : m_clients)
|
||||
if(it.second.broadcastFlags & Z21::PowerLocoTurnout)
|
||||
sendTo(Z21::LanXBCTrackPowerOn(), it.first);
|
||||
}
|
||||
}
|
||||
|
||||
void WLANmaus::powerOnChanged(bool value)
|
||||
{
|
||||
if(value)
|
||||
{
|
||||
for(auto it : m_clients)
|
||||
if(it.second.broadcastFlags & Z21::PowerLocoTurnout)
|
||||
sendTo(Z21::LanXBCTrackPowerOn(), it.first);
|
||||
}
|
||||
else
|
||||
{
|
||||
for(auto it : m_clients)
|
||||
if(it.second.broadcastFlags & Z21::PowerLocoTurnout)
|
||||
sendTo(Z21::LanXBCTrackPowerOff(), it.first);
|
||||
}
|
||||
}
|
||||
|
||||
void WLANmaus::decoderChanged(const Decoder& decoder, DecoderChangeFlags, uint32_t)
|
||||
{
|
||||
if(&decoder == m_blockLocoInfo)
|
||||
return;
|
||||
|
||||
EventLoop::call(
|
||||
[this, dec=decoder.shared_ptr_c<const Decoder>()]()
|
||||
{
|
||||
broadcastLocoInfo(*dec);
|
||||
});
|
||||
}
|
||||
|
||||
void WLANmaus::receive()
|
||||
{
|
||||
m_socket.async_receive_from(boost::asio::buffer(m_receiveBuffer), m_receiveEndpoint,
|
||||
[this](const boost::system::error_code& ec, std::size_t bytesReceived)
|
||||
{
|
||||
if(!ec)
|
||||
{
|
||||
if((bytesReceived >= sizeof(Z21::Message)))
|
||||
{
|
||||
const Z21::Message* message = reinterpret_cast<const Z21::Message*>(m_receiveBuffer.data());
|
||||
|
||||
if(m_debugLog)
|
||||
EventLoop::call(
|
||||
[this, src=toString(m_receiveEndpoint), data=Z21::toString(*message)]()
|
||||
{
|
||||
Log::log(*this, LogMessage::D2005_X_RX_X, src, data);
|
||||
});
|
||||
|
||||
switch(message->header())
|
||||
{
|
||||
case Z21::LAN_X:
|
||||
{
|
||||
// TODO check XOR
|
||||
const uint8_t xheader = static_cast<const Z21::LanX*>(message)->xheader;
|
||||
switch(xheader)
|
||||
{
|
||||
case 0x21:
|
||||
if(*message == Z21::LanXGetStatus())
|
||||
{
|
||||
EventLoop::call(
|
||||
[this, endpoint=m_receiveEndpoint]()
|
||||
{
|
||||
Z21::LanXStatusChanged response;
|
||||
|
||||
if(!commandStation || commandStation->emergencyStop)
|
||||
response.db1 |= Z21_CENTRALSTATE_EMERGENCYSTOP;
|
||||
if(!commandStation || !commandStation->powerOn)
|
||||
response.db1 |= Z21_CENTRALSTATE_TRACKVOLTAGEOFF;
|
||||
|
||||
response.calcChecksum();
|
||||
|
||||
sendTo(response, endpoint);
|
||||
});
|
||||
}
|
||||
else if(*message == Z21::LanXSetTrackPowerOn())
|
||||
{
|
||||
EventLoop::call(
|
||||
[this, endpoint=m_receiveEndpoint]()
|
||||
{
|
||||
if(commandStation)
|
||||
{
|
||||
commandStation->emergencyStop = false;
|
||||
commandStation->powerOn = true;
|
||||
if(commandStation->powerOn)
|
||||
sendTo(Z21::LanXBCTrackPowerOn(), endpoint);
|
||||
}
|
||||
});
|
||||
}
|
||||
else if(*message == Z21::LanXSetTrackPowerOff())
|
||||
{
|
||||
EventLoop::call(
|
||||
[this, endpoint=m_receiveEndpoint]()
|
||||
{
|
||||
if(commandStation)
|
||||
{
|
||||
commandStation->powerOn = false;
|
||||
if(!commandStation->powerOn)
|
||||
sendTo(Z21::LanXBCTrackPowerOff(), endpoint);
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x80:
|
||||
if(*message == Z21::LanXSetStop())
|
||||
{
|
||||
EventLoop::call(
|
||||
[this, endpoint=m_receiveEndpoint]()
|
||||
{
|
||||
if(commandStation)
|
||||
{
|
||||
commandStation->emergencyStop = true;
|
||||
if(commandStation->emergencyStop)
|
||||
sendTo(Z21::LanXBCStopped(), endpoint);
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xE3:
|
||||
if(const Z21::LanXGetLocoInfo* r = static_cast<const Z21::LanXGetLocoInfo*>(message);
|
||||
r->db0 == 0xF0)
|
||||
{
|
||||
EventLoop::call(
|
||||
[this, request=*r, endpoint=m_receiveEndpoint]()
|
||||
{
|
||||
if(commandStation)
|
||||
if(auto decoder = commandStation->getDecoder(DecoderProtocol::DCC, request.address(), request.isLongAddress()))
|
||||
{
|
||||
m_clients[endpoint].locoInfo.insert(locoInfoKey(request.address(), request.isLongAddress()));
|
||||
sendTo(Z21::LanXLocoInfo(*decoder), endpoint);
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xE4:
|
||||
if(const Z21::LanXSetLocoDrive* locoDrive = static_cast<const Z21::LanXSetLocoDrive*>(message);
|
||||
locoDrive->db0 >= 0x10 && locoDrive->db0 <= 0x13)
|
||||
{
|
||||
EventLoop::call(
|
||||
[this, request=*locoDrive]()
|
||||
{
|
||||
if(!commandStation)
|
||||
return;
|
||||
|
||||
if(auto decoder = commandStation->getDecoder(DecoderProtocol::DCC, request.address(), request.isLongAddress()))
|
||||
{
|
||||
//m_blockLocoInfo = decoder.get();
|
||||
decoder->direction = request.direction();
|
||||
decoder->emergencyStop = request.isEmergencyStop();
|
||||
decoder->throttle = Decoder::speedStepToThrottle(request.speedStep(), request.speedSteps());
|
||||
//broadcastLocoInfo(*decoder);
|
||||
//m_blockLocoInfo = nullptr;
|
||||
}
|
||||
else
|
||||
Log::log(*this, LogMessage::I2001_UNKNOWN_LOCO_ADDRESS_X, request.address());
|
||||
});
|
||||
}
|
||||
else if(const Z21::LanXSetLocoFunction* locoFunction = static_cast<const Z21::LanXSetLocoFunction*>(message);
|
||||
locoFunction->db0 == 0xF8 &&
|
||||
locoFunction->switchType() != Z21::LanXSetLocoFunction::SwitchType::Invalid)
|
||||
{
|
||||
EventLoop::call(
|
||||
[this, request=*locoFunction]()
|
||||
{
|
||||
if(commandStation)
|
||||
if(auto decoder = commandStation->getDecoder(DecoderProtocol::DCC, request.address(), request.isLongAddress()))
|
||||
if(auto function = decoder->getFunction(request.functionIndex()))
|
||||
switch(request.switchType())
|
||||
{
|
||||
case Z21::LanXSetLocoFunction::SwitchType::Off:
|
||||
function->value = false;
|
||||
break;
|
||||
|
||||
case Z21::LanXSetLocoFunction::SwitchType::On:
|
||||
function->value = true;
|
||||
break;
|
||||
|
||||
case Z21::LanXSetLocoFunction::SwitchType::Toggle:
|
||||
function->value = !function->value;
|
||||
break;
|
||||
|
||||
case Z21::LanXSetLocoFunction::SwitchType::Invalid:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xF1:
|
||||
if(*message == Z21::LanXGetFirmwareVersion())
|
||||
{
|
||||
EventLoop::call(
|
||||
[this, endpoint=m_receiveEndpoint]()
|
||||
{
|
||||
sendTo(Z21::LanXGetFirmwareVersionReply(1, 30), endpoint);
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Z21::LAN_GET_LOCO_MODE:
|
||||
if(message->dataLen() == sizeof(Z21::LanGetLocoMode))
|
||||
{
|
||||
// TODO: reply without invoking event loop
|
||||
EventLoop::call(
|
||||
[this, address=static_cast<const Z21::LanGetLocoMode*>(message)->address(), endpoint=m_receiveEndpoint]()
|
||||
{
|
||||
sendTo(Z21::LanGetLocoModeReply(address, Z21::LocoMode::DCC), endpoint);
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case Z21::LAN_SET_LOCO_MODE:
|
||||
if(message->dataLen() == sizeof(Z21::LanSetLocoMode))
|
||||
{
|
||||
// ignore, we always report DCC
|
||||
}
|
||||
break;
|
||||
|
||||
case Z21::LAN_GET_SERIAL_NUMBER:
|
||||
if(message->dataLen() == sizeof(Z21::LanGetSerialNumber))
|
||||
{
|
||||
EventLoop::call(
|
||||
[this, endpoint=m_receiveEndpoint]()
|
||||
{
|
||||
sendTo(Z21::LanGetSerialNumberReply(123456789), endpoint);
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case Z21::LAN_GET_HWINFO:
|
||||
if(message->dataLen() == sizeof(Z21::LanGetHardwareInfo))
|
||||
{
|
||||
EventLoop::call(
|
||||
[this, endpoint=m_receiveEndpoint]()
|
||||
{
|
||||
sendTo(Z21::LanGetHardwareInfoReply(Z21::HWT_Z21_START, 1, 30), endpoint);
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case Z21::LAN_SET_BROADCASTFLAGS:
|
||||
if(message->dataLen() == sizeof(Z21::LanSetBroadcastFlags))
|
||||
{
|
||||
EventLoop::call(
|
||||
[this, broadcastFlags=static_cast<const Z21::LanSetBroadcastFlags*>(message)->broadcastFlags(), endpoint=m_receiveEndpoint]()
|
||||
{
|
||||
m_clients[endpoint].broadcastFlags = broadcastFlags;
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case Z21::LAN_SYSTEMSTATE_GETDATA:
|
||||
if(message->dataLen() == sizeof(Z21::LanSystemStateGetData))
|
||||
{
|
||||
EventLoop::call(
|
||||
[this, endpoint=m_receiveEndpoint]()
|
||||
{
|
||||
Z21::LanSystemStateDataChanged response;
|
||||
|
||||
if(!commandStation || commandStation->emergencyStop)
|
||||
response.centralState |= Z21_CENTRALSTATE_EMERGENCYSTOP;
|
||||
if(!commandStation || !commandStation->powerOn)
|
||||
response.centralState |= Z21_CENTRALSTATE_TRACKVOLTAGEOFF;
|
||||
|
||||
sendTo(response, endpoint);
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case Z21::LAN_LOGOFF:
|
||||
if(message->dataLen() == sizeof(Z21::LanLogoff))
|
||||
{
|
||||
EventLoop::call(
|
||||
[this, endpoint=m_receiveEndpoint]()
|
||||
{
|
||||
m_clients.erase(endpoint);
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
receive();
|
||||
}
|
||||
else
|
||||
EventLoop::call(
|
||||
[this, ec]()
|
||||
{
|
||||
Log::log(*this, LogMessage::E2009_SOCKET_RECEIVE_FAILED_X, ec);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void WLANmaus::sendTo(const Z21::Message& message, const boost::asio::ip::udp::endpoint& endpoint)
|
||||
{
|
||||
if(debugLog)
|
||||
Log::log(*this, LogMessage::D2004_X_TX_X, toString(endpoint), Z21::toString(message));
|
||||
|
||||
// TODO: add to queue, send async
|
||||
|
||||
boost::system::error_code ec;
|
||||
m_socket.send_to(boost::asio::buffer(&message, message.dataLen()), endpoint, 0, ec);
|
||||
if(ec)
|
||||
EventLoop::call([this, ec](){ Log::log(*this, LogMessage::E2011_SOCKET_SEND_FAILED_X, ec); });
|
||||
/*
|
||||
m_socket.async_send_to(boost::asio::buffer(&msg, msg.dataLen), endpoint,
|
||||
[this](const boost::system::error_code& ec, std::size_t)
|
||||
{
|
||||
if(ec)
|
||||
EventLoop::call([this, ec](){ Log::log(*this, LogMessage::E2011_SOCKET_SEND_FAILED_X, ec); });
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
void WLANmaus::broadcastLocoInfo(const Decoder& decoder)
|
||||
{
|
||||
const uint16_t key = locoInfoKey(decoder.address, decoder.longAddress);
|
||||
const Z21::LanXLocoInfo message(decoder);
|
||||
|
||||
for(auto it : m_clients)
|
||||
if(it.second.broadcastFlags & Z21::PowerLocoTurnout)
|
||||
if(it.second.locoInfo.count(key))
|
||||
sendTo(message, it.first);
|
||||
}
|
||||
@ -1,82 +0,0 @@
|
||||
/**
|
||||
* server/src/hardware/controller/z21app.hpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef TRAINTASTIC_SERVER_HARDWARE_CONTROLLER_WLANMAUS_HPP
|
||||
#define TRAINTASTIC_SERVER_HARDWARE_CONTROLLER_WLANMAUS_HPP
|
||||
|
||||
#include "controller.hpp"
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
struct z21_lan_header;
|
||||
|
||||
namespace Z21 {
|
||||
enum BroadcastFlags : uint32_t;
|
||||
class Message;
|
||||
}
|
||||
|
||||
class WLANmaus : public Controller
|
||||
{
|
||||
protected:
|
||||
struct Client
|
||||
{
|
||||
Z21::BroadcastFlags broadcastFlags = static_cast<Z21::BroadcastFlags>(0);
|
||||
std::set<uint16_t> locoInfo;
|
||||
};
|
||||
|
||||
boost::asio::ip::udp::socket m_socket;
|
||||
boost::asio::ip::udp::endpoint m_receiveEndpoint;
|
||||
std::array<uint8_t,64> m_receiveBuffer;
|
||||
std::map<boost::asio::ip::udp::endpoint, Client> m_clients;
|
||||
Decoder* m_blockLocoInfo;
|
||||
std::atomic_bool m_debugLog;
|
||||
|
||||
constexpr uint16_t locoInfoKey(uint16_t address, bool longAddress)
|
||||
{
|
||||
if(longAddress)
|
||||
return address | 0xC000;
|
||||
else
|
||||
return address;
|
||||
}
|
||||
|
||||
bool setActive(bool& value) final;
|
||||
|
||||
void emergencyStopChanged(bool value) final;
|
||||
void powerOnChanged(bool value) final;
|
||||
void decoderChanged(const Decoder& decoder, DecoderChangeFlags, uint32_t) final;
|
||||
|
||||
void receive();
|
||||
void sendTo(const Z21::Message& message, const boost::asio::ip::udp::endpoint& endpoint);
|
||||
void broadcastLocoInfo(const Decoder& decoder);
|
||||
|
||||
public:
|
||||
CLASS_ID("controller.wlanmaus")
|
||||
CREATE(WLANmaus);
|
||||
|
||||
Property<uint16_t> port;
|
||||
Property<bool> debugLog;
|
||||
|
||||
WLANmaus(const std::weak_ptr<World> world, std::string_view _id);
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2021 Reinder Feenstra
|
||||
* Copyright (C) 2019-2022 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -26,41 +26,33 @@
|
||||
#include "decoderchangeflags.hpp"
|
||||
#include "decoderfunction.hpp"
|
||||
#include "decoderfunctions.hpp"
|
||||
#include "../protocol/dcc/dcc.hpp"
|
||||
#include "../../world/world.hpp"
|
||||
#include "../commandstation/commandstation.hpp"
|
||||
#include "../../core/attributes.hpp"
|
||||
#include "../../log/log.hpp"
|
||||
#include "../../utils/displayname.hpp"
|
||||
#include "../../utils/almostzero.hpp"
|
||||
|
||||
//constexpr uint16_t addressDCCMin = 1;
|
||||
constexpr uint16_t addressDCCShortMax = 127;
|
||||
|
||||
const std::shared_ptr<Decoder> Decoder::null;
|
||||
|
||||
Decoder::Decoder(const std::weak_ptr<World>& world, std::string_view _id) :
|
||||
IdObject(world, _id),
|
||||
name{this, "name", "", PropertyFlags::ReadWrite | PropertyFlags::Store},
|
||||
commandStation{this, "command_station", nullptr, PropertyFlags::ReadWrite | PropertyFlags::Store,
|
||||
[this](const std::shared_ptr<CommandStation>& value)
|
||||
interface{this, "interface", nullptr, PropertyFlags::ReadWrite | PropertyFlags::Store, nullptr,
|
||||
[this](const std::shared_ptr<DecoderController>& newValue)
|
||||
{
|
||||
std::shared_ptr<Decoder> decoder = std::dynamic_pointer_cast<Decoder>(shared_from_this());
|
||||
assert(decoder);
|
||||
|
||||
//if(value)
|
||||
// TODO: check compatible??
|
||||
|
||||
if(commandStation)
|
||||
commandStation->decoders->removeObject(decoder);
|
||||
|
||||
if(value)
|
||||
value->decoders->addObject(decoder);
|
||||
|
||||
return true;
|
||||
if(!newValue || newValue->addDecoder(*this))
|
||||
{
|
||||
if(interface.value())
|
||||
interface->removeDecoder(*this);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}},
|
||||
protocol{this, "protocol", DecoderProtocol::Auto, PropertyFlags::ReadWrite | PropertyFlags::Store,
|
||||
[this](const DecoderProtocol& value)
|
||||
{
|
||||
if(value == DecoderProtocol::DCC && address > addressDCCShortMax)
|
||||
if(value == DecoderProtocol::DCC && DCC::isLongAddress(address))
|
||||
longAddress = true;
|
||||
updateEditable();
|
||||
}},
|
||||
@ -69,7 +61,7 @@ Decoder::Decoder(const std::weak_ptr<World>& world, std::string_view _id) :
|
||||
{
|
||||
if(protocol == DecoderProtocol::DCC)
|
||||
{
|
||||
if(value > addressDCCShortMax)
|
||||
if(DCC::isLongAddress(value))
|
||||
longAddress = true;
|
||||
updateEditable();
|
||||
}
|
||||
@ -103,20 +95,19 @@ Decoder::Decoder(const std::weak_ptr<World>& world, std::string_view _id) :
|
||||
functions.setValueInternal(std::make_shared<DecoderFunctions>(*this, functions.name()));
|
||||
|
||||
auto w = world.lock();
|
||||
assert(w);
|
||||
|
||||
m_worldMute = contains(w->state.value(), WorldState::Mute);
|
||||
m_worldNoSmoke = contains(w->state.value(), WorldState::NoSmoke);
|
||||
|
||||
// const bool editable = w && contains(w->state.value(), WorldState::Edit) && speedStep == 0;
|
||||
|
||||
Attributes::addDisplayName(name, DisplayName::Object::name);
|
||||
Attributes::addEnabled(name, false);
|
||||
m_interfaceItems.add(name);
|
||||
|
||||
Attributes::addDisplayName(commandStation, DisplayName::Hardware::commandStation);
|
||||
Attributes::addEnabled(commandStation, false);
|
||||
Attributes::addObjectList(commandStation, w->commandStations);
|
||||
m_interfaceItems.add(commandStation);
|
||||
Attributes::addDisplayName(interface, DisplayName::Hardware::interface);
|
||||
Attributes::addEnabled(interface, false);
|
||||
Attributes::addObjectList(interface, w->decoderControllers);
|
||||
m_interfaceItems.add(interface);
|
||||
|
||||
Attributes::addEnabled(protocol, false);
|
||||
Attributes::addValues(protocol, DecoderProtocolValues);
|
||||
@ -156,6 +147,20 @@ void Decoder::addToWorld()
|
||||
world->decoders->addObject(shared_ptr<Decoder>());
|
||||
}
|
||||
|
||||
void Decoder::loaded()
|
||||
{
|
||||
IdObject::loaded();
|
||||
if(interface)
|
||||
{
|
||||
if(!interface->addDecoder(*this))
|
||||
{
|
||||
if(auto object = std::dynamic_pointer_cast<Object>(interface.value()))
|
||||
Log::log(*this, LogMessage::C2001_ADDRESS_ALREADY_USED_AT_X, *object);
|
||||
interface.setValueInternal(nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Decoder::hasFunction(uint32_t number) const
|
||||
{
|
||||
for(auto& f : *functions)
|
||||
@ -220,8 +225,8 @@ void Decoder::setFunctionValue(uint32_t number, bool value)
|
||||
|
||||
void Decoder::destroying()
|
||||
{
|
||||
if(commandStation.value())
|
||||
commandStation = nullptr;
|
||||
if(interface.value())
|
||||
interface = nullptr;
|
||||
if(auto world = m_world.lock())
|
||||
world->decoders->removeObject(shared_ptr<Decoder>());
|
||||
IdObject::destroying();
|
||||
@ -273,15 +278,16 @@ void Decoder::updateEditable(bool editable)
|
||||
{
|
||||
const bool stopped = editable && almostZero(throttle.value());
|
||||
Attributes::setEnabled(name, editable);
|
||||
Attributes::setEnabled(commandStation, stopped);
|
||||
Attributes::setEnabled(interface, stopped);
|
||||
Attributes::setEnabled(protocol, stopped);
|
||||
Attributes::setEnabled(address, stopped);
|
||||
Attributes::setEnabled(longAddress, stopped && protocol == DecoderProtocol::DCC && address < addressDCCShortMax);
|
||||
Attributes::setEnabled(longAddress, stopped && protocol == DecoderProtocol::DCC && !DCC::isLongAddress(address));
|
||||
Attributes::setEnabled(speedSteps, stopped);
|
||||
}
|
||||
|
||||
void Decoder::changed(DecoderChangeFlags changes, uint32_t functionNumber)
|
||||
{
|
||||
if(commandStation)
|
||||
commandStation->decoderChanged(*this, changes, functionNumber);
|
||||
if(interface)
|
||||
interface->decoderChanged(*this, changes, functionNumber);
|
||||
decoderChanged(*this, changes, functionNumber);
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2021 Reinder Feenstra
|
||||
* Copyright (C) 2019-2022 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -26,11 +26,15 @@
|
||||
#include <type_traits>
|
||||
#include "../../core/idobject.hpp"
|
||||
#include "../../core/objectproperty.hpp"
|
||||
#include "../../core/commandstationproperty.hpp"
|
||||
#include "../../enum/decoderprotocol.hpp"
|
||||
#include "../../enum/direction.hpp"
|
||||
#include "decodercontroller.hpp"
|
||||
#include "decoderfunctions.hpp"
|
||||
|
||||
#ifdef interface
|
||||
#undef interface // interface is defined in combaseapi.h
|
||||
#endif
|
||||
|
||||
enum class DecoderChangeFlags;
|
||||
class DecoderFunction;
|
||||
|
||||
@ -43,6 +47,7 @@ class Decoder : public IdObject
|
||||
bool m_worldNoSmoke;
|
||||
|
||||
protected:
|
||||
void loaded() final;
|
||||
void destroying() override;
|
||||
void worldEvent(WorldState state, WorldEvent event) final;
|
||||
void updateEditable();
|
||||
@ -74,7 +79,7 @@ class Decoder : public IdObject
|
||||
static const std::shared_ptr<Decoder> null;
|
||||
|
||||
Property<std::string> name;
|
||||
CommandStationProperty commandStation;
|
||||
ObjectProperty<DecoderController> interface;
|
||||
Property<DecoderProtocol> protocol;
|
||||
Property<uint16_t> address;
|
||||
Property<bool> longAddress;
|
||||
@ -85,6 +90,8 @@ class Decoder : public IdObject
|
||||
ObjectProperty<DecoderFunctions> functions;
|
||||
Property<std::string> notes;
|
||||
|
||||
boost::signals2::signal<void (Decoder&, DecoderChangeFlags, uint32_t)> decoderChanged;
|
||||
|
||||
Decoder(const std::weak_ptr<World>& world, std::string_view _id);
|
||||
|
||||
void addToWorld() final;
|
||||
|
||||
92
server/src/hardware/decoder/decodercontroller.cpp
Normale Datei
92
server/src/hardware/decoder/decodercontroller.cpp
Normale Datei
@ -0,0 +1,92 @@
|
||||
/**
|
||||
* server/src/hardware/decoder/decodercontroller.cpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "decodercontroller.hpp"
|
||||
#include "decoder.hpp"
|
||||
#include "decoderchangeflags.hpp"
|
||||
#include "../../utils/almostzero.hpp"
|
||||
|
||||
bool DecoderController::addDecoder(Decoder& decoder)
|
||||
{
|
||||
if(decoder.protocol != DecoderProtocol::Auto && findDecoder(decoder) != m_decoders.end())
|
||||
return false;
|
||||
else if(findDecoder(DecoderProtocol::Auto, decoder.address) != m_decoders.end())
|
||||
return false;
|
||||
|
||||
m_decoders.emplace_back(decoder.shared_ptr<Decoder>());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DecoderController::removeDecoder(Decoder& decoder)
|
||||
{
|
||||
auto it = findDecoder(decoder);
|
||||
if(it != m_decoders.end())
|
||||
{
|
||||
m_decoders.erase(it);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::shared_ptr<Decoder>& DecoderController::getDecoder(DecoderProtocol protocol, uint16_t address, bool dccLongAddress, bool fallbackToProtocolAuto)
|
||||
{
|
||||
auto it = findDecoder(protocol, address, dccLongAddress);
|
||||
if(it != m_decoders.end())
|
||||
return *it;
|
||||
else if(fallbackToProtocolAuto && protocol != DecoderProtocol::Auto && (it = findDecoder(DecoderProtocol::Auto, address)) != m_decoders.end())
|
||||
return *it;
|
||||
else
|
||||
return Decoder::null;
|
||||
}
|
||||
|
||||
DecoderController::DecoderVector::iterator DecoderController::findDecoder(const Decoder& decoder)
|
||||
{
|
||||
return findDecoder(decoder.protocol, decoder.address, decoder.longAddress);
|
||||
}
|
||||
|
||||
DecoderController::DecoderVector::iterator DecoderController::findDecoder(DecoderProtocol protocol, uint16_t address, bool dccLongAddress)
|
||||
{
|
||||
if(protocol == DecoderProtocol::DCC)
|
||||
{
|
||||
return std::find_if(m_decoders.begin(), m_decoders.end(),
|
||||
[address, dccLongAddress](const auto& it)
|
||||
{
|
||||
return it->protocol == DecoderProtocol::DCC && it->address == address && it->longAddress == dccLongAddress;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::find_if(m_decoders.begin(), m_decoders.end(),
|
||||
[protocol, address](const auto& it)
|
||||
{
|
||||
return it->protocol == protocol && it->address == address;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void DecoderController::restoreDecoderSpeed()
|
||||
{
|
||||
for(const auto& decoder : m_decoders)
|
||||
if(!decoder->emergencyStop && !almostZero(decoder->throttle.value()))
|
||||
decoderChanged(*decoder, DecoderChangeFlags::Throttle, 0);
|
||||
}
|
||||
57
server/src/hardware/decoder/decodercontroller.hpp
Normale Datei
57
server/src/hardware/decoder/decodercontroller.hpp
Normale Datei
@ -0,0 +1,57 @@
|
||||
/**
|
||||
* server/src/hardware/decoder/decodercontroller.hpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef TRAINTASTIC_SERVER_HARDWARE_DECODER_DECODERCONTROLLER_HPP
|
||||
#define TRAINTASTIC_SERVER_HARDWARE_DECODER_DECODERCONTROLLER_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
class Decoder;
|
||||
enum class DecoderChangeFlags;
|
||||
enum class DecoderProtocol : uint8_t;
|
||||
|
||||
class DecoderController
|
||||
{
|
||||
public:
|
||||
using DecoderVector = std::vector<std::shared_ptr<Decoder>>;
|
||||
|
||||
protected:
|
||||
DecoderVector m_decoders;
|
||||
|
||||
DecoderVector::iterator findDecoder(const Decoder& decoder);
|
||||
DecoderVector::iterator findDecoder(DecoderProtocol protocol, uint16_t address, bool dccLongAddress = false);
|
||||
|
||||
/// \brief restore speed of all decoders that are not (emergency) stopped
|
||||
void restoreDecoderSpeed();
|
||||
|
||||
public:
|
||||
[[nodiscard]] virtual bool addDecoder(Decoder& decoder);
|
||||
[[nodiscard]] virtual bool removeDecoder(Decoder& decoder);
|
||||
|
||||
const std::shared_ptr<Decoder>& getDecoder(DecoderProtocol protocol, uint16_t address, bool dccLongAddress = false, bool fallbackToProtocolAuto = false);
|
||||
|
||||
virtual void decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber) = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2020 Reinder Feenstra
|
||||
* Copyright (C) 2019-2022 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -22,7 +22,6 @@
|
||||
|
||||
#include "decoderlist.hpp"
|
||||
#include "decoderlisttablemodel.hpp"
|
||||
#include "../commandstation/commandstation.hpp"
|
||||
#include "../../world/getworld.hpp"
|
||||
#include "../../core/attributes.hpp"
|
||||
#include "../../utils/displayname.hpp"
|
||||
@ -35,12 +34,13 @@ DecoderList::DecoderList(Object& _parent, const std::string& parentPropertyName)
|
||||
auto world = getWorld(&this->parent());
|
||||
if(!world)
|
||||
return std::shared_ptr<Decoder>();
|
||||
|
||||
auto decoder = Decoder::create(world, world->getUniqueId("decoder"));
|
||||
//addObject(decoder);
|
||||
if(auto* cs = dynamic_cast<CommandStation*>(&this->parent()))
|
||||
decoder->commandStation = cs->shared_ptr<CommandStation>();
|
||||
//else if(world->commandStations->length() == 1)
|
||||
// decoder->commandStation = cs->shared_ptr<CommandStation>();
|
||||
if(const auto controller = std::dynamic_pointer_cast<DecoderController>(parent().shared_from_this()))
|
||||
{
|
||||
// todo: select free address?
|
||||
decoder->interface = controller;
|
||||
}
|
||||
return decoder;
|
||||
}}
|
||||
, remove{*this, "remove",
|
||||
@ -69,6 +69,33 @@ TableModelPtr DecoderList::getModel()
|
||||
return std::make_shared<DecoderListTableModel>(*this);
|
||||
}
|
||||
|
||||
std::shared_ptr<Decoder> DecoderList::getDecoder(uint16_t address) const
|
||||
{
|
||||
auto it = std::find_if(begin(), end(),
|
||||
[address](const auto& decoder)
|
||||
{
|
||||
return decoder->address.value() == address;
|
||||
});
|
||||
if(it != end())
|
||||
return *it;
|
||||
return {};
|
||||
}
|
||||
|
||||
std::shared_ptr<Decoder> DecoderList::getDecoder(DecoderProtocol protocol, uint16_t address, bool longAddress) const
|
||||
{
|
||||
auto it = std::find_if(begin(), end(),
|
||||
[protocol, address, longAddress](const auto& decoder)
|
||||
{
|
||||
return
|
||||
decoder->protocol.value() == protocol &&
|
||||
decoder->address.value() == address &&
|
||||
(protocol != DecoderProtocol::DCC || decoder->longAddress == longAddress);
|
||||
});
|
||||
if(it != end())
|
||||
return *it;
|
||||
return {};
|
||||
}
|
||||
|
||||
void DecoderList::worldEvent(WorldState state, WorldEvent event)
|
||||
{
|
||||
ObjectList<Decoder>::worldEvent(state, event);
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2020 Reinder Feenstra
|
||||
* Copyright (C) 2019-2022 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -42,6 +42,9 @@ class DecoderList : public ObjectList<Decoder>
|
||||
DecoderList(Object& _parent, const std::string& parentPropertyName);
|
||||
|
||||
TableModelPtr getModel() final;
|
||||
|
||||
std::shared_ptr<Decoder> getDecoder(uint16_t address) const;
|
||||
std::shared_ptr<Decoder> getDecoder(DecoderProtocol protocol, uint16_t address, bool longAddress = false) const;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@ -24,12 +24,31 @@
|
||||
#include "../../world/world.hpp"
|
||||
#include "list/inputlisttablemodel.hpp"
|
||||
#include "../../core/attributes.hpp"
|
||||
#include "../../log/log.hpp"
|
||||
#include "../../utils/displayname.hpp"
|
||||
|
||||
Input::Input(const std::weak_ptr<World> world, std::string_view _id) :
|
||||
IdObject(world, _id),
|
||||
name{this, "name", id, PropertyFlags::ReadWrite | PropertyFlags::Store},
|
||||
value{this, "value", TriState::Undefined, PropertyFlags::ReadOnly | PropertyFlags::StoreState}
|
||||
Input::Input(const std::weak_ptr<World> world, std::string_view _id)
|
||||
: IdObject(world, _id)
|
||||
, name{this, "name", id, PropertyFlags::ReadWrite | PropertyFlags::Store}
|
||||
, interface{this, "interface", nullptr, PropertyFlags::ReadWrite | PropertyFlags::Store, nullptr,
|
||||
[this](const std::shared_ptr<InputController>& newValue)
|
||||
{
|
||||
if(!newValue || newValue->addInput(*this))
|
||||
{
|
||||
if(interface.value())
|
||||
interface->removeInput(*this);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}}
|
||||
, address{this, "address", 1, PropertyFlags::ReadWrite | PropertyFlags::Store, nullptr,
|
||||
[this](const uint32_t& newValue)
|
||||
{
|
||||
if(interface)
|
||||
return interface->changeInputAddress(*this, newValue);
|
||||
return true;
|
||||
}}
|
||||
, value{this, "value", TriState::Undefined, PropertyFlags::ReadOnly | PropertyFlags::StoreState}
|
||||
, consumers{*this, "consumers", {}, PropertyFlags::ReadOnly | PropertyFlags::NoStore}
|
||||
{
|
||||
auto w = world.lock();
|
||||
@ -38,9 +57,21 @@ Input::Input(const std::weak_ptr<World> world, std::string_view _id) :
|
||||
Attributes::addDisplayName(name, DisplayName::Object::name);
|
||||
Attributes::addEnabled(name, editable);
|
||||
m_interfaceItems.add(name);
|
||||
|
||||
Attributes::addDisplayName(interface, DisplayName::Hardware::interface);
|
||||
Attributes::addEnabled(interface, editable);
|
||||
Attributes::addObjectList(interface, w->inputControllers);
|
||||
m_interfaceItems.add(interface);
|
||||
|
||||
Attributes::addDisplayName(address, DisplayName::Hardware::address);
|
||||
Attributes::addEnabled(address, editable);
|
||||
Attributes::addMinMax(address, std::numeric_limits<uint32_t>::min(), std::numeric_limits<uint32_t>::max());
|
||||
m_interfaceItems.add(address);
|
||||
|
||||
Attributes::addObjectEditor(value, false);
|
||||
Attributes::addValues(value, TriStateValues);
|
||||
m_interfaceItems.add(value);
|
||||
|
||||
Attributes::addObjectEditor(consumers, false); //! \todo add client support first
|
||||
m_interfaceItems.add(consumers);
|
||||
}
|
||||
@ -53,8 +84,24 @@ void Input::addToWorld()
|
||||
world->inputs->addObject(shared_ptr<Input>());
|
||||
}
|
||||
|
||||
void Input::loaded()
|
||||
{
|
||||
IdObject::loaded();
|
||||
if(interface)
|
||||
{
|
||||
if(!interface->addInput(*this))
|
||||
{
|
||||
if(auto object = std::dynamic_pointer_cast<Object>(interface.value()))
|
||||
Log::log(*this, LogMessage::C2001_ADDRESS_ALREADY_USED_AT_X, *object);
|
||||
interface.setValueInternal(nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Input::destroying()
|
||||
{
|
||||
if(interface.value())
|
||||
interface = nullptr;
|
||||
if(auto world = m_world.lock())
|
||||
world->inputs->removeObject(shared_ptr<Input>());
|
||||
IdObject::destroying();
|
||||
@ -67,6 +114,8 @@ void Input::worldEvent(WorldState state, WorldEvent event)
|
||||
const bool editable = contains(state, WorldState::Edit);
|
||||
|
||||
Attributes::setEnabled(name, editable);
|
||||
Attributes::setEnabled(interface, editable);
|
||||
Attributes::setEnabled(address, editable);
|
||||
}
|
||||
|
||||
void Input::updateValue(TriState _value)
|
||||
|
||||
@ -27,13 +27,23 @@
|
||||
#include "../../core/objectproperty.hpp"
|
||||
#include "../../core/objectvectorproperty.hpp"
|
||||
#include "../../enum/tristate.hpp"
|
||||
#include "inputcontroller.hpp"
|
||||
|
||||
#ifdef interface
|
||||
#undef interface // interface is defined in combaseapi.h
|
||||
#endif
|
||||
|
||||
class Input : public IdObject
|
||||
{
|
||||
CLASS_ID("input")
|
||||
DEFAULT_ID("input")
|
||||
CREATE(Input)
|
||||
|
||||
friend class InputController;
|
||||
|
||||
protected:
|
||||
void addToWorld() override;
|
||||
void loaded() override;
|
||||
void destroying() override;
|
||||
void worldEvent(WorldState state, WorldEvent event) override;
|
||||
virtual void valueChanged(TriState /*_value*/) {}
|
||||
@ -41,7 +51,11 @@ class Input : public IdObject
|
||||
void updateValue(TriState _value);
|
||||
|
||||
public:
|
||||
static constexpr uint32_t invalidAddress = std::numeric_limits<uint32_t>::max();
|
||||
|
||||
Property<std::string> name;
|
||||
ObjectProperty<InputController> interface;
|
||||
Property<uint32_t> address;
|
||||
Property<TriState> value;
|
||||
ObjectVectorProperty<Object> consumers;
|
||||
|
||||
|
||||
103
server/src/hardware/input/inputcontroller.cpp
Normale Datei
103
server/src/hardware/input/inputcontroller.cpp
Normale Datei
@ -0,0 +1,103 @@
|
||||
/**
|
||||
* server/src/hardware/input/inputcontroller.cpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "inputcontroller.hpp"
|
||||
#include "input.hpp"
|
||||
#include "monitor/inputmonitor.hpp"
|
||||
#include "../../utils/inrange.hpp"
|
||||
|
||||
bool InputController::isInputAddressAvailable(uint32_t address) const
|
||||
{
|
||||
return
|
||||
inRange(address, inputAddressMinMax()) &&
|
||||
m_inputs.find(address) == m_inputs.end();
|
||||
}
|
||||
|
||||
uint32_t InputController::getUnusedInputAddress() const
|
||||
{
|
||||
const auto end = m_inputs.cend();
|
||||
const auto range = inputAddressMinMax();
|
||||
for(uint32_t address = range.first; address < range.second; address++)
|
||||
if(m_inputs.find(address) == end)
|
||||
return address;
|
||||
return Input::invalidAddress;
|
||||
}
|
||||
|
||||
bool InputController::changeInputAddress(Input& input, uint32_t newAddress)
|
||||
{
|
||||
assert(input.interface.value().get() == this);
|
||||
|
||||
if(!isInputAddressAvailable(newAddress))
|
||||
return false;
|
||||
|
||||
auto node = m_inputs.extract(input.address); // old address
|
||||
node.key() = newAddress;
|
||||
m_inputs.insert(std::move(node));
|
||||
input.value.setValueInternal(TriState::Undefined);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InputController::addInput(Input& input)
|
||||
{
|
||||
if(isInputAddressAvailable(input.address))
|
||||
{
|
||||
m_inputs.insert({input.address, input.shared_ptr<Input>()});
|
||||
input.value.setValueInternal(TriState::Undefined);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
bool InputController::removeInput(Input& input)
|
||||
{
|
||||
assert(input.interface.value().get() == this);
|
||||
auto it = m_inputs.find(input.address);
|
||||
if(it != m_inputs.end() && it->second.get() == &input)
|
||||
{
|
||||
m_inputs.erase(it);
|
||||
input.value.setValueInternal(TriState::Undefined);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
void InputController::updateInputValue(uint32_t address, TriState value)
|
||||
{
|
||||
if(auto it = m_inputs.find(address); it != m_inputs.end())
|
||||
it->second->updateValue(value);
|
||||
if(auto monitor = m_inputMonitor.lock())
|
||||
monitor->inputValueChanged(*monitor, address, value);
|
||||
}
|
||||
|
||||
std::shared_ptr<InputMonitor> InputController::inputMonitor()
|
||||
{
|
||||
auto monitor = m_inputMonitor.lock();
|
||||
if(!monitor)
|
||||
{
|
||||
monitor = std::make_shared<InputMonitor>(*this);
|
||||
m_inputMonitor = monitor;
|
||||
}
|
||||
return monitor;
|
||||
}
|
||||
101
server/src/hardware/input/inputcontroller.hpp
Normale Datei
101
server/src/hardware/input/inputcontroller.hpp
Normale Datei
@ -0,0 +1,101 @@
|
||||
/**
|
||||
* server/src/hardware/input/inputcontroller.hpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef TRAINTASTIC_SERVER_HARDWARE_INPUT_INPUTCONTROLLER_HPP
|
||||
#define TRAINTASTIC_SERVER_HARDWARE_INPUT_INPUTCONTROLLER_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
#include "../../enum/tristate.hpp"
|
||||
|
||||
class Input;
|
||||
class InputMonitor;
|
||||
|
||||
class InputController
|
||||
{
|
||||
public:
|
||||
using InputMap = std::unordered_map<uint32_t, std::shared_ptr<Input>>;
|
||||
|
||||
protected:
|
||||
InputMap m_inputs;
|
||||
std::weak_ptr<InputMonitor> m_inputMonitor;
|
||||
|
||||
public:
|
||||
/**
|
||||
*
|
||||
*/
|
||||
inline const InputMap& inputs() const { return m_inputs; }
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
virtual std::pair<uint32_t, uint32_t> inputAddressMinMax() const = 0;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
[[nodiscard]] virtual bool isInputAddressAvailable(uint32_t address) const;
|
||||
|
||||
/**
|
||||
* @brief Get the next unused input address
|
||||
*
|
||||
* @return An usused address or #Input::invalidAddress if no unused address is available.
|
||||
*/
|
||||
uint32_t getUnusedInputAddress() const;
|
||||
|
||||
/**
|
||||
*
|
||||
* @return \c true if changed, \c false otherwise.
|
||||
*/
|
||||
[[nodiscard]] virtual bool changeInputAddress(Input& input, uint32_t newAddress);
|
||||
|
||||
/**
|
||||
*
|
||||
* @return \c true if added, \c false otherwise.
|
||||
*/
|
||||
[[nodiscard]] virtual bool addInput(Input& input);
|
||||
|
||||
/**
|
||||
*
|
||||
* @return \c true if removed, \c false otherwise.
|
||||
*/
|
||||
[[nodiscard]] virtual bool removeInput(Input& input);
|
||||
|
||||
/**
|
||||
* @brief Update the input value
|
||||
*
|
||||
* This function should be called by the hardware layer whenever the input value changes.
|
||||
*
|
||||
* @param[in] address Input address
|
||||
* @param[in] value New input value
|
||||
*/
|
||||
void updateInputValue(uint32_t address, TriState value);
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*/
|
||||
std::shared_ptr<InputMonitor> inputMonitor();
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -22,59 +22,64 @@
|
||||
|
||||
#include "inputlist.hpp"
|
||||
#include "inputlisttablemodel.hpp"
|
||||
#include "../inputs.hpp"
|
||||
#include "../inputcontroller.hpp"
|
||||
#include "../../../world/getworld.hpp"
|
||||
#include "../../../core/attributes.hpp"
|
||||
#include "../../../utils/displayname.hpp"
|
||||
|
||||
InputList::InputList(Object& _parent, const std::string& parentPropertyName) :
|
||||
ObjectList<Input>(_parent, parentPropertyName),
|
||||
add{*this, "add",
|
||||
[this](std::string_view inputClassId)
|
||||
{
|
||||
auto world = getWorld(&this->parent());
|
||||
if(!world)
|
||||
return std::shared_ptr<Input>();
|
||||
auto input = Inputs::create(world, inputClassId, world->getUniqueId("input"));
|
||||
if(auto locoNetInput = std::dynamic_pointer_cast<LocoNetInput>(input); locoNetInput && world->loconets->length == 1)
|
||||
InputList::InputList(Object& _parent, const std::string& parentPropertyName)
|
||||
: ObjectList<Input>(_parent, parentPropertyName)
|
||||
, m_parentIsInputController(dynamic_cast<InputController*>(&_parent))
|
||||
, add{*this, "add",
|
||||
[this]()
|
||||
{
|
||||
auto& loconet = world->loconets->operator[](0);
|
||||
if(uint16_t address = loconet->getUnusedInputAddress(); address != LocoNetInput::addressInvalid)
|
||||
auto world = getWorld(&parent());
|
||||
if(!world)
|
||||
return std::shared_ptr<Input>();
|
||||
|
||||
auto input = Input::create(world, world->getUniqueId(Input::defaultId));
|
||||
if(const auto controller = std::dynamic_pointer_cast<InputController>(parent().shared_from_this()))
|
||||
{
|
||||
locoNetInput->address = address;
|
||||
locoNetInput->loconet = loconet;
|
||||
if(const uint32_t address = controller->getUnusedInputAddress(); address != Input::invalidAddress)
|
||||
{
|
||||
input->address = address;
|
||||
input->interface = controller;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(auto xpressNetInput = std::dynamic_pointer_cast<XpressNetInput>(input); xpressNetInput && world->xpressnets->length == 1)
|
||||
{
|
||||
auto& xpressnet = world->xpressnets->operator[](0);
|
||||
if(uint16_t address = xpressnet->getUnusedInputAddress(); address != XpressNetInput::addressInvalid)
|
||||
{
|
||||
xpressNetInput->address = address;
|
||||
xpressNetInput->xpressnet = xpressnet;
|
||||
}
|
||||
}
|
||||
return input;
|
||||
}}
|
||||
return input;
|
||||
}}
|
||||
, remove{*this, "remove",
|
||||
[this](const std::shared_ptr<Input>& input)
|
||||
{
|
||||
if(containsObject(input))
|
||||
input->destroy();
|
||||
assert(!containsObject(input));
|
||||
}}
|
||||
[this](const std::shared_ptr<Input>& input)
|
||||
{
|
||||
if(containsObject(input))
|
||||
input->destroy();
|
||||
assert(!containsObject(input));
|
||||
}}
|
||||
, inputMonitor{*this, "input_monitor",
|
||||
[this]()
|
||||
{
|
||||
if(const auto controller = std::dynamic_pointer_cast<InputController>(parent().shared_from_this()))
|
||||
return controller->inputMonitor();
|
||||
else
|
||||
return std::shared_ptr<InputMonitor>();
|
||||
}}
|
||||
{
|
||||
auto w = getWorld(&_parent);
|
||||
const bool editable = w && contains(w->state.value(), WorldState::Edit);
|
||||
|
||||
Attributes::addDisplayName(add, DisplayName::List::add);
|
||||
Attributes::addEnabled(add, editable);
|
||||
Attributes::addClassList(add, Inputs::classList);
|
||||
m_interfaceItems.add(add);
|
||||
|
||||
Attributes::addDisplayName(remove, DisplayName::List::remove);
|
||||
Attributes::addEnabled(remove, editable);
|
||||
m_interfaceItems.add(remove);
|
||||
|
||||
if(m_parentIsInputController)
|
||||
{
|
||||
Attributes::addDisplayName(inputMonitor, DisplayName::Hardware::inputMonitor);
|
||||
m_interfaceItems.add(inputMonitor);
|
||||
}
|
||||
}
|
||||
|
||||
TableModelPtr InputList::getModel()
|
||||
|
||||
@ -26,21 +26,28 @@
|
||||
#include "../../../core/objectlist.hpp"
|
||||
#include "../../../core/method.hpp"
|
||||
#include "../input.hpp"
|
||||
#include "../monitor/inputmonitor.hpp"
|
||||
|
||||
class InputList : public ObjectList<Input>
|
||||
{
|
||||
CLASS_ID("list.input")
|
||||
|
||||
private:
|
||||
const bool m_parentIsInputController;
|
||||
|
||||
protected:
|
||||
void worldEvent(WorldState state, WorldEvent event) final;
|
||||
bool isListedProperty(const std::string& name) final;
|
||||
|
||||
public:
|
||||
CLASS_ID("input_list")
|
||||
|
||||
Method<std::shared_ptr<Input>(std::string_view)> add;
|
||||
Method<std::shared_ptr<Input>()> add;
|
||||
Method<void(const std::shared_ptr<Input>&)> remove;
|
||||
Method<std::shared_ptr<InputMonitor>()> inputMonitor;
|
||||
|
||||
InputList(Object& _parent, const std::string& parentPropertyName);
|
||||
|
||||
inline bool parentIsInputController() const { return m_parentIsInputController; }
|
||||
|
||||
TableModelPtr getModel() final;
|
||||
};
|
||||
|
||||
|
||||
@ -22,30 +22,39 @@
|
||||
|
||||
#include "inputlisttablemodel.hpp"
|
||||
#include "inputlist.hpp"
|
||||
#include "../loconetinput.hpp"
|
||||
#include "../../../utils/displayname.hpp"
|
||||
|
||||
constexpr uint32_t columnId = 0;
|
||||
constexpr uint32_t columnName = 1;
|
||||
constexpr uint32_t columnBus = 2;
|
||||
constexpr uint32_t columnAddress = 3;
|
||||
|
||||
bool InputListTableModel::isListedProperty(const std::string& name)
|
||||
{
|
||||
return
|
||||
name == "id" ||
|
||||
name == "name";
|
||||
name == "name" ||
|
||||
name == "interface" ||
|
||||
name == "address";
|
||||
}
|
||||
|
||||
InputListTableModel::InputListTableModel(InputList& list) :
|
||||
ObjectListTableModel<Input>(list)
|
||||
InputListTableModel::InputListTableModel(InputList& list)
|
||||
: ObjectListTableModel<Input>(list)
|
||||
, m_columnInterface(list.parentIsInputController() ? invalidColumn : 2)
|
||||
, m_columnAddress(list.parentIsInputController() ? 2 : 3)
|
||||
{
|
||||
setColumnHeaders({
|
||||
DisplayName::Object::id,
|
||||
DisplayName::Object::name,
|
||||
"input_list:bus",
|
||||
DisplayName::Hardware::address,
|
||||
});
|
||||
if(list.parentIsInputController())
|
||||
{
|
||||
setColumnHeaders({
|
||||
DisplayName::Object::id,
|
||||
DisplayName::Object::name,
|
||||
DisplayName::Hardware::address,
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
setColumnHeaders({
|
||||
DisplayName::Object::id,
|
||||
DisplayName::Object::name,
|
||||
DisplayName::Hardware::interface,
|
||||
DisplayName::Hardware::address,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
std::string InputListTableModel::getText(uint32_t column, uint32_t row) const
|
||||
@ -53,32 +62,27 @@ std::string InputListTableModel::getText(uint32_t column, uint32_t row) const
|
||||
if(row < rowCount())
|
||||
{
|
||||
const Input& input = getItem(row);
|
||||
const LocoNetInput* inputLocoNet = dynamic_cast<const LocoNetInput*>(&input);
|
||||
|
||||
switch(column)
|
||||
if(column == columnId)
|
||||
return input.id;
|
||||
else if(column == columnName)
|
||||
return input.name;
|
||||
else if(column == m_columnInterface)
|
||||
{
|
||||
case columnId:
|
||||
return input.id;
|
||||
|
||||
case columnName:
|
||||
return input.name;
|
||||
|
||||
case columnBus: // virtual method @ Input ??
|
||||
if(inputLocoNet && inputLocoNet->loconet)
|
||||
return inputLocoNet->loconet->getObjectId();
|
||||
if(const auto& interface = std::dynamic_pointer_cast<Object>(input.interface.value()))
|
||||
{
|
||||
if(auto property = interface->getProperty("name"); property && !property->toString().empty())
|
||||
return property->toString();
|
||||
else
|
||||
return interface->getObjectId();
|
||||
}
|
||||
else
|
||||
return "";
|
||||
|
||||
case columnAddress: // virtual method @ Input ??
|
||||
if(inputLocoNet)
|
||||
return std::to_string(inputLocoNet->address);
|
||||
else
|
||||
return "";
|
||||
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
else if(column == m_columnAddress)
|
||||
return std::to_string(input.address.value());
|
||||
else
|
||||
assert(false);
|
||||
}
|
||||
|
||||
return "";
|
||||
@ -90,4 +94,8 @@ void InputListTableModel::propertyChanged(BaseProperty& property, uint32_t row)
|
||||
changed(row, columnId);
|
||||
else if(property.name() == "name")
|
||||
changed(row, columnName);
|
||||
}
|
||||
else if(property.name() == "interface" && m_columnInterface != invalidColumn)
|
||||
changed(row, m_columnInterface);
|
||||
else if(property.name() == "address")
|
||||
changed(row, m_columnAddress);
|
||||
}
|
||||
|
||||
@ -32,6 +32,12 @@ class InputListTableModel : public ObjectListTableModel<Input>
|
||||
{
|
||||
friend class InputList;
|
||||
|
||||
private:
|
||||
static constexpr uint32_t columnId = 0;
|
||||
static constexpr uint32_t columnName = 1;
|
||||
const uint32_t m_columnInterface;
|
||||
const uint32_t m_columnAddress;
|
||||
|
||||
protected:
|
||||
void propertyChanged(BaseProperty& property, uint32_t row) final;
|
||||
|
||||
|
||||
@ -1,104 +0,0 @@
|
||||
/**
|
||||
* server/src/hardware/input/loconetinput.cpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "loconetinput.hpp"
|
||||
#include "../../core/attributes.hpp"
|
||||
#include "../../world/world.hpp"
|
||||
#include "../../log/log.hpp"
|
||||
#include "../../utils/displayname.hpp"
|
||||
|
||||
LocoNetInput::LocoNetInput(const std::weak_ptr<World> world, std::string_view _id) :
|
||||
Input(world, _id),
|
||||
loconet{this, "loconet", nullptr, PropertyFlags::ReadWrite | PropertyFlags::Store,
|
||||
[this](const std::shared_ptr<LocoNet::LocoNet>& newValue)
|
||||
{
|
||||
if(!newValue || newValue->addInput(*this))
|
||||
{
|
||||
if(loconet.value())
|
||||
loconet->removeInput(*this);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}},
|
||||
address{this, "address", addressMin, PropertyFlags::ReadWrite | PropertyFlags::Store, nullptr,
|
||||
[this](const uint16_t& newValue)
|
||||
{
|
||||
if(loconet)
|
||||
return loconet->changeInputAddress(*this, newValue);
|
||||
return true;
|
||||
}}
|
||||
{
|
||||
auto w = world.lock();
|
||||
const bool editable = w && contains(w->state.value(), WorldState::Edit);
|
||||
|
||||
Attributes::addDisplayName(loconet, DisplayName::Hardware::loconet);
|
||||
Attributes::addEnabled(loconet, editable);
|
||||
Attributes::addObjectList(loconet, w->loconets);
|
||||
m_interfaceItems.add(loconet);
|
||||
|
||||
Attributes::addDisplayName(address, DisplayName::Hardware::address);
|
||||
Attributes::addEnabled(address, editable);
|
||||
Attributes::addMinMax(address, addressMin, addressMax);
|
||||
m_interfaceItems.add(address);
|
||||
}
|
||||
|
||||
void LocoNetInput::loaded()
|
||||
{
|
||||
Input::loaded();
|
||||
if(loconet)
|
||||
{
|
||||
if(!loconet->addInput(*this))
|
||||
{
|
||||
Log::log(*this, LogMessage::C2001_ADDRESS_ALREADY_USED_AT_X, *loconet);
|
||||
loconet.setValueInternal(nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LocoNetInput::destroying()
|
||||
{
|
||||
if(loconet)
|
||||
loconet = nullptr;
|
||||
Input::destroying();
|
||||
}
|
||||
|
||||
void LocoNetInput::worldEvent(WorldState state, WorldEvent event)
|
||||
{
|
||||
Input::worldEvent(state, event);
|
||||
|
||||
const bool editable = contains(state, WorldState::Edit);
|
||||
|
||||
Attributes::setEnabled(loconet, editable);
|
||||
Attributes::setEnabled(address, editable);
|
||||
}
|
||||
|
||||
void LocoNetInput::idChanged(const std::string& newId)
|
||||
{
|
||||
if(loconet)
|
||||
loconet->inputMonitorIdChanged(address, newId);
|
||||
}
|
||||
|
||||
void LocoNetInput::valueChanged(TriState _value)
|
||||
{
|
||||
if(loconet)
|
||||
loconet->inputMonitorValueChanged(address, _value);
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
/**
|
||||
* server/src/hardware/input/monitor/loconetinputmonitor.cpp
|
||||
* server/src/hardware/input/monitor/inputmonitor.cpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
@ -20,31 +20,29 @@
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "loconetinputmonitor.hpp"
|
||||
#include "../../protocol/loconet/loconet.hpp"
|
||||
#include "../loconetinput.hpp"
|
||||
#include "inputmonitor.hpp"
|
||||
#include "../inputcontroller.hpp"
|
||||
#include "../input.hpp"
|
||||
|
||||
LocoNetInputMonitor::LocoNetInputMonitor(std::shared_ptr<LocoNet::LocoNet> loconet) :
|
||||
InputMonitor(),
|
||||
m_loconet{std::move(loconet)}
|
||||
InputMonitor::InputMonitor(InputController& controller)
|
||||
: Object()
|
||||
, m_controller{controller}
|
||||
, addressMin{this, "address_min", m_controller.inputAddressMinMax().first, PropertyFlags::ReadOnly | PropertyFlags::NoStore}
|
||||
, addressMax{this, "address_max", m_controller.inputAddressMinMax().second, PropertyFlags::ReadOnly | PropertyFlags::NoStore}
|
||||
{
|
||||
addressMin.setValueInternal(LocoNetInput::addressMin);
|
||||
addressMax.setValueInternal(LocoNetInput::addressMax);
|
||||
m_loconet->m_inputMonitors.emplace_back(this);
|
||||
}
|
||||
|
||||
LocoNetInputMonitor::~LocoNetInputMonitor()
|
||||
std::string InputMonitor::getObjectId() const
|
||||
{
|
||||
if(auto it = std::find(m_loconet->m_inputMonitors.begin(), m_loconet->m_inputMonitors.end(), this); it != m_loconet->m_inputMonitors.end())
|
||||
m_loconet->m_inputMonitors.erase(it);
|
||||
return ""; // todo
|
||||
}
|
||||
|
||||
std::vector<InputMonitor::InputInfo> LocoNetInputMonitor::getInputInfo() const
|
||||
std::vector<InputMonitor::InputInfo> InputMonitor::getInputInfo() const
|
||||
{
|
||||
std::vector<InputInfo> inputInfo;
|
||||
for(auto it : m_loconet->m_inputs)
|
||||
for(auto it : m_controller.inputs())
|
||||
{
|
||||
LocoNetInput& input = *(it.second);
|
||||
const auto& input = *(it.second);
|
||||
InputInfo info(input.address, input.id, input.value);
|
||||
inputInfo.push_back(info);
|
||||
}
|
||||
@ -28,11 +28,18 @@
|
||||
#include "../../../core/property.hpp"
|
||||
#include "../../../enum/tristate.hpp"
|
||||
|
||||
class InputController;
|
||||
|
||||
class InputMonitor : public Object
|
||||
{
|
||||
CLASS_ID("input_monitor")
|
||||
|
||||
private:
|
||||
InputController& m_controller;
|
||||
|
||||
public:
|
||||
std::function<void(InputMonitor&, uint32_t, std::string_view)> inputIdChanged;
|
||||
std::function<void(InputMonitor&, uint32_t, TriState)> inputValueChanged;
|
||||
boost::signals2::signal<void(InputMonitor&, uint32_t, std::string_view)> inputIdChanged;
|
||||
boost::signals2::signal<void(InputMonitor&, uint32_t, TriState)> inputValueChanged;
|
||||
|
||||
struct InputInfo
|
||||
{
|
||||
@ -51,14 +58,11 @@ class InputMonitor : public Object
|
||||
Property<uint32_t> addressMin;
|
||||
Property<uint32_t> addressMax;
|
||||
|
||||
InputMonitor() :
|
||||
Object(),
|
||||
addressMin{this, "address_min", 0, PropertyFlags::ReadOnly | PropertyFlags::NoStore},
|
||||
addressMax{this, "address_max", 0, PropertyFlags::ReadOnly | PropertyFlags::NoStore}
|
||||
{
|
||||
}
|
||||
InputMonitor(InputController& controller);
|
||||
|
||||
virtual std::vector<InputInfo> getInputInfo() const = 0;
|
||||
std::string getObjectId() const final;
|
||||
|
||||
virtual std::vector<InputInfo> getInputInfo() const;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@ -1,52 +0,0 @@
|
||||
/**
|
||||
* server/src/hardware/input/monitor/xpressnetinputmonitor.cpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "xpressnetinputmonitor.hpp"
|
||||
#include "../../protocol/xpressnet/xpressnet.hpp"
|
||||
#include "../xpressnetinput.hpp"
|
||||
|
||||
XpressNetInputMonitor::XpressNetInputMonitor(std::shared_ptr<XpressNet::XpressNet> xpressnet) :
|
||||
InputMonitor(),
|
||||
m_xpressnet{std::move(xpressnet)}
|
||||
{
|
||||
addressMin.setValueInternal(XpressNetInput::addressMin);
|
||||
addressMax.setValueInternal(XpressNetInput::addressMax);
|
||||
m_xpressnet->m_inputMonitors.emplace_back(this);
|
||||
}
|
||||
|
||||
XpressNetInputMonitor::~XpressNetInputMonitor()
|
||||
{
|
||||
if(auto it = std::find(m_xpressnet->m_inputMonitors.begin(), m_xpressnet->m_inputMonitors.end(), this); it != m_xpressnet->m_inputMonitors.end())
|
||||
m_xpressnet->m_inputMonitors.erase(it);
|
||||
}
|
||||
|
||||
std::vector<InputMonitor::InputInfo> XpressNetInputMonitor::getInputInfo() const
|
||||
{
|
||||
std::vector<InputInfo> inputInfo;
|
||||
for(auto it : m_xpressnet->m_inputs)
|
||||
{
|
||||
XpressNetInput& input = *(it.second);
|
||||
InputInfo info(input.address, input.id, input.value);
|
||||
inputInfo.push_back(info);
|
||||
}
|
||||
return inputInfo;
|
||||
}
|
||||
@ -1,104 +0,0 @@
|
||||
/**
|
||||
* server/src/hardware/input/xpressnetinput.cpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "xpressnetinput.hpp"
|
||||
#include "../../core/attributes.hpp"
|
||||
#include "../../world/world.hpp"
|
||||
#include "../../log/log.hpp"
|
||||
#include "../../utils/displayname.hpp"
|
||||
|
||||
XpressNetInput::XpressNetInput(const std::weak_ptr<World> world, std::string_view _id) :
|
||||
Input(world, _id),
|
||||
xpressnet{this, "xpressnet", nullptr, PropertyFlags::ReadWrite | PropertyFlags::Store,
|
||||
[this](const std::shared_ptr<XpressNet::XpressNet>& newValue)
|
||||
{
|
||||
if(!newValue || newValue->addInput(*this))
|
||||
{
|
||||
if(xpressnet.value())
|
||||
xpressnet->removeInput(*this);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}},
|
||||
address{this, "address", addressMin, PropertyFlags::ReadWrite | PropertyFlags::Store, nullptr,
|
||||
[this](const uint16_t& newValue)
|
||||
{
|
||||
if(xpressnet)
|
||||
return xpressnet->changeInputAddress(*this, newValue);
|
||||
return true;
|
||||
}}
|
||||
{
|
||||
auto w = world.lock();
|
||||
const bool editable = w && contains(w->state.value(), WorldState::Edit);
|
||||
|
||||
Attributes::addDisplayName(xpressnet, DisplayName::Hardware::xpressnet);
|
||||
Attributes::addEnabled(xpressnet, editable);
|
||||
Attributes::addObjectList(xpressnet, w->xpressnets);
|
||||
m_interfaceItems.add(xpressnet);
|
||||
|
||||
Attributes::addDisplayName(address, DisplayName::Hardware::address);
|
||||
Attributes::addEnabled(address, editable);
|
||||
Attributes::addMinMax(address, addressMin, addressMax);
|
||||
m_interfaceItems.add(address);
|
||||
}
|
||||
|
||||
void XpressNetInput::loaded()
|
||||
{
|
||||
Input::loaded();
|
||||
if(xpressnet)
|
||||
{
|
||||
if(!xpressnet->addInput(*this))
|
||||
{
|
||||
Log::log(*this, LogMessage::C2001_ADDRESS_ALREADY_USED_AT_X, *xpressnet);
|
||||
xpressnet.setValueInternal(nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void XpressNetInput::destroying()
|
||||
{
|
||||
if(xpressnet)
|
||||
xpressnet = nullptr;
|
||||
Input::destroying();
|
||||
}
|
||||
|
||||
void XpressNetInput::worldEvent(WorldState state, WorldEvent event)
|
||||
{
|
||||
Input::worldEvent(state, event);
|
||||
|
||||
const bool editable = contains(state, WorldState::Edit);
|
||||
|
||||
Attributes::setEnabled(xpressnet, editable);
|
||||
Attributes::setEnabled(address, editable);
|
||||
}
|
||||
|
||||
void XpressNetInput::idChanged(const std::string& newId)
|
||||
{
|
||||
if(xpressnet)
|
||||
xpressnet->inputMonitorIdChanged(address, newId);
|
||||
}
|
||||
|
||||
void XpressNetInput::valueChanged(TriState _value)
|
||||
{
|
||||
if(xpressnet)
|
||||
xpressnet->inputMonitorValueChanged(address, _value);
|
||||
}
|
||||
@ -1,57 +0,0 @@
|
||||
/**
|
||||
* server/src/hardware/input/xpressnetinput.hpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef TRAINTASTIC_SERVER_HARDWARE_INPUT_XPRESSNETINPUT_HPP
|
||||
#define TRAINTASTIC_SERVER_HARDWARE_INPUT_XPRESSNETINPUT_HPP
|
||||
|
||||
#include "input.hpp"
|
||||
#include "../../core/objectproperty.hpp"
|
||||
#include "../protocol/xpressnet/xpressnet.hpp"
|
||||
|
||||
class XpressNetInput : public Input
|
||||
{
|
||||
friend class XpressNet::XpressNet;
|
||||
|
||||
protected:
|
||||
void loaded() final;
|
||||
void destroying() final;
|
||||
void worldEvent(WorldState state, WorldEvent event) final;
|
||||
void idChanged(const std::string& newId) final;
|
||||
void valueChanged(TriState _value) final;
|
||||
|
||||
inline void updateValue(TriState _value) { Input::updateValue(_value); }
|
||||
|
||||
public:
|
||||
CLASS_ID("input.xpressnet")
|
||||
CREATE(XpressNetInput)
|
||||
|
||||
static constexpr uint16_t addressInvalid = 0;
|
||||
static constexpr uint16_t addressMin = 1;
|
||||
static constexpr uint16_t addressMax = 2048;
|
||||
|
||||
ObjectProperty<XpressNet::XpressNet> xpressnet;
|
||||
Property<uint16_t> address;
|
||||
|
||||
XpressNetInput(const std::weak_ptr<World> world, std::string_view _id);
|
||||
};
|
||||
|
||||
#endif
|
||||
263
server/src/hardware/interface/dccplusplusinterface.cpp
Normale Datei
263
server/src/hardware/interface/dccplusplusinterface.cpp
Normale Datei
@ -0,0 +1,263 @@
|
||||
/**
|
||||
* server/src/hardware/interface/dccplusplusinterface.cpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "dccplusplusinterface.hpp"
|
||||
#include "../input/list/inputlisttablemodel.hpp"
|
||||
#include "../output/list/outputlisttablemodel.hpp"
|
||||
#include "../protocol/dccplusplus/messages.hpp"
|
||||
#include "../protocol/dccplusplus/iohandler/serialiohandler.hpp"
|
||||
#include "../../core/attributes.hpp"
|
||||
#include "../../log/log.hpp"
|
||||
#include "../../log/logmessageexception.hpp"
|
||||
#include "../../utils/displayname.hpp"
|
||||
#include "../../utils/inrange.hpp"
|
||||
#include "../../utils/serialport.hpp"
|
||||
#include "../../world/world.hpp"
|
||||
|
||||
DCCPlusPlusInterface::DCCPlusPlusInterface(const std::weak_ptr<World>& world, std::string_view _id)
|
||||
: Interface(world, _id)
|
||||
, device{this, "device", "", PropertyFlags::ReadWrite | PropertyFlags::Store}
|
||||
, baudrate{this, "baudrate", 115200, PropertyFlags::ReadWrite | PropertyFlags::Store}
|
||||
, flowControl{this, "flow_control", SerialFlowControl::None, PropertyFlags::ReadWrite | PropertyFlags::Store}
|
||||
, dccplusplus{this, "dccplusplus", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject}
|
||||
, decoders{this, "decoders", nullptr, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::SubObject}
|
||||
{
|
||||
dccplusplus.setValueInternal(std::make_shared<DCCPlusPlus::Settings>(*this, dccplusplus.name()));
|
||||
decoders.setValueInternal(std::make_shared<DecoderList>(*this, decoders.name()));
|
||||
|
||||
Attributes::addDisplayName(device, DisplayName::Serial::device);
|
||||
Attributes::addEnabled(device, !online);
|
||||
m_interfaceItems.insertBefore(device, notes);
|
||||
|
||||
Attributes::addDisplayName(baudrate, DisplayName::Serial::baudrate);
|
||||
Attributes::addEnabled(baudrate, !online);
|
||||
Attributes::addMinMax(baudrate, SerialPort::baudrateMin, SerialPort::baudrateMax);
|
||||
Attributes::addValues(baudrate, SerialPort::baudrateValues);
|
||||
m_interfaceItems.insertBefore(baudrate, notes);
|
||||
|
||||
Attributes::addDisplayName(flowControl, DisplayName::Serial::flowControl);
|
||||
Attributes::addEnabled(flowControl, !online);
|
||||
Attributes::addValues(flowControl, SerialFlowControlValues);
|
||||
m_interfaceItems.insertBefore(flowControl, notes);
|
||||
|
||||
Attributes::addDisplayName(dccplusplus, DisplayName::Hardware::dccplusplus);
|
||||
m_interfaceItems.insertBefore(dccplusplus, notes);
|
||||
|
||||
Attributes::addDisplayName(decoders, DisplayName::Hardware::decoders);
|
||||
m_interfaceItems.insertBefore(decoders, notes);
|
||||
}
|
||||
|
||||
bool DCCPlusPlusInterface::addDecoder(Decoder& decoder)
|
||||
{
|
||||
const bool success = DecoderController::addDecoder(decoder);
|
||||
if(success)
|
||||
decoders->addObject(decoder.shared_ptr<Decoder>());
|
||||
return success;
|
||||
}
|
||||
|
||||
bool DCCPlusPlusInterface::removeDecoder(Decoder& decoder)
|
||||
{
|
||||
const bool success = DecoderController::removeDecoder(decoder);
|
||||
if(success)
|
||||
decoders->removeObject(decoder.shared_ptr<Decoder>());
|
||||
return success;
|
||||
}
|
||||
|
||||
void DCCPlusPlusInterface::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber)
|
||||
{
|
||||
if(m_kernel)
|
||||
m_kernel->decoderChanged(decoder, changes, functionNumber);
|
||||
}
|
||||
|
||||
bool DCCPlusPlusInterface::setOnline(bool& value)
|
||||
{
|
||||
if(!m_kernel && value)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_kernel = DCCPlusPlus::Kernel::create<DCCPlusPlus::SerialIOHandler>(dccplusplus->config(), device.value(), baudrate.value(), flowControl.value());
|
||||
|
||||
status.setValueInternal(InterfaceStatus::Initializing);
|
||||
|
||||
m_kernel->setLogId(id.value());
|
||||
m_kernel->setOnStarted(
|
||||
[this]()
|
||||
{
|
||||
status.setValueInternal(InterfaceStatus::Online);
|
||||
|
||||
if(auto w = m_world.lock())
|
||||
{
|
||||
const bool powerOn = contains(w->state.value(), WorldState::PowerOn);
|
||||
|
||||
if(!powerOn)
|
||||
m_kernel->powerOff();
|
||||
|
||||
if(contains(w->state.value(), WorldState::Run))
|
||||
{
|
||||
m_kernel->clearEmergencyStop();
|
||||
restoreDecoderSpeed();
|
||||
}
|
||||
else
|
||||
m_kernel->emergencyStop();
|
||||
|
||||
if(powerOn)
|
||||
m_kernel->powerOn();
|
||||
}
|
||||
});
|
||||
m_kernel->setOnPowerOnChanged(
|
||||
[this](bool powerOn)
|
||||
{
|
||||
if(auto w = m_world.lock())
|
||||
{
|
||||
if(powerOn && !contains(w->state.value(), WorldState::PowerOn))
|
||||
w->powerOn();
|
||||
else if(!powerOn && contains(w->state.value(), WorldState::PowerOn))
|
||||
w->powerOff();
|
||||
}
|
||||
});
|
||||
m_kernel->setDecoderController(this);
|
||||
m_kernel->start();
|
||||
|
||||
m_dccplusplusPropertyChanged = dccplusplus->propertyChanged.connect(
|
||||
[this](BaseProperty& property)
|
||||
{
|
||||
if(&property != &dccplusplus->startupDelay)
|
||||
m_kernel->setConfig(dccplusplus->config());
|
||||
});
|
||||
|
||||
Attributes::setEnabled({device, baudrate, flowControl}, false);
|
||||
}
|
||||
catch(const LogMessageException& e)
|
||||
{
|
||||
status.setValueInternal(InterfaceStatus::Offline);
|
||||
Log::log(*this, e.message(), e.args());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if(m_kernel && !value)
|
||||
{
|
||||
Attributes::setEnabled({device, baudrate, flowControl}, true);
|
||||
|
||||
m_dccplusplusPropertyChanged.disconnect();
|
||||
|
||||
m_kernel->stop();
|
||||
m_kernel.reset();
|
||||
|
||||
status.setValueInternal(InterfaceStatus::Offline);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void DCCPlusPlusInterface::addToWorld()
|
||||
{
|
||||
Interface::addToWorld();
|
||||
|
||||
if(auto world = m_world.lock())
|
||||
{
|
||||
world->decoderControllers->add(std::dynamic_pointer_cast<DecoderController>(shared_from_this()));
|
||||
}
|
||||
}
|
||||
|
||||
void DCCPlusPlusInterface::loaded()
|
||||
{
|
||||
Interface::loaded();
|
||||
|
||||
check();
|
||||
}
|
||||
|
||||
void DCCPlusPlusInterface::destroying()
|
||||
{
|
||||
for(const auto& decoder : *decoders)
|
||||
{
|
||||
assert(decoder->interface.value() == std::dynamic_pointer_cast<DecoderController>(shared_from_this()));
|
||||
decoder->interface = nullptr;
|
||||
}
|
||||
|
||||
if(auto world = m_world.lock())
|
||||
{
|
||||
world->decoderControllers->remove(std::dynamic_pointer_cast<DecoderController>(shared_from_this()));
|
||||
}
|
||||
|
||||
Interface::destroying();
|
||||
}
|
||||
|
||||
void DCCPlusPlusInterface::worldEvent(WorldState state, WorldEvent event)
|
||||
{
|
||||
Interface::worldEvent(state, event);
|
||||
|
||||
if(m_kernel)
|
||||
{
|
||||
switch(event)
|
||||
{
|
||||
case WorldEvent::PowerOff:
|
||||
m_kernel->powerOff();
|
||||
break;
|
||||
|
||||
case WorldEvent::PowerOn:
|
||||
m_kernel->powerOn();
|
||||
break;
|
||||
|
||||
case WorldEvent::Stop:
|
||||
m_kernel->emergencyStop();
|
||||
break;
|
||||
|
||||
case WorldEvent::Run:
|
||||
m_kernel->clearEmergencyStop();
|
||||
restoreDecoderSpeed();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DCCPlusPlusInterface::check() const
|
||||
{
|
||||
for(const auto& decoder : *decoders)
|
||||
checkDecoder(*decoder);
|
||||
}
|
||||
|
||||
void DCCPlusPlusInterface::checkDecoder(const Decoder& decoder) const
|
||||
{
|
||||
if(decoder.protocol != DecoderProtocol::Auto && decoder.protocol != DecoderProtocol::DCC)
|
||||
Log::log(decoder, LogMessage::C2002_DCCPLUSPLUS_ONLY_SUPPORTS_THE_DCC_PROTOCOL);
|
||||
|
||||
if(decoder.protocol == DecoderProtocol::DCC && decoder.address <= 127 && decoder.longAddress)
|
||||
Log::log(decoder, LogMessage::C2003_DCCPLUSPLUS_DOESNT_SUPPORT_DCC_LONG_ADDRESSES_BELOW_128);
|
||||
|
||||
if(decoder.speedSteps != Decoder::speedStepsAuto && decoder.speedSteps != dccplusplus->speedSteps)
|
||||
Log::log(decoder, LogMessage::W2003_COMMAND_STATION_DOESNT_SUPPORT_X_SPEEDSTEPS_USING_X, decoder.speedSteps.value(), dccplusplus->speedSteps.value());
|
||||
|
||||
for(const auto& function : *decoder.functions)
|
||||
if(function->number > DCCPlusPlus::Config::functionNumberMax)
|
||||
{
|
||||
Log::log(decoder, LogMessage::W2002_COMMAND_STATION_DOESNT_SUPPORT_FUNCTIONS_ABOVE_FX, DCCPlusPlus::Config::functionNumberMax);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void DCCPlusPlusInterface::idChanged(const std::string& newId)
|
||||
{
|
||||
if(m_kernel)
|
||||
m_kernel->setLogId(newId);
|
||||
}
|
||||
77
server/src/hardware/interface/dccplusplusinterface.hpp
Normale Datei
77
server/src/hardware/interface/dccplusplusinterface.hpp
Normale Datei
@ -0,0 +1,77 @@
|
||||
/**
|
||||
* server/src/hardware/interface/dccplusplusinterface.hpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef TRAINTASTIC_SERVER_HARDWARE_INTERFACE_DCCPLUSPLUSINTERFACE_HPP
|
||||
#define TRAINTASTIC_SERVER_HARDWARE_INTERFACE_DCCPLUSPLUSINTERFACE_HPP
|
||||
|
||||
#include "interface.hpp"
|
||||
#include "../protocol/dccplusplus/kernel.hpp"
|
||||
#include "../protocol/dccplusplus/settings.hpp"
|
||||
#include "../decoder/decodercontroller.hpp"
|
||||
#include "../decoder/decoderlist.hpp"
|
||||
#include "../../core/objectproperty.hpp"
|
||||
#include "../../enum/serialflowcontrol.hpp"
|
||||
|
||||
/**
|
||||
* @brief DCC++(EX) hardware interface
|
||||
*/
|
||||
class DCCPlusPlusInterface final
|
||||
: public Interface
|
||||
, public DecoderController
|
||||
{
|
||||
CLASS_ID("interface.dccplusplus")
|
||||
CREATE(DCCPlusPlusInterface)
|
||||
DEFAULT_ID("dccplusplus")
|
||||
|
||||
private:
|
||||
std::unique_ptr<DCCPlusPlus::Kernel> m_kernel;
|
||||
boost::signals2::connection m_dccplusplusPropertyChanged;
|
||||
|
||||
void addToWorld() final;
|
||||
void loaded() final;
|
||||
void destroying() final;
|
||||
void worldEvent(WorldState state, WorldEvent event) final;
|
||||
|
||||
void check() const;
|
||||
void checkDecoder(const Decoder& decoder) const;
|
||||
|
||||
void idChanged(const std::string& newId) final;
|
||||
|
||||
protected:
|
||||
bool setOnline(bool& value) final;
|
||||
|
||||
public:
|
||||
Property<std::string> device;
|
||||
Property<uint32_t> baudrate;
|
||||
Property<SerialFlowControl> flowControl;
|
||||
ObjectProperty<DCCPlusPlus::Settings> dccplusplus;
|
||||
ObjectProperty<DecoderList> decoders;
|
||||
|
||||
DCCPlusPlusInterface(const std::weak_ptr<World>& world, std::string_view _id);
|
||||
|
||||
// DecoderController:
|
||||
[[nodiscard]] bool addDecoder(Decoder& decoder) final;
|
||||
[[nodiscard]] bool removeDecoder(Decoder& decoder) final;
|
||||
void decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber) final;
|
||||
};
|
||||
|
||||
#endif
|
||||
266
server/src/hardware/interface/ecosinterface.cpp
Normale Datei
266
server/src/hardware/interface/ecosinterface.cpp
Normale Datei
@ -0,0 +1,266 @@
|
||||
/**
|
||||
* server/src/hardware/interface/ecosinterface.cpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ecosinterface.hpp"
|
||||
#include "../input/list/inputlisttablemodel.hpp"
|
||||
#include "../output/list/outputlisttablemodel.hpp"
|
||||
#include "../protocol/ecos/iohandler/tcpiohandler.hpp"
|
||||
#include "../../core/attributes.hpp"
|
||||
#include "../../log/log.hpp"
|
||||
#include "../../log/logmessageexception.hpp"
|
||||
#include "../../utils/displayname.hpp"
|
||||
#include "../../utils/inrange.hpp"
|
||||
#include "../../world/world.hpp"
|
||||
|
||||
ECoSInterface::ECoSInterface(const std::weak_ptr<World>& world, std::string_view _id)
|
||||
: Interface(world, _id)
|
||||
, hostname{this, "hostname", "", PropertyFlags::ReadWrite | PropertyFlags::Store}
|
||||
, ecos{this, "ecos", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject}
|
||||
, decoders{this, "decoders", nullptr, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::SubObject}
|
||||
, inputs{this, "inputs", nullptr, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::SubObject}
|
||||
, outputs{this, "outputs", nullptr, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::SubObject}
|
||||
, testCommand{this, "test_command", "", PropertyFlags::ReadWrite | PropertyFlags::NoStore}
|
||||
, testCommandSend{*this, "test_command_send",
|
||||
[this]()
|
||||
{
|
||||
if(m_kernel)
|
||||
m_kernel->postSend(testCommand.value() + "\n");
|
||||
}}
|
||||
{
|
||||
name = "ECoS";
|
||||
ecos.setValueInternal(std::make_shared<ECoS::Settings>(*this, ecos.name()));
|
||||
decoders.setValueInternal(std::make_shared<DecoderList>(*this, decoders.name()));
|
||||
inputs.setValueInternal(std::make_shared<InputList>(*this, inputs.name()));
|
||||
outputs.setValueInternal(std::make_shared<OutputList>(*this, outputs.name()));
|
||||
|
||||
Attributes::addDisplayName(hostname, DisplayName::IP::hostname);
|
||||
Attributes::addEnabled(hostname, !online);
|
||||
m_interfaceItems.insertBefore(hostname, notes);
|
||||
|
||||
m_interfaceItems.insertBefore(ecos, notes);
|
||||
|
||||
Attributes::addDisplayName(decoders, DisplayName::Hardware::decoders);
|
||||
m_interfaceItems.insertBefore(decoders, notes);
|
||||
|
||||
Attributes::addDisplayName(inputs, DisplayName::Hardware::inputs);
|
||||
m_interfaceItems.insertBefore(inputs, notes);
|
||||
|
||||
Attributes::addDisplayName(outputs, DisplayName::Hardware::outputs);
|
||||
m_interfaceItems.insertBefore(outputs, notes);
|
||||
|
||||
m_interfaceItems.add(testCommand);
|
||||
m_interfaceItems.add(testCommandSend);
|
||||
}
|
||||
|
||||
bool ECoSInterface::addDecoder(Decoder& decoder)
|
||||
{
|
||||
const bool success = DecoderController::addDecoder(decoder);
|
||||
if(success)
|
||||
decoders->addObject(decoder.shared_ptr<Decoder>());
|
||||
return success;
|
||||
}
|
||||
|
||||
bool ECoSInterface::removeDecoder(Decoder& decoder)
|
||||
{
|
||||
const bool success = DecoderController::removeDecoder(decoder);
|
||||
if(success)
|
||||
decoders->removeObject(decoder.shared_ptr<Decoder>());
|
||||
return success;
|
||||
}
|
||||
|
||||
void ECoSInterface::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber)
|
||||
{
|
||||
if(m_kernel)
|
||||
m_kernel->decoderChanged(decoder, changes, functionNumber);
|
||||
}
|
||||
|
||||
bool ECoSInterface::addInput(Input& input)
|
||||
{
|
||||
const bool success = InputController::addInput(input);
|
||||
if(success)
|
||||
inputs->addObject(input.shared_ptr<Input>());
|
||||
return success;
|
||||
}
|
||||
|
||||
bool ECoSInterface::removeInput(Input& input)
|
||||
{
|
||||
const bool success = InputController::removeInput(input);
|
||||
if(success)
|
||||
inputs->removeObject(input.shared_ptr<Input>());
|
||||
return success;
|
||||
}
|
||||
|
||||
bool ECoSInterface::addOutput(Output& output)
|
||||
{
|
||||
const bool success = OutputController::addOutput(output);
|
||||
if(success)
|
||||
outputs->addObject(output.shared_ptr<Output>());
|
||||
return success;
|
||||
}
|
||||
|
||||
bool ECoSInterface::removeOutput(Output& output)
|
||||
{
|
||||
const bool success = OutputController::removeOutput(output);
|
||||
if(success)
|
||||
outputs->removeObject(output.shared_ptr<Output>());
|
||||
return success;
|
||||
}
|
||||
|
||||
bool ECoSInterface::setOutputValue(uint32_t address, bool value)
|
||||
{
|
||||
return
|
||||
m_kernel &&
|
||||
inRange(address, outputAddressMinMax()) &&
|
||||
m_kernel->setOutput(static_cast<uint16_t>(address), value);
|
||||
}
|
||||
|
||||
bool ECoSInterface::setOnline(bool& value)
|
||||
{
|
||||
if(!m_kernel && value)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_kernel = ECoS::Kernel::create<ECoS::TCPIOHandler>(ecos->config(), hostname.value());
|
||||
|
||||
status.setValueInternal(InterfaceStatus::Initializing);
|
||||
|
||||
m_kernel->setLogId(id.value());
|
||||
m_kernel->setOnStarted(
|
||||
[this]()
|
||||
{
|
||||
status.setValueInternal(InterfaceStatus::Online);
|
||||
});
|
||||
|
||||
m_kernel->setDecoderController(this);
|
||||
m_kernel->setInputController(this);
|
||||
m_kernel->setOutputController(this);
|
||||
m_kernel->start();
|
||||
|
||||
m_ecosPropertyChanged = ecos->propertyChanged.connect(
|
||||
[this](BaseProperty&)
|
||||
{
|
||||
m_kernel->setConfig(ecos->config());
|
||||
});
|
||||
|
||||
if(auto w = m_world.lock())
|
||||
{
|
||||
if(contains(w->state.value(), WorldState::Run))
|
||||
m_kernel->go();
|
||||
else
|
||||
m_kernel->emergencyStop();
|
||||
}
|
||||
|
||||
Attributes::setEnabled(hostname, false);
|
||||
}
|
||||
catch(const LogMessageException& e)
|
||||
{
|
||||
status.setValueInternal(InterfaceStatus::Offline);
|
||||
Log::log(*this, e.message(), e.args());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if(m_kernel && !value)
|
||||
{
|
||||
Attributes::setEnabled(hostname, true);
|
||||
|
||||
m_ecosPropertyChanged.disconnect();
|
||||
|
||||
m_kernel->stop();
|
||||
m_kernel.reset();
|
||||
|
||||
status.setValueInternal(InterfaceStatus::Offline);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ECoSInterface::addToWorld()
|
||||
{
|
||||
Interface::addToWorld();
|
||||
|
||||
if(auto world = m_world.lock())
|
||||
{
|
||||
world->decoderControllers->add(std::dynamic_pointer_cast<DecoderController>(shared_from_this()));
|
||||
world->inputControllers->add(std::dynamic_pointer_cast<InputController>(shared_from_this()));
|
||||
world->outputControllers->add(std::dynamic_pointer_cast<OutputController>(shared_from_this()));
|
||||
}
|
||||
}
|
||||
|
||||
void ECoSInterface::destroying()
|
||||
{
|
||||
for(const auto& decoder : *decoders)
|
||||
{
|
||||
assert(decoder->interface.value() == std::dynamic_pointer_cast<DecoderController>(shared_from_this()));
|
||||
decoder->interface = nullptr;
|
||||
}
|
||||
|
||||
for(const auto& input : *inputs)
|
||||
{
|
||||
assert(input->interface.value() == std::dynamic_pointer_cast<InputController>(shared_from_this()));
|
||||
input->interface = nullptr;
|
||||
}
|
||||
|
||||
for(const auto& output : *outputs)
|
||||
{
|
||||
assert(output->interface.value() == std::dynamic_pointer_cast<OutputController>(shared_from_this()));
|
||||
output->interface = nullptr;
|
||||
}
|
||||
|
||||
if(auto world = m_world.lock())
|
||||
{
|
||||
world->decoderControllers->remove(std::dynamic_pointer_cast<DecoderController>(shared_from_this()));
|
||||
world->inputControllers->remove(std::dynamic_pointer_cast<InputController>(shared_from_this()));
|
||||
world->outputControllers->remove(std::dynamic_pointer_cast<OutputController>(shared_from_this()));
|
||||
}
|
||||
|
||||
Interface::destroying();
|
||||
}
|
||||
|
||||
void ECoSInterface::worldEvent(WorldState state, WorldEvent event)
|
||||
{
|
||||
Interface::worldEvent(state, event);
|
||||
|
||||
if(m_kernel)
|
||||
{
|
||||
switch(event)
|
||||
{
|
||||
case WorldEvent::PowerOff:
|
||||
case WorldEvent::Stop:
|
||||
m_kernel->emergencyStop();
|
||||
break;
|
||||
|
||||
case WorldEvent::PowerOn:
|
||||
case WorldEvent::Run:
|
||||
if(contains(state, WorldState::PowerOn | WorldState::Run))
|
||||
m_kernel->go();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ECoSInterface::idChanged(const std::string& newId)
|
||||
{
|
||||
if(m_kernel)
|
||||
m_kernel->setLogId(newId);
|
||||
}
|
||||
93
server/src/hardware/interface/ecosinterface.hpp
Normale Datei
93
server/src/hardware/interface/ecosinterface.hpp
Normale Datei
@ -0,0 +1,93 @@
|
||||
/**
|
||||
* server/src/hardware/interface/ecosinterface.hpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef TRAINTASTIC_SERVER_HARDWARE_INTERFACE_ECOSINTERFACE_HPP
|
||||
#define TRAINTASTIC_SERVER_HARDWARE_INTERFACE_ECOSINTERFACE_HPP
|
||||
|
||||
#include "interface.hpp"
|
||||
#include "../protocol/ecos/kernel.hpp"
|
||||
#include "../protocol/ecos/settings.hpp"
|
||||
#include "../decoder/decodercontroller.hpp"
|
||||
#include "../decoder/decoderlist.hpp"
|
||||
#include "../input/inputcontroller.hpp"
|
||||
#include "../input/list/inputlist.hpp"
|
||||
#include "../output/outputcontroller.hpp"
|
||||
#include "../output/list/outputlist.hpp"
|
||||
#include "../../core/objectproperty.hpp"
|
||||
|
||||
/**
|
||||
* @brief ECoS hardware interface
|
||||
*/
|
||||
class ECoSInterface final
|
||||
: public Interface
|
||||
, public DecoderController
|
||||
, public InputController
|
||||
, public OutputController
|
||||
{
|
||||
CLASS_ID("interface.ecos")
|
||||
DEFAULT_ID("ecos")
|
||||
CREATE(ECoSInterface)
|
||||
|
||||
private:
|
||||
std::unique_ptr<ECoS::Kernel> m_kernel;
|
||||
boost::signals2::connection m_ecosPropertyChanged;
|
||||
|
||||
void addToWorld() final;
|
||||
void destroying() final;
|
||||
void worldEvent(WorldState state, WorldEvent event) final;
|
||||
|
||||
void idChanged(const std::string& newId) final;
|
||||
|
||||
void typeChanged();
|
||||
|
||||
protected:
|
||||
bool setOnline(bool& value) final;
|
||||
|
||||
public:
|
||||
Property<std::string> hostname;
|
||||
ObjectProperty<ECoS::Settings> ecos;
|
||||
ObjectProperty<DecoderList> decoders;
|
||||
ObjectProperty<InputList> inputs;
|
||||
ObjectProperty<OutputList> outputs;
|
||||
Property<std::string> testCommand;
|
||||
Method<void()> testCommandSend;
|
||||
|
||||
ECoSInterface(const std::weak_ptr<World>& world, std::string_view _id);
|
||||
|
||||
// DecoderController:
|
||||
[[nodiscard]] bool addDecoder(Decoder& decoder) final;
|
||||
[[nodiscard]] bool removeDecoder(Decoder& decoder) final;
|
||||
void decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber) final;
|
||||
|
||||
// InputController:
|
||||
std::pair<uint32_t, uint32_t> inputAddressMinMax() const final { return {1, 1}; }
|
||||
[[nodiscard]] bool addInput(Input& input) final;
|
||||
[[nodiscard]] bool removeInput(Input& input) final;
|
||||
|
||||
// OutputController:
|
||||
std::pair<uint32_t, uint32_t> outputAddressMinMax() const final { return {1, 1}; }
|
||||
[[nodiscard]] bool addOutput(Output& output) final;
|
||||
[[nodiscard]] bool removeOutput(Output& output) final;
|
||||
[[nodiscard]] bool setOutputValue(uint32_t address, bool value) final;
|
||||
};
|
||||
|
||||
#endif
|
||||
88
server/src/hardware/interface/interface.cpp
Normale Datei
88
server/src/hardware/interface/interface.cpp
Normale Datei
@ -0,0 +1,88 @@
|
||||
/**
|
||||
* server/src/hardware/interface/interface.cpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2021-2022 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "interface.hpp"
|
||||
#include "interfacelisttablemodel.hpp"
|
||||
#include "../../core/attributes.hpp"
|
||||
#include "../../utils/displayname.hpp"
|
||||
#include "../../world/world.hpp"
|
||||
|
||||
Interface::Interface(const std::weak_ptr<World>& world, std::string_view _id)
|
||||
: IdObject(world, _id)
|
||||
, name{this, "name", "", PropertyFlags::ReadWrite | PropertyFlags::Store}
|
||||
, online{this, "online", false, PropertyFlags::ReadWrite | PropertyFlags::NoStore, nullptr, std::bind(&Interface::setOnline, this, std::placeholders::_1)}
|
||||
, status{this, "status", InterfaceStatus::Offline, PropertyFlags::ReadOnly | PropertyFlags::NoStore}
|
||||
, notes{this, "notes", "", PropertyFlags::ReadWrite | PropertyFlags::Store}
|
||||
{
|
||||
auto w = world.lock();
|
||||
const bool editable = w && contains(w->state.value(), WorldState::Edit);
|
||||
|
||||
Attributes::addDisplayName(name, DisplayName::Object::name);
|
||||
Attributes::addEnabled(name, editable);
|
||||
m_interfaceItems.add(name);
|
||||
|
||||
Attributes::addDisplayName(online, DisplayName::Interface::online);
|
||||
m_interfaceItems.add(online);
|
||||
|
||||
Attributes::addDisplayName(status, DisplayName::Interface::status);
|
||||
Attributes::addValues(status, interfaceStatusValues);
|
||||
m_interfaceItems.add(status);
|
||||
|
||||
Attributes::addDisplayName(notes, DisplayName::Object::notes);
|
||||
m_interfaceItems.add(notes);
|
||||
}
|
||||
|
||||
void Interface::addToWorld()
|
||||
{
|
||||
IdObject::addToWorld();
|
||||
|
||||
if(auto world = m_world.lock())
|
||||
world->interfaces->addObject(shared_ptr<Interface>());
|
||||
}
|
||||
|
||||
void Interface::destroying()
|
||||
{
|
||||
if(auto world = m_world.lock())
|
||||
world->interfaces->removeObject(shared_ptr<Interface>());
|
||||
IdObject::destroying();
|
||||
}
|
||||
|
||||
void Interface::worldEvent(WorldState state, WorldEvent event)
|
||||
{
|
||||
IdObject::worldEvent(state, event);
|
||||
|
||||
Attributes::setEnabled(name, contains(state, WorldState::Edit));
|
||||
|
||||
switch(event)
|
||||
{
|
||||
case WorldEvent::Offline:
|
||||
online = false;
|
||||
break;
|
||||
|
||||
case WorldEvent::Online:
|
||||
online = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
50
server/src/hardware/interface/interface.hpp
Normale Datei
50
server/src/hardware/interface/interface.hpp
Normale Datei
@ -0,0 +1,50 @@
|
||||
/**
|
||||
* server/src/hardware/interface/interface.hpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef TRAINTASTIC_SERVER_HARDWARE_INTERFACE_INTERFACE_HPP
|
||||
#define TRAINTASTIC_SERVER_HARDWARE_INTERFACE_INTERFACE_HPP
|
||||
|
||||
#include "../../core/idobject.hpp"
|
||||
#include "../../enum/interfacestatus.hpp"
|
||||
|
||||
/**
|
||||
* @brief Base class for a hardware interface
|
||||
*/
|
||||
class Interface : public IdObject
|
||||
{
|
||||
protected:
|
||||
Interface(const std::weak_ptr<World>& world, std::string_view _id);
|
||||
|
||||
void addToWorld() override;
|
||||
void destroying() override;
|
||||
void worldEvent(WorldState state, WorldEvent event) override;
|
||||
|
||||
virtual bool setOnline(bool& value) = 0;
|
||||
|
||||
public:
|
||||
Property<std::string> name;
|
||||
Property<bool> online;
|
||||
Property<InterfaceStatus> status;
|
||||
Property<std::string> notes;
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -1,9 +1,9 @@
|
||||
/**
|
||||
* server/src/hardware/commandstation/commandstationlist.cpp
|
||||
* server/src/hardware/interface/interfacelist.cpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2020 Reinder Feenstra
|
||||
* Copyright (C) 2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -20,26 +20,26 @@
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "commandstationlist.hpp"
|
||||
#include "commandstationlisttablemodel.hpp"
|
||||
#include "commandstations.hpp"
|
||||
#include "interfacelist.hpp"
|
||||
#include "interfacelisttablemodel.hpp"
|
||||
#include "interfaces.hpp"
|
||||
#include "../../world/world.hpp"
|
||||
#include "../../world/getworld.hpp"
|
||||
#include "../../core/attributes.hpp"
|
||||
#include "../../utils/displayname.hpp"
|
||||
|
||||
CommandStationList::CommandStationList(Object& _parent, const std::string& parentPropertyName) :
|
||||
ObjectList<CommandStation>(_parent, parentPropertyName),
|
||||
InterfaceList::InterfaceList(Object& _parent, const std::string& parentPropertyName) :
|
||||
ObjectList<Interface>(_parent, parentPropertyName),
|
||||
add{*this, "add",
|
||||
[this](std::string_view commandStationClassId)
|
||||
[this](std::string_view interfaceClassId)
|
||||
{
|
||||
auto world = getWorld(this);
|
||||
if(!world)
|
||||
return std::shared_ptr<CommandStation>();
|
||||
return CommandStations::create(world, commandStationClassId, world->getUniqueId("cs"));
|
||||
return std::shared_ptr<Interface>();
|
||||
return Interfaces::create(world, interfaceClassId);
|
||||
}},
|
||||
remove{*this, "remove",
|
||||
[this](const std::shared_ptr<CommandStation>& object)
|
||||
[this](const std::shared_ptr<Interface>& object)
|
||||
{
|
||||
if(containsObject(object))
|
||||
object->destroy();
|
||||
@ -51,7 +51,7 @@ CommandStationList::CommandStationList(Object& _parent, const std::string& paren
|
||||
|
||||
Attributes::addDisplayName(add, DisplayName::List::add);
|
||||
Attributes::addEnabled(add, editable);
|
||||
Attributes::addClassList(add, CommandStations::classList);
|
||||
Attributes::addClassList(add, Interfaces::classList);
|
||||
m_interfaceItems.add(add);
|
||||
|
||||
Attributes::addDisplayName(remove, DisplayName::List::remove);
|
||||
@ -59,14 +59,14 @@ CommandStationList::CommandStationList(Object& _parent, const std::string& paren
|
||||
m_interfaceItems.add(remove);
|
||||
}
|
||||
|
||||
TableModelPtr CommandStationList::getModel()
|
||||
TableModelPtr InterfaceList::getModel()
|
||||
{
|
||||
return std::make_shared<CommandStationListTableModel>(*this);
|
||||
return std::make_shared<InterfaceListTableModel>(*this);
|
||||
}
|
||||
|
||||
void CommandStationList::worldEvent(WorldState state, WorldEvent event)
|
||||
void InterfaceList::worldEvent(WorldState state, WorldEvent event)
|
||||
{
|
||||
ObjectList<CommandStation>::worldEvent(state, event);
|
||||
ObjectList<Interface>::worldEvent(state, event);
|
||||
|
||||
const bool editable = contains(state, WorldState::Edit);
|
||||
|
||||
@ -74,7 +74,7 @@ void CommandStationList::worldEvent(WorldState state, WorldEvent event)
|
||||
Attributes::setEnabled(remove, editable);
|
||||
}
|
||||
|
||||
bool CommandStationList::isListedProperty(const std::string& name)
|
||||
bool InterfaceList::isListedProperty(const std::string& name)
|
||||
{
|
||||
return CommandStationListTableModel::isListedProperty(name);
|
||||
return InterfaceListTableModel::isListedProperty(name);
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
/**
|
||||
* server/src/hardware/controller/controllerlist.hpp
|
||||
* server/src/hardware/interface/interfacelist.hpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2020 Reinder Feenstra
|
||||
* Copyright (C) 2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -20,27 +20,26 @@
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef TRAINTASTIC_SERVER_HARDWARE_CONTROLLER_CONTROLLERLIST_HPP
|
||||
#define TRAINTASTIC_SERVER_HARDWARE_CONTROLLER_CONTROLLERLIST_HPP
|
||||
#ifndef TRAINTASTIC_SERVER_HARDWARE_INTERFACE_INTERFACELIST_HPP
|
||||
#define TRAINTASTIC_SERVER_HARDWARE_INTERFACE_INTERFACELIST_HPP
|
||||
|
||||
#include "../../core/objectlist.hpp"
|
||||
#include "../../core/method.hpp"
|
||||
#include "controller.hpp"
|
||||
#include "interface.hpp"
|
||||
|
||||
class ControllerList : public ObjectList<Controller>
|
||||
class InterfaceList : public ObjectList<Interface>
|
||||
{
|
||||
protected:
|
||||
void worldEvent(WorldState state, WorldEvent event) final;
|
||||
bool isListedProperty(const std::string& name) final;
|
||||
|
||||
public:
|
||||
CLASS_ID("controller_list")
|
||||
CLASS_ID("list.interface")
|
||||
|
||||
Method<std::shared_ptr<Controller>(std::string_view)> add;
|
||||
Method<void(const std::shared_ptr<Controller>&)> remove;
|
||||
Method<std::shared_ptr<Interface>(std::string_view)> add;
|
||||
Method<void(const std::shared_ptr<Interface>&)> remove;
|
||||
|
||||
ControllerList(Object& _parent, const std::string& parentPropertyName);
|
||||
InterfaceList(Object& _parent, const std::string& parentPropertyName);
|
||||
|
||||
TableModelPtr getModel() final;
|
||||
};
|
||||
@ -1,9 +1,9 @@
|
||||
/**
|
||||
* server/src/core/
|
||||
* server/src/hardware/interface/interfacelisttablemodel.cpp
|
||||
*
|
||||
* This file is part of the traintastic source code
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2021 Reinder Feenstra
|
||||
* Copyright (C) 2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -20,48 +20,48 @@
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "controllerlisttablemodel.hpp"
|
||||
#include "controllerlist.hpp"
|
||||
#include "interfacelisttablemodel.hpp"
|
||||
#include "interfacelist.hpp"
|
||||
#include "../../utils/displayname.hpp"
|
||||
|
||||
constexpr uint32_t columnId = 0;
|
||||
constexpr uint32_t columnName = 1;
|
||||
constexpr uint32_t columnActive = 2;
|
||||
constexpr uint32_t columnStatus = 2;
|
||||
|
||||
bool ControllerListTableModel::isListedProperty(const std::string& name)
|
||||
bool InterfaceListTableModel::isListedProperty(const std::string& name)
|
||||
{
|
||||
return
|
||||
name == "id" ||
|
||||
name == "name" ||
|
||||
name == "active";
|
||||
name == "status";
|
||||
}
|
||||
|
||||
ControllerListTableModel::ControllerListTableModel(ControllerList& list) :
|
||||
ObjectListTableModel<Controller>(list)
|
||||
InterfaceListTableModel::InterfaceListTableModel(InterfaceList& list) :
|
||||
ObjectListTableModel<Interface>(list)
|
||||
{
|
||||
setColumnHeaders({
|
||||
DisplayName::Object::id,
|
||||
DisplayName::Object::name,
|
||||
DisplayName::Controller::active,
|
||||
DisplayName::Interface::status,
|
||||
});
|
||||
}
|
||||
|
||||
std::string ControllerListTableModel::getText(uint32_t column, uint32_t row) const
|
||||
std::string InterfaceListTableModel::getText(uint32_t column, uint32_t row) const
|
||||
{
|
||||
if(row < rowCount())
|
||||
{
|
||||
const Controller& controller = getItem(row);
|
||||
const Interface& interface = getItem(row);
|
||||
|
||||
switch(column)
|
||||
{
|
||||
case columnId:
|
||||
return controller.id;
|
||||
return interface.id;
|
||||
|
||||
case columnName:
|
||||
return controller.name;
|
||||
return interface.name;
|
||||
|
||||
case columnActive:
|
||||
return controller.active ? "\u2022" : "";
|
||||
case columnStatus:
|
||||
return "<todo>";
|
||||
|
||||
default:
|
||||
assert(false);
|
||||
@ -72,12 +72,12 @@ std::string ControllerListTableModel::getText(uint32_t column, uint32_t row) con
|
||||
return "";
|
||||
}
|
||||
|
||||
void ControllerListTableModel::propertyChanged(BaseProperty& property, uint32_t row)
|
||||
void InterfaceListTableModel::propertyChanged(BaseProperty& property, uint32_t row)
|
||||
{
|
||||
if(property.name() == "id")
|
||||
changed(row, columnId);
|
||||
else if(property.name() == "name")
|
||||
changed(row, columnName);
|
||||
else if(property.name() == "active")
|
||||
changed(row, columnActive);
|
||||
else if(property.name() == "status")
|
||||
changed(row, columnStatus);
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
/**
|
||||
* server/src/hardware/commandstation/controllerlisttablemodel.hpp
|
||||
* server/src/hardware/interface/interfacelisttablemodel.hpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2021 Reinder Feenstra
|
||||
* Copyright (C) 2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -20,27 +20,27 @@
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef TRAINTASTIC_SERVER_HARDWARE_CONTROLLER_CONTROLLERLISTTABLEMODEL_HPP
|
||||
#define TRAINTASTIC_SERVER_HARDWARE_CONTROLLER_CONTROLLERLISTTABLEMODEL_HPP
|
||||
#ifndef TRAINTASTIC_SERVER_HARDWARE_INTERFACE_INTERFACELISTTABLEMODEL_HPP
|
||||
#define TRAINTASTIC_SERVER_HARDWARE_INTERFACE_INTERFACELISTTABLEMODEL_HPP
|
||||
|
||||
#include "../../core/objectlisttablemodel.hpp"
|
||||
#include "controllerlist.hpp"
|
||||
#include "interfacelist.hpp"
|
||||
|
||||
class ControllerList;
|
||||
class InterfaceList;
|
||||
|
||||
class ControllerListTableModel : public ObjectListTableModel<Controller>
|
||||
class InterfaceListTableModel : public ObjectListTableModel<Interface>
|
||||
{
|
||||
friend class ControllerList;
|
||||
friend class InterfaceList;
|
||||
|
||||
protected:
|
||||
void propertyChanged(BaseProperty& property, uint32_t row) final;
|
||||
|
||||
public:
|
||||
CLASS_ID("controller_list_table_model")
|
||||
CLASS_ID("table_model.list.interface")
|
||||
|
||||
static bool isListedProperty(const std::string& name);
|
||||
|
||||
ControllerListTableModel(ControllerList& list);
|
||||
InterfaceListTableModel(InterfaceList& interfaceList);
|
||||
|
||||
std::string getText(uint32_t column, uint32_t row) const final;
|
||||
};
|
||||
@ -1,9 +1,9 @@
|
||||
/**
|
||||
* server/src/hardware/controller/controllers.cpp
|
||||
* server/src/hardware/interface/interfaces.cpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2020 Reinder Feenstra
|
||||
* Copyright (C) 2021-2022 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -20,16 +20,17 @@
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "controllers.hpp"
|
||||
#include "interfaces.hpp"
|
||||
#include "../../utils/ifclassidcreate.hpp"
|
||||
#include "../../world/world.hpp"
|
||||
|
||||
std::shared_ptr<Controller> Controllers::create(const std::weak_ptr<World>& world, std::string_view classId, std::string_view id)
|
||||
std::shared_ptr<Interface> Interfaces::create(const std::shared_ptr<World>& world, std::string_view classId, std::string_view id)
|
||||
{
|
||||
if(classId == WLANmaus::classId)
|
||||
return WLANmaus::create(world, id);
|
||||
#ifdef USB_XPRESSNET
|
||||
else if(classId == USBXpressNetController::classId)
|
||||
return USBXpressNetController::create(world, id);
|
||||
#endif
|
||||
else
|
||||
return std::shared_ptr<Controller>();
|
||||
IF_CLASSID_CREATE(DCCPlusPlusInterface)
|
||||
IF_CLASSID_CREATE(ECoSInterface)
|
||||
IF_CLASSID_CREATE(LocoNetInterface)
|
||||
IF_CLASSID_CREATE(WlanMausInterface)
|
||||
IF_CLASSID_CREATE(XpressNetInterface)
|
||||
IF_CLASSID_CREATE(Z21Interface)
|
||||
return std::shared_ptr<Interface>();
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
/**
|
||||
* server/src/hardware/controller/controllers.hpp
|
||||
* server/src/hardware/interface/interfaces.hpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2020 Reinder Feenstra
|
||||
* Copyright (C) 2021-2022 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -20,29 +20,33 @@
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef TRAINTASTIC_SERVER_HARDWARE_CONTROLLER_CONTROLLERS_HPP
|
||||
#define TRAINTASTIC_SERVER_HARDWARE_CONTROLLER_CONTROLLERS_HPP
|
||||
#ifndef TRAINTASTIC_SERVER_HARDWARE_INTERFACE_INTERFACES_HPP
|
||||
#define TRAINTASTIC_SERVER_HARDWARE_INTERFACE_INTERFACES_HPP
|
||||
|
||||
#include "controller.hpp"
|
||||
#include "interface.hpp"
|
||||
#include "../../utils/makearray.hpp"
|
||||
|
||||
#include "wlanmaus.hpp"
|
||||
#ifdef USB_XPRESSNET
|
||||
#include "usbxpressnetcontroller.hpp"
|
||||
#endif
|
||||
#include "dccplusplusinterface.hpp"
|
||||
#include "ecosinterface.hpp"
|
||||
#include "loconetinterface.hpp"
|
||||
#include "wlanmausinterface.hpp"
|
||||
#include "xpressnetinterface.hpp"
|
||||
#include "z21interface.hpp"
|
||||
|
||||
struct Controllers
|
||||
struct Interfaces
|
||||
{
|
||||
static constexpr std::string_view classIdPrefix = "controller.";
|
||||
static constexpr std::string_view classIdPrefix = "interface.";
|
||||
|
||||
static constexpr auto classList = makeArray(
|
||||
#ifdef USB_XPRESSNET
|
||||
USBXpressNetController::classId,
|
||||
#endif
|
||||
WLANmaus::classId
|
||||
DCCPlusPlusInterface::classId,
|
||||
ECoSInterface::classId,
|
||||
LocoNetInterface::classId,
|
||||
WlanMausInterface::classId,
|
||||
XpressNetInterface::classId,
|
||||
Z21Interface::classId
|
||||
);
|
||||
|
||||
static std::shared_ptr<Controller> create(const std::weak_ptr<World>& world, std::string_view classId, std::string_view id);
|
||||
static std::shared_ptr<Interface> create(const std::shared_ptr<World>& world, std::string_view classId, std::string_view id = std::string_view{});
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
374
server/src/hardware/interface/loconetinterface.cpp
Normale Datei
374
server/src/hardware/interface/loconetinterface.cpp
Normale Datei
@ -0,0 +1,374 @@
|
||||
/**
|
||||
* server/src/hardware/interface/loconetinterface.cpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2022 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "loconetinterface.hpp"
|
||||
#include "../input/list/inputlisttablemodel.hpp"
|
||||
#include "../output/list/outputlisttablemodel.hpp"
|
||||
#include "../protocol/loconet/iohandler/serialiohandler.hpp"
|
||||
#include "../protocol/loconet/iohandler/tcpbinaryiohandler.hpp"
|
||||
#include "../protocol/loconet/iohandler/lbserveriohandler.hpp"
|
||||
#include "../protocol/loconet/iohandler/z21iohandler.hpp"
|
||||
#include "../../core/attributes.hpp"
|
||||
#include "../../log/log.hpp"
|
||||
#include "../../log/logmessageexception.hpp"
|
||||
#include "../../utils/displayname.hpp"
|
||||
#include "../../utils/inrange.hpp"
|
||||
#include "../../world/world.hpp"
|
||||
|
||||
LocoNetInterface::LocoNetInterface(const std::weak_ptr<World>& world, std::string_view _id)
|
||||
: Interface(world, _id)
|
||||
, type{this, "type", LocoNetInterfaceType::Serial, PropertyFlags::ReadWrite | PropertyFlags::Store,
|
||||
[this](LocoNetInterfaceType)
|
||||
{
|
||||
typeChanged();
|
||||
}}
|
||||
, device{this, "device", "", PropertyFlags::ReadWrite | PropertyFlags::Store}
|
||||
, baudrate{this, "baudrate", 19200, PropertyFlags::ReadWrite | PropertyFlags::Store}
|
||||
, flowControl{this, "flow_control", SerialFlowControl::None, PropertyFlags::ReadWrite | PropertyFlags::Store}
|
||||
, hostname{this, "hostname", "192.168.1.203", PropertyFlags::ReadWrite | PropertyFlags::Store}
|
||||
, port{this, "port", 5550, PropertyFlags::ReadWrite | PropertyFlags::Store}
|
||||
, loconet{this, "loconet", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject}
|
||||
, decoders{this, "decoders", nullptr, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::SubObject}
|
||||
, inputs{this, "inputs", nullptr, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::SubObject}
|
||||
, outputs{this, "outputs", nullptr, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::SubObject}
|
||||
{
|
||||
name = "LocoNet";
|
||||
loconet.setValueInternal(std::make_shared<LocoNet::Settings>(*this, loconet.name()));
|
||||
decoders.setValueInternal(std::make_shared<DecoderList>(*this, decoders.name()));
|
||||
inputs.setValueInternal(std::make_shared<InputList>(*this, inputs.name()));
|
||||
outputs.setValueInternal(std::make_shared<OutputList>(*this, outputs.name()));
|
||||
|
||||
Attributes::addDisplayName(type, DisplayName::Interface::type);
|
||||
Attributes::addEnabled(type, !online);
|
||||
Attributes::addValues(type, locoNetInterfaceTypeValues);
|
||||
m_interfaceItems.insertBefore(type, notes);
|
||||
|
||||
Attributes::addDisplayName(device, DisplayName::Serial::device);
|
||||
Attributes::addEnabled(device, !online);
|
||||
Attributes::addVisible(device, false);
|
||||
m_interfaceItems.insertBefore(device, notes);
|
||||
|
||||
Attributes::addDisplayName(baudrate, DisplayName::Serial::baudrate);
|
||||
Attributes::addEnabled(baudrate, !online);
|
||||
Attributes::addVisible(baudrate, false);
|
||||
m_interfaceItems.insertBefore(baudrate, notes);
|
||||
|
||||
Attributes::addDisplayName(flowControl, DisplayName::Serial::flowControl);
|
||||
Attributes::addEnabled(flowControl, !online);
|
||||
Attributes::addValues(flowControl, SerialFlowControlValues);
|
||||
Attributes::addVisible(flowControl, false);
|
||||
m_interfaceItems.insertBefore(flowControl, notes);
|
||||
|
||||
Attributes::addDisplayName(hostname, DisplayName::IP::hostname);
|
||||
Attributes::addEnabled(hostname, !online);
|
||||
Attributes::addVisible(hostname, false);
|
||||
m_interfaceItems.insertBefore(hostname, notes);
|
||||
|
||||
Attributes::addDisplayName(port, DisplayName::IP::port);
|
||||
Attributes::addEnabled(port, !online);
|
||||
Attributes::addVisible(port, false);
|
||||
m_interfaceItems.insertBefore(port, notes);
|
||||
|
||||
Attributes::addDisplayName(loconet, DisplayName::Hardware::loconet);
|
||||
m_interfaceItems.insertBefore(loconet, notes);
|
||||
|
||||
Attributes::addDisplayName(decoders, DisplayName::Hardware::decoders);
|
||||
m_interfaceItems.insertBefore(decoders, notes);
|
||||
|
||||
Attributes::addDisplayName(inputs, DisplayName::Hardware::inputs);
|
||||
m_interfaceItems.insertBefore(inputs, notes);
|
||||
|
||||
Attributes::addDisplayName(outputs, DisplayName::Hardware::outputs);
|
||||
m_interfaceItems.insertBefore(outputs, notes);
|
||||
|
||||
typeChanged();
|
||||
}
|
||||
|
||||
bool LocoNetInterface::addDecoder(Decoder& decoder)
|
||||
{
|
||||
const bool success = DecoderController::addDecoder(decoder);
|
||||
if(success)
|
||||
decoders->addObject(decoder.shared_ptr<Decoder>());
|
||||
return success;
|
||||
}
|
||||
|
||||
bool LocoNetInterface::removeDecoder(Decoder& decoder)
|
||||
{
|
||||
const bool success = DecoderController::removeDecoder(decoder);
|
||||
if(success)
|
||||
decoders->removeObject(decoder.shared_ptr<Decoder>());
|
||||
return success;
|
||||
}
|
||||
|
||||
void LocoNetInterface::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber)
|
||||
{
|
||||
if(m_kernel)
|
||||
m_kernel->decoderChanged(decoder, changes, functionNumber);
|
||||
}
|
||||
|
||||
bool LocoNetInterface::addInput(Input& input)
|
||||
{
|
||||
const bool success = InputController::addInput(input);
|
||||
if(success)
|
||||
inputs->addObject(input.shared_ptr<Input>());
|
||||
return success;
|
||||
}
|
||||
|
||||
bool LocoNetInterface::removeInput(Input& input)
|
||||
{
|
||||
const bool success = InputController::removeInput(input);
|
||||
if(success)
|
||||
inputs->removeObject(input.shared_ptr<Input>());
|
||||
return success;
|
||||
}
|
||||
|
||||
bool LocoNetInterface::addOutput(Output& output)
|
||||
{
|
||||
const bool success = OutputController::addOutput(output);
|
||||
if(success)
|
||||
outputs->addObject(output.shared_ptr<Output>());
|
||||
return success;
|
||||
}
|
||||
|
||||
bool LocoNetInterface::removeOutput(Output& output)
|
||||
{
|
||||
const bool success = OutputController::removeOutput(output);
|
||||
if(success)
|
||||
outputs->removeObject(output.shared_ptr<Output>());
|
||||
return success;
|
||||
}
|
||||
|
||||
bool LocoNetInterface::setOutputValue(uint32_t address, bool value)
|
||||
{
|
||||
return
|
||||
m_kernel &&
|
||||
inRange(address, outputAddressMinMax()) &&
|
||||
m_kernel->setOutput(static_cast<uint16_t>(address), value);
|
||||
}
|
||||
|
||||
bool LocoNetInterface::setOnline(bool& value)
|
||||
{
|
||||
if(!m_kernel && value)
|
||||
{
|
||||
try
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case LocoNetInterfaceType::Serial:
|
||||
m_kernel = LocoNet::Kernel::create<LocoNet::SerialIOHandler>(loconet->config(), device.value(), baudrate.value(), flowControl.value());
|
||||
break;
|
||||
|
||||
case LocoNetInterfaceType::TCPBinary:
|
||||
m_kernel = LocoNet::Kernel::create<LocoNet::TCPBinaryIOHandler>(loconet->config(), hostname.value(), port.value());
|
||||
break;
|
||||
|
||||
case LocoNetInterfaceType::LBServer:
|
||||
m_kernel = LocoNet::Kernel::create<LocoNet::LBServerIOHandler>(loconet->config(), hostname.value(), port.value());
|
||||
break;
|
||||
|
||||
case LocoNetInterfaceType::Z21:
|
||||
m_kernel = LocoNet::Kernel::create<LocoNet::Z21IOHandler>(loconet->config(), hostname.value());
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
status.setValueInternal(InterfaceStatus::Initializing);
|
||||
|
||||
m_kernel->setLogId(id.value());
|
||||
m_kernel->setOnStarted(
|
||||
[this]()
|
||||
{
|
||||
status.setValueInternal(InterfaceStatus::Online);
|
||||
});
|
||||
m_kernel->setOnGlobalPowerChanged(
|
||||
[this](bool powerOn)
|
||||
{
|
||||
if(auto w = m_world.lock())
|
||||
{
|
||||
if(powerOn && !contains(w->state.value(), WorldState::PowerOn))
|
||||
w->powerOn();
|
||||
else if(!powerOn && contains(w->state.value(), WorldState::PowerOn))
|
||||
w->powerOff();
|
||||
}
|
||||
});
|
||||
m_kernel->setOnIdle(
|
||||
[this]()
|
||||
{
|
||||
if(auto w = m_world.lock(); w && contains(w->state.value(), WorldState::Run))
|
||||
w->stop();
|
||||
});
|
||||
m_kernel->setDecoderController(this);
|
||||
m_kernel->setInputController(this);
|
||||
m_kernel->setOutputController(this);
|
||||
m_kernel->start();
|
||||
|
||||
m_loconetPropertyChanged = loconet->propertyChanged.connect(
|
||||
[this](BaseProperty&)
|
||||
{
|
||||
m_kernel->setConfig(loconet->config());
|
||||
});
|
||||
|
||||
if(auto w = m_world.lock())
|
||||
{
|
||||
m_kernel->setPowerOn(contains(w->state.value(), WorldState::PowerOn));
|
||||
|
||||
if(contains(w->state.value(), WorldState::Run))
|
||||
m_kernel->resume();
|
||||
else
|
||||
m_kernel->emergencyStop();
|
||||
}
|
||||
|
||||
Attributes::setEnabled(type, false);
|
||||
Attributes::setEnabled(device, false);
|
||||
Attributes::setEnabled(baudrate, false);
|
||||
Attributes::setEnabled(flowControl, false);
|
||||
Attributes::setEnabled(hostname, false);
|
||||
Attributes::setEnabled(port, false);
|
||||
}
|
||||
catch(const LogMessageException& e)
|
||||
{
|
||||
status.setValueInternal(InterfaceStatus::Offline);
|
||||
Log::log(*this, e.message(), e.args());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if(m_kernel && !value)
|
||||
{
|
||||
Attributes::setEnabled(type, true);
|
||||
Attributes::setEnabled(device, true);
|
||||
Attributes::setEnabled(baudrate, true);
|
||||
Attributes::setEnabled(flowControl, true);
|
||||
Attributes::setEnabled(hostname, true);
|
||||
Attributes::setEnabled(port, true);
|
||||
|
||||
m_loconetPropertyChanged.disconnect();
|
||||
|
||||
m_kernel->stop();
|
||||
m_kernel.reset();
|
||||
|
||||
status.setValueInternal(InterfaceStatus::Offline);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void LocoNetInterface::addToWorld()
|
||||
{
|
||||
Interface::addToWorld();
|
||||
|
||||
if(auto world = m_world.lock())
|
||||
{
|
||||
world->decoderControllers->add(std::dynamic_pointer_cast<DecoderController>(shared_from_this()));
|
||||
world->inputControllers->add(std::dynamic_pointer_cast<InputController>(shared_from_this()));
|
||||
world->outputControllers->add(std::dynamic_pointer_cast<OutputController>(shared_from_this()));
|
||||
}
|
||||
}
|
||||
|
||||
void LocoNetInterface::loaded()
|
||||
{
|
||||
Interface::loaded();
|
||||
|
||||
typeChanged();
|
||||
}
|
||||
|
||||
void LocoNetInterface::destroying()
|
||||
{
|
||||
for(const auto& decoder : *decoders)
|
||||
{
|
||||
assert(decoder->interface.value() == std::dynamic_pointer_cast<DecoderController>(shared_from_this()));
|
||||
decoder->interface = nullptr;
|
||||
}
|
||||
|
||||
for(const auto& input : *inputs)
|
||||
{
|
||||
assert(input->interface.value() == std::dynamic_pointer_cast<InputController>(shared_from_this()));
|
||||
input->interface = nullptr;
|
||||
}
|
||||
|
||||
for(const auto& output : *outputs)
|
||||
{
|
||||
assert(output->interface.value() == std::dynamic_pointer_cast<OutputController>(shared_from_this()));
|
||||
output->interface = nullptr;
|
||||
}
|
||||
|
||||
if(auto world = m_world.lock())
|
||||
{
|
||||
world->decoderControllers->remove(std::dynamic_pointer_cast<DecoderController>(shared_from_this()));
|
||||
world->inputControllers->remove(std::dynamic_pointer_cast<InputController>(shared_from_this()));
|
||||
world->outputControllers->remove(std::dynamic_pointer_cast<OutputController>(shared_from_this()));
|
||||
}
|
||||
|
||||
Interface::destroying();
|
||||
}
|
||||
|
||||
void LocoNetInterface::worldEvent(WorldState state, WorldEvent event)
|
||||
{
|
||||
Interface::worldEvent(state, event);
|
||||
|
||||
if(m_kernel)
|
||||
{
|
||||
switch(event)
|
||||
{
|
||||
case WorldEvent::PowerOff:
|
||||
m_kernel->setPowerOn(false);
|
||||
break;
|
||||
|
||||
case WorldEvent::PowerOn:
|
||||
m_kernel->setPowerOn(true);
|
||||
if(contains(state, WorldState::Run))
|
||||
m_kernel->resume();
|
||||
break;
|
||||
|
||||
case WorldEvent::Stop:
|
||||
m_kernel->emergencyStop();
|
||||
break;
|
||||
|
||||
case WorldEvent::Run:
|
||||
if(contains(state, WorldState::PowerOn))
|
||||
m_kernel->resume();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LocoNetInterface::idChanged(const std::string& newId)
|
||||
{
|
||||
if(m_kernel)
|
||||
m_kernel->setLogId(newId);
|
||||
}
|
||||
|
||||
void LocoNetInterface::typeChanged()
|
||||
{
|
||||
const bool serialVisible = isSerial(type);
|
||||
Attributes::setVisible(device, serialVisible);
|
||||
Attributes::setVisible(baudrate, serialVisible);
|
||||
Attributes::setVisible(flowControl, serialVisible);
|
||||
|
||||
const bool networkVisible = isNetwork(type);
|
||||
Attributes::setVisible(hostname, networkVisible);
|
||||
Attributes::setVisible(port, networkVisible && type != LocoNetInterfaceType::Z21);
|
||||
}
|
||||
99
server/src/hardware/interface/loconetinterface.hpp
Normale Datei
99
server/src/hardware/interface/loconetinterface.hpp
Normale Datei
@ -0,0 +1,99 @@
|
||||
/**
|
||||
* server/src/hardware/interface/loconetinterface.hpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef TRAINTASTIC_SERVER_HARDWARE_INTERFACE_LOCONETINTERFACE_HPP
|
||||
#define TRAINTASTIC_SERVER_HARDWARE_INTERFACE_LOCONETINTERFACE_HPP
|
||||
|
||||
#include "interface.hpp"
|
||||
#include "../protocol/loconet/kernel.hpp"
|
||||
#include "../protocol/loconet/settings.hpp"
|
||||
#include "../decoder/decodercontroller.hpp"
|
||||
#include "../decoder/decoderlist.hpp"
|
||||
#include "../input/inputcontroller.hpp"
|
||||
#include "../input/list/inputlist.hpp"
|
||||
#include "../output/outputcontroller.hpp"
|
||||
#include "../output/list/outputlist.hpp"
|
||||
#include "../../core/objectproperty.hpp"
|
||||
#include "../../enum/loconetinterfacetype.hpp"
|
||||
#include "../../enum/serialflowcontrol.hpp"
|
||||
|
||||
/**
|
||||
* @brief LocoNet hardware interface
|
||||
*/
|
||||
class LocoNetInterface final
|
||||
: public Interface
|
||||
, public DecoderController
|
||||
, public InputController
|
||||
, public OutputController
|
||||
{
|
||||
CLASS_ID("interface.loconet")
|
||||
DEFAULT_ID("loconet")
|
||||
CREATE(LocoNetInterface)
|
||||
|
||||
private:
|
||||
std::unique_ptr<LocoNet::Kernel> m_kernel;
|
||||
boost::signals2::connection m_loconetPropertyChanged;
|
||||
|
||||
void addToWorld() final;
|
||||
void loaded() final;
|
||||
void destroying() final;
|
||||
void worldEvent(WorldState state, WorldEvent event) final;
|
||||
|
||||
void idChanged(const std::string& newId) final;
|
||||
|
||||
void typeChanged();
|
||||
|
||||
protected:
|
||||
bool setOnline(bool& value) final;
|
||||
|
||||
public:
|
||||
Property<LocoNetInterfaceType> type;
|
||||
Property<std::string> device;
|
||||
Property<uint32_t> baudrate;
|
||||
Property<SerialFlowControl> flowControl;
|
||||
Property<std::string> hostname;
|
||||
Property<uint16_t> port;
|
||||
ObjectProperty<LocoNet::Settings> loconet;
|
||||
ObjectProperty<DecoderList> decoders;
|
||||
ObjectProperty<InputList> inputs;
|
||||
ObjectProperty<OutputList> outputs;
|
||||
|
||||
LocoNetInterface(const std::weak_ptr<World>& world, std::string_view _id);
|
||||
|
||||
// DecoderController:
|
||||
[[nodiscard]] bool addDecoder(Decoder& decoder) final;
|
||||
[[nodiscard]] bool removeDecoder(Decoder& decoder) final;
|
||||
void decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber) final;
|
||||
|
||||
// InputController:
|
||||
std::pair<uint32_t, uint32_t> inputAddressMinMax() const final { return {LocoNet::Kernel::inputAddressMin, LocoNet::Kernel::inputAddressMax}; }
|
||||
[[nodiscard]] bool addInput(Input& input) final;
|
||||
[[nodiscard]] bool removeInput(Input& input) final;
|
||||
|
||||
// OutputController:
|
||||
std::pair<uint32_t, uint32_t> outputAddressMinMax() const final { return {LocoNet::Kernel::outputAddressMin, LocoNet::Kernel::outputAddressMax}; }
|
||||
[[nodiscard]] bool addOutput(Output& output) final;
|
||||
[[nodiscard]] bool removeOutput(Output& output) final;
|
||||
[[nodiscard]] bool setOutputValue(uint32_t address, bool value) final;
|
||||
};
|
||||
|
||||
#endif
|
||||
137
server/src/hardware/interface/wlanmausinterface.cpp
Normale Datei
137
server/src/hardware/interface/wlanmausinterface.cpp
Normale Datei
@ -0,0 +1,137 @@
|
||||
/**
|
||||
* server/src/hardware/interface/wlanmausinterface.cpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2022 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "wlanmausinterface.hpp"
|
||||
#include "../protocol/z21/iohandler/udpserveriohandler.hpp"
|
||||
#include "../../core/attributes.hpp"
|
||||
#include "../../log/log.hpp"
|
||||
#include "../../log/logmessageexception.hpp"
|
||||
#include "../../utils/displayname.hpp"
|
||||
#include "../../world/world.hpp"
|
||||
|
||||
WlanMausInterface::WlanMausInterface(const std::weak_ptr<World>& world, std::string_view _id)
|
||||
: Interface(world, _id)
|
||||
, z21{this, "z21", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject}
|
||||
{
|
||||
z21.setValueInternal(std::make_shared<Z21::ServerSettings>(*this, z21.name()));
|
||||
|
||||
Attributes::addDisplayName(z21, DisplayName::Hardware::z21);
|
||||
m_interfaceItems.insertBefore(z21, notes);
|
||||
}
|
||||
|
||||
void WlanMausInterface::worldEvent(WorldState state, WorldEvent event)
|
||||
{
|
||||
Interface::worldEvent(state, event);
|
||||
|
||||
if(m_kernel)
|
||||
{
|
||||
switch(event)
|
||||
{
|
||||
case WorldEvent::PowerOff:
|
||||
case WorldEvent::PowerOn:
|
||||
case WorldEvent::Stop:
|
||||
case WorldEvent::Run:
|
||||
m_kernel->setState(contains(state, WorldState::PowerOn), !contains(state, WorldState::Run));
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WlanMausInterface::idChanged(const std::string& newId)
|
||||
{
|
||||
if(m_kernel)
|
||||
m_kernel->setLogId(newId);
|
||||
}
|
||||
|
||||
bool WlanMausInterface::setOnline(bool& value)
|
||||
{
|
||||
if(!m_kernel && value)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto world = m_world.lock();
|
||||
assert(world);
|
||||
m_kernel = Z21::ServerKernel::create<Z21::UDPServerIOHandler>(z21->config(), world->decoders.value());
|
||||
|
||||
status.setValueInternal(InterfaceStatus::Initializing);
|
||||
|
||||
m_kernel->setLogId(id.value());
|
||||
m_kernel->setOnStarted(
|
||||
[this]()
|
||||
{
|
||||
status.setValueInternal(InterfaceStatus::Online);
|
||||
});
|
||||
|
||||
m_kernel->setOnTrackPowerOff(
|
||||
[this]()
|
||||
{
|
||||
if(auto w = m_world.lock(); w && contains(w->state.value(), WorldState::PowerOn))
|
||||
w->powerOff();
|
||||
});
|
||||
m_kernel->setOnTrackPowerOn(
|
||||
[this]()
|
||||
{
|
||||
if(auto w = m_world.lock())
|
||||
{
|
||||
if(!contains(w->state.value(), WorldState::PowerOn))
|
||||
w->powerOn();
|
||||
if(!contains(w->state.value(), WorldState::Run))
|
||||
w->run();
|
||||
}
|
||||
});
|
||||
m_kernel->setOnEmergencyStop(
|
||||
[this]()
|
||||
{
|
||||
if(auto w = m_world.lock(); w && contains(w->state.value(), WorldState::Run))
|
||||
w->stop();
|
||||
});
|
||||
|
||||
m_kernel->start();
|
||||
|
||||
m_z21PropertyChanged = z21->propertyChanged.connect(
|
||||
[this](BaseProperty&)
|
||||
{
|
||||
m_kernel->setConfig(z21->config());
|
||||
});
|
||||
|
||||
if(auto w = m_world.lock())
|
||||
m_kernel->setState(contains(w->state.value(), WorldState::PowerOn), !contains(w->state.value(), WorldState::Run));
|
||||
}
|
||||
catch(const LogMessageException& e)
|
||||
{
|
||||
status.setValueInternal(InterfaceStatus::Offline);
|
||||
Log::log(*this, e.message(), e.args());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if(m_kernel && !value)
|
||||
{
|
||||
m_kernel->stop();
|
||||
m_kernel.reset();
|
||||
|
||||
status.setValueInternal(InterfaceStatus::Offline);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
/**
|
||||
* server/src/hardware/input/loconetinput.hpp
|
||||
* server/src/hardware/interface/wlanmausinterface.hpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2021 Reinder Feenstra
|
||||
* Copyright (C) 2019-2022 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -20,38 +20,37 @@
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef TRAINTASTIC_SERVER_HARDWARE_INPUT_LOCONETINPUT_HPP
|
||||
#define TRAINTASTIC_SERVER_HARDWARE_INPUT_LOCONETINPUT_HPP
|
||||
#ifndef TRAINTASTIC_SERVER_HARDWARE_INTERFACE_WLANMAUSINTERFACE_HPP
|
||||
#define TRAINTASTIC_SERVER_HARDWARE_INTERFACE_WLANMAUSINTERFACE_HPP
|
||||
|
||||
#include "input.hpp"
|
||||
#include "interface.hpp"
|
||||
#include "../protocol/z21/serverkernel.hpp"
|
||||
#include "../protocol/z21/serversettings.hpp"
|
||||
#include "../../core/objectproperty.hpp"
|
||||
#include "../protocol/loconet/loconet.hpp"
|
||||
|
||||
class LocoNetInput : public Input
|
||||
/**
|
||||
* @brief WLANmaus/Z21 app hardware interface
|
||||
*/
|
||||
class WlanMausInterface : public Interface
|
||||
{
|
||||
friend class LocoNet::LocoNet;
|
||||
CLASS_ID("interface.wlanmaus")
|
||||
DEFAULT_ID("wlanmaus")
|
||||
CREATE(WlanMausInterface)
|
||||
|
||||
private:
|
||||
std::unique_ptr<Z21::ServerKernel> m_kernel;
|
||||
boost::signals2::connection m_z21PropertyChanged;
|
||||
|
||||
protected:
|
||||
void loaded() final;
|
||||
void destroying() final;
|
||||
void worldEvent(WorldState state, WorldEvent event) final;
|
||||
void idChanged(const std::string& newId) final;
|
||||
void valueChanged(TriState _value) final;
|
||||
|
||||
inline void updateValue(TriState _value) { Input::updateValue(_value); }
|
||||
bool setOnline(bool& value) final;
|
||||
|
||||
public:
|
||||
CLASS_ID("input.loconet")
|
||||
CREATE(LocoNetInput)
|
||||
ObjectProperty<Z21::ServerSettings> z21;
|
||||
|
||||
static constexpr uint16_t addressInvalid = 0;
|
||||
static constexpr uint16_t addressMin = 1;
|
||||
static constexpr uint16_t addressMax = 4096;
|
||||
|
||||
ObjectProperty<LocoNet::LocoNet> loconet;
|
||||
Property<uint16_t> address;
|
||||
|
||||
LocoNetInput(const std::weak_ptr<World> world, std::string_view _id);
|
||||
WlanMausInterface(const std::weak_ptr<World>& world, std::string_view _id);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
437
server/src/hardware/interface/xpressnetinterface.cpp
Normale Datei
437
server/src/hardware/interface/xpressnetinterface.cpp
Normale Datei
@ -0,0 +1,437 @@
|
||||
/**
|
||||
* server/src/hardware/interface/xpressnetinterface.cpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2022 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "xpressnetinterface.hpp"
|
||||
#include "../input/list/inputlisttablemodel.hpp"
|
||||
#include "../output/list/outputlisttablemodel.hpp"
|
||||
#include "../protocol/xpressnet/messages.hpp"
|
||||
#include "../protocol/xpressnet/iohandler/serialiohandler.hpp"
|
||||
#include "../protocol/xpressnet/iohandler/liusbiohandler.hpp"
|
||||
#include "../protocol/xpressnet/iohandler/rosofts88xpressnetliiohandler.hpp"
|
||||
#include "../protocol/xpressnet/iohandler/tcpiohandler.hpp"
|
||||
#include "../../core/attributes.hpp"
|
||||
#include "../../log/log.hpp"
|
||||
#include "../../log/logmessageexception.hpp"
|
||||
#include "../../utils/displayname.hpp"
|
||||
#include "../../utils/inrange.hpp"
|
||||
#include "../../world/world.hpp"
|
||||
|
||||
XpressNetInterface::XpressNetInterface(const std::weak_ptr<World>& world, std::string_view _id)
|
||||
: Interface(world, _id)
|
||||
, type{this, "type", XpressNetInterfaceType::Serial, PropertyFlags::ReadWrite | PropertyFlags::Store,
|
||||
[this](XpressNetInterfaceType)
|
||||
{
|
||||
updateVisible();
|
||||
}}
|
||||
, serialInterfaceType{this, "interface", XpressNetSerialInterfaceType::LenzLI100, PropertyFlags::ReadWrite | PropertyFlags::Store,
|
||||
[this](XpressNetSerialInterfaceType value)
|
||||
{
|
||||
switch(value)
|
||||
{
|
||||
case XpressNetSerialInterfaceType::LenzLI100:
|
||||
case XpressNetSerialInterfaceType::RoSoftS88XPressNetLI:
|
||||
baudrate.setValueInternal(9600);
|
||||
flowControl.setValueInternal(SerialFlowControl::Hardware);
|
||||
break;
|
||||
|
||||
case XpressNetSerialInterfaceType::LenzLI100F:
|
||||
case XpressNetSerialInterfaceType::LenzLI101F:
|
||||
baudrate.setValueInternal(19200);
|
||||
flowControl.setValueInternal(SerialFlowControl::Hardware);
|
||||
break;
|
||||
|
||||
case XpressNetSerialInterfaceType::LenzLIUSB:
|
||||
baudrate.setValueInternal(57600);
|
||||
flowControl.setValueInternal(SerialFlowControl::None);
|
||||
break;
|
||||
|
||||
case XpressNetSerialInterfaceType::DigikeijsDR5000:
|
||||
baudrate.setValueInternal(115200);
|
||||
flowControl.setValueInternal(SerialFlowControl::None);
|
||||
break;
|
||||
}
|
||||
updateVisible();
|
||||
}}
|
||||
, device{this, "device", "", PropertyFlags::ReadWrite | PropertyFlags::Store}
|
||||
, baudrate{this, "baudrate", 19200, PropertyFlags::ReadWrite | PropertyFlags::Store}
|
||||
, flowControl{this, "flow_control", SerialFlowControl::None, PropertyFlags::ReadWrite | PropertyFlags::Store}
|
||||
, hostname{this, "hostname", "192.168.1.203", PropertyFlags::ReadWrite | PropertyFlags::Store}
|
||||
, port{this, "port", 5550, PropertyFlags::ReadWrite | PropertyFlags::Store}
|
||||
, s88StartAddress{this, "s88_start_address", XpressNet::RoSoftS88XpressNetLI::S88StartAddress::startAddressDefault, PropertyFlags::ReadWrite | PropertyFlags::Store}
|
||||
, s88ModuleCount{this, "s88_module_count", XpressNet::RoSoftS88XpressNetLI::S88ModuleCount::moduleCountDefault, PropertyFlags::ReadWrite | PropertyFlags::Store}
|
||||
, xpressnet{this, "xpressnet", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject}
|
||||
, decoders{this, "decoders", nullptr, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::SubObject}
|
||||
, inputs{this, "inputs", nullptr, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::SubObject}
|
||||
, outputs{this, "outputs", nullptr, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::SubObject}
|
||||
{
|
||||
xpressnet.setValueInternal(std::make_shared<XpressNet::Settings>(*this, xpressnet.name()));
|
||||
decoders.setValueInternal(std::make_shared<DecoderList>(*this, decoders.name()));
|
||||
inputs.setValueInternal(std::make_shared<InputList>(*this, inputs.name()));
|
||||
outputs.setValueInternal(std::make_shared<OutputList>(*this, outputs.name()));
|
||||
|
||||
Attributes::addDisplayName(type, DisplayName::Interface::type);
|
||||
Attributes::addEnabled(type, !online);
|
||||
Attributes::addValues(type, xpressNetInterfaceTypeValues);
|
||||
m_interfaceItems.insertBefore(type, notes);
|
||||
|
||||
Attributes::addValues(serialInterfaceType, XpressNetSerialInterfaceTypeValues);
|
||||
Attributes::addEnabled(serialInterfaceType, !online);
|
||||
Attributes::addVisible(serialInterfaceType, false);
|
||||
m_interfaceItems.insertBefore(serialInterfaceType, notes);
|
||||
|
||||
Attributes::addDisplayName(device, DisplayName::Serial::device);
|
||||
Attributes::addEnabled(device, !online);
|
||||
Attributes::addVisible(device, false);
|
||||
m_interfaceItems.insertBefore(device, notes);
|
||||
|
||||
Attributes::addDisplayName(baudrate, DisplayName::Serial::baudrate);
|
||||
Attributes::addEnabled(baudrate, !online);
|
||||
Attributes::addVisible(baudrate, false);
|
||||
m_interfaceItems.insertBefore(baudrate, notes);
|
||||
|
||||
Attributes::addDisplayName(flowControl, DisplayName::Serial::flowControl);
|
||||
Attributes::addEnabled(flowControl, !online);
|
||||
Attributes::addValues(flowControl, SerialFlowControlValues);
|
||||
Attributes::addVisible(flowControl, false);
|
||||
m_interfaceItems.insertBefore(flowControl, notes);
|
||||
|
||||
Attributes::addDisplayName(hostname, DisplayName::IP::hostname);
|
||||
Attributes::addEnabled(hostname, !online);
|
||||
Attributes::addVisible(hostname, false);
|
||||
m_interfaceItems.insertBefore(hostname, notes);
|
||||
|
||||
Attributes::addDisplayName(port, DisplayName::IP::port);
|
||||
Attributes::addEnabled(port, !online);
|
||||
Attributes::addVisible(port, false);
|
||||
m_interfaceItems.insertBefore(port, notes);
|
||||
|
||||
Attributes::addMinMax(s88StartAddress, XpressNet::RoSoftS88XpressNetLI::S88StartAddress::startAddressMin, XpressNet::RoSoftS88XpressNetLI::S88StartAddress::startAddressMax);
|
||||
Attributes::addEnabled(s88StartAddress, !online);
|
||||
Attributes::addVisible(s88StartAddress, false);
|
||||
m_interfaceItems.insertBefore(s88StartAddress, notes);
|
||||
|
||||
Attributes::addMinMax(s88ModuleCount, XpressNet::RoSoftS88XpressNetLI::S88ModuleCount::moduleCountMin, XpressNet::RoSoftS88XpressNetLI::S88ModuleCount::moduleCountMax);
|
||||
Attributes::addEnabled(s88ModuleCount, !online);
|
||||
Attributes::addVisible(s88ModuleCount, false);
|
||||
m_interfaceItems.insertBefore(s88ModuleCount, notes);
|
||||
|
||||
Attributes::addDisplayName(xpressnet, DisplayName::Hardware::xpressnet);
|
||||
m_interfaceItems.insertBefore(xpressnet, notes);
|
||||
|
||||
Attributes::addDisplayName(decoders, DisplayName::Hardware::decoders);
|
||||
m_interfaceItems.insertBefore(decoders, notes);
|
||||
|
||||
Attributes::addDisplayName(inputs, DisplayName::Hardware::inputs);
|
||||
m_interfaceItems.insertBefore(inputs, notes);
|
||||
|
||||
Attributes::addDisplayName(outputs, DisplayName::Hardware::outputs);
|
||||
m_interfaceItems.insertBefore(outputs, notes);
|
||||
|
||||
updateVisible();
|
||||
}
|
||||
|
||||
bool XpressNetInterface::addDecoder(Decoder& decoder)
|
||||
{
|
||||
const bool success = DecoderController::addDecoder(decoder);
|
||||
if(success)
|
||||
decoders->addObject(decoder.shared_ptr<Decoder>());
|
||||
return success;
|
||||
}
|
||||
|
||||
bool XpressNetInterface::removeDecoder(Decoder& decoder)
|
||||
{
|
||||
const bool success = DecoderController::removeDecoder(decoder);
|
||||
if(success)
|
||||
decoders->removeObject(decoder.shared_ptr<Decoder>());
|
||||
return success;
|
||||
}
|
||||
|
||||
void XpressNetInterface::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber)
|
||||
{
|
||||
if(m_kernel)
|
||||
m_kernel->decoderChanged(decoder, changes, functionNumber);
|
||||
}
|
||||
|
||||
bool XpressNetInterface::addInput(Input& input)
|
||||
{
|
||||
const bool success = InputController::addInput(input);
|
||||
if(success)
|
||||
inputs->addObject(input.shared_ptr<Input>());
|
||||
return success;
|
||||
}
|
||||
|
||||
bool XpressNetInterface::removeInput(Input& input)
|
||||
{
|
||||
const bool success = InputController::removeInput(input);
|
||||
if(success)
|
||||
inputs->removeObject(input.shared_ptr<Input>());
|
||||
return success;
|
||||
}
|
||||
|
||||
bool XpressNetInterface::addOutput(Output& output)
|
||||
{
|
||||
const bool success = OutputController::addOutput(output);
|
||||
if(success)
|
||||
outputs->addObject(output.shared_ptr<Output>());
|
||||
return success;
|
||||
}
|
||||
|
||||
bool XpressNetInterface::removeOutput(Output& output)
|
||||
{
|
||||
const bool success = OutputController::removeOutput(output);
|
||||
if(success)
|
||||
outputs->removeObject(output.shared_ptr<Output>());
|
||||
return success;
|
||||
}
|
||||
|
||||
bool XpressNetInterface::setOutputValue(uint32_t address, bool value)
|
||||
{
|
||||
return
|
||||
m_kernel &&
|
||||
inRange(address, outputAddressMinMax()) &&
|
||||
m_kernel->setOutput(static_cast<uint16_t>(address), value);
|
||||
}
|
||||
|
||||
bool XpressNetInterface::setOnline(bool& value)
|
||||
{
|
||||
if(!m_kernel && value)
|
||||
{
|
||||
try
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case XpressNetInterfaceType::Serial:
|
||||
switch(serialInterfaceType)
|
||||
{
|
||||
case XpressNetSerialInterfaceType::LenzLI100:
|
||||
case XpressNetSerialInterfaceType::LenzLI100F:
|
||||
case XpressNetSerialInterfaceType::LenzLI101F:
|
||||
m_kernel = XpressNet::Kernel::create<XpressNet::SerialIOHandler>(xpressnet->config(), device.value(), baudrate.value(), flowControl.value());
|
||||
break;
|
||||
|
||||
case XpressNetSerialInterfaceType::RoSoftS88XPressNetLI:
|
||||
m_kernel = XpressNet::Kernel::create<XpressNet::RoSoftS88XPressNetLIIOHandler>(xpressnet->config(), device.value(), baudrate.value(), flowControl.value(), s88StartAddress.value(), s88ModuleCount.value());
|
||||
break;
|
||||
|
||||
case XpressNetSerialInterfaceType::LenzLIUSB:
|
||||
case XpressNetSerialInterfaceType::DigikeijsDR5000:
|
||||
m_kernel = XpressNet::Kernel::create<XpressNet::LIUSBIOHandler>(xpressnet->config(), device.value(), baudrate.value(), flowControl.value());
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case XpressNetInterfaceType::Network:
|
||||
m_kernel = XpressNet::Kernel::create<XpressNet::TCPIOHandler>(xpressnet->config(), hostname.value(), port.value());
|
||||
break;
|
||||
}
|
||||
|
||||
if(!m_kernel)
|
||||
{
|
||||
assert(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
status.setValueInternal(InterfaceStatus::Initializing);
|
||||
|
||||
m_kernel->setLogId(id.value());
|
||||
m_kernel->setOnStarted(
|
||||
[this]()
|
||||
{
|
||||
status.setValueInternal(InterfaceStatus::Online);
|
||||
});
|
||||
m_kernel->setOnNormalOperationResumed(
|
||||
[this]()
|
||||
{
|
||||
if(auto w = m_world.lock())
|
||||
{
|
||||
if(!contains(w->state.value(), WorldState::PowerOn))
|
||||
w->powerOn();
|
||||
if(!contains(w->state.value(), WorldState::Run))
|
||||
w->run();
|
||||
}
|
||||
});
|
||||
m_kernel->setOnTrackPowerOff(
|
||||
[this]()
|
||||
{
|
||||
if(auto w = m_world.lock())
|
||||
{
|
||||
if(contains(w->state.value(), WorldState::PowerOn))
|
||||
w->powerOff();
|
||||
if(contains(w->state.value(), WorldState::Run))
|
||||
w->stop();
|
||||
}
|
||||
});
|
||||
m_kernel->setOnEmergencyStop(
|
||||
[this]()
|
||||
{
|
||||
if(auto w = m_world.lock(); w && contains(w->state.value(), WorldState::Run))
|
||||
w->stop();
|
||||
});
|
||||
|
||||
m_kernel->setDecoderController(this);
|
||||
m_kernel->setInputController(this);
|
||||
m_kernel->setOutputController(this);
|
||||
m_kernel->start();
|
||||
|
||||
m_xpressnetPropertyChanged = xpressnet->propertyChanged.connect(
|
||||
[this](BaseProperty&)
|
||||
{
|
||||
m_kernel->setConfig(xpressnet->config());
|
||||
});
|
||||
|
||||
if(auto w = m_world.lock())
|
||||
{
|
||||
if(!contains(w->state.value(), WorldState::PowerOn))
|
||||
m_kernel->trackPowerOff();
|
||||
else if(!contains(w->state.value(), WorldState::Run))
|
||||
m_kernel->emergencyStop();
|
||||
else
|
||||
m_kernel->normalOperationsResumed();
|
||||
}
|
||||
|
||||
Attributes::setEnabled({type, serialInterfaceType, device, baudrate, flowControl, hostname, port, s88StartAddress, s88ModuleCount}, false);
|
||||
}
|
||||
catch(const LogMessageException& e)
|
||||
{
|
||||
status.setValueInternal(InterfaceStatus::Offline);
|
||||
Log::log(*this, e.message(), e.args());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if(m_kernel && !value)
|
||||
{
|
||||
Attributes::setEnabled({type, serialInterfaceType, device, baudrate, flowControl, hostname, port, s88StartAddress, s88ModuleCount}, true);
|
||||
|
||||
m_xpressnetPropertyChanged.disconnect();
|
||||
|
||||
m_kernel->stop();
|
||||
m_kernel.reset();
|
||||
|
||||
status.setValueInternal(InterfaceStatus::Offline);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void XpressNetInterface::addToWorld()
|
||||
{
|
||||
Interface::addToWorld();
|
||||
|
||||
if(auto world = m_world.lock())
|
||||
{
|
||||
world->decoderControllers->add(std::dynamic_pointer_cast<DecoderController>(shared_from_this()));
|
||||
world->inputControllers->add(std::dynamic_pointer_cast<InputController>(shared_from_this()));
|
||||
world->outputControllers->add(std::dynamic_pointer_cast<OutputController>(shared_from_this()));
|
||||
}
|
||||
}
|
||||
|
||||
void XpressNetInterface::loaded()
|
||||
{
|
||||
Interface::loaded();
|
||||
|
||||
updateVisible();
|
||||
}
|
||||
|
||||
void XpressNetInterface::destroying()
|
||||
{
|
||||
for(const auto& decoder : *decoders)
|
||||
{
|
||||
assert(decoder->interface.value() == std::dynamic_pointer_cast<DecoderController>(shared_from_this()));
|
||||
decoder->interface = nullptr;
|
||||
}
|
||||
|
||||
for(const auto& input : *inputs)
|
||||
{
|
||||
assert(input->interface.value() == std::dynamic_pointer_cast<InputController>(shared_from_this()));
|
||||
input->interface = nullptr;
|
||||
}
|
||||
|
||||
for(const auto& output : *outputs)
|
||||
{
|
||||
assert(output->interface.value() == std::dynamic_pointer_cast<OutputController>(shared_from_this()));
|
||||
output->interface = nullptr;
|
||||
}
|
||||
|
||||
if(auto world = m_world.lock())
|
||||
{
|
||||
world->decoderControllers->remove(std::dynamic_pointer_cast<DecoderController>(shared_from_this()));
|
||||
world->inputControllers->remove(std::dynamic_pointer_cast<InputController>(shared_from_this()));
|
||||
world->outputControllers->remove(std::dynamic_pointer_cast<OutputController>(shared_from_this()));
|
||||
}
|
||||
|
||||
Interface::destroying();
|
||||
}
|
||||
|
||||
void XpressNetInterface::worldEvent(WorldState state, WorldEvent event)
|
||||
{
|
||||
Interface::worldEvent(state, event);
|
||||
|
||||
if(m_kernel)
|
||||
{
|
||||
switch(event)
|
||||
{
|
||||
case WorldEvent::PowerOff:
|
||||
m_kernel->trackPowerOff();
|
||||
break;
|
||||
|
||||
case WorldEvent::PowerOn:
|
||||
m_kernel->normalOperationsResumed();
|
||||
if(!contains(state, WorldState::Run))
|
||||
m_kernel->emergencyStop();
|
||||
break;
|
||||
|
||||
case WorldEvent::Stop:
|
||||
m_kernel->emergencyStop();
|
||||
break;
|
||||
|
||||
case WorldEvent::Run:
|
||||
if(contains(state, WorldState::PowerOn))
|
||||
m_kernel->normalOperationsResumed();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void XpressNetInterface::idChanged(const std::string& newId)
|
||||
{
|
||||
if(m_kernel)
|
||||
m_kernel->setLogId(newId);
|
||||
}
|
||||
|
||||
void XpressNetInterface::updateVisible()
|
||||
{
|
||||
const bool isSerial = (type == XpressNetInterfaceType::Serial);
|
||||
Attributes::setVisible(serialInterfaceType, isSerial);
|
||||
Attributes::setVisible(device, isSerial);
|
||||
Attributes::setVisible(baudrate, isSerial);
|
||||
Attributes::setVisible(flowControl, isSerial);
|
||||
|
||||
const bool isNetwork = (type == XpressNetInterfaceType::Network);
|
||||
Attributes::setVisible(hostname, isNetwork);
|
||||
Attributes::setVisible(port, isNetwork);
|
||||
|
||||
const bool isRoSoftS88XPressNetLI = isSerial && (serialInterfaceType == XpressNetSerialInterfaceType::RoSoftS88XPressNetLI);
|
||||
Attributes::setVisible(s88StartAddress, isRoSoftS88XPressNetLI);
|
||||
Attributes::setVisible(s88ModuleCount, isRoSoftS88XPressNetLI);
|
||||
}
|
||||
103
server/src/hardware/interface/xpressnetinterface.hpp
Normale Datei
103
server/src/hardware/interface/xpressnetinterface.hpp
Normale Datei
@ -0,0 +1,103 @@
|
||||
/**
|
||||
* server/src/hardware/interface/xpressnetinterface.hpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef TRAINTASTIC_SERVER_HARDWARE_INTERFACE_XPRESSNETINTERFACE_HPP
|
||||
#define TRAINTASTIC_SERVER_HARDWARE_INTERFACE_XPRESSNETINTERFACE_HPP
|
||||
|
||||
#include "interface.hpp"
|
||||
#include "../protocol/xpressnet/kernel.hpp"
|
||||
#include "../protocol/xpressnet/settings.hpp"
|
||||
#include "../decoder/decodercontroller.hpp"
|
||||
#include "../decoder/decoderlist.hpp"
|
||||
#include "../input/inputcontroller.hpp"
|
||||
#include "../input/list/inputlist.hpp"
|
||||
#include "../output/outputcontroller.hpp"
|
||||
#include "../output/list/outputlist.hpp"
|
||||
#include "../../core/objectproperty.hpp"
|
||||
#include "../../enum/xpressnetinterfacetype.hpp"
|
||||
#include "../../enum/xpressnetserialinterfacetype.hpp"
|
||||
#include "../../enum/serialflowcontrol.hpp"
|
||||
|
||||
/**
|
||||
* @brief XpressNet hardware interface
|
||||
*/
|
||||
class XpressNetInterface final
|
||||
: public Interface
|
||||
, public DecoderController
|
||||
, public InputController
|
||||
, public OutputController
|
||||
{
|
||||
CLASS_ID("interface.xpressnet")
|
||||
DEFAULT_ID("xpressnet")
|
||||
CREATE(XpressNetInterface)
|
||||
|
||||
private:
|
||||
std::unique_ptr<XpressNet::Kernel> m_kernel;
|
||||
boost::signals2::connection m_xpressnetPropertyChanged;
|
||||
|
||||
void addToWorld() final;
|
||||
void loaded() final;
|
||||
void destroying() final;
|
||||
void worldEvent(WorldState state, WorldEvent event) final;
|
||||
|
||||
void idChanged(const std::string& newId) final;
|
||||
|
||||
void updateVisible();
|
||||
|
||||
protected:
|
||||
bool setOnline(bool& value) final;
|
||||
|
||||
public:
|
||||
Property<XpressNetInterfaceType> type;
|
||||
Property<XpressNetSerialInterfaceType> serialInterfaceType;
|
||||
Property<std::string> device;
|
||||
Property<uint32_t> baudrate;
|
||||
Property<SerialFlowControl> flowControl;
|
||||
Property<std::string> hostname;
|
||||
Property<uint16_t> port;
|
||||
Property<uint8_t> s88StartAddress;
|
||||
Property<uint8_t> s88ModuleCount;
|
||||
ObjectProperty<XpressNet::Settings> xpressnet;
|
||||
ObjectProperty<DecoderList> decoders;
|
||||
ObjectProperty<InputList> inputs;
|
||||
ObjectProperty<OutputList> outputs;
|
||||
|
||||
XpressNetInterface(const std::weak_ptr<World>& world, std::string_view _id);
|
||||
|
||||
// DecoderController:
|
||||
[[nodiscard]] bool addDecoder(Decoder& decoder) final;
|
||||
[[nodiscard]] bool removeDecoder(Decoder& decoder) final;
|
||||
void decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber) final;
|
||||
|
||||
// InputController:
|
||||
std::pair<uint32_t, uint32_t> inputAddressMinMax() const final { return {XpressNet::Kernel::ioAddressMin, XpressNet::Kernel::ioAddressMax}; }
|
||||
[[nodiscard]] bool addInput(Input& input) final;
|
||||
[[nodiscard]] bool removeInput(Input& input) final;
|
||||
|
||||
// OutputController:
|
||||
std::pair<uint32_t, uint32_t> outputAddressMinMax() const final { return {XpressNet::Kernel::ioAddressMin, XpressNet::Kernel::ioAddressMax}; }
|
||||
[[nodiscard]] bool addOutput(Output& output) final;
|
||||
[[nodiscard]] bool removeOutput(Output& output) final;
|
||||
[[nodiscard]] bool setOutputValue(uint32_t address, bool value) final;
|
||||
};
|
||||
|
||||
#endif
|
||||
260
server/src/hardware/interface/z21interface.cpp
Normale Datei
260
server/src/hardware/interface/z21interface.cpp
Normale Datei
@ -0,0 +1,260 @@
|
||||
/**
|
||||
* server/src/hardware/interface/z21interface.cpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "z21interface.hpp"
|
||||
#include "../decoder/decoderlisttablemodel.hpp"
|
||||
#include "../protocol/z21/messages.hpp"
|
||||
#include "../protocol/z21/iohandler/udpclientiohandler.hpp"
|
||||
#include "../../core/attributes.hpp"
|
||||
#include "../../log/log.hpp"
|
||||
#include "../../log/logmessageexception.hpp"
|
||||
#include "../../utils/category.hpp"
|
||||
#include "../../utils/displayname.hpp"
|
||||
#include "../../utils/inrange.hpp"
|
||||
#include "../../world/world.hpp"
|
||||
|
||||
Z21Interface::Z21Interface(const std::weak_ptr<World>& world, std::string_view _id)
|
||||
: Interface(world, _id)
|
||||
, hostname{this, "hostname", "192.168.1.203", PropertyFlags::ReadWrite | PropertyFlags::Store}
|
||||
, port{this, "port", 21105, PropertyFlags::ReadWrite | PropertyFlags::Store}
|
||||
, z21{this, "z21", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject}
|
||||
, decoders{this, "decoders", nullptr, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::SubObject}
|
||||
, hardwareType{this, "hardware_type", "", PropertyFlags::ReadOnly | PropertyFlags::NoStore}
|
||||
, serialNumber{this, "serial_number", "", PropertyFlags::ReadOnly | PropertyFlags::NoStore}
|
||||
, firmwareVersion{this, "firmware_version", "", PropertyFlags::ReadOnly | PropertyFlags::NoStore}
|
||||
{
|
||||
z21.setValueInternal(std::make_shared<Z21::ClientSettings>(*this, z21.name()));
|
||||
decoders.setValueInternal(std::make_shared<DecoderList>(*this, decoders.name()));
|
||||
|
||||
Attributes::addDisplayName(hostname, DisplayName::IP::hostname);
|
||||
Attributes::addEnabled(hostname, !online);
|
||||
m_interfaceItems.insertBefore(hostname, notes);
|
||||
|
||||
Attributes::addDisplayName(port, DisplayName::IP::port);
|
||||
Attributes::addEnabled(port, !online);
|
||||
m_interfaceItems.insertBefore(port, notes);
|
||||
|
||||
Attributes::addDisplayName(z21, DisplayName::Hardware::z21);
|
||||
m_interfaceItems.insertBefore(z21, notes);
|
||||
|
||||
Attributes::addDisplayName(decoders, DisplayName::Hardware::decoders);
|
||||
m_interfaceItems.insertBefore(decoders, notes);
|
||||
|
||||
Attributes::addCategory(hardwareType, Category::info);
|
||||
m_interfaceItems.insertBefore(hardwareType, notes);
|
||||
|
||||
Attributes::addCategory(serialNumber, Category::info);
|
||||
m_interfaceItems.insertBefore(serialNumber, notes);
|
||||
|
||||
Attributes::addCategory(firmwareVersion, Category::info);
|
||||
m_interfaceItems.insertBefore(firmwareVersion, notes);
|
||||
}
|
||||
|
||||
bool Z21Interface::addDecoder(Decoder& decoder)
|
||||
{
|
||||
const bool success = DecoderController::addDecoder(decoder);
|
||||
if(success)
|
||||
decoders->addObject(decoder.shared_ptr<Decoder>());
|
||||
return success;
|
||||
}
|
||||
|
||||
bool Z21Interface::removeDecoder(Decoder& decoder)
|
||||
{
|
||||
const bool success = DecoderController::removeDecoder(decoder);
|
||||
if(success)
|
||||
decoders->removeObject(decoder.shared_ptr<Decoder>());
|
||||
return success;
|
||||
}
|
||||
|
||||
void Z21Interface::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber)
|
||||
{
|
||||
if(m_kernel)
|
||||
m_kernel->decoderChanged(decoder, changes, functionNumber);
|
||||
}
|
||||
|
||||
bool Z21Interface::setOnline(bool& value)
|
||||
{
|
||||
if(!m_kernel && value)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_kernel = Z21::ClientKernel::create<Z21::UDPClientIOHandler>(z21->config(), hostname.value(), port.value());
|
||||
|
||||
status.setValueInternal(InterfaceStatus::Initializing);
|
||||
|
||||
m_kernel->setLogId(id.value());
|
||||
m_kernel->setOnStarted(
|
||||
[this]()
|
||||
{
|
||||
status.setValueInternal(InterfaceStatus::Online);
|
||||
});
|
||||
m_kernel->setOnSerialNumberChanged(
|
||||
[this](uint32_t newValue)
|
||||
{
|
||||
serialNumber.setValueInternal(std::to_string(newValue));
|
||||
});
|
||||
m_kernel->setOnHardwareInfoChanged(
|
||||
[this](Z21::HardwareType type, uint8_t versionMajor, uint8_t versionMinor)
|
||||
{
|
||||
hardwareType.setValueInternal(std::string(Z21::toString(type)));
|
||||
Log::log(*this, LogMessage::I2002_HARDWARE_TYPE_X, hardwareType.value());
|
||||
|
||||
if(versionMajor != 0 || versionMinor != 0)
|
||||
{
|
||||
firmwareVersion.setValueInternal(std::to_string(versionMajor).append(".").append(std::to_string(versionMinor)));
|
||||
Log::log(*this, LogMessage::I2003_FIRMWARE_VERSION_X, firmwareVersion.value());
|
||||
}
|
||||
else
|
||||
firmwareVersion.setValueInternal("");
|
||||
});
|
||||
m_kernel->setOnTrackPowerOnChanged(
|
||||
[this](bool powerOn)
|
||||
{
|
||||
if(auto w = m_world.lock())
|
||||
{
|
||||
if(powerOn == contains(w->state.value(), WorldState::PowerOn))
|
||||
return;
|
||||
|
||||
if(powerOn)
|
||||
w->powerOn();
|
||||
else
|
||||
w->powerOff();
|
||||
}
|
||||
});
|
||||
m_kernel->setOnEmergencyStop(
|
||||
[this]()
|
||||
{
|
||||
if(auto w = m_world.lock(); w && contains(w->state.value(), WorldState::Run))
|
||||
w->stop();
|
||||
});
|
||||
|
||||
m_kernel->setDecoderController(this);
|
||||
|
||||
m_kernel->start();
|
||||
|
||||
m_z21PropertyChanged = z21->propertyChanged.connect(
|
||||
[this](BaseProperty&)
|
||||
{
|
||||
m_kernel->setConfig(z21->config());
|
||||
});
|
||||
|
||||
if(auto w = m_world.lock())
|
||||
{
|
||||
if(contains(w->state.value(), WorldState::PowerOn))
|
||||
m_kernel->trackPowerOn();
|
||||
else
|
||||
m_kernel->trackPowerOff();
|
||||
|
||||
if(!contains(w->state.value(), WorldState::Run))
|
||||
m_kernel->emergencyStop();
|
||||
}
|
||||
|
||||
Attributes::setEnabled({hostname, port}, false);
|
||||
}
|
||||
catch(const LogMessageException& e)
|
||||
{
|
||||
status.setValueInternal(InterfaceStatus::Offline);
|
||||
Log::log(*this, e.message(), e.args());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if(m_kernel && !value)
|
||||
{
|
||||
Attributes::setEnabled({hostname, port}, true);
|
||||
|
||||
m_z21PropertyChanged.disconnect();
|
||||
|
||||
m_kernel->stop();
|
||||
m_kernel.reset();
|
||||
|
||||
status.setValueInternal(InterfaceStatus::Offline);
|
||||
hardwareType.setValueInternal("");
|
||||
serialNumber.setValueInternal("");
|
||||
firmwareVersion.setValueInternal("");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Z21Interface::addToWorld()
|
||||
{
|
||||
Interface::addToWorld();
|
||||
|
||||
if(auto world = m_world.lock())
|
||||
{
|
||||
world->decoderControllers->add(std::dynamic_pointer_cast<DecoderController>(shared_from_this()));
|
||||
}
|
||||
}
|
||||
|
||||
void Z21Interface::destroying()
|
||||
{
|
||||
for(const auto& decoder : *decoders)
|
||||
{
|
||||
assert(decoder->interface.value() == std::dynamic_pointer_cast<DecoderController>(shared_from_this()));
|
||||
decoder->interface = nullptr;
|
||||
}
|
||||
|
||||
if(auto world = m_world.lock())
|
||||
{
|
||||
world->decoderControllers->remove(std::dynamic_pointer_cast<DecoderController>(shared_from_this()));
|
||||
}
|
||||
|
||||
Interface::destroying();
|
||||
}
|
||||
|
||||
void Z21Interface::worldEvent(WorldState state, WorldEvent event)
|
||||
{
|
||||
Interface::worldEvent(state, event);
|
||||
|
||||
if(m_kernel)
|
||||
{
|
||||
switch(event)
|
||||
{
|
||||
case WorldEvent::PowerOff:
|
||||
m_kernel->trackPowerOff();
|
||||
break;
|
||||
|
||||
case WorldEvent::PowerOn:
|
||||
m_kernel->trackPowerOn();
|
||||
if(!contains(state, WorldState::Run))
|
||||
m_kernel->emergencyStop();
|
||||
break;
|
||||
|
||||
case WorldEvent::Stop:
|
||||
m_kernel->emergencyStop();
|
||||
break;
|
||||
|
||||
case WorldEvent::Run:
|
||||
if(contains(state, WorldState::PowerOn))
|
||||
m_kernel->trackPowerOn();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Z21Interface::idChanged(const std::string& newId)
|
||||
{
|
||||
if(m_kernel)
|
||||
m_kernel->setLogId(newId);
|
||||
}
|
||||
76
server/src/hardware/interface/z21interface.hpp
Normale Datei
76
server/src/hardware/interface/z21interface.hpp
Normale Datei
@ -0,0 +1,76 @@
|
||||
/**
|
||||
* server/src/hardware/interface/z21interface.hpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef TRAINTASTIC_SERVER_HARDWARE_INTERFACE_Z21INTERFACE_HPP
|
||||
#define TRAINTASTIC_SERVER_HARDWARE_INTERFACE_Z21INTERFACE_HPP
|
||||
|
||||
#include "interface.hpp"
|
||||
#include "../protocol/z21/clientkernel.hpp"
|
||||
#include "../protocol/z21/clientsettings.hpp"
|
||||
#include "../decoder/decodercontroller.hpp"
|
||||
#include "../decoder/decoderlist.hpp"
|
||||
#include "../../core/objectproperty.hpp"
|
||||
|
||||
/**
|
||||
* @brief Z21 hardware interface
|
||||
*/
|
||||
class Z21Interface final
|
||||
: public Interface
|
||||
, public DecoderController
|
||||
{
|
||||
CLASS_ID("interface.z21")
|
||||
DEFAULT_ID("z21")
|
||||
CREATE(Z21Interface)
|
||||
|
||||
private:
|
||||
std::unique_ptr<Z21::ClientKernel> m_kernel;
|
||||
boost::signals2::connection m_z21PropertyChanged;
|
||||
|
||||
void addToWorld() final;
|
||||
void destroying() final;
|
||||
void worldEvent(WorldState state, WorldEvent event) final;
|
||||
|
||||
void idChanged(const std::string& newId) final;
|
||||
|
||||
void updateVisible();
|
||||
|
||||
protected:
|
||||
bool setOnline(bool& value) final;
|
||||
|
||||
public:
|
||||
Property<std::string> hostname;
|
||||
Property<uint16_t> port;
|
||||
ObjectProperty<Z21::ClientSettings> z21;
|
||||
ObjectProperty<DecoderList> decoders;
|
||||
Property<std::string> hardwareType;
|
||||
Property<std::string> serialNumber;
|
||||
Property<std::string> firmwareVersion;
|
||||
|
||||
Z21Interface(const std::weak_ptr<World>& world, std::string_view _id);
|
||||
|
||||
// DecoderController:
|
||||
[[nodiscard]] bool addDecoder(Decoder& decoder) final;
|
||||
[[nodiscard]] bool removeDecoder(Decoder& decoder) final;
|
||||
void decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber) final;
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -1,64 +0,0 @@
|
||||
/**
|
||||
* server/src/hardware/output/keyboard/loconetoutputkeyboard.cpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "loconetoutputkeyboard.hpp"
|
||||
#include "../../protocol/loconet/loconet.hpp"
|
||||
#include "../loconetoutput.hpp"
|
||||
|
||||
LocoNetOutputKeyboard::LocoNetOutputKeyboard(std::shared_ptr<LocoNet::LocoNet> loconet) :
|
||||
OutputKeyboard(),
|
||||
m_loconet{std::move(loconet)}
|
||||
{
|
||||
addressMin.setValueInternal(LocoNetOutput::addressMin);
|
||||
addressMax.setValueInternal(LocoNetOutput::addressMax);
|
||||
m_loconet->m_outputKeyboards.emplace_back(this);
|
||||
}
|
||||
|
||||
LocoNetOutputKeyboard::~LocoNetOutputKeyboard()
|
||||
{
|
||||
if(auto it = std::find(m_loconet->m_outputKeyboards.begin(), m_loconet->m_outputKeyboards.end(), this); it != m_loconet->m_outputKeyboards.end())
|
||||
m_loconet->m_outputKeyboards.erase(it);
|
||||
}
|
||||
|
||||
std::vector<OutputKeyboard::OutputInfo> LocoNetOutputKeyboard::getOutputInfo() const
|
||||
{
|
||||
std::vector<OutputInfo> outputInfo;
|
||||
for(auto it : m_loconet->m_outputs)
|
||||
{
|
||||
LocoNetOutput& output = *(it.second);
|
||||
OutputInfo info(output.address, output.id, output.value);
|
||||
outputInfo.push_back(info);
|
||||
}
|
||||
return outputInfo;
|
||||
}
|
||||
|
||||
void LocoNetOutputKeyboard::setOutputValue(uint32_t address, bool value)
|
||||
{
|
||||
if(address < LocoNetOutput::addressMin || address > LocoNetOutput::addressMax)
|
||||
return;
|
||||
|
||||
auto it = m_loconet->m_outputs.find(address);
|
||||
if(it != m_loconet->m_outputs.end())
|
||||
it->second->value = toTriState(value);
|
||||
else
|
||||
m_loconet->send(LocoNet::SwitchRequest(address - 1, value));
|
||||
}
|
||||
55
server/src/hardware/output/keyboard/outputkeyboard.cpp
Normale Datei
55
server/src/hardware/output/keyboard/outputkeyboard.cpp
Normale Datei
@ -0,0 +1,55 @@
|
||||
/**
|
||||
* server/src/hardware/output/keyboard/outputkeyboard.cpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2021 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "outputkeyboard.hpp"
|
||||
#include "../output.hpp"
|
||||
#include "../outputcontroller.hpp"
|
||||
#include "../../../utils/inrange.hpp"
|
||||
|
||||
OutputKeyboard::OutputKeyboard(OutputController& controller)
|
||||
: Object()
|
||||
, m_controller{controller}
|
||||
, addressMin{this, "address_min", m_controller.outputAddressMinMax().first, PropertyFlags::ReadOnly | PropertyFlags::NoStore}
|
||||
, addressMax{this, "address_max", m_controller.outputAddressMinMax().second, PropertyFlags::ReadOnly | PropertyFlags::NoStore}
|
||||
{
|
||||
}
|
||||
|
||||
std::string OutputKeyboard::getObjectId() const
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
std::vector<OutputKeyboard::OutputInfo> OutputKeyboard::getOutputInfo() const
|
||||
{
|
||||
std::vector<OutputInfo> outputInfo;
|
||||
for(auto it : m_controller.outputs())
|
||||
{
|
||||
const auto& output = *(it.second);
|
||||
outputInfo.emplace_back(OutputInfo{output.address, output.id, output.value});
|
||||
}
|
||||
return outputInfo;
|
||||
}
|
||||
|
||||
void OutputKeyboard::setOutputValue(uint32_t address, bool value)
|
||||
{
|
||||
m_controller.setOutputValue(address, value);
|
||||
}
|
||||
@ -28,11 +28,18 @@
|
||||
#include "../../../core/property.hpp"
|
||||
#include "../../../enum/tristate.hpp"
|
||||
|
||||
class OutputController;
|
||||
|
||||
class OutputKeyboard : public Object
|
||||
{
|
||||
CLASS_ID("output_keyboard")
|
||||
|
||||
private:
|
||||
OutputController& m_controller;
|
||||
|
||||
public:
|
||||
std::function<void(OutputKeyboard&, uint32_t, std::string_view)> outputIdChanged;
|
||||
std::function<void(OutputKeyboard&, uint32_t, TriState)> outputValueChanged;
|
||||
boost::signals2::signal<void(OutputKeyboard&, uint32_t, std::string_view)> outputIdChanged;
|
||||
boost::signals2::signal<void(OutputKeyboard&, uint32_t, TriState)> outputValueChanged;
|
||||
|
||||
struct OutputInfo
|
||||
{
|
||||
@ -51,15 +58,12 @@ class OutputKeyboard : public Object
|
||||
Property<uint32_t> addressMin;
|
||||
Property<uint32_t> addressMax;
|
||||
|
||||
OutputKeyboard() :
|
||||
Object(),
|
||||
addressMin{this, "address_min", 0, PropertyFlags::ReadOnly | PropertyFlags::NoStore},
|
||||
addressMax{this, "address_max", 0, PropertyFlags::ReadOnly | PropertyFlags::NoStore}
|
||||
{
|
||||
}
|
||||
OutputKeyboard(OutputController& controller);
|
||||
|
||||
virtual std::vector<OutputInfo> getOutputInfo() const = 0;
|
||||
virtual void setOutputValue(uint32_t address, bool value) = 0;
|
||||
std::string getObjectId() const final;
|
||||
|
||||
std::vector<OutputInfo> getOutputInfo() const;
|
||||
void setOutputValue(uint32_t address, bool value);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@ -22,31 +22,32 @@
|
||||
|
||||
#include "outputlist.hpp"
|
||||
#include "outputlisttablemodel.hpp"
|
||||
#include "../outputs.hpp"
|
||||
#include "../outputcontroller.hpp"
|
||||
#include "../../../world/getworld.hpp"
|
||||
#include "../../../core/attributes.hpp"
|
||||
#include "../../../utils/displayname.hpp"
|
||||
|
||||
OutputList::OutputList(Object& _parent, const std::string& parentPropertyName) :
|
||||
ObjectList<Output>(_parent, parentPropertyName),
|
||||
add{*this, "add",
|
||||
[this](std::string_view outputClassId)
|
||||
{
|
||||
auto world = getWorld(&this->parent());
|
||||
if(!world)
|
||||
return std::shared_ptr<Output>();
|
||||
auto output = Outputs::create(world, outputClassId, world->getUniqueId("output"));
|
||||
if(auto locoNetOutput = std::dynamic_pointer_cast<LocoNetOutput>(output); locoNetOutput && world->loconets->length == 1)
|
||||
OutputList::OutputList(Object& _parent, const std::string& parentPropertyName)
|
||||
: ObjectList<Output>(_parent, parentPropertyName)
|
||||
, m_parentIsOutputController(dynamic_cast<OutputController*>(&_parent))
|
||||
, add{*this, "add",
|
||||
[this]()
|
||||
{
|
||||
auto& loconet = world->loconets->operator[](0);
|
||||
if(uint16_t address = loconet->getUnusedOutputAddress(); address != LocoNetOutput::addressInvalid)
|
||||
auto world = getWorld(&parent());
|
||||
if(!world)
|
||||
return std::shared_ptr<Output>();
|
||||
|
||||
auto output = Output::create(world, world->getUniqueId(Output::defaultId));
|
||||
if(const auto controller = std::dynamic_pointer_cast<OutputController>(parent().shared_from_this()))
|
||||
{
|
||||
locoNetOutput->address = address;
|
||||
locoNetOutput->loconet = loconet;
|
||||
if(const uint32_t address = controller->getUnusedOutputAddress(); address != Output::invalidAddress)
|
||||
{
|
||||
output->address = address;
|
||||
output->interface = controller;
|
||||
}
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}}
|
||||
return output;
|
||||
}}
|
||||
, remove{*this, "remove",
|
||||
[this](const std::shared_ptr<Output>& output)
|
||||
{
|
||||
@ -54,18 +55,31 @@ OutputList::OutputList(Object& _parent, const std::string& parentPropertyName) :
|
||||
output->destroy();
|
||||
assert(!containsObject(output));
|
||||
}}
|
||||
, outputKeyboard{*this, "output_keyboard",
|
||||
[this]()
|
||||
{
|
||||
if(const auto controller = std::dynamic_pointer_cast<OutputController>(parent().shared_from_this()))
|
||||
return controller->outputKeyboard();
|
||||
else
|
||||
return std::shared_ptr<OutputKeyboard>();
|
||||
}}
|
||||
{
|
||||
auto w = getWorld(&_parent);
|
||||
const bool editable = w && contains(w->state.value(), WorldState::Edit);
|
||||
|
||||
Attributes::addDisplayName(add, DisplayName::List::add);
|
||||
Attributes::addEnabled(add, editable);
|
||||
Attributes::addClassList(add, Outputs::classList);
|
||||
m_interfaceItems.add(add);
|
||||
|
||||
Attributes::addDisplayName(remove, DisplayName::List::remove);
|
||||
Attributes::addEnabled(remove, editable);
|
||||
m_interfaceItems.add(remove);
|
||||
|
||||
if(m_parentIsOutputController)
|
||||
{
|
||||
Attributes::addDisplayName(outputKeyboard, DisplayName::Hardware::outputKeyboard);
|
||||
m_interfaceItems.add(outputKeyboard);
|
||||
}
|
||||
}
|
||||
|
||||
TableModelPtr OutputList::getModel()
|
||||
|
||||
Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden Mehr anzeigen
Laden…
x
In neuem Issue referenzieren
Einen Benutzer sperren