From 754655aed1a0fd866fbb387cb3707214c885a7be Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Sat, 15 Jan 2022 08:48:04 +0100 Subject: [PATCH 01/12] world: added simulation option --- client/src/mainwindow.cpp | 24 +++- client/src/mainwindow.hpp | 1 + .../interface/dccplusplusinterface.cpp | 8 +- .../interface/dccplusplusinterface.hpp | 2 +- .../src/hardware/interface/ecosinterface.cpp | 10 +- .../src/hardware/interface/ecosinterface.hpp | 4 +- server/src/hardware/interface/interface.cpp | 7 +- server/src/hardware/interface/interface.hpp | 2 +- .../hardware/interface/loconetinterface.cpp | 2 +- .../hardware/interface/loconetinterface.hpp | 4 +- .../hardware/interface/wlanmausinterface.cpp | 8 +- .../hardware/interface/wlanmausinterface.hpp | 2 +- .../hardware/interface/xpressnetinterface.cpp | 8 +- .../hardware/interface/xpressnetinterface.hpp | 2 +- .../src/hardware/interface/z21interface.cpp | 8 +- .../src/hardware/interface/z21interface.hpp | 2 +- server/src/world/world.cpp | 131 +++++++++++++----- server/src/world/world.hpp | 2 + shared/src/traintastic/enum/logmessage.hpp | 5 +- shared/src/traintastic/enum/worldevent.hpp | 2 + shared/src/traintastic/set/worldstate.hpp | 1 + shared/translations/en-us.txt | 4 + 22 files changed, 181 insertions(+), 58 deletions(-) diff --git a/client/src/mainwindow.cpp b/client/src/mainwindow.cpp index 4fea6ecb..1c6e5189 100644 --- a/client/src/mainwindow.cpp +++ b/client/src/mainwindow.cpp @@ -222,6 +222,15 @@ MainWindow::MainWindow(QWidget* parent) : }); m_worldEditAction->setCheckable(true); m_menuWorld->addSeparator(); + m_worldSimulationAction = m_menuWorld->addAction(Theme::getIcon("simulation"), Locale::tr("world:simulation"), + [this](bool checked) + { + if(m_world) + if(AbstractProperty* property = m_world->getProperty("simulation")) + property->setValueBool(checked); + }); + m_worldSimulationAction->setCheckable(true); + m_menuWorld->addSeparator(); m_menuWorld->addAction(Theme::getIcon("world"), Locale::tr("qtapp.mainmenu:world_properties"), [this](){ showObject("world", Locale::tr("qtapp.mainmenu:world_properties")); }); m_menuObjects = menuBar()->addMenu(Locale::tr("qtapp.mainmenu:objects")); @@ -424,11 +433,17 @@ void MainWindow::worldChanged() if(auto* state = m_world->getProperty("state")) connect(state, &AbstractProperty::valueChangedInt64, this, &MainWindow::worldStateChanged); - //if(AbstractProperty* edit = m_world->getProperty("edit")) - // connect(edit, &AbstractProperty::valueChangedBool, m_worldEditAction, &QAction::setChecked); + if(AbstractProperty* simulation = m_world->getProperty("simulation")) + { + connect(simulation, &AbstractProperty::attributeChanged, + [this](AttributeName attribute, const QVariant& value) + { + if(attribute == AttributeName::Enabled) + m_worldSimulationAction->setEnabled(value.toBool()); + }); - //if(AbstractProperty* edit = m_world->getProperty("edit")) - // connect(edit, &AbstractProperty::valueChangedBool, m_worldEditAction, &QAction::setChecked); + m_worldSimulationAction->setEnabled(simulation->getAttributeBool(AttributeName::Enabled, true)); + } } updateWindowTitle(); @@ -694,4 +709,5 @@ void MainWindow::worldStateChanged(int64_t value) m_worldNoSmokeMenuAction->setChecked(contains(state, WorldState::NoSmoke)); m_worldNoSmokeToolbarAction->setChecked(contains(state, WorldState::NoSmoke)); m_worldEditAction->setChecked(contains(state, WorldState::Edit)); + m_worldSimulationAction->setChecked(contains(state, WorldState::Simulation)); } diff --git a/client/src/mainwindow.hpp b/client/src/mainwindow.hpp index f620fa7f..b13be8d8 100644 --- a/client/src/mainwindow.hpp +++ b/client/src/mainwindow.hpp @@ -72,6 +72,7 @@ class MainWindow : public QMainWindow QAction* m_worldMuteMenuAction; QAction* m_worldNoSmokeMenuAction; QAction* m_worldEditAction; + QAction* m_worldSimulationAction; QMenu* m_menuObjects; QAction* m_actionLuaScript; QAction* m_actionFullScreen; diff --git a/server/src/hardware/interface/dccplusplusinterface.cpp b/server/src/hardware/interface/dccplusplusinterface.cpp index ec7b597f..e209c69b 100644 --- a/server/src/hardware/interface/dccplusplusinterface.cpp +++ b/server/src/hardware/interface/dccplusplusinterface.cpp @@ -117,8 +117,14 @@ bool DCCPlusPlusInterface::setOutputValue(uint32_t address, bool value) m_kernel->setOutput(static_cast(address), value); } -bool DCCPlusPlusInterface::setOnline(bool& value) +bool DCCPlusPlusInterface::setOnline(bool& value, bool simulation) { + if(simulation) + { + Log::log(*this, LogMessage::N2001_SIMULATION_NOT_SUPPORTED); + return false; + } + if(!m_kernel && value) { try diff --git a/server/src/hardware/interface/dccplusplusinterface.hpp b/server/src/hardware/interface/dccplusplusinterface.hpp index 6026549c..2a816893 100644 --- a/server/src/hardware/interface/dccplusplusinterface.hpp +++ b/server/src/hardware/interface/dccplusplusinterface.hpp @@ -60,7 +60,7 @@ class DCCPlusPlusInterface final void idChanged(const std::string& newId) final; protected: - bool setOnline(bool& value) final; + bool setOnline(bool& value, bool simulation) final; public: Property device; diff --git a/server/src/hardware/interface/ecosinterface.cpp b/server/src/hardware/interface/ecosinterface.cpp index b3ab5a84..4a3e88f6 100644 --- a/server/src/hardware/interface/ecosinterface.cpp +++ b/server/src/hardware/interface/ecosinterface.cpp @@ -3,7 +3,7 @@ * * This file is part of the traintastic source code. * - * Copyright (C) 2021 Reinder Feenstra + * Copyright (C) 2021-2022 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -133,8 +133,14 @@ bool ECoSInterface::setOutputValue(uint32_t address, bool value) m_kernel->setOutput(static_cast(address), value); } -bool ECoSInterface::setOnline(bool& value) +bool ECoSInterface::setOnline(bool& value, bool simulation) { + if(simulation) + { + Log::log(*this, LogMessage::N2001_SIMULATION_NOT_SUPPORTED); + return false; + } + if(!m_kernel && value) { try diff --git a/server/src/hardware/interface/ecosinterface.hpp b/server/src/hardware/interface/ecosinterface.hpp index 762409f2..a0a511a9 100644 --- a/server/src/hardware/interface/ecosinterface.hpp +++ b/server/src/hardware/interface/ecosinterface.hpp @@ -3,7 +3,7 @@ * * This file is part of the traintastic source code. * - * Copyright (C) 2021 Reinder Feenstra + * Copyright (C) 2021-2022 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -60,7 +60,7 @@ class ECoSInterface final void typeChanged(); protected: - bool setOnline(bool& value) final; + bool setOnline(bool& value, bool simulation) final; public: Property hostname; diff --git a/server/src/hardware/interface/interface.cpp b/server/src/hardware/interface/interface.cpp index e8d11d63..04329146 100644 --- a/server/src/hardware/interface/interface.cpp +++ b/server/src/hardware/interface/interface.cpp @@ -29,7 +29,12 @@ Interface::Interface(const std::weak_ptr& world, std::string_view _id) : IdObject(world, _id) , name{this, "name", "", PropertyFlags::ReadWrite | PropertyFlags::Store} - , online{this, "online", false, PropertyFlags::ReadWrite | PropertyFlags::NoStore, nullptr, std::bind(&Interface::setOnline, this, std::placeholders::_1)} + , online{this, "online", false, PropertyFlags::ReadWrite | PropertyFlags::NoStore, nullptr, + [this](bool& value) + { + assert(m_world.lock()); + return setOnline(value, contains(m_world.lock()->state.value(), WorldState::Simulation)); + }} , status{this, "status", InterfaceStatus::Offline, PropertyFlags::ReadOnly | PropertyFlags::NoStore} , notes{this, "notes", "", PropertyFlags::ReadWrite | PropertyFlags::Store} { diff --git a/server/src/hardware/interface/interface.hpp b/server/src/hardware/interface/interface.hpp index 7b5c4a7e..c32167e1 100644 --- a/server/src/hardware/interface/interface.hpp +++ b/server/src/hardware/interface/interface.hpp @@ -38,7 +38,7 @@ class Interface : public IdObject void destroying() override; void worldEvent(WorldState state, WorldEvent event) override; - virtual bool setOnline(bool& value) = 0; + virtual bool setOnline(bool& value, bool simulation) = 0; public: Property name; diff --git a/server/src/hardware/interface/loconetinterface.cpp b/server/src/hardware/interface/loconetinterface.cpp index 6a22af9d..2b2de295 100644 --- a/server/src/hardware/interface/loconetinterface.cpp +++ b/server/src/hardware/interface/loconetinterface.cpp @@ -165,7 +165,7 @@ bool LocoNetInterface::setOutputValue(uint32_t address, bool value) m_kernel->setOutput(static_cast(address), value); } -bool LocoNetInterface::setOnline(bool& value) +bool LocoNetInterface::setOnline(bool& value, bool simulation) { if(!m_kernel && value) { diff --git a/server/src/hardware/interface/loconetinterface.hpp b/server/src/hardware/interface/loconetinterface.hpp index 0a62cbc9..c8228f4f 100644 --- a/server/src/hardware/interface/loconetinterface.hpp +++ b/server/src/hardware/interface/loconetinterface.hpp @@ -3,7 +3,7 @@ * * This file is part of the traintastic source code. * - * Copyright (C) 2019-2021 Reinder Feenstra + * Copyright (C) 2019-2022 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -63,7 +63,7 @@ class LocoNetInterface final void typeChanged(); protected: - bool setOnline(bool& value) final; + bool setOnline(bool& value, bool simulation) final; public: Property type; diff --git a/server/src/hardware/interface/wlanmausinterface.cpp b/server/src/hardware/interface/wlanmausinterface.cpp index b6fc4733..70a9e753 100644 --- a/server/src/hardware/interface/wlanmausinterface.cpp +++ b/server/src/hardware/interface/wlanmausinterface.cpp @@ -65,8 +65,14 @@ void WlanMausInterface::idChanged(const std::string& newId) m_kernel->setLogId(newId); } -bool WlanMausInterface::setOnline(bool& value) +bool WlanMausInterface::setOnline(bool& value, bool simulation) { + if(simulation) + { + Log::log(*this, LogMessage::N2001_SIMULATION_NOT_SUPPORTED); + return false; + } + if(!m_kernel && value) { try diff --git a/server/src/hardware/interface/wlanmausinterface.hpp b/server/src/hardware/interface/wlanmausinterface.hpp index 08c2b217..76c3dbba 100644 --- a/server/src/hardware/interface/wlanmausinterface.hpp +++ b/server/src/hardware/interface/wlanmausinterface.hpp @@ -44,7 +44,7 @@ class WlanMausInterface : public Interface protected: void worldEvent(WorldState state, WorldEvent event) final; void idChanged(const std::string& newId) final; - bool setOnline(bool& value) final; + bool setOnline(bool& value, bool simulation) final; public: ObjectProperty z21; diff --git a/server/src/hardware/interface/xpressnetinterface.cpp b/server/src/hardware/interface/xpressnetinterface.cpp index 4afbc352..1739e2f7 100644 --- a/server/src/hardware/interface/xpressnetinterface.cpp +++ b/server/src/hardware/interface/xpressnetinterface.cpp @@ -211,8 +211,14 @@ bool XpressNetInterface::setOutputValue(uint32_t address, bool value) m_kernel->setOutput(static_cast(address), value); } -bool XpressNetInterface::setOnline(bool& value) +bool XpressNetInterface::setOnline(bool& value, bool simulation) { + if(simulation) + { + Log::log(*this, LogMessage::N2001_SIMULATION_NOT_SUPPORTED); + return false; + } + if(!m_kernel && value) { try diff --git a/server/src/hardware/interface/xpressnetinterface.hpp b/server/src/hardware/interface/xpressnetinterface.hpp index 77eba9e7..48790e4e 100644 --- a/server/src/hardware/interface/xpressnetinterface.hpp +++ b/server/src/hardware/interface/xpressnetinterface.hpp @@ -64,7 +64,7 @@ class XpressNetInterface final void updateVisible(); protected: - bool setOnline(bool& value) final; + bool setOnline(bool& value, bool simulation) final; public: Property type; diff --git a/server/src/hardware/interface/z21interface.cpp b/server/src/hardware/interface/z21interface.cpp index 5c817e73..688b8ff9 100644 --- a/server/src/hardware/interface/z21interface.cpp +++ b/server/src/hardware/interface/z21interface.cpp @@ -91,8 +91,14 @@ void Z21Interface::decoderChanged(const Decoder& decoder, DecoderChangeFlags cha m_kernel->decoderChanged(decoder, changes, functionNumber); } -bool Z21Interface::setOnline(bool& value) +bool Z21Interface::setOnline(bool& value, bool simulation) { + if(simulation) + { + Log::log(*this, LogMessage::N2001_SIMULATION_NOT_SUPPORTED); + return false; + } + if(!m_kernel && value) { try diff --git a/server/src/hardware/interface/z21interface.hpp b/server/src/hardware/interface/z21interface.hpp index e4607b4b..509e68a3 100644 --- a/server/src/hardware/interface/z21interface.hpp +++ b/server/src/hardware/interface/z21interface.hpp @@ -54,7 +54,7 @@ class Z21Interface final void updateVisible(); protected: - bool setOnline(bool& value) final; + bool setOnline(bool& value, bool simulation) final; public: Property hostname; diff --git a/server/src/world/world.cpp b/server/src/world/world.cpp index b31e2092..abb40ec6 100644 --- a/server/src/world/world.cpp +++ b/server/src/world/world.cpp @@ -103,76 +103,47 @@ World::World(Private /*unused*/) : offline{*this, "offline", [this]() { - Log::log(*this, LogMessage::N1013_COMMUNICATION_DISABLED); - state.setValueInternal(state.value() - WorldState::Online); event(WorldEvent::Offline); }}, online{*this, "online", [this]() { - Log::log(*this, LogMessage::N1012_COMMUNICATION_ENABLED); - state.setValueInternal(state.value() + WorldState::Online); event(WorldEvent::Online); }}, powerOff{*this, "power_off", MethodFlags::ScriptCallable, [this]() { - Log::log(*this, LogMessage::N1015_POWER_OFF); - state.setValueInternal(state.value() - WorldState::PowerOn); event(WorldEvent::PowerOff); }}, powerOn{*this, "power_on", [this]() { - Log::log(*this, LogMessage::N1014_POWER_ON); - state.setValueInternal(state.value() + WorldState::PowerOn); event(WorldEvent::PowerOn); }}, run{*this, "run", [this]() { - Log::log(*this, LogMessage::N1016_RUNNING); - state.setValueInternal(state.value() + WorldState::Run); event(WorldEvent::Run); }}, stop{*this, "stop", MethodFlags::ScriptCallable, [this]() { - Log::log(*this, LogMessage::N1017_STOPPED); - state.setValueInternal(state.value() - WorldState::Run); event(WorldEvent::Stop); }}, mute{this, "mute", false, PropertyFlags::ReadWrite | PropertyFlags::NoStore, [this](bool value) { - if(value) - { - Log::log(*this, LogMessage::N1018_MUTE_ENABLED); - state.setValueInternal(state.value() + WorldState::Mute); - event(WorldEvent::Mute); - } - else - { - Log::log(*this, LogMessage::N1019_MUTE_DISABLED); - state.setValueInternal(state.value() - WorldState::Mute); - event(WorldEvent::Unmute); - } + event(value ? WorldEvent::Mute : WorldEvent::Unmute); }}, noSmoke{this, "no_smoke", false, PropertyFlags::ReadWrite | PropertyFlags::NoStore, [this](bool value) { - if(value) - { - Log::log(*this, LogMessage::N1021_SMOKE_DISABLED); - state.setValueInternal(state.value() + WorldState::NoSmoke); - event(WorldEvent::NoSmoke); - } - else - { - Log::log(*this, LogMessage::N1020_SMOKE_ENABLED); - state.setValueInternal(state.value() - WorldState::NoSmoke); - event(WorldEvent::Smoke); - } + event(value ? WorldEvent::NoSmoke : WorldEvent::Smoke); + }}, + simulation{this, "simulation", false, PropertyFlags::ReadWrite | PropertyFlags::NoStore, + [this](bool value) + { + event(value ? WorldEvent::SimulationEnabled : WorldEvent::SimulationDisabled); }}, save{*this, "save", MethodFlags::NoScript, [this]() @@ -294,11 +265,16 @@ World::World(Private /*unused*/) : m_interfaceItems.add(mute); Attributes::addObjectEditor(noSmoke, false); m_interfaceItems.add(noSmoke); + Attributes::addEnabled(simulation, false); + Attributes::addObjectEditor(simulation, false); + m_interfaceItems.add(simulation); Attributes::addObjectEditor(save, false); m_interfaceItems.add(save); m_interfaceItems.add(onEvent); + + updateEnabled(); } std::string World::getUniqueId(std::string_view prefix) const @@ -384,12 +360,95 @@ void World::worldEvent(WorldState worldState, WorldEvent worldEvent) void World::event(const WorldEvent value) { + // Update state: + switch(value) + { + case WorldEvent::EditDisabled: + state.setValueInternal(state.value() - WorldState::Edit); + break; + + case WorldEvent::EditEnabled: + state.setValueInternal(state.value() + WorldState::Edit); + break; + + case WorldEvent::Offline: + Log::log(*this, LogMessage::N1013_COMMUNICATION_DISABLED); + state.setValueInternal(state.value() - WorldState::Online); + break; + + case WorldEvent::Online: + Log::log(*this, LogMessage::N1012_COMMUNICATION_ENABLED); + state.setValueInternal(state.value() + WorldState::Online); + break; + + case WorldEvent::PowerOff: + Log::log(*this, LogMessage::N1015_POWER_OFF); + state.setValueInternal(state.value() - WorldState::PowerOn); + break; + + case WorldEvent::PowerOn: + Log::log(*this, LogMessage::N1014_POWER_ON); + state.setValueInternal(state.value() + WorldState::PowerOn); + break; + + case WorldEvent::Stop: + Log::log(*this, LogMessage::N1017_STOPPED); + state.setValueInternal(state.value() - WorldState::Run); + break; + + case WorldEvent::Run: + Log::log(*this, LogMessage::N1016_RUNNING); + state.setValueInternal(state.value() + WorldState::Run); + break; + + case WorldEvent::Unmute: + Log::log(*this, LogMessage::N1019_MUTE_DISABLED); + state.setValueInternal(state.value() - WorldState::Mute); + break; + + case WorldEvent::Mute: + Log::log(*this, LogMessage::N1018_MUTE_ENABLED); + state.setValueInternal(state.value() + WorldState::Mute); + break; + + case WorldEvent::NoSmoke: + Log::log(*this, LogMessage::N1021_SMOKE_DISABLED); + state.setValueInternal(state.value() + WorldState::NoSmoke); + break; + + case WorldEvent::Smoke: + Log::log(*this, LogMessage::N1020_SMOKE_ENABLED); + state.setValueInternal(state.value() - WorldState::NoSmoke); + break; + + case WorldEvent::SimulationDisabled: + Log::log(*this, LogMessage::N1023_SIMULATION_DISABLED); + state.setValueInternal(state.value() - WorldState::Simulation); + break; + + case WorldEvent::SimulationEnabled: + Log::log(*this, LogMessage::N1024_SIMULATION_ENABLED); + state.setValueInternal(state.value() + WorldState::Simulation); + break; + } + + updateEnabled(); + const WorldState worldState = state; worldEvent(worldState, value); for(auto& it : m_objects) it.second.lock()->worldEvent(worldState, value); } +void World::updateEnabled() +{ + const bool isOnline = contains(state.value(), WorldState::Online); + const bool isPoweredOn = contains(state.value(), WorldState::PowerOn); + const bool isRunning = contains(state.value(), WorldState::Run); + + Attributes::setEnabled(simulation, !isOnline && !isPoweredOn && !isRunning); +} + void World::updateScaleRatio() { if(scale != WorldScale::Custom) diff --git a/server/src/world/world.hpp b/server/src/world/world.hpp index 545c1f73..2c95b2dc 100644 --- a/server/src/world/world.hpp +++ b/server/src/world/world.hpp @@ -61,6 +61,7 @@ class World : public Object private: struct Private {}; + void updateEnabled(); void updateScaleRatio(); protected: @@ -113,6 +114,7 @@ class World : public Object Method stop; Property mute; Property noSmoke; + Property simulation; Method save; diff --git a/shared/src/traintastic/enum/logmessage.hpp b/shared/src/traintastic/enum/logmessage.hpp index 2951d987..9bd8319c 100644 --- a/shared/src/traintastic/enum/logmessage.hpp +++ b/shared/src/traintastic/enum/logmessage.hpp @@ -3,7 +3,7 @@ * * This file is part of the traintastic source code. * - * Copyright (C) 2021 Reinder Feenstra + * Copyright (C) 2021-2022 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -97,6 +97,9 @@ enum class LogMessage : uint32_t N1020_SMOKE_ENABLED = LogMessageOffset::notice + 1020, N1021_SMOKE_DISABLED = LogMessageOffset::notice + 1021, N1022_SAVED_WORLD_X = LogMessageOffset::notice + 1022, + N1023_SIMULATION_DISABLED = LogMessageOffset::notice + 1022, + N1024_SIMULATION_ENABLED = LogMessageOffset::notice + 1023, + N2001_SIMULATION_NOT_SUPPORTED = LogMessageOffset::notice + 2001, N9999_X = LogMessageOffset::notice + 9999, // Warning: diff --git a/shared/src/traintastic/enum/worldevent.hpp b/shared/src/traintastic/enum/worldevent.hpp index 7ad9049e..632bf434 100644 --- a/shared/src/traintastic/enum/worldevent.hpp +++ b/shared/src/traintastic/enum/worldevent.hpp @@ -40,6 +40,8 @@ enum class WorldEvent : uint64_t Mute = 9, NoSmoke = 10, Smoke = 11, + SimulationDisabled = 12, + SimulationEnabled = 13, }; template<> diff --git a/shared/src/traintastic/set/worldstate.hpp b/shared/src/traintastic/set/worldstate.hpp index 2014108f..80488a4f 100644 --- a/shared/src/traintastic/set/worldstate.hpp +++ b/shared/src/traintastic/set/worldstate.hpp @@ -34,6 +34,7 @@ enum class WorldState : uint32_t Run = 1 << 3, Mute = 1 << 4, NoSmoke = 1 << 5, + Simulation = 1 << 6, }; template<> diff --git a/shared/translations/en-us.txt b/shared/translations/en-us.txt index a5bd3355..b67868ba 100644 --- a/shared/translations/en-us.txt +++ b/shared/translations/en-us.txt @@ -262,6 +262,9 @@ message:N1019=Mute: disabled message:N1020=Smoke: enabled message:N1021=Smoke: disabled message:N1022=Saved world: %1 +message:N1023=Simulation: enabled +message:N1024=Simulation: disabled +message:N2001=Simulation not supported message:N9999=%1 message:W1001=Discovery disabled, only allowed on port %1 message:W1002=Setting %1 doesnt exist @@ -446,6 +449,7 @@ world:rail_vehicles=Rail vehicles world:run=Run world:scale=Scale world:scale_ratio=Scale ratio +world:simulation=Simulation world:stop=Stop world:trains=Trains world:uuid=UUID From 2e29ff6e77244b169d929c4b64ac055006f27d1f Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Sat, 15 Jan 2022 08:51:14 +0100 Subject: [PATCH 02/12] loconet: added basic slot simulation --- .../hardware/interface/loconetinterface.cpp | 40 ++-- .../loconet/iohandler/simulationiohandler.cpp | 200 ++++++++++++++++++ .../loconet/iohandler/simulationiohandler.hpp | 50 +++++ .../hardware/protocol/loconet/messages.hpp | 78 +++++-- 4 files changed, 332 insertions(+), 36 deletions(-) create mode 100644 server/src/hardware/protocol/loconet/iohandler/simulationiohandler.cpp create mode 100644 server/src/hardware/protocol/loconet/iohandler/simulationiohandler.hpp diff --git a/server/src/hardware/interface/loconetinterface.cpp b/server/src/hardware/interface/loconetinterface.cpp index 2b2de295..64c5c961 100644 --- a/server/src/hardware/interface/loconetinterface.cpp +++ b/server/src/hardware/interface/loconetinterface.cpp @@ -24,6 +24,7 @@ #include "../input/list/inputlisttablemodel.hpp" #include "../output/list/outputlisttablemodel.hpp" #include "../protocol/loconet/iohandler/serialiohandler.hpp" +#include "../protocol/loconet/iohandler/simulationiohandler.hpp" #include "../protocol/loconet/iohandler/tcpbinaryiohandler.hpp" #include "../protocol/loconet/iohandler/lbserveriohandler.hpp" #include "../protocol/loconet/iohandler/z21iohandler.hpp" @@ -171,27 +172,34 @@ bool LocoNetInterface::setOnline(bool& value, bool simulation) { try { - switch(type) + if(simulation) { - case LocoNetInterfaceType::Serial: - m_kernel = LocoNet::Kernel::create(loconet->config(), device.value(), baudrate.value(), flowControl.value()); - break; + m_kernel = LocoNet::Kernel::create(loconet->config()); + } + else + { + switch(type) + { + case LocoNetInterfaceType::Serial: + m_kernel = LocoNet::Kernel::create(loconet->config(), device.value(), baudrate.value(), flowControl.value()); + break; - case LocoNetInterfaceType::TCPBinary: - m_kernel = LocoNet::Kernel::create(loconet->config(), hostname.value(), port.value()); - break; + case LocoNetInterfaceType::TCPBinary: + m_kernel = LocoNet::Kernel::create(loconet->config(), hostname.value(), port.value()); + break; - case LocoNetInterfaceType::LBServer: - m_kernel = LocoNet::Kernel::create(loconet->config(), hostname.value(), port.value()); - break; + case LocoNetInterfaceType::LBServer: + m_kernel = LocoNet::Kernel::create(loconet->config(), hostname.value(), port.value()); + break; - case LocoNetInterfaceType::Z21: - m_kernel = LocoNet::Kernel::create(loconet->config(), hostname.value()); - break; + case LocoNetInterfaceType::Z21: + m_kernel = LocoNet::Kernel::create(loconet->config(), hostname.value()); + break; - default: - assert(false); - return false; + default: + assert(false); + return false; + } } status.setValueInternal(InterfaceStatus::Initializing); diff --git a/server/src/hardware/protocol/loconet/iohandler/simulationiohandler.cpp b/server/src/hardware/protocol/loconet/iohandler/simulationiohandler.cpp new file mode 100644 index 00000000..7a84ea0d --- /dev/null +++ b/server/src/hardware/protocol/loconet/iohandler/simulationiohandler.cpp @@ -0,0 +1,200 @@ +/** + * server/src/hardware/protocol/loconet/iohandler/simulationiohandler.cpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "simulationiohandler.hpp" +#include "../kernel.hpp" +#include "../messages.hpp" + +namespace LocoNet { + +static std::shared_ptr copy(const Message& message) +{ + auto* bytes = new std::byte[message.size()]; + std::memcpy(bytes, &message, message.size()); + return std::shared_ptr{bytes}; +} + +static void updateActive(SlotReadData& locoSlot) +{ + locoSlot.setActive( + (locoSlot.spd != SPEED_STOP && locoSlot.spd != SPEED_ESTOP) || + (locoSlot.dirf & (SL_F0 | SL_F4 | SL_F3 | SL_F2 | SL_F1)) != 0 || + (locoSlot.snd & (SL_F8 | SL_F7 | SL_F6 | SL_F5)) != 0); +} + +SimulationIOHandler::SimulationIOHandler(Kernel& kernel) + : IOHandler(kernel) +{ + for(uint8_t slot = SLOT_LOCO_MIN; slot <= SLOT_LOCO_MAX; slot++) + m_locoSlots[slot - SLOT_LOCO_MIN].slot = slot; +} + +bool SimulationIOHandler::send(const Message& message) +{ + reply(message); // echo message back + + switch(message.opCode) + { + case OPC_UNLINK_SLOTS: + break; // unimplemented + + case OPC_LINK_SLOTS: + break; // unimplemented + + case OPC_MOVE_SLOTS: + break; // unimplemented + + case OPC_RQ_SL_DATA: + { + const auto& requestSlotData = static_cast(message); + if(isLocoSlot(requestSlotData.slot)) + { + auto& locoSlot = m_locoSlots[requestSlotData.slot - SLOT_LOCO_MIN]; + updateChecksum(locoSlot); + reply(locoSlot); + } + break; + } + case OPC_SW_STATE: + break; // unimplemented + + case OPC_SW_ACK: + break; // unimplemented + + case OPC_LOCO_ADR: + { + const auto& locoAdr = static_cast(message); + + // find slot for address + { + auto* it = std::find_if(m_locoSlots.begin(), m_locoSlots.end(), + [address=locoAdr.address()](const auto& locoSlot) + { + return locoSlot.address() == address; + }); + + if(it != m_locoSlots.end()) + { + updateChecksum(*it); + reply(*it); + return true; + } + } + + // find a free slot + { + auto* it = std::find_if(m_locoSlots.begin(), m_locoSlots.end(), + [](const auto& locoSlot) + { + return locoSlot.isFree(); + }); + + if(it != m_locoSlots.end()) + { + it->setBusy(true); + it->setAddress(locoAdr.address()); + updateChecksum(*it); + reply(*it); + return true; + } + } + + // no free slot + reply(LongAck(message.opCode, 0)); + + break; + } + case OPC_IMM_PACKET: + break; // unimplemented + + case OPC_WR_SL_DATA: + break; // unimplemented + + // no response: + case OPC_LOCO_SPD: + { + const auto& locoSpd = static_cast(message); + if(isLocoSlot(locoSpd.slot)) + { + auto& locoSlot = m_locoSlots[locoSpd.slot - SLOT_LOCO_MIN]; + locoSlot.spd = locoSpd.speed; + updateActive(locoSlot); + } + break; + } + case OPC_LOCO_DIRF: + { + const auto& locoDirF = static_cast(message); + if(isLocoSlot(locoDirF.slot)) + { + auto& locoSlot = m_locoSlots[locoDirF.slot - SLOT_LOCO_MIN]; + locoSlot.dirf = locoDirF.dirf; + updateActive(locoSlot); + } + break; + } + case OPC_LOCO_SND: + { + const auto& locoSnd = static_cast(message); + if(isLocoSlot(locoSnd.slot)) + { + auto& locoSlot = m_locoSlots[locoSnd.slot - SLOT_LOCO_MIN]; + locoSlot.snd = locoSnd.snd; + updateActive(locoSlot); + } + break; + } + case OPC_BUSY: + case OPC_GPOFF: + case OPC_GPON: + case OPC_IDLE: + case OPC_LOCO_F9F12: + case OPC_SW_REQ: + case OPC_SW_REP: + case OPC_INPUT_REP: + case OPC_LONG_ACK: + case OPC_SLOT_STAT1: + case OPC_CONSIST_FUNC: + case OPC_MULTI_SENSE: + case OPC_D4: + case OPC_MULTI_SENSE_LONG: + case OPC_PEER_XFER: + case OPC_SL_RD_DATA: + assert(!hasResponse(message)); // no response + break; + } + + return true; +} + +void SimulationIOHandler::reply(const Message& message) +{ + // post the reply, so it has some delay + //! \todo better delay simulation? at least loconet message transfer time? + m_kernel.ioContext().post( + [this, data=copy(message)]() + { + m_kernel.receive(*reinterpret_cast(data.get())); + }); +} + +} diff --git a/server/src/hardware/protocol/loconet/iohandler/simulationiohandler.hpp b/server/src/hardware/protocol/loconet/iohandler/simulationiohandler.hpp new file mode 100644 index 00000000..01dd090e --- /dev/null +++ b/server/src/hardware/protocol/loconet/iohandler/simulationiohandler.hpp @@ -0,0 +1,50 @@ +/** + * server/src/hardware/protocol/loconet/iohandler/simulationiohandler.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_LOCONET_IOHANDLER_SIMULATIONIOHANDLER_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_LOCONET_IOHANDLER_SIMULATIONIOHANDLER_HPP + +#include "iohandler.hpp" +#include "../messages.hpp" + +namespace LocoNet { + +class SimulationIOHandler final : public IOHandler +{ + private: + std::array m_locoSlots; + + void reply(const Message& message); + + public: + SimulationIOHandler(Kernel& kernel); + + void start() final {} + void stop() final {} + + bool send(const Message& message) final; +}; + +} + +#endif + diff --git a/server/src/hardware/protocol/loconet/messages.hpp b/server/src/hardware/protocol/loconet/messages.hpp index a914e37c..3d6538e8 100644 --- a/server/src/hardware/protocol/loconet/messages.hpp +++ b/server/src/hardware/protocol/loconet/messages.hpp @@ -219,6 +219,11 @@ struct LocoAdr : Message { checksum = calcChecksum(*this); } + + uint16_t address() const + { + return (static_cast(addressHigh) << 7) | addressLow; + } }; static_assert(sizeof(LocoAdr) == 4); @@ -544,21 +549,21 @@ static_assert(sizeof(InputRep) == 4); struct LongAck : Message { - uint8_t lpoc; + uint8_t lopc; uint8_t ack1; uint8_t checksum; - LongAck() : + LongAck(OpCode _lopc = static_cast(0), uint8_t _ack1 = 0) : Message{OPC_LONG_ACK}, - lpoc{0}, - ack1{0}, - checksum{0} + lopc{static_cast(_lopc & 0x7F)}, + ack1{_ack1}, + checksum{calcChecksum(*this)} { } OpCode respondingOpCode() const { - return static_cast(0x80 | lpoc); + return static_cast(0x80 | lopc); } }; static_assert(sizeof(LongAck) == 4); @@ -1065,48 +1070,81 @@ struct MultiSenseLongTransponder : MultiSenseLong }; static_assert(sizeof(MultiSenseLongTransponder) == 9); -// OPC_SL_RD_DATA [E7 0E 1F 13 6F 01 30 07 08 19 00 00 00 52] struct SlotReadDataBase : Message { uint8_t len; uint8_t slot; - SlotReadDataBase() : - Message(OPC_SL_RD_DATA), - len{14} + SlotReadDataBase(uint8_t _slot = 0) + : Message(OPC_SL_RD_DATA) + , len{14} + , slot{_slot} { } }; struct SlotReadData : SlotReadDataBase { - uint8_t stat; - uint8_t adr; - uint8_t spd; - uint8_t dirf; - uint8_t trk; - uint8_t ss2; - uint8_t adr2; - uint8_t snd; - uint8_t id1; - uint8_t id2; + uint8_t stat = 0; + uint8_t adr = 0; + uint8_t spd = 0; + uint8_t dirf = 0; + uint8_t trk = 0; + uint8_t ss2 = 0; + uint8_t adr2 = 0; + uint8_t snd = 0; + uint8_t id1 = 0; + uint8_t id2 = 0; uint8_t checksum; + SlotReadData(uint8_t _slot = 0) + : SlotReadDataBase(_slot) + , checksum{calcChecksum(*this)} + { + } + bool isBusy() const { return stat & SL_BUSY; } + void setBusy(bool value) + { + if(value) + stat |= SL_BUSY; + else + stat &= ~SL_BUSY; + } + bool isActive() const { return stat & SL_ACTIVE; } + void setActive(bool value) + { + if(value) + stat |= SL_ACTIVE; + else + stat &= ~SL_ACTIVE; + } + + bool isFree() const + { + return !isBusy() && !isActive(); + } + uint16_t address() const { return (static_cast(adr2) << 7) | adr; } + void setAddress(uint16_t value) + { + adr = value & 0x7F; + adr2 = (value >> 7) & 0x7F; + } + bool isEmergencyStop() const { return spd == 0x01; From 125883e56f9abee770a9f3fd6f23c41e62306dcc Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Sat, 15 Jan 2022 11:47:50 +0100 Subject: [PATCH 03/12] fix: added missing include --- .../hardware/protocol/loconet/iohandler/simulationiohandler.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/hardware/protocol/loconet/iohandler/simulationiohandler.hpp b/server/src/hardware/protocol/loconet/iohandler/simulationiohandler.hpp index 01dd090e..44952d6d 100644 --- a/server/src/hardware/protocol/loconet/iohandler/simulationiohandler.hpp +++ b/server/src/hardware/protocol/loconet/iohandler/simulationiohandler.hpp @@ -24,6 +24,7 @@ #define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_LOCONET_IOHANDLER_SIMULATIONIOHANDLER_HPP #include "iohandler.hpp" +#include #include "../messages.hpp" namespace LocoNet { From 63fdc43cf4f5758488e701c4ef7e6a1e478717c9 Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Sat, 15 Jan 2022 12:02:46 +0100 Subject: [PATCH 04/12] loconet: implemented handling free slot in internal cache --- .../src/hardware/protocol/loconet/kernel.cpp | 19 +++++++++++++++++-- .../src/hardware/protocol/loconet/kernel.hpp | 3 ++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/server/src/hardware/protocol/loconet/kernel.cpp b/server/src/hardware/protocol/loconet/kernel.cpp index 2e111cc8..088948fc 100644 --- a/server/src/hardware/protocol/loconet/kernel.cpp +++ b/server/src/hardware/protocol/loconet/kernel.cpp @@ -3,7 +3,7 @@ * * This file is part of the traintastic source code. * - * Copyright (C) 2019-2021 Reinder Feenstra + * Copyright (C) 2019-2022 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -399,7 +399,13 @@ void Kernel::receive(const Message& message) const uint8_t slot = *(reinterpret_cast(&message) + 2); if(m_decoderController && isLocoSlot(slot)) { - const SlotReadData& slotReadData = static_cast(message); + const auto& slotReadData = static_cast(message); + if(slotReadData.isFree()) + { + clearLocoSlot(slotReadData.slot); + break; + } + LocoSlot* locoSlot = getLocoSlot(slotReadData.slot, false); assert(locoSlot); @@ -781,6 +787,15 @@ Kernel::LocoSlot* Kernel::getLocoSlot(uint8_t slot, bool sendSlotDataRequestIfNe return &it->second; } +void Kernel::clearLocoSlot(uint8_t slot) +{ + if(auto it = m_slots.find(slot); it != m_slots.end()) + m_slots.erase(it); + + if(auto it = std::find_if(m_addressToSlot.begin(), m_addressToSlot.end(), [slot](const auto& item){ return item.second == slot; }); it != m_addressToSlot.end()) + m_addressToSlot.erase(it); +} + std::shared_ptr Kernel::getDecoder(uint16_t address) { return m_decoderController->getDecoder(DecoderProtocol::DCC, address, DCC::isLongAddress(address), true); diff --git a/server/src/hardware/protocol/loconet/kernel.hpp b/server/src/hardware/protocol/loconet/kernel.hpp index b5b8a5e2..7eff96fc 100644 --- a/server/src/hardware/protocol/loconet/kernel.hpp +++ b/server/src/hardware/protocol/loconet/kernel.hpp @@ -3,7 +3,7 @@ * * This file is part of the traintastic source code. * - * Copyright (C) 2019-2021 Reinder Feenstra + * Copyright (C) 2019-2022 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -152,6 +152,7 @@ class Kernel Kernel(const Config& config); LocoSlot* getLocoSlot(uint8_t slot, bool sendSlotDataRequestIfNew = true); + void clearLocoSlot(uint8_t slot); std::shared_ptr getDecoder(uint16_t address); From 17375484cc92bc896f2f3709c9e9ac7344a557f5 Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Sat, 15 Jan 2022 20:03:12 +0100 Subject: [PATCH 05/12] fix: auto* -> auto (doesn't work with clang/msvc) --- .../protocol/loconet/iohandler/simulationiohandler.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/hardware/protocol/loconet/iohandler/simulationiohandler.cpp b/server/src/hardware/protocol/loconet/iohandler/simulationiohandler.cpp index 7a84ea0d..908632c9 100644 --- a/server/src/hardware/protocol/loconet/iohandler/simulationiohandler.cpp +++ b/server/src/hardware/protocol/loconet/iohandler/simulationiohandler.cpp @@ -86,7 +86,7 @@ bool SimulationIOHandler::send(const Message& message) // find slot for address { - auto* it = std::find_if(m_locoSlots.begin(), m_locoSlots.end(), + auto it = std::find_if(m_locoSlots.begin(), m_locoSlots.end(), // NOLINT [readability-qualified-auto] [address=locoAdr.address()](const auto& locoSlot) { return locoSlot.address() == address; @@ -102,7 +102,7 @@ bool SimulationIOHandler::send(const Message& message) // find a free slot { - auto* it = std::find_if(m_locoSlots.begin(), m_locoSlots.end(), + auto it = std::find_if(m_locoSlots.begin(), m_locoSlots.end(), // NOLINT [readability-qualified-auto] [](const auto& locoSlot) { return locoSlot.isFree(); From fa463f30239af9a76e99b9eab42e140d762fc32e Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Sun, 30 Jan 2022 15:16:48 +0100 Subject: [PATCH 06/12] xpressnet: fix: corrected operation/emergency stop commands --- .../hardware/interface/xpressnetinterface.cpp | 16 ++++----- .../hardware/protocol/xpressnet/kernel.cpp | 12 +++---- .../hardware/protocol/xpressnet/kernel.hpp | 6 ++-- .../hardware/protocol/xpressnet/messages.hpp | 35 +++++++++++++++++++ 4 files changed, 52 insertions(+), 17 deletions(-) diff --git a/server/src/hardware/interface/xpressnetinterface.cpp b/server/src/hardware/interface/xpressnetinterface.cpp index 1739e2f7..21bcaf3b 100644 --- a/server/src/hardware/interface/xpressnetinterface.cpp +++ b/server/src/hardware/interface/xpressnetinterface.cpp @@ -307,11 +307,11 @@ bool XpressNetInterface::setOnline(bool& value, bool simulation) if(auto w = m_world.lock()) { if(!contains(w->state.value(), WorldState::PowerOn)) - m_kernel->trackPowerOff(); + m_kernel->stopOperations(); else if(!contains(w->state.value(), WorldState::Run)) - m_kernel->emergencyStop(); + m_kernel->stopAllLocomotives(); else - m_kernel->normalOperationsResumed(); + m_kernel->resumeOperations(); } Attributes::setEnabled({type, serialInterfaceType, device, baudrate, flowControl, hostname, port, s88StartAddress, s88ModuleCount}, false); @@ -395,22 +395,22 @@ void XpressNetInterface::worldEvent(WorldState state, WorldEvent event) switch(event) { case WorldEvent::PowerOff: - m_kernel->trackPowerOff(); + m_kernel->stopOperations(); break; case WorldEvent::PowerOn: - m_kernel->normalOperationsResumed(); + m_kernel->resumeOperations(); if(!contains(state, WorldState::Run)) - m_kernel->emergencyStop(); + m_kernel->stopAllLocomotives(); break; case WorldEvent::Stop: - m_kernel->emergencyStop(); + m_kernel->stopAllLocomotives(); break; case WorldEvent::Run: if(contains(state, WorldState::PowerOn)) - m_kernel->normalOperationsResumed(); + m_kernel->resumeOperations(); break; default: diff --git a/server/src/hardware/protocol/xpressnet/kernel.cpp b/server/src/hardware/protocol/xpressnet/kernel.cpp index ec34c8ac..9eff599d 100644 --- a/server/src/hardware/protocol/xpressnet/kernel.cpp +++ b/server/src/hardware/protocol/xpressnet/kernel.cpp @@ -219,33 +219,33 @@ void Kernel::receive(const Message& message) } } -void Kernel::normalOperationsResumed() +void Kernel::resumeOperations() { m_ioContext.post( [this]() { if(m_trackPowerOn != TriState::True || m_emergencyStop != TriState::False) - send(NormalOperationResumed()); + send(ResumeOperationsRequest()); }); } -void Kernel::trackPowerOff() +void Kernel::stopOperations() { m_ioContext.post( [this]() { if(m_trackPowerOn != TriState::False) - send(TrackPowerOff()); + send(StopOperationsRequest()); }); } -void Kernel::emergencyStop() +void Kernel::stopAllLocomotives() { m_ioContext.post( [this]() { if(m_emergencyStop != TriState::True) - send(EmergencyStop()); + send(StopAllLocomotivesRequest()); }); } diff --git a/server/src/hardware/protocol/xpressnet/kernel.hpp b/server/src/hardware/protocol/xpressnet/kernel.hpp index 09bbace9..e7e229c4 100644 --- a/server/src/hardware/protocol/xpressnet/kernel.hpp +++ b/server/src/hardware/protocol/xpressnet/kernel.hpp @@ -259,19 +259,19 @@ class Kernel * * */ - void normalOperationsResumed(); + void resumeOperations(); /** * * */ - void trackPowerOff(); + void stopOperations(); /** * * */ - void emergencyStop(); + void stopAllLocomotives(); /** * diff --git a/server/src/hardware/protocol/xpressnet/messages.hpp b/server/src/hardware/protocol/xpressnet/messages.hpp index 6a568ae3..9c647bd4 100644 --- a/server/src/hardware/protocol/xpressnet/messages.hpp +++ b/server/src/hardware/protocol/xpressnet/messages.hpp @@ -154,6 +154,41 @@ struct FeedbackBroadcast : Message } }; +struct ResumeOperationsRequest : Message +{ + uint8_t db1 = 0x81; + uint8_t checksum = 0xA0; + + ResumeOperationsRequest() + { + header = 0x21; + } +}; +static_assert(sizeof(ResumeOperationsRequest) == 3); + +struct StopOperationsRequest : Message +{ + uint8_t db1 = 0x80; + uint8_t checksum = 0xA1; + + StopOperationsRequest() + { + header = 0x21; + } +}; +static_assert(sizeof(StopOperationsRequest) == 3); + +struct StopAllLocomotivesRequest : Message +{ + uint8_t checksum = 0x80; + + StopAllLocomotivesRequest() + { + header = 0x80; + } +}; +static_assert(sizeof(StopAllLocomotivesRequest) == 2); + struct EmergencyStopLocomotive : Message { uint8_t addressHigh; From 1673e62ea739b5a071b7ce8b54731106afd878d5 Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Sun, 30 Jan 2022 15:20:34 +0100 Subject: [PATCH 07/12] xpressnet: added basic simulation support --- .../hardware/interface/xpressnetinterface.cpp | 56 ++++---- .../xpressnet/iohandler/hardwareiohandler.cpp | 130 ++++++++++++++++++ .../xpressnet/iohandler/hardwareiohandler.hpp | 55 ++++++++ .../xpressnet/iohandler/iohandler.cpp | 100 +------------- .../xpressnet/iohandler/iohandler.hpp | 15 +- .../xpressnet/iohandler/serialiohandler.cpp | 4 +- .../xpressnet/iohandler/serialiohandler.hpp | 6 +- .../iohandler/simulationiohandler.cpp | 78 +++++++++++ .../iohandler/simulationiohandler.hpp | 49 +++++++ .../xpressnet/iohandler/tcpiohandler.cpp | 4 +- .../xpressnet/iohandler/tcpiohandler.hpp | 6 +- 11 files changed, 354 insertions(+), 149 deletions(-) create mode 100644 server/src/hardware/protocol/xpressnet/iohandler/hardwareiohandler.cpp create mode 100644 server/src/hardware/protocol/xpressnet/iohandler/hardwareiohandler.hpp create mode 100644 server/src/hardware/protocol/xpressnet/iohandler/simulationiohandler.cpp create mode 100644 server/src/hardware/protocol/xpressnet/iohandler/simulationiohandler.hpp diff --git a/server/src/hardware/interface/xpressnetinterface.cpp b/server/src/hardware/interface/xpressnetinterface.cpp index 21bcaf3b..3e718d6b 100644 --- a/server/src/hardware/interface/xpressnetinterface.cpp +++ b/server/src/hardware/interface/xpressnetinterface.cpp @@ -25,6 +25,7 @@ #include "../output/list/outputlisttablemodel.hpp" #include "../protocol/xpressnet/messages.hpp" #include "../protocol/xpressnet/iohandler/serialiohandler.hpp" +#include "../protocol/xpressnet/iohandler/simulationiohandler.hpp" #include "../protocol/xpressnet/iohandler/liusbiohandler.hpp" #include "../protocol/xpressnet/iohandler/rosofts88xpressnetliiohandler.hpp" #include "../protocol/xpressnet/iohandler/tcpiohandler.hpp" @@ -213,41 +214,42 @@ bool XpressNetInterface::setOutputValue(uint32_t address, bool value) bool XpressNetInterface::setOnline(bool& value, bool simulation) { - if(simulation) - { - Log::log(*this, LogMessage::N2001_SIMULATION_NOT_SUPPORTED); - return false; - } - if(!m_kernel && value) { try { - switch(type) + if(simulation) { - case XpressNetInterfaceType::Serial: - switch(serialInterfaceType) - { - case XpressNetSerialInterfaceType::LenzLI100: - case XpressNetSerialInterfaceType::LenzLI100F: - case XpressNetSerialInterfaceType::LenzLI101F: - m_kernel = XpressNet::Kernel::create(xpressnet->config(), device.value(), baudrate.value(), flowControl.value()); - break; + m_kernel = XpressNet::Kernel::create(xpressnet->config()); + } + else + { + switch(type) + { + case XpressNetInterfaceType::Serial: + switch(serialInterfaceType) + { + case XpressNetSerialInterfaceType::LenzLI100: + case XpressNetSerialInterfaceType::LenzLI100F: + case XpressNetSerialInterfaceType::LenzLI101F: + m_kernel = XpressNet::Kernel::create(xpressnet->config(), device.value(), baudrate.value(), flowControl.value()); + break; - case XpressNetSerialInterfaceType::RoSoftS88XPressNetLI: - m_kernel = XpressNet::Kernel::create(xpressnet->config(), device.value(), baudrate.value(), flowControl.value(), s88StartAddress.value(), s88ModuleCount.value()); - break; + case XpressNetSerialInterfaceType::RoSoftS88XPressNetLI: + m_kernel = XpressNet::Kernel::create(xpressnet->config(), device.value(), baudrate.value(), flowControl.value(), s88StartAddress.value(), s88ModuleCount.value()); + break; - case XpressNetSerialInterfaceType::LenzLIUSB: - case XpressNetSerialInterfaceType::DigikeijsDR5000: - m_kernel = XpressNet::Kernel::create(xpressnet->config(), device.value(), baudrate.value(), flowControl.value()); - break; - } - break; + case XpressNetSerialInterfaceType::LenzLIUSB: + case XpressNetSerialInterfaceType::DigikeijsDR5000: + m_kernel = XpressNet::Kernel::create(xpressnet->config(), device.value(), baudrate.value(), flowControl.value()); + break; + } + break; - case XpressNetInterfaceType::Network: - m_kernel = XpressNet::Kernel::create(xpressnet->config(), hostname.value(), port.value()); - break; + case XpressNetInterfaceType::Network: + m_kernel = XpressNet::Kernel::create(xpressnet->config(), hostname.value(), port.value()); + break; + } } if(!m_kernel) diff --git a/server/src/hardware/protocol/xpressnet/iohandler/hardwareiohandler.cpp b/server/src/hardware/protocol/xpressnet/iohandler/hardwareiohandler.cpp new file mode 100644 index 00000000..e0fa9ee0 --- /dev/null +++ b/server/src/hardware/protocol/xpressnet/iohandler/hardwareiohandler.cpp @@ -0,0 +1,130 @@ +/** + * server/src/hardware/protocol/xpressnet/iohandler/hardwareiohandler.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 "hardwareiohandler.hpp" +#include "../kernel.hpp" +#include "../messages.hpp" +#include "../../../../core/eventloop.hpp" +#include "../../../../log/log.hpp" + +namespace XpressNet { + +HardwareIOHandler::HardwareIOHandler(Kernel& kernel) + : IOHandler{kernel} + , m_readBufferOffset{0} + , m_writeBufferOffset{0} + , m_extraHeader{false} +{ +} + +bool HardwareIOHandler::send(const Message& message) +{ + if(m_writeBufferOffset + message.size() > m_writeBuffer.size()) + return false; + + const bool wasEmpty = m_writeBufferOffset == 0; + + if(m_extraHeader) + { + m_writeBuffer[m_writeBufferOffset++] = std::byte{0xFF}; + m_writeBuffer[m_writeBufferOffset++] = std::byte{0xFE}; + } + + memcpy(m_writeBuffer.data() + m_writeBufferOffset, &message, message.size()); + m_writeBufferOffset += message.size(); + + if(wasEmpty) + write(); + + return true; +} + +void HardwareIOHandler::processRead(size_t bytesTransferred) +{ + const std::byte* pos = m_readBuffer.data(); + bytesTransferred += m_readBufferOffset; + + while(bytesTransferred > (m_extraHeader ? 3 : 1)) + { + const Message* message = nullptr; + size_t drop = 0; + + if(m_extraHeader) // each message is prepended by [FF FE] or [FF FD] + { + while(drop < bytesTransferred - 2) + { + message = reinterpret_cast(pos + 2); + if(pos[0] == std::byte{0xFF} && + (pos[1] == std::byte{0xFE} || pos[1] == std::byte{0xFD}) && + message->size() <= bytesTransferred && + isChecksumValid(*message)) + { + pos += 2; + bytesTransferred -= 2; + break; + } + + drop++; + pos++; + bytesTransferred--; + } + } + else + { + while(drop < bytesTransferred) + { + message = reinterpret_cast(pos); + if(message->size() <= bytesTransferred && isChecksumValid(*message)) + break; + + drop++; + pos++; + bytesTransferred--; + } + } + + if(drop != 0) + { + EventLoop::call( + [this, drop]() + { + Log::log(m_kernel.logId(), LogMessage::W2001_RECEIVED_MALFORMED_DATA_DROPPED_X_BYTES, drop); + }); + } + + assert(message); + if(message->size() <= bytesTransferred) + { + m_kernel.receive(*message); + pos += message->size(); + bytesTransferred -= message->size(); + } + else + break; + } + + if(bytesTransferred != 0) + memmove(m_readBuffer.data(), pos, bytesTransferred); + m_readBufferOffset = bytesTransferred; +} + +} diff --git a/server/src/hardware/protocol/xpressnet/iohandler/hardwareiohandler.hpp b/server/src/hardware/protocol/xpressnet/iohandler/hardwareiohandler.hpp new file mode 100644 index 00000000..652746c6 --- /dev/null +++ b/server/src/hardware/protocol/xpressnet/iohandler/hardwareiohandler.hpp @@ -0,0 +1,55 @@ +/** + * server/src/hardware/protocol/xpressnet/iohandler/hardwareiohandler.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021-2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_XPRESSNET_IOHANDLER_HARDWAREIOHANDLER_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_XPRESSNET_IOHANDLER_HARDWAREIOHANDLER_HPP + +#include +#include +#include "iohandler.hpp" + +namespace XpressNet { + +class Kernel; +struct Message; + +class HardwareIOHandler : public IOHandler +{ + protected: + std::array m_readBuffer; + size_t m_readBufferOffset; + std::array m_writeBuffer; + size_t m_writeBufferOffset; + bool m_extraHeader; //!< every message is prepended by [FF FD] or [FF FE] + + HardwareIOHandler(Kernel& kernel); + + void processRead(size_t bytesTransferred); + virtual void write() = 0; + + public: + bool send(const Message& message) final; +}; + +} + +#endif diff --git a/server/src/hardware/protocol/xpressnet/iohandler/iohandler.cpp b/server/src/hardware/protocol/xpressnet/iohandler/iohandler.cpp index e7d71434..e85531a5 100644 --- a/server/src/hardware/protocol/xpressnet/iohandler/iohandler.cpp +++ b/server/src/hardware/protocol/xpressnet/iohandler/iohandler.cpp @@ -3,7 +3,7 @@ * * This file is part of the traintastic source code. * - * Copyright (C) 2021 Reinder Feenstra + * Copyright (C) 2021-2022 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -21,110 +21,12 @@ */ #include "iohandler.hpp" -#include "../kernel.hpp" -#include "../messages.hpp" -#include "../../../../core/eventloop.hpp" -#include "../../../../log/log.hpp" namespace XpressNet { IOHandler::IOHandler(Kernel& kernel) : m_kernel{kernel} - , m_readBufferOffset{0} - , m_writeBufferOffset{0} - , m_extraHeader{false} { } -bool IOHandler::send(const Message& message) -{ - if(m_writeBufferOffset + message.size() > m_writeBuffer.size()) - return false; - - const bool wasEmpty = m_writeBufferOffset == 0; - - if(m_extraHeader) - { - m_writeBuffer[m_writeBufferOffset++] = std::byte{0xFF}; - m_writeBuffer[m_writeBufferOffset++] = std::byte{0xFE}; - } - - memcpy(m_writeBuffer.data() + m_writeBufferOffset, &message, message.size()); - m_writeBufferOffset += message.size(); - - if(wasEmpty) - write(); - - return true; -} - -void IOHandler::processRead(size_t bytesTransferred) -{ - const std::byte* pos = m_readBuffer.data(); - bytesTransferred += m_readBufferOffset; - - while(bytesTransferred > (m_extraHeader ? 3 : 1)) - { - const Message* message = nullptr; - size_t drop = 0; - - if(m_extraHeader) // each message is prepended by [FF FE] or [FF FD] - { - while(drop < bytesTransferred - 2) - { - message = reinterpret_cast(pos + 2); - if(pos[0] == std::byte{0xFF} && - (pos[1] == std::byte{0xFE} || pos[1] == std::byte{0xFD}) && - message->size() <= bytesTransferred && - isChecksumValid(*message)) - { - pos += 2; - bytesTransferred -= 2; - break; - } - - drop++; - pos++; - bytesTransferred--; - } - } - else - { - while(drop < bytesTransferred) - { - message = reinterpret_cast(pos); - if(message->size() <= bytesTransferred && isChecksumValid(*message)) - break; - - drop++; - pos++; - bytesTransferred--; - } - } - - if(drop != 0) - { - EventLoop::call( - [this, drop]() - { - Log::log(m_kernel.logId(), LogMessage::W2001_RECEIVED_MALFORMED_DATA_DROPPED_X_BYTES, drop); - }); - } - - assert(message); - if(message->size() <= bytesTransferred) - { - m_kernel.receive(*message); - pos += message->size(); - bytesTransferred -= message->size(); - } - else - break; - } - - if(bytesTransferred != 0) - memmove(m_readBuffer.data(), pos, bytesTransferred); - m_readBufferOffset = bytesTransferred; -} - } diff --git a/server/src/hardware/protocol/xpressnet/iohandler/iohandler.hpp b/server/src/hardware/protocol/xpressnet/iohandler/iohandler.hpp index 6e1b0318..cb68a110 100644 --- a/server/src/hardware/protocol/xpressnet/iohandler/iohandler.hpp +++ b/server/src/hardware/protocol/xpressnet/iohandler/iohandler.hpp @@ -3,7 +3,7 @@ * * This file is part of the traintastic source code. * - * Copyright (C) 2021 Reinder Feenstra + * Copyright (C) 2021-2022 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -23,9 +23,6 @@ #ifndef TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_XPRESSNET_IOHANDLER_IOHANDLER_HPP #define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_XPRESSNET_IOHANDLER_IOHANDLER_HPP -#include -#include - namespace XpressNet { class Kernel; @@ -35,17 +32,9 @@ class IOHandler { protected: Kernel& m_kernel; - std::array m_readBuffer; - size_t m_readBufferOffset; - std::array m_writeBuffer; - size_t m_writeBufferOffset; - bool m_extraHeader; //!< every message is prepended by [FF FD] or [FF FE] IOHandler(Kernel& kernel); - void processRead(size_t bytesTransferred); - virtual void write() = 0; - public: IOHandler(const IOHandler&) = delete; IOHandler& operator =(const IOHandler&) = delete; @@ -55,7 +44,7 @@ class IOHandler virtual void start() = 0; virtual void stop() = 0; - bool send(const Message& message); + virtual bool send(const Message& message) = 0; }; } diff --git a/server/src/hardware/protocol/xpressnet/iohandler/serialiohandler.cpp b/server/src/hardware/protocol/xpressnet/iohandler/serialiohandler.cpp index aea3124c..6aa66202 100644 --- a/server/src/hardware/protocol/xpressnet/iohandler/serialiohandler.cpp +++ b/server/src/hardware/protocol/xpressnet/iohandler/serialiohandler.cpp @@ -3,7 +3,7 @@ * * This file is part of the traintastic source code. * - * Copyright (C) 2021 Reinder Feenstra + * Copyright (C) 2021-2022 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -30,7 +30,7 @@ namespace XpressNet { SerialIOHandler::SerialIOHandler(Kernel& kernel, const std::string& device, uint32_t baudrate, SerialFlowControl flowControl) - : IOHandler(kernel) + : HardwareIOHandler(kernel) , m_serialPort{m_kernel.ioContext()} { SerialPort::open(m_serialPort, device, baudrate, 8, SerialParity::None, SerialStopBits::One, flowControl); diff --git a/server/src/hardware/protocol/xpressnet/iohandler/serialiohandler.hpp b/server/src/hardware/protocol/xpressnet/iohandler/serialiohandler.hpp index cfa0dcf8..3f8e3dd2 100644 --- a/server/src/hardware/protocol/xpressnet/iohandler/serialiohandler.hpp +++ b/server/src/hardware/protocol/xpressnet/iohandler/serialiohandler.hpp @@ -3,7 +3,7 @@ * * This file is part of the traintastic source code. * - * Copyright (C) 2021 Reinder Feenstra + * Copyright (C) 2021-2022 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -23,13 +23,13 @@ #ifndef TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_XPRESSNET_IOHANDLER_SERIALIOHANDLER_HPP #define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_XPRESSNET_IOHANDLER_SERIALIOHANDLER_HPP -#include "iohandler.hpp" +#include "hardwareiohandler.hpp" #include #include "../../../../enum/serialflowcontrol.hpp" namespace XpressNet { -class SerialIOHandler : public IOHandler +class SerialIOHandler : public HardwareIOHandler { private: boost::asio::serial_port m_serialPort; diff --git a/server/src/hardware/protocol/xpressnet/iohandler/simulationiohandler.cpp b/server/src/hardware/protocol/xpressnet/iohandler/simulationiohandler.cpp new file mode 100644 index 00000000..4160119e --- /dev/null +++ b/server/src/hardware/protocol/xpressnet/iohandler/simulationiohandler.cpp @@ -0,0 +1,78 @@ +/** + * server/src/hardware/protocol/xpressnet/iohandler/simulationiohandler.cpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "simulationiohandler.hpp" +#include "../kernel.hpp" +#include "../messages.hpp" + +namespace XpressNet { + +static std::shared_ptr copy(const Message& message) +{ + auto* bytes = new std::byte[message.size()]; + std::memcpy(bytes, &message, message.size()); + return std::shared_ptr{bytes}; +} + +SimulationIOHandler::SimulationIOHandler(Kernel& kernel) + : IOHandler(kernel) +{ +} + +bool SimulationIOHandler::send(const Message& message) +{ + switch(message.header) + { + case 0x21: + if(message == ResumeOperationsRequest()) + reply(NormalOperationResumed(), 3); + else if(message == StopOperationsRequest()) + reply(TrackPowerOff(), 3); + break; + + case 0x80: + if(message == StopAllLocomotivesRequest()) + reply(EmergencyStop(), 3); + break; + } + + return true; +} + +void SimulationIOHandler::reply(const Message& message) +{ + // post the reply, so it has some delay + //! \todo better delay simulation? at least xpressnet message transfer time? + m_kernel.ioContext().post( + [this, data=copy(message)]() + { + m_kernel.receive(*reinterpret_cast(data.get())); + }); +} + +void SimulationIOHandler::reply(const Message& message, const size_t count) +{ + for(size_t i = 0; i < count; i++) + reply(message); +} + +} diff --git a/server/src/hardware/protocol/xpressnet/iohandler/simulationiohandler.hpp b/server/src/hardware/protocol/xpressnet/iohandler/simulationiohandler.hpp new file mode 100644 index 00000000..da2b9eb9 --- /dev/null +++ b/server/src/hardware/protocol/xpressnet/iohandler/simulationiohandler.hpp @@ -0,0 +1,49 @@ +/** + * server/src/hardware/protocol/xpressnet/iohandler/simulationiohandler.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_XPRESSNET_IOHANDLER_SIMULATIONIOHANDLER_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_XPRESSNET_IOHANDLER_SIMULATIONIOHANDLER_HPP + +#include "iohandler.hpp" +#include + +namespace XpressNet { + +class SimulationIOHandler final : public IOHandler +{ + private: + void reply(const Message& message); + void reply(const Message& message, size_t count); + + public: + SimulationIOHandler(Kernel& kernel); + + void start() final {} + void stop() final {} + + bool send(const Message& message) final; +}; + +} + +#endif + diff --git a/server/src/hardware/protocol/xpressnet/iohandler/tcpiohandler.cpp b/server/src/hardware/protocol/xpressnet/iohandler/tcpiohandler.cpp index 205dfbcc..434ff2ad 100644 --- a/server/src/hardware/protocol/xpressnet/iohandler/tcpiohandler.cpp +++ b/server/src/hardware/protocol/xpressnet/iohandler/tcpiohandler.cpp @@ -3,7 +3,7 @@ * * This file is part of the traintastic source code. * - * Copyright (C) 2021 Reinder Feenstra + * Copyright (C) 2021-2022 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -31,7 +31,7 @@ namespace XpressNet { TCPIOHandler::TCPIOHandler(Kernel& kernel, const std::string& hostname, uint16_t port) - : IOHandler(kernel) + : HardwareIOHandler(kernel) , m_socket{m_kernel.ioContext()} { m_extraHeader = true; diff --git a/server/src/hardware/protocol/xpressnet/iohandler/tcpiohandler.hpp b/server/src/hardware/protocol/xpressnet/iohandler/tcpiohandler.hpp index e19d0294..f1f8d4ef 100644 --- a/server/src/hardware/protocol/xpressnet/iohandler/tcpiohandler.hpp +++ b/server/src/hardware/protocol/xpressnet/iohandler/tcpiohandler.hpp @@ -3,7 +3,7 @@ * * This file is part of the traintastic source code. * - * Copyright (C) 2021 Reinder Feenstra + * Copyright (C) 2021-2022 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -23,12 +23,12 @@ #ifndef TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_XPRESSNET_IOHANDLER_TCPIOHANDLER_HPP #define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_XPRESSNET_IOHANDLER_TCPIOHANDLER_HPP -#include "iohandler.hpp" +#include "hardwareiohandler.hpp" #include namespace XpressNet { -class TCPIOHandler final : public IOHandler +class TCPIOHandler final : public HardwareIOHandler { private: boost::asio::ip::tcp::socket m_socket; From d4e862854f1759d3840554475ed4e6abb7008d3c Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Sun, 30 Jan 2022 17:07:13 +0100 Subject: [PATCH 08/12] z21: added basic simulation support --- .../src/hardware/interface/z21interface.cpp | 12 +- .../z21/iohandler/simulationiohandler.cpp | 215 ++++++++++++++++++ .../z21/iohandler/simulationiohandler.hpp | 60 +++++ 3 files changed, 280 insertions(+), 7 deletions(-) create mode 100644 server/src/hardware/protocol/z21/iohandler/simulationiohandler.cpp create mode 100644 server/src/hardware/protocol/z21/iohandler/simulationiohandler.hpp diff --git a/server/src/hardware/interface/z21interface.cpp b/server/src/hardware/interface/z21interface.cpp index 688b8ff9..7a1a98ed 100644 --- a/server/src/hardware/interface/z21interface.cpp +++ b/server/src/hardware/interface/z21interface.cpp @@ -23,6 +23,7 @@ #include "z21interface.hpp" #include "../decoder/decoderlisttablemodel.hpp" #include "../protocol/z21/messages.hpp" +#include "../protocol/z21/iohandler/simulationiohandler.hpp" #include "../protocol/z21/iohandler/udpclientiohandler.hpp" #include "../../core/attributes.hpp" #include "../../log/log.hpp" @@ -93,17 +94,14 @@ void Z21Interface::decoderChanged(const Decoder& decoder, DecoderChangeFlags cha bool Z21Interface::setOnline(bool& value, bool simulation) { - if(simulation) - { - Log::log(*this, LogMessage::N2001_SIMULATION_NOT_SUPPORTED); - return false; - } - if(!m_kernel && value) { try { - m_kernel = Z21::ClientKernel::create(z21->config(), hostname.value(), port.value()); + if(simulation) + m_kernel = Z21::ClientKernel::create(z21->config()); + else + m_kernel = Z21::ClientKernel::create(z21->config(), hostname.value(), port.value()); status.setValueInternal(InterfaceStatus::Initializing); diff --git a/server/src/hardware/protocol/z21/iohandler/simulationiohandler.cpp b/server/src/hardware/protocol/z21/iohandler/simulationiohandler.cpp new file mode 100644 index 00000000..afd3b29e --- /dev/null +++ b/server/src/hardware/protocol/z21/iohandler/simulationiohandler.cpp @@ -0,0 +1,215 @@ +/** + * server/src/hardware/protocol/z21/iohandler/simulationiohandler.cpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "simulationiohandler.hpp" +#include "../clientkernel.hpp" +#include "../messages.hpp" + +namespace Z21 { + +static std::shared_ptr copy(const Message& message) +{ + auto* bytes = new std::byte[message.dataLen()]; + std::memcpy(bytes, &message, message.dataLen()); + return std::shared_ptr{bytes}; +} + +SimulationIOHandler::SimulationIOHandler(Kernel& kernel) + : IOHandler(kernel) +{ +} + +bool SimulationIOHandler::send(const Message& message) +{ + switch(message.header()) + { + case LAN_X: + { + const auto& lanX = static_cast(message); + + switch(lanX.xheader) + { + case 0x21: + if(message == LanXGetVersion()) + { + reply(LanXGetVersionReply(xBusVersion, CommandStationId::Z21)); + } + else if(message == LanXGetStatus()) + { + LanXStatusChanged response; + if(m_emergencyStop) + response.db1 |= Z21_CENTRALSTATE_EMERGENCYSTOP; + if(!m_trackPowerOn) + response.db1 |= Z21_CENTRALSTATE_TRACKVOLTAGEOFF; + response.calcChecksum(); + reply(response); + } + else if(message == LanXSetTrackPowerOn()) + { + const bool changed = !m_trackPowerOn || m_emergencyStop; + m_trackPowerOn = true; + m_emergencyStop = false; + reply(LanXBCTrackPowerOn()); + if(changed && (m_broadcastFlags & BroadcastFlags::SystemStatusChanges) == BroadcastFlags::SystemStatusChanges) + { + replyLanSystemStateDataChanged(); + } + } + else if(message == LanXSetTrackPowerOff()) + { + const bool changed = m_trackPowerOn; + m_trackPowerOn = false; + reply(LanXBCTrackPowerOff()); + if(changed && (m_broadcastFlags & BroadcastFlags::SystemStatusChanges) == BroadcastFlags::SystemStatusChanges) + { + replyLanSystemStateDataChanged(); + } + } + break; + + case 0x80: + if(message == LanXSetStop()) + { + const bool changed = !m_emergencyStop; + m_emergencyStop = true; + reply(LanXBCStopped()); + if(changed && (m_broadcastFlags & BroadcastFlags::SystemStatusChanges) == BroadcastFlags::SystemStatusChanges) + { + replyLanSystemStateDataChanged(); + } + } + break; + + case 0xE3: + if(const auto& getLocoInfo = static_cast(message); + getLocoInfo.db0 == 0xF0) + { + // not (yet) supported + } + break; + + case 0xE4: + if(const auto& setLocoDrive = static_cast(message); + setLocoDrive.db0 >= 0x10 && setLocoDrive.db0 <= 0x13) + { + // not (yet) supported + } + else if(const auto& setLocoFunction = static_cast(message); + setLocoFunction.db0 == 0xF8 && + setLocoFunction.switchType() != LanXSetLocoFunction::SwitchType::Invalid) + { + // not (yet) supported + } + break; + + case 0xF1: + if(message == LanXGetFirmwareVersion()) + { + reply(LanXGetFirmwareVersionReply(firmwareVersionMajor, ServerConfig::firmwareVersionMinor)); + } + break; + } + break; + } + case LAN_GET_SERIAL_NUMBER: + if(message.dataLen() == sizeof(LanGetSerialNumber)) + { + reply(LanGetSerialNumberReply(serialNumber)); + } + break; + + case LAN_GET_HWINFO: + if(message.dataLen() == sizeof(LanGetHardwareInfo)) + { + reply(LanGetHardwareInfoReply(hardwareType, firmwareVersionMajor, firmwareVersionMinor)); + } + break; + + case LAN_GET_BROADCASTFLAGS: + if(message == LanGetBroadcastFlags()) + { + reply(LanSetBroadcastFlags(m_broadcastFlags)); + } + break; + + case LAN_SET_BROADCASTFLAGS: + if(message.dataLen() == sizeof(LanSetBroadcastFlags)) + { + m_broadcastFlags = static_cast(message).broadcastFlags(); + } + break; + + case LAN_SYSTEMSTATE_GETDATA: + if(message == LanSystemStateGetData()) + { + replyLanSystemStateDataChanged(); + } + break; + + case LAN_LOGOFF: + case LAN_GET_CODE: + case LAN_GET_LOCO_MODE: + case LAN_SET_LOCO_MODE: + case LAN_GET_TURNOUTMODE: + case LAN_SET_TURNOUTMODE: + case LAN_RMBUS_DATACHANGED: + case LAN_RMBUS_GETDATA: + case LAN_RMBUS_PROGRAMMODULE: + case LAN_SYSTEMSTATE_DATACHANGED: + case LAN_RAILCOM_DATACHANGED: + case LAN_RAILCOM_GETDATA: + case LAN_LOCONET_Z21_RX: + case LAN_LOCONET_Z21_TX: + case LAN_LOCONET_FROM_LAN: + case LAN_LOCONET_DISPATCH_ADDR: + case LAN_LOCONET_DETECTOR: + case LAN_CAN_DETECTOR: + break; // not (yet) supported + } + + return true; +} + +void SimulationIOHandler::reply(const Message& message) +{ + // post the reply, so it has some delay + //! \todo better delay simulation? at least z21 message transfer time? + m_kernel.ioContext().post( + [this, data=copy(message)]() + { + static_cast(m_kernel).receive(*reinterpret_cast(data.get())); + }); +} + +void SimulationIOHandler::replyLanSystemStateDataChanged() +{ + LanSystemStateDataChanged message; + + if(m_emergencyStop) + message.centralState |= Z21_CENTRALSTATE_EMERGENCYSTOP; + if(!m_trackPowerOn) + message.centralState |= Z21_CENTRALSTATE_TRACKVOLTAGEOFF; + + reply(message); +} + +} diff --git a/server/src/hardware/protocol/z21/iohandler/simulationiohandler.hpp b/server/src/hardware/protocol/z21/iohandler/simulationiohandler.hpp new file mode 100644 index 00000000..26690473 --- /dev/null +++ b/server/src/hardware/protocol/z21/iohandler/simulationiohandler.hpp @@ -0,0 +1,60 @@ +/** + * server/src/hardware/protocol/z21/iohandler/simulationiohandler.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_Z21_IOHANDLER_SIMULATIONIOHANDLER_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_Z21_IOHANDLER_SIMULATIONIOHANDLER_HPP + +#include "iohandler.hpp" +#include +#include "../messages.hpp" + +namespace Z21 { + +class SimulationIOHandler final : public IOHandler +{ + private: + static constexpr uint8_t firmwareVersionMajor = 1; + static constexpr uint8_t firmwareVersionMinor = 30; + static constexpr HardwareType hardwareType = HWT_Z21_NEW; + static constexpr uint32_t serialNumber = 123456789; + static constexpr uint8_t xBusVersion = 30; + + bool m_emergencyStop = false; + bool m_trackPowerOn = false; + BroadcastFlags m_broadcastFlags = BroadcastFlags::None; + + void reply(const Message& message); + void replyLanSystemStateDataChanged(); + + public: + SimulationIOHandler(Kernel& kernel); + + void start() final {} + void stop() final {} + + bool send(const Message& message) final; +}; + +} + +#endif + From 0407693c19e6c57842801cbfd2b2b0adfc384547 Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Sun, 30 Jan 2022 18:56:31 +0100 Subject: [PATCH 09/12] fix: added missing include --- .../protocol/xpressnet/iohandler/simulationiohandler.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/hardware/protocol/xpressnet/iohandler/simulationiohandler.hpp b/server/src/hardware/protocol/xpressnet/iohandler/simulationiohandler.hpp index da2b9eb9..a7dece4a 100644 --- a/server/src/hardware/protocol/xpressnet/iohandler/simulationiohandler.hpp +++ b/server/src/hardware/protocol/xpressnet/iohandler/simulationiohandler.hpp @@ -25,6 +25,7 @@ #include "iohandler.hpp" #include +#include namespace XpressNet { From 7b08bfcdcd066dfaba190f0a6358df17cd29ecb5 Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Thu, 10 Feb 2022 21:22:04 +0100 Subject: [PATCH 10/12] dcc++: added basic simulation support --- .../interface/dccplusplusinterface.cpp | 16 ++-- .../{iohandler.cpp => hardwareiohandler.cpp} | 14 ++-- .../iohandler/hardwareiohandler.hpp | 52 +++++++++++++ .../dccplusplus/iohandler/iohandler.hpp | 15 ++-- .../dccplusplus/iohandler/serialiohandler.cpp | 4 +- .../dccplusplus/iohandler/serialiohandler.hpp | 6 +- .../iohandler/simulationiohandler.cpp | 77 +++++++++++++++++++ .../iohandler/simulationiohandler.hpp | 49 ++++++++++++ .../protocol/dccplusplus/messages.hpp | 41 ++++++++++ 9 files changed, 245 insertions(+), 29 deletions(-) rename server/src/hardware/protocol/dccplusplus/iohandler/{iohandler.cpp => hardwareiohandler.cpp} (83%) create mode 100644 server/src/hardware/protocol/dccplusplus/iohandler/hardwareiohandler.hpp create mode 100644 server/src/hardware/protocol/dccplusplus/iohandler/simulationiohandler.cpp create mode 100644 server/src/hardware/protocol/dccplusplus/iohandler/simulationiohandler.hpp diff --git a/server/src/hardware/interface/dccplusplusinterface.cpp b/server/src/hardware/interface/dccplusplusinterface.cpp index e209c69b..9b5dae39 100644 --- a/server/src/hardware/interface/dccplusplusinterface.cpp +++ b/server/src/hardware/interface/dccplusplusinterface.cpp @@ -25,6 +25,7 @@ #include "../output/list/outputlisttablemodel.hpp" #include "../protocol/dccplusplus/messages.hpp" #include "../protocol/dccplusplus/iohandler/serialiohandler.hpp" +#include "../protocol/dccplusplus/iohandler/simulationiohandler.hpp" #include "../../core/attributes.hpp" #include "../../log/log.hpp" #include "../../log/logmessageexception.hpp" @@ -119,17 +120,18 @@ bool DCCPlusPlusInterface::setOutputValue(uint32_t address, bool value) bool DCCPlusPlusInterface::setOnline(bool& value, bool simulation) { - if(simulation) - { - Log::log(*this, LogMessage::N2001_SIMULATION_NOT_SUPPORTED); - return false; - } - if(!m_kernel && value) { try { - m_kernel = DCCPlusPlus::Kernel::create(dccplusplus->config(), device.value(), baudrate.value(), flowControl.value()); + if(simulation) + { + m_kernel = DCCPlusPlus::Kernel::create(dccplusplus->config()); + } + else + { + m_kernel = DCCPlusPlus::Kernel::create(dccplusplus->config(), device.value(), baudrate.value(), flowControl.value()); + } status.setValueInternal(InterfaceStatus::Initializing); diff --git a/server/src/hardware/protocol/dccplusplus/iohandler/iohandler.cpp b/server/src/hardware/protocol/dccplusplus/iohandler/hardwareiohandler.cpp similarity index 83% rename from server/src/hardware/protocol/dccplusplus/iohandler/iohandler.cpp rename to server/src/hardware/protocol/dccplusplus/iohandler/hardwareiohandler.cpp index 8e2ca0da..e64d10f2 100644 --- a/server/src/hardware/protocol/dccplusplus/iohandler/iohandler.cpp +++ b/server/src/hardware/protocol/dccplusplus/iohandler/hardwareiohandler.cpp @@ -1,9 +1,9 @@ /** - * server/src/hardware/protocol/dccplusplus/iohandler/iohandler.cpp + * server/src/hardware/protocol/dccplusplus/iohandler/hardwareiohandler.cpp * * This file is part of the traintastic source code. * - * Copyright (C) 2021 Reinder Feenstra + * Copyright (C) 2021-2022 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -20,19 +20,19 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "iohandler.hpp" +#include "hardwareiohandler.hpp" #include "../kernel.hpp" namespace DCCPlusPlus { -IOHandler::IOHandler(Kernel& kernel) - : m_kernel{kernel} +HardwareIOHandler::HardwareIOHandler(Kernel& kernel) + : IOHandler(kernel) , m_readBufferOffset{0} , m_writeBufferOffset{0} { } -bool IOHandler::send(std::string_view message) +bool HardwareIOHandler::send(std::string_view message) { if(m_writeBufferOffset + message.size() > m_writeBuffer.size()) return false; @@ -47,7 +47,7 @@ bool IOHandler::send(std::string_view message) return true; } -void IOHandler::processRead(size_t bytesTransferred) +void HardwareIOHandler::processRead(size_t bytesTransferred) { const char* pos = reinterpret_cast(m_readBuffer.data()); bytesTransferred += m_readBufferOffset; diff --git a/server/src/hardware/protocol/dccplusplus/iohandler/hardwareiohandler.hpp b/server/src/hardware/protocol/dccplusplus/iohandler/hardwareiohandler.hpp new file mode 100644 index 00000000..1e9b6eee --- /dev/null +++ b/server/src/hardware/protocol/dccplusplus/iohandler/hardwareiohandler.hpp @@ -0,0 +1,52 @@ +/** + * server/src/hardware/protocol/dccplusplus/iohandler/hardwareiohandler.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021-2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_DCCPLUSPLUS_IOHANDLER_HARDWAREIOHANDLER_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_DCCPLUSPLUS_IOHANDLER_HARDWAREIOHANDLER_HPP + +#include +#include "iohandler.hpp" + +namespace DCCPlusPlus { + +class Kernel; + +class HardwareIOHandler : public IOHandler +{ + protected: + std::array m_readBuffer; + size_t m_readBufferOffset; + std::array m_writeBuffer; + size_t m_writeBufferOffset; + + HardwareIOHandler(Kernel& kernel); + + void processRead(size_t bytesTransferred); + virtual void write() = 0; + + public: + bool send(std::string_view message) final; +}; + +} + +#endif diff --git a/server/src/hardware/protocol/dccplusplus/iohandler/iohandler.hpp b/server/src/hardware/protocol/dccplusplus/iohandler/iohandler.hpp index 0aa9c6e2..31748c44 100644 --- a/server/src/hardware/protocol/dccplusplus/iohandler/iohandler.hpp +++ b/server/src/hardware/protocol/dccplusplus/iohandler/iohandler.hpp @@ -25,7 +25,6 @@ #include #include -#include namespace DCCPlusPlus { @@ -35,15 +34,11 @@ class IOHandler { protected: Kernel& m_kernel; - std::array m_readBuffer; - size_t m_readBufferOffset; - std::array m_writeBuffer; - size_t m_writeBufferOffset; - IOHandler(Kernel& kernel); - - void processRead(size_t bytesTransferred); - virtual void write() = 0; + IOHandler(Kernel& kernel) + : m_kernel{kernel} + { + } public: IOHandler(const IOHandler&) = delete; @@ -54,7 +49,7 @@ class IOHandler virtual void start() = 0; virtual void stop() = 0; - bool send(std::string_view message); + virtual bool send(std::string_view message) = 0; }; } diff --git a/server/src/hardware/protocol/dccplusplus/iohandler/serialiohandler.cpp b/server/src/hardware/protocol/dccplusplus/iohandler/serialiohandler.cpp index d329be8d..e1ad2020 100644 --- a/server/src/hardware/protocol/dccplusplus/iohandler/serialiohandler.cpp +++ b/server/src/hardware/protocol/dccplusplus/iohandler/serialiohandler.cpp @@ -3,7 +3,7 @@ * * This file is part of the traintastic source code. * - * Copyright (C) 2021 Reinder Feenstra + * Copyright (C) 2021-2022 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -30,7 +30,7 @@ namespace DCCPlusPlus { SerialIOHandler::SerialIOHandler(Kernel& kernel, const std::string& device, uint32_t baudrate, SerialFlowControl flowControl) - : IOHandler(kernel) + : HardwareIOHandler(kernel) , m_serialPort{m_kernel.ioContext()} { SerialPort::open(m_serialPort, device, baudrate, 8, SerialParity::None, SerialStopBits::One, flowControl); diff --git a/server/src/hardware/protocol/dccplusplus/iohandler/serialiohandler.hpp b/server/src/hardware/protocol/dccplusplus/iohandler/serialiohandler.hpp index 142419ed..fc449f2c 100644 --- a/server/src/hardware/protocol/dccplusplus/iohandler/serialiohandler.hpp +++ b/server/src/hardware/protocol/dccplusplus/iohandler/serialiohandler.hpp @@ -3,7 +3,7 @@ * * This file is part of the traintastic source code. * - * Copyright (C) 2021 Reinder Feenstra + * Copyright (C) 2021-2022 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -23,13 +23,13 @@ #ifndef TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_DCCPLUSPLUS_IOHANDLER_SERIALIOHANDLER_HPP #define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_DCCPLUSPLUS_IOHANDLER_SERIALIOHANDLER_HPP -#include "iohandler.hpp" +#include "hardwareiohandler.hpp" #include #include "../../../../enum/serialflowcontrol.hpp" namespace DCCPlusPlus { -class SerialIOHandler final : public IOHandler +class SerialIOHandler final : public HardwareIOHandler { private: boost::asio::serial_port m_serialPort; diff --git a/server/src/hardware/protocol/dccplusplus/iohandler/simulationiohandler.cpp b/server/src/hardware/protocol/dccplusplus/iohandler/simulationiohandler.cpp new file mode 100644 index 00000000..a1da11b1 --- /dev/null +++ b/server/src/hardware/protocol/dccplusplus/iohandler/simulationiohandler.cpp @@ -0,0 +1,77 @@ +/** + * server/src/hardware/protocol/dccplusplus/iohandler/simulationiohandler.cpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "simulationiohandler.hpp" +#include "../kernel.hpp" +#include "../messages.hpp" +#include "../../../../utils/endswith.hpp" + +namespace DCCPlusPlus { + +SimulationIOHandler::SimulationIOHandler(Kernel& kernel) + : IOHandler(kernel) +{ +} + +bool SimulationIOHandler::send(std::string_view message) +{ + if(message.size() < 4 || message[0] != '<' || !endsWith(message, ">\n")) + return false; + + switch(message[1]) + { + case '0': // power off + if(message == Ex::powerOff()) + reply(Ex::powerOffResponse()); + else if(message == Ex::powerOff(Ex::Track::Main)) + reply(Ex::powerOffResponse(Ex::Track::Main)); + else if(message == Ex::powerOff(Ex::Track::Programming)) + reply(Ex::powerOffResponse(Ex::Track::Programming)); + break; + + case '1': // power on + if(message == Ex::powerOn()) + reply(Ex::powerOnResponse()); + else if(message == Ex::powerOn(Ex::Track::Main)) + reply(Ex::powerOnResponse(Ex::Track::Main)); + else if(message == Ex::powerOn(Ex::Track::Programming)) + reply(Ex::powerOnResponse(Ex::Track::Programming)); + else if(message == Ex::powerOnJoin()) + reply(Ex::powerOnJoinResponse()); + break; + } + + return true; +} + +void SimulationIOHandler::reply(std::string_view message) +{ + // post the reply, so it has some delay + //! \todo better delay simulation? at least dcc++ message transfer time? + m_kernel.ioContext().post( + [this, data=std::string(message)]() + { + m_kernel.receive(data); + }); +} + +} diff --git a/server/src/hardware/protocol/dccplusplus/iohandler/simulationiohandler.hpp b/server/src/hardware/protocol/dccplusplus/iohandler/simulationiohandler.hpp new file mode 100644 index 00000000..c02c93ae --- /dev/null +++ b/server/src/hardware/protocol/dccplusplus/iohandler/simulationiohandler.hpp @@ -0,0 +1,49 @@ +/** + * server/src/hardware/protocol/dccplusplus/iohandler/simulationiohandler.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_DCCPLUSPLUS_IOHANDLER_SIMULATIONIOHANDLER_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_DCCPLUSPLUS_IOHANDLER_SIMULATIONIOHANDLER_HPP + +#include "iohandler.hpp" +#include +#include + +namespace DCCPlusPlus { + +class SimulationIOHandler final : public IOHandler +{ + private: + void reply(std::string_view message); + + public: + SimulationIOHandler(Kernel& kernel); + + void start() final {} + void stop() final {} + + bool send(std::string_view message) final; +}; + +} + +#endif + diff --git a/server/src/hardware/protocol/dccplusplus/messages.hpp b/server/src/hardware/protocol/dccplusplus/messages.hpp index 59a55c75..324fa8b0 100644 --- a/server/src/hardware/protocol/dccplusplus/messages.hpp +++ b/server/src/hardware/protocol/dccplusplus/messages.hpp @@ -42,6 +42,11 @@ namespace Ex { return "<0>\n"; } + constexpr std::string_view powerOffResponse() + { + return "\n"; + } + constexpr std::string_view powerOff(Track track) { switch(track) @@ -55,12 +60,30 @@ namespace Ex { return ""; } + constexpr std::string_view powerOffResponse(Track track) + { + switch(track) + { + case Track::Main: + return "\n"; + + case Track::Programming: + return "\n"; + } + return ""; + } + //! Turn Power ON to tracks (Both Main & Programming) constexpr std::string_view powerOn() { return "<1>\n"; } + constexpr std::string_view powerOnResponse() + { + return "\n"; + } + constexpr std::string_view powerOn(Track track) { switch(track) @@ -74,11 +97,29 @@ namespace Ex { return ""; } + constexpr std::string_view powerOnResponse(Track track) + { + switch(track) + { + case Track::Main: + return "\n"; + + case Track::Programming: + return "\n"; + } + return ""; + } + constexpr std::string_view powerOnJoin() { return "<1 JOIN>\n"; } + constexpr std::string_view powerOnJoinResponse() + { + return "\n"; + } + //! Displays the instantaneous current on the MAIN Track constexpr std::string_view getMainTrackCurrent() { From 1f633674ee1b861a9a5ef574f3d7ccac8963a584 Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Thu, 10 Feb 2022 21:24:16 +0100 Subject: [PATCH 11/12] interface: only allow online if world is online --- server/src/hardware/interface/interface.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/src/hardware/interface/interface.cpp b/server/src/hardware/interface/interface.cpp index 04329146..3be902d1 100644 --- a/server/src/hardware/interface/interface.cpp +++ b/server/src/hardware/interface/interface.cpp @@ -46,6 +46,7 @@ Interface::Interface(const std::weak_ptr& world, std::string_view _id) m_interfaceItems.add(name); Attributes::addDisplayName(online, DisplayName::Interface::online); + Attributes::addEnabled(online, w && contains(w->state.value(), WorldState::Online)); m_interfaceItems.add(online); Attributes::addDisplayName(status, DisplayName::Interface::status); @@ -81,9 +82,11 @@ void Interface::worldEvent(WorldState state, WorldEvent event) { case WorldEvent::Offline: online = false; + Attributes::setEnabled(online, false); break; case WorldEvent::Online: + Attributes::setEnabled(online, true); online = true; break; From 5986c4075d04ef6766ae3ab1e5d8164b7e5ffceb Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Thu, 10 Feb 2022 21:39:28 +0100 Subject: [PATCH 12/12] added simulation icon --- client/gfx/dark/dark.qrc | 1 + client/gfx/dark/simulation.svg | 97 +++++++++++++++++++++++++++++++++ client/gfx/light/light.qrc | 1 + client/gfx/light/simulation.svg | 97 +++++++++++++++++++++++++++++++++ 4 files changed, 196 insertions(+) create mode 100644 client/gfx/dark/simulation.svg create mode 100644 client/gfx/light/simulation.svg diff --git a/client/gfx/dark/dark.qrc b/client/gfx/dark/dark.qrc index 6ef4c72b..17d42743 100644 --- a/client/gfx/dark/dark.qrc +++ b/client/gfx/dark/dark.qrc @@ -71,5 +71,6 @@ board_tile.rail.bridge_45_right.svg board_tile.rail.sensor.svg board_tile.rail.tunnel.svg + simulation.svg diff --git a/client/gfx/dark/simulation.svg b/client/gfx/dark/simulation.svg new file mode 100644 index 00000000..be7dd409 --- /dev/null +++ b/client/gfx/dark/simulation.svg @@ -0,0 +1,97 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/client/gfx/light/light.qrc b/client/gfx/light/light.qrc index e1ec1de6..b3ce0688 100644 --- a/client/gfx/light/light.qrc +++ b/client/gfx/light/light.qrc @@ -48,5 +48,6 @@ board_tile.rail.bridge_45_right.svg board_tile.rail.sensor.svg board_tile.rail.tunnel.svg + simulation.svg \ No newline at end of file diff --git a/client/gfx/light/simulation.svg b/client/gfx/light/simulation.svg new file mode 100644 index 00000000..d350a33d --- /dev/null +++ b/client/gfx/light/simulation.svg @@ -0,0 +1,97 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + +