Merge branch 'simulate'

Dieser Commit ist enthalten in:
Reinder Feenstra 2022-02-12 00:07:28 +01:00
Commit 679b8528b9
54 geänderte Dateien mit 1641 neuen und 277 gelöschten Zeilen

Datei anzeigen

@ -71,5 +71,6 @@
<file>board_tile.rail.bridge_45_right.svg</file>
<file>board_tile.rail.sensor.svg</file>
<file>board_tile.rail.tunnel.svg</file>
<file>simulation.svg</file>
</qresource>
</RCC>

Datei anzeigen

@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="96"
height="96"
viewBox="0 0 25.399999 25.400001"
version="1.1"
id="svg8"
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"
sodipodi:docname="simulation.svg">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.6"
inkscape:cx="27.311043"
inkscape:cy="56.158862"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="true"
units="px"
inkscape:window-width="1920"
inkscape:window-height="1015"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
id="grid3713"
spacingx="0.26458333"
empspacing="4" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-nc/4.0/" />
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by-nc/4.0/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:prohibits
rdf:resource="http://creativecommons.org/ns#CommercialUse" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
</cc:License>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-271.59998)">
<ellipse
style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:1.58749998;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path896"
cx="106.44751"
cy="263.92557"
rx="11.641666"
ry="5.8208332"
transform="rotate(19.407636)" />
<ellipse
style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:1.69410527;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path896-3"
cx="-298.21597"
cy="254.10666"
rx="14.48158"
ry="3.8138311"
transform="matrix(0.75558689,-0.65504843,0.93672507,0.3500659,0,0)" />
</g>
</svg>

Nachher

Breite:  |  Höhe:  |  Größe: 3.2 KiB

Datei anzeigen

@ -48,6 +48,7 @@
<file>board_tile.rail.bridge_45_right.svg</file>
<file>board_tile.rail.sensor.svg</file>
<file>board_tile.rail.tunnel.svg</file>
<file>simulation.svg</file>
<file>output_keyboard.svg</file>
<file>input_monitor.svg</file>
</qresource>

Datei anzeigen

@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="96"
height="96"
viewBox="0 0 25.399999 25.400001"
version="1.1"
id="svg8"
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"
sodipodi:docname="simulation.svg">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.6"
inkscape:cx="27.311043"
inkscape:cy="55.801719"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="true"
units="px"
inkscape:window-width="1920"
inkscape:window-height="1015"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
id="grid3713"
spacingx="0.26458333"
empspacing="4" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-nc/4.0/" />
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by-nc/4.0/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:prohibits
rdf:resource="http://creativecommons.org/ns#CommercialUse" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
</cc:License>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-271.59998)">
<ellipse
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:1.58749998;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path896"
cx="106.44751"
cy="263.92557"
rx="11.641666"
ry="5.8208332"
transform="rotate(19.407636)" />
<ellipse
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:1.69410527;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path896-3"
cx="-298.21597"
cy="254.10666"
rx="14.48158"
ry="3.8138311"
transform="matrix(0.75558689,-0.65504843,0.93672507,0.3500659,0,0)" />
</g>
</svg>

Nachher

Breite:  |  Höhe:  |  Größe: 3.2 KiB

Datei anzeigen

@ -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));
}

Datei anzeigen

@ -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;

Datei anzeigen

@ -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"
@ -153,13 +154,20 @@ bool DCCPlusPlusInterface::setOutputValue(uint32_t channel, uint32_t address, bo
m_kernel->setOutput(channel, static_cast<uint16_t>(address), value);
}
bool DCCPlusPlusInterface::setOnline(bool& value)
bool DCCPlusPlusInterface::setOnline(bool& value, bool simulation)
{
if(!m_kernel && value)
{
try
{
m_kernel = DCCPlusPlus::Kernel::create<DCCPlusPlus::SerialIOHandler>(dccplusplus->config(), device.value(), baudrate.value(), SerialFlowControl::None);
if(simulation)
{
m_kernel = DCCPlusPlus::Kernel::create<DCCPlusPlus::SimulationIOHandler>(dccplusplus->config());
}
else
{
m_kernel = DCCPlusPlus::Kernel::create<DCCPlusPlus::SerialIOHandler>(dccplusplus->config(), device.value(), baudrate.value(), SerialFlowControl::None);
}
status.setValueInternal(InterfaceStatus::Initializing);

Datei anzeigen

@ -63,7 +63,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<std::string> device;

Datei anzeigen

@ -136,8 +136,14 @@ bool ECoSInterface::setOutputValue(uint32_t channel, uint32_t address, bool valu
m_kernel->setOutput(static_cast<uint16_t>(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

Datei anzeigen

@ -60,7 +60,7 @@ class ECoSInterface final
void typeChanged();
protected:
bool setOnline(bool& value) final;
bool setOnline(bool& value, bool simulation) final;
public:
Property<std::string> hostname;

Datei anzeigen

@ -29,7 +29,11 @@
Interface::Interface(World& world, std::string_view _id)
: IdObject(world, _id)
, name{this, "name", "", PropertyFlags::ReadWrite | PropertyFlags::Store}
, online{this, "online", false, PropertyFlags::ReadWrite | PropertyFlags::NoStore, nullptr, std::bind(&Interface::setOnline, this, std::placeholders::_1)}
, online{this, "online", false, PropertyFlags::ReadWrite | PropertyFlags::NoStore, nullptr,
[this](bool& value)
{
return setOnline(value, contains(m_world.state.value(), WorldState::Simulation));
}}
, status{this, "status", InterfaceStatus::Offline, PropertyFlags::ReadOnly | PropertyFlags::NoStore}
, notes{this, "notes", "", PropertyFlags::ReadWrite | PropertyFlags::Store}
{
@ -40,6 +44,7 @@ Interface::Interface(World& world, std::string_view _id)
m_interfaceItems.add(name);
Attributes::addDisplayName(online, DisplayName::Interface::online);
Attributes::addEnabled(online, contains(m_world.state.value(), WorldState::Online));
m_interfaceItems.add(online);
Attributes::addDisplayName(status, DisplayName::Interface::status);
@ -72,9 +77,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;

Datei anzeigen

@ -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<std::string> name;

Datei anzeigen

@ -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"
@ -168,33 +169,40 @@ bool LocoNetInterface::setOutputValue(uint32_t channel, uint32_t address, bool v
m_kernel->setOutput(static_cast<uint16_t>(address), value);
}
bool LocoNetInterface::setOnline(bool& value)
bool LocoNetInterface::setOnline(bool& value, bool simulation)
{
if(!m_kernel && value)
{
try
{
switch(type)
if(simulation)
{
case LocoNetInterfaceType::Serial:
m_kernel = LocoNet::Kernel::create<LocoNet::SerialIOHandler>(loconet->config(), device.value(), baudrate.value(), flowControl.value());
break;
m_kernel = LocoNet::Kernel::create<LocoNet::SimulationIOHandler>(loconet->config());
}
else
{
switch(type)
{
case LocoNetInterfaceType::Serial:
m_kernel = LocoNet::Kernel::create<LocoNet::SerialIOHandler>(loconet->config(), device.value(), baudrate.value(), flowControl.value());
break;
case LocoNetInterfaceType::TCPBinary:
m_kernel = LocoNet::Kernel::create<LocoNet::TCPBinaryIOHandler>(loconet->config(), hostname.value(), port.value());
break;
case LocoNetInterfaceType::TCPBinary:
m_kernel = LocoNet::Kernel::create<LocoNet::TCPBinaryIOHandler>(loconet->config(), hostname.value(), port.value());
break;
case LocoNetInterfaceType::LBServer:
m_kernel = LocoNet::Kernel::create<LocoNet::LBServerIOHandler>(loconet->config(), hostname.value(), port.value());
break;
case LocoNetInterfaceType::LBServer:
m_kernel = LocoNet::Kernel::create<LocoNet::LBServerIOHandler>(loconet->config(), hostname.value(), port.value());
break;
case LocoNetInterfaceType::Z21:
m_kernel = LocoNet::Kernel::create<LocoNet::Z21IOHandler>(loconet->config(), hostname.value());
break;
case LocoNetInterfaceType::Z21:
m_kernel = LocoNet::Kernel::create<LocoNet::Z21IOHandler>(loconet->config(), hostname.value());
break;
default:
assert(false);
return false;
default:
assert(false);
return false;
}
}
status.setValueInternal(InterfaceStatus::Initializing);

Datei anzeigen

@ -63,7 +63,7 @@ class LocoNetInterface final
void typeChanged();
protected:
bool setOnline(bool& value) final;
bool setOnline(bool& value, bool simulation) final;
public:
Property<LocoNetInterfaceType> type;

Datei anzeigen

@ -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

Datei anzeigen

@ -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::ServerSettings> z21;

Datei anzeigen

@ -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"
@ -215,37 +216,44 @@ bool XpressNetInterface::setOutputValue(uint32_t channel, uint32_t address, bool
m_kernel->setOutput(static_cast<uint16_t>(address), value);
}
bool XpressNetInterface::setOnline(bool& value)
bool XpressNetInterface::setOnline(bool& value, bool simulation)
{
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::SerialIOHandler>(xpressnet->config(), device.value(), baudrate.value(), flowControl.value());
break;
m_kernel = XpressNet::Kernel::create<XpressNet::SimulationIOHandler>(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::SerialIOHandler>(xpressnet->config(), device.value(), baudrate.value(), flowControl.value());
break;
case XpressNetSerialInterfaceType::RoSoftS88XPressNetLI:
m_kernel = XpressNet::Kernel::create<XpressNet::RoSoftS88XPressNetLIIOHandler>(xpressnet->config(), device.value(), baudrate.value(), flowControl.value(), s88StartAddress.value(), s88ModuleCount.value());
break;
case XpressNetSerialInterfaceType::RoSoftS88XPressNetLI:
m_kernel = XpressNet::Kernel::create<XpressNet::RoSoftS88XPressNetLIIOHandler>(xpressnet->config(), device.value(), baudrate.value(), flowControl.value(), s88StartAddress.value(), s88ModuleCount.value());
break;
case XpressNetSerialInterfaceType::LenzLIUSB:
case XpressNetSerialInterfaceType::DigikeijsDR5000:
m_kernel = XpressNet::Kernel::create<XpressNet::LIUSBIOHandler>(xpressnet->config(), device.value(), baudrate.value(), flowControl.value());
break;
}
break;
case XpressNetSerialInterfaceType::LenzLIUSB:
case XpressNetSerialInterfaceType::DigikeijsDR5000:
m_kernel = XpressNet::Kernel::create<XpressNet::LIUSBIOHandler>(xpressnet->config(), device.value(), baudrate.value(), flowControl.value());
break;
}
break;
case XpressNetInterfaceType::Network:
m_kernel = XpressNet::Kernel::create<XpressNet::TCPIOHandler>(xpressnet->config(), hostname.value(), port.value());
break;
case XpressNetInterfaceType::Network:
m_kernel = XpressNet::Kernel::create<XpressNet::TCPIOHandler>(xpressnet->config(), hostname.value(), port.value());
break;
}
}
if(!m_kernel)
@ -297,11 +305,11 @@ bool XpressNetInterface::setOnline(bool& value)
});
if(!contains(m_world.state.value(), WorldState::PowerOn))
m_kernel->trackPowerOff();
m_kernel->stopOperations();
else if(!contains(m_world.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);
}
@ -378,22 +386,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:

Datei anzeigen

@ -64,7 +64,7 @@ class XpressNetInterface final
void updateVisible();
protected:
bool setOnline(bool& value) final;
bool setOnline(bool& value, bool simulation) final;
public:
Property<XpressNetInterfaceType> type;

Datei anzeigen

@ -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"
@ -91,13 +92,16 @@ 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(!m_kernel && value)
{
try
{
m_kernel = Z21::ClientKernel::create<Z21::UDPClientIOHandler>(z21->config(), hostname.value(), port.value());
if(simulation)
m_kernel = Z21::ClientKernel::create<Z21::SimulationIOHandler>(z21->config());
else
m_kernel = Z21::ClientKernel::create<Z21::UDPClientIOHandler>(z21->config(), hostname.value(), port.value());
status.setValueInternal(InterfaceStatus::Initializing);

Datei anzeigen

@ -54,7 +54,7 @@ class Z21Interface final
void updateVisible();
protected:
bool setOnline(bool& value) final;
bool setOnline(bool& value, bool simulation) final;
public:
Property<std::string> hostname;

Datei anzeigen

@ -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<const char*>(m_readBuffer.data());
bytesTransferred += m_readBufferOffset;

Datei anzeigen

@ -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 <array>
#include "iohandler.hpp"
namespace DCCPlusPlus {
class Kernel;
class HardwareIOHandler : public IOHandler
{
protected:
std::array<char, 1024> m_readBuffer;
size_t m_readBufferOffset;
std::array<char, 1024> 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

Datei anzeigen

@ -25,7 +25,6 @@
#include <cstddef>
#include <string_view>
#include <array>
namespace DCCPlusPlus {
@ -35,15 +34,11 @@ class IOHandler
{
protected:
Kernel& m_kernel;
std::array<char, 1024> m_readBuffer;
size_t m_readBufferOffset;
std::array<char, 1024> 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;
};
}

Datei anzeigen

@ -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);

Datei anzeigen

@ -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 <boost/asio/serial_port.hpp>
#include "../../../../enum/serialflowcontrol.hpp"
namespace DCCPlusPlus {
class SerialIOHandler final : public IOHandler
class SerialIOHandler final : public HardwareIOHandler
{
private:
boost::asio::serial_port m_serialPort;

Datei anzeigen

@ -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);
});
}
}

Datei anzeigen

@ -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 <array>
#include <cstddef>
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

Datei anzeigen

@ -42,6 +42,11 @@ namespace Ex {
return "<0>\n";
}
constexpr std::string_view powerOffResponse()
{
return "<p0>\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 "<p0 MAIN>\n";
case Track::Programming:
return "<p0 PROG>\n";
}
return "";
}
//! Turn Power ON to tracks (Both Main & Programming)
constexpr std::string_view powerOn()
{
return "<1>\n";
}
constexpr std::string_view powerOnResponse()
{
return "<p1>\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 "<p1 MAIN>\n";
case Track::Programming:
return "<p1 PROG>\n";
}
return "";
}
constexpr std::string_view powerOnJoin()
{
return "<1 JOIN>\n";
}
constexpr std::string_view powerOnJoinResponse()
{
return "<p1 JOIN>\n";
}
//! Displays the instantaneous current on the MAIN Track
constexpr std::string_view getMainTrackCurrent()
{

Datei anzeigen

@ -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<std::byte[]> copy(const Message& message)
{
auto* bytes = new std::byte[message.size()];
std::memcpy(bytes, &message, message.size());
return std::shared_ptr<std::byte[]>{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<const RequestSlotData&>(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<const LocoAdr&>(message);
// find slot for address
{
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;
});
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(), // NOLINT [readability-qualified-auto]
[](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<const LocoSpd&>(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<const LocoDirF&>(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<const LocoSnd&>(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<const Message*>(data.get()));
});
}
}

Datei anzeigen

@ -0,0 +1,51 @@
/**
* 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 <array>
#include "../messages.hpp"
namespace LocoNet {
class SimulationIOHandler final : public IOHandler
{
private:
std::array<SlotReadData, SLOT_LOCO_MAX - SLOT_LOCO_MIN + 1> m_locoSlots;
void reply(const Message& message);
public:
SimulationIOHandler(Kernel& kernel);
void start() final {}
void stop() final {}
bool send(const Message& message) final;
};
}
#endif

Datei anzeigen

@ -399,7 +399,13 @@ void Kernel::receive(const Message& message)
const uint8_t slot = *(reinterpret_cast<const uint8_t*>(&message) + 2);
if(m_decoderController && isLocoSlot(slot))
{
const SlotReadData& slotReadData = static_cast<const SlotReadData&>(message);
const auto& slotReadData = static_cast<const SlotReadData&>(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<Decoder> Kernel::getDecoder(uint16_t address)
{
return m_decoderController->getDecoder(DecoderProtocol::DCC, address, DCC::isLongAddress(address), true);

Datei anzeigen

@ -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<Decoder> getDecoder(uint16_t address);

Datei anzeigen

@ -219,6 +219,11 @@ struct LocoAdr : Message
{
checksum = calcChecksum(*this);
}
uint16_t address() const
{
return (static_cast<uint16_t>(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<OpCode>(0), uint8_t _ack1 = 0) :
Message{OPC_LONG_ACK},
lpoc{0},
ack1{0},
checksum{0}
lopc{static_cast<uint8_t>(_lopc & 0x7F)},
ack1{_ack1},
checksum{calcChecksum(*this)}
{
}
OpCode respondingOpCode() const
{
return static_cast<OpCode>(0x80 | lpoc);
return static_cast<OpCode>(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<uint16_t>(adr2) << 7) | adr;
}
void setAddress(uint16_t value)
{
adr = value & 0x7F;
adr2 = (value >> 7) & 0x7F;
}
bool isEmergencyStop() const
{
return spd == 0x01;

Datei anzeigen

@ -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<const Message*>(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<const Message*>(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;
}
}

Datei anzeigen

@ -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 <cstddef>
#include <array>
#include "iohandler.hpp"
namespace XpressNet {
class Kernel;
struct Message;
class HardwareIOHandler : public IOHandler
{
protected:
std::array<std::byte, 1024> m_readBuffer;
size_t m_readBufferOffset;
std::array<std::byte, 1024> 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

Datei anzeigen

@ -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<const Message*>(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<const Message*>(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;
}
}

Datei anzeigen

@ -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 <cstddef>
#include <array>
namespace XpressNet {
class Kernel;
@ -35,17 +32,9 @@ class IOHandler
{
protected:
Kernel& m_kernel;
std::array<std::byte, 1024> m_readBuffer;
size_t m_readBufferOffset;
std::array<std::byte, 1024> 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;
};
}

Datei anzeigen

@ -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);

Datei anzeigen

@ -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 <boost/asio/serial_port.hpp>
#include "../../../../enum/serialflowcontrol.hpp"
namespace XpressNet {
class SerialIOHandler : public IOHandler
class SerialIOHandler : public HardwareIOHandler
{
private:
boost::asio::serial_port m_serialPort;

Datei anzeigen

@ -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<std::byte[]> copy(const Message& message)
{
auto* bytes = new std::byte[message.size()];
std::memcpy(bytes, &message, message.size());
return std::shared_ptr<std::byte[]>{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<const Message*>(data.get()));
});
}
void SimulationIOHandler::reply(const Message& message, const size_t count)
{
for(size_t i = 0; i < count; i++)
reply(message);
}
}

Datei anzeigen

@ -0,0 +1,50 @@
/**
* 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 <array>
#include <cstddef>
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

Datei anzeigen

@ -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;

Datei anzeigen

@ -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 <boost/asio/ip/tcp.hpp>
namespace XpressNet {
class TCPIOHandler final : public IOHandler
class TCPIOHandler final : public HardwareIOHandler
{
private:
boost::asio::ip::tcp::socket m_socket;

Datei anzeigen

@ -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());
});
}

Datei anzeigen

@ -259,19 +259,19 @@ class Kernel
*
*
*/
void normalOperationsResumed();
void resumeOperations();
/**
*
*
*/
void trackPowerOff();
void stopOperations();
/**
*
*
*/
void emergencyStop();
void stopAllLocomotives();
/**
*

Datei anzeigen

@ -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;

Datei anzeigen

@ -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<std::byte[]> copy(const Message& message)
{
auto* bytes = new std::byte[message.dataLen()];
std::memcpy(bytes, &message, message.dataLen());
return std::shared_ptr<std::byte[]>{bytes};
}
SimulationIOHandler::SimulationIOHandler(Kernel& kernel)
: IOHandler(kernel)
{
}
bool SimulationIOHandler::send(const Message& message)
{
switch(message.header())
{
case LAN_X:
{
const auto& lanX = static_cast<const LanX&>(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<const LanXGetLocoInfo&>(message);
getLocoInfo.db0 == 0xF0)
{
// not (yet) supported
}
break;
case 0xE4:
if(const auto& setLocoDrive = static_cast<const LanXSetLocoDrive&>(message);
setLocoDrive.db0 >= 0x10 && setLocoDrive.db0 <= 0x13)
{
// not (yet) supported
}
else if(const auto& setLocoFunction = static_cast<const LanXSetLocoFunction&>(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<const LanSetBroadcastFlags&>(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<ClientKernel&>(m_kernel).receive(*reinterpret_cast<const Message*>(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);
}
}

Datei anzeigen

@ -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 <array>
#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

Datei anzeigen

@ -106,76 +106,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]()
@ -297,11 +268,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
@ -387,12 +363,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)

Datei anzeigen

@ -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<void()> stop;
Property<bool> mute;
Property<bool> noSmoke;
Property<bool> simulation;
Method<void()> save;

Datei anzeigen

@ -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:

Datei anzeigen

@ -40,6 +40,8 @@ enum class WorldEvent : uint64_t
Mute = 9,
NoSmoke = 10,
Smoke = 11,
SimulationDisabled = 12,
SimulationEnabled = 13,
};
template<>

Datei anzeigen

@ -34,6 +34,7 @@ enum class WorldState : uint32_t
Run = 1 << 3,
Mute = 1 << 4,
NoSmoke = 1 << 5,
Simulation = 1 << 6,
};
template<>

Datei anzeigen

@ -267,6 +267,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
@ -451,6 +454,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