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
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:
Ursprung
2bfbbb267f
Commit
265ac0b84d
24
.clang-tidy
24
.clang-tidy
@ -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}
|
||||
@ -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
1
.gitignore
vendored
@ -49,4 +49,5 @@ CMakeLists.txt.*
|
||||
shared/translations/*.lang
|
||||
CMakeSettings.json
|
||||
.venv
|
||||
traintastic.geany
|
||||
|
||||
|
||||
@ -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": {
|
||||
"title": "$wizard.add_interface.connection:title$",
|
||||
"text": "$wizard.add_interface.connection:text$",
|
||||
|
||||
@ -119,6 +119,10 @@ file(GLOB SOURCES
|
||||
"src/hardware/programming/lncv/*.cpp"
|
||||
"src/hardware/protocol/*.hpp"
|
||||
"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/*.cpp"
|
||||
"src/hardware/protocol/dccex/iohandler/*.hpp"
|
||||
|
||||
35
server/src/enum/cbusinterfacetype.hpp
Normale Datei
35
server/src/enum/cbusinterfacetype.hpp
Normale Datei
@ -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
|
||||
|
||||
163
server/src/hardware/interface/cbusinterface.cpp
Normale Datei
163
server/src/hardware/interface/cbusinterface.cpp
Normale Datei
@ -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);
|
||||
}
|
||||
84
server/src/hardware/interface/cbusinterface.hpp
Normale Datei
84
server/src/hardware/interface/cbusinterface.hpp
Normale Datei
@ -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
|
||||
41
server/src/hardware/protocol/cbus/config.hpp
Normale Datei
41
server/src/hardware/protocol/cbus/config.hpp
Normale Datei
@ -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
|
||||
85
server/src/hardware/protocol/cbus/featureflags.hpp
Normale Datei
85
server/src/hardware/protocol/cbus/featureflags.hpp
Normale Datei
@ -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
|
||||
62
server/src/hardware/protocol/cbus/inputstate.hpp
Normale Datei
62
server/src/hardware/protocol/cbus/inputstate.hpp
Normale Datei
@ -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
|
||||
101
server/src/hardware/protocol/cbus/iohandler/hardwareiohandler.cpp
Normale Datei
101
server/src/hardware/protocol/cbus/iohandler/hardwareiohandler.cpp
Normale Datei
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
54
server/src/hardware/protocol/cbus/iohandler/hardwareiohandler.hpp
Normale Datei
54
server/src/hardware/protocol/cbus/iohandler/hardwareiohandler.hpp
Normale Datei
@ -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
|
||||
61
server/src/hardware/protocol/cbus/iohandler/iohandler.hpp
Normale Datei
61
server/src/hardware/protocol/cbus/iohandler/iohandler.hpp
Normale Datei
@ -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
|
||||
110
server/src/hardware/protocol/cbus/iohandler/serialiohandler.cpp
Normale Datei
110
server/src/hardware/protocol/cbus/iohandler/serialiohandler.cpp
Normale Datei
@ -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();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
50
server/src/hardware/protocol/cbus/iohandler/serialiohandler.hpp
Normale Datei
50
server/src/hardware/protocol/cbus/iohandler/serialiohandler.hpp
Normale Datei
@ -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
|
||||
130
server/src/hardware/protocol/cbus/iohandler/simulationiohandler.cpp
Normale Datei
130
server/src/hardware/protocol/cbus/iohandler/simulationiohandler.cpp
Normale Datei
@ -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()));
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
55
server/src/hardware/protocol/cbus/iohandler/simulationiohandler.hpp
Normale Datei
55
server/src/hardware/protocol/cbus/iohandler/simulationiohandler.hpp
Normale Datei
@ -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
|
||||
|
||||
145
server/src/hardware/protocol/cbus/iohandler/tcpiohandler.cpp
Normale Datei
145
server/src/hardware/protocol/cbus/iohandler/tcpiohandler.cpp
Normale Datei
@ -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();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
53
server/src/hardware/protocol/cbus/iohandler/tcpiohandler.hpp
Normale Datei
53
server/src/hardware/protocol/cbus/iohandler/tcpiohandler.hpp
Normale Datei
@ -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
|
||||
595
server/src/hardware/protocol/cbus/kernel.cpp
Normale Datei
595
server/src/hardware/protocol/cbus/kernel.cpp
Normale Datei
@ -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));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
231
server/src/hardware/protocol/cbus/kernel.hpp
Normale Datei
231
server/src/hardware/protocol/cbus/kernel.hpp
Normale Datei
@ -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
|
||||
159
server/src/hardware/protocol/cbus/messages.cpp
Normale Datei
159
server/src/hardware/protocol/cbus/messages.cpp
Normale Datei
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
410
server/src/hardware/protocol/cbus/messages.hpp
Normale Datei
410
server/src/hardware/protocol/cbus/messages.hpp
Normale Datei
@ -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
|
||||
93
server/src/hardware/protocol/cbus/opcode.hpp
Normale Datei
93
server/src/hardware/protocol/cbus/opcode.hpp
Normale Datei
@ -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
|
||||
62
server/src/hardware/protocol/cbus/outputstate.hpp
Normale Datei
62
server/src/hardware/protocol/cbus/outputstate.hpp
Normale Datei
@ -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
|
||||
61
server/src/hardware/protocol/cbus/settings.cpp
Normale Datei
61
server/src/hardware/protocol/cbus/settings.cpp
Normale Datei
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
57
server/src/hardware/protocol/cbus/settings.hpp
Normale Datei
57
server/src/hardware/protocol/cbus/settings.hpp
Normale Datei
@ -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
|
||||
@ -65,6 +65,7 @@ namespace DisplayName
|
||||
constexpr std::string_view throttles = "hardware:throttles";
|
||||
constexpr std::string_view xpressnet = "hardware:xpressnet";
|
||||
constexpr std::string_view z21 = "hardware:z21";
|
||||
constexpr std::string_view cbus = "hardware:cbus";
|
||||
}
|
||||
namespace Interface
|
||||
{
|
||||
|
||||
@ -126,7 +126,6 @@ void World::init(World& world)
|
||||
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.loconetInterfaces.setValueInternal(std::make_shared<ControllerList<LocoNetInterface>>(world, world.loconetInterfaces.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.inputs.setValueInternal(std::make_shared<InputList>(world, world.inputs.name(), inputListColumns));
|
||||
|
||||
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -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"
|
||||
]
|
||||
}
|
||||
43
shared/src/traintastic/enum/cbusinterfacetype.hpp
Normale Datei
43
shared/src/traintastic/enum/cbusinterfacetype.hpp
Normale Datei
@ -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
|
||||
|
||||
|
||||
@ -757,7 +757,7 @@
|
||||
},
|
||||
{
|
||||
"term": "hardware:throttles",
|
||||
"definition": "Drosseln"
|
||||
"definition": "Handregler"
|
||||
},
|
||||
{
|
||||
"term": "hsi88:s88_left",
|
||||
@ -823,6 +823,10 @@
|
||||
"term": "interface.loconet:interface",
|
||||
"definition": "Schnittstelle"
|
||||
},
|
||||
{
|
||||
"term": "interface.cbus:interface",
|
||||
"definition": "Schnittstelle"
|
||||
},
|
||||
{
|
||||
"term": "interface.marklin_can:marklin_can_locomotive_list",
|
||||
"definition": "M\u00e4rklin CAN: Lokomotivenliste"
|
||||
@ -873,7 +877,7 @@
|
||||
},
|
||||
{
|
||||
"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",
|
||||
@ -2717,7 +2721,7 @@
|
||||
},
|
||||
{
|
||||
"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",
|
||||
|
||||
Laden…
x
In neuem Issue referenzieren
Einen Benutzer sperren