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
|
shared/translations/*.lang
|
||||||
CMakeSettings.json
|
CMakeSettings.json
|
||||||
.venv
|
.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": {
|
"connection_z21": {
|
||||||
"title": "$wizard.add_interface.connection:title$",
|
"title": "$wizard.add_interface.connection:title$",
|
||||||
"text": "$wizard.add_interface.connection:text$",
|
"text": "$wizard.add_interface.connection:text$",
|
||||||
|
|||||||
@ -119,6 +119,10 @@ file(GLOB SOURCES
|
|||||||
"src/hardware/programming/lncv/*.cpp"
|
"src/hardware/programming/lncv/*.cpp"
|
||||||
"src/hardware/protocol/*.hpp"
|
"src/hardware/protocol/*.hpp"
|
||||||
"src/hardware/protocol/*.cpp"
|
"src/hardware/protocol/*.cpp"
|
||||||
|
"src/hardware/protocol/cbus/*.hpp"
|
||||||
|
"src/hardware/protocol/cbus/*.cpp"
|
||||||
|
"src/hardware/protocol/cbus/iohandler/*.hpp"
|
||||||
|
"src/hardware/protocol/cbus/iohandler/*.cpp"
|
||||||
"src/hardware/protocol/dccex/*.hpp"
|
"src/hardware/protocol/dccex/*.hpp"
|
||||||
"src/hardware/protocol/dccex/*.cpp"
|
"src/hardware/protocol/dccex/*.cpp"
|
||||||
"src/hardware/protocol/dccex/iohandler/*.hpp"
|
"src/hardware/protocol/dccex/iohandler/*.hpp"
|
||||||
|
|||||||
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 throttles = "hardware:throttles";
|
||||||
constexpr std::string_view xpressnet = "hardware:xpressnet";
|
constexpr std::string_view xpressnet = "hardware:xpressnet";
|
||||||
constexpr std::string_view z21 = "hardware:z21";
|
constexpr std::string_view z21 = "hardware:z21";
|
||||||
|
constexpr std::string_view cbus = "hardware:cbus";
|
||||||
}
|
}
|
||||||
namespace Interface
|
namespace Interface
|
||||||
{
|
{
|
||||||
|
|||||||
@ -126,7 +126,6 @@ void World::init(World& world)
|
|||||||
world.identificationControllers.setValueInternal(std::make_shared<ControllerList<IdentificationController>>(world, world.identificationControllers.name()));
|
world.identificationControllers.setValueInternal(std::make_shared<ControllerList<IdentificationController>>(world, world.identificationControllers.name()));
|
||||||
world.lncvProgrammingControllers.setValueInternal(std::make_shared<ControllerList<LNCVProgrammingController>>(world, world.lncvProgrammingControllers.name()));
|
world.lncvProgrammingControllers.setValueInternal(std::make_shared<ControllerList<LNCVProgrammingController>>(world, world.lncvProgrammingControllers.name()));
|
||||||
world.loconetInterfaces.setValueInternal(std::make_shared<ControllerList<LocoNetInterface>>(world, world.loconetInterfaces.name()));
|
world.loconetInterfaces.setValueInternal(std::make_shared<ControllerList<LocoNetInterface>>(world, world.loconetInterfaces.name()));
|
||||||
|
|
||||||
world.interfaces.setValueInternal(std::make_shared<InterfaceList>(world, world.interfaces.name()));
|
world.interfaces.setValueInternal(std::make_shared<InterfaceList>(world, world.interfaces.name()));
|
||||||
world.decoders.setValueInternal(std::make_shared<DecoderList>(world, world.decoders.name(), decoderListColumns));
|
world.decoders.setValueInternal(std::make_shared<DecoderList>(world, world.decoders.name(), decoderListColumns));
|
||||||
world.inputs.setValueInternal(std::make_shared<InputList>(world, world.inputs.name(), inputListColumns));
|
world.inputs.setValueInternal(std::make_shared<InputList>(world, world.inputs.name(), inputListColumns));
|
||||||
|
|||||||
@ -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",
|
"term": "hardware:throttles",
|
||||||
"definition": "Drosseln"
|
"definition": "Handregler"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"term": "hsi88:s88_left",
|
"term": "hsi88:s88_left",
|
||||||
@ -823,6 +823,10 @@
|
|||||||
"term": "interface.loconet:interface",
|
"term": "interface.loconet:interface",
|
||||||
"definition": "Schnittstelle"
|
"definition": "Schnittstelle"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"term": "interface.cbus:interface",
|
||||||
|
"definition": "Schnittstelle"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"term": "interface.marklin_can:marklin_can_locomotive_list",
|
"term": "interface.marklin_can:marklin_can_locomotive_list",
|
||||||
"definition": "M\u00e4rklin CAN: Lokomotivenliste"
|
"definition": "M\u00e4rklin CAN: Lokomotivenliste"
|
||||||
@ -873,7 +877,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"term": "interface_list:list_is_empty",
|
"term": "interface_list:list_is_empty",
|
||||||
"definition": "Schnittstelle ist nicht leer.\nDr\u00fccke dem Plus-Knopf um eine \nneue Schnittstelle zu erstellen."
|
"definition": "Keine Schnittstelle definiert.\nDr\u00fccke dem Plus-Knopf um eine \nneue Schnittstelle zu erstellen."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"term": "interface_state:error",
|
"term": "interface_state:error",
|
||||||
@ -2717,7 +2721,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"term": "tray_icon.language_changed_message_box:text",
|
"term": "tray_icon.language_changed_message_box:text",
|
||||||
"definition": "Tritastic server muss neu gestartet werden, damit die Sprach\u00e4nderung wirksam wird. Jetzt Neustarten?"
|
"definition": "Traitastic server muss neu gestartet werden, damit die Sprach\u00e4nderung wirksam wird. Jetzt Neustarten?"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"term": "tray_icon.menu:advanced",
|
"term": "tray_icon.menu:advanced",
|
||||||
@ -3299,4 +3303,4 @@
|
|||||||
"term": "zone:speed_limit",
|
"term": "zone:speed_limit",
|
||||||
"definition": "Geschwindigkeitbegrenzung"
|
"definition": "Geschwindigkeitbegrenzung"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
Laden…
x
In neuem Issue referenzieren
Einen Benutzer sperren