Add base files for CBUS/VLCB to Server (Interface, IOHandler)
Einige Prüfungen sind noch ausstehend
Build / client macos-15-arm64 (push) Waiting to run
Build / client macos-15-intel (push) Waiting to run
Build / client ubuntu_24.04 (push) Waiting to run
Build / client ubuntu_24.04_arm64 (push) Waiting to run
Build / client raspberrypios_arm64 (push) Waiting to run
Build / client raspberrypios_arm7 (push) Waiting to run
Build / client windows_x64_msvc (push) Waiting to run
Build / server ubuntu_24.04 (debug+ccov) (push) Waiting to run
Build / server macos-15-arm64 (push) Waiting to run
Build / server macos-15-intel (push) Waiting to run
Build / server raspberrypios_arm64 (push) Waiting to run
Build / server raspberrypios_arm7 (push) Waiting to run
Build / server ubuntu_24.04 (push) Waiting to run
Build / server ubuntu_24.04_arm64 (push) Waiting to run
Build / server windows_x64_clang (push) Waiting to run
Build / shared data raspberrypios_10 (push) Blocked by required conditions
Build / shared data ubuntu_24.04 (push) Blocked by required conditions
Build / language files (push) Waiting to run
Build / manual (push) Waiting to run
Build / package innosetup (push) Blocked by required conditions
Build / Deploy to website (push) Blocked by required conditions
Build / Update contributers in README.md (push) Waiting to run

Dieser Commit ist enthalten in:
Tom 2026-02-19 22:46:56 +01:00
Ursprung 2bfbbb267f
Commit 265ac0b84d
34 geänderte Dateien mit 2981 neuen und 83 gelöschten Zeilen

Datei anzeigen

@ -1,24 +0,0 @@
---
Checks: >
-*,
misc-*,
-misc-no-recursion,
modernize-*,
-modernize-avoid-bind,
-modernize-avoid-c-arrays,
-modernize-use-trailing-return-type,
readability-*,
-readability-braces-around-statements,
-readability-implicit-bool-conversion,
-readability-identifier-length,
-readability-magic-numbers,
-readability-suspicious-call-argument
WarningsAsErrors: >
readability-*,
-readability-function-cognitive-complexity
CheckOptions:
- {key: readability-identifier-naming.NamespaceCase, value: CamelCase}
- {key: readability-identifier-naming.ClassCase, value: CamelCase}
- {key: readability-identifier-naming.StructCase, value: CamelCase}

Datei anzeigen

@ -1,23 +0,0 @@
root = true
[*]
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.{hpp|cpp|lua|yml}]
indent_style = space
indent_size = 2
[*.py]
indent_style = space
indent_size = 4
[*.js]
charset = utf-8
indent_style = space
indent_size = 2
[**CMakeLists.txt]
indent_style = space
indent_size = 2

1
.gitignore vendored
Datei anzeigen

@ -49,4 +49,5 @@ CMakeLists.txt.*
shared/translations/*.lang shared/translations/*.lang
CMakeSettings.json CMakeSettings.json
.venv .venv
traintastic.geany

Datei anzeigen

@ -286,6 +286,33 @@
} }
] ]
}, },
"connection_cbus": {
"title": "$wizard.add_interface.connection:title$",
"text": "$wizard.add_interface.connection:text$",
"type": "radio",
"options": [
{
"name": "$wizard.add_interface.connection:command_station_usb_port$",
"next": "serial_port",
"actions": {
"create_interface": {
"class_id": "interface.cbus",
"properties": {
"name": "%command_station%",
"type": "serial",
"baudrate": 115200,
"flow_control": "hardware",
"cbus.command_station": "%cbus_command_station%"
}
}
}
},
{
"name": "$wizard.add_interface.connection:cbus_interface$",
"next": "interface_cbus"
}
]
},
"connection_z21": { "connection_z21": {
"title": "$wizard.add_interface.connection:title$", "title": "$wizard.add_interface.connection:title$",
"text": "$wizard.add_interface.connection:text$", "text": "$wizard.add_interface.connection:text$",

Datei anzeigen

@ -119,6 +119,10 @@ 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/*.hpp"
"src/hardware/protocol/cbus/*.cpp"
"src/hardware/protocol/cbus/iohandler/*.hpp"
"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"

Datei anzeigen

@ -0,0 +1,35 @@
/**
* server/src/enum/cbusinterface.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2021 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef TRAINTASTIC_SERVER_ENUM_CBUSINTERFACETYPE_HPP
#define TRAINTASTIC_SERVER_ENUM_CBUSINTERFACETYPE_HPP
#include <traintastic/enum/cbusinterfacetype.hpp>
#include <array>
inline constexpr std::array<CBUSInterfaceType, 2> CBUSInterfaceTypeValues{{
CBUSInterfaceType::Serial,
CBUSInterfaceType::NetworkTCP,
}};
#endif

Datei anzeigen

@ -0,0 +1,163 @@
/**
* server/src/hardware/interface/cbus/cbusinterface.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2025 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 "../input/list/inputlist.hpp"
#include "../output/list/outputlist.hpp"
#include "../protocol/cbus/kernel.hpp"
#include "../protocol/cbus/settings.hpp"
#include "../protocol/cbus/messages.hpp"
#include "../protocol/cbus/iohandler/serialiohandler.hpp"
#include "../protocol/cbus/iohandler/simulationiohandler.hpp"
#include "../protocol/cbus/iohandler/tcpiohandler.hpp"
#include "../../core/attributes.hpp"
#include "../../core/eventloop.hpp"
#include "../../core/objectproperty.tpp"
#include "../../log/log.hpp"
#include "../../log/logmessageexception.hpp"
#include "../../utils/displayname.hpp"
#include "../../utils/inrange.hpp"
#include "../../utils/makearray.hpp"
#include "../../world/world.hpp"
constexpr auto inputListColumns = InputListColumn::Address;
constexpr auto outputListColumns = OutputListColumn::Channel | OutputListColumn::Address;
CREATE_IMPL(CBUSInterface)
CBUSInterface::CBUSInterface(World& world, std::string_view _id)
: Interface(world, _id)
, InputController(static_cast<IdObject&>(*this))
, OutputController(static_cast<IdObject&>(*this))
, type{this, "type", CBUSInterfaceType::Serial, PropertyFlags::ReadWrite | PropertyFlags::Store,
[this](CBUSInterfaceType /*value*/)
{
updateVisible();
}}
, device{this, "device", "", PropertyFlags::ReadWrite | PropertyFlags::Store}
, baudrate{this, "baudrate", 115200, PropertyFlags::ReadWrite | PropertyFlags::Store}
, flowControl{this, "flow_control", SerialFlowControl::None, PropertyFlags::ReadWrite | PropertyFlags::Store}
, hostname{this, "hostname", "", PropertyFlags::ReadWrite | PropertyFlags::Store}
, port{this, "port", 5550, PropertyFlags::ReadWrite | PropertyFlags::Store}
, cbus{this, "cbus", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject}
{
name = "CBUS";
cbus.setValueInternal(std::make_shared<CBUS::Settings>(*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(baudrate, DisplayName::Serial::baudrate);
Attributes::addEnabled(baudrate, !online);
Attributes::addVisible(baudrate, false);
Attributes::addDisplayName(flowControl, DisplayName::Serial::flowControl);
Attributes::addEnabled(flowControl, !online);
Attributes::addValues(flowControl, SerialFlowControlValues);
Attributes::addVisible(flowControl, false);
m_interfaceItems.insertBefore(flowControl, notes);
Attributes::addDisplayName(hostname, DisplayName::IP::hostname);
Attributes::addEnabled(hostname, !online);
Attributes::addVisible(hostname, false);
m_interfaceItems.insertBefore(hostname, notes);
Attributes::addDisplayName(port, DisplayName::IP::port);
Attributes::addEnabled(port, !online);
Attributes::addVisible(port, false);
m_interfaceItems.insertBefore(port, notes);
Attributes::addDisplayName(cbus, DisplayName::Hardware::cbus);
m_interfaceItems.insertBefore(cbus, notes);
m_interfaceItems.insertBefore(inputs, notes);
m_interfaceItems.insertBefore(outputs, notes);
updateVisible();
}
CBUSInterface::~CBUSInterface() = default;
void CBUSInterface::addToWorld()
{
Interface::addToWorld();
InputController::addToWorld(inputListColumns);
OutputController::addToWorld(outputListColumns);
}
void CBUSInterface::loaded()
{
Interface::loaded();
updateVisible();
}
std::span<const InputChannel> CBUSInterface::inputChannels() const {
return {};
}
std::pair<unsigned int, unsigned int> CBUSInterface::inputAddressMinMax(InputChannel) const {
return {0,0};
}
std::span<const OutputChannel> CBUSInterface::outputChannels() const {
return {};
}
std::pair<unsigned int, unsigned int> CBUSInterface::outputAddressMinMax(OutputChannel) const {
return {0,0};
}
bool CBUSInterface::setOutputValue(OutputChannel, uint32_t, OutputValue) {
return false;
}
bool CBUSInterface::setOnline(bool& value, bool /*simulation*/)
{
value = false;
return true;
}
void CBUSInterface::destroying()
{
OutputController::destroying();
InputController::destroying();
Interface::destroying();
}
void CBUSInterface::updateVisible()
{
const bool isSerial = (type == CBUSInterfaceType::Serial);
Attributes::setVisible(device, isSerial);
Attributes::setVisible(baudrate, isSerial);
Attributes::setVisible(flowControl, isSerial);
const bool isNetwork = (type == CBUSInterfaceType::NetworkTCP);
Attributes::setVisible(hostname, isNetwork);
Attributes::setVisible(port, isNetwork);
}

Datei anzeigen

@ -0,0 +1,84 @@
/**
* server/src/hardware/interface/cbus/cbusinterface.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2025 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 "../input/inputcontroller.hpp"
#include "../output/outputcontroller.hpp"
#include "../../core/serialdeviceproperty.hpp"
#include "../../core/objectproperty.hpp"
#include "../../enum/cbusinterfacetype.hpp"
#include "../../enum/serialflowcontrol.hpp"
#include <span>
namespace CBUS {
class Kernel;
class Settings;
}
/**
* @brief CBUS hardware interface
*/
class CBUSInterface final
: public Interface
, public InputController
, public OutputController
{
CLASS_ID("interface.cbus")
DEFAULT_ID("cbus")
CREATE_DEF(CBUSInterface)
private:
std::unique_ptr<CBUS::Kernel> m_kernel;
boost::signals2::connection m_cbusPropertyChanged;
void addToWorld() final;
void loaded() final;
void destroying() final;
void updateVisible();
public:
Property<CBUSInterfaceType> type;
SerialDeviceProperty device;
Property<uint32_t> baudrate;
Property<SerialFlowControl> flowControl;
Property<std::string> hostname;
Property<uint16_t> port;
ObjectProperty<CBUS::Settings> cbus;
CBUSInterface(World& world, std::string_view _id);
~CBUSInterface() final;
bool setOnline(bool& value, bool simulation) final;
std::span<const InputChannel> inputChannels() const final;
std::pair<uint32_t, uint32_t> inputAddressMinMax(InputChannel) const final;
std::span<const OutputChannel> outputChannels() const final;
std::pair<uint32_t, uint32_t> outputAddressMinMax(OutputChannel) const final;
[[nodiscard]] bool setOutputValue(OutputChannel channel, uint32_t address, OutputValue value) final;
};
#endif

Datei anzeigen

@ -0,0 +1,41 @@
/**
* server/src/hardware/protocol/cbus/config.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_CBUS_CONFIG_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_CBUS_CONFIG_HPP
#include <chrono>
namespace CBUS {
struct Config
{
std::chrono::milliseconds heartbeatTimeout;
std::chrono::milliseconds startupDelay;
bool debugLogRXTX;
bool debugLogHeartbeat;
};
}
#endif

Datei anzeigen

@ -0,0 +1,85 @@
/**
* server/src/hardware/protocol/cbus/featureflags.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_CBUS_FEATUREFLAGS_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_CBUS_FEATUREFLAGS_HPP
#include <cstdint>
namespace CBUS {
enum class FeatureFlags1 : uint8_t
{
None = 0x00,
Input = 0x01,
Output = 0x02,
Throttle = 0x04,
};
enum class FeatureFlags2 : uint8_t
{
None = 0x00,
};
enum class FeatureFlags3 : uint8_t
{
None = 0x00,
};
enum class FeatureFlags4 : uint8_t
{
None = 0x00,
};
template<class T>
constexpr bool isFeatureFlagType()
{
return
std::is_same_v<T, FeatureFlags1> ||
std::is_same_v<T, FeatureFlags2> ||
std::is_same_v<T, FeatureFlags3> ||
std::is_same_v<T, FeatureFlags4>;
}
}
template<class T, std::enable_if_t<CBUS::isFeatureFlagType<T>()>* = nullptr>
constexpr T operator&(const T& lhs, const T& rhs)
{
using UT = std::underlying_type_t<T>;
return static_cast<T>(static_cast<UT>(lhs) & static_cast<UT>(rhs));
}
template<class T, std::enable_if_t<CBUS::isFeatureFlagType<T>()>* = nullptr>
constexpr T operator|(const T& lhs, const T& rhs)
{
using UT = std::underlying_type_t<T>;
return static_cast<T>(static_cast<UT>(lhs) | static_cast<UT>(rhs));
}
template<class T, std::enable_if_t<CBUS::isFeatureFlagType<T>()>* = nullptr>
constexpr bool contains(T set, T value)
{
return (set & value) == value;
}
#endif

Datei anzeigen

@ -0,0 +1,62 @@
/**
* server/src/hardware/protocol/cbus/inputstate.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_TRAINTASTICDIY_INPUTSTATE_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_TRAINTASTICDIY_INPUTSTATE_HPP
#include <cstdint>
#include <string_view>
namespace CBUS {
enum class InputState : uint8_t
{
Undefined = 0,
False = 1,
True = 2,
Invalid = 3,
};
}
constexpr std::string_view toString(CBUS::InputState value)
{
using InputState = CBUS::InputState;
switch(value)
{
case InputState::Undefined:
return "Undefined";
case InputState::False:
return "False";
case InputState::True:
return "True";
case InputState::Invalid:
return "Invalid";
}
return {};
}
#endif

Datei anzeigen

@ -0,0 +1,101 @@
/**
* server/src/hardware/protocol/cbus/iohandler/hardwareiohandler.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2022-2023 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"
#include "../../../../utils/tohex.hpp"
namespace CBUS {
HardwareIOHandler::HardwareIOHandler(Kernel& kernel)
: IOHandler{kernel}
, m_readBufferOffset{0}
, m_writeBufferOffset{0}
{
}
bool HardwareIOHandler::send(const Message& message)
{
if(m_writeBufferOffset + message.size() > m_writeBuffer.size())
return false;
const bool wasEmpty = m_writeBufferOffset == 0;
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 > 1)
{
const Message* message = nullptr;
size_t drop = 0;
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, bytes=toHex(pos - drop, drop, true)]()
{
Log::log(m_kernel.logId, LogMessage::W2003_RECEIVED_MALFORMED_DATA_DROPPED_X_BYTES_X, drop, bytes);
});
}
assert(message);
if(message->size() <= bytesTransferred)
{
m_kernel.receive(*message);
pos += message->size();
bytesTransferred -= message->size();
}
else
break;
}
if(bytesTransferred != 0)
memmove(m_readBuffer.data(), pos, bytesTransferred);
m_readBufferOffset = bytesTransferred;
}
}

Datei anzeigen

@ -0,0 +1,54 @@
/**
* server/src/hardware/protocol/cbus/iohandler/hardwareiohandler.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_CBUS_IOHANDLER_HARDWAREIOHANDLER_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_CBUS_IOHANDLER_HARDWAREIOHANDLER_HPP
#include <cstddef>
#include <array>
#include "iohandler.hpp"
namespace CBUS {
class Kernel;
struct Message;
class HardwareIOHandler : public IOHandler
{
protected:
std::array<std::byte, 1500> m_readBuffer;
size_t m_readBufferOffset;
std::array<std::byte, 1500> m_writeBuffer;
size_t m_writeBufferOffset;
HardwareIOHandler(Kernel& kernel);
void processRead(size_t bytesTransferred);
virtual void write() = 0;
public:
bool send(const Message& message) final;
};
}
#endif

Datei anzeigen

@ -0,0 +1,61 @@
/**
* server/src/hardware/protocol/cbus/iohandler/iohandler.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_CBUS_IOHANDLER_IOHANDLER_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_CBUS_IOHANDLER_IOHANDLER_HPP
namespace CBUS {
class Kernel;
struct Message;
class IOHandler
{
protected:
Kernel& m_kernel;
IOHandler(Kernel& kernel)
: m_kernel{kernel}
{
}
public:
IOHandler(const IOHandler&) = delete;
IOHandler& operator =(const IOHandler&) = delete;
virtual ~IOHandler() = default;
virtual void start() = 0;
virtual void stop() = 0;
virtual bool send(const Message& message) = 0;
};
template<class T>
constexpr bool isSimulation()
{
return false;
}
}
#endif

Datei anzeigen

@ -0,0 +1,110 @@
/**
* server/src/hardware/protocol/cbus/iohandler/serialiohandler.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2022-2024 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 "serialiohandler.hpp"
#include "../kernel.hpp"
#include "../messages.hpp"
#include "../../../../core/eventloop.hpp"
#include "../../../../log/log.hpp"
#include "../../../../utils/serialport.hpp"
namespace CBUS {
SerialIOHandler::SerialIOHandler(Kernel& kernel, const std::string& device, uint32_t baudrate, SerialFlowControl flowControl)
: HardwareIOHandler(kernel)
, m_serialPort{m_kernel.ioContext()}
{
SerialPort::open(m_serialPort, device, baudrate, 8, SerialParity::None, SerialStopBits::One, flowControl);
}
SerialIOHandler::~SerialIOHandler()
{
if(m_serialPort.is_open())
{
boost::system::error_code ec;
m_serialPort.close(ec);
// ignore the error
}
}
void SerialIOHandler::start()
{
read();
m_kernel.started();
}
void SerialIOHandler::stop()
{
m_serialPort.close();
}
void SerialIOHandler::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 SerialIOHandler::write()
{
m_serialPort.async_write_some(boost::asio::buffer(m_writeBuffer.data(), m_writeBufferOffset),
[this](const boost::system::error_code& ec, std::size_t bytesTransferred)
{
if(!ec)
{
if(bytesTransferred < m_writeBufferOffset)
{
m_writeBufferOffset -= bytesTransferred;
memmove(m_writeBuffer.data(), m_writeBuffer.data() + bytesTransferred, m_writeBufferOffset);
write();
}
else
m_writeBufferOffset = 0;
}
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();
});
}
});
}
}

Datei anzeigen

@ -0,0 +1,50 @@
/**
* server/src/hardware/protocol/cbus/iohandler/serialiohandler.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_CBUS_IOHANDLER_SERIALIOHANDLER_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_CBUS_IOHANDLER_SERIALIOHANDLER_HPP
#include "hardwareiohandler.hpp"
#include <boost/asio/serial_port.hpp>
#include "../../../../enum/serialflowcontrol.hpp"
namespace CBUS {
class SerialIOHandler final : public HardwareIOHandler
{
private:
boost::asio::serial_port m_serialPort;
void read();
void write() final;
public:
SerialIOHandler(Kernel& kernel, const std::string& device, uint32_t baudrate, SerialFlowControl flowControl);
~SerialIOHandler() final;
void start() final;
void stop() final;
};
}
#endif

Datei anzeigen

@ -0,0 +1,130 @@
/**
* server/src/hardware/protocol/cbus/iohandler/simulationiohandler.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2022-2024 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 <version.hpp>
namespace CBUS {
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)
{
}
void SimulationIOHandler::start()
{
m_kernel.started();
}
bool SimulationIOHandler::send(const Message& message)
{
switch(message.opCode)
{
case OpCode::Heartbeat:
reply(Heartbeat());
break;
case OpCode::GetInputState:
{
const auto& getInputState = static_cast<const GetInputState&>(message);
reply(SetInputState(getInputState.address(), InputState::Invalid));
break;
}
case OpCode::GetOutputState:
{
const auto& getOutputState = static_cast<const GetOutputState&>(message);
reply(SetOutputState(getOutputState.address(), OutputState::Invalid));
break;
}
case OpCode::SetOutputState:
{
#ifndef NDEBUG
const auto& setOutputState = static_cast<const SetOutputState&>(message);
assert(setOutputState.state == OutputState::False || setOutputState.state == OutputState::True);
#endif
reply(message);
break;
}
case OpCode::ThrottleSubUnsub:
{
// TODO
break;
}
case OpCode::ThrottleSetFunction:
{
// TODO
break;
}
case OpCode::ThrottleSetSpeedDirection:
{
// TODO
break;
}
case OpCode::GetFeatures:
{
reply(Features(FeatureFlags1::Input | FeatureFlags1::Output));
break;
}
case OpCode::GetInfo:
{
constexpr std::string_view text{"Traintastic DIY simulator v" TRAINTASTIC_VERSION};
static_assert(text.size() <= 255);
auto info = std::make_unique<std::byte[]>(sizeof(InfoBase) + text.size() + sizeof(Checksum));
auto& infoBase = *reinterpret_cast<InfoBase*>(info.get());
infoBase.opCode = OpCode::Info;
infoBase.length = text.length();
std::memcpy(info.get() + sizeof(InfoBase), text.data(), text.size());
updateChecksum(infoBase);
reply(infoBase);
break;
}
case OpCode::SetInputState:
case OpCode::Features:
case OpCode::Info:
assert(false); // only send by device
break;
}
return true;
}
void SimulationIOHandler::reply(const Message& message)
{
// post the reply, so it has some delay
//! \todo better delay simulation? at least message transfer time?
m_kernel.ioContext().post(
[this, data=copy(message)]()
{
m_kernel.receive(*reinterpret_cast<const Message*>(data.get()));
});
}
}

Datei anzeigen

@ -0,0 +1,55 @@
/**
* server/src/hardware/protocol/cbus/iohandler/simulationiohandler.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2022,2024 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_SIMULATIONIOHANDLER_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_CBUS_IOHANDLER_SIMULATIONIOHANDLER_HPP
#include "iohandler.hpp"
#include <array>
#include <cstddef>
namespace CBUS {
class SimulationIOHandler final : public IOHandler
{
private:
void reply(const Message& message);
public:
SimulationIOHandler(Kernel& kernel);
void start() final;
void stop() final {}
bool send(const Message& message) final;
};
template<>
constexpr bool isSimulation<SimulationIOHandler>()
{
return true;
}
}
#endif

Datei anzeigen

@ -0,0 +1,145 @@
/**
* server/src/hardware/protocol/cbus/iohandler/tcpiohandler.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2022-2024 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 "tcpiohandler.hpp"
#include <boost/asio/write.hpp>
#include "../kernel.hpp"
#include "../messages.hpp"
#include "../../../../core/eventloop.hpp"
#include "../../../../log/log.hpp"
#include "../../../../log/logmessageexception.hpp"
namespace CBUS {
TCPIOHandler::TCPIOHandler(Kernel& kernel, std::string hostname, uint16_t port)
: HardwareIOHandler(kernel)
, m_hostname{std::move(hostname)}
, m_port{port}
, m_socket{m_kernel.ioContext()}
{
}
TCPIOHandler::~TCPIOHandler()
{
assert(!m_socket.is_open());
}
void TCPIOHandler::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 TCPIOHandler::stop()
{
boost::system::error_code ec;
m_socket.cancel(ec);
m_socket.close(ec);
// ignore errors
m_connected = false;
}
void TCPIOHandler::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 TCPIOHandler::write()
{
if(m_writeBufferOffset == 0 || !m_connected)
{
return;
}
m_socket.async_write_some(boost::asio::buffer(m_writeBuffer.data(), m_writeBufferOffset),
[this](const boost::system::error_code& ec, std::size_t bytesTransferred)
{
if(!ec)
{
m_writeBufferOffset -= bytesTransferred;
if(m_writeBufferOffset > 0)
{
memmove(m_writeBuffer.data(), m_writeBuffer.data() + bytesTransferred, m_writeBufferOffset);
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();
});
}
});
}
}

Datei anzeigen

@ -0,0 +1,53 @@
/**
* server/src/hardware/protocol/cbus/iohandler/tcpiohandler.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2022-2024 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_TCPIOHANDLER_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_CBUS_IOHANDLER_TCPIOHANDLER_HPP
#include "hardwareiohandler.hpp"
#include <boost/asio/ip/tcp.hpp>
namespace CBUS {
class TCPIOHandler final : public HardwareIOHandler
{
private:
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;
void read();
void write() final;
public:
TCPIOHandler(Kernel& kernel, std::string hostname, uint16_t port);
~TCPIOHandler() final;
void start() final;
void stop() final;
};
}
#endif

Datei anzeigen

@ -0,0 +1,595 @@
/**
* server/src/hardware/protocol/cbus/kernel.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2022-2025 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 "kernel.hpp"
#include "messages.hpp"
#include "../../decoder/decoder.hpp"
#include "../../decoder/decoderchangeflags.hpp"
#include "../../decoder/list/decoderlist.hpp"
#include "../../input/inputcontroller.hpp"
#include "../../output/outputcontroller.hpp"
#include "../../../utils/inrange.hpp"
#include "../../../utils/setthreadname.hpp"
#include "../../../core/eventloop.hpp"
#include "../../../core/objectproperty.tpp"
#include "../../../log/log.hpp"
#include "../../../log/logmessageexception.hpp"
#include "../../../world/world.hpp"
namespace CBUS {
constexpr TriState toTriState(InputState value)
{
switch(value)
{
case InputState::False:
return TriState::False;
case InputState::True:
return TriState::True;
case InputState::Undefined:
case InputState::Invalid:
break;
}
return TriState::Undefined;
}
constexpr TriState toTriState(OutputState value)
{
switch(value)
{
case OutputState::False:
return TriState::False;
return TriState::False;
case OutputState::True:
return TriState::True;
case OutputState::Undefined:
case OutputState::Invalid:
break;
}
return TriState::Undefined;
}
Kernel::Kernel(std::string logId_, World& world, const Config& config, bool simulation)
: KernelBase(std::move(logId_))
, m_world{world}
, m_simulation{simulation}
, m_startupDelayTimer{m_ioContext}
, m_heartbeatTimeout{m_ioContext}
, m_inputController{nullptr}
, m_outputController{nullptr}
, m_config{config}
{
}
void Kernel::setConfig(const Config& config)
{
m_ioContext.post(
[this, newConfig=config]()
{
m_config = newConfig;
});
}
void Kernel::start()
{
assert(m_ioHandler);
assert(!m_started);
assert(m_inputValues.empty());
assert(m_outputValues.empty());
assert(m_throttleSubscriptions.empty());
assert(m_decoderSubscriptions.empty());
m_featureFlagsSet = false;
m_featureFlags1 = FeatureFlags1::None;
m_featureFlags2 = FeatureFlags2::None;
m_featureFlags3 = FeatureFlags3::None;
m_featureFlags4 = FeatureFlags4::None;
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;
}
});
#ifndef NDEBUG
m_started = true;
#endif
}
void Kernel::stop()
{
for(auto& it : m_decoderSubscriptions)
it.second.connection.disconnect();
m_ioContext.post(
[this]()
{
m_heartbeatTimeout.cancel();
m_ioHandler->stop();
});
m_ioContext.stop();
m_thread.join();
m_inputValues.clear();
m_outputValues.clear();
m_throttleSubscriptions.clear();
m_decoderSubscriptions.clear();
#ifndef NDEBUG
m_started = false;
#endif
}
void Kernel::started()
{
assert(isKernelThread());
m_startupDelayTimer.expires_after(boost::asio::chrono::milliseconds(m_config.startupDelay));
m_startupDelayTimer.async_wait(std::bind(&Kernel::startupDelayExpired, this, std::placeholders::_1));
}
void Kernel::receive(const Message& message)
{
if(m_config.debugLogRXTX && (message != Heartbeat() || m_config.debugLogHeartbeat))
EventLoop::call(
[this, msg=toString(message)]()
{
Log::log(logId, LogMessage::D2002_RX_X, msg);
});
restartHeartbeatTimeout();
switch(message.opCode)
{
case OpCode::Heartbeat:
break;
case OpCode::SetInputState:
{
if(!m_featureFlagsSet || !hasFeatureInput())
break;
const auto& setInputState = static_cast<const SetInputState&>(message);
const uint16_t address = setInputState.address();
if(inRange(address, ioAddressMin, ioAddressMax))
{
auto it = m_inputValues.find(address);
if(it == m_inputValues.end() || it->second != setInputState.state)
{
m_inputValues[address] = setInputState.state;
EventLoop::call(
[this, address, state=setInputState.state]()
{
if(state == InputState::Invalid)
{
if(m_inputController->inputMap().count({InputChannel::Input, address}) != 0)
Log::log(logId, LogMessage::W2004_INPUT_ADDRESS_X_IS_INVALID, address);
}
else
m_inputController->updateInputValue(InputChannel::Input, address, toTriState(state));
});
}
}
break;
}
case OpCode::SetOutputState:
{
if(!m_featureFlagsSet || !hasFeatureOutput())
break;
const auto& setOutputState = static_cast<const SetOutputState&>(message);
const uint16_t address = setOutputState.address();
if(inRange(address, ioAddressMin, ioAddressMax))
{
auto it = m_outputValues.find(address);
if(it == m_outputValues.end() || it->second != setOutputState.state)
{
m_outputValues[address] = setOutputState.state;
EventLoop::call(
[this, address, state=setOutputState.state]()
{
if(state == OutputState::Invalid)
{
if(m_outputController->outputMap().count({OutputChannel::Output, address}) != 0)
Log::log(logId, LogMessage::W2005_OUTPUT_ADDRESS_X_IS_INVALID, address);
}
else
m_outputController->updateOutputValue(OutputChannel::Output, address, toTriState(state));
});
}
}
break;
}
case OpCode::ThrottleSubUnsub:
{
if(!m_featureFlagsSet || !hasFeatureThrottle())
break;
const auto& subUnsub = static_cast<const ThrottleSubUnsub&>(message);
switch(subUnsub.action())
{
case ThrottleSubUnsub::Unsubscribe:
throttleUnsubscribe(subUnsub.throttleId(), {subUnsub.address(), subUnsub.isLongAddress()});
send(subUnsub);
break;
case ThrottleSubUnsub::Subscribe:
throttleSubscribe(subUnsub.throttleId(), {subUnsub.address(), subUnsub.isLongAddress()});
EventLoop::call(
[this, subUnsub]()
{
if(auto decoder = getDecoder(subUnsub.address(), subUnsub.isLongAddress()))
{
uint8_t speedMax = 0;
uint8_t speed = 0;
if(!decoder->emergencyStop)
{
speedMax = decoder->speedSteps.value();
if(speedMax == Decoder::speedStepsAuto)
speedMax = std::numeric_limits<uint8_t>::max();
speed = Decoder::throttleToSpeedStep(decoder->throttle, speedMax);
}
postSend(ThrottleSetSpeedDirection(subUnsub.throttleId(), subUnsub.address(), subUnsub.isLongAddress(), speed, speedMax, decoder->direction));
for(const auto& function : *decoder->functions)
postSend(ThrottleSetFunction(subUnsub.throttleId(), subUnsub.address(), subUnsub.isLongAddress(), function->number, function->value));
}
});
break;
}
break;
}
case OpCode::ThrottleSetFunction:
{
if(!m_featureFlagsSet || !hasFeatureThrottle())
break;
const auto& throttleSetFunction = static_cast<const ThrottleSetFunction&>(message);
throttleSubscribe(throttleSetFunction.throttleId(), {throttleSetFunction.address(), throttleSetFunction.isLongAddress()});
EventLoop::call(
[this, throttleSetFunction]()
{
if(auto decoder = getDecoder(throttleSetFunction.address(), throttleSetFunction.isLongAddress()))
{
bool value = false;
if(auto function = decoder->getFunction(throttleSetFunction.functionNumber()))
{
function->value = throttleSetFunction.functionValue();
if(function->value != throttleSetFunction.functionValue())
{
send(ThrottleSetFunction(
throttleSetFunction.throttleId(),
throttleSetFunction.address(),
throttleSetFunction.isLongAddress(),
throttleSetFunction.functionNumber(),
function->value));
}
}
else
{
// warning or debug?
send(ThrottleSetFunction(
throttleSetFunction.throttleId(),
throttleSetFunction.address(),
throttleSetFunction.isLongAddress(),
throttleSetFunction.functionNumber(),
value));
}
}
});
break;
}
case OpCode::ThrottleSetSpeedDirection:
{
if(!m_featureFlagsSet || !hasFeatureThrottle())
break;
const auto& throttleSetSpeedDirection = static_cast<const ThrottleSetSpeedDirection&>(message);
throttleSubscribe(throttleSetSpeedDirection.throttleId(), {throttleSetSpeedDirection.address(), throttleSetSpeedDirection.isLongAddress()});
EventLoop::call(
[this, throttleSetSpeedDirection]()
{
if(auto decoder = getDecoder(throttleSetSpeedDirection.address(), throttleSetSpeedDirection.isLongAddress()))
{
if(throttleSetSpeedDirection.isSpeedSet())
{
decoder->emergencyStop = throttleSetSpeedDirection.isEmergencyStop();
if(!throttleSetSpeedDirection.isEmergencyStop())
decoder->throttle = throttleSetSpeedDirection.throttle();
}
if(throttleSetSpeedDirection.isDirectionSet())
decoder->direction = throttleSetSpeedDirection.direction();
}
});
break;
}
case OpCode::Features:
{
const auto& features = static_cast<const Features&>(message);
m_featureFlagsSet = true;
m_featureFlags1 = features.featureFlags1;
m_featureFlags2 = features.featureFlags2;
m_featureFlags3 = features.featureFlags3;
m_featureFlags4 = features.featureFlags4;
if(hasFeatureInput())
EventLoop::call(
[this]()
{
for(const auto& it : m_inputController->inputMap())
postSend(GetInputState(static_cast<uint16_t>(it.first.address)));
});
if(hasFeatureOutput())
EventLoop::call(
[this]()
{
for(const auto& it : m_outputController->outputMap())
postSend(GetOutputState(static_cast<uint16_t>(it.first.id)));
});
break;
}
case OpCode::Info:
{
const auto& info = static_cast<const InfoBase&>(message);
EventLoop::call(
[this, text=std::string(info.text())]()
{
Log::log(logId, LogMessage::I2005_X, text);
});
break;
}
case OpCode::GetInfo:
case OpCode::GetFeatures:
case OpCode::GetOutputState:
case OpCode::GetInputState:
assert(false);
break;
}
}
bool Kernel::setOutput(uint16_t address, bool value)
{
postSend(SetOutputState(address, value ? OutputState::True : OutputState::False));
return true;
}
void Kernel::simulateInputChange(uint16_t address, SimulateInputAction action)
{
if(m_simulation)
m_ioContext.post(
[this, address, action]()
{
CBUS::InputState state;
auto it = m_inputValues.find(address);
switch(action)
{
case SimulateInputAction::SetFalse:
if(it != m_inputValues.end() && it->second == InputState::False)
return; // no change
state = InputState::False;
break;
case SimulateInputAction::SetTrue:
if(it != m_inputValues.end() && it->second == InputState::True)
return; // no change
state = InputState::True;
break;
case SimulateInputAction::Toggle:
state = (it == m_inputValues.end() || it->second == InputState::True) ? InputState::False : InputState::True;
break;
default:
assert(false);
return;
}
receive(SetInputState(address, state));
});
}
void Kernel::setIOHandler(std::unique_ptr<IOHandler> handler)
{
assert(handler);
assert(!m_ioHandler);
m_ioHandler = std::move(handler);
}
void Kernel::send(const Message& message)
{
if(m_ioHandler->send(message))
{
if(m_config.debugLogRXTX && (message != Heartbeat() || m_config.debugLogHeartbeat))
EventLoop::call(
[this, msg=toString(message)]()
{
Log::log(logId, LogMessage::D2001_TX_X, msg);
});
}
else
{} // log message and go to error state
}
void Kernel::startupDelayExpired(const boost::system::error_code& ec)
{
if(ec)
return;
send(GetInfo());
send(GetFeatures());
restartHeartbeatTimeout();
KernelBase::started();
}
void Kernel::restartHeartbeatTimeout()
{
m_heartbeatTimeout.expires_after(m_config.heartbeatTimeout);
m_heartbeatTimeout.async_wait(std::bind(&Kernel::heartbeatTimeoutExpired, this, std::placeholders::_1));
}
void Kernel::heartbeatTimeoutExpired(const boost::system::error_code& ec)
{
if(ec)
return;
m_heartbeatTimeout.cancel();
send(Heartbeat());
restartHeartbeatTimeout();
}
std::shared_ptr<Decoder> Kernel::getDecoder(uint16_t address, bool longAddress) const
{
const auto& decoderList = *m_world.decoders;
std::shared_ptr<Decoder> decoder = decoderList.getDecoder(longAddress ? DecoderProtocol::DCCLong : DecoderProtocol::DCCShort, address);
if(!decoder)
decoder = decoderList.getDecoder(address);
return decoder;
}
void Kernel::throttleSubscribe(uint16_t throttleId, std::pair<uint16_t, bool> key)
{
auto [unused, added] = m_throttleSubscriptions[throttleId].insert(key);
if(added)
{
EventLoop::call(
[this, key]()
{
if(auto it = m_decoderSubscriptions.find(key); it == m_decoderSubscriptions.end())
{
if(auto decoder = getDecoder(key.first, key.second))
m_decoderSubscriptions.emplace(key, DecoderSubscription{decoder->decoderChanged.connect(std::bind(&Kernel::throttleDecoderChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)), 1});
}
else
{
it->second.count++;
}
});
}
}
void Kernel::throttleUnsubscribe(uint16_t throttleId, std::pair<uint16_t, bool> key)
{
{
auto& subscriptions = m_throttleSubscriptions[throttleId];
subscriptions.erase(key);
if(subscriptions.empty())
m_throttleSubscriptions.erase(throttleId);
}
EventLoop::call(
[this, key]()
{
if(auto it = m_decoderSubscriptions.find(key); it != m_decoderSubscriptions.end())
{
assert(it->second.count > 0);
if(--it->second.count == 0)
{
it->second.connection.disconnect();
m_decoderSubscriptions.erase(it);
}
}
});
}
void Kernel::throttleDecoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber)
{
const std::pair<uint16_t, bool> key(decoder.address, decoder.protocol == DecoderProtocol::DCCLong);
if(has(changes, DecoderChangeFlags::Direction | DecoderChangeFlags::EmergencyStop | DecoderChangeFlags::SpeedSteps | DecoderChangeFlags::Throttle))
{
const bool emergencyStop = decoder.emergencyStop.value();
uint8_t speedMax = 0;
if(!emergencyStop)
{
speedMax = decoder.speedSteps.value();
if(speedMax == Decoder::speedStepsAuto)
speedMax = std::numeric_limits<uint8_t>::max();
}
m_ioContext.post(
[this,
key,
direction=decoder.direction.value(),
speed=speedMax > 0 ? Decoder::throttleToSpeedStep(decoder.throttle, speedMax) : 0,
speedMax]()
{
for(const auto& it : m_throttleSubscriptions)
if(it.second.count(key) != 0)
send(ThrottleSetSpeedDirection(it.first, key.first, key.second, speed, speedMax, direction));
});
}
if(has(changes, DecoderChangeFlags::FunctionValue))
{
assert(functionNumber <= std::numeric_limits<uint8_t>::max());
m_ioContext.post(
[this,
key,
number=static_cast<uint8_t>(functionNumber),
value=decoder.getFunctionValue(functionNumber)]()
{
for(const auto& it : m_throttleSubscriptions)
if(it.second.count(key) != 0)
send(ThrottleSetFunction(it.first, key.first, key.second, number, value));
});
}
}
}

Datei anzeigen

@ -0,0 +1,231 @@
/**
* server/src/hardware/protocol/cbus/kernel.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2022-2024 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_KERNEL_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_CBUS_KERNEL_HPP
#include "../kernelbase.hpp"
#include <unordered_map>
#include <set>
#include <boost/asio/steady_timer.hpp>
#include <boost/signals2/signal.hpp>
#include <traintastic/enum/tristate.hpp>
#include "config.hpp"
#include "featureflags.hpp"
#include "inputstate.hpp"
#include "outputstate.hpp"
#include "iohandler/iohandler.hpp"
class World;
enum class SimulateInputAction;
class InputController;
class OutputController;
class Decoder;
enum class DecoderChangeFlags;
namespace CBUS {
struct Message;
class Kernel : public ::KernelBase
{
private:
struct DecoderSubscription
{
boost::signals2::connection connection;
size_t count; //!< number of throttles subscribed to the decoder
};
World& m_world;
std::unique_ptr<IOHandler> m_ioHandler;
const bool m_simulation;
std::string m_logId;
boost::asio::steady_timer m_startupDelayTimer;
boost::asio::steady_timer m_heartbeatTimeout;
bool m_featureFlagsSet;
FeatureFlags1 m_featureFlags1;
FeatureFlags2 m_featureFlags2;
FeatureFlags3 m_featureFlags3;
FeatureFlags4 m_featureFlags4;
InputController* m_inputController;
std::unordered_map<uint16_t, InputState> m_inputValues;
OutputController* m_outputController;
std::unordered_map<uint16_t, OutputState> m_outputValues;
std::unordered_map<uint16_t, std::set<std::pair<uint16_t, bool>>> m_throttleSubscriptions;
std::map<std::pair<uint16_t, bool>, DecoderSubscription> m_decoderSubscriptions;
Config m_config;
Kernel(std::string logId, World& world, const Config& config, bool simulation);
void setIOHandler(std::unique_ptr<IOHandler> handler);
template<class T>
void postSend(const T& message)
{
m_ioContext.post(
[this, message]()
{
send(message);
});
}
void send(const Message& message);
inline bool hasFeatureInput() const { return contains(m_featureFlags1, FeatureFlags1::Input); }
inline bool hasFeatureOutput() const { return contains(m_featureFlags1, FeatureFlags1::Output); }
inline bool hasFeatureThrottle() const { return contains(m_featureFlags1, FeatureFlags1::Throttle); }
void startupDelayExpired(const boost::system::error_code& ec);
void restartHeartbeatTimeout();
void heartbeatTimeoutExpired(const boost::system::error_code& ec);
std::shared_ptr<Decoder> getDecoder(uint16_t address, bool longAddress) const;
void throttleSubscribe(uint16_t throttleId, std::pair<uint16_t, bool> key);
void throttleUnsubscribe(uint16_t throttleId, std::pair<uint16_t, bool> key);
void throttleDecoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber);
public:
static constexpr uint16_t ioAddressMin = 1;
static constexpr uint16_t ioAddressMax = std::numeric_limits<uint16_t>::max();
Kernel(const Kernel&) = delete;
Kernel& operator =(const Kernel&) = delete;
#ifndef NDEBUG
bool isKernelThread() const
{
return std::this_thread::get_id() == m_thread.get_id();
}
#endif
/**
* \brief Create kernel and IO handler
*
* \param[in] config TraintasticDIY 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_, World& world, const Config& config, Args... args)
{
static_assert(std::is_base_of_v<IOHandler, IOHandlerType>);
std::unique_ptr<Kernel> kernel{new Kernel(std::move(logId_), world, config, isSimulation<IOHandlerType>())};
kernel->setIOHandler(std::make_unique<IOHandlerType>(*kernel, std::forward<Args>(args)...));
return kernel;
}
/**
* \brief Access the IO handler
*
* \return The IO handler
* \note The IO handler runs in the kernel's IO context, not all functions can be called safely!
*/
template<class T>
T& ioHandler()
{
assert(dynamic_cast<T*>(m_ioHandler.get()));
return static_cast<T&>(*m_ioHandler);
}
/**
* \brief Set TraintasticDIY configuration
*
* \param[in] config The TraintasticDIY configuration
*/
void setConfig(const Config& config);
/**
* \brief Set the input controller
*
* \param[in] inputController The input controller
* \note This function may not be called when the kernel is running.
*/
inline void setInputController(InputController* inputController)
{
assert(!m_started);
m_inputController = inputController;
}
/**
* \brief Set the output controller
*
* \param[in] outputController The output controller
* \note This function may not be called when the kernel is running.
*/
inline void setOutputController(OutputController* outputController)
{
assert(!m_started);
m_outputController = outputController;
}
/**
* \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;
/**
* \brief ...
*
* This must be called by the IO handler whenever a TraintasticDIY message is received.
*
* \param[in] message The received TraintasticDIY message
* \note This function must run in the kernel's IO context
*/
void receive(const Message& message);
/**
*
* \param[in] address Output address, #ioAddressMin..#ioAddressMax
* \param[in] value Output value: \c true is on, \c false is off.
* \return \c true if send successful, \c false otherwise.
*/
bool setOutput(uint16_t address, bool value);
/**
* \brief Simulate input change
* \param[in] address Input address, #ioAddressMin..#ioAddressMax
* \param[in] action Simulation action to perform
*/
void simulateInputChange(uint16_t address, SimulateInputAction action);
};
}
#endif

Datei anzeigen

@ -0,0 +1,159 @@
/**
* server/src/hardware/protocol/cbus/messages.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2022-2023 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 "messages.hpp"
#include <cassert>
#include "../../../utils/tohex.hpp"
namespace CBUS {
static constexpr std::string_view toString(ThrottleSubUnsub::Action action)
{
switch(action)
{
case ThrottleSubUnsub::Unsubscribe:
return "unsub";
case ThrottleSubUnsub::Subscribe:
return "sub";
}
return {};
}
Checksum calcChecksum(const Message& message)
{
const auto* p = reinterpret_cast<const uint8_t*>(&message);
const size_t dataSize = message.dataSize();
uint8_t checksum = p[0];
for(size_t i = 1; i <= dataSize; i++)
checksum ^= p[i];
return static_cast<Checksum>(checksum);
}
void updateChecksum(Message& message)
{
*(reinterpret_cast<Checksum*>(&message) + message.dataSize() + 1) = calcChecksum(message);
}
bool isChecksumValid(const Message& message)
{
return calcChecksum(message) == *(reinterpret_cast<const Checksum*>(&message) + message.dataSize() + 1);
}
std::string toString(const Message& message)
{
std::string s{::toString(message.opCode)};
switch(message.opCode)
{
case OpCode::Heartbeat:
case OpCode::GetInfo:
case OpCode::GetFeatures:
assert(message.dataSize() == 0);
break;
case OpCode::GetInputState:
{
const auto& getInputState = static_cast<const GetInputState&>(message);
s.append(" address=").append(std::to_string(getInputState.address()));
break;
}
case OpCode::SetInputState:
{
const auto& setInputState = static_cast<const SetInputState&>(message);
s.append(" address=").append(std::to_string(setInputState.address()));
s.append(" state=").append(::toString(setInputState.state));
break;
}
case OpCode::GetOutputState:
{
const auto& getOutputState = static_cast<const GetOutputState&>(message);
s.append(" address=").append(std::to_string(getOutputState.address()));
break;
}
case OpCode::SetOutputState:
{
const auto& setOutputState = static_cast<const SetOutputState&>(message);
s.append(" address=").append(std::to_string(setOutputState.address()));
s.append(" state=").append(::toString(setOutputState.state));
break;
}
case OpCode::ThrottleSubUnsub:
{
const auto& throttleSubUnsub = static_cast<const ThrottleSubUnsub&>(message);
s.append(" throttle=").append(std::to_string(throttleSubUnsub.throttleId()));
s.append(" address=").append(std::to_string(throttleSubUnsub.address()));
s.append(" action=").append(toString(throttleSubUnsub.action()));
break;
}
case OpCode::ThrottleSetFunction:
{
const auto& throttleSetFunction = static_cast<const ThrottleSetFunction&>(message);
s.append(" throttle=").append(std::to_string(throttleSetFunction.throttleId()));
s.append(" address=").append(std::to_string(throttleSetFunction.address()));
s.append(" function=").append(std::to_string(throttleSetFunction.functionNumber()));
s.append(" value=").append(throttleSetFunction.functionValue() ? "on" : "off");
break;
}
case OpCode::ThrottleSetSpeedDirection:
{
const auto& throttleSetSpeedDirection = static_cast<const ThrottleSetSpeedDirection&>(message);
s.append(" throttle=").append(std::to_string(throttleSetSpeedDirection.throttleId()));
s.append(" address=").append(std::to_string(throttleSetSpeedDirection.address()));
if(throttleSetSpeedDirection.isSpeedSet())
{
if(!throttleSetSpeedDirection.isEmergencyStop())
{
s.append(" speed=").append(std::to_string(throttleSetSpeedDirection.speed));
s.append(" speed_max=").append(std::to_string(throttleSetSpeedDirection.speedMax));
}
else
s.append(" estop");
}
if(throttleSetSpeedDirection.isDirectionSet())
s.append(" direction=").append((throttleSetSpeedDirection.direction() == Direction::Forward) ? "fwd" : "rev");
break;
}
case OpCode::Features:
{
break;
}
case OpCode::Info:
{
break;
}
}
s.append(" [");
const auto* bytes = reinterpret_cast<const uint8_t*>(&message);
for(size_t i = 0; i < message.size(); i++)
{
if(i != 0)
s.append(" ");
s.append(toHex(bytes[i]));
}
s.append("]");
return s;
}
}

Datei anzeigen

@ -0,0 +1,410 @@
/**
* server/src/hardware/protocol/cbus/messages.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2022-2023 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_MESSAGES_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_CBUS_MESSAGES_HPP
#include <cstring>
#include <string>
#include "opcode.hpp"
#include "inputstate.hpp"
#include "outputstate.hpp"
#include "featureflags.hpp"
#include "../../../enum/direction.hpp"
#include "../../../utils/byte.hpp"
namespace CBUS {
using Checksum = std::byte;
struct Message;
Checksum calcChecksum(const Message& message);
void updateChecksum(Message& message);
bool isChecksumValid(const Message& message);
std::string toString(const Message& message);
struct Message
{
OpCode opCode;
Message(OpCode opCode_)
: opCode{opCode_}
{
}
size_t dataSize() const
{
if(const uint8_t len = (static_cast<uint8_t>(opCode) & 0x0F); len != 0x0F)
return len;
return sizeof(uint8_t) + *(reinterpret_cast<const uint8_t*>(this) + 1);
}
size_t size() const
{
return sizeof(Message) + dataSize() + 1;
}
};
struct Heartbeat : Message
{
Checksum checksum;
Heartbeat()
: Message(OpCode::Heartbeat)
, checksum{calcChecksum(*this)}
{
}
};
static_assert(sizeof(Heartbeat) == 2);
struct GetInputState : Message
{
uint8_t addressHigh;
uint8_t addressLow;
Checksum checksum;
GetInputState(uint16_t address_ = 0)
: Message(OpCode::GetInputState)
, addressHigh{high8(address_)}
, addressLow{low8(address_)}
, checksum{calcChecksum(*this)}
{
}
uint16_t address() const
{
return to16(addressLow, addressHigh);
}
};
static_assert(sizeof(GetInputState) == 4);
struct SetInputState : Message
{
uint8_t addressHigh;
uint8_t addressLow;
InputState state;
Checksum checksum;
SetInputState(uint16_t address_, InputState state_)
: Message(OpCode::SetInputState)
, addressHigh{high8(address_)}
, addressLow{low8(address_)}
, state{state_}
, checksum{calcChecksum(*this)}
{
}
uint16_t address() const
{
return to16(addressLow, addressHigh);
}
};
static_assert(sizeof(SetInputState) == 5);
struct GetOutputState : Message
{
uint8_t addressHigh;
uint8_t addressLow;
Checksum checksum;
GetOutputState(uint16_t address_ = 0)
: Message(OpCode::GetOutputState)
, addressHigh{high8(address_)}
, addressLow{low8(address_)}
, checksum{calcChecksum(*this)}
{
}
uint16_t address() const
{
return to16(addressLow, addressHigh);
}
};
static_assert(sizeof(GetOutputState) == 4);
struct SetOutputState : Message
{
uint8_t addressHigh;
uint8_t addressLow;
OutputState state;
Checksum checksum;
SetOutputState(uint16_t address_, OutputState state_)
: Message(OpCode::SetOutputState)
, addressHigh{high8(address_)}
, addressLow{low8(address_)}
, state{state_}
, checksum{calcChecksum(*this)}
{
}
uint16_t address() const
{
return to16(addressLow, addressHigh);
}
};
static_assert(sizeof(SetOutputState) == 5);
struct ThrottleMessage : Message
{
static constexpr uint8_t addressHighMask = 0x3F;
uint8_t throttleIdHigh;
uint8_t throttleIdLow;
uint8_t addressHigh;
uint8_t addressLow;
ThrottleMessage(OpCode opCode_, uint16_t throttleId_, uint16_t address_, bool longAddress)
: Message(opCode_)
, throttleIdHigh{high8(throttleId_)}
, throttleIdLow{low8(throttleId_)}
, addressHigh((high8(address_) & addressHighMask) | (longAddress ? 0x80 : 0x00))
, addressLow{low8(address_)}
{
}
uint16_t throttleId() const
{
return to16(throttleIdLow, throttleIdHigh);
}
uint16_t address() const
{
return to16(addressLow, addressHigh & addressHighMask);
}
bool isLongAddress() const
{
return (addressHigh & 0x80);
}
};
static_assert(sizeof(ThrottleMessage) == 5);
struct ThrottleSubUnsub : ThrottleMessage
{
static constexpr uint8_t addressHighSubUnsubBit = 0x40;
enum Action
{
Unsubscribe = 0,
Subscribe = 1
};
Checksum checksum;
ThrottleSubUnsub(uint16_t throttleId_, uint16_t address_, bool longAddress, Action action_)
: ThrottleMessage(OpCode::ThrottleSubUnsub, throttleId_, address_, longAddress)
, checksum{calcChecksum(*this)}
{
setAction(action_);
}
Action action() const
{
return (addressHigh & addressHighSubUnsubBit) ? Subscribe : Unsubscribe;
}
void setAction(Action value)
{
assert(value == Subscribe || value == Unsubscribe);
switch(value)
{
case Subscribe:
addressHigh |= addressHighSubUnsubBit; // set
break;
case Unsubscribe:
addressHigh &= ~addressHighSubUnsubBit; // clear
break;
}
}
};
static_assert(sizeof(ThrottleSubUnsub) == 6);
struct ThrottleSetSpeedDirection : ThrottleMessage
{
static constexpr uint8_t flagDirectionForward = 0x01;
static constexpr uint8_t flagDirectionSet = 0x40;
static constexpr uint8_t flagSpeedSet = 0x80;
uint8_t speed;
uint8_t speedMax;
uint8_t flags;
Checksum checksum;
ThrottleSetSpeedDirection(uint16_t throttleId_, uint16_t address_, bool longAddress, uint8_t speed_, uint8_t speedMax_, Direction direction_)
: ThrottleMessage(OpCode::ThrottleSetSpeedDirection, throttleId_, address_, longAddress)
, speed{speed_}
, speedMax{speedMax_}
, flags{flagSpeedSet | flagDirectionSet}
{
assert(direction_ != Direction::Unknown);
if(direction_ == Direction::Forward)
flags |= flagDirectionForward;
updateChecksum(*this);
}
ThrottleSetSpeedDirection(uint16_t throttleId_, uint16_t address_, bool longAddress, uint8_t speed_, uint8_t speedMax_)
: ThrottleMessage(OpCode::ThrottleSetSpeedDirection, throttleId_, address_, longAddress)
, speed{speed_}
, speedMax{speedMax_}
, flags{flagSpeedSet}
, checksum{calcChecksum(*this)}
{
}
ThrottleSetSpeedDirection(uint16_t throttleId_, uint16_t address_, bool longAddress, Direction direction_)
: ThrottleMessage(OpCode::ThrottleSetSpeedDirection, throttleId_, address_, longAddress)
, speed{0}
, speedMax{0}
, flags{flagDirectionSet}
{
assert(direction_ != Direction::Unknown);
if(direction_ == Direction::Forward)
flags |= flagDirectionForward;
updateChecksum(*this);
}
bool isEmergencyStop() const
{
return speedMax == 0;
}
float throttle() const
{
return (speedMax > 0) ? std::min<float>(static_cast<float>(speed) / static_cast<float>(speedMax), 1) : 0;
}
bool isSpeedSet() const
{
return (flags & flagSpeedSet);
}
bool isDirectionSet() const
{
return (flags & flagDirectionSet);
}
Direction direction() const
{
return (flags & flagDirectionForward) ? Direction::Forward : Direction::Reverse;
}
};
static_assert(sizeof(ThrottleSetSpeedDirection) == 9);
struct ThrottleSetFunction : ThrottleMessage
{
static constexpr uint8_t functionNumberMask = 0x7F;
static constexpr uint8_t functionValueMask = 0x80;
static constexpr uint8_t functionValueOn = 0x80;
static constexpr uint8_t functionValueOff = 0x00;
uint8_t function;
Checksum checksum;
ThrottleSetFunction(uint16_t throttleId_, uint16_t address_, bool longAddress, uint8_t functionNumber_, bool functionValue_)
: ThrottleMessage(OpCode::ThrottleSetFunction, throttleId_, address_, longAddress)
, function((functionNumber_ & functionNumberMask) | (functionValue_ ? functionValueOn : functionValueOff))
, checksum{calcChecksum(*this)}
{
}
uint8_t functionNumber() const
{
return function & functionNumberMask;
}
bool functionValue() const
{
return (function & functionValueMask) == functionValueOn;
}
};
static_assert(sizeof(ThrottleSetFunction) == 7);
struct GetFeatures : Message
{
Checksum checksum;
GetFeatures()
: Message(OpCode::GetFeatures)
, checksum{calcChecksum(*this)}
{
}
};
static_assert(sizeof(GetFeatures) == 2);
struct Features : Message
{
FeatureFlags1 featureFlags1;
FeatureFlags2 featureFlags2;
FeatureFlags3 featureFlags3;
FeatureFlags4 featureFlags4;
Checksum checksum;
Features(FeatureFlags1 ff1 = FeatureFlags1::None, FeatureFlags2 ff2 = FeatureFlags2::None, FeatureFlags3 ff3 = FeatureFlags3::None, FeatureFlags4 ff4 = FeatureFlags4::None)
: Message(OpCode::Features)
, featureFlags1{ff1}
, featureFlags2{ff2}
, featureFlags3{ff3}
, featureFlags4{ff4}
, checksum{calcChecksum(*this)}
{
}
};
static_assert(sizeof(Features) == 6);
struct GetInfo : Message
{
Checksum checksum;
GetInfo()
: Message(OpCode::GetInfo)
, checksum{calcChecksum(*this)}
{
}
};
static_assert(sizeof(GetInfo) == 2);
struct InfoBase : Message
{
uint8_t length;
std::string_view text() const
{
return {reinterpret_cast<const char*>(this) + sizeof(Message) + sizeof(length), length};
}
};
static_assert(sizeof(InfoBase) == 2);
}
inline bool operator ==(const CBUS::Message& lhs, const CBUS::Message& rhs)
{
return lhs.size() == rhs.size() && std::memcmp(&lhs, &rhs, lhs.size()) == 0;
}
inline bool operator !=(const CBUS::Message& lhs, const CBUS::Message& rhs)
{
return lhs.size() != rhs.size() || std::memcmp(&lhs, &rhs, lhs.size()) != 0;
}
#endif

Datei anzeigen

@ -0,0 +1,93 @@
/**
* server/src/hardware/protocol/cbus/opcode.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2022-2023 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_OPCODE_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_CBUS_OPCODE_HPP
#include <cstdint>
namespace CBUS {
enum class OpCode : uint8_t
{
Heartbeat = 0x00,
GetInputState = 0x12,
SetInputState = 0x13,
GetOutputState = 0x22,
SetOutputState = 0x23,
ThrottleSubUnsub = 0x34,
ThrottleSetFunction = 0x35,
ThrottleSetSpeedDirection = 0x37,
GetFeatures = 0xE0,
Features = 0xE4,
GetInfo = 0xF0,
Info = 0xFF,
};
}
constexpr std::string_view toString(CBUS::OpCode value)
{
using OpCode = CBUS::OpCode;
switch(value)
{
case OpCode::Heartbeat:
return "Heartbeat";
case OpCode::GetInputState:
return "GetInputState";
case OpCode::SetInputState:
return "SetInputState";
case OpCode::GetOutputState:
return "GetOutputState";
case OpCode::SetOutputState:
return "SetOutputState";
case OpCode::ThrottleSubUnsub:
return "ThrottleSubUnsub";
case OpCode::ThrottleSetFunction:
return "ThrottleSetFunction";
case OpCode::ThrottleSetSpeedDirection:
return "ThrottleSetSpeedDirection";
case OpCode::GetFeatures:
return "GetFeatures";
case OpCode::Features:
return "Features";
case OpCode::GetInfo:
return "GetInfo";
case OpCode::Info:
return "Info";
}
return {};
}
#endif

Datei anzeigen

@ -0,0 +1,62 @@
/**
* server/src/hardware/protocol/cbus/outputstate.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_CBUS_OUTPUTSTATE_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_CBUS_OUTPUTSTATE_HPP
#include <cstdint>
#include <string_view>
namespace CBUS {
enum class OutputState : uint8_t
{
Undefined = 0,
False = 1,
True = 2,
Invalid = 3,
};
}
constexpr std::string_view toString(CBUS::OutputState value)
{
using OutputState = CBUS::OutputState;
switch(value)
{
case OutputState::Undefined:
return "Undefined";
case OutputState::False:
return "False";
case OutputState::True:
return "True";
case OutputState::Invalid:
return "Invalid";
}
return {};
}
#endif

Datei anzeigen

@ -0,0 +1,61 @@
/**
* server/src/hardware/protocol/cbus/settings.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2022-2023 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 "settings.hpp"
#include "../../../core/attributes.hpp"
#include "../../../utils/displayname.hpp"
namespace CBUS {
Settings::Settings(Object& _parent, std::string_view parentPropertyName)
: SubObject(_parent, parentPropertyName)
, startupDelay{this, "startup_delay", startupDelayDefault, PropertyFlags::ReadWrite | PropertyFlags::Store}
, heartbeatTimeout{this, "heartbeat_timeout", heartbeatTimeoutDefault, PropertyFlags::ReadWrite | PropertyFlags::Store}
, debugLogRXTX{this, "debug_log_rx_tx", false, PropertyFlags::ReadWrite | PropertyFlags::Store}
, debugLogHeartbeat{this, "debug_log_heartbeat", false, PropertyFlags::ReadWrite | PropertyFlags::Store}
{
Attributes::addMinMax(startupDelay, startupDelayMin, startupDelayMax);
m_interfaceItems.add(startupDelay);
Attributes::addMinMax(heartbeatTimeout, heartbeatTimeoutMin, heartbeatTimeoutMax);
m_interfaceItems.add(heartbeatTimeout);
Attributes::addDisplayName(debugLogRXTX, DisplayName::Hardware::debugLogRXTX);
m_interfaceItems.add(debugLogRXTX);
m_interfaceItems.add(debugLogHeartbeat);
}
Config Settings::config() const
{
Config config;
config.startupDelay = std::chrono::milliseconds(startupDelay);
config.heartbeatTimeout = std::chrono::milliseconds(heartbeatTimeout);
config.debugLogRXTX = debugLogRXTX;
config.debugLogHeartbeat = debugLogHeartbeat;
return config;
}
}

Datei anzeigen

@ -0,0 +1,57 @@
/**
* server/src/hardware/protocol/cbus/settings.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2022-2023 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_SETTINGS_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_CBUS_SETTINGS_HPP
#include "../../../core/subobject.hpp"
#include "../../../core/property.hpp"
#include "config.hpp"
namespace CBUS {
class Settings final : public SubObject
{
CLASS_ID("cbus_settings")
private:
static constexpr uint16_t startupDelayMin = 0;
static constexpr uint16_t startupDelayDefault = 500;
static constexpr uint16_t startupDelayMax = 60'000;
static constexpr uint16_t heartbeatTimeoutMin = 100;
static constexpr uint16_t heartbeatTimeoutDefault = 1'000;
static constexpr uint16_t heartbeatTimeoutMax = 60'000;
public:
Property<uint16_t> startupDelay;
Property<uint16_t> heartbeatTimeout;
Property<bool> debugLogRXTX;
Property<bool> debugLogHeartbeat;
Settings(Object& _parent, std::string_view parentPropertyName);
Config config() const;
};
}
#endif

Datei anzeigen

@ -65,6 +65,7 @@ namespace DisplayName
constexpr std::string_view throttles = "hardware:throttles"; constexpr std::string_view throttles = "hardware:throttles";
constexpr std::string_view xpressnet = "hardware:xpressnet"; constexpr std::string_view xpressnet = "hardware:xpressnet";
constexpr std::string_view z21 = "hardware:z21"; constexpr std::string_view z21 = "hardware:z21";
constexpr std::string_view cbus = "hardware:cbus";
} }
namespace Interface namespace Interface
{ {

Datei anzeigen

@ -126,7 +126,6 @@ void World::init(World& world)
world.identificationControllers.setValueInternal(std::make_shared<ControllerList<IdentificationController>>(world, world.identificationControllers.name())); world.identificationControllers.setValueInternal(std::make_shared<ControllerList<IdentificationController>>(world, world.identificationControllers.name()));
world.lncvProgrammingControllers.setValueInternal(std::make_shared<ControllerList<LNCVProgrammingController>>(world, world.lncvProgrammingControllers.name())); world.lncvProgrammingControllers.setValueInternal(std::make_shared<ControllerList<LNCVProgrammingController>>(world, world.lncvProgrammingControllers.name()));
world.loconetInterfaces.setValueInternal(std::make_shared<ControllerList<LocoNetInterface>>(world, world.loconetInterfaces.name())); world.loconetInterfaces.setValueInternal(std::make_shared<ControllerList<LocoNetInterface>>(world, world.loconetInterfaces.name()));
world.interfaces.setValueInternal(std::make_shared<InterfaceList>(world, world.interfaces.name())); world.interfaces.setValueInternal(std::make_shared<InterfaceList>(world, world.interfaces.name()));
world.decoders.setValueInternal(std::make_shared<DecoderList>(world, world.decoders.name(), decoderListColumns)); world.decoders.setValueInternal(std::make_shared<DecoderList>(world, world.decoders.name(), decoderListColumns));
world.inputs.setValueInternal(std::make_shared<InputList>(world, world.inputs.name(), inputListColumns)); world.inputs.setValueInternal(std::make_shared<InputList>(world, world.inputs.name(), inputListColumns));

Datei anzeigen

@ -1,14 +0,0 @@
{
"default-registry": {
"kind": "git",
"baseline": "b322364f06308bdd24823f9d8f03fe0cc86fd46f",
"repository": "https://github.com/microsoft/vcpkg"
},
"registries": [
{
"kind": "artifact",
"location": "https://github.com/microsoft/vcpkg-ce-catalog/archive/refs/heads/main.zip",
"name": "microsoft"
}
]
}

Datei anzeigen

@ -1,17 +0,0 @@
{
"dependencies": [
"boost-asio",
"boost-beast",
"boost-program-options",
"boost-signals2",
"boost-uuid",
"boost-url",
{
"name": "libarchive",
"default-features": false,
"features": ["lzma"]
},
"lua",
"zlib"
]
}

Datei anzeigen

@ -0,0 +1,43 @@
/**
* shared/src/traintastic/enum/cbusinterfacetype.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_SHARED_TRAINTASTIC_ENUM_CBUSINTERFACETYPE_HPP
#define TRAINTASTIC_SHARED_TRAINTASTIC_ENUM_CBUSINTERFACETYPE_HPP
#include <cstdint>
#include "enum.hpp"
enum class CBUSInterfaceType : uint8_t
{
Serial = 0,
NetworkTCP = 1,
};
TRAINTASTIC_ENUM(CBUSInterfaceType, "cbus_interface_type", 2,
{
{CBUSInterfaceType::Serial, "serial"},
{CBUSInterfaceType::NetworkTCP, "network_tcp"},
});
#endif

Datei anzeigen

@ -757,7 +757,7 @@
}, },
{ {
"term": "hardware:throttles", "term": "hardware:throttles",
"definition": "Drosseln" "definition": "Handregler"
}, },
{ {
"term": "hsi88:s88_left", "term": "hsi88:s88_left",
@ -823,6 +823,10 @@
"term": "interface.loconet:interface", "term": "interface.loconet:interface",
"definition": "Schnittstelle" "definition": "Schnittstelle"
}, },
{
"term": "interface.cbus:interface",
"definition": "Schnittstelle"
},
{ {
"term": "interface.marklin_can:marklin_can_locomotive_list", "term": "interface.marklin_can:marklin_can_locomotive_list",
"definition": "M\u00e4rklin CAN: Lokomotivenliste" "definition": "M\u00e4rklin CAN: Lokomotivenliste"
@ -873,7 +877,7 @@
}, },
{ {
"term": "interface_list:list_is_empty", "term": "interface_list:list_is_empty",
"definition": "Schnittstelle ist nicht leer.\nDr\u00fccke dem Plus-Knopf um eine \nneue Schnittstelle zu erstellen." "definition": "Keine Schnittstelle definiert.\nDr\u00fccke dem Plus-Knopf um eine \nneue Schnittstelle zu erstellen."
}, },
{ {
"term": "interface_state:error", "term": "interface_state:error",
@ -2717,7 +2721,7 @@
}, },
{ {
"term": "tray_icon.language_changed_message_box:text", "term": "tray_icon.language_changed_message_box:text",
"definition": "Tritastic server muss neu gestartet werden, damit die Sprach\u00e4nderung wirksam wird. Jetzt Neustarten?" "definition": "Traitastic server muss neu gestartet werden, damit die Sprach\u00e4nderung wirksam wird. Jetzt Neustarten?"
}, },
{ {
"term": "tray_icon.menu:advanced", "term": "tray_icon.menu:advanced",
@ -3299,4 +3303,4 @@
"term": "zone:speed_limit", "term": "zone:speed_limit",
"definition": "Geschwindigkeitbegrenzung" "definition": "Geschwindigkeitbegrenzung"
} }
] ]