[cbus] added interface/kernel/iohandlers (untested due to leak of hardware)
Dieser Commit ist enthalten in:
Ursprung
2bfbbb267f
Commit
ff784fcf49
@ -105,6 +105,7 @@ file(GLOB SOURCES
|
|||||||
"src/hardware/input/monitor/*.cpp"
|
"src/hardware/input/monitor/*.cpp"
|
||||||
"src/hardware/interface/*.hpp"
|
"src/hardware/interface/*.hpp"
|
||||||
"src/hardware/interface/*.cpp"
|
"src/hardware/interface/*.cpp"
|
||||||
|
"src/hardware/interface/cbus/*.cpp"
|
||||||
"src/hardware/interface/marklincan/*.hpp"
|
"src/hardware/interface/marklincan/*.hpp"
|
||||||
"src/hardware/interface/marklincan/*.cpp"
|
"src/hardware/interface/marklincan/*.cpp"
|
||||||
"src/hardware/output/*.hpp"
|
"src/hardware/output/*.hpp"
|
||||||
@ -119,6 +120,8 @@ file(GLOB SOURCES
|
|||||||
"src/hardware/programming/lncv/*.cpp"
|
"src/hardware/programming/lncv/*.cpp"
|
||||||
"src/hardware/protocol/*.hpp"
|
"src/hardware/protocol/*.hpp"
|
||||||
"src/hardware/protocol/*.cpp"
|
"src/hardware/protocol/*.cpp"
|
||||||
|
"src/hardware/protocol/cbus/*.cpp"
|
||||||
|
"src/hardware/protocol/cbus/iohandler/*.cpp"
|
||||||
"src/hardware/protocol/dccex/*.hpp"
|
"src/hardware/protocol/dccex/*.hpp"
|
||||||
"src/hardware/protocol/dccex/*.cpp"
|
"src/hardware/protocol/dccex/*.cpp"
|
||||||
"src/hardware/protocol/dccex/iohandler/*.hpp"
|
"src/hardware/protocol/dccex/iohandler/*.hpp"
|
||||||
|
|||||||
40
server/src/hardware/interface/cbus/cbussettings.cpp
Normale Datei
40
server/src/hardware/interface/cbus/cbussettings.cpp
Normale Datei
@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* This file is part of Traintastic,
|
||||||
|
* see <https://github.com/traintastic/traintastic>.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2026 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 "cbussettings.hpp"
|
||||||
|
#include "../../../core/attributes.hpp"
|
||||||
|
#include "../../../utils/displayname.hpp"
|
||||||
|
|
||||||
|
CBUSSettings::CBUSSettings(Object& _parent, std::string_view parentPropertyName)
|
||||||
|
: SubObject(_parent, parentPropertyName)
|
||||||
|
, debugLogRXTX{this, "debug_log_rx_tx", false, PropertyFlags::ReadWrite | PropertyFlags::Store}
|
||||||
|
{
|
||||||
|
Attributes::addDisplayName(debugLogRXTX, DisplayName::Hardware::debugLogRXTX);
|
||||||
|
//Attributes::addGroup(debugLogRXTX, Group::debug);
|
||||||
|
m_interfaceItems.add(debugLogRXTX);
|
||||||
|
}
|
||||||
|
|
||||||
|
CBUS::Config CBUSSettings::config() const
|
||||||
|
{
|
||||||
|
return CBUS::Config{
|
||||||
|
.debugLogRXTX = debugLogRXTX,
|
||||||
|
};
|
||||||
|
}
|
||||||
41
server/src/hardware/interface/cbus/cbussettings.hpp
Normale Datei
41
server/src/hardware/interface/cbus/cbussettings.hpp
Normale Datei
@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* This file is part of Traintastic,
|
||||||
|
* see <https://github.com/traintastic/traintastic>.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2026 Reinder Feenstra
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TRAINTASTIC_SERVER_HARDWARE_INTERFACE_CBUS_CBUSSETTINGS_HPP
|
||||||
|
#define TRAINTASTIC_SERVER_HARDWARE_INTERFACE_CBUS_CBUSSETTINGS_HPP
|
||||||
|
|
||||||
|
#include "../../../core/subobject.hpp"
|
||||||
|
#include "../../../core/property.hpp"
|
||||||
|
#include "../../../hardware/protocol/cbus/cbusconfig.hpp"
|
||||||
|
|
||||||
|
class CBUSSettings final : public SubObject
|
||||||
|
{
|
||||||
|
CLASS_ID("cbus_settings")
|
||||||
|
|
||||||
|
public:
|
||||||
|
Property<bool> debugLogRXTX;
|
||||||
|
|
||||||
|
CBUSSettings(Object& _parent, std::string_view parentPropertyName);
|
||||||
|
|
||||||
|
CBUS::Config config() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
238
server/src/hardware/interface/cbusinterface.cpp
Normale Datei
238
server/src/hardware/interface/cbusinterface.cpp
Normale Datei
@ -0,0 +1,238 @@
|
|||||||
|
/**
|
||||||
|
* This file is part of Traintastic,
|
||||||
|
* see <https://github.com/traintastic/traintastic>.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2026 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 "cbusinterface.hpp"
|
||||||
|
#include "cbus/cbussettings.hpp"
|
||||||
|
#include "../protocol/cbus/cbuskernel.hpp"
|
||||||
|
#include "../protocol/cbus/iohandler/cbuscanusbiohandler.hpp"
|
||||||
|
#include "../protocol/cbus/iohandler/cbuscanetheriohandler.hpp"
|
||||||
|
/*
|
||||||
|
#include "../protocol/cbus/simulator/cbussimulator.hpp"
|
||||||
|
*/
|
||||||
|
#include "../../core/attributes.hpp"
|
||||||
|
#include "../../core/eventloop.hpp"
|
||||||
|
#include "../../core/method.tpp"
|
||||||
|
#include "../../core/objectproperty.tpp"
|
||||||
|
#include "../../log/log.hpp"
|
||||||
|
#include "../../log/logmessageexception.hpp"
|
||||||
|
#include "../../utils/displayname.hpp"
|
||||||
|
#include "../../world/world.hpp"
|
||||||
|
|
||||||
|
CREATE_IMPL(CBUSInterface)
|
||||||
|
|
||||||
|
CBUSInterface::CBUSInterface(World& world, std::string_view _id)
|
||||||
|
: Interface(world, _id)
|
||||||
|
, type{this, "type", CBUSInterfaceType::CANUSB, PropertyFlags::ReadWrite | PropertyFlags::Store,
|
||||||
|
[this](CBUSInterfaceType /*value*/)
|
||||||
|
{
|
||||||
|
updateVisible();
|
||||||
|
}}
|
||||||
|
, device{this, "device", "", PropertyFlags::ReadWrite | PropertyFlags::Store}
|
||||||
|
, hostname{this, "hostname", "", PropertyFlags::ReadWrite | PropertyFlags::Store}
|
||||||
|
, port{this, "port", 0, PropertyFlags::ReadWrite | PropertyFlags::Store}
|
||||||
|
, cbus{this, "cbus", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject}
|
||||||
|
{
|
||||||
|
name = "CBUS/VLCB";
|
||||||
|
cbus.setValueInternal(std::make_shared<CBUSSettings>(*this, cbus.name()));
|
||||||
|
|
||||||
|
Attributes::addDisplayName(type, DisplayName::Interface::type);
|
||||||
|
Attributes::addEnabled(type, !online);
|
||||||
|
Attributes::addValues(type, CBUSInterfaceTypeValues);
|
||||||
|
m_interfaceItems.insertBefore(type, notes);
|
||||||
|
|
||||||
|
Attributes::addEnabled(device, !online);
|
||||||
|
Attributes::addVisible(device, false);
|
||||||
|
m_interfaceItems.insertBefore(device, notes);
|
||||||
|
|
||||||
|
Attributes::addDisplayName(hostname, DisplayName::IP::hostname);
|
||||||
|
Attributes::addEnabled(hostname, !online);
|
||||||
|
Attributes::addVisible(hostname, false);
|
||||||
|
m_interfaceItems.insertBefore(hostname, notes);
|
||||||
|
|
||||||
|
Attributes::addDisplayName(port, DisplayName::IP::port);
|
||||||
|
Attributes::addEnabled(port, !online);
|
||||||
|
Attributes::addVisible(port, false);
|
||||||
|
m_interfaceItems.insertBefore(port, notes);
|
||||||
|
|
||||||
|
m_interfaceItems.insertBefore(cbus, notes);
|
||||||
|
|
||||||
|
m_cbusPropertyChanged = cbus->propertyChanged.connect(
|
||||||
|
[this](BaseProperty& /*property*/)
|
||||||
|
{
|
||||||
|
if(m_kernel)
|
||||||
|
{
|
||||||
|
m_kernel->setConfig(cbus->config());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
updateVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
CBUSInterface::~CBUSInterface() = default;
|
||||||
|
|
||||||
|
void CBUSInterface::addToWorld()
|
||||||
|
{
|
||||||
|
Interface::addToWorld();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CBUSInterface::loaded()
|
||||||
|
{
|
||||||
|
Interface::loaded();
|
||||||
|
|
||||||
|
updateVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CBUSInterface::destroying()
|
||||||
|
{
|
||||||
|
m_cbusPropertyChanged.disconnect();
|
||||||
|
Interface::destroying();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CBUSInterface::worldEvent(WorldState state, WorldEvent event)
|
||||||
|
{
|
||||||
|
Interface::worldEvent(state, event);
|
||||||
|
|
||||||
|
switch(event)
|
||||||
|
{
|
||||||
|
case WorldEvent::PowerOff:
|
||||||
|
if(m_kernel)
|
||||||
|
{
|
||||||
|
m_kernel->trackOff();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WorldEvent::PowerOn:
|
||||||
|
if(m_kernel)
|
||||||
|
{
|
||||||
|
m_kernel->trackOn();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WorldEvent::Stop:
|
||||||
|
if(m_kernel)
|
||||||
|
{
|
||||||
|
m_kernel->requestEmergencyStop();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WorldEvent::Run:
|
||||||
|
if(m_kernel)
|
||||||
|
{
|
||||||
|
// TODO: send all known speed values
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CBUSInterface::setOnline(bool& value, bool simulation)
|
||||||
|
{
|
||||||
|
if(!m_kernel && value)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
(void)simulation; // silence warning until simulation is added
|
||||||
|
/* TODO: simulation support
|
||||||
|
if(simulation)
|
||||||
|
{
|
||||||
|
m_simulator = std::make_unique<CBUS::Simulator>();
|
||||||
|
m_kernel = CBUS::Kernel::create<CBUS::SimulationIOHandler>(id.value(), cbus->config(), std::ref(*m_simulator));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
switch(type)
|
||||||
|
{
|
||||||
|
case CBUSInterfaceType::CANUSB:
|
||||||
|
m_kernel = CBUS::Kernel::create<CBUS::CANUSBIOHandler>(id.value(), cbus->config(), device.value());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CBUSInterfaceType::CANEther:
|
||||||
|
m_kernel = CBUS::Kernel::create<CBUS::CANEtherIOHandler>(id.value(), cbus->config(), hostname.value(), port.value());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(InterfaceState::Initializing);
|
||||||
|
|
||||||
|
m_kernel->setOnStarted(
|
||||||
|
[this]()
|
||||||
|
{
|
||||||
|
setState(InterfaceState::Online);
|
||||||
|
});
|
||||||
|
m_kernel->setOnError(
|
||||||
|
[this]()
|
||||||
|
{
|
||||||
|
setState(InterfaceState::Error);
|
||||||
|
online = false; // communication no longer possible
|
||||||
|
});
|
||||||
|
m_kernel->onTrackOff =
|
||||||
|
[this]()
|
||||||
|
{
|
||||||
|
m_world.powerOff();
|
||||||
|
};
|
||||||
|
m_kernel->onTrackOn =
|
||||||
|
[this]()
|
||||||
|
{
|
||||||
|
m_world.powerOn();
|
||||||
|
};
|
||||||
|
m_kernel->onEmergencyStop =
|
||||||
|
[this]()
|
||||||
|
{
|
||||||
|
m_world.stop();
|
||||||
|
};
|
||||||
|
|
||||||
|
m_kernel->start();
|
||||||
|
}
|
||||||
|
catch(const LogMessageException& e)
|
||||||
|
{
|
||||||
|
setState(InterfaceState::Offline);
|
||||||
|
Log::log(*this, e.message(), e.args());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(m_kernel && !value)
|
||||||
|
{
|
||||||
|
m_kernel->stop();
|
||||||
|
EventLoop::deleteLater(m_kernel.release());
|
||||||
|
/*
|
||||||
|
EventLoop::deleteLater(m_simulator.release());
|
||||||
|
*/
|
||||||
|
|
||||||
|
if(status->state != InterfaceState::Error)
|
||||||
|
{
|
||||||
|
setState(InterfaceState::Offline);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CBUSInterface::updateVisible()
|
||||||
|
{
|
||||||
|
const bool isSerial = (type == CBUSInterfaceType::CANUSB);
|
||||||
|
Attributes::setVisible(device, isSerial);
|
||||||
|
|
||||||
|
const bool isNetwork = (type == CBUSInterfaceType::CANEther);
|
||||||
|
Attributes::setVisible(hostname, isNetwork);
|
||||||
|
Attributes::setVisible(port, isNetwork);
|
||||||
|
}
|
||||||
72
server/src/hardware/interface/cbusinterface.hpp
Normale Datei
72
server/src/hardware/interface/cbusinterface.hpp
Normale Datei
@ -0,0 +1,72 @@
|
|||||||
|
/**
|
||||||
|
* This file is part of Traintastic,
|
||||||
|
* see <https://github.com/traintastic/traintastic>.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2026 Reinder Feenstra
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TRAINTASTIC_SERVER_HARDWARE_INTERFACE_CBUSINTERFACE_HPP
|
||||||
|
#define TRAINTASTIC_SERVER_HARDWARE_INTERFACE_CBUSINTERFACE_HPP
|
||||||
|
|
||||||
|
#include "interface.hpp"
|
||||||
|
#include "../../core/serialdeviceproperty.hpp"
|
||||||
|
#include <traintastic/enum/cbusinterfacetype.hpp>
|
||||||
|
|
||||||
|
class CBUSSettings;
|
||||||
|
|
||||||
|
namespace CBUS {
|
||||||
|
class Kernel;
|
||||||
|
class Simulator {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief CBUS hardware interface
|
||||||
|
*/
|
||||||
|
class CBUSInterface final
|
||||||
|
: public Interface
|
||||||
|
{
|
||||||
|
CLASS_ID("interface.cbus")
|
||||||
|
DEFAULT_ID("cbus")
|
||||||
|
CREATE_DEF(CBUSInterface)
|
||||||
|
|
||||||
|
public:
|
||||||
|
Property<CBUSInterfaceType> type;
|
||||||
|
SerialDeviceProperty device;
|
||||||
|
Property<std::string> hostname;
|
||||||
|
Property<uint16_t> port;
|
||||||
|
ObjectProperty<CBUSSettings> cbus;
|
||||||
|
|
||||||
|
CBUSInterface(World& world, std::string_view _id);
|
||||||
|
~CBUSInterface() final;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void addToWorld() final;
|
||||||
|
void loaded() final;
|
||||||
|
void destroying() final;
|
||||||
|
void worldEvent(WorldState state, WorldEvent event) final;
|
||||||
|
|
||||||
|
bool setOnline(bool& value, bool simulation) final;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<CBUS::Kernel> m_kernel;
|
||||||
|
std::unique_ptr<CBUS::Simulator> m_simulator;
|
||||||
|
boost::signals2::connection m_cbusPropertyChanged;
|
||||||
|
|
||||||
|
void updateVisible();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@ -1,9 +1,8 @@
|
|||||||
/**
|
/**
|
||||||
* server/src/hardware/interface/interfaces.cpp
|
* This file is part of Traintastic,
|
||||||
|
* see <https://github.com/traintastic/traintastic>.
|
||||||
*
|
*
|
||||||
* This file is part of the traintastic source code.
|
* Copyright (C) 2021-2026 Reinder Feenstra
|
||||||
*
|
|
||||||
* Copyright (C) 2021-2025 Reinder Feenstra
|
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License
|
* modify it under the terms of the GNU General Public License
|
||||||
@ -25,6 +24,7 @@
|
|||||||
#include "../../world/world.hpp"
|
#include "../../world/world.hpp"
|
||||||
#include "../../utils/makearray.hpp"
|
#include "../../utils/makearray.hpp"
|
||||||
|
|
||||||
|
#include "cbusinterface.hpp"
|
||||||
#include "dccexinterface.hpp"
|
#include "dccexinterface.hpp"
|
||||||
#include "ecosinterface.hpp"
|
#include "ecosinterface.hpp"
|
||||||
#include "hsi88.hpp"
|
#include "hsi88.hpp"
|
||||||
@ -39,6 +39,7 @@
|
|||||||
std::span<const std::string_view> Interfaces::classList()
|
std::span<const std::string_view> Interfaces::classList()
|
||||||
{
|
{
|
||||||
static constexpr auto classes = makeArray(
|
static constexpr auto classes = makeArray(
|
||||||
|
CBUSInterface::classId,
|
||||||
DCCEXInterface::classId,
|
DCCEXInterface::classId,
|
||||||
ECoSInterface::classId,
|
ECoSInterface::classId,
|
||||||
HSI88Interface::classId,
|
HSI88Interface::classId,
|
||||||
@ -55,6 +56,7 @@ std::span<const std::string_view> Interfaces::classList()
|
|||||||
|
|
||||||
std::shared_ptr<Interface> Interfaces::create(World& world, std::string_view classId, std::string_view id)
|
std::shared_ptr<Interface> Interfaces::create(World& world, std::string_view classId, std::string_view id)
|
||||||
{
|
{
|
||||||
|
IF_CLASSID_CREATE(CBUSInterface)
|
||||||
IF_CLASSID_CREATE(DCCEXInterface)
|
IF_CLASSID_CREATE(DCCEXInterface)
|
||||||
IF_CLASSID_CREATE(ECoSInterface)
|
IF_CLASSID_CREATE(ECoSInterface)
|
||||||
IF_CLASSID_CREATE(HSI88Interface)
|
IF_CLASSID_CREATE(HSI88Interface)
|
||||||
|
|||||||
168
server/src/hardware/protocol/cbus/cbusgetminorpriority.hpp
Normale Datei
168
server/src/hardware/protocol/cbus/cbusgetminorpriority.hpp
Normale Datei
@ -0,0 +1,168 @@
|
|||||||
|
/**
|
||||||
|
* This file is part of Traintastic,
|
||||||
|
* see <https://github.com/traintastic/traintastic>.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2026 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_CBUS_CBUSGETMINORPRIORITY_HPP
|
||||||
|
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_CBUS_CBUSGETMINORPRIORITY_HPP
|
||||||
|
|
||||||
|
#include "cbusopcode.hpp"
|
||||||
|
#include "cbuspriority.hpp"
|
||||||
|
|
||||||
|
namespace CBUS {
|
||||||
|
|
||||||
|
constexpr MinorPriority getMinorPriority(OpCode opCode)
|
||||||
|
{
|
||||||
|
switch(opCode)
|
||||||
|
{
|
||||||
|
using enum OpCode;
|
||||||
|
|
||||||
|
case HLT:
|
||||||
|
case ARST:
|
||||||
|
case RESTP:
|
||||||
|
return MinorPriority::High;
|
||||||
|
|
||||||
|
case BON:
|
||||||
|
case TOF:
|
||||||
|
case TON:
|
||||||
|
case ESTOP:
|
||||||
|
case RTOF:
|
||||||
|
case RTON:
|
||||||
|
return MinorPriority::AboveNormal;
|
||||||
|
|
||||||
|
case ACK:
|
||||||
|
case NAK:
|
||||||
|
case RSTAT:
|
||||||
|
case RQMN:
|
||||||
|
case KLOC:
|
||||||
|
case QLOC:
|
||||||
|
case DKEEP:
|
||||||
|
case DBG1:
|
||||||
|
case RLOC:
|
||||||
|
case QCON:
|
||||||
|
case ALOC:
|
||||||
|
case STMOD:
|
||||||
|
case PCON:
|
||||||
|
case KCON:
|
||||||
|
case DSPD:
|
||||||
|
case DFLG:
|
||||||
|
case DFNON:
|
||||||
|
case DFNOF:
|
||||||
|
case DFUN:
|
||||||
|
case GLOC:
|
||||||
|
case ERR:
|
||||||
|
case RDCC3:
|
||||||
|
case WCVO:
|
||||||
|
case WCVB:
|
||||||
|
case QCVS:
|
||||||
|
case PCVS:
|
||||||
|
case RDCC4:
|
||||||
|
case WCVS:
|
||||||
|
case RDCC5:
|
||||||
|
case WCVOA:
|
||||||
|
case CABDAT:
|
||||||
|
case FCLK:
|
||||||
|
case RDCC6:
|
||||||
|
case PLOC:
|
||||||
|
case STAT:
|
||||||
|
return MinorPriority::Normal;
|
||||||
|
|
||||||
|
case QNN:
|
||||||
|
case RQNP:
|
||||||
|
case EXTC:
|
||||||
|
case SNN:
|
||||||
|
case SSTAT:
|
||||||
|
case NNRSM:
|
||||||
|
case RQNN:
|
||||||
|
case NNREL:
|
||||||
|
case NNACK:
|
||||||
|
case NNLRN:
|
||||||
|
case NNULN:
|
||||||
|
case NNCLR:
|
||||||
|
case NNEVN:
|
||||||
|
case NERD:
|
||||||
|
case RQEVN:
|
||||||
|
case WRACK:
|
||||||
|
case RQDAT:
|
||||||
|
case RQDDS:
|
||||||
|
case BOOTM:
|
||||||
|
case ENUM:
|
||||||
|
case NNRST:
|
||||||
|
case EXTC1:
|
||||||
|
case CMDERR:
|
||||||
|
case EVNLF:
|
||||||
|
case NVRD:
|
||||||
|
case NENRD:
|
||||||
|
case RQNPN:
|
||||||
|
case NUMEV:
|
||||||
|
case CANID:
|
||||||
|
case EXTC2:
|
||||||
|
case ACON:
|
||||||
|
case ACOF:
|
||||||
|
case AREQ:
|
||||||
|
case ARON:
|
||||||
|
case AROF:
|
||||||
|
case EVULN:
|
||||||
|
case NVSET:
|
||||||
|
case NVANS:
|
||||||
|
case ASON:
|
||||||
|
case ASOF:
|
||||||
|
case ASRQ:
|
||||||
|
case PARAN:
|
||||||
|
case REVAL:
|
||||||
|
case ARSON:
|
||||||
|
case ARSOF:
|
||||||
|
case EXTC3:
|
||||||
|
case ACON1:
|
||||||
|
case ACOF1:
|
||||||
|
case REQEV:
|
||||||
|
case ARON1:
|
||||||
|
case AROF1:
|
||||||
|
case NEVAL:
|
||||||
|
case PNN:
|
||||||
|
case ASON1:
|
||||||
|
case ASOF1:
|
||||||
|
case ARSON1:
|
||||||
|
case ARSOF1:
|
||||||
|
case EXTC4:
|
||||||
|
case NAME:
|
||||||
|
case PARAMS:
|
||||||
|
case ACON3:
|
||||||
|
case ACOF3:
|
||||||
|
case ENRSP:
|
||||||
|
case ARON3:
|
||||||
|
case AROF3:
|
||||||
|
case EVLRNI:
|
||||||
|
case ACDAT:
|
||||||
|
case ARDAT:
|
||||||
|
case ASON3:
|
||||||
|
case ASOF3:
|
||||||
|
case DDES:
|
||||||
|
case DDRS:
|
||||||
|
case ARSON3:
|
||||||
|
case ARSOF3:
|
||||||
|
case EXTC6:
|
||||||
|
return MinorPriority::Low;
|
||||||
|
}
|
||||||
|
return MinorPriority::Low;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
221
server/src/hardware/protocol/cbus/cbuskernel.cpp
Normale Datei
221
server/src/hardware/protocol/cbus/cbuskernel.cpp
Normale Datei
@ -0,0 +1,221 @@
|
|||||||
|
/**
|
||||||
|
* This file is part of Traintastic,
|
||||||
|
* see <https://github.com/traintastic/traintastic>.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2026 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 "cbuskernel.hpp"
|
||||||
|
#include "cbusmessages.hpp"
|
||||||
|
#include "cbustostring.hpp"
|
||||||
|
/*
|
||||||
|
#include "simulator/cbussimulator.hpp"
|
||||||
|
*/
|
||||||
|
#include "../../../core/eventloop.hpp"
|
||||||
|
#include "../../../log/log.hpp"
|
||||||
|
#include "../../../log/logmessageexception.hpp"
|
||||||
|
#include "../../../utils/setthreadname.hpp"
|
||||||
|
|
||||||
|
namespace CBUS {
|
||||||
|
|
||||||
|
Kernel::Kernel(std::string logId_, const Config& config, bool simulation)
|
||||||
|
: KernelBase(std::move(logId_))
|
||||||
|
, m_simulation{simulation}
|
||||||
|
, m_config{config}
|
||||||
|
{
|
||||||
|
assert(isEventLoopThread());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Kernel::setConfig(const Config& config)
|
||||||
|
{
|
||||||
|
assert(isEventLoopThread());
|
||||||
|
|
||||||
|
m_ioContext.post(
|
||||||
|
[this, newConfig=config]()
|
||||||
|
{
|
||||||
|
m_config = newConfig;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Kernel::start()
|
||||||
|
{
|
||||||
|
assert(isEventLoopThread());
|
||||||
|
assert(m_ioHandler);
|
||||||
|
|
||||||
|
m_thread = std::thread(
|
||||||
|
[this]()
|
||||||
|
{
|
||||||
|
setThreadName("cbus");
|
||||||
|
auto work = std::make_shared<boost::asio::io_context::work>(m_ioContext);
|
||||||
|
m_ioContext.run();
|
||||||
|
});
|
||||||
|
|
||||||
|
m_ioContext.post(
|
||||||
|
[this]()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
m_ioHandler->start();
|
||||||
|
}
|
||||||
|
catch(const LogMessageException& e)
|
||||||
|
{
|
||||||
|
EventLoop::call(
|
||||||
|
[this, e]()
|
||||||
|
{
|
||||||
|
Log::log(logId, e.message(), e.args());
|
||||||
|
error();
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Kernel::stop()
|
||||||
|
{
|
||||||
|
assert(isEventLoopThread());
|
||||||
|
|
||||||
|
m_ioContext.post(
|
||||||
|
[this]()
|
||||||
|
{
|
||||||
|
m_ioHandler->stop();
|
||||||
|
});
|
||||||
|
|
||||||
|
m_ioContext.stop();
|
||||||
|
|
||||||
|
m_thread.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Kernel::started()
|
||||||
|
{
|
||||||
|
assert(isKernelThread());
|
||||||
|
|
||||||
|
send(RequestCommandStationStatus());
|
||||||
|
send(QueryNodeNumber());
|
||||||
|
|
||||||
|
::KernelBase::started();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Kernel::receive(uint8_t /*canId*/, const Message& message)
|
||||||
|
{
|
||||||
|
assert(isKernelThread());
|
||||||
|
|
||||||
|
if(m_config.debugLogRXTX)
|
||||||
|
{
|
||||||
|
EventLoop::call(
|
||||||
|
[this, msg=toString(message)]()
|
||||||
|
{
|
||||||
|
Log::log(logId, LogMessage::D2002_RX_X, msg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(message.opCode)
|
||||||
|
{
|
||||||
|
case OpCode::TOF:
|
||||||
|
m_trackOn = false;
|
||||||
|
if(onTrackOff) [[likely]]
|
||||||
|
{
|
||||||
|
EventLoop::call(onTrackOff);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OpCode::TON:
|
||||||
|
m_trackOn = true;
|
||||||
|
if(onTrackOn) [[likely]]
|
||||||
|
{
|
||||||
|
EventLoop::call(onTrackOn);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OpCode::ESTOP:
|
||||||
|
if(onEmergencyStop) [[likely]]
|
||||||
|
{
|
||||||
|
EventLoop::call(onEmergencyStop);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Kernel::trackOff()
|
||||||
|
{
|
||||||
|
assert(isEventLoopThread());
|
||||||
|
|
||||||
|
m_ioContext.post(
|
||||||
|
[this]()
|
||||||
|
{
|
||||||
|
if(m_trackOn)
|
||||||
|
{
|
||||||
|
send(RequestTrackOff());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Kernel::trackOn()
|
||||||
|
{
|
||||||
|
assert(isEventLoopThread());
|
||||||
|
|
||||||
|
m_ioContext.post(
|
||||||
|
[this]()
|
||||||
|
{
|
||||||
|
if(!m_trackOn)
|
||||||
|
{
|
||||||
|
send(RequestTrackOn());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Kernel::requestEmergencyStop()
|
||||||
|
{
|
||||||
|
assert(isEventLoopThread());
|
||||||
|
|
||||||
|
m_ioContext.post(
|
||||||
|
[this]()
|
||||||
|
{
|
||||||
|
send(RequestEmergencyStop());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Kernel::setIOHandler(std::unique_ptr<IOHandler> handler)
|
||||||
|
{
|
||||||
|
assert(isEventLoopThread());
|
||||||
|
assert(handler);
|
||||||
|
assert(!m_ioHandler);
|
||||||
|
m_ioHandler = std::move(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Kernel::send(const Message& message)
|
||||||
|
{
|
||||||
|
assert(isKernelThread());
|
||||||
|
|
||||||
|
if(m_config.debugLogRXTX)
|
||||||
|
{
|
||||||
|
EventLoop::call(
|
||||||
|
[this, msg=toString(message)]()
|
||||||
|
{
|
||||||
|
Log::log(logId, LogMessage::D2001_TX_X, msg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if(auto ec = m_ioHandler->send(message); ec)
|
||||||
|
{
|
||||||
|
(void)ec; // FIXME: handle error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
110
server/src/hardware/protocol/cbus/cbuskernel.hpp
Normale Datei
110
server/src/hardware/protocol/cbus/cbuskernel.hpp
Normale Datei
@ -0,0 +1,110 @@
|
|||||||
|
/**
|
||||||
|
* This file is part of Traintastic,
|
||||||
|
* see <https://github.com/traintastic/traintastic>.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2026 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_CBUS_CBUSKERNEL_HPP
|
||||||
|
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_CBUS_CBUSKERNEL_HPP
|
||||||
|
|
||||||
|
#include "../kernelbase.hpp"
|
||||||
|
#include <span>
|
||||||
|
#include <traintastic/enum/direction.hpp>
|
||||||
|
#include "cbusconfig.hpp"
|
||||||
|
#include "iohandler/cbusiohandler.hpp"
|
||||||
|
|
||||||
|
namespace CBUS {
|
||||||
|
|
||||||
|
class Kernel : public ::KernelBase
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::function<void()> onTrackOff;
|
||||||
|
std::function<void()> onTrackOn;
|
||||||
|
std::function<void()> onEmergencyStop;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create kernel and IO handler
|
||||||
|
*
|
||||||
|
* @param[in] config CBUS configuration
|
||||||
|
* @param[in] args IO handler arguments
|
||||||
|
* @return The kernel instance
|
||||||
|
*/
|
||||||
|
template<class IOHandlerType, class... Args>
|
||||||
|
static std::unique_ptr<Kernel> create(std::string logId_, const Config& config, Args... args)
|
||||||
|
{
|
||||||
|
static_assert(std::is_base_of_v<IOHandler, IOHandlerType>);
|
||||||
|
std::unique_ptr<Kernel> kernel{new Kernel(std::move(logId_), config, isSimulation<IOHandlerType>())};
|
||||||
|
kernel->setIOHandler(std::make_unique<IOHandlerType>(*kernel, std::forward<Args>(args)...));
|
||||||
|
return kernel;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
bool isKernelThread() const
|
||||||
|
{
|
||||||
|
return std::this_thread::get_id() == m_thread.get_id();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set CBUS configuration
|
||||||
|
*
|
||||||
|
* @param[in] config The CBUS configuration
|
||||||
|
*/
|
||||||
|
void setConfig(const Config& config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Start the kernel and IO handler
|
||||||
|
*/
|
||||||
|
void start();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Stop the kernel and IO handler
|
||||||
|
*/
|
||||||
|
void stop();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Notify kernel the IO handler is started.
|
||||||
|
* \note This function must run in the kernel's IO context
|
||||||
|
*/
|
||||||
|
void started() final;
|
||||||
|
|
||||||
|
void receive(uint8_t canId, const Message& message);
|
||||||
|
|
||||||
|
void trackOff();
|
||||||
|
void trackOn();
|
||||||
|
void requestEmergencyStop();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<IOHandler> m_ioHandler;
|
||||||
|
const bool m_simulation;
|
||||||
|
Config m_config;
|
||||||
|
bool m_trackOn = false;
|
||||||
|
|
||||||
|
Kernel(std::string logId_, const Config& config, bool simulation);
|
||||||
|
|
||||||
|
Kernel(const Kernel&) = delete;
|
||||||
|
Kernel& operator =(const Kernel&) = delete;
|
||||||
|
|
||||||
|
void setIOHandler(std::unique_ptr<IOHandler> handler);
|
||||||
|
|
||||||
|
void send(const Message& message);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@ -167,6 +167,11 @@ enum class OpCode : uint8_t
|
|||||||
EXTC6 = 0xFF, //!< Extended op-code with 6 data bytes
|
EXTC6 = 0xFF, //!< Extended op-code with 6 data bytes
|
||||||
};
|
};
|
||||||
|
|
||||||
|
constexpr uint8_t dataSize(OpCode opc)
|
||||||
|
{
|
||||||
|
return static_cast<uint8_t>(opc) >> 5; // highest 3 bits determine data length
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
46
server/src/hardware/protocol/cbus/cbuspriority.hpp
Normale Datei
46
server/src/hardware/protocol/cbus/cbuspriority.hpp
Normale Datei
@ -0,0 +1,46 @@
|
|||||||
|
/**
|
||||||
|
* This file is part of Traintastic,
|
||||||
|
* see <https://github.com/traintastic/traintastic>.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2026 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_CBUS_CBUSPRIORITY_HPP
|
||||||
|
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_CBUS_CBUSPRIORITY_HPP
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace CBUS {
|
||||||
|
|
||||||
|
enum class MajorPriority : uint8_t
|
||||||
|
{
|
||||||
|
Highest = 0b00,
|
||||||
|
Next = 0b01,
|
||||||
|
Lowest = 0b10,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class MinorPriority : uint8_t
|
||||||
|
{
|
||||||
|
High = 0b00,
|
||||||
|
AboveNormal = 0b01,
|
||||||
|
Normal = 0b10,
|
||||||
|
Low = 0b11,
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
381
server/src/hardware/protocol/cbus/cbustostring.cpp
Normale Datei
381
server/src/hardware/protocol/cbus/cbustostring.cpp
Normale Datei
@ -0,0 +1,381 @@
|
|||||||
|
/**
|
||||||
|
* This file is part of Traintastic,
|
||||||
|
* see <https://github.com/traintastic/traintastic>.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2026 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 "cbustostring.hpp"
|
||||||
|
#include <format>
|
||||||
|
#include "cbusmessages.hpp"
|
||||||
|
#include "../../../utils/tohex.hpp"
|
||||||
|
|
||||||
|
namespace CBUS {
|
||||||
|
|
||||||
|
std::string toString(const Message& message)
|
||||||
|
{
|
||||||
|
std::string s(toString(message.opCode));
|
||||||
|
|
||||||
|
switch (message.opCode)
|
||||||
|
{
|
||||||
|
using enum OpCode;
|
||||||
|
|
||||||
|
// 00-1F – 0 data byte packets:
|
||||||
|
case ACK:
|
||||||
|
case NAK:
|
||||||
|
case HLT:
|
||||||
|
case BON:
|
||||||
|
case TOF:
|
||||||
|
case TON:
|
||||||
|
case ESTOP:
|
||||||
|
case ARST:
|
||||||
|
case RTOF:
|
||||||
|
case RTON:
|
||||||
|
case RESTP:
|
||||||
|
case RSTAT:
|
||||||
|
case QNN:
|
||||||
|
case RQNP:
|
||||||
|
case RQMN:
|
||||||
|
break; // no data
|
||||||
|
|
||||||
|
// 20–3F - 1 data byte packets:
|
||||||
|
case KLOC:
|
||||||
|
case QLOC:
|
||||||
|
case DKEEP:
|
||||||
|
{
|
||||||
|
const auto& m = static_cast<const EngineMessage&>(message);
|
||||||
|
s.append(std::format(" session={}", m.session));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DBG1:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EXTC:
|
||||||
|
break;
|
||||||
|
|
||||||
|
// 40–5F - 2 data byte packets:
|
||||||
|
case RLOC:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case QCON:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SNN:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ALOC:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case STMOD:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PCON:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case KCON:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DSPD:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DFLG:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DFNON:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DFNOF:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SSTAT:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NNRSM:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RQNN:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NNREL:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NNACK:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NNLRN:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NNULN:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NNCLR:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NNEVN:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NERD:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RQEVN:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WRACK:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RQDAT:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RQDDS:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BOOTM:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ENUM:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NNRST:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EXTC1:
|
||||||
|
break;
|
||||||
|
|
||||||
|
// 60-7F - 3 data byte packets:
|
||||||
|
case DFUN:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GLOC:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ERR:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CMDERR:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EVNLF:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NVRD:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NENRD:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RQNPN:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NUMEV:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CANID:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EXTC2:
|
||||||
|
break;
|
||||||
|
|
||||||
|
// 80-9F - 4 data byte packets:
|
||||||
|
case RDCC3:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WCVO:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WCVB:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case QCVS:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PCVS:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ACON:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ACOF:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AREQ:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ARON:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AROF:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EVULN:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NVSET:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NVANS:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ASON:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ASOF:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ASRQ:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PARAN:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case REVAL:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ARSON:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ARSOF:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EXTC3:
|
||||||
|
break;
|
||||||
|
|
||||||
|
// A0-BF - 5 data byte packets:
|
||||||
|
case RDCC4:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WCVS:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ACON1:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ACOF1:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case REQEV:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ARON1:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AROF1:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NEVAL:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PNN:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ASON1:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ASOF1:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ARSON1:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ARSOF1:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EXTC4:
|
||||||
|
break;
|
||||||
|
|
||||||
|
// C0-DF - 6 data byte packets:
|
||||||
|
case RDCC5:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WCVOA:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CABDAT:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FCLK:
|
||||||
|
break;
|
||||||
|
|
||||||
|
// E0-FF - 7 data byte packets:
|
||||||
|
case RDCC6:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PLOC:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NAME:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case STAT:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PARAMS:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ACON3:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ACOF3:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ENRSP:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ARON3:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AROF3:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EVLRNI:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ACDAT:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ARDAT:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ASON3:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ASOF3:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DDES:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DDRS:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ARSON3:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ARSOF3:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EXTC6:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
s.append(" [");
|
||||||
|
s.append(toHex(&message, message.size()));
|
||||||
|
s.append("]");
|
||||||
|
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -27,13 +27,15 @@
|
|||||||
|
|
||||||
namespace CBUS {
|
namespace CBUS {
|
||||||
|
|
||||||
|
struct Message;
|
||||||
|
|
||||||
constexpr std::string_view toString(OpCode opCode)
|
constexpr std::string_view toString(OpCode opCode)
|
||||||
{
|
{
|
||||||
switch(opCode)
|
switch(opCode)
|
||||||
{
|
{
|
||||||
using enum OpCode;
|
using enum OpCode;
|
||||||
|
|
||||||
// 00-1F – 0 data bytes packets:
|
// 00-1F – 0 data byte packets:
|
||||||
case ACK: return "ACK";
|
case ACK: return "ACK";
|
||||||
case NAK: return "NAK";
|
case NAK: return "NAK";
|
||||||
case HLT: return "HLT";
|
case HLT: return "HLT";
|
||||||
@ -171,6 +173,8 @@ constexpr std::string_view toString(OpCode opCode)
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string toString(const Message& message);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
165
server/src/hardware/protocol/cbus/iohandler/cbusasciiiohandler.cpp
Normale Datei
165
server/src/hardware/protocol/cbus/iohandler/cbusasciiiohandler.cpp
Normale Datei
@ -0,0 +1,165 @@
|
|||||||
|
/**
|
||||||
|
* This file is part of Traintastic,
|
||||||
|
* see <https://github.com/traintastic/traintastic>.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2026 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 "cbusasciiiohandler.hpp"
|
||||||
|
#include "../cbusgetminorpriority.hpp"
|
||||||
|
#include "../cbuskernel.hpp"
|
||||||
|
#include "../messages/cbusmessage.hpp"
|
||||||
|
#include "../../../../core/eventloop.hpp"
|
||||||
|
#include "../../../../log/log.hpp"
|
||||||
|
#include "../../../../utils/tohex.hpp"
|
||||||
|
#include "../../../../utils/fromchars.hpp"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::string buildFrame(CBUS::MajorPriority majorPriority, CBUS::MinorPriority minorPriority, uint8_t canId, const CBUS::Message& message)
|
||||||
|
{
|
||||||
|
const uint16_t sid =
|
||||||
|
(static_cast<uint16_t>(majorPriority) << 14) |
|
||||||
|
(static_cast<uint16_t>(minorPriority) << 12) |
|
||||||
|
(static_cast<uint16_t>(canId) << 5);
|
||||||
|
|
||||||
|
std::string frame(":S");
|
||||||
|
frame.append(toHex(sid));
|
||||||
|
frame.append("N");
|
||||||
|
frame.append(toHex(&message, message.size()));
|
||||||
|
frame.append(";");
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace CBUS {
|
||||||
|
|
||||||
|
ASCIIIOHandler::ASCIIIOHandler(Kernel& kernel, uint8_t canId)
|
||||||
|
: IOHandler(kernel, canId)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
std::error_code ASCIIIOHandler::send(const Message& message)
|
||||||
|
{
|
||||||
|
const bool wasEmpty = m_writeQueue.empty();
|
||||||
|
|
||||||
|
// FIXME: handle priority queueing
|
||||||
|
// FIXME: what to do with MajorPriority?
|
||||||
|
m_writeQueue.emplace(buildFrame(MajorPriority::Lowest, getMinorPriority(message.opCode), m_canId, message));
|
||||||
|
|
||||||
|
if(wasEmpty)
|
||||||
|
{
|
||||||
|
write();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void ASCIIIOHandler::logDropIfNonZeroAndReset(size_t& drop)
|
||||||
|
{
|
||||||
|
if(drop != 0)
|
||||||
|
{
|
||||||
|
EventLoop::call(
|
||||||
|
[this, drop]()
|
||||||
|
{
|
||||||
|
Log::log(m_kernel.logId, LogMessage::W2001_RECEIVED_MALFORMED_DATA_DROPPED_X_BYTES, drop);
|
||||||
|
});
|
||||||
|
drop = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ASCIIIOHandler::processRead(std::size_t bytesTransferred)
|
||||||
|
{
|
||||||
|
constexpr size_t maxFrameSize = 24; // :SXXXXNXXXXXXXXXXXXXXXX;
|
||||||
|
|
||||||
|
std::string_view buffer{m_readBuffer.data(), m_readBufferOffset + bytesTransferred};
|
||||||
|
|
||||||
|
size_t drop = 0;
|
||||||
|
|
||||||
|
while(!buffer.empty())
|
||||||
|
{
|
||||||
|
logDropIfNonZeroAndReset(drop);
|
||||||
|
|
||||||
|
if(auto pos = buffer.find(':'); pos != 0)
|
||||||
|
{
|
||||||
|
if(pos == std::string_view::npos)
|
||||||
|
{
|
||||||
|
// no start marker drop all bytes:
|
||||||
|
drop += buffer.size();
|
||||||
|
buffer = {};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// drop bytes before start marker:
|
||||||
|
drop += pos;
|
||||||
|
buffer.remove_prefix(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto end = buffer.find(';');
|
||||||
|
if(end == std::string_view::npos)
|
||||||
|
{
|
||||||
|
// no end marker yet, wait for more data
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string_view frame{buffer.data(), end + 1};
|
||||||
|
|
||||||
|
buffer.remove_prefix(frame.size()); // consume frame
|
||||||
|
|
||||||
|
if(frame.size() > maxFrameSize)
|
||||||
|
{
|
||||||
|
drop += frame.size();
|
||||||
|
}
|
||||||
|
else if(frame[1] == 'S' && frame[6] == 'N') // CBUS only uses standard non RTR CAN frames
|
||||||
|
{
|
||||||
|
uint16_t sid;
|
||||||
|
if(fromChars(frame.substr(2, sizeof(sid) * 2), sid, 16).ec != std::errc())
|
||||||
|
{
|
||||||
|
continue; // error reading, ignore frame
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto dataLength = (frame.size() - 7) / 2;
|
||||||
|
std::array<uint8_t, 8> data;
|
||||||
|
std::errc ec = std::errc();
|
||||||
|
for(size_t i = 0; i < dataLength; ++i)
|
||||||
|
{
|
||||||
|
ec = fromChars(frame.substr(7 + i * 2, 2), data[i], 16).ec;
|
||||||
|
if(ec != std::errc())
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(ec != std::errc())
|
||||||
|
{
|
||||||
|
continue; // error reading, ignore frame
|
||||||
|
}
|
||||||
|
|
||||||
|
m_kernel.receive((sid >> 5) & 0x7F, *reinterpret_cast<const Message*>(data.data()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logDropIfNonZeroAndReset(drop);
|
||||||
|
|
||||||
|
if(buffer.size() != 0)
|
||||||
|
{
|
||||||
|
memmove(m_readBuffer.data(), buffer.data(), buffer.size());
|
||||||
|
}
|
||||||
|
m_readBufferOffset = buffer.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
53
server/src/hardware/protocol/cbus/iohandler/cbusasciiiohandler.hpp
Normale Datei
53
server/src/hardware/protocol/cbus/iohandler/cbusasciiiohandler.hpp
Normale Datei
@ -0,0 +1,53 @@
|
|||||||
|
/**
|
||||||
|
* This file is part of Traintastic,
|
||||||
|
* see <https://github.com/traintastic/traintastic>.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2026 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_CBUS_IOHANDLER_CBUSASCIIIOHANDLER_HPP
|
||||||
|
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_CBUS_IOHANDLER_CBUSASCIIIOHANDLER_HPP
|
||||||
|
|
||||||
|
#include "cbusiohandler.hpp"
|
||||||
|
#include <array>
|
||||||
|
#include <string>
|
||||||
|
#include <queue>
|
||||||
|
|
||||||
|
namespace CBUS {
|
||||||
|
|
||||||
|
class ASCIIIOHandler : public IOHandler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
[[nodiscard]] std::error_code send(const Message& message) override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::array<char, 1024> m_readBuffer;
|
||||||
|
size_t m_readBufferOffset;
|
||||||
|
std::queue<std::string> m_writeQueue;
|
||||||
|
|
||||||
|
ASCIIIOHandler(Kernel& kernel, uint8_t canId);
|
||||||
|
|
||||||
|
void logDropIfNonZeroAndReset(size_t& drop);
|
||||||
|
void processRead(std::size_t bytesTransferred);
|
||||||
|
|
||||||
|
virtual void read() = 0;
|
||||||
|
virtual void write() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
135
server/src/hardware/protocol/cbus/iohandler/cbuscanetheriohandler.cpp
Normale Datei
135
server/src/hardware/protocol/cbus/iohandler/cbuscanetheriohandler.cpp
Normale Datei
@ -0,0 +1,135 @@
|
|||||||
|
/**
|
||||||
|
* This file is part of Traintastic,
|
||||||
|
* see <https://github.com/traintastic/traintastic>.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2026 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 "cbuscanetheriohandler.hpp"
|
||||||
|
#include <boost/asio/write.hpp>
|
||||||
|
#include "../cbuskernel.hpp"
|
||||||
|
#include "../../../../core/eventloop.hpp"
|
||||||
|
#include "../../../../log/log.hpp"
|
||||||
|
#include "../../../../log/logmessageexception.hpp"
|
||||||
|
|
||||||
|
namespace CBUS {
|
||||||
|
|
||||||
|
CANEtherIOHandler::CANEtherIOHandler(Kernel& kernel, std::string hostname, uint16_t port)
|
||||||
|
: ASCIIIOHandler(kernel, canId)
|
||||||
|
, m_hostname{std::move(hostname)}
|
||||||
|
, m_port{port}
|
||||||
|
, m_socket{m_kernel.ioContext()}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void CANEtherIOHandler::start()
|
||||||
|
{
|
||||||
|
boost::system::error_code ec;
|
||||||
|
|
||||||
|
m_endpoint.port(m_port);
|
||||||
|
m_endpoint.address(boost::asio::ip::make_address(m_hostname, ec));
|
||||||
|
if(ec)
|
||||||
|
{
|
||||||
|
throw LogMessageException(LogMessage::E2003_MAKE_ADDRESS_FAILED_X, ec);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_socket.async_connect(m_endpoint,
|
||||||
|
[this](const boost::system::error_code& err)
|
||||||
|
{
|
||||||
|
if(!err)
|
||||||
|
{
|
||||||
|
m_socket.set_option(boost::asio::socket_base::linger(true, 0));
|
||||||
|
m_socket.set_option(boost::asio::ip::tcp::no_delay(true));
|
||||||
|
|
||||||
|
m_connected = true;
|
||||||
|
|
||||||
|
read();
|
||||||
|
write();
|
||||||
|
|
||||||
|
m_kernel.started();
|
||||||
|
}
|
||||||
|
else if(err != boost::asio::error::operation_aborted)
|
||||||
|
{
|
||||||
|
EventLoop::call(
|
||||||
|
[this, err]()
|
||||||
|
{
|
||||||
|
Log::log(m_kernel.logId, LogMessage::E2005_SOCKET_CONNECT_FAILED_X, err);
|
||||||
|
m_kernel.error();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void CANEtherIOHandler::stop()
|
||||||
|
{
|
||||||
|
boost::system::error_code ec;
|
||||||
|
m_socket.cancel(ec);
|
||||||
|
m_socket.close(ec);
|
||||||
|
// ignore errors
|
||||||
|
m_connected = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CANEtherIOHandler::read()
|
||||||
|
{
|
||||||
|
m_socket.async_read_some(boost::asio::buffer(m_readBuffer.data() + m_readBufferOffset, m_readBuffer.size() - m_readBufferOffset),
|
||||||
|
[this](const boost::system::error_code& ec, std::size_t bytesTransferred)
|
||||||
|
{
|
||||||
|
if(!ec)
|
||||||
|
{
|
||||||
|
processRead(bytesTransferred);
|
||||||
|
read();
|
||||||
|
}
|
||||||
|
else if(ec != boost::asio::error::operation_aborted)
|
||||||
|
{
|
||||||
|
EventLoop::call(
|
||||||
|
[this, ec]()
|
||||||
|
{
|
||||||
|
Log::log(m_kernel.logId, LogMessage::E1007_SOCKET_READ_FAILED_X, ec);
|
||||||
|
m_kernel.error();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void CANEtherIOHandler::write()
|
||||||
|
{
|
||||||
|
assert(!m_writeQueue.empty());
|
||||||
|
const auto& message = m_writeQueue.front();
|
||||||
|
boost::asio::async_write(m_socket, boost::asio::buffer(message.data(), message.size()),
|
||||||
|
[this](const boost::system::error_code& ec, std::size_t /*bytesTransferred*/)
|
||||||
|
{
|
||||||
|
if(!ec)
|
||||||
|
{
|
||||||
|
m_writeQueue.pop();
|
||||||
|
if(!m_writeQueue.empty())
|
||||||
|
{
|
||||||
|
write();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(ec != boost::asio::error::operation_aborted)
|
||||||
|
{
|
||||||
|
EventLoop::call(
|
||||||
|
[this, ec]()
|
||||||
|
{
|
||||||
|
Log::log(m_kernel.logId, LogMessage::E1006_SOCKET_WRITE_FAILED_X, ec);
|
||||||
|
m_kernel.error();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,54 @@
|
|||||||
|
/**
|
||||||
|
* This file is part of Traintastic,
|
||||||
|
* see <https://github.com/traintastic/traintastic>.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2026 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_CBUS_IOHANDLER_CBUSCANETHERIOHANDLER_HPP
|
||||||
|
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_CBUS_IOHANDLER_CBUSCANETHERIOHANDLER_HPP
|
||||||
|
|
||||||
|
#include "cbusasciiiohandler.hpp"
|
||||||
|
#include <boost/asio/ip/tcp.hpp>
|
||||||
|
|
||||||
|
namespace CBUS {
|
||||||
|
|
||||||
|
class CANEtherIOHandler final : public ASCIIIOHandler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CANEtherIOHandler(Kernel& kernel, std::string hostname, uint16_t port);
|
||||||
|
|
||||||
|
void start() final;
|
||||||
|
void stop() final;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void read() final;
|
||||||
|
void write() final;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr uint8_t canId = 0x7D; //!< CANEther fixed CAN_ID
|
||||||
|
|
||||||
|
const std::string m_hostname;
|
||||||
|
const uint16_t m_port;
|
||||||
|
boost::asio::ip::tcp::socket m_socket;
|
||||||
|
boost::asio::ip::tcp::endpoint m_endpoint;
|
||||||
|
bool m_connected = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
109
server/src/hardware/protocol/cbus/iohandler/cbuscanusbiohandler.cpp
Normale Datei
109
server/src/hardware/protocol/cbus/iohandler/cbuscanusbiohandler.cpp
Normale Datei
@ -0,0 +1,109 @@
|
|||||||
|
/**
|
||||||
|
* This file is part of Traintastic,
|
||||||
|
* see <https://github.com/traintastic/traintastic>.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2026 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 "cbuscanusbiohandler.hpp"
|
||||||
|
#include <boost/asio/write.hpp>
|
||||||
|
#include "../cbuskernel.hpp"
|
||||||
|
#include "../../../../core/eventloop.hpp"
|
||||||
|
#include "../../../../log/log.hpp"
|
||||||
|
#include "../../../../utils/serialport.hpp"
|
||||||
|
|
||||||
|
namespace CBUS {
|
||||||
|
|
||||||
|
CANUSBIOHandler::CANUSBIOHandler(Kernel& kernel, const std::string& device)
|
||||||
|
: ASCIIIOHandler(kernel, canId)
|
||||||
|
, m_serialPort{m_kernel.ioContext()}
|
||||||
|
{
|
||||||
|
// FIXME: check serial settings, just a guess, if more settings are needed add them to the interface
|
||||||
|
SerialPort::open(m_serialPort, device, 115'200, 8, SerialParity::None, SerialStopBits::One, SerialFlowControl::None);
|
||||||
|
}
|
||||||
|
|
||||||
|
CANUSBIOHandler::~CANUSBIOHandler()
|
||||||
|
{
|
||||||
|
if(m_serialPort.is_open())
|
||||||
|
{
|
||||||
|
boost::system::error_code ec;
|
||||||
|
m_serialPort.close(ec);
|
||||||
|
// ignore the error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CANUSBIOHandler::start()
|
||||||
|
{
|
||||||
|
read();
|
||||||
|
m_kernel.started();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CANUSBIOHandler::stop()
|
||||||
|
{
|
||||||
|
m_serialPort.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CANUSBIOHandler::read()
|
||||||
|
{
|
||||||
|
m_serialPort.async_read_some(boost::asio::buffer(m_readBuffer.data() + m_readBufferOffset, m_readBuffer.size() - m_readBufferOffset),
|
||||||
|
[this](const boost::system::error_code& ec, std::size_t bytesTransferred)
|
||||||
|
{
|
||||||
|
if(!ec)
|
||||||
|
{
|
||||||
|
processRead(bytesTransferred);
|
||||||
|
read();
|
||||||
|
}
|
||||||
|
else if(ec != boost::asio::error::operation_aborted)
|
||||||
|
{
|
||||||
|
EventLoop::call(
|
||||||
|
[this, ec]()
|
||||||
|
{
|
||||||
|
Log::log(m_kernel.logId, LogMessage::E2002_SERIAL_READ_FAILED_X, ec);
|
||||||
|
m_kernel.error();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void CANUSBIOHandler::write()
|
||||||
|
{
|
||||||
|
assert(!m_writeQueue.empty());
|
||||||
|
const auto& message = m_writeQueue.front();
|
||||||
|
boost::asio::async_write(m_serialPort, boost::asio::buffer(message.data(), message.size()),
|
||||||
|
[this](const boost::system::error_code& ec, std::size_t /*bytesTransferred*/)
|
||||||
|
{
|
||||||
|
if(!ec)
|
||||||
|
{
|
||||||
|
m_writeQueue.pop();
|
||||||
|
if(!m_writeQueue.empty())
|
||||||
|
{
|
||||||
|
write();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(ec != boost::asio::error::operation_aborted)
|
||||||
|
{
|
||||||
|
EventLoop::call(
|
||||||
|
[this, ec]()
|
||||||
|
{
|
||||||
|
Log::log(m_kernel.logId, LogMessage::E2001_SERIAL_WRITE_FAILED_X, ec);
|
||||||
|
m_kernel.error();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
51
server/src/hardware/protocol/cbus/iohandler/cbuscanusbiohandler.hpp
Normale Datei
51
server/src/hardware/protocol/cbus/iohandler/cbuscanusbiohandler.hpp
Normale Datei
@ -0,0 +1,51 @@
|
|||||||
|
/**
|
||||||
|
* This file is part of Traintastic,
|
||||||
|
* see <https://github.com/traintastic/traintastic>.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2026 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_CBUS_IOHANDLER_CBUSCANUSBIOHANDLER_HPP
|
||||||
|
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_CBUS_IOHANDLER_CBUSCANUSBIOHANDLER_HPP
|
||||||
|
|
||||||
|
#include "cbusasciiiohandler.hpp"
|
||||||
|
#include <boost/asio/serial_port.hpp>
|
||||||
|
|
||||||
|
namespace CBUS {
|
||||||
|
|
||||||
|
class CANUSBIOHandler final : public ASCIIIOHandler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CANUSBIOHandler(Kernel& kernel, const std::string& device);
|
||||||
|
~CANUSBIOHandler() final;
|
||||||
|
|
||||||
|
void start() final;
|
||||||
|
void stop() final;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void read() final;
|
||||||
|
void write() final;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr uint8_t canId = 0x7C; //!< CANUSB fixed CAN_ID
|
||||||
|
|
||||||
|
boost::asio::serial_port m_serialPort;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
32
server/src/hardware/protocol/cbus/iohandler/cbusiohandler.cpp
Normale Datei
32
server/src/hardware/protocol/cbus/iohandler/cbusiohandler.cpp
Normale Datei
@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* This file is part of Traintastic,
|
||||||
|
* see <https://github.com/traintastic/traintastic>.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2026 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 "cbusiohandler.hpp"
|
||||||
|
|
||||||
|
namespace CBUS {
|
||||||
|
|
||||||
|
IOHandler::IOHandler(Kernel& kernel, uint8_t canId)
|
||||||
|
: m_kernel{kernel}
|
||||||
|
, m_canId{canId}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
61
server/src/hardware/protocol/cbus/iohandler/cbusiohandler.hpp
Normale Datei
61
server/src/hardware/protocol/cbus/iohandler/cbusiohandler.hpp
Normale Datei
@ -0,0 +1,61 @@
|
|||||||
|
/**
|
||||||
|
* This file is part of Traintastic,
|
||||||
|
* see <https://github.com/traintastic/traintastic>.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2026 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_CBUS_IOHANDLER_CBUSIOHANDLER_HPP
|
||||||
|
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_CBUS_IOHANDLER_CBUSIOHANDLER_HPP
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <system_error>
|
||||||
|
|
||||||
|
namespace CBUS {
|
||||||
|
|
||||||
|
class Kernel;
|
||||||
|
struct Message;
|
||||||
|
|
||||||
|
class IOHandler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IOHandler(const IOHandler&) = delete;
|
||||||
|
IOHandler& operator =(const IOHandler&) = delete;
|
||||||
|
|
||||||
|
virtual ~IOHandler() = default;
|
||||||
|
|
||||||
|
virtual void start() = 0;
|
||||||
|
virtual void stop() = 0;
|
||||||
|
|
||||||
|
[[nodiscard]] virtual std::error_code send(const Message& message) = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Kernel& m_kernel;
|
||||||
|
const uint8_t m_canId;
|
||||||
|
|
||||||
|
IOHandler(Kernel& kernel, uint8_t canId);
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
constexpr bool isSimulation()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@ -30,6 +30,11 @@ struct Message
|
|||||||
{
|
{
|
||||||
OpCode opCode;
|
OpCode opCode;
|
||||||
|
|
||||||
|
constexpr uint8_t size() const
|
||||||
|
{
|
||||||
|
return sizeof(OpCode) + dataSize(opCode);
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Message(OpCode opc)
|
Message(OpCode opc)
|
||||||
: opCode{opc}
|
: opCode{opc}
|
||||||
|
|||||||
46
shared/src/traintastic/enum/cbusinterfacetype.hpp
Normale Datei
46
shared/src/traintastic/enum/cbusinterfacetype.hpp
Normale Datei
@ -0,0 +1,46 @@
|
|||||||
|
/**
|
||||||
|
* This file is part of Traintastic,
|
||||||
|
* see <https://github.com/traintastic/traintastic>.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2026 Reinder Feenstra
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TRAINTASTIC_SHARED_TRAINTASTIC_ENUM_CBUSINTERFACETYPE_HPP
|
||||||
|
#define TRAINTASTIC_SHARED_TRAINTASTIC_ENUM_CBUSINTERFACETYPE_HPP
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <array>
|
||||||
|
#include "enum.hpp"
|
||||||
|
|
||||||
|
enum class CBUSInterfaceType : uint8_t
|
||||||
|
{
|
||||||
|
CANUSB = 0,
|
||||||
|
CANEther = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
TRAINTASTIC_ENUM(CBUSInterfaceType, "cbus_interface_type", 2,
|
||||||
|
{
|
||||||
|
{CBUSInterfaceType::CANUSB, "canusb"},
|
||||||
|
{CBUSInterfaceType::CANEther, "canether"},
|
||||||
|
});
|
||||||
|
|
||||||
|
inline constexpr std::array<CBUSInterfaceType, 2> CBUSInterfaceTypeValues{{
|
||||||
|
CBUSInterfaceType::CANUSB,
|
||||||
|
CBUSInterfaceType::CANEther,
|
||||||
|
}};
|
||||||
|
|
||||||
|
#endif
|
||||||
@ -1,4 +1,8 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"term": "class_id:interface.cbus",
|
||||||
|
"definition": "CBUS/VLCB"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"term": "class_id:interface.dccex",
|
"term": "class_id:interface.dccex",
|
||||||
"definition": "DCC-EX"
|
"definition": "DCC-EX"
|
||||||
|
|||||||
Laden…
x
In neuem Issue referenzieren
Einen Benutzer sperren