Merge branch 'simulate'
Dieser Commit ist enthalten in:
Commit
679b8528b9
@ -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>
|
||||
|
||||
97
client/gfx/dark/simulation.svg
Normale Datei
97
client/gfx/dark/simulation.svg
Normale Datei
@ -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 |
@ -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>
|
||||
|
||||
97
client/gfx/light/simulation.svg
Normale Datei
97
client/gfx/light/simulation.svg
Normale Datei
@ -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 |
@ -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));
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
@ -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
|
||||
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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()
|
||||
{
|
||||
|
||||
200
server/src/hardware/protocol/loconet/iohandler/simulationiohandler.cpp
Normale Datei
200
server/src/hardware/protocol/loconet/iohandler/simulationiohandler.cpp
Normale Datei
@ -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()));
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
130
server/src/hardware/protocol/xpressnet/iohandler/hardwareiohandler.cpp
Normale Datei
130
server/src/hardware/protocol/xpressnet/iohandler/hardwareiohandler.cpp
Normale Datei
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -259,19 +259,19 @@ class Kernel
|
||||
*
|
||||
*
|
||||
*/
|
||||
void normalOperationsResumed();
|
||||
void resumeOperations();
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*/
|
||||
void trackPowerOff();
|
||||
void stopOperations();
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*/
|
||||
void emergencyStop();
|
||||
void stopAllLocomotives();
|
||||
|
||||
/**
|
||||
*
|
||||
|
||||
@ -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;
|
||||
|
||||
215
server/src/hardware/protocol/z21/iohandler/simulationiohandler.cpp
Normale Datei
215
server/src/hardware/protocol/z21/iohandler/simulationiohandler.cpp
Normale Datei
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
60
server/src/hardware/protocol/z21/iohandler/simulationiohandler.hpp
Normale Datei
60
server/src/hardware/protocol/z21/iohandler/simulationiohandler.hpp
Normale Datei
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -40,6 +40,8 @@ enum class WorldEvent : uint64_t
|
||||
Mute = 9,
|
||||
NoSmoke = 10,
|
||||
Smoke = 11,
|
||||
SimulationDisabled = 12,
|
||||
SimulationEnabled = 13,
|
||||
};
|
||||
|
||||
template<>
|
||||
|
||||
@ -34,6 +34,7 @@ enum class WorldState : uint32_t
|
||||
Run = 1 << 3,
|
||||
Mute = 1 << 4,
|
||||
NoSmoke = 1 << 5,
|
||||
Simulation = 1 << 6,
|
||||
};
|
||||
|
||||
template<>
|
||||
|
||||
@ -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
|
||||
|
||||
Laden…
x
In neuem Issue referenzieren
Einen Benutzer sperren