pax_global_header00006660000000000000000000000064150045625060014515gustar00rootroot0000000000000052 comment=c3d223e9110e15ebc0d0a442e6b9f8170efd01b3 railcontrol-24+dfsg1/000077500000000000000000000000001500456250600146345ustar00rootroot00000000000000railcontrol-24+dfsg1/.cproject000066400000000000000000000117261500456250600164550ustar00rootroot00000000000000 make all true true true railcontrol-24+dfsg1/.gitignore000066400000000000000000000005441500456250600166270ustar00rootroot00000000000000railcontrol railcontrol.conf railcontrol.exe railcontrol.sqlite railcontrol.sqlite-journal railcontrol.sqlite.* railcontrol.log railcontrol.log.* *.tar.xz *.zip Storage/sqlite/sqlite3 *.o *.so *.bak test/testloco tools/Cc-Schnitte-Sniffer gmon.out amalgamation.cpp .*.swp svg/* Version.cpp .settings/ CMakeCache.txt CMakeFiles/* cmake_install.cmake .idea/ railcontrol-24+dfsg1/.project000066400000000000000000000014541500456250600163070ustar00rootroot00000000000000 RailControl org.eclipse.cdt.managedbuilder.core.genmakebuilder clean,full,incremental, org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder full,incremental, org.eclipse.cdt.core.cnature org.eclipse.cdt.core.ccnature org.eclipse.cdt.managedbuilder.core.managedBuildNature org.eclipse.cdt.managedbuilder.core.ScannerConfigNature railcontrol-24+dfsg1/ArgumentHandler.cpp000066400000000000000000000034611500456250600204240ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include #include "ArgumentHandler.h" #include "Utils/Utils.h" ArgumentHandler::ArgumentHandler(const int argc, char* argv[], const std::map& linkMap, const char defaultSwitch) { for (int i = 1; i < argc; ++i) { char* arg = argv[i]; if (arg[0] != '-') { argumentMap[defaultSwitch] = arg; continue; } if (arg[1] != '-') { argumentMap[arg[1]] = ""; continue; } std::string argString = arg + 2; std::deque parts; Utils::Utils::SplitString(argString, "=", parts); if (parts.size() < 1) { continue; } std::string& argumentLong = parts[0]; if (linkMap.count(argumentLong) == 0) { std::cout << "Unknown argument " << argumentLong << std::endl; continue; } char argumentShort = linkMap.at(argumentLong); argumentMap[argumentShort] = (parts.size() == 1 ? "" : parts[1]); } } std::string ArgumentHandler::GetArgumentString(const char argument, const std::string& defaultValue) { if (GetArgumentBool(argument) == false) { return defaultValue; } return argumentMap.at(argument); } railcontrol-24+dfsg1/ArgumentHandler.h000066400000000000000000000026371500456250600200750ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include "Utils/Integer.h" class ArgumentHandler { public: ArgumentHandler() = delete; ArgumentHandler(const int argc, char* argv[], const std::map& linkMap, const char defaultSwitch); inline bool GetArgumentBool(const char argument) { return (argumentMap.count(argument) == 1); } std::string GetArgumentString(const char argument, const std::string& defaultValue = ""); inline int GetArgumentInt(const char argument, const int defaultValue = 0) { return Utils::Integer::StringToInteger(GetArgumentString(argument, std::to_string(defaultValue))); } private: std::map argumentMap; }; railcontrol-24+dfsg1/CMakeLists.txt000066400000000000000000000210361500456250600173760ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.5) set(RAILCONTROL_VERSION "24") project(CMakeTest) add_executable(railcontrol Storage/sqlite/sqlite3.c Storage/sqlite/sqlite3.h Storage/sqlite/sqlite3ext.h Manager.cpp Manager.h Server/Web/WebClient.cpp Server/Web/WebClient.h ArgumentHandler.cpp ArgumentHandler.h Config.cpp Config.h ControlInterface.h DataModel/Accessory.cpp DataModel/Accessory.h DataModel/AccessoryBase.cpp DataModel/AccessoryBase.h DataModel/AccessoryConfig.h DataModel/Cluster.cpp DataModel/Cluster.h DataModel/DataModel.h DataModel/Feedback.cpp DataModel/Feedback.h DataModel/FeedbackConfig.h DataModel/HardwareHandle.cpp DataModel/HardwareHandle.h DataModel/Layer.h DataModel/LayoutItem.cpp DataModel/LayoutItem.h DataModel/LockableItem.cpp DataModel/LockableItem.h DataModel/Loco.cpp DataModel/Loco.h DataModel/LocoBase.cpp DataModel/LocoBase.h DataModel/LocoConfig.h DataModel/LocoFunctions.cpp DataModel/LocoFunctions.h DataModel/MultipleUnit.cpp DataModel/MultipleUnit.h DataModel/Object.cpp DataModel/Object.h DataModel/ObjectIdentifier.cpp DataModel/ObjectIdentifier.h DataModel/Relation.cpp DataModel/Relation.h DataModel/Route.cpp DataModel/Route.h DataModel/Serializable.cpp DataModel/Serializable.h DataModel/Signal.cpp DataModel/Signal.h DataModel/Switch.cpp DataModel/Switch.h DataModel/Text.cpp DataModel/Text.h DataModel/Track.cpp DataModel/Track.h DataTypes.h Fallthrough.h Hardware/AccessoryCache.cpp Hardware/AccessoryCache.h Hardware/CS1.h Hardware/CS2Tcp.cpp Hardware/CS2Tcp.h Hardware/CS2Udp.cpp Hardware/CS2Udp.h Hardware/Capabilities.h Hardware/CcSchnitte.cpp Hardware/CcSchnitte.h Hardware/DR5000.h Hardware/DccPpExSerial.cpp Hardware/DccPpExSerial.h Hardware/DccPpExTcp.cpp Hardware/DccPpExTcp.h Hardware/Ecos.h Hardware/FeedbackCache.cpp Hardware/FeedbackCache.h Hardware/HardwareHandler.cpp Hardware/HardwareHandler.h Hardware/HardwareInterface.h Hardware/HardwareParams.h Hardware/Hsi88.cpp Hardware/Hsi88.h Hardware/Intellibox.h Hardware/Intellibox2.h Hardware/LocoCache.cpp Hardware/LocoCache.h Hardware/LocoNetAdapter63120.h Hardware/LocoNetAdapter63820.h Hardware/M6051.cpp Hardware/M6051.h Hardware/MasterControl.h Hardware/MasterControl2.h Hardware/OpenDcc.h Hardware/Protocols/DccPpEx.cpp Hardware/Protocols/DccPpEx.h Hardware/Protocols/EsuCAN.cpp Hardware/Protocols/EsuCAN.h Hardware/Protocols/LocoNet.cpp Hardware/Protocols/LocoNet.h Hardware/Protocols/LocoNetLocoCache.h Hardware/Protocols/MaerklinCAN.h Hardware/Protocols/MaerklinCANCommon.cpp Hardware/Protocols/MaerklinCANCommon.h Hardware/Protocols/P50x.cpp Hardware/Protocols/P50x.h Hardware/Protocols/P50xCache.h Hardware/Protocols/P50xEthernet.h Hardware/Protocols/P50xSerial.h Hardware/Protocols/P50xUhlenbrock.h Hardware/Protocols/Z21.cpp Hardware/Protocols/Z21.h Hardware/Protocols/Z21FeedbackCache.h Hardware/Protocols/Z21LocoCache.h Hardware/Protocols/Z21TurnoutCache.h Hardware/RedBox.h Hardware/Rektor.h Hardware/TwinCenter.h Hardware/Virtual.cpp Hardware/Virtual.h Hardware/Z21.h Hardware/ZLib.cpp Hardware/ZLib.h Languages.cpp Languages.h Logger/Logger.cpp Logger/Logger.h Logger/LoggerClient.h Logger/LoggerClientConsole.h Logger/LoggerClientFile.h Logger/LoggerClientTcp.h Logger/LoggerServer.cpp Logger/LoggerServer.h Network/Select.h Network/Serial.cpp Network/Serial.h Network/TcpClient.cpp Network/TcpClient.h Network/TcpConnection.cpp Network/TcpConnection.h Network/TcpServer.cpp Network/TcpServer.h Network/UdpClient.h Network/UdpConnection.cpp Network/UdpConnection.h Network/UdpServer.cpp Network/UdpServer.h RailControl.cpp RailControl.h Server/CS2/CS2Client.cpp Server/CS2/CS2Client.h Server/CS2/CS2Server.cpp Server/CS2/CS2Server.h Server/Web/HtmlTag.cpp Server/Web/HtmlTag.h Server/Web/HtmlTagAccessory.cpp Server/Web/HtmlTagAccessory.h Server/Web/HtmlTagButton.cpp Server/Web/HtmlTagButton.h Server/Web/HtmlTagButtonCancel.h Server/Web/HtmlTagButtonCommand.cpp Server/Web/HtmlTagButtonCommand.h Server/Web/HtmlTagButtonCommandFullScreen.cpp Server/Web/HtmlTagButtonCommandFullScreen.h Server/Web/HtmlTagButtonCommandPressRelease.cpp Server/Web/HtmlTagButtonCommandPressRelease.h Server/Web/HtmlTagButtonCommandToggle.cpp Server/Web/HtmlTagButtonCommandToggle.h Server/Web/HtmlTagButtonCommandWide.h Server/Web/HtmlTagButtonMinus.h Server/Web/HtmlTagButtonOK.h Server/Web/HtmlTagButtonPlus.h Server/Web/HtmlTagButtonPopup.cpp Server/Web/HtmlTagButtonPopup.h Server/Web/HtmlTagButtonPopupWide.h Server/Web/HtmlTagFeedback.cpp Server/Web/HtmlTagFeedback.h Server/Web/HtmlTagInput.cpp Server/Web/HtmlTagInput.h Server/Web/HtmlTagInputCheckbox.h Server/Web/HtmlTagInputCheckboxWithLabel.h Server/Web/HtmlTagInputHidden.h Server/Web/HtmlTagInputInteger.cpp Server/Web/HtmlTagInputInteger.h Server/Web/HtmlTagInputIntegerWithLabel.h Server/Web/HtmlTagInputSlider.cpp Server/Web/HtmlTagInputSlider.h Server/Web/HtmlTagInputSliderLocoSpeed.cpp Server/Web/HtmlTagInputSliderLocoSpeed.h Server/Web/HtmlTagInputText.h Server/Web/HtmlTagInputTextWithLabel.h Server/Web/HtmlTagLabel.h Server/Web/HtmlTagLayoutItem.cpp Server/Web/HtmlTagLayoutItem.h Server/Web/HtmlTagRoute.cpp Server/Web/HtmlTagRoute.h Server/Web/HtmlTagSelect.cpp Server/Web/HtmlTagSelect.h Server/Web/HtmlTagSelectMultiple.cpp Server/Web/HtmlTagSelectMultiple.h Server/Web/HtmlTagSelectMultipleWithLabel.cpp Server/Web/HtmlTagSelectMultipleWithLabel.h Server/Web/HtmlTagSelectOrientation.h Server/Web/HtmlTagSelectOrientationWithLabel.h Server/Web/HtmlTagSelectWithLabel.cpp Server/Web/HtmlTagSelectWithLabel.h Server/Web/HtmlTagSignal.cpp Server/Web/HtmlTagSignal.h Server/Web/HtmlTagSwitch.cpp Server/Web/HtmlTagSwitch.h Server/Web/HtmlTagText.cpp Server/Web/HtmlTagText.h Server/Web/HtmlTagTrack.cpp Server/Web/HtmlTagTrack.h Server/Web/Response.cpp Server/Web/Response.h Server/Web/ResponseCsv.cpp Server/Web/ResponseCsv.h Server/Web/ResponseHtml.cpp Server/Web/ResponseHtml.h Server/Web/ResponseHtmlFull.cpp Server/Web/ResponseHtmlFull.h Server/Web/ResponseHtmlNotFound.cpp Server/Web/ResponseHtmlNotFound.h Server/Web/ResponseHtmlNotImplemented.cpp Server/Web/ResponseHtmlNotImplemented.h Server/Web/WebClientCluster.cpp Server/Web/WebClientCluster.h Server/Web/WebClientRoute.cpp Server/Web/WebClientRoute.h Server/Web/WebClientSignal.cpp Server/Web/WebClientSignal.h Server/Web/WebClientStatic.cpp Server/Web/WebClientStatic.h Server/Web/WebClientText.cpp Server/Web/WebClientText.h Server/Web/WebClientTrack.cpp Server/Web/WebClientTrack.h Server/Web/WebServer.cpp Server/Web/WebServer.h Server/Z21/Z21Client.cpp Server/Z21/Z21Client.h Server/Z21/Z21Server.cpp Server/Z21/Z21Server.h Storage/Sqlite.cpp Storage/Sqlite.h Storage/StorageHandler.cpp Storage/StorageHandler.h Storage/StorageInterface.h Storage/StorageParams.h Storage/TransactionGuard.cpp Storage/TransactionGuard.h Utils/Integer.cpp Utils/Integer.h Utils/Network.cpp Utils/Network.h Utils/ThreadSafeQueue.h Utils/Utils.cpp Utils/Utils.h Version.cpp Version.cpp.dummy Version.cpp.in Version.h Hardware/zlib/adler32.c Hardware/zlib/compress.c Hardware/zlib/crc32.c Hardware/zlib/crc32.h Hardware/zlib/deflate.c Hardware/zlib/deflate.h Hardware/zlib/gzclose.c Hardware/zlib/gzguts.h Hardware/zlib/gzlib.c Hardware/zlib/gzread.c Hardware/zlib/gzwrite.c Hardware/zlib/infback.c Hardware/zlib/inffast.c Hardware/zlib/inffast.h Hardware/zlib/inffixed.h Hardware/zlib/inflate.c Hardware/zlib/inflate.h Hardware/zlib/inftrees.c Hardware/zlib/inftrees.h Hardware/zlib/trees.c Hardware/zlib/trees.h Hardware/zlib/uncompr.c Hardware/zlib/zconf.h Hardware/zlib/zlib.h Hardware/zlib/zutil.c Hardware/zlib/zutil.h ) set(CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS "") set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED True) set(CMAKE_CXX_FLAGS_DEBUG "-O0") set(CMAKE_CXX_FLAGS_RELEASE "-O2") set(CMAKE_CXX_FLAGS "-g -Wall -Wextra -pedantic -Werror") set(CMAKE_C_FLAGS "-g -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_RTREE -DHAVE_USLEEP -Wno-implicit-function-declaration -Wno-return-local-addr") set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) target_link_libraries(railcontrol PRIVATE Threads::Threads dl) target_include_directories(railcontrol PRIVATE .) add_custom_command(OUTPUT Version.cpp Version.cpp.dummy DEPENDS Version.cpp.in COMMAND git status -s| wc -l > ${CMAKE_CURRENT_SOURCE_DIR}/git_dirty.txt COMMAND sed s/@COMPILE_TIMESTAMP@/`date +%s`/ < ${CMAKE_CURRENT_SOURCE_DIR}/Version.cpp.in | sed s/@GIT_HASH@/`git log -1 --format=%H`/ | sed s/@GIT_TIMESTAMP@/`git log -1 --format=%at`/ | sed s,@GIT_DIRTY@,`cat ${CMAKE_CURRENT_SOURCE_DIR}/git_dirty.txt`, | sed s/@RAILCONTROL_VERSION@/${RAILCONTROL_VERSION}/ > ${CMAKE_CURRENT_SOURCE_DIR}/Version.cpp COMMAND rm ${CMAKE_CURRENT_SOURCE_DIR}/git_dirty.txt COMMENT "Creating Version.cpp") railcontrol-24+dfsg1/CONTRIBUTORS000066400000000000000000000003741500456250600165200ustar00rootroot00000000000000RailControl's main developer: Teddy (Dominik Mahrer) Other developers, testers and supporters: Detlef Behlert Gerhard Bertelsmann Ingo Haschler Karl Foppe Marc Schärlinger Mebus Moritz Kürten Ralf Bertenburg Simon Jacomet Stefan Koller Viktor Krön railcontrol-24+dfsg1/Config.cpp000066400000000000000000000045451500456250600165550ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include // std::ws #include #include "Config.h" #include "Logger/Logger.h" #include "Utils/Integer.h" using std::map; using std::string; Config::Config(const std::string& fileName) { Logger::Logger* logger = Logger::LoggerServer::Instance().GetLogger("Config"); // read config values logger->Info(Languages::TextReadingConfigFile, fileName); std::ifstream configFile; configFile.open(fileName); if (!configFile.is_open()) { logger->Warning(Languages::TextUnableToOpenFile, fileName); return; } for (string line; std::getline(configFile, line); ) { std::istringstream iss(line); string configKey; string eq; string configValue; iss >> configKey >> eq >> configValue >> std::ws; if ((configKey.size() == 0) || (configKey[0] == '#')) { continue; } if (eq.compare("=") != 0) { continue; } config[configKey] = configValue; logger->Info(Languages::TextParameterFoundInConfigFile, configKey, configValue); } configFile.close(); } const string& Config::getStringValue(const string& key, const string& defaultValue) { if (config.count(key) != 1) { return defaultValue; } return config[key]; } int Config::getIntValue(const string& key, const int defaultValue) { if (config.count(key) != 1) { return defaultValue; } return Utils::Integer::StringToInteger(config[key], defaultValue); } bool Config::getBoolValue(const string& key, const bool defaultValue) { const string value = Utils::Utils::StringToLower(getStringValue(key, string(defaultValue ? "1" : "0"))); return ((value.compare("true") == 0) || (value.compare("on") == 0) || (value.compare("1") == 0)); } railcontrol-24+dfsg1/Config.h000066400000000000000000000021651500456250600162160ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include class Config { public: Config(const std::string& fileName); const std::string& getStringValue(const std::string& key, const std::string& defaultValue); int getIntValue(const std::string& key, const int defaultValue); bool getBoolValue(const std::string& key, const bool defaultValue); private: std::map config; }; railcontrol-24+dfsg1/ControlInterface.h000066400000000000000000000264731500456250600202620ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include #include "DataModel/AccessoryConfig.h" #include "DataModel/Feedback.h" #include "DataModel/FeedbackConfig.h" #include "DataModel/LocoConfig.h" #include "DataModel/LocoFunctions.h" #include "DataTypes.h" #include "Hardware/Capabilities.h" namespace DataModel { class Accessory; class Loco; class Signal; class Route; class Switch; class Track; } namespace Hardware { class HardwareParams; } class ControlInterface { public: inline ControlInterface(ControlType controlType) : controlType(controlType) { } virtual ~ControlInterface() { } virtual void Start() { } virtual void ReInit(__attribute__((unused)) const Hardware::HardwareParams* params) { } virtual void Stop() { } ControlType GetControlType() const { return controlType; } virtual const std::string& GetName() const = 0; virtual const std::string& GetShortName() const { return GetName(); } virtual void AccessoryDelete(__attribute__((unused)) const AccessoryID accessoryID, __attribute__((unused)) const std::string& name, __attribute__((unused)) const std::string& matchKey) { } virtual void AccessoryProtocols(__attribute__((unused)) std::vector& protocols) const { } virtual bool AccessoryProtocolSupported(__attribute__((unused)) Protocol protocol) const { return false; } virtual void AccessorySettings(__attribute__((unused)) const AccessoryID accessoryID, __attribute__((unused)) const std::string& name, __attribute__((unused)) const std::string& matchKey) { } virtual void AccessoryState(__attribute__((unused)) const ControlType controlType, __attribute__((unused)) const DataModel::Accessory* accessory) { } virtual void ArgumentTypes(__attribute__((unused)) std::map& argumentTypes) const { } virtual void Booster(__attribute__((unused)) const ControlType controlType, __attribute__((unused)) const BoosterState state) { } virtual Hardware::Capabilities GetCapabilities() const { return Hardware::CapabilityNone; } inline bool CanHandle(const Hardware::Capabilities capability) const { Hardware::Capabilities hardwareCapabilities = GetCapabilities(); return capability == (hardwareCapabilities & capability); } virtual void FeedbackDelete(__attribute__((unused)) const FeedbackID feedbackID, __attribute__((unused)) const std::string& name) { } virtual void FeedbackSettings(__attribute__((unused)) const FeedbackID feedbackID, __attribute__((unused)) const std::string& name) { } virtual void FeedbackState(__attribute__((unused)) const std::string& name, __attribute__((unused)) const FeedbackID feedbackID, __attribute__((unused)) const DataModel::Feedback::FeedbackState state) { } virtual void LayerDelete(__attribute__((unused)) const LayerID layerID, __attribute__((unused)) const std::string& name) { } virtual void LayerSettings(__attribute__((unused)) const LayerID layerID, __attribute__((unused)) const std::string& name) { } virtual void LocoBaseDestinationReached(__attribute__((unused)) const DataModel::LocoBase* loco, __attribute__((unused)) const DataModel::Route* route, __attribute__((unused)) const DataModel::Track* track) { } virtual void LocoBaseOrientation(__attribute__((unused)) const ControlType controlType, __attribute__((unused)) const DataModel::LocoBase* loco, __attribute__((unused)) const Orientation orientation) { } virtual void LocoBaseFunction(__attribute__((unused)) const ControlType controlType, __attribute__((unused)) const DataModel::LocoBase* loco, __attribute__((unused)) const DataModel::LocoFunctionNr function, __attribute__((unused)) const DataModel::LocoFunctionState on) { } virtual void LocoBaseRelease(__attribute__((unused)) const DataModel::LocoBase* loco) { } virtual void LocoBaseSpeed(__attribute__((unused)) const ControlType controlType, __attribute__((unused)) const DataModel::LocoBase* loco, __attribute__((unused)) const Speed speed) { } virtual void LocoBaseStart(__attribute__((unused)) const DataModel::LocoBase* loco) { } virtual void LocoBaseStop(__attribute__((unused)) const DataModel::LocoBase* loco) { } virtual void LocoProtocols(__attribute__((unused)) std::vector& protocols) const { } virtual bool LocoProtocolSupported(__attribute__((unused)) Protocol protocol) const { return false; } virtual void LocoDelete(__attribute__((unused)) const LocoID locoID, __attribute__((unused)) const std::string& name, __attribute__((unused)) const std::string& matchKey) { } virtual void LocoSettings(__attribute__((unused)) const LocoID locoID, __attribute__((unused)) const std::string& name, __attribute__((unused)) const std::string& matchKey) { } virtual void MultipleUnitDelete(__attribute__((unused)) const MultipleUnitID multipleUnitID, __attribute__((unused)) const std::string& name, __attribute__((unused)) const std::string& matchKey) { } virtual void MultipleUnitSettings(__attribute__((unused)) const MultipleUnitID multipleUnitID, __attribute__((unused)) const std::string& name, __attribute__((unused)) const std::string& matchKey) { } virtual void RouteDelete(__attribute__((unused)) const RouteID routeID, __attribute__((unused)) const std::string& name) { } virtual void RouteRelease(__attribute__((unused)) const RouteID routeID) { } virtual void RouteSettings(__attribute__((unused)) const RouteID routeID, __attribute__((unused)) const std::string& name) { } virtual void SwitchDelete(__attribute__((unused)) const SwitchID switchID, __attribute__((unused)) const std::string& name, __attribute__((unused)) const std::string& matchKey) { } virtual void SwitchSettings(__attribute__((unused)) const SwitchID switchID, __attribute__((unused)) const std::string& name, __attribute__((unused)) const std::string& matchKey) { } virtual void SwitchState(__attribute__((unused)) const ControlType controlType, __attribute__((unused)) const DataModel::Switch* mySwitch) { } virtual void TrackDelete(__attribute__((unused)) const TrackID trackID, __attribute__((unused)) const std::string& name) { } virtual void TrackSettings(__attribute__((unused)) const TrackID trackID, __attribute__((unused)) const std::string& name) { } virtual void TrackState(__attribute__((unused)) const DataModel::Track* track) { } virtual void SignalDelete(__attribute__((unused)) const SignalID signalID, __attribute__((unused)) const std::string& name, __attribute__((unused)) const std::string& matchKey) { } virtual void SignalSettings(__attribute__((unused)) const SignalID signalID, __attribute__((unused)) const std::string& name, __attribute__((unused)) const std::string& matchKey) { } virtual void SignalState(__attribute__((unused)) const ControlType controlType, __attribute__((unused)) const DataModel::Signal* signal) { } virtual void ClusterDelete(__attribute__((unused)) const ClusterID clusterID, __attribute__((unused)) const std::string& name) { } virtual void ClusterSettings(__attribute__((unused)) const ClusterID clusterID, __attribute__((unused)) const std::string& name) { } virtual void TextDelete(__attribute__((unused)) const TextID textID, __attribute__((unused)) const std::string& name) { } virtual void TextSettings(__attribute__((unused)) const TextID textID, __attribute__((unused)) const std::string& name) { } virtual void LocoBaseSpeedOrientationFunctions(const DataModel::LocoBase* loco, const Speed speed, const Orientation orientation, std::vector& functions) { LocoBaseSpeed(ControlTypeInternal, loco, speed); LocoBaseOrientation(ControlTypeInternal, loco, orientation); for (const DataModel::LocoFunctionEntry& functionEntry : functions) { LocoBaseFunction(ControlTypeInternal, loco, functionEntry.nr, functionEntry.state); } } virtual void ProgramRead(__attribute__((unused)) const ProgramMode mode, __attribute__((unused)) const Address address, __attribute__((unused)) const CvNumber cv) { } virtual void ProgramWrite(__attribute__((unused)) const ProgramMode mode, __attribute__((unused)) const Address address, __attribute__((unused)) const CvNumber cv, __attribute__((unused)) const CvValue value) { } virtual void ProgramValue(__attribute__((unused)) const CvNumber cv, __attribute__((unused)) const CvValue value) { } virtual void AddUnmatchedLocos(__attribute__((unused)) std::map& list) const { } virtual std::map GetUnmatchedLocos(__attribute__((unused)) const std::string& matchKey) const { std::map out; return out; } virtual DataModel::LocoConfig GetLocoByMatchKey(__attribute__((unused)) const std::string& matchKey) const { return DataModel::LocoConfig(LocoTypeLoco); } virtual void AddUnmatchedMultipleUnits(__attribute__((unused)) std::map& list) const { } virtual std::map GetUnmatchedMultipleUnits(__attribute__((unused)) const std::string& matchKey) const { std::map out; return out; } virtual DataModel::LocoConfig GetMultipleUnitByMatchKey(__attribute__((unused)) const std::string& matchKey) const { return DataModel::LocoConfig(LocoTypeMultipleUnit); } virtual void AddUnmatchedAccessories(__attribute__((unused)) std::map& list) const { } virtual std::map GetUnmatchedAccessories(__attribute__((unused)) const std::string& matchKey) const { std::map out; return out; } virtual DataModel::AccessoryConfig GetAccessoryByMatchKey(__attribute__((unused)) const std::string& matchKey) const { return DataModel::AccessoryConfig(); } virtual void AddUnmatchedFeedbacks(__attribute__((unused)) std::map& list) const { } virtual std::map GetUnmatchedFeedbacks(__attribute__((unused)) const std::string& matchKey) const { std::map out; return out; } virtual DataModel::FeedbackConfig GetFeedbackByMatchKey(__attribute__((unused)) const std::string& matchKey) const { return DataModel::FeedbackConfig(); } private: ControlType controlType; }; railcontrol-24+dfsg1/DataModel/000077500000000000000000000000001500456250600164665ustar00rootroot00000000000000railcontrol-24+dfsg1/DataModel/Accessory.cpp000066400000000000000000000041451500456250600211310ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include #include "DataModel/Accessory.h" #include "Utils/Utils.h" using std::map; using std::string; namespace DataModel { std::string Accessory::Serialize() const { string str = "objectType=Accessory;"; str += AccessoryBase::Serialize(); str += ";" + LayoutItem::Serialize(); str += ";" + LockableItem::Serialize(); str += ";port=" + std::to_string(port); return str; } bool Accessory::Deserialize(const std::string& serialized) { map arguments; ParseArguments(serialized, arguments); string objectType = Utils::Utils::GetStringMapEntry(arguments, "objectType"); if (objectType.compare("Accessory") != 0) { return false; } AccessoryBase::Deserialize(arguments); LayoutItem::Deserialize(arguments); LockableItem::Deserialize(arguments); port = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "port")); SetWidth(Width1); SetHeight(Height1); SetVisible(VisibleYes); return true; } Accessory& Accessory::operator=(const Hardware::AccessoryCacheEntry& accessory) { SetControlID(accessory.GetControlID()); SetName(accessory.GetName()); SetProtocol(accessory.GetProtocol()); SetAddress(accessory.GetAddress()); SetPort(AddressPortRed); SetMatchKey(accessory.GetMatchKey()); SetWidth(Width1); SetHeight(Height1); SetVisible(VisibleYes); return *this; } } // namespace DataModel railcontrol-24+dfsg1/DataModel/Accessory.h000066400000000000000000000044131500456250600205740ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "DataTypes.h" #include "DataModel/AccessoryBase.h" #include "DataModel/HardwareHandle.h" #include "DataModel/LayoutItem.h" #include "DataModel/LockableItem.h" #include "Languages.h" class Manager; namespace DataModel { class Accessory : public AccessoryBase, public LayoutItem, public LockableItem { Accessory() = delete; Accessory(const Accessory&) = delete; Accessory& operator=(const Accessory&) = delete; public: inline Accessory(const AccessoryID accessoryID) : AccessoryBase(), LayoutItem(accessoryID), LockableItem(), port(AddressPortRed) { } inline Accessory(__attribute__((unused)) Manager* manager, const AccessoryID accessoryID) : Accessory(accessoryID) { } inline Accessory(const std::string& serialized) : Accessory(AccessoryNone) { Deserialize(serialized); } virtual ~Accessory() { } virtual ObjectType GetObjectType() const override { return ObjectTypeAccessory; } virtual std::string GetLayoutType() const override { return Languages::GetText(Languages::TextAccessory); } virtual std::string Serialize() const override; using HardwareHandle::Deserialize; virtual bool Deserialize(const std::string& serialized) override; Accessory& operator=(const Hardware::AccessoryCacheEntry& accessory); inline void SetPort(AddressPort port) { this->port = port; } inline AddressPort GetPort() const { return port; } private: AddressPort port; }; } // namespace DataModel railcontrol-24+dfsg1/DataModel/AccessoryBase.cpp000066400000000000000000000046351500456250600217300ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include #include "DataModel/AccessoryBase.h" #include "Utils/Utils.h" using std::map; using std::string; namespace DataModel { std::string AccessoryBase::Serialize() const { string str = HardwareHandle::Serialize(); str += ";type=" + std::to_string(accessoryType); str += ";state=" + std::to_string(accessoryState); str += ";duration=" + std::to_string(duration); str += ";inverted=" + std::to_string(inverted); str += ";lastused=" + std::to_string(lastUsed); str += ";counter=" + std::to_string(counter); str += ";matchkey=" + matchKey; return str; } bool AccessoryBase::Deserialize(const map& arguments) { HardwareHandle::Deserialize(arguments); accessoryType = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "type")); accessoryState = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "state", AccessoryStateOff)); duration = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "duration", DefaultAccessoryPulseDuration)); inverted = Utils::Utils::GetBoolMapEntry(arguments, "inverted"); lastUsed = Utils::Utils::GetIntegerMapEntry(arguments, "lastused", 0); counter = Utils::Utils::GetIntegerMapEntry(arguments, "counter", 0); matchKey = Utils::Utils::GetStringMapEntry(arguments, "matchkey"); return true; } AccessoryState AccessoryBase::CalculateInvertedAccessoryState(const AccessoryState state) const { if (inverted == false) { return state; } switch(state) { case AccessoryStateOff: return AccessoryStateOn; case AccessoryStateOn: return AccessoryStateOff; default: return state; } } } // namespace DataModel railcontrol-24+dfsg1/DataModel/AccessoryBase.h000066400000000000000000000124051500456250600213670ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include #include #include "DataModel/HardwareHandle.h" #include "Hardware/AccessoryCache.h" namespace DataModel { enum AccessoryType : uint8_t { // 2 MSB are functional type (on-on, on push, on-off) AccessoryTypeOnOn = 0x00, AccessoryTypeOnPush = 0x40, AccessoryTypeOnOff = 0x80, AccessoryTypeConnectionMask = 0xC0, // 6 LSB are display type (default, straight, turn, ...) AccessoryTypeDefault = 0x00, AccessoryTypeStraight = 0x01, AccessoryTypeTurn = 0x02, AccessoryTypeDecoupler = 0x03, AccessoryTypeLight = 0x04, AccessoryTypeLightInhouse = 0x05, AccessoryTypeLightStreet = 0x06, AccessoryTypeMask = 0x3F, SignalTypeSimpleLeft = 0, SignalTypeSimpleRight = 1, SignalTypeChDwarf = 10, SignalTypeChLMain = 11, SignalTypeChLDistant = 12, SignalTypeChLCombined = 13, SignalTypeChNMain = 14, SignalTypeChNDistant = 15, SignalTypeDeCombined = 20, SignalTypeDeHVMain = 21, SignalTypeDeHVDistant = 22, SignalTypeDeBlock = 23, SwitchTypeLeft = 0, SwitchTypeRight = 1, SwitchTypeThreeWay = 2, SwitchTypeMaerklinLeft = 3, SwitchTypeMaerklinRight = 4 }; enum AccessoryState : uint8_t { DefaultState = 0, AccessoryStateOff = 0, AccessoryStateOn = 1, SignalStateStop = 0, SignalStateClear, SignalStateAspect2, SignalStateAspect3, SignalStateAspect4, SignalStateAspect5, SignalStateAspect6, SignalStateAspect7, SignalStateAspect8, SignalStateAspect9, SignalStateAspect10, SignalStateDark = 0x1F, SignalStateStopExpected = SignalStateStop + 0x20, SignalStateClearExpected, SignalStateAspect2Expected, SignalStateAspect3Expected, SignalStateAspect4Expected, SignalStateAspect5Expected, SignalStateAspect6Expected, SignalStateAspect7Expected, SignalStateAspect8Expected, SignalStateAspect9Expected, SignalStateAspect10Expected, SignalStateMax = SignalStateAspect10Expected, SwitchStateTurnout = 0, SwitchStateStraight = 1, SwitchStateThird = 2, InvalidState = 0xFF }; typedef signed char AddressOffset; typedef unsigned short AccessoryPulseDuration; static const AccessoryPulseDuration DefaultAccessoryPulseDuration = 100; class AccessoryBase : public HardwareHandle { public: AccessoryBase() : HardwareHandle(), accessoryType(AccessoryTypeDefault), accessoryState(AccessoryStateOff), duration(0), inverted(false), lastUsed(0), counter(0), matchKey("") { } virtual ~AccessoryBase() {} inline AccessoryType GetAccessoryType() const { return accessoryType; } virtual inline void SetAccessoryType(AccessoryType accessoryType) { this->accessoryType = accessoryType; } inline AccessoryState GetAccessoryState() const { return accessoryState; } AccessoryState CalculateInvertedAccessoryState(const AccessoryState state) const; inline AccessoryState GetInvertedAccessoryState() const { return CalculateInvertedAccessoryState(accessoryState); } inline void SetAccessoryState(const AccessoryState state) { this->accessoryState = state; lastUsed = std::time(nullptr); ++counter; } inline AccessoryPulseDuration GetAccessoryPulseDuration() const { return duration; } inline void SetAccessoryPulseDuration(const AccessoryPulseDuration duration) { this->duration = duration; } inline bool GetInverted() const { return inverted; } inline void SetInverted(const bool inverted) { this->inverted = inverted; } inline time_t GetLastUsed() const { return lastUsed; } inline void SetMatchKey(const std::string& matchKey) { this->matchKey = matchKey; } inline void ClearMatchKey() { matchKey.clear(); } inline std::string GetMatchKey() const { return matchKey; } protected: virtual std::string Serialize() const; virtual bool Deserialize(const std::map& arguments); private: AccessoryType accessoryType; AccessoryState accessoryState; AccessoryPulseDuration duration; // duration in ms after which the accessory command will be turned off on rails. 0 = no turn off / turn off must be made manually bool inverted; time_t lastUsed; unsigned int counter; std::string matchKey; }; } // namespace DataModel railcontrol-24+dfsg1/DataModel/AccessoryConfig.h000066400000000000000000000112711500456250600217220ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "DataModel/Accessory.h" #include "DataModel/Signal.h" #include "DataModel/Switch.h" #include "Hardware/AccessoryCache.h" namespace DataModel { class AccessoryConfig { public: inline AccessoryConfig() : controlId(ControlNone), address(AddressDefault), protocol(ProtocolNone), isInUse(false) { } inline AccessoryConfig(const DataModel::Accessory& accessory) : controlId(accessory.GetControlID()), objectIdentifier(ObjectTypeAccessory, accessory.GetID()), address(accessory.GetAddress()), protocol(accessory.GetProtocol()), name(accessory.GetName()), matchKey(accessory.GetMatchKey()), isInUse(accessory.IsInUse()) { } inline AccessoryConfig(const DataModel::Signal& signal) : controlId(signal.GetControlID()), objectIdentifier(ObjectTypeSignal, signal.GetID()), address(signal.GetAddress()), protocol(signal.GetProtocol()), name(signal.GetName()), matchKey(signal.GetMatchKey()), isInUse(signal.IsInUse()) { } inline AccessoryConfig(const DataModel::Switch& mySwitch) : controlId(mySwitch.GetControlID()), objectIdentifier(ObjectTypeSwitch, mySwitch.GetID()), address(mySwitch.GetAddress()), protocol(mySwitch.GetProtocol()), name(mySwitch.GetName()), matchKey(mySwitch.GetMatchKey()), isInUse(mySwitch.IsInUse()) { } inline AccessoryConfig(const Hardware::AccessoryCacheEntry& entry) : controlId(entry.GetControlID()), objectIdentifier(entry.GetObjectIdentifier()), address(entry.GetAddress()), protocol(entry.GetProtocol()), name(entry.GetName()), matchKey(entry.GetMatchKey()), isInUse(false) { } inline AccessoryConfig& operator=(const DataModel::Accessory& accessory) { controlId = accessory.GetControlID(); objectIdentifier = ObjectIdentifier(ObjectTypeAccessory, accessory.GetID()); address = accessory.GetAddress(); protocol = accessory.GetProtocol(); name = accessory.GetName(); matchKey = accessory.GetMatchKey(); isInUse = accessory.IsInUse(); return *this; } inline AccessoryConfig& operator=(const DataModel::Signal& signal) { controlId = signal.GetControlID(); objectIdentifier = ObjectIdentifier(ObjectTypeSignal, signal.GetID()); address = signal.GetAddress(); protocol = signal.GetProtocol(); name = signal.GetName(); matchKey = signal.GetMatchKey(); isInUse = signal.IsInUse(); return *this; } inline AccessoryConfig& operator=(const DataModel::Switch& accessory) { controlId = accessory.GetControlID(); objectIdentifier = ObjectIdentifier(ObjectTypeSwitch, accessory.GetID()); address = accessory.GetAddress(); protocol = accessory.GetProtocol(); name = accessory.GetName(); matchKey = accessory.GetMatchKey(); isInUse = accessory.IsInUse(); return *this; } inline AccessoryConfig& operator=(const Hardware::AccessoryCacheEntry& entry) { controlId = entry.GetControlID(); objectIdentifier = entry.GetObjectIdentifier(); address = entry.GetAddress(); protocol = entry.GetProtocol(); name = entry.GetName(); matchKey = entry.GetMatchKey(); return *this; } inline ControlID GetControlId() const { return controlId; } inline ObjectIdentifier GetObjectIdentifier() const { return objectIdentifier; } inline Address GetAddress() const { return address; } inline Protocol GetProtocol() const { return protocol; } inline std::string GetName() const { return name; } inline void SetName(const std::string& name) { this->name = name; } inline std::string GetMatchKey() const { return matchKey; } inline bool IsInUse() const { return isInUse; } private: ControlID controlId; ObjectIdentifier objectIdentifier; Address address; Protocol protocol; std::string name; std::string matchKey; bool isInUse; }; } // namespace DataModel railcontrol-24+dfsg1/DataModel/Cluster.cpp000066400000000000000000000103251500456250600206140ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include #include "DataModel/Cluster.h" #include "DataModel/LockableItem.h" #include "DataModel/Relation.h" #include "DataModel/Signal.h" #include "DataModel/Track.h" #include "Manager.h" #include "Utils/Utils.h" using std::map; using std::string; using std::to_string; namespace DataModel { std::string Cluster::Serialize() const { string str; str += "objectType=Cluster;"; str += Object::Serialize(); str += ";orientation="; str += to_string(orientation); return str; } bool Cluster::Deserialize(const string& serialized) { map arguments; ParseArguments(serialized, arguments); Object::Deserialize(arguments); if (!arguments.count("objectType") || arguments.at("objectType").compare("Cluster") != 0) { return false; } orientation = static_cast(Utils::Utils::GetBoolMapEntry(arguments, "orientation", OrientationRight)); return true; } bool Cluster::CanSetLocoBaseOrientationUnlocked(const Orientation orientation, const ObjectIdentifier& locoBaseIdentifier) { if (this->orientation == orientation) { return true; } for (auto relation : tracks) { Track* track = dynamic_cast(relation->GetObject2()); if (!track) { return false; } // invert track orientation if needed (XOR) and check with desired orientation value const Relation::Data invert = relation->GetData(); if ((track->GetLocoBaseOrientation() ^ invert) == orientation) { continue; } const DataModel::LockableItem::LockState lockState = track->GetLockState(); if (lockState == DataModel::LockableItem::LockStateFree) { continue; } const ObjectIdentifier& locoBaseOfTrack = track->GetLocoBase(); if ((lockState == DataModel::LockableItem::LockStateReserved) && locoBaseOfTrack.IsSet() && (locoBaseOfTrack == locoBaseIdentifier)) { continue; } return false; } return true; } bool Cluster::SetLocoBaseOrientation(const Orientation orientation, const ObjectIdentifier& locoBaseIdentifier) { std::lock_guard Guard(orientationMutex); if (!CanSetLocoBaseOrientationUnlocked(orientation, locoBaseIdentifier)) { return false; } this->orientation = orientation; for (auto relation : tracks) { Track* track = dynamic_cast(relation->GetObject2()); if (!track) { continue; } // invert orientation if needed (XOR) and set track orientation const Relation::Data invert = relation->GetData(); track->SetLocoBaseOrientationForce(static_cast(orientation ^ invert)); } return true; } void Cluster::DeleteTracks() { while (tracks.size() > 0) { Relation* trackRelation = tracks.back(); Track* track = dynamic_cast(trackRelation->GetObject2()); if (track) { track->DeleteCluster(); } tracks.pop_back(); delete trackRelation; } } void Cluster::DeleteTrack(Track* trackToDelete) { for (unsigned int index = 0; index < tracks.size(); ++index) { if (tracks[index]->GetObject2() != trackToDelete) { continue; } delete tracks[index]; tracks.erase(tracks.begin() + index); trackToDelete->DeleteCluster(); return; } } void Cluster::AssignTracks(const std::vector& newTracks) { DeleteTracks(); tracks = newTracks; for (auto trackRelation : tracks) { Track* track = dynamic_cast(trackRelation->GetObject2()); if (track) { track->SetCluster(this, static_cast(trackRelation->GetData())); } } } } // namespace DataModel railcontrol-24+dfsg1/DataModel/Cluster.h000066400000000000000000000047331500456250600202670ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include #include "DataTypes.h" #include "DataModel/Object.h" class Manager; namespace DataModel { class Relation; class Signal; class Track; class Cluster : public Object { public: Cluster(__attribute__((unused)) Manager* manager, const ClusterID clusterID) : Object(clusterID), orientation(OrientationRight) { } Cluster(const std::string& serialized) : Object(ClusterNone), orientation(OrientationRight) { Deserialize(serialized); } virtual ~Cluster() { DeleteTracks(); } inline ObjectType GetObjectType() const override { return ObjectTypeCluster; } std::string Serialize() const override; bool Deserialize(const std::string& serialized) override; inline bool CanSetLocoBaseOrientation(const Orientation orientation, const ObjectIdentifier& locoBaseIdentifier) { std::lock_guard Guard(orientationMutex); return CanSetLocoBaseOrientationUnlocked(orientation, locoBaseIdentifier); } bool SetLocoBaseOrientation(const Orientation orientation, const ObjectIdentifier& locoBaseIdentifier); inline Orientation GetLocoOrientation() const { return orientation; } inline const std::vector& GetTracks() const { return tracks; } void DeleteTracks(); void DeleteTrack(DataModel::Track* trackToDelete); void AssignTracks(const std::vector& newTracks); private: bool CanSetLocoBaseOrientationUnlocked(const Orientation orientation, const ObjectIdentifier& locoBaseIdentifier); Orientation orientation; mutable std::mutex orientationMutex; std::vector tracks; }; } // namespace DataModel railcontrol-24+dfsg1/DataModel/DataModel.h000066400000000000000000000023621500456250600204740ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include "DataModel/Accessory.h" #include "DataModel/AccessoryBase.h" #include "DataModel/Cluster.h" #include "DataModel/Feedback.h" #include "DataModel/Layer.h" #include "DataModel/Loco.h" #include "DataModel/LocoBase.h" #include "DataModel/LocoFunctions.h" #include "DataModel/MultipleUnit.h" #include "DataModel/ObjectIdentifier.h" #include "DataModel/Relation.h" #include "DataModel/Route.h" #include "DataModel/Signal.h" #include "DataModel/Switch.h" #include "DataModel/Text.h" #include "DataModel/Track.h" railcontrol-24+dfsg1/DataModel/Feedback.cpp000066400000000000000000000074471500456250600206720ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include #include "DataModel/Feedback.h" #include "Manager.h" #include "Utils/Utils.h" using std::map; using std::string; using std::to_string; namespace DataModel { string Feedback::Serialize() const { string str; str = "objectType=Feedback;" + LayoutItem::Serialize(); str += ";controlID=" + to_string(controlID); str += ";pin=" + to_string(pin); str += ";feedbacktype=" + to_string(feedbackType); str += ";route=" + to_string(routeId); str += ";inverted=" + to_string(inverted); str += ";state=" + to_string(stateCounter > 0); str += ";matchkey=" + matchKey; return str; } bool Feedback::Deserialize(const string& serialized) { map arguments; ParseArguments(serialized, arguments); string objectType = Utils::Utils::GetStringMapEntry(arguments, "objectType"); if (objectType.compare("Feedback") != 0) { return false; } LayoutItem::Deserialize(arguments); SetHeight(Height1); SetWidth(Width1); controlID = Utils::Utils::GetIntegerMapEntry(arguments, "controlID", ControlIdNone); pin = Utils::Utils::GetIntegerMapEntry(arguments, "pin"); feedbackType = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "feedbacktype", FeedbackTypeDefault)); routeId = Utils::Utils::GetIntegerMapEntry(arguments, "route", RouteNone); inverted = Utils::Utils::GetBoolMapEntry(arguments, "inverted", false); stateCounter = Utils::Utils::GetBoolMapEntry(arguments, "state", FeedbackStateFree) ? MaxStateCounter : 0; matchKey = Utils::Utils::GetStringMapEntry(arguments, "matchkey"); return true; } void Feedback::SetState(const FeedbackState newState) { FeedbackState state = static_cast(newState != inverted); { std::lock_guard Guard(updateMutex); if (state == FeedbackStateFree) { if (stateCounter < MaxStateCounter) { return; } stateCounter = MaxStateCounter - 1; return; } const unsigned char oldStateCounter = stateCounter; stateCounter = MaxStateCounter; if (oldStateCounter > 0) { return; } } manager->FeedbackPublishState(this); UpdateTrackState(FeedbackStateOccupied); Route* route = manager->GetRoute(routeId); if (route) { static Logger::Logger* logger = Logger::Logger::GetLogger(Languages::GetText(Languages::TextManager)); route->Execute(logger, ObjectIdentifier()); } } void Feedback::UpdateTrackState(const FeedbackState state) { if (!track) { return; } track->SetFeedbackState(GetID(), state); } void Feedback::Debounce() { { std::lock_guard Guard(updateMutex); if (stateCounter == MaxStateCounter || stateCounter == 0) { return; } --stateCounter; if (stateCounter != 0) { return; } } manager->FeedbackPublishState(this); UpdateTrackState(FeedbackStateFree); } Feedback& Feedback::operator=(const Hardware::FeedbackCacheEntry& feedback) { SetControlID(feedback.GetControlID()); SetPin(feedback.GetPin()); SetName(feedback.GetName()); SetMatchKey(feedback.GetMatchKey()); return *this; } } // namespace DataModel railcontrol-24+dfsg1/DataModel/Feedback.h000066400000000000000000000076001500456250600203260ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include "DataTypes.h" #include "DataModel/ObjectIdentifier.h" #include "DataModel/LayoutItem.h" #include "Hardware/FeedbackCache.h" #include "Languages.h" class Manager; namespace DataModel { class Track; enum FeedbackType : unsigned char { FeedbackTypeDefault = 0, FeedbackTypeStraight = 1, FeedbackTypeTurn = 2 }; class Feedback : public LayoutItem { public: enum FeedbackState : bool { FeedbackStateFree = false, FeedbackStateOccupied = true }; inline Feedback(Manager* manager, const FeedbackID feedbackID) : LayoutItem(feedbackID), controlID(ControlIdNone), pin(FeedbackPinNone), manager(manager), feedbackType(FeedbackTypeDefault), routeId(RouteNone), inverted(false), track(nullptr), stateCounter(0) { } inline Feedback(Manager* manager, const std::string& serialized) : manager(manager), track(nullptr) { Deserialize(serialized); } inline ObjectType GetObjectType() const override { return ObjectTypeFeedback; } std::string Serialize() const override; bool Deserialize(const std::string& serialized) override; inline bool IsInUse() const { return track != nullptr; } inline std::string GetLayoutType() const override { return Languages::GetText(Languages::TextFeedback); } inline FeedbackType GetFeedbackType() const { return feedbackType; } inline void SetFeedbackType(const FeedbackType type) { this->feedbackType = type; } inline RouteID GetRouteId() const { return routeId; } inline void SetRouteId(const RouteID routeId) { this->routeId = routeId; } inline void SetInverted(const bool inverted) { this->inverted = inverted; } inline bool GetInverted() const { return inverted; } void SetState(const FeedbackState state); inline FeedbackState GetState() const { return static_cast(stateCounter > 0); } void Debounce(); inline void SetControlID(const ControlID controlID) { this->controlID = controlID; } inline ControlID GetControlID() const { return controlID; } inline void SetPin(const FeedbackPin pin) { this->pin = pin; } inline FeedbackPin GetPin() const { return pin; } inline void SetTrack(DataModel::Track* track = nullptr) { this->track = track; } inline Track* GetTrack() { return track; } inline void SetMatchKey(const std::string& matchKey) { this->matchKey = matchKey; } inline void ClearMatchKey() { matchKey.clear(); } inline std::string GetMatchKey() const { return matchKey; } Feedback& operator=(const Hardware::FeedbackCacheEntry& feedback); private: void UpdateTrackState(const FeedbackState state); ControlID controlID; FeedbackPin pin; Manager* manager; FeedbackType feedbackType; RouteID routeId; bool inverted; Track* track; unsigned char stateCounter; static const unsigned char MaxStateCounter = 10; mutable std::mutex updateMutex; std::string matchKey; }; } // namespace DataModel railcontrol-24+dfsg1/DataModel/FeedbackConfig.h000066400000000000000000000054711500456250600214600ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "DataModel/Feedback.h" #include "Hardware/FeedbackCache.h" namespace DataModel { class FeedbackConfig { public: inline FeedbackConfig() : controlId(ControlNone), feedbackId(FeedbackNone), pin(FeedbackPinNone), isInUse(false) { } inline FeedbackConfig(const DataModel::Feedback& feedback) : controlId(feedback.GetControlID()), feedbackId(feedback.GetID()), pin(feedback.GetPin()), name(feedback.GetName()), matchKey(feedback.GetMatchKey()), isInUse(feedback.IsInUse()) { } inline FeedbackConfig(const Hardware::FeedbackCacheEntry& feedback) : controlId(feedback.GetControlID()), feedbackId(feedback.GetFeedbackID()), pin(feedback.GetPin()), name(feedback.GetName()), matchKey(feedback.GetMatchKey()), isInUse(false) { } inline FeedbackConfig& operator=(const DataModel::Feedback& feedback) { controlId = feedback.GetControlID(); feedbackId = feedback.GetID(); pin = feedback.GetPin(); name = feedback.GetName(); matchKey = feedback.GetMatchKey(); isInUse = feedback.IsInUse(); return *this; } inline FeedbackConfig& operator=(const Hardware::FeedbackCacheEntry& feedback) { controlId = feedback.GetControlID(); feedbackId = feedback.GetFeedbackID(); pin = feedback.GetPin(); name = feedback.GetName(); matchKey = feedback.GetMatchKey(); return *this; } inline ControlID GetControlId() const { return controlId; } inline FeedbackID GetFeedbackId() const { return feedbackId; } inline FeedbackPin GetPin() const { return pin; } inline std::string GetName() const { return name; } inline void SetName(const std::string& name) { this->name = name; } inline std::string GetMatchKey() const { return matchKey; } inline bool IsInUse() const { return isInUse; } private: ControlID controlId; FeedbackID feedbackId; FeedbackPin pin; std::string name; std::string matchKey; bool isInUse; }; } // namespace DataModel railcontrol-24+dfsg1/DataModel/HardwareHandle.cpp000066400000000000000000000031501500456250600220420ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include "DataModel/HardwareHandle.h" #include "Utils/Utils.h" using std::map; using std::string; using std::to_string; namespace DataModel { std::string HardwareHandle::Serialize() const { string str = "controlID=" + to_string(controlID); str += ";protocol=" + to_string(protocol); str += ";address=" + to_string(address); str += ";serveraddress=" + to_string(serverAddress); return str; } bool HardwareHandle::Deserialize(const map& arguments) { controlID = Utils::Utils::GetIntegerMapEntry(arguments, "controlID", ControlIdNone); protocol = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "protocol", ProtocolNone)); address = Utils::Utils::GetIntegerMapEntry(arguments, "address"); serverAddress = static_cast
(Utils::Utils::GetIntegerMapEntry(arguments, "serveraddress", AddressNone)); return true; } } // namespace DataModel railcontrol-24+dfsg1/DataModel/HardwareHandle.h000066400000000000000000000045361500456250600215200ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include "DataTypes.h" #include "DataModel/Serializable.h" namespace DataModel { class HardwareHandle { // simply stores controlID, protocol and base address of an object // this is only to prevent copying code and use a base class instead public: inline HardwareHandle() : controlID(ControlNone), protocol(ProtocolNone), address(AddressDefault), serverAddress(AddressNone) { } virtual ~HardwareHandle() { } inline void SetControlID(ControlID controlID) { this->controlID = controlID; } inline ControlID GetControlID() const { return controlID; } inline void SetProtocol(Protocol protocol) { this->protocol = protocol; } inline Protocol GetProtocol() const { return protocol; } inline void SetAddress(Address address) { if (address == AddressNone) { this->address = AddressDefault; } else { this->address = address; } } inline Address GetAddress() const { return address; } inline void SetServerAddress(const Address serverAddress) { if (serverAddress == AddressNone) { this->serverAddress = this->address; } else { this->serverAddress = serverAddress; } } inline Address GetServerAddress() const { return serverAddress; } protected: virtual std::string Serialize() const; virtual bool Deserialize(const std::map& arguments); private: ControlID controlID; Protocol protocol; Address address; Address serverAddress; }; } // namespace DataModel railcontrol-24+dfsg1/DataModel/Layer.h000066400000000000000000000024731500456250600177210ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include "DataTypes.h" #include "DataModel/Object.h" class Manager; namespace DataModel { class Layer : public Object { public: Layer(const std::string& serialized) : Object() { Object::Deserialize(serialized); } Layer(__attribute__((unused)) Manager* manager, const LayerID layerID) : Object(layerID) { } virtual std::string Serialize() const { return "objectType=Layer;" + Object::Serialize(); } ObjectType GetObjectType() const { return ObjectTypeLayer; } }; } // namespace DataModel railcontrol-24+dfsg1/DataModel/LayoutItem.cpp000066400000000000000000000072751500456250600213010ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include #include #include "DataModel/LayoutItem.h" #include "Utils/Utils.h" using std::map; using std::stringstream; using std::string; namespace DataModel { bool LayoutItem::MapPosition(const LayoutPosition posX, const LayoutPosition posY, const LayoutItemSize width, const LayoutItemSize height, const LayoutRotation rotation, LayoutPosition& x, LayoutPosition& y, LayoutItemSize& w, LayoutItemSize& h) { x = posX; y = posY; switch (rotation) { case Rotation0: case Rotation180: w = width; h = height; return true; case Rotation90: case Rotation270: w = height; h = width; return true; default: return false; } } bool LayoutItem::CheckPositionFree(const LayoutPosition posX, const LayoutPosition posY, const LayoutPosition posZ) { if (this->visible == false) { return true; } if (this->posZ != posZ) { return true; } LayoutPosition x; LayoutPosition y; LayoutItemSize w; LayoutItemSize h; bool ret = MapPosition(this->posX, this->posY, this->width, this->height, this->rotation, x, y, w, h); if (ret == false) { return false; } for (LayoutPosition ix = x; ix < x + w; ix++) { for (LayoutPosition iy = y; iy < y + h; iy++) { if (ix == posX && iy == posY) { return false; } } } return true; } std::string LayoutItem::Serialize() const { stringstream ss; ss << Object::Serialize() << ";visible=" << static_cast(visible) << ";posX=" << static_cast(posX) << ";posY=" << static_cast(posY) << ";posZ=" << static_cast(posZ) << ";width=" << static_cast(width) << ";height=" << static_cast(height) << ";rotation=" << static_cast(rotation); return ss.str(); } bool LayoutItem::Deserialize(const std::string& serialized) { map arguments; ParseArguments(serialized, arguments); return Deserialize(arguments); } bool LayoutItem::Deserialize(const map& arguments) { Object::Deserialize(arguments); visible = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "visible")); if (visible > VisibleYes) { visible = VisibleYes; } posX = Utils::Utils::GetIntegerMapEntry(arguments, "posX", 0); posY = Utils::Utils::GetIntegerMapEntry(arguments, "posY", 0); posZ = Utils::Utils::GetIntegerMapEntry(arguments, "posZ", 0); width = Utils::Utils::GetIntegerMapEntry(arguments, "width", Width1); height = Utils::Utils::GetIntegerMapEntry(arguments, "height", Height1); rotation = Utils::Utils::GetIntegerMapEntry(arguments, "rotation", Rotation0); if (rotation > Rotation270) { rotation = Rotation0; } return true; } std::string LayoutItem::Rotation(LayoutRotation rotation) { switch (rotation) { case Rotation90: return "90"; case Rotation180: return "180"; case Rotation270: return "270"; case Rotation0: default: return "0"; } } } // namespace DataModel railcontrol-24+dfsg1/DataModel/LayoutItem.h000066400000000000000000000130351500456250600207350ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include "DataTypes.h" #include "DataModel/Object.h" namespace DataModel { class LayoutItem : public Object { public: enum LayoutRotationEnum : unsigned char { Rotation0 = 0, Rotation90, Rotation180, Rotation270, RotationNotRelevant }; class LayoutRotation { public: inline LayoutRotation() : rotation(Rotation0) { } inline LayoutRotation(const LayoutRotationEnum rotation) : rotation(rotation) { } inline LayoutRotation(const int rotation) { *this = rotation; } inline LayoutRotation& operator=(const int rotation) { this->rotation = (rotation > RotationNotRelevant || rotation < Rotation0) ? Rotation0 : static_cast(rotation); return *this; } inline LayoutRotation& operator=(const LayoutRotationEnum rotation) { this->rotation = rotation; return *this; } inline bool operator==(const LayoutRotationEnum rotation) const { return this->rotation == rotation; } inline bool operator!=(const LayoutRotationEnum rotation) const { return this->rotation != rotation; } inline LayoutRotation& operator++() { unsigned char r = static_cast(rotation); ++r; r &= 0x03; rotation = static_cast(r); return *this; } inline operator LayoutRotationEnum() const { return rotation; } private: LayoutRotationEnum rotation; }; typedef unsigned char LayoutItemSize; typedef signed char LayoutPosition; enum Visible : unsigned char { VisibleNo = 0, VisibleYes, VisibleNotRelevant }; static const LayoutItemSize Width1 = 1; static const LayoutItemSize Height1 = 1; static const LayoutItemSize Height2 = 2; inline LayoutItem(const ObjectID objectID) : Object(objectID), visible(VisibleYes), posX(0), posY(0), posZ(0), width(Width1), height(Height1), rotation(Rotation0) { } inline LayoutItem() : LayoutItem(0) { } virtual ~LayoutItem() {} static bool MapPosition(const LayoutPosition posX, const LayoutPosition posY, const LayoutItemSize width, const LayoutItemSize height, const LayoutRotation rotation, LayoutPosition& x, LayoutPosition& y, LayoutItemSize& w, LayoutItemSize& h); virtual bool Position(LayoutPosition& x, LayoutPosition& y, LayoutPosition& z, LayoutItemSize& w, LayoutItemSize& h, LayoutRotation& r) const { z = posZ; r = rotation; return MapPosition(posX, posY, width, height, rotation, x, y, w, h); } virtual bool CheckPositionFree(const LayoutPosition posX, const LayoutPosition posY, const LayoutPosition posZ); virtual std::string Serialize() const override; virtual bool Deserialize(const std::string& serialized) override; virtual std::string GetLayoutType() const = 0; inline void SetVisible(const Visible visible) { this->visible = visible; } inline Visible GetVisible() const { return visible; } inline void SetPosX(const LayoutPosition x) { this->posX = x; } inline LayoutPosition GetPosX() const { return posX; } inline void SetPosY(const LayoutPosition y) { this->posY = y; } inline LayoutPosition GetPosY() const { return posY; } inline void SetPosZ(const LayoutPosition z) { this->posZ = z; } inline LayoutPosition GetPosZ() const { return posZ; } inline bool HasPosition(const LayoutPosition x, const LayoutPosition y, const LayoutPosition z) const { return posX == x && posY == y && posZ == z; } inline bool IsVisibleOnLayer(const LayoutPosition z) const { return posZ == z && visible == VisibleYes; } inline void SetWidth(const LayoutItemSize width) { this->width = width; } inline LayoutItemSize GetWidth() const { return width; } inline void SetHeight(const LayoutItemSize height) { this->height = height; } inline LayoutItemSize GetHeight() const { return height; } inline void SetRotation(const LayoutRotation rotation) { this->rotation = rotation; } inline LayoutRotation GetRotation() const { return rotation; } virtual std::string Rotation() const { return Rotation(rotation); } inline void Rotate() { ++rotation; } static std::string Rotation(LayoutRotation rotation); protected: virtual bool Deserialize(const std::map& arguments) override; private: Visible visible; LayoutPosition posX; LayoutPosition posY; LayoutPosition posZ; LayoutItemSize width; LayoutItemSize height; LayoutRotation rotation; }; } // namespace DataModel railcontrol-24+dfsg1/DataModel/LockableItem.cpp000066400000000000000000000070571500456250600215360ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include "DataModel/LockableItem.h" #include "DataModel/Object.h" #include "Utils/Utils.h" using std::map; using std::stringstream; using std::string; namespace DataModel { string LockableItem::Serialize() const { return "lockstate=" + std::to_string(lockState) + ";locobaseid=" + std::to_string(locoBaseIdentifier.GetObjectID()) + ";locobasetype=" + std::to_string(locoBaseIdentifier.GetObjectType()); } bool LockableItem::Deserialize(const map arguments) { const LocoID locoID = Utils::Utils::GetIntegerMapEntry(arguments, "locoID", LocoNone); // FIXME: Remove later: 2024-02-08 locoBaseIdentifier.SetObjectID(Utils::Utils::GetIntegerMapEntry(arguments, "locobaseid", locoID)); locoBaseIdentifier.SetObjectType(static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "locobasetype", ObjectTypeLoco))); lockState = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "lockState", LockStateFree)); // FIXME: Remove later: 2024-02-08 lockState = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "lockstate", lockState)); return true; } bool LockableItem::Reserve(Logger::Logger* logger, const ObjectIdentifier& locoBaseIdentifier) { std::lock_guard Guard(lockMutex); if (this->locoBaseIdentifier == locoBaseIdentifier) { if (lockState == LockStateFree) { lockState = LockStateReserved; } return true; } if (this->locoBaseIdentifier.IsSet() || (lockState != LockStateFree)) { Object *object = dynamic_cast(this); if (!object) { return false; } logger->Debug(Languages::TextIsNotFree, object->GetName()); return false; } lockState = LockStateReserved; this->locoBaseIdentifier = locoBaseIdentifier; return true; } bool LockableItem::Lock(Logger::Logger* logger, const ObjectIdentifier& locoBaseIdentifier) { std::lock_guard Guard(lockMutex); if (this->locoBaseIdentifier != locoBaseIdentifier) { Object *object = dynamic_cast(this); if (object == nullptr) { return false; } logger->Debug(Languages::TextIsNotFree, object->GetName()); return false; } if (lockState != LockStateReserved && lockState != LockStateHardLocked) { Object *object = dynamic_cast(this); if (object == nullptr) { return false; } logger->Debug(Languages::TextIsNotFree, object->GetName()); return false; } lockState = LockStateHardLocked; return true; } bool LockableItem::Release(__attribute__((unused)) Logger::Logger* logger, const ObjectIdentifier& locoBaseIdentifier) { std::lock_guard Guard(lockMutex); if (this->locoBaseIdentifier != locoBaseIdentifier && locoBaseIdentifier.IsSet()) { return false; } this->locoBaseIdentifier.Clear(); lockState = LockStateFree; return true; } } // namespace DataModel railcontrol-24+dfsg1/DataModel/LockableItem.h000066400000000000000000000036421500456250600211770ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include "DataTypes.h" #include "Logger/Logger.h" namespace DataModel { class LockableItem { public: enum LockState : unsigned char { LockStateFree = 0, LockStateReserved, LockStateSoftLocked, LockStateHardLocked }; inline LockableItem() : lockState(LockStateFree), locoBaseIdentifier() { } virtual ~LockableItem() {}; std::string Serialize() const; bool Deserialize(const std::map arguments); inline const ObjectIdentifier& GetLocoBase() const { return locoBaseIdentifier; } inline LockState GetLockState() const { return lockState; } virtual bool Reserve(Logger::Logger* logger, const ObjectIdentifier& locoBaseIdentifier); virtual bool Lock(Logger::Logger* logger, const ObjectIdentifier& locoBaseIdentifier); virtual bool Release(Logger::Logger* logger, const ObjectIdentifier& locoBaseIdentifier); inline bool IsInUse() const { return lockState != LockStateFree || locoBaseIdentifier.IsSet(); } private: std::mutex lockMutex; LockState lockState; ObjectIdentifier locoBaseIdentifier; }; } // namespace DataModel railcontrol-24+dfsg1/DataModel/Loco.cpp000066400000000000000000000033071500456250600200710ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include #include #include "DataModel/Loco.h" #include "Hardware/LocoCache.h" #include "Utils/Utils.h" using std::map; using std::string; using std::to_string; using std::vector; namespace DataModel { std::string Loco::Serialize() const { string str; str += "objectType=Loco;"; str += LocoBase::Serialize(); return str; } bool Loco::Deserialize(const std::string& serialized) { map arguments; ParseArguments(serialized, arguments); if (!arguments.count("objectType") || arguments.at("objectType").compare("Loco") != 0) { return false; } LocoBase::Deserialize(arguments); return true; } Loco& Loco::operator=(const Hardware::LocoCacheEntry& loco) { SetControlID(loco.GetControlID()); SetAddress(loco.GetAddress()); SetProtocol(loco.GetProtocol()); SetName(loco.GetName()); SetMatchKey(loco.GetMatchKey()); ConfigureFunctions(loco.GetFunctionStates()); return *this; } } // namespace DataModel railcontrol-24+dfsg1/DataModel/Loco.h000066400000000000000000000032001500456250600175260ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include #include #include "DataTypes.h" #include "DataModel/LocoFunctions.h" #include "DataModel/LocoBase.h" #include "DataModel/Relation.h" namespace DataModel { class Loco : public LocoBase { public: Loco() = delete; Loco(const Loco&) = delete; Loco& operator=(const Loco&) = delete; inline Loco(Manager* manager, const LocoID locoID) : LocoBase(manager, locoID) { } inline Loco(Manager* manager, const std::string& serialized) : LocoBase(manager, serialized) { Loco::Deserialize(serialized); } virtual ~Loco() { } inline ObjectType GetObjectType() const override { return ObjectTypeLoco; } std::string Serialize() const override; bool Deserialize(const std::string& serialized) override; Loco& operator=(const Hardware::LocoCacheEntry& loco); }; } // namespace DataModel railcontrol-24+dfsg1/DataModel/LocoBase.cpp000066400000000000000000000602561500456250600206720ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include #include #include "DataModel/LocoBase.h" #include "DataModel/Track.h" #include "Hardware/LocoCache.h" #include "Manager.h" #include "Utils/Utils.h" using std::map; using std::string; using std::to_string; using std::vector; namespace DataModel { LocoBase::~LocoBase() { while (true) { { std::lock_guard Guard(stateMutex); if (state == LocoStateManual) { break; } } logger->Info(Languages::TextWaitingUntilHasStopped, GetName()); Utils::Utils::SleepForSeconds(1); } } std::string LocoBase::Serialize() const { string str; str += Object::Serialize(); str += ";" + HardwareHandle::Serialize(); str += ";functions=" + functions.Serialize(); str += ";orientation=" + to_string(orientation); str += ";track=" + to_string(trackFrom ? trackFrom->GetID() : TrackNone); str += ";length=" + to_string(length); str += ";pushpull=" + to_string(pushpull); str += ";maxspeed=" + to_string(maxSpeed); str += ";travelspeed=" + to_string(travelSpeed); str += ";reducedspeed=" + to_string(reducedSpeed); str += ";creepingspeed=" + to_string(creepingSpeed); str += ";propulsion=" + to_string(propulsion); str += ";type=" + to_string(trainType); str += ";matchkey=" + matchKey; return str; } bool LocoBase::Deserialize(const std::string& serialized) { map arguments; ParseArguments(serialized, arguments); return LocoBase::Deserialize(arguments); } bool LocoBase::Deserialize(const std::map& arguments) { Object::Deserialize(arguments); HardwareHandle::Deserialize(arguments); trackFrom = manager->GetTrack(static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "track", TrackNone))); functions.Deserialize(Utils::Utils::GetStringMapEntry(arguments, "functions", "0")); orientation = static_cast(Utils::Utils::GetBoolMapEntry(arguments, "orientation", OrientationRight)); length = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "length", 0)); pushpull = Utils::Utils::GetBoolMapEntry(arguments, "pushpull", false); maxSpeed = Utils::Utils::GetIntegerMapEntry(arguments, "maxspeed", MaxSpeed); travelSpeed = Utils::Utils::GetIntegerMapEntry(arguments, "travelspeed", DefaultTravelSpeed); reducedSpeed = Utils::Utils::GetIntegerMapEntry(arguments, "reducedspeed", DefaultReducedSpeed); creepingSpeed = Utils::Utils::GetIntegerMapEntry(arguments, "creepspeed", DefaultCreepingSpeed); creepingSpeed = Utils::Utils::GetIntegerMapEntry(arguments, "creepingspeed", creepingSpeed); propulsion = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "propulsion", PropulsionUnknown)); trainType = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "type", TrainTypeUnknown)); matchKey = Utils::Utils::GetStringMapEntry(arguments, "matchkey"); return true; } bool LocoBase::SetTrack(const TrackID trackID) { std::lock_guard Guard(stateMutex); // there must not be set a track if (trackFrom) { return false; } trackFrom = manager->GetTrack(trackID); return true; } TrackID LocoBase::GetTrackId() const { return (trackFrom ? trackFrom->GetID() : TrackNone); } bool LocoBase::Release() { manager->LocoBaseSpeed(ControlTypeInternal, this, MinSpeed); ForceManualMode(); { std::lock_guard Guard(stateMutex); if (routeFirst) { Route* removeRoute = routeFirst; routeFirst = nullptr; releaseRouteQueue.EnqueueBack(removeRoute); } if (routeSecond) { Route* removeRoute = routeSecond; routeSecond = nullptr; releaseRouteQueue.EnqueueBack(removeRoute); } if (trackFrom) { Track* removeTrack = trackFrom; trackFrom = nullptr; releaseTrackQueue.EnqueueBack(removeTrack); } if (trackFirst) { Track* removeTrack = trackFirst; trackFirst = nullptr; releaseTrackQueue.EnqueueBack(removeTrack); } if (trackSecond) { Track* removeTrack = trackSecond; trackSecond = nullptr; releaseTrackQueue.EnqueueBack(removeTrack); } feedbackIdOver = FeedbackNone; feedbackIdStop = FeedbackNone; feedbackIdCreep = FeedbackNone; feedbackIdReduced = FeedbackNone; feedbackIdFirst = FeedbackNone; } ReleaseRouteAndTrack(); return true; } void LocoBase::ReleaseRouteAndTrack() { const ObjectIdentifier identifier = GetObjectIdentifier(); while(!releaseTrackQueue.IsEmpty()) { Track* track = releaseTrackQueue.Dequeue(); track->Release(logger, identifier); } while(!releaseRouteQueue.IsEmpty()) { Route* route = releaseRouteQueue.Dequeue(); route->Release(logger, identifier); } } bool LocoBase::CheckFreeingTrack(const TrackID trackID) const { std::lock_guard Guard(stateMutex); if (trackFrom && (trackFrom->GetID() == trackID) && !trackFirst) { return false; } if (trackFirst && (trackFirst->GetID() == trackID)) { return false; } if (trackSecond && (trackSecond->GetID() == trackID)) { return false; } return true; } bool LocoBase::GoToAutoMode() { std::lock_guard Guard(stateMutex); if (!trackFrom) { logger->Warning(Languages::TextCanNotStartNotOnTrack); return false; } if (state == LocoStateError) { logger->Warning(Languages::TextCanNotStartInErrorState); return false; } if (state == LocoStateTerminated) { locoThread.join(); state = LocoStateManual; } if (state != LocoStateManual) { logger->Info(Languages::TextCanNotStartAlreadyRunning); return false; } followUpRoute = RouteAuto; state = LocoStateAutomodeGetFirst; locoThread = std::thread(&DataModel::LocoBase::AutoMode, this); return true; } void LocoBase::RequestManualMode() { if (state == LocoStateManual || state == LocoStateTerminated) { return; } requestManualMode = true; } bool LocoBase::GoToManualMode() { if (state == LocoStateManual) { return true; } if (state != LocoStateTerminated) { return false; } locoThread.join(); state = LocoStateManual; return true; } void LocoBase::ForceManualMode() { { std::lock_guard Guard(stateMutex); switch (state) { case LocoStateManual: return; case LocoStateTerminated: break; default: state = LocoStateOff; break; } } locoThread.join(); state = LocoStateManual; } void LocoBase::AutoMode() { Utils::Utils::SetMinThreadPriority(); const string& name = GetName(); Utils::Utils::SetThreadName(name); logger->Info(Languages::TextIsNowInAutoMode); while (true) { unsigned char pause = 0; { // sleep must be outside of locked block std::lock_guard Guard(stateMutex); if (!feedbackIdsReached.IsEmpty()) { FeedbackID feedbackId = feedbackIdsReached.Dequeue(); if (feedbackId == feedbackIdFirst) { FeedbackIdFirstReached(); } else if (feedbackId == feedbackIdStop) { FeedbackIdStopReached(); } } else { switch (state) { case LocoStateOff: // automode is turned off, terminate thread logger->Info(Languages::TextIsNowInManualMode); state = LocoStateTerminated; requestManualMode = false; wait = 0; return; case LocoStateAutomodeGetFirst: if (requestManualMode || (followUpRoute == RouteStop)) { state = LocoStateOff; break; } if (wait > 0) { --wait; break; } GetTimetableDestinationFirst(); break; case LocoStateAutomodeGetSecond: if (requestManualMode) { logger->Info(Languages::TextIsRunningWaitingUntilDestination); state = LocoStateStopping; break; } if (manager->GetNrOfTracksToReserve() <= 1) { break; } if (wait > 0) { break; } GetTimetableDestinationSecond(); break; case LocoStateAutomodeRunning: // loco is already running, waiting until destination reached if (requestManualMode) { logger->Info(Languages::TextIsRunningWaitingUntilDestination); state = LocoStateStopping; } break; case LocoStateStopping: if (requestManualMode) { logger->Info(Languages::TextHasNotReachedDestination); break; } if (trackSecond) { state = LocoStateAutomodeRunning; } else if (trackFirst) { state = LocoStateAutomodeGetSecond; } else { state = LocoStateAutomodeGetFirst; } break; case LocoStateTerminated: logger->Error(Languages::TextIsInTerminatedState); state = LocoStateError; break; case LocoStateManual: logger->Error(Languages::TextIsInManualState); state = LocoStateError; #include "Fallthrough.h" case LocoStateError: logger->Error(Languages::TextIsInErrorState); manager->LocoBaseSpeed(ControlTypeInternal, this, MinSpeed); if (requestManualMode) { state = LocoStateOff; } break; } pause = 1; } } ReleaseRouteAndTrack(); // FIXME: make configurable Utils::Utils::SleepForSeconds(pause); } } Route* LocoBase::GetNextDestination(const Track* const track, const bool allowLocoTurn) { if (timeTableQueue.IsEmpty()) { if (followUpRoute == RouteStop) { RequestManualMode(); return nullptr; } const Route* route = nullptr; if (followUpRoute != RouteAuto) { route = manager->GetRoute(followUpRoute); } else { route = SearchDestination(track, allowLocoTurn); } if (route) { AddTimeTable(route, route->GetFollowUpRoute()); } } return GetDestinationFromTimeTable(track, allowLocoTurn); } void LocoBase::GetTimetableDestinationFirst() { if (routeFirst) { state = LocoStateError; logger->Error(Languages::TextHasAlreadyReservedRoute); return; } Route* route = GetNextDestination(trackFrom, true); if (!route) { return; } PrepareDestinationFirst(route); } void LocoBase::GetTimetableDestinationSecond() { Route* route = GetNextDestination(trackFirst, false); if (!route) { return; } PrepareDestinationSecond(route); } void LocoBase::PrepareDestinationFirst(Route* const route) { if (!route) { return; } Track* newTrack = manager->GetTrack(route->GetToTrack()); if (!newTrack) { return; } bool isOrientationSet = newTrack->SetLocoBaseOrientation(route->GetToOrientation()); if (!isOrientationSet) { return; } bool turnLoco = (trackFrom->GetLocoBaseOrientation() != route->GetFromOrientation()); Orientation newLocoOrientation = static_cast(orientation != turnLoco); if (turnLoco) { bool canTurnOrientation = trackFrom->SetLocoBaseOrientation(route->GetFromOrientation()); if (!canTurnOrientation) { return; } manager->TrackPublishState(trackFrom); } manager->LocoBaseOrientation(ControlTypeInternal, this, newLocoOrientation); logger->Info(Languages::TextHeadingToVia, newTrack->GetName(), route->GetName()); trackFirst = newTrack; routeFirst = route; feedbackIdFirstReduced = FeedbackNone; feedbackIdFirstCreep = FeedbackNone; feedbackIdFirst = FeedbackNone; feedbackIdReduced = routeFirst->GetFeedbackIdReduced(); reducedDelay = routeFirst->GetReducedDelay(); feedbackIdCreep = routeFirst->GetFeedbackIdCreep(); creepDelay = routeFirst->GetCreepDelay(); feedbackIdStop = routeFirst->GetFeedbackIdStop(); stopDelay = routeFirst->GetStopDelay(); feedbackIdOver = routeFirst->GetFeedbackIdOver(); wait = routeFirst->GetWaitAfterRelease(); // start loco manager->TrackPublishState(newTrack); Speed newSpeed; switch (routeFirst->GetSpeed()) { case Route::SpeedTravel: newSpeed = travelSpeed; break; case Route::SpeedReduced: newSpeed = reducedSpeed; break; case Route::SpeedCreeping: default: newSpeed = creepingSpeed; break; } manager->LocoBaseSpeed(ControlTypeInternal, this, newSpeed); state = LocoStateAutomodeGetSecond; } Route* LocoBase::GetDestinationFromTimeTable(const Track* const track, const bool allowLocoTurn) { if (timeTableQueue.IsEmpty()) { return nullptr; } const TimeTableEntry entry = timeTableQueue.Dequeue(); Route* const route = manager->GetRoute(entry.first); if (route->GetFromTrack() != track->GetID()) { return nullptr; } logger->Debug(Languages::TextUsingRouteFromTimetable, route->GetName()); bool ret = ExecuteRoute(track, allowLocoTurn, route); if (!ret) { timeTableQueue.EnqueueFront(entry); return nullptr; } followUpRoute = entry.second; return route; } void LocoBase::AddTimeTable(const Route* route, const RouteID followUpRoute) { logger->Debug(Languages::TextAddingRouteToTimetable, route->GetName()); requestManualMode = false; const TimeTableEntry entry(route->GetID(), followUpRoute); timeTableQueue.EnqueueBack(entry); } void LocoBase::PrepareDestinationSecond(Route* const route) { if (!route) { return; } Track* newTrack = manager->GetTrack(route->GetToTrack()); if (!newTrack) { return; } const bool isOrientationSet = newTrack->SetLocoBaseOrientation(route->GetToOrientation()); if (!isOrientationSet) { return; } logger->Info(Languages::TextHeadingToViaVia, newTrack->GetName(), routeFirst->GetName(), route->GetName()); trackSecond = newTrack; routeSecond = route; feedbackIdFirst = feedbackIdStop; feedbackIdFirstCreep = feedbackIdCreep; feedbackIdFirstReduced = feedbackIdReduced; feedbackIdOver = routeSecond->GetFeedbackIdOver(); feedbackIdStop = routeSecond->GetFeedbackIdStop(); feedbackIdCreep = routeSecond->GetFeedbackIdCreep(); feedbackIdReduced = routeSecond->GetFeedbackIdReduced(); wait = routeSecond->GetWaitAfterRelease(); manager->TrackPublishState(newTrack); state = LocoStateAutomodeRunning; } Route* LocoBase::SearchDestination(const Track* const track, const bool allowLocoTurn) { if (manager->Booster() == BoosterStateStop) { return nullptr; } logger->Debug(Languages::TextLookingForDestination, track->GetName()); if (routeSecond) { state = LocoStateError; logger->Error(Languages::TextHasAlreadyReservedRoute); return nullptr; } if (!track) { state = LocoStateOff; logger->Info(Languages::TextIsNotOnTrack); return nullptr; } const ObjectIdentifier& locoBaseOfTrack = track->GetLocoBase(); if (locoBaseOfTrack != GetObjectIdentifier()) { state = LocoStateError; logger->Error(Languages::TextIsOnOcupiedTrack, track->GetName(), manager->GetLocoBaseName(locoBaseOfTrack)); return nullptr; } vector validRoutes; track->GetValidRoutes(logger, this, allowLocoTurn, validRoutes); for (auto route : validRoutes) { bool ret = ReserveRoute(track, allowLocoTurn, route); if (ret) { return route; } } logger->Debug(Languages::TextNoValidRouteFound, GetName()); return nullptr; } bool LocoBase::ReserveRoute(const Track* const track, const bool allowLocoTurn, Route* const route) { const ObjectIdentifier locoBaseIdentifier = GetObjectIdentifier(); if (!route->Reserve(logger, locoBaseIdentifier)) { return false; } Track* newTrack = manager->GetTrack(route->GetToTrack()); if (!newTrack) { route->Release(logger, locoBaseIdentifier); return false; } bool canSetOrientation = newTrack->CanSetLocoBaseOrientation(route->GetToOrientation(), locoBaseIdentifier); if (!canSetOrientation) { route->Release(logger, locoBaseIdentifier); newTrack->Release(logger, locoBaseIdentifier); return false; } if (!allowLocoTurn && track->GetLocoBaseOrientation() != route->GetFromOrientation()) { route->Release(logger, locoBaseIdentifier); newTrack->Release(logger, locoBaseIdentifier); return false; } logger->Debug(Languages::TextReservingRoute, route->GetName()); return true; } bool LocoBase::ExecuteRoute(const Track* const track, const bool allowLocoTurn, Route* const route) { const LockableItem::LockState lockState = route->GetLockState(); if (lockState == LockableItem::LockStateFree) { if (!ReserveRoute(track, allowLocoTurn, route)) { return false; } } if (lockState == LockableItem::LockStateReserved) { if (!route->Lock(logger, GetObjectIdentifier())) { return false; } } logger->Debug(Languages::TextExecutingRoute, route->GetName()); const ObjectIdentifier& locoBaseIdentifier = GetObjectIdentifier(); if (!route->Execute(logger, locoBaseIdentifier)) { route->Release(logger, locoBaseIdentifier); Track* newTrack = manager->GetTrack(route->GetToTrack()); if (newTrack) { newTrack->Release(logger, locoBaseIdentifier); } return false; } return true; } Speed LocoBase::GetRouteSpeed(const Route::Speed routeSpeed) { switch (routeSpeed) { case Route::SpeedTravel: return travelSpeed; case Route::SpeedReduced: return reducedSpeed; case Route::SpeedCreeping: return creepingSpeed; default: return MinSpeed; } } void LocoBase::LocationStopReached(const FeedbackID feedbackID, const Delay stopDelay) { Utils::Utils::SleepForMilliseconds(stopDelay * 100); if (feedbackID != feedbackIdStop) { return; } LocationStopReached(); } void LocoBase::LocationStopReached() { manager->LocoBaseSpeed(ControlTypeInternal, this, MinSpeed); feedbackIdsReached.EnqueueBack(feedbackIdStop); } void LocoBase::LocationCreepReached(const FeedbackID feedbackID, const Delay creepDelay) { Utils::Utils::SleepForMilliseconds(creepDelay * 100); if (feedbackID != feedbackIdCreep) { return; } LocationCreepReached(); } void LocoBase::LocationCreepReached() { if (speed > creepingSpeed) { manager->LocoBaseSpeed(ControlTypeInternal, this, creepingSpeed); } if (feedbackIdFirst != FeedbackNone) { feedbackIdsReached.EnqueueBack(feedbackIdFirst); } } void LocoBase::LocationReducedReached(const FeedbackID feedbackID, const Delay reducedDelay) { Utils::Utils::SleepForMilliseconds(reducedDelay * 100); if (feedbackID != feedbackIdReduced) { return; } LocationReducedReached(); } void LocoBase::LocationReducedReached() { if (speed > reducedSpeed) { manager->LocoBaseSpeed(ControlTypeInternal, this, reducedSpeed); } if (feedbackIdFirst != 0) { feedbackIdsReached.EnqueueBack(feedbackIdFirst); } } void LocoBase::LocationReached(const FeedbackID feedbackID) { if (feedbackID == feedbackIdFirstReduced) { Route::Speed routeSpeed = routeSecond->GetSpeed(); switch (routeSpeed) { case Route::SpeedReduced: case Route::SpeedCreeping: if (speed > reducedSpeed) { manager->LocoBaseSpeed(ControlTypeInternal, this, reducedSpeed); } break; default: break; } } if (feedbackID == feedbackIdFirstCreep) { Route::Speed routeSpeed = routeSecond->GetSpeed(); switch (routeSpeed) { case Route::SpeedCreeping: if (speed > creepingSpeed) { manager->LocoBaseSpeed(ControlTypeInternal, this, creepingSpeed); } break; default: break; } } if (feedbackID == feedbackIdFirst) { Speed newSpeed = GetRouteSpeed(routeSecond->GetSpeed()); if (speed > newSpeed) { manager->LocoBaseSpeed(ControlTypeInternal, this, newSpeed); } feedbackIdsReached.EnqueueBack(feedbackIdFirst); } if (feedbackID == feedbackIdReduced) { if (reducedDelay) { __attribute((unused)) auto r = std::async(std::launch::async, LocationReducedReachedStatic, this, feedbackID, reducedDelay); } else { LocationReducedReached(); } } if (feedbackID == feedbackIdCreep) { if (creepDelay) { __attribute((unused)) auto r = std::async(std::launch::async, LocationCreepReachedStatic, this, feedbackID, creepDelay); } else { LocationCreepReached(); } } if (feedbackID == feedbackIdStop) { if (stopDelay) { __attribute((unused)) auto r = std::async(std::launch::async, LocationStopReachedStatic, this, feedbackID, stopDelay); } else { LocationStopReached(); } } if (feedbackID == feedbackIdOver) { manager->LocoBaseSpeed(ControlTypeInternal, this, MinSpeed); manager->Booster(ControlTypeInternal, BoosterStateStop); logger->Error(Languages::TextHitOverrun, manager->GetFeedbackName(feedbackID)); } } void LocoBase::SetSpeed(const Speed speed) { this->speed = speed; } void LocoBase::SetFunctionState(const DataModel::LocoFunctionNr nr, const DataModel::LocoFunctionState state) { functions.SetFunctionState(nr, state); } void LocoBase::SetOrientation(const Orientation orientation) { this->orientation = orientation; } void LocoBase::FeedbackIdFirstReached() { if (!routeFirst || !trackFrom) { manager->LocoBaseSpeed(ControlTypeInternal, this, MinSpeed); state = LocoStateError; logger->Error(Languages::TextIsInAutomodeWithoutRouteTrack); return; } Speed newSpeed = GetRouteSpeed(routeSecond->GetSpeed()); if (speed != newSpeed) { manager->LocoBaseSpeed(ControlTypeInternal, this, newSpeed); } ShiftRoute(); // set state switch (state) { case LocoStateAutomodeRunning: state = LocoStateAutomodeGetSecond; break; case LocoStateStopping: // do nothing break; default: logger->Error(Languages::TextIsInInvalidAutomodeState, state, manager->GetFeedbackName(feedbackIdFirst)); state = LocoStateError; break; } feedbackIdFirstCreep = FeedbackNone; feedbackIdFirstReduced = FeedbackNone; feedbackIdFirst = FeedbackNone; } void LocoBase::FeedbackIdStopReached() { if (!routeFirst || !trackFrom) { manager->LocoBaseSpeed(ControlTypeInternal, this, MinSpeed); state = LocoStateError; logger->Error(Languages::TextIsInAutomodeWithoutRouteTrack); return; } if (routeSecond) { // this happens when FeedbackIdFirst has been missed manager->LocoDestinationReached(this, routeSecond, trackSecond); } else { manager->LocoDestinationReached(this, routeFirst, trackFirst); } logger->Info(Languages::TextReachedItsDestination); while (routeFirst) { ShiftRoute(); } // set state switch (state) { case LocoStateAutomodeRunning: case LocoStateAutomodeGetSecond: state = LocoStateAutomodeGetFirst; break; case LocoStateStopping: state = LocoStateOff; break; default: logger->Error(Languages::TextIsInInvalidAutomodeState, state, manager->GetFeedbackName(feedbackIdStop)); state = LocoStateError; break; } feedbackIdStop = FeedbackNone; feedbackIdCreep = FeedbackNone; feedbackIdReduced = FeedbackNone; } void LocoBase::ShiftRoute() { Route* removeRoute = routeFirst; routeFirst = routeSecond; routeSecond = nullptr; Track* removeTrack = trackFrom; trackFrom = trackFirst; trackFirst = trackSecond; trackSecond = nullptr; releaseRouteQueue.EnqueueBack(removeRoute); releaseTrackQueue.EnqueueBack(removeTrack); } DataModel::LocoFunctionNr LocoBase::GetFunctionNumberFromFunctionIcon(const DataModel::LocoFunctionIcon icon) const { for (DataModel::LocoFunctionNr nr = 0; nr < NumberOfLocoFunctions; ++nr) { if (icon == functions.GetFunctionIcon(nr)) { return nr; } } return NumberOfLocoFunctions; } LocoBase& LocoBase::operator=(const Hardware::LocoCacheEntry& loco) { SetControlID(loco.GetControlID()); SetAddress(loco.GetAddress()); SetProtocol(loco.GetProtocol()); SetName(loco.GetName()); SetMatchKey(loco.GetMatchKey()); ConfigureFunctions(loco.GetFunctionStates()); return *this; } } // namespace DataModel railcontrol-24+dfsg1/DataModel/LocoBase.h000066400000000000000000000241661500456250600203370ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include #include #include "DataTypes.h" #include "Logger/Logger.h" #include "DataModel/HardwareHandle.h" #include "DataModel/LocoFunctions.h" #include "DataModel/Object.h" #include "DataModel/Relation.h" #include "DataModel/Route.h" #include "Utils/ThreadSafeQueue.h" class Manager; namespace Hardware { class LocoCacheEntry; } namespace DataModel { class ObjectIdentifier; class Route; class Track; class LocoBase : public Object, public HardwareHandle { public: enum NrOfTracksToReserve : unsigned char { ReserveOne = 1, ReserveTwo = 2 }; enum AutoModeType : unsigned char { AutoModeTypeFull = 0, AutoModeTypeTimetable }; LocoBase() = delete; LocoBase(const LocoBase&) = delete; LocoBase& operator=(const LocoBase&) = delete; inline LocoBase(Manager* manager, const LocoID locoID) : Object(locoID), HardwareHandle(), manager(manager), length(0), pushpull(false), maxSpeed(0), travelSpeed(0), reducedSpeed(0), creepingSpeed(0), propulsion(PropulsionUnknown), trainType(TrainTypeUnknown), speed(MinSpeed), orientation(OrientationRight), state(LocoStateManual), requestManualMode(false), trackFrom(nullptr), trackFirst(nullptr), trackSecond(nullptr), routeFirst(nullptr), routeSecond(nullptr), feedbackIdFirstReduced(FeedbackNone), feedbackIdFirstCreep(FeedbackNone), feedbackIdFirst(FeedbackNone), feedbackIdReduced(FeedbackNone), feedbackIdCreep(FeedbackNone), feedbackIdStop(FeedbackNone), feedbackIdOver(FeedbackNone), feedbackIdsReached(), wait(0), followUpRoute(RouteAuto), matchKey("") { logger = Logger::Logger::GetLogger(GetName()); } inline LocoBase(Manager* manager, const std::string& serialized) : LocoBase(manager, LocoNone) { LocoBase::Deserialize(serialized); logger = Logger::Logger::GetLogger(GetName()); } virtual ~LocoBase(); inline Logger::Logger* GetLogger() const { return logger; } std::string Serialize() const override; bool Deserialize(const std::string& serialized) override; bool Deserialize(const std::map& arguments) override; virtual void SetName(const std::string& name) override { Object::SetName(name); logger = Logger::Logger::GetLogger(name); } bool GoToAutoMode(); void RequestManualMode(); bool GoToManualMode(); void AddTimeTable(const Route* route, const RouteID followUpRoute); bool SetTrack(const TrackID trackID); TrackID GetTrackId() const; bool Release(); void ReleaseRouteAndTrack(); bool CheckFreeingTrack(const TrackID trackID) const; void LocationReached(const FeedbackID feedbackID); virtual void SetSpeed(const Speed speed); inline Speed GetSpeed() const { return speed; } virtual void SetFunctionState(const DataModel::LocoFunctionNr nr, const DataModel::LocoFunctionState state); inline DataModel::LocoFunctionState GetFunctionState(const DataModel::LocoFunctionNr nr) const { return functions.GetFunctionState(nr); } inline std::vector GetFunctionStates() const { return functions.GetFunctionStates(); } inline void GetFunctions(LocoFunctionEntry* out) const { functions.GetFunctions(out); } inline void ConfigureFunctions(const std::vector& newEntries) { functions.ConfigureFunctions(newEntries); } virtual void SetOrientation(const Orientation orientation); inline Orientation GetOrientation() const { return orientation; } inline bool IsInManualMode() const { return this->state == LocoStateManual; } inline bool IsInUse() const { return this->speed > 0 || this->state != LocoStateManual || this->trackFrom != nullptr || this->routeFirst != nullptr; } inline Length GetLength() const { return length; } inline void SetLength(const Length length) { this->length = length; } inline bool GetPushpull() const { return pushpull; } inline Speed GetMaxSpeed() const { return maxSpeed; } inline Speed GetTravelSpeed() const { return travelSpeed; } inline Speed GetReducedSpeed() const { return reducedSpeed; } inline Speed GetCreepingSpeed() const { return creepingSpeed; } inline void SetPushpull(const bool pushpull) { this->pushpull = pushpull; } inline void SetMaxSpeed(const Speed speed) { maxSpeed = speed; } inline void SetTravelSpeed(const Speed speed) { travelSpeed = speed; } inline void SetReducedSpeed(const Speed speed) { reducedSpeed = speed; } inline void SetCreepingSpeed(const Speed speed) { creepingSpeed = speed; } inline void SetPropulsion(const Propulsion propulsion) { this->propulsion = propulsion; } inline Propulsion GetPropulsion() const { return propulsion; } inline void SetTrainType(const TrainType trainType) { this->trainType = trainType; } inline TrainType GetTrainType() const { return trainType; } inline void SetMatchKey(const std::string& matchKey) { this->matchKey = matchKey; } inline void ClearMatchKey() { matchKey.clear(); } inline std::string GetMatchKey() const { return matchKey; } DataModel::LocoFunctionNr GetFunctionNumberFromFunctionIcon(const DataModel::LocoFunctionIcon icon) const; DataModel::LocoFunctionIcon GetFunctionIcon(const DataModel::LocoFunctionNr nr) const { return functions.GetFunctionIcon(nr); } DataModel::LocoFunctionType GetFunctionType(const DataModel::LocoFunctionNr nr) const { return functions.GetFunctionType(nr); } inline LocoID GetLocoIdWithPrefix() const { return GetID() + (GetObjectType() == ObjectTypeMultipleUnit ? MultipleUnitIdPrefix : 0); } LocoBase& operator=(const Hardware::LocoCacheEntry& loco); protected: Manager* manager; private: typedef std::pair TimeTableEntry; enum LocoState : unsigned char { LocoStateManual = 0, LocoStateTerminated, LocoStateOff, LocoStateAutomodeGetFirst, LocoStateAutomodeGetSecond, LocoStateAutomodeRunning, LocoStateStopping, LocoStateError }; void SetMinThreadPriorityAndThreadName(); void AutoMode(); Route* GetNextDestination(const Track* const track, const bool allowLocoTurn); void GetTimetableDestinationFirst(); void PrepareDestinationFirst(Route* const route); void GetTimetableDestinationSecond(); DataModel::Route* GetDestinationFromTimeTable(const Track* const track, const bool allowLocoTurn); void PrepareDestinationSecond(Route* const route); DataModel::Route* SearchDestination(const DataModel::Track* const oldToTrack, const bool allowLocoTurn); bool ReserveRoute(const Track* const track, const bool allowLocoTurn, Route* const route); bool ExecuteRoute(const Track* const track, const bool allowLocoTurn, Route* const route); void FeedbackIdFirstReached(); void FeedbackIdStopReached(); void ShiftRoute(); void ForceManualMode(); bool GoToAutoModeInternal(const LocoState newState); Speed GetRouteSpeed(const Route::Speed routeSpeed); void LocationStopReached(const FeedbackID feedbackID, const Delay stopDelay); void LocationStopReached(); static inline void LocationStopReachedStatic(LocoBase* locoBase, const FeedbackID feedbackID, const Delay stopDelay) { locoBase->LocationStopReached(feedbackID, stopDelay); } void LocationCreepReached(const FeedbackID feedbackID, const Delay creepDelay); void LocationCreepReached(); static inline void LocationCreepReachedStatic(LocoBase* locoBase, const FeedbackID feedbackID, const Delay creepDelay) { locoBase->LocationCreepReached(feedbackID, creepDelay); } void LocationReducedReached(const FeedbackID feedbackID, const Delay reducedDelay); void LocationReducedReached(); static inline void LocationReducedReachedStatic(LocoBase* locoBase, const FeedbackID feedbackID, const Delay reducedDelay) { locoBase->LocationReducedReached(feedbackID, reducedDelay); } mutable std::mutex stateMutex; std::thread locoThread; Length length; bool pushpull; Speed maxSpeed; Speed travelSpeed; Speed reducedSpeed; Speed creepingSpeed; Propulsion propulsion; TrainType trainType; Speed speed; Orientation orientation; volatile LocoState state; volatile bool requestManualMode; Track* trackFrom; Track* trackFirst; Track* trackSecond; Route* routeFirst; Route* routeSecond; Utils::ThreadSafeQueue releaseTrackQueue; Utils::ThreadSafeQueue releaseRouteQueue; volatile FeedbackID feedbackIdFirstReduced; volatile FeedbackID feedbackIdFirstCreep; volatile FeedbackID feedbackIdFirst; volatile FeedbackID feedbackIdReduced; volatile Delay reducedDelay; volatile FeedbackID feedbackIdCreep; volatile Delay creepDelay; volatile FeedbackID feedbackIdStop; volatile Delay stopDelay; volatile FeedbackID feedbackIdOver; Utils::ThreadSafeQueue feedbackIdsReached; Pause wait; Utils::ThreadSafeQueue timeTableQueue; RouteID followUpRoute; std::string matchKey; LocoFunctions functions; Logger::Logger* logger; }; } // namespace DataModel railcontrol-24+dfsg1/DataModel/LocoConfig.h000066400000000000000000000107141500456250600206640ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "DataModel/Loco.h" #include "DataModel/MultipleUnit.h" #include "Hardware/LocoCache.h" namespace DataModel { class LocoConfig { public: inline LocoConfig(const LocoType type = LocoTypeLoco) : controlId(ControlNone), locoId(LocoNone), address(AddressDefault), protocol(ProtocolNone), type(type), isInUse(false) { } inline LocoConfig(const DataModel::Loco& loco) : controlId(loco.GetControlID()), locoId(loco.GetID()), address(loco.GetAddress()), protocol(loco.GetProtocol()), type(LocoTypeLoco), name(loco.GetName()), matchKey(loco.GetMatchKey()), isInUse(loco.IsInUse()) { ConfigureFunctions(loco.GetFunctionStates()); } inline LocoConfig(const Hardware::LocoCacheEntry& loco) : controlId(loco.GetControlID()), locoId(loco.GetLocoID()), address(loco.GetAddress()), protocol(loco.GetProtocol()), type(loco.GetType()), name(loco.GetName()), matchKey(loco.GetMatchKey()), isInUse(false), slaves(loco.GetSlaveIDs()) { ConfigureFunctions(loco.GetFunctionStates()); } inline LocoConfig& operator=(const DataModel::Loco& loco) { controlId = loco.GetControlID(); locoId = loco.GetID(); address = loco.GetAddress(); protocol = loco.GetProtocol(); type = LocoTypeLoco; name = loco.GetName(); matchKey = loco.GetMatchKey(); isInUse = loco.IsInUse(); ConfigureFunctions(loco.GetFunctionStates()); return *this; } inline LocoConfig& operator=(const Hardware::LocoCacheEntry& loco) { controlId = loco.GetControlID(); locoId = loco.GetLocoID(); address = loco.GetAddress(); protocol = loco.GetProtocol(); type = loco.GetType(); name = loco.GetName(); matchKey = loco.GetMatchKey(); ConfigureFunctions(loco.GetFunctionStates()); slaves = loco.GetSlaveIDs(); return *this; } inline LocoConfig& operator=(const DataModel::MultipleUnit& multipleUnit) { controlId = multipleUnit.GetControlID(); locoId = multipleUnit.GetID(); address = multipleUnit.GetAddress(); protocol = ProtocolNone; type = LocoTypeMultipleUnit; name = multipleUnit.GetName(); matchKey = multipleUnit.GetMatchKey(); isInUse = multipleUnit.IsInUse(); ConfigureFunctions(multipleUnit.GetFunctionStates()); slaves = multipleUnit.GetSlaveIDs(); return *this; } inline ControlID GetControlId() const { return controlId; } inline LocoID GetLocoId() const { return locoId; } inline Address GetAddress() const { return address; } inline Protocol GetProtocol() const { return protocol; } inline LocoType GetType() const { return type; } inline void SetType(const LocoType type) { this->type = type; } inline std::string GetName() const { return name; } inline void SetName(const std::string& name) { this->name = name; } inline std::string GetMatchKey() const { return matchKey; } inline bool IsInUse() const { return isInUse; } inline void GetFunctions(LocoFunctionEntry* out) const { functions.GetFunctions(out); } inline void ConfigureFunctions(const std::vector& newEntries) { functions.ConfigureFunctions(newEntries); } inline void AddSlave(const LocoID locoID) { slaves.push_back(locoID); } inline const std::vector& GetSlaves() const { return slaves; } private: ControlID controlId; LocoID locoId; Address address; Protocol protocol; LocoType type; std::string name; std::string matchKey; bool isInUse; LocoFunctions functions; std::vector slaves; }; } // namespace DataModel railcontrol-24+dfsg1/DataModel/LocoFunctions.cpp000066400000000000000000003017171500456250600217700ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include #include #include "DataModel/LocoFunctions.h" #include "Utils/Integer.h" #include "Utils/Utils.h" namespace DataModel { LocoFunctions::LocoFunctions() { for (LocoFunctionNr nr = 0; nr < NumberOfLocoFunctions; ++nr) { entries[nr].nr = nr; entries[nr].type = LocoFunctionTypeNone; entries[nr].icon = LocoFunctionIconNone; entries[nr].timer = 0; } } std::vector LocoFunctions::GetFunctionStates() const { std::vector out; for (LocoFunctionNr i = 0; i < NumberOfLocoFunctions; ++i) { if (entries[i].type == LocoFunctionTypeNone) { continue; } out.push_back(entries[i]); } return out; } void LocoFunctions::ConfigureFunctions(const std::vector& newEntries) { for (LocoFunctionNr nr = 0; nr < NumberOfLocoFunctions; ++nr) { entries[nr].type = LocoFunctionTypeNone; } for (const LocoFunctionEntry& newEntry : newEntries) { LocoFunctionNr nr = newEntry.nr; LocoFunctionState state = entries[nr].state; entries[nr] = newEntry; entries[nr].state = state; } } void LocoFunctions::SetFunctionStates(const std::vector& newEntries) { for (LocoFunctionNr nr = 0; nr < NumberOfLocoFunctions; ++nr) { entries[nr].type = LocoFunctionTypeNone; } for (const LocoFunctionEntry& newEntry : newEntries) { LocoFunctionNr nr = newEntry.nr; entries[nr] = newEntry; } } std::string LocoFunctions::Serialize() const { std::string out; for (LocoFunctionNr nr = 0; nr < NumberOfLocoFunctions; ++nr) { if (entries[nr].type == LocoFunctionTypeNone) { continue; } out += "f" + std::to_string(static_cast(nr)); out += ":" + std::to_string(static_cast(entries[nr].state)); out += ":" + std::to_string(static_cast(entries[nr].type)); out += ":" + std::to_string(static_cast(entries[nr].icon)); if (entries[nr].type != LocoFunctionTypeTimer) { continue; } out += ":" + std::to_string(static_cast(entries[nr].icon)); } return out; } bool LocoFunctions::Deserialize(const std::string& serialized) { std::deque functionsSerialized; Utils::Utils::SplitString(serialized, "f", functionsSerialized); for (std::string& functionSerialized : functionsSerialized) { if (functionSerialized.size() == 0) { continue; } std::deque functionTexts; Utils::Utils::SplitString(functionSerialized, ":", functionTexts); const size_t nrOfTexts = functionTexts.size(); if (nrOfTexts != 4 && nrOfTexts != 5) { continue; } LocoFunctionNr nr = Utils::Integer::StringToInteger(functionTexts.front()); functionTexts.pop_front(); entries[nr].state = static_cast(Utils::Integer::StringToInteger(functionTexts.front(), LocoFunctionStateOff)); functionTexts.pop_front(); entries[nr].type = static_cast(Utils::Integer::StringToInteger(functionTexts.front(), LocoFunctionTypePermanent)); functionTexts.pop_front(); entries[nr].icon = static_cast(Utils::Integer::StringToInteger(functionTexts.front(), LocoFunctionIconDefault)); functionTexts.pop_front(); if (nrOfTexts == 4) { entries[nr].timer = 0; continue; } entries[nr].timer = static_cast(Utils::Integer::StringToInteger(functionTexts.front(), 0)); functionTexts.pop_front(); } return true; } std::string LocoFunctions::GetLocoFunctionIcon(const LocoFunctionNr nr, const LocoFunctionIcon icon) { switch (icon) { case LocoFunctionIconNone: return ""; case LocoFunctionIconShuntingMode: return "" "" "" "" "" ""; case LocoFunctionIconInertia: return "" "" "" "kg" ""; case LocoFunctionIconLight: return "" "" "" "" "" "" "" "" "" "" "" "" ""; case LocoFunctionIconHeadlightLowBeamForward: return "" "" "" "" "" "" ""; case LocoFunctionIconHeadlightLowBeamReverse: return "" "" "" "" "" "" ""; case LocoFunctionIconHeadlightHighBeamForward: return "" "" "" "" "" "" "" ""; case LocoFunctionIconHeadlightHighBeamReverse: return "" "" "" "" "" "" "" ""; case LocoFunctionIconBacklightForward: return "" "" "" "" "" "" "" "" ""; case LocoFunctionIconBacklightReverse: return "" "" "" "" "" "" "" "" ""; case LocoFunctionIconShuntingLight: return "" "" "" "" "" "" "" ""; case LocoFunctionIconBlinkingLight: return "" "" "" "" "" "" "" "" "" "" ""; case LocoFunctionIconInteriorLight1: return "" "" "" "" "" "" "" "" "1" ""; case LocoFunctionIconInteriorLight2: return "" "" "" "" "" "" "" "" "2" ""; case LocoFunctionIconTableLight1: return "" "" "" "" "" "" "" "" "" "" "" "" ""; case LocoFunctionIconTableLight2: return "" "" "" "" "" "" "" "" "" "" "" "" ""; case LocoFunctionIconTableLight3: return "" "" "" "" "" "" "" "" "" "" "" "" ""; case LocoFunctionIconCabLight1: return "" "" "" "" "" "" "" "" ""; case LocoFunctionIconCabLight2: return "" "" "" "" "" "" "" "" ""; case LocoFunctionIconCabLight12: return "" "" "" "" "" "" "" "" "" ""; case LocoFunctionIconDriversDeskLight: return "" "" "" "" "" ""; case LocoFunctionIconTrainDestinationIndicator: return "" "" "Zürich" "" "" "" "" "" "" ""; case LocoFunctionIconLocomotiveNumberIndicator: return "" "" "X4014" "" "" "" "" "" "" ""; case LocoFunctionIconEngineLight: return "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""; case LocoFunctionIconFireBox: return "" "" "" "" "" "" "" "" ""; case LocoFunctionIconStairsLight: return "" "" "" "" "" "" "" "" "" ""; case LocoFunctionIconSmokeGenerator: return "" "" "" "" ""; case LocoFunctionIconTelex1: return "" "" "" "" ""; case LocoFunctionIconTelex2: return "" "" "" "" ""; case LocoFunctionIconTelex12: return "" "" "" ""; case LocoFunctionIconPanto1: return "" "" "" "" "" "" ""; case LocoFunctionIconPanto2: return "" "" "" "" "" "" ""; case LocoFunctionIconPanto12: return "" "" "" "" "" ""; case LocoFunctionIconUp: return "" "" ""; case LocoFunctionIconDown: return "" "" ""; case LocoFunctionIconUpDown1: return "" "" ""; case LocoFunctionIconUpDown2: return "" "" ""; case LocoFunctionIconLeft: return "" "" ""; case LocoFunctionIconRight: return "" "" ""; case LocoFunctionIconLeftRight: return "" "" ""; case LocoFunctionIconTurnLeft: return "" "" ""; case LocoFunctionIconTurnRight: return "" "" ""; case LocoFunctionIconTurn: return "" "" ""; case LocoFunctionIconCrane: return "" "" "" "" ""; case LocoFunctionIconMagnet: return "" "" "" "" "" ""; case LocoFunctionIconCraneHook: return "" "" ""; case LocoFunctionIconFan: return "" "" "" "" "" "" "" "" "" "" ""; case LocoFunctionIconBreak: return "" "" "" "" "" "" ""; case LocoFunctionIconNoSound: return "" "" "" "" "" "" "" "" ""; case LocoFunctionIconSoundGeneral: return "" "" "" "" "" "" ""; case LocoFunctionIconRunning1: return "" "" "" "" "" "" "" "" "1" "" "" "" "" ""; case LocoFunctionIconRunning2: return "" "" "" "" "" "" "" "" "2" "" "" "" "" ""; case LocoFunctionIconEngine1: return "" "" "" "M" "" "" "" "" "1" ""; case LocoFunctionIconEngine2: return "" "" "" "M" "" "" "" "" "2" ""; case LocoFunctionIconBreak1: return "" "" "" "" "" "" "" "" "" "1" ""; case LocoFunctionIconBreak2: return "" "" "" "" "" "" "" "" "" "2" ""; case LocoFunctionIconCurve: return "" "" "" "" "" "" "" "" "" "" "" "" ""; case LocoFunctionIconHorn1: return "" "" "" "" "" "1" ""; case LocoFunctionIconHorn2: return "" "" "" "" "" "2" ""; case LocoFunctionIconWhistle1: return "" "" "" "" "" "" ""; case LocoFunctionIconWhistle2: return "" "" "" "" "" ""; case LocoFunctionIconBell: return "" "" "" "" "" "" "" ""; case LocoFunctionIconStationAnnouncement1: return "" "" "" "" "" "1" ""; case LocoFunctionIconStationAnnouncement2: return "" "" "" "" "" "2" ""; case LocoFunctionIconStationAnnouncement3: return "" "" "" "" "" "3" ""; case LocoFunctionIconSpeak: return "" "" ""; case LocoFunctionIconRadio: return "" "" "" "" "" "" ""; case LocoFunctionIconMusic1: return "" "" "" "" "" "" "" "" "1" ""; case LocoFunctionIconMusic2: return "" "" "" "" "" "" "" "" "2" ""; case LocoFunctionIconOpenDoor: return "" "" "" ""; case LocoFunctionIconCloseDoor: return "" "" "" ""; case LocoFunctionIconFan1: return "" "" "" "" "" "" "1" "" "" "" "" ""; case LocoFunctionIconFan2: return "" "" "" "" "" "" "2" "" "" "" "" ""; case LocoFunctionIconFan3: return "" "" "" "" "" "" "3" "" "" "" "" ""; case LocoFunctionIconShovelCoal: return "" "" "" "" "" "" ""; case LocoFunctionIconCompressedAir: return "" "" "" "" "" "" "" "" ""; case LocoFunctionIconReliefValve: return "" "" "" "" "" "" "" "" "" ""; case LocoFunctionIconSteamBlowOut: return "" "" "" "" "" "" "" "" "" "" ""; case LocoFunctionIconSteamBlow: return "" "" "" "" "" "" "" "" "" "" ""; case LocoFunctionIconDrainValve: return "" "" "" "" "" "" "" "" "" ""; case LocoFunctionIconShakingRust: return "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""; case LocoFunctionIconAirPump: return "" "" "" "" "" "" "" "" "" ""; case LocoFunctionIconWaterPump: return "" "" "" "" "" "" "" "" "" "" ""; case LocoFunctionIconBufferPush: return "" "" "" "" "" "" "" "" ""; case LocoFunctionIconGenerator: return "" "" "" "" "" "" "" "" "" "" ""; case LocoFunctionIconGearBox: return "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""; case LocoFunctionIconGearUp: return "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""; case LocoFunctionIconGearDown: return "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""; case LocoFunctionIconFillWater: return "" "" "" "" "" "" "" "" "" "" ""; case LocoFunctionIconFillDiesel: return "" "" "" "" "" "" "" "" "" ""; case LocoFunctionIconFillGas: return "" "" "" "" "" "" "" "" "" "" "" ""; case LocoFunctionIconSand: return "" "" "" "" "" "" "" "" ""; case LocoFunctionIconRailJoint: return "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""; case LocoFunctionIconCoupler: return "" "" "" "" "" "" ""; case LocoFunctionIconPanto: return "" "" "" "" "" "" "" ""; case LocoFunctionIconMainSwitch: return "" "" "" "" "" "" ""; case LocoFunctionIconSoundLouder: return "" "" "" "" "" ""; case LocoFunctionIconSoundLower: return "" "" "" "" ""; case LocoFunctionIconNoBreak: return "" "" "" "" "" "" "" "" "" "" "" ""; default: return "" "F" + std::to_string(nr) + "" ""; } } } // namespace DataModel railcontrol-24+dfsg1/DataModel/LocoFunctions.h000066400000000000000000000170241500456250600214300ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "DataModel/Serializable.h" namespace DataModel { typedef uint16_t LocoFunctionNr; // 16 bit because icon is added with 256 to select f number and icon in same select dropdown box in relation. static const LocoFunctionNr NumberOfLocoFunctions = 33; // f0 - f32 = 33 enum LocoFunctionState : uint8_t { LocoFunctionStateOff = 0, LocoFunctionStateOn = 1, LocoFunctionState0s5 = 5, LocoFunctionState1s0 = 10, LocoFunctionState1s5 = 15, LocoFunctionState2s0 = 20, }; enum LocoFunctionType : uint8_t { LocoFunctionTypeNone = 0, LocoFunctionTypePermanent = 1, LocoFunctionTypeMoment = 2, LocoFunctionTypeFlashing = 3, // actually not implemented LocoFunctionTypeTimer = 4 // actually not implemented }; enum LocoFunctionIcon : uint16_t // 16 bit because icon is added with 256 to select f number and icon in same select dropdown box in relation. { // Do not change numbers! // Only add numbers! // If you add numbers, add them also in // - ProtocolMaerklin.cpp // - WebServer::WebClient::HtmlTagRelationObject // - WebServer::WebClient::HandleLocoEdit LocoFunctionIconNone = 0, LocoFunctionIconDefault = 1, // logical functions LocoFunctionIconShuntingMode = 2, LocoFunctionIconInertia, // light functions LocoFunctionIconLight = 32, LocoFunctionIconHeadlightLowBeamForward, LocoFunctionIconHeadlightLowBeamReverse, LocoFunctionIconHeadlightHighBeamForward, LocoFunctionIconHeadlightHighBeamReverse, LocoFunctionIconBacklightForward, LocoFunctionIconBacklightReverse, LocoFunctionIconShuntingLight, LocoFunctionIconBlinkingLight, LocoFunctionIconInteriorLight1, LocoFunctionIconInteriorLight2, LocoFunctionIconTableLight1, LocoFunctionIconTableLight2, LocoFunctionIconTableLight3, LocoFunctionIconCabLight1, LocoFunctionIconCabLight2, LocoFunctionIconCabLight12, LocoFunctionIconDriversDeskLight, LocoFunctionIconTrainDestinationIndicator, LocoFunctionIconLocomotiveNumberIndicator, LocoFunctionIconEngineLight, LocoFunctionIconFireBox, LocoFunctionIconStairsLight, // mechanical functions LocoFunctionIconSmokeGenerator = 64, LocoFunctionIconTelex1, LocoFunctionIconTelex2, LocoFunctionIconTelex12, LocoFunctionIconPanto1, LocoFunctionIconPanto2, LocoFunctionIconPanto12, LocoFunctionIconUp, LocoFunctionIconDown, LocoFunctionIconUpDown1, LocoFunctionIconUpDown2, LocoFunctionIconLeft, LocoFunctionIconRight, LocoFunctionIconLeftRight, LocoFunctionIconTurnLeft, LocoFunctionIconTurnRight, LocoFunctionIconTurn, LocoFunctionIconCrane, LocoFunctionIconMagnet, LocoFunctionIconCraneHook, LocoFunctionIconFan, LocoFunctionIconBreak, // sound functions LocoFunctionIconNoSound = 96, LocoFunctionIconSoundGeneral, LocoFunctionIconRunning1, LocoFunctionIconRunning2, LocoFunctionIconEngine1, LocoFunctionIconEngine2, LocoFunctionIconBreak1, LocoFunctionIconBreak2, LocoFunctionIconCurve, LocoFunctionIconHorn1, LocoFunctionIconHorn2, LocoFunctionIconWhistle1, LocoFunctionIconWhistle2, LocoFunctionIconBell, LocoFunctionIconStationAnnouncement1, LocoFunctionIconStationAnnouncement2, LocoFunctionIconStationAnnouncement3, LocoFunctionIconSpeak, LocoFunctionIconRadio, LocoFunctionIconMusic1, LocoFunctionIconMusic2, LocoFunctionIconOpenDoor, LocoFunctionIconCloseDoor, LocoFunctionIconFan1, LocoFunctionIconFan2, LocoFunctionIconFan3, LocoFunctionIconShovelCoal, LocoFunctionIconCompressedAir, LocoFunctionIconReliefValve, LocoFunctionIconSteamBlowOut, LocoFunctionIconSteamBlow, LocoFunctionIconDrainValve, LocoFunctionIconShakingRust, LocoFunctionIconAirPump, LocoFunctionIconWaterPump, LocoFunctionIconBufferPush, LocoFunctionIconGenerator, LocoFunctionIconGearBox, LocoFunctionIconGearUp, LocoFunctionIconGearDown, LocoFunctionIconFillWater, LocoFunctionIconFillDiesel, LocoFunctionIconFillGas, LocoFunctionIconSand, LocoFunctionIconRailJoint, LocoFunctionIconCoupler, LocoFunctionIconPanto, LocoFunctionIconMainSwitch, LocoFunctionIconSoundLouder, LocoFunctionIconSoundLower, LocoFunctionIconNoBreak, MaxLocoFunctionIcons }; typedef uint8_t LocoFunctionTimer; class LocoFunctionEntry { public: inline LocoFunctionEntry() : nr(0), state(LocoFunctionStateOff), type(LocoFunctionTypeNone), icon(LocoFunctionIconNone), timer(0) { } LocoFunctionNr nr; LocoFunctionState state; LocoFunctionType type; LocoFunctionIcon icon; LocoFunctionTimer timer; }; class LocoFunctions : private Serializable { public: LocoFunctions(); inline LocoFunctions(const std::string& serialized) : LocoFunctions() { Deserialize(serialized); } inline void SetFunctionState(const LocoFunctionNr nr, const LocoFunctionState state) { if (nr >= NumberOfLocoFunctions) { return; } entries[nr].state = state; } inline void SetFunction(const DataModel::LocoFunctionNr nr, const DataModel::LocoFunctionType type, const DataModel::LocoFunctionIcon icon, const DataModel::LocoFunctionTimer timer) { if (nr >= NumberOfLocoFunctions) { return; } LocoFunctionEntry& entry = entries[nr]; entry.type = type; entry.icon = icon; entry.timer = timer; } inline void ClearFunction(const DataModel::LocoFunctionNr nr) { if (nr >= NumberOfLocoFunctions) { return; } LocoFunctionEntry& entry = entries[nr]; entry.state = LocoFunctionStateOff; entry.type = LocoFunctionTypeNone; entry.icon = LocoFunctionIconNone; entry.timer = 0; } void ConfigureFunctions(const std::vector& newEntries); void SetFunctionStates(const std::vector& newEntries); inline LocoFunctionState GetFunctionState(const LocoFunctionNr nr) const { if (nr >= NumberOfLocoFunctions) { return LocoFunctionStateOff; } return entries[nr].state; } std::vector GetFunctionStates() const; inline void GetFunctions(LocoFunctionEntry* out) const { for (int nr = 0; nr < NumberOfLocoFunctions; ++nr) { out[nr] = entries[nr]; } } std::string Serialize() const override; bool Deserialize(const std::string& serialized) override; inline LocoFunctionIcon GetFunctionIcon(const LocoFunctionNr nr) const { if (nr >= NumberOfLocoFunctions) { return LocoFunctionIconNone; } return entries[nr].icon; } static std::string GetLocoFunctionIcon(const LocoFunctionNr nr, const LocoFunctionIcon icon); inline LocoFunctionType GetFunctionType(const LocoFunctionNr nr) const { if (nr >= NumberOfLocoFunctions) { return LocoFunctionTypeNone; } return entries[nr].type; } private: LocoFunctionEntry entries[NumberOfLocoFunctions]; }; } // namespace DataModel railcontrol-24+dfsg1/DataModel/MultipleUnit.cpp000066400000000000000000000056271500456250600216370ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include "Manager.h" #include "DataModel/MultipleUnit.h" #include #include using std::map; using std::string; namespace DataModel { std::string MultipleUnit::Serialize() const { string str = "objectType=MultipleUnit;"; str += LocoBase::Serialize(); return str; } bool MultipleUnit::Deserialize(const std::string& serialized) { map arguments; ParseArguments(serialized, arguments); if (!arguments.count("objectType") || arguments.at("objectType").compare("MultipleUnit") != 0) { return false; } LocoBase::Deserialize(arguments); return true; } void MultipleUnit::CalculatePropulsion() { uint8_t newPropulsion = PropulsionUnknown; for (auto slave : slaves) { Loco* loco = manager->GetLoco(slave->ObjectID2()); if (loco == nullptr) { continue; } const uint8_t locoPropulsion = loco->GetPropulsion(); newPropulsion |= locoPropulsion; } LocoBase::SetPropulsion(static_cast(newPropulsion)); } void MultipleUnit::DeleteSlaves() { while (slaves.size() > 0) { Relation* slave = slaves.back(); slaves.pop_back(); delete slave; } } bool MultipleUnit::AssignSlaves(const std::vector& newslaves) { DeleteSlaves(); slaves = newslaves; CalculatePropulsion(); return true; } void MultipleUnit::SetSpeed(const Speed speed) { LocoBase::SetSpeed(speed); for (auto slave : slaves) { manager->LocoBaseSpeed(ControlTypeInternal, slave->ObjectIdentifier2(), speed); Utils::Utils::SleepForMilliseconds(10); } } void MultipleUnit::SetFunctionState(const DataModel::LocoFunctionNr nr, const DataModel::LocoFunctionState state) { LocoBase::SetFunctionState(nr, state); for (auto slave : slaves) { manager->LocoBaseFunctionState(ControlTypeInternal, slave->ObjectIdentifier2(), nr, state); Utils::Utils::SleepForMilliseconds(10); } } void MultipleUnit::SetOrientation(const Orientation orientation) { LocoBase::SetOrientation(orientation); for (auto slave : slaves) { manager->LocoBaseOrientation(ControlTypeInternal, slave->ObjectIdentifier2(), OrientationChange); Utils::Utils::SleepForMilliseconds(10); } } } // namespace DataModel railcontrol-24+dfsg1/DataModel/MultipleUnit.h000066400000000000000000000045041500456250600212750ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include "DataTypes.h" #include "DataModel/LocoBase.h" #include "DataModel/Relation.h" namespace DataModel { class ObjectIdentifier; class MultipleUnit : public LocoBase { public: MultipleUnit() = delete; MultipleUnit(const MultipleUnit&) = delete; MultipleUnit& operator=(const MultipleUnit&) = delete; inline MultipleUnit(Manager* manager, const MultipleUnitID multipleUnitID) : LocoBase(manager, multipleUnitID) { } inline MultipleUnit(Manager* manager, const std::string& serialized) : LocoBase(manager, serialized) { MultipleUnit::Deserialize(serialized); } inline ~MultipleUnit() { DeleteSlaves(); } inline ObjectType GetObjectType() const override { return ObjectTypeMultipleUnit; } std::string Serialize() const override; bool Deserialize(const std::string& serialized) override; void CalculatePropulsion(); void DeleteSlaves(); bool AssignSlaves(const std::vector& newslaves); inline const std::vector& GetSlaves() const { return slaves; } inline const std::vector GetSlaveIDs() const { std::vector out; for (auto const & slave : slaves) { out.push_back(slave->ObjectID2()); } return out; } void SetSpeed(const Speed speed) override; void SetFunctionState(const DataModel::LocoFunctionNr nr, const DataModel::LocoFunctionState state) override; void SetOrientation(const Orientation orientation) override; private: std::vector slaves; }; } // namespace DataModel railcontrol-24+dfsg1/DataModel/Object.cpp000066400000000000000000000027451500456250600204100ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include #include "DataModel/Object.h" #include "Utils/Utils.h" using std::map; using std::stringstream; using std::string; namespace DataModel { std::string Object::Serialize() const { return "objectID=" + std::to_string(objectID) + ";name=" + Utils::Utils::UrlEncode(name); } bool Object::Deserialize(const std::string& serialized) { map arguments; ParseArguments(serialized, arguments); return Object::Deserialize(arguments); } bool Object::Deserialize(const map& arguments) { objectID = Utils::Utils::GetIntegerMapEntry(arguments, "objectID", ObjectNone); name = Utils::Utils::UrlDecode(Utils::Utils::GetStringMapEntry(arguments, "name")); return true; } } // namespace DataModel railcontrol-24+dfsg1/DataModel/Object.h000066400000000000000000000037031500456250600200500ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include "DataTypes.h" #include "DataModel/ObjectIdentifier.h" #include "DataModel/Serializable.h" namespace DataModel { class Object : protected Serializable { public: Object(const Object&) = delete; Object& operator=(const Object&) = delete; inline Object() : objectID(ObjectNone), name(std::to_string(objectID)) { } inline Object(const ObjectID objectID) : objectID(objectID), name(std::to_string(objectID)) { } virtual ~Object() { } virtual ObjectType GetObjectType() const = 0; virtual std::string Serialize() const override; virtual bool Deserialize(const std::string& serialized) override; inline ObjectID GetID() const { return objectID; } inline const ObjectIdentifier GetObjectIdentifier() const { return ObjectIdentifier(GetObjectType(), GetID()); } virtual inline void SetName(const std::string& name) { this->name = name; } inline const std::string& GetName() const { return name; } protected: virtual bool Deserialize(const std::map& arguments); private: ObjectID objectID; std::string name; }; } // namespace DataModel railcontrol-24+dfsg1/DataModel/ObjectIdentifier.cpp000066400000000000000000000146631500456250600224150ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include "DataModel/ObjectIdentifier.h" #include "Utils/Integer.h" #include "Utils/Utils.h" namespace DataModel { bool ObjectIdentifier::Deserialize(const std::map& arguments) { objectID = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "track", ObjectNone)); if (objectID != ObjectNone) { objectType = ObjectTypeTrack; return true; } objectID = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "signal", ObjectNone)); if (objectID != ObjectNone) { objectType = ObjectTypeSignal; return true; } objectID = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "switch", ObjectNone)); if (objectID != ObjectNone) { objectType = ObjectTypeSwitch; return true; } objectID = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "accessory", ObjectNone)); if (objectID != ObjectNone) { objectType = ObjectTypeAccessory; return true; } objectID = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "feedback", ObjectNone)); if (objectID != ObjectNone) { objectType = ObjectTypeFeedback; return true; } objectID = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "route", ObjectNone)); if (objectID != ObjectNone) { objectType = ObjectTypeRoute; return true; } objectID = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "text", ObjectNone)); if (objectID != ObjectNone) { objectType = ObjectTypeText; return true; } objectID = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "pause", ObjectNone)); if (objectID != ObjectNone) { objectType = ObjectTypePause; return true; } objectID = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "multipleunit", ObjectNone)); if (objectID != ObjectNone) { objectType = ObjectTypeMultipleUnit; return true; } objectID = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "booster", ObjectNone)); if (objectID != ObjectNone) { objectType = ObjectTypeBooster; return true; } objectType = ObjectTypeNone; return false; } ObjectIdentifier& ObjectIdentifier::operator=(const std::string& text) { if (text.substr(0, 5).compare("track") == 0) { objectType = ObjectTypeTrack; objectID = Utils::Integer::StringToInteger(text.substr(5), ObjectNone); return *this; } if (text.substr(0, 6).compare("signal") == 0) { objectType = ObjectTypeSignal; objectID = Utils::Integer::StringToInteger(text.substr(6), ObjectNone); return *this; } if (text.substr(0, 5).compare("route") == 0) { objectType = ObjectTypeRoute; objectID = Utils::Integer::StringToInteger(text.substr(5), ObjectNone); return *this; } if (text.substr(0, 4).compare("loco") == 0) { objectType = ObjectTypeLoco; objectID = Utils::Integer::StringToInteger(text.substr(4), ObjectNone); return *this; } if (text.substr(0, 8).compare("feedback") == 0) { objectType = ObjectTypeFeedback; objectID = Utils::Integer::StringToInteger(text.substr(8), ObjectNone); return *this; } if (text.substr(0, 9).compare("accessory") == 0) { objectType = ObjectTypeAccessory; objectID = Utils::Integer::StringToInteger(text.substr(9), ObjectNone); return *this; } if (text.substr(0, 6).compare("switch") == 0) { objectType = ObjectTypeSwitch; objectID = Utils::Integer::StringToInteger(text.substr(6), ObjectNone); return *this; } if (text.substr(0, 5).compare("layer") == 0) { objectType = ObjectTypeLayer; objectID = Utils::Integer::StringToInteger(text.substr(5), ObjectNone); return *this; } if (text.substr(0, 7).compare("cluster") == 0) { objectType = ObjectTypeCluster; objectID = Utils::Integer::StringToInteger(text.substr(7), ObjectNone); return *this; } if (text.substr(0, 9).compare("timetable") == 0) { objectType = ObjectTypeTimeTable; objectID = Utils::Integer::StringToInteger(text.substr(9), ObjectNone); return *this; } if (text.substr(0, 4).compare("text") == 0) { objectType = ObjectTypeText; objectID = Utils::Integer::StringToInteger(text.substr(4), ObjectNone); return *this; } if (text.substr(0, 5).compare("pause") == 0) { objectType = ObjectTypePause; objectID = Utils::Integer::StringToInteger(text.substr(5), ObjectNone); return *this; } if (text.substr(0, 12).compare("multipleunit") == 0) { objectType = ObjectTypeMultipleUnit; objectID = Utils::Integer::StringToInteger(text.substr(12), ObjectNone); return *this; } if (text.substr(0, 7).compare("booster") == 0) { objectType = ObjectTypeBooster; objectID = Utils::Integer::StringToInteger(text.substr(7), ObjectNone); return *this; } objectType = ObjectTypeTrack; objectID = Utils::Integer::StringToInteger(text, ObjectNone); return *this; } std::string ObjectIdentifier::ObjectTypeToString(const ObjectType objectType) { switch (objectType) { case ObjectTypeNone: return "none"; case ObjectTypeLoco: return "loco"; case ObjectTypeTrack: return "track"; case ObjectTypeFeedback: return "feedback"; case ObjectTypeAccessory: return "accessory"; case ObjectTypeSwitch: return "switch"; case ObjectTypeRoute: return "route"; case ObjectTypeLayer: return "layer"; case ObjectTypeSignal: return "signal"; case ObjectTypeCluster: return "cluster"; case ObjectTypeTimeTable: return "timetable"; case ObjectTypeText: return "text"; case ObjectTypePause: return "pause"; case ObjectTypeMultipleUnit: return "multipleunit"; case ObjectTypeBooster: return "booster"; } return "object"; } } // namespace DataModel railcontrol-24+dfsg1/DataModel/ObjectIdentifier.h000066400000000000000000000056711500456250600220610ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include "DataTypes.h" namespace DataModel { class ObjectIdentifier { public: inline ObjectIdentifier() : objectType(ObjectTypeNone), objectID(ObjectNone) { } inline ObjectIdentifier(const std::string& text) { *this = text; } inline ObjectIdentifier(const ObjectType objectType, const ObjectID objectID) : objectType(objectType), objectID(objectID) { } inline ObjectIdentifier(const std::map& arguments) { Deserialize(arguments); } inline ObjectIdentifier(const ObjectIdentifier& other) = default; inline std::string Serialize() const { return GetObjectTypeAsString() + "=" + GetObjectIdAsString(); } bool Deserialize(const std::map& arguments); inline ObjectIdentifier& operator=(const ObjectIdentifier& other) = default; inline void SetObjectType(const ObjectType objectType) { this->objectType = objectType; } inline void SetObjectID(const ObjectID objectID) { this->objectID = objectID; } ObjectIdentifier& operator=(const std::string& text); inline bool operator==(const ObjectIdentifier& other) const { return this->objectType == other.objectType && this->objectID == other.objectID; } inline bool operator!=(const ObjectIdentifier& other) const { return !(*this == other); } inline operator std::string() const { return GetObjectTypeAsString() + GetObjectIdAsString(); } inline void Clear() { objectType = ObjectTypeNone; objectID = ObjectNone; } inline bool IsSet() const { return (objectType != ObjectTypeNone) && (objectID != ObjectNone); } inline ObjectType GetObjectType() const { return objectType; } inline ObjectID GetObjectID() const { return objectID; } inline std::string GetObjectTypeAsString() const { return ObjectTypeToString(objectType); } inline std::string GetObjectIdAsString() const { return std::to_string(objectID); } static std::string ObjectTypeToString(const ObjectType objectType); private: ObjectType objectType; ObjectID objectID; }; } // namespace DataModel railcontrol-24+dfsg1/DataModel/Relation.cpp000066400000000000000000000157371500456250600207640ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include #include #include "DataModel/Relation.h" #include "Manager.h" #include "Utils/Utils.h" using std::map; using std::stringstream; using std::string; namespace DataModel { std::string Relation::Serialize() const { stringstream ss; ss << LockableItem::Serialize() << ";type=" << static_cast(type) << ";objectType1=" << static_cast(ObjectType1()) << ";objectID1=" << object1.GetObjectID() << ";objectType2=" << static_cast(ObjectType2()) << ";objectID2=" << object2.GetObjectID() << ";priority=" << static_cast(priority) << ";data=" << static_cast(data); return ss.str(); } bool Relation::Deserialize(const std::string& serialized) { map arguments; ParseArguments(serialized, arguments); LockableItem::Deserialize(arguments); object1.SetObjectType(static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "objectType1"))); type = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "type")); object1.SetObjectID(static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "objectID1"))); object2.SetObjectType(static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "objectType2"))); object2.SetObjectID(static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "objectID2"))); priority = Utils::Utils::GetIntegerMapEntry(arguments, "priority"); data = Utils::Utils::GetIntegerMapEntry(arguments, "data"); return true; } bool Relation::Execute(Logger::Logger* logger, const ObjectIdentifier& locoBaseIdentifier, const Delay delay) { switch (ObjectType2()) { case ObjectTypeAccessory: { bool ret = manager->AccessoryState(ControlTypeInternal, ObjectID2(), static_cast(data), true); if (ret == false) { return false; } break; } case ObjectTypeSwitch: { bool ret = manager->SwitchState(ControlTypeInternal, ObjectID2(), static_cast(data), true); if (ret == false) { return false; } break; } case ObjectTypeSignal: { bool ret = manager->SignalState(ControlTypeInternal, ObjectID2(), static_cast(data), true); if (ret == false) { return false; } break; } case ObjectTypeTrack: manager->TrackSetLocoOrientation(ObjectID2(), static_cast(data)); return true; case ObjectTypeRoute: return manager->RouteExecute(logger, locoBaseIdentifier, ObjectID2()); case ObjectTypeLoco: { const DataModel::LocoFunctionNr nr = static_cast(ObjectID2()); if (data > DataModel::LocoFunctionStateOn) { manager->LocoBaseFunctionState(ControlTypeInternal, locoBaseIdentifier, nr, DataModel::LocoFunctionStateOn); Utils::Utils::SleepForMilliseconds(static_cast(data) * 100); manager->LocoBaseFunctionState(ControlTypeInternal, locoBaseIdentifier, nr, DataModel::LocoFunctionStateOff); } else { manager->LocoBaseFunctionState(ControlTypeInternal, locoBaseIdentifier, nr, static_cast(data)); } return true; } case ObjectTypePause: Utils::Utils::SleepForMilliseconds(static_cast(data) * 100); return true; case ObjectTypeMultipleUnit: // abused for loco orientation manager->LocoBaseOrientation(ControlTypeInternal, locoBaseIdentifier, static_cast(data)); return true; case ObjectTypeBooster: manager->Booster(ControlTypeInternal, static_cast(data)); return true; default: return false; } Utils::Utils::SleepForMilliseconds(delay); return true; } LockableItem* Relation::GetObject2() { switch (ObjectType2()) { case ObjectTypeAccessory: return manager->GetAccessory(ObjectID2()); case ObjectTypeSwitch: return manager->GetSwitch(ObjectID2()); case ObjectTypeTrack: return manager->GetTrack(ObjectID2()); case ObjectTypeSignal: return manager->GetSignal(ObjectID2()); case ObjectTypeRoute: return manager->GetRoute(ObjectID2()); default: return nullptr; } } bool Relation::Reserve(Logger::Logger* logger, const ObjectIdentifier& locoBaseIdentifier) { bool ret = LockableItem::Reserve(logger, locoBaseIdentifier); if (ret == false) { logger->Debug(Languages::TextUnableToReserve); return false; } const ObjectType objectType2 = ObjectType2(); if (objectType2 == ObjectTypeLoco || objectType2 == ObjectTypePause || objectType2 == ObjectTypeMultipleUnit || objectType2 == ObjectTypeBooster) { return true; } LockableItem* lockable = GetObject2(); if (lockable == nullptr) { logger->Debug(Languages::TextRelationTargetNotFound); LockableItem::Release(logger, locoBaseIdentifier); return false; } Route* route = dynamic_cast(lockable); if (route != nullptr) { return route->Reserve(logger, locoBaseIdentifier); } return lockable->Reserve(logger, locoBaseIdentifier); } bool Relation::Lock(Logger::Logger* logger, const ObjectIdentifier& locoBaseIdentifier) { bool ret = LockableItem::Lock(logger, locoBaseIdentifier); if (ret == false) { return false; } const ObjectType objectType2 = ObjectType2(); if (objectType2 == ObjectTypeLoco || objectType2 == ObjectTypePause || objectType2 == ObjectTypeMultipleUnit || objectType2 == ObjectTypeBooster) { return true; } LockableItem* lockable = GetObject2(); if (lockable == nullptr) { LockableItem::Release(logger, locoBaseIdentifier); return false; } Route* route = dynamic_cast(lockable); if (route != nullptr) { return route->Lock(logger, locoBaseIdentifier); } bool retLockable = lockable->Lock(logger, locoBaseIdentifier); if (retLockable == true) { return true; } Object* object = dynamic_cast(lockable); if (object == nullptr) { return false; } logger->Debug(Languages::TextUnableToLock, object->GetName()); return false; } bool Relation::Release(Logger::Logger* logger, const ObjectIdentifier& locoBaseIdentifier) { LockableItem* object = GetObject2(); if (object != nullptr) { object->Release(logger, locoBaseIdentifier); } return LockableItem::Release(logger, locoBaseIdentifier); } } // namespace DataModel railcontrol-24+dfsg1/DataModel/Relation.h000066400000000000000000000075531500456250600204260ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include "DataModel/Accessory.h" #include "DataModel/LockableItem.h" #include "DataModel/ObjectIdentifier.h" #include "DataModel/Serializable.h" #include "DataTypes.h" #include "Logger/Logger.h" class Manager; namespace DataModel { class Relation : protected Serializable, public LockableItem { public: enum RelationType : unsigned char { RelationTypeCalculate = 0, RelationTypeLocoSlave = (ObjectTypeLoco << 3), // FIXME: 2024-03-17: not used anymore, TypeMultipeUnitSlave instead RelationTypeTrackSignal = (ObjectTypeTrack << 3), RelationTypeTrackFeedback = (ObjectTypeTrack << 3) + 1, RelationTypeFeedbackAtSet = (ObjectTypeFeedback << 3), RelationTypeFeedbackAtUnset = (ObjectTypeFeedback << 3) + 1, RelationTypeRouteAtLock = (ObjectTypeRoute << 3), RelationTypeRouteAtUnlock = (ObjectTypeRoute << 3) + 1, RelationTypeClusterTrack = (ObjectTypeCluster << 3), RelationTypeMultipleUnitLoco = (ObjectTypeMultipleUnit << 3), }; typedef unsigned short Data; static const Data DefaultData = 0; inline Relation(Manager* manager, const ObjectIdentifier& object1, const ObjectIdentifier& object2, const RelationType type, const Priority priority = 0, const Data data = 0) : manager(manager), object1(object1), object2(object2), type(type), priority(priority), data(data) { } inline Relation(Manager* manager, const std::string& serialized) : manager(manager), data(0) { Deserialize(serialized); } virtual ~Relation() { } virtual std::string Serialize() const override; virtual bool Deserialize(const std::string& serialized) override; inline ObjectID ObjectID1() const { return object1.GetObjectID(); } inline void ObjectID1(ObjectID objectID1) { object1.SetObjectID(objectID1); } inline ObjectType ObjectType2() const { return object2.GetObjectType(); } inline ObjectID ObjectID2() const { return object2.GetObjectID(); } inline ObjectIdentifier ObjectIdentifier2() const { return object2; } LockableItem* GetObject2(); inline RelationType GetType() const { return type; } inline Priority GetPriority() const { return priority; } inline Data GetData() const { return data; } inline bool CompareObject2(const ObjectIdentifier& identifier) const { return object2 == identifier; } bool Reserve(Logger::Logger* logger, const ObjectIdentifier& locoBaseIdentifier) override; bool Lock(Logger::Logger* logger, const ObjectIdentifier& locoBaseIdentifier) override; bool Release(Logger::Logger* logger, const ObjectIdentifier& locoBaseIdentifier) override; bool Execute(Logger::Logger* logger, const ObjectIdentifier& locoBaseIdentifier, const Delay delay); private: inline ObjectType ObjectType1() const { return object1.GetObjectType(); } Manager* manager; ObjectIdentifier object1; ObjectIdentifier object2; RelationType type; Priority priority; Data data; }; } // namespace DataModel railcontrol-24+dfsg1/DataModel/Route.cpp000066400000000000000000000271331500456250600202760ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include #include #include "DataModel/LocoBase.h" #include "DataModel/Relation.h" #include "DataModel/Route.h" #include "Manager.h" #include "Utils/Utils.h" using std::map; using std::string; using std::to_string; namespace DataModel { Route::Route(Manager* manager, const std::string& serialized) : LockableItem(), manager(manager), executeAtUnlock(false) { Deserialize(serialized); Track* track = manager->GetTrack(fromTrack); if (!track) { return; } track->AddRoute(this); } std::string Route::Serialize() const { std::string str; str = "objectType=Route;"; str += LayoutItem::Serialize(); str += ";" + LockableItem::Serialize(); str += ";delay=" + to_string(delay); str += ";lastused=" + to_string(lastUsed); str += ";counter=" + to_string(counter); str += ";automode=" + to_string(automode); if (automode == AutomodeNo) { return str; } str += ";fromTrack=" + to_string(fromTrack); str += ";fromorientation=" + to_string(fromOrientation); str += ";toTrack=" + to_string(toTrack); str += ";toorientation=" + to_string(toOrientation); str += ";speed=" + to_string(speed); str += ";feedbackIdReduced=" + to_string(feedbackIdReduced); str += ";reduceddelay=" + to_string(reducedDelay); str += ";feedbackIdCreep=" + to_string(feedbackIdCreep); str += ";creepdelay=" + to_string(creepDelay); str += ";feedbackIdStop=" + to_string(feedbackIdStop); str += ";stopdelay=" + to_string(stopDelay); str += ";feedbackIdOver=" + to_string(feedbackIdOver); str += ";pushpull=" + to_string(pushpull); str += ";propulsion=" + to_string(propulsion); str += ";traintype=" + to_string(trainType); str += ";mintrainlength=" + to_string(minTrainLength); str += ";maxtrainlength=" + to_string(maxTrainLength); str += ";waitafterrelease=" + to_string(waitAfterRelease); str += ";followuproute=" + to_string(followUpRoute); return str; } bool Route::Deserialize(const std::string& serialized) { map arguments; ParseArguments(serialized, arguments); string objectType = Utils::Utils::GetStringMapEntry(arguments, "objectType"); if (objectType.compare("Route") != 0) { return false; } LayoutItem::Deserialize(arguments); LockableItem::Deserialize(arguments); delay = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "delay", DefaultDelay)); lastUsed = Utils::Utils::GetIntegerMapEntry(arguments, "lastused", 0); counter = Utils::Utils::GetIntegerMapEntry(arguments, "counter", 0); automode = static_cast(Utils::Utils::GetBoolMapEntry(arguments, "automode", AutomodeNo)); if (automode == AutomodeNo) { fromTrack = TrackNone; fromOrientation = OrientationRight; toTrack = TrackNone; toOrientation = OrientationRight; speed = SpeedTravel; feedbackIdReduced = FeedbackNone; reducedDelay = 0; feedbackIdCreep = FeedbackNone; creepDelay = 0; feedbackIdStop = FeedbackNone; stopDelay = 0; feedbackIdOver = FeedbackNone; pushpull = PushpullTypeBoth; propulsion = PropulsionAll; trainType = TrainTypeAll; minTrainLength = 0; maxTrainLength = 0; waitAfterRelease = 0; followUpRoute = RouteNone; return true; } fromTrack = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "fromTrack", TrackNone)); fromOrientation = static_cast(Utils::Utils::GetBoolMapEntry(arguments, "fromorientation", OrientationRight)); toTrack = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "toTrack", TrackNone)); toOrientation = static_cast(Utils::Utils::GetBoolMapEntry(arguments, "toorientation", OrientationRight)); speed = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "speed", SpeedTravel)); feedbackIdReduced = Utils::Utils::GetIntegerMapEntry(arguments, "feedbackIdReduced", FeedbackNone); reducedDelay = Utils::Utils::GetIntegerMapEntry(arguments, "reduceddelay", 0); feedbackIdCreep = Utils::Utils::GetIntegerMapEntry(arguments, "feedbackIdCreep", FeedbackNone); creepDelay = Utils::Utils::GetIntegerMapEntry(arguments, "creepdelay", 0); feedbackIdStop = Utils::Utils::GetIntegerMapEntry(arguments, "feedbackIdStop", FeedbackNone); stopDelay = Utils::Utils::GetIntegerMapEntry(arguments, "stopdelay", 0); feedbackIdOver = Utils::Utils::GetIntegerMapEntry(arguments, "feedbackIdOver", FeedbackNone); pushpull = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "pushpull", PushpullTypeBoth)); propulsion = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "propulsion", propulsion)); trainType = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "traintype", trainType)); minTrainLength = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "mintrainlength", 0)); maxTrainLength = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "maxtrainlength", 0)); waitAfterRelease = Utils::Utils::GetIntegerMapEntry(arguments, "waitafterrelease", 0); followUpRoute = Utils::Utils::GetIntegerMapEntry(arguments, "followuproute", RouteNone); return true; } void Route::DeleteRelations(std::vector& relations) { while (!relations.empty()) { delete relations.back(); relations.pop_back(); } } bool Route::AssignRelations(std::vector& relations, const std::vector& newRelations) { std::lock_guard Guard(updateMutex); if (GetLockState() != LockStateFree) { return false; } DeleteRelations(relations); relations = newRelations; return true; } bool Route::FromTrackOrientation(Logger::Logger* logger, const TrackID trackID, const Orientation trackOrientation, const LocoBase* locoBase, const bool allowLocoTurn) { if (!automode) { return false; } if (fromTrack != trackID) { return false; } const Length locoLength = locoBase->GetLength(); if (locoLength < minTrainLength) { logger->Debug(Languages::TextTrainIsToShort, GetName()); return false; } if (maxTrainLength > 0 && locoLength > maxTrainLength) { logger->Debug(Languages::TextTrainIsToLong, GetName()); return false; } const bool locoPushpull = locoBase->GetPushpull(); if (pushpull != locoPushpull && pushpull != PushpullTypeBoth) { logger->Debug(Languages::TextDifferentPushpullTypes, GetName()); return false; } const Propulsion locoPropulsion = locoBase->GetPropulsion(); if ((propulsion & locoPropulsion) != locoPropulsion) { logger->Debug(Languages::TextDifferentPropulsions, GetName()); return false; } const TrainType locoTrainType = locoBase->GetTrainType(); if (!(trainType & locoTrainType)) { logger->Debug(Languages::TextDifferentTrainTypes, GetName()); return false; } if (fromOrientation == trackOrientation) { return true; } if (allowLocoTurn && locoPushpull) { Track* trackBase = manager->GetTrack(fromTrack); if (trackBase != nullptr) { bool allowTrackTurn = trackBase->GetAllowLocoTurn(); if (allowTrackTurn == true) { return true; } } } logger->Debug(Languages::TextDifferentOrientations, GetName()); return false; } bool Route::Execute(Logger::Logger* logger, const ObjectIdentifier& locoBaseIdentifier) { const bool isInUse = IsInUse(); if (isInUse && (locoBaseIdentifier != GetLocoBase())) { logger->Info(Languages::TextRouteIsLocked, GetName()); return false; } std::lock_guard Guard(updateMutex); for (auto relation : relationsAtLock) { bool retRelation = relation->Execute(logger, locoBaseIdentifier, delay); if (!retRelation) { return false; } } lastUsed = std::time(nullptr); ++counter; if (isInUse) { executeAtUnlock = true; } return true; } bool Route::Reserve(Logger::Logger* logger, const ObjectIdentifier& locoBaseIdentifier) { if (manager->Booster() == BoosterStateStop) { logger->Debug(Languages::TextBoosterIsTurnedOff); return false; } std::lock_guard Guard(updateMutex); bool ret = LockableItem::Reserve(logger, locoBaseIdentifier); if (!ret) { return false; } if (automode == AutomodeYes) { Track* track = manager->GetTrack(toTrack); if (!track || !track->CanSetLocoBaseOrientation(toOrientation) || !track->Reserve(logger, locoBaseIdentifier) || !track->SetLocoBaseOrientation(toOrientation)) { logger->Debug(Languages::TextUnableToReserveRouteToTrack, GetName(), track->GetName()); ReleaseInternal(logger, locoBaseIdentifier); return false; } } for (auto relation : relationsAtLock) { bool retRelation = relation->Reserve(logger, locoBaseIdentifier); if (!retRelation) { const LockableItem* item = relation->GetObject2(); const Object* object = dynamic_cast(item); const string objectName = object ? object->GetName() : Languages::GetText(Languages::TextUnknownElement); logger->Debug(Languages::TextUnableToReserveRouteElement, GetName(), objectName); ReleaseInternalWithToTrack(logger, locoBaseIdentifier); return false; } } return true; } bool Route::Lock(Logger::Logger* logger, const ObjectIdentifier& locoBaseIdentifier) { if (manager->Booster() == BoosterStateStop) { logger->Debug(Languages::TextBoosterIsTurnedOff); return false; } std::lock_guard Guard(updateMutex); bool ret = LockableItem::Lock(logger, locoBaseIdentifier); if (!ret) { return false; } if (automode == AutomodeYes) { Track* track = manager->GetTrack(toTrack); if (!track || !track->Lock(logger, locoBaseIdentifier)) { ReleaseInternal(logger, locoBaseIdentifier); return false; } } for (auto relation : relationsAtLock) { bool retRelation = relation->Lock(logger, locoBaseIdentifier); if (!retRelation) { ReleaseInternalWithToTrack(logger, locoBaseIdentifier); return false; } } return true; } bool Route::ReleaseInternal(Logger::Logger* logger, const ObjectIdentifier& locoBaseIdentifier) { if (executeAtUnlock) { for (auto relation : relationsAtUnlock) { relation->Execute(logger, locoBaseIdentifier, delay); } executeAtUnlock = false; } for (auto relation : relationsAtLock) { relation->Release(logger, locoBaseIdentifier); } return LockableItem::Release(logger, locoBaseIdentifier); } void Route::ReleaseInternalWithToTrack(Logger::Logger* logger, const ObjectIdentifier& locoBaseIdentifier) { Track* track = manager->GetTrack(toTrack); if (track) { track->Release(logger, locoBaseIdentifier); } ReleaseInternal(logger, locoBaseIdentifier); } bool Route::ObjectIsPartOfRoute(const ObjectIdentifier& identifier) const { for (auto relation : relationsAtLock) { if (relation->CompareObject2(identifier)) { return true; } } for (auto relation : relationsAtUnlock) { if (relation->CompareObject2(identifier)) { return true; } } return false; } } // namespace DataModel railcontrol-24+dfsg1/DataModel/Route.h000066400000000000000000000226271500456250600177460ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include #include "DataTypes.h" #include "DataModel/LayoutItem.h" #include "DataModel/LockableItem.h" #include "DataModel/ObjectIdentifier.h" #include "Logger/Logger.h" class Manager; namespace DataModel { class LocoBase; class Relation; class Route : public LayoutItem, public LockableItem { public: static const Delay DefaultDelay = 250; enum PushpullType : unsigned char { PushpullTypeNo = 0, PushpullTypeOnly = 1, PushpullTypeBoth = 2 }; enum Speed : unsigned char { SpeedMax = 3, SpeedTravel = 2, SpeedReduced = 1, SpeedCreeping = 0 }; Route() = delete; Route(Manager* manager, const RouteID routeID) : LayoutItem(routeID), LockableItem(), manager(manager), executeAtUnlock(false), delay(0), pushpull(PushpullTypeBoth), propulsion(PropulsionUnknown), trainType(TrainTypeAll), minTrainLength(0), maxTrainLength(0), automode(AutomodeNo), fromTrack(), fromOrientation(OrientationRight), toTrack(), toOrientation(OrientationRight), speed(SpeedTravel), feedbackIdReduced(FeedbackNone), reducedDelay(0), feedbackIdCreep(FeedbackNone), creepDelay(0), feedbackIdStop(FeedbackNone), stopDelay(0), feedbackIdOver(FeedbackNone), waitAfterRelease(0), lastUsed(0), counter(0) { } Route(Manager* manager, const std::string& serialized); inline ~Route() { DeleteRelations(relationsAtLock); DeleteRelations(relationsAtUnlock); } inline ObjectType GetObjectType() const override { return ObjectTypeRoute; } std::string Serialize() const override; bool Deserialize(const std::string& serialized) override; inline std::string GetLayoutType() const override { return Languages::GetText(Languages::TextRoute); } inline void DeleteRelationsAtLock() { DeleteRelations(relationsAtLock); } inline void DeleteRelationsAtUnlock() { DeleteRelations(relationsAtUnlock); } inline bool AssignRelationsAtLock(const std::vector& newRelations) { return AssignRelations(relationsAtLock, newRelations); } inline bool AssignRelationsAtUnlock(const std::vector& newRelations) { return AssignRelations(relationsAtUnlock, newRelations); } inline const std::vector& GetRelationsAtLock() const { return relationsAtLock; } inline const std::vector& GetRelationsAtUnlock() const { return relationsAtUnlock; } bool FromTrackOrientation(Logger::Logger* logger, const TrackID trackID, const Orientation trackOrientation, const DataModel::LocoBase* loco, const bool allowLocoTurn); bool Execute(Logger::Logger* logger, const ObjectIdentifier& locoBaseIdentifier); inline static bool ExecuteStatic(Logger::Logger* logger, Route* route) { return route->Execute(logger, ObjectIdentifier()); } bool Reserve(Logger::Logger* logger, const ObjectIdentifier& locoBaseIdentifier) override; bool Lock(Logger::Logger* logger, const ObjectIdentifier& locoBaseIdentifier) override; bool Release(Logger::Logger* logger, const ObjectIdentifier& locoBaseIdentifier) override { std::lock_guard Guard(updateMutex); return ReleaseInternal(logger, locoBaseIdentifier); } inline Delay GetDelay() const { return delay; } inline void SetDelay(Delay delay) { this->delay = delay; } inline PushpullType GetPushpull() const { return pushpull; } inline void SetPushpull(const PushpullType pushpull) { this->pushpull = pushpull; } inline Propulsion GetPropulsion() const { return propulsion; } inline void SetPropulsion(const Propulsion propulsion) { this->propulsion = propulsion; } inline TrainType GetTrainType() const { return trainType; } inline void SetTrainType(const TrainType trainType) { this->trainType = trainType; } inline Length GetMinTrainLength() const { return minTrainLength; } inline void SetMinTrainLength(const Length length) { this->minTrainLength = length; } inline Length GetMaxTrainLength() const { return maxTrainLength; } inline void SetMaxTrainLength(const Length length) { this->maxTrainLength = length; } inline time_t GetLastUsed() const { return lastUsed; } inline void SetAutomode(const Automode automode) { this->automode = automode; } inline Automode GetAutomode() const { return automode; } inline void SetFromTrack(const TrackID fromTrack) { this->fromTrack = fromTrack; } inline TrackID GetFromTrack() const { return fromTrack; } inline void SetFromOrientation(const Orientation fromOrientation) { this->fromOrientation = fromOrientation; } inline Orientation GetFromOrientation() const { return fromOrientation; } inline void SetToTrack(const TrackID toTrack) { this->toTrack = toTrack; } inline TrackID GetToTrack() const { return toTrack; } inline void SetToOrientation(const Orientation toOrientation) { this->toOrientation = toOrientation; } inline Orientation GetToOrientation() const { return toOrientation; } inline void SetSpeed(Speed startSpeed) { this->speed = startSpeed; } inline Speed GetSpeed() const { return speed; } inline void SetFeedbackIdReduced(const FeedbackID feedbackIdReduced) { this->feedbackIdReduced = feedbackIdReduced; } inline FeedbackID GetFeedbackIdReduced() const { return feedbackIdReduced; } inline void SetReducedDelay(const Delay reducedDelay) { this->reducedDelay = reducedDelay; } inline Delay GetReducedDelay() const { return reducedDelay; } inline void SetFeedbackIdCreep(const FeedbackID feedbackIdCreep) { this->feedbackIdCreep = feedbackIdCreep; } inline FeedbackID GetFeedbackIdCreep() const { return feedbackIdCreep; } inline void SetCreepDelay(const Delay creepDelay) { this->creepDelay = creepDelay; } inline Delay GetCreepDelay() const { return creepDelay; } inline void SetFeedbackIdStop(const FeedbackID feedbackIdStop) { this->feedbackIdStop = feedbackIdStop; } inline FeedbackID GetFeedbackIdStop() const { return feedbackIdStop; } inline void SetStopDelay(const Delay stopDelay) { this->stopDelay = stopDelay; } inline Delay GetStopDelay() const { return stopDelay; } inline void SetFeedbackIdOver(const FeedbackID feedbackIdOver) { this->feedbackIdOver = feedbackIdOver; } inline FeedbackID GetFeedbackIdOver() const { return feedbackIdOver; } inline void SetWaitAfterRelease(const Pause wait) { this->waitAfterRelease = wait; } inline Pause GetWaitAfterRelease() const { return waitAfterRelease; } inline void SetFollowUpRoute(const RouteID followUpRoute) { this->followUpRoute = followUpRoute; } inline RouteID GetFollowUpRoute() const { return followUpRoute; } static inline bool CompareShortest(const Route* s1, const Route* s2) { return s1->GetMinTrainLength() < s2->GetMinTrainLength(); } static inline bool CompareLastUsed(const Route* s1, const Route* s2) { return s1->GetLastUsed() < s2->GetLastUsed(); } bool ObjectIsPartOfRoute(const ObjectIdentifier& identifier) const; private: bool ReleaseInternal(Logger::Logger* logger, const ObjectIdentifier& locoBaseIdentifier); void ReleaseInternalWithToTrack(Logger::Logger* logger, const ObjectIdentifier& locoBaseIdentifier); static void DeleteRelations(std::vector& relations); bool AssignRelations(std::vector& relations, const std::vector& newRelations); Manager* manager; std::mutex updateMutex; bool executeAtUnlock; Delay delay; std::vector relationsAtLock; std::vector relationsAtUnlock; PushpullType pushpull; Propulsion propulsion; TrainType trainType; Length minTrainLength; Length maxTrainLength; Automode automode; TrackID fromTrack; Orientation fromOrientation; TrackID toTrack; Orientation toOrientation; Speed speed; FeedbackID feedbackIdReduced; Delay reducedDelay; FeedbackID feedbackIdCreep; Delay creepDelay; FeedbackID feedbackIdStop; Delay stopDelay; FeedbackID feedbackIdOver; Pause waitAfterRelease; RouteID followUpRoute; time_t lastUsed; unsigned int counter; }; } // namespace DataModel railcontrol-24+dfsg1/DataModel/Serializable.cpp000066400000000000000000000025121500456250600216000ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include "DataModel/Serializable.h" #include "Utils/Utils.h" using std::deque; using std::string; using std::map; namespace DataModel { void Serializable::ParseArguments(const string& serialized, map& arguments) { deque parts; Utils::Utils::SplitString(serialized, ";", parts); for (auto& part : parts) { if (part.length() == 0) { continue; } deque keyValue; Utils::Utils::SplitString(part, "=", keyValue); if (keyValue.size() < 2) { continue; } string value = keyValue[1]; arguments[keyValue[0]] = value; } } } railcontrol-24+dfsg1/DataModel/Serializable.h000066400000000000000000000021601500456250600212440ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include namespace DataModel { class Serializable { public: virtual ~Serializable() {}; virtual std::string Serialize() const = 0; virtual bool Deserialize(const std::string& serialized) = 0; protected: static void ParseArguments(const std::string& serialized, std::map& arguments); }; } // namespace DataModel railcontrol-24+dfsg1/DataModel/Signal.cpp000066400000000000000000000314261500456250600204150ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include "DataModel/Signal.h" #include "Manager.h" #include "Utils/Utils.h" using std::map; using std::string; namespace DataModel { void Signal::SetAccessoryType(AccessoryType type) { AccessoryBase::SetAccessoryType(type); ResetStateAddressMap(); } ObjectType Signal::GetObjectType() const { return ObjectTypeSignal; } std::string Signal::GetLayoutType() const { return Languages::GetText(Languages::TextSignal); } std::string Signal::Serialize() const { string str = "objectType=Signal;"; str += AccessoryBase::Serialize(); str += ";"; str += LayoutItem::Serialize(); str += ";"; str += LockableItem::Serialize(); for (auto& stateAddress : stateAddressMap) { str += ";address"; str += std::to_string(stateAddress.first); str += "="; str += std::to_string(stateAddress.second); } return str; } bool Signal::Deserialize(const std::string& serialized) { map arguments; ParseArguments(serialized, arguments); string objectType = Utils::Utils::GetStringMapEntry(arguments, "objectType"); if (objectType.compare("Signal") != 0) { return false; } AccessoryBase::Deserialize(arguments); LayoutItem::Deserialize(arguments); LockableItem::Deserialize(arguments); SetWidth(Width1); SetVisible(VisibleYes); for (int i = 0; i <= SignalStateMax; ++i) { int address = Utils::Utils::GetIntegerMapEntry(arguments, "address" + std::to_string(i), -1); if (address == -1) { continue; } SetStateAddressOffset(static_cast(i), address); } return true; } Signal& Signal::operator=(const Hardware::AccessoryCacheEntry& accessory) { SetControlID(accessory.GetControlID()); SetName(accessory.GetName()); SetAddress(accessory.GetAddress()); SetProtocol(accessory.GetProtocol()); SetMatchKey(accessory.GetMatchKey()); return *this; } void Signal::ResetStateAddressMap() { stateAddressMap.clear(); switch(GetAccessoryType()) { case SignalTypeChDwarf: SetStateAddressOffset(SignalStateStop, 0); SetStateAddressOffset(SignalStateClear, 1); SetStateAddressOffset(SignalStateStopExpected, 2); break; case SignalTypeChLMain: SetStateAddressOffset(SignalStateStop, 0); SetStateAddressOffset(SignalStateClear, 1); SetStateAddressOffset(SignalStateAspect2, 2); SetStateAddressOffset(SignalStateAspect3, 3); SetStateAddressOffset(SignalStateAspect5, 4); SetStateAddressOffset(SignalStateAspect6, 5); break; case SignalTypeChLDistant: SetStateAddressOffset(SignalStateStopExpected, 0); SetStateAddressOffset(SignalStateClearExpected, 1); SetStateAddressOffset(SignalStateAspect2Expected, 2); SetStateAddressOffset(SignalStateAspect3Expected, 3); SetStateAddressOffset(SignalStateAspect5Expected, 4); break; case SignalTypeChNMain: SetStateAddressOffset(SignalStateStop, 0); SetStateAddressOffset(SignalStateClear, 1); SetStateAddressOffset(SignalStateAspect2, 2); SetStateAddressOffset(SignalStateAspect3, 3); SetStateAddressOffset(SignalStateAspect4, 4); SetStateAddressOffset(SignalStateAspect5, 5); SetStateAddressOffset(SignalStateAspect6, 6); SetStateAddressOffset(SignalStateAspect7, 7); SetStateAddressOffset(SignalStateAspect8, 8); SetStateAddressOffset(SignalStateAspect9, 9); SetStateAddressOffset(SignalStateAspect10, 10); SetStateAddressOffset(SignalStateStopExpected, 11); SetStateAddressOffset(SignalStateAspect2Expected, 12); SetStateAddressOffset(SignalStateAspect3Expected, 13); SetStateAddressOffset(SignalStateAspect4Expected, 14); SetStateAddressOffset(SignalStateAspect5Expected, 15); SetStateAddressOffset(SignalStateAspect6Expected, 16); SetStateAddressOffset(SignalStateAspect7Expected, 17); SetStateAddressOffset(SignalStateAspect8Expected, 18); SetStateAddressOffset(SignalStateAspect9Expected, 19); SetStateAddressOffset(SignalStateAspect10Expected, 20); break; case SignalTypeDeCombined: SetStateAddressOffset(SignalStateStop, 0); SetStateAddressOffset(SignalStateClear, 1); SetStateAddressOffset(SignalStateStopExpected, 2); SetStateAddressOffset(SignalStateAspect4, 3); SetStateAddressOffset(SignalStateAspect7, 4); SetStateAddressOffset(SignalStateDark, 5); break; case SignalTypeDeHVMain: SetStateAddressOffset(SignalStateStop, 0); SetStateAddressOffset(SignalStateClear, 1); SetStateAddressOffset(SignalStateAspect2, 2); SetStateAddressOffset(SignalStateAspect3, 3); break; case SignalTypeSimpleLeft: case SignalTypeSimpleRight: case SignalTypeDeBlock: default: SetStateAddressOffset(SignalStateStop, 0); SetStateAddressOffset(SignalStateClear, 1); break; } } bool Signal::UsesAddress(Address address) const { const Address baseAddress = GetAddress(); for (auto offset : stateAddressMap) { const Address addressOffset = offset.second >> 1; if ((baseAddress + addressOffset) == address) { return true; } } return false; } std::map Signal::GetStateOptions() const { std::map out; switch(GetAccessoryType()) { case SignalTypeChDwarf: out.emplace(SignalStateStop, StateOption(Languages::TextSignalStateStop, GetStateAddressOffset(SignalStateStop))); out.emplace(SignalStateClear, StateOption(Languages::TextSignalStateClear, GetStateAddressOffset(SignalStateClear))); out.emplace(SignalStateStopExpected, StateOption(Languages::TextSignalStateCaution, GetStateAddressOffset(SignalStateStopExpected))); break; case SignalTypeChLMain: out.emplace(SignalStateStop, StateOption(Languages::TextSignalStateStop, GetStateAddressOffset(SignalStateStop))); out.emplace(SignalStateClear, StateOption(Languages::TextSignalStateClear, GetStateAddressOffset(SignalStateClear))); out.emplace(SignalStateAspect2, StateOption(Languages::TextSignalStateClear40, GetStateAddressOffset(SignalStateAspect2))); out.emplace(SignalStateAspect3, StateOption(Languages::TextSignalStateClear60, GetStateAddressOffset(SignalStateAspect3))); out.emplace(SignalStateAspect5, StateOption(Languages::TextSignalStateClear90, GetStateAddressOffset(SignalStateAspect5))); out.emplace(SignalStateAspect6, StateOption(Languages::TextSignalStateShortClear, GetStateAddressOffset(SignalStateAspect6))); break; case SignalTypeChLDistant: out.emplace(SignalStateStopExpected, StateOption(Languages::TextSignalStateStopExpected, GetStateAddressOffset(SignalStateStopExpected))); out.emplace(SignalStateClearExpected, StateOption(Languages::TextSignalStateClearExpected, GetStateAddressOffset(SignalStateClearExpected))); out.emplace(SignalStateAspect2Expected, StateOption(Languages::TextSignalStateClear40Expected, GetStateAddressOffset(SignalStateAspect2Expected))); out.emplace(SignalStateAspect3Expected, StateOption(Languages::TextSignalStateClear60Expected, GetStateAddressOffset(SignalStateAspect3Expected))); out.emplace(SignalStateAspect5Expected, StateOption(Languages::TextSignalStateClear90Expected, GetStateAddressOffset(SignalStateAspect5Expected))); break; case SignalTypeChNMain: out.emplace(SignalStateStop, StateOption(Languages::TextSignalStateStop, GetStateAddressOffset(SignalStateStop))); out.emplace(SignalStateClear, StateOption(Languages::TextSignalStateClear, GetStateAddressOffset(SignalStateClear))); out.emplace(SignalStateAspect2, StateOption(Languages::TextSignalStateClear40, GetStateAddressOffset(SignalStateAspect2))); out.emplace(SignalStateAspect3, StateOption(Languages::TextSignalStateClear50, GetStateAddressOffset(SignalStateAspect3))); out.emplace(SignalStateAspect4, StateOption(Languages::TextSignalStateClear60, GetStateAddressOffset(SignalStateAspect4))); out.emplace(SignalStateAspect5, StateOption(Languages::TextSignalStateClear70, GetStateAddressOffset(SignalStateAspect5))); out.emplace(SignalStateAspect6, StateOption(Languages::TextSignalStateClear80, GetStateAddressOffset(SignalStateAspect6))); out.emplace(SignalStateAspect7, StateOption(Languages::TextSignalStateClear90, GetStateAddressOffset(SignalStateAspect7))); out.emplace(SignalStateAspect8, StateOption(Languages::TextSignalStateClear100, GetStateAddressOffset(SignalStateAspect8))); out.emplace(SignalStateAspect9, StateOption(Languages::TextSignalStateClear110, GetStateAddressOffset(SignalStateAspect9))); out.emplace(SignalStateAspect10, StateOption(Languages::TextSignalStateClear120, GetStateAddressOffset(SignalStateAspect10))); out.emplace(SignalStateStopExpected, StateOption(Languages::TextSignalStateStopExpected, GetStateAddressOffset(SignalStateStopExpected))); out.emplace(SignalStateAspect2Expected, StateOption(Languages::TextSignalStateClear40Expected, GetStateAddressOffset(SignalStateAspect2Expected))); out.emplace(SignalStateAspect3Expected, StateOption(Languages::TextSignalStateClear50Expected, GetStateAddressOffset(SignalStateAspect3Expected))); out.emplace(SignalStateAspect4Expected, StateOption(Languages::TextSignalStateClear60Expected, GetStateAddressOffset(SignalStateAspect4Expected))); out.emplace(SignalStateAspect5Expected, StateOption(Languages::TextSignalStateClear70Expected, GetStateAddressOffset(SignalStateAspect5Expected))); out.emplace(SignalStateAspect6Expected, StateOption(Languages::TextSignalStateClear80Expected, GetStateAddressOffset(SignalStateAspect6Expected))); out.emplace(SignalStateAspect7Expected, StateOption(Languages::TextSignalStateClear90Expected, GetStateAddressOffset(SignalStateAspect7Expected))); out.emplace(SignalStateAspect8Expected, StateOption(Languages::TextSignalStateClear100Expected, GetStateAddressOffset(SignalStateAspect8Expected))); out.emplace(SignalStateAspect9Expected, StateOption(Languages::TextSignalStateClear110Expected, GetStateAddressOffset(SignalStateAspect9Expected))); out.emplace(SignalStateAspect10Expected, StateOption(Languages::TextSignalStateClear120Expected, GetStateAddressOffset(SignalStateAspect10Expected))); break; case SignalTypeDeCombined: out.emplace(SignalStateStop, StateOption(Languages::TextSignalStateStop, GetStateAddressOffset(SignalStateStop))); out.emplace(SignalStateClear, StateOption(Languages::TextSignalStateClear, GetStateAddressOffset(SignalStateClear))); out.emplace(SignalStateStopExpected, StateOption(Languages::TextSignalStateStopExpected, GetStateAddressOffset(SignalStateStopExpected))); out.emplace(SignalStateAspect4, StateOption(Languages::TextSignalStateShunting, GetStateAddressOffset(SignalStateAspect4))); out.emplace(SignalStateAspect7, StateOption(Languages::TextSignalStateZs7, GetStateAddressOffset(SignalStateAspect7))); out.emplace(SignalStateDark, StateOption(Languages::TextSignalStateDark, GetStateAddressOffset(SignalStateDark))); break; case SignalTypeDeHVMain: out.emplace(SignalStateStop, StateOption(Languages::TextSignalStateStop, GetStateAddressOffset(SignalStateStop))); out.emplace(SignalStateClear, StateOption(Languages::TextSignalStateClear, GetStateAddressOffset(SignalStateClear))); out.emplace(SignalStateAspect2, StateOption(Languages::TextSignalStateSlow, GetStateAddressOffset(SignalStateAspect2))); out.emplace(SignalStateAspect3, StateOption(Languages::TextSignalStateShunting, GetStateAddressOffset(SignalStateAspect3))); break; case SignalTypeSimpleLeft: case SignalTypeSimpleRight: case SignalTypeDeBlock: default: out.emplace(SignalStateStop, StateOption(Languages::TextSignalStateStop, GetStateAddressOffset(SignalStateStop))); out.emplace(SignalStateClear, StateOption(Languages::TextSignalStateClear, GetStateAddressOffset(SignalStateClear))); break; } return out; } AccessoryState Signal::CalculateMappedSignalState(const Address address, const AccessoryState state) const { const Address baseAddress = GetAddress(); const AddressOffset addressOffset = ((address - baseAddress) << 1) + state; for (auto& state : stateAddressMap) { if (state.second == addressOffset) { return state.first; } } return InvalidState; } } // namespace DataModel railcontrol-24+dfsg1/DataModel/Signal.h000066400000000000000000000074731500456250600200670ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include "DataModel/AccessoryBase.h" #include "DataModel/LayoutItem.h" #include "DataModel/LockableItem.h" #include "DataModel/Track.h" #include "DataTypes.h" #include "Languages.h" class Manager; namespace DataModel { class Signal : public AccessoryBase, public LayoutItem, public LockableItem { public: class StateOption { public: StateOption() = delete; StateOption& operator=(const StateOption&) = delete; inline StateOption(const Languages::TextSelector text, const AddressOffset addressOffset) : text(text), addressOffset(addressOffset) { } inline StateOption(const StateOption&) = default; inline operator Languages::TextSelector() const { return text; } const Languages::TextSelector text; const AddressOffset addressOffset; }; Signal() = delete; Signal(const Signal&) = delete; Signal& operator=(const Signal&) = delete; inline Signal(__attribute__((unused)) const Manager* const manager, const SignalID signalID) : AccessoryBase(), LayoutItem(signalID), LockableItem(), track(nullptr) { } inline Signal(const Manager* const manager, const std::string& serialized) : Signal(manager, SignalNone) { Deserialize(serialized); } virtual void SetAccessoryType(AccessoryType type) override; virtual ObjectType GetObjectType() const override; virtual std::string GetLayoutType() const override; std::string Serialize() const override; using HardwareHandle::Deserialize; virtual bool Deserialize(const std::string& serialized) override; inline Track* GetTrack() const { return track; } inline void SetTrack(Track* const track = nullptr) { this->track = track; } bool UsesAddress(Address address) const; std::map GetStateOptions() const; inline Address GetMappedAddress() const { AddressOffset offset = GetStateAddressOffset(); return offset < 0 ? 0 : GetAddress() + ((offset) >> 1); } inline AccessoryState GetMappedAccessoryState() const { return static_cast(GetStateAddressOffset() & 0x01); } inline void SetStateAddressOffset(const AccessoryState state, const AddressOffset addressOffset) { stateAddressMap[state] = addressOffset; } inline void SetStateAddressOffsets(const std::map& newOffsets) { stateAddressMap = newOffsets; } inline DataModel::AddressOffset GetStateAddressOffset() const { return GetStateAddressOffset(GetAccessoryState()); } inline AddressOffset GetStateAddressOffset(const AccessoryState state) const { return stateAddressMap.count(state) != 1 ? -1 : stateAddressMap.at(state); } AccessoryState CalculateMappedSignalState(const Address address, const AccessoryState state) const; Signal& operator=(const Hardware::AccessoryCacheEntry& accessory); private: void ResetStateAddressMap(); std::map stateAddressMap; Track* track; }; } // namespace DataModel railcontrol-24+dfsg1/DataModel/Switch.cpp000066400000000000000000000074061500456250600204420ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include "DataModel/Switch.h" #include "Utils/Utils.h" using std::map; using std::stringstream; using std::string; namespace DataModel { std::string Switch::Serialize() const { string str = "objectType=Switch;"; str += AccessoryBase::Serialize(); str += ";"; str += LayoutItem::Serialize(); str += ";"; str += LockableItem::Serialize(); return str; } bool Switch::Deserialize(const std::string& serialized) { map arguments; ParseArguments(serialized, arguments); string objectType = Utils::Utils::GetStringMapEntry(arguments, "objectType"); if (objectType.compare("Switch") != 0) { return false; } AccessoryBase::Deserialize(arguments); LayoutItem::Deserialize(arguments); LockableItem::Deserialize(arguments); SetSizeFromType(); SetVisible(VisibleYes); return true; } void Switch::SetAccessoryState(const AccessoryState state) { AccessoryState checkedState = state; if (GetAccessoryType() != SwitchTypeThreeWay && state == SwitchStateThird) { checkedState = SwitchStateTurnout; } AccessoryBase::SetAccessoryState(checkedState); } bool Switch::UsesAddress(Address address) const { switch(GetAccessoryType()) { case SwitchTypeThreeWay: return (GetAddress() == address) || (GetAddress() + 1 == address); case SwitchTypeLeft: case SwitchTypeRight: case SwitchTypeMaerklinLeft: case SwitchTypeMaerklinRight: default: return (GetAddress() == address); } } std::map Switch::GetStateOptions() const { std::map out; out[DataModel::SwitchStateStraight] = Languages::TextStraight; switch(GetAccessoryType()) { case DataModel::SwitchTypeThreeWay: out[DataModel::SwitchStateTurnout] = Languages::TextLeft; out[DataModel::SwitchStateThird] = Languages::TextRight; break; default: out[DataModel::SwitchStateTurnout] = Languages::TextTurnout; break; } return out; } AccessoryState Switch::CalculateInvertedSwitchState(const Address address, const AccessoryState state) const { if (!GetInverted()) { if ((GetAddress() + 1) == address && state == SwitchStateTurnout) { return SwitchStateThird; } return state; } switch(state) { case SwitchStateTurnout: case SwitchStateThird: return SwitchStateStraight; case SwitchStateStraight: return GetAddress() == address ? SwitchStateTurnout : SwitchStateThird; default: return state; } } DataModel::LayoutItem::LayoutItemSize Switch::CalculateHeightFromType(AccessoryType type) { switch (type) { case SwitchTypeMaerklinLeft: case SwitchTypeMaerklinRight: return 2; default: return Height1; } } Switch& Switch::operator=(const Hardware::AccessoryCacheEntry& accessory) { SetControlID(accessory.GetControlID()); SetName(accessory.GetName()); SetAddress(accessory.GetAddress()); SetProtocol(accessory.GetProtocol()); SetMatchKey(accessory.GetMatchKey()); return *this; } } // namespace DataModel railcontrol-24+dfsg1/DataModel/Switch.h000066400000000000000000000047671500456250600201160ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "DataModel/AccessoryBase.h" #include "DataModel/LayoutItem.h" #include "DataModel/LockableItem.h" #include "DataTypes.h" class Manager; namespace DataModel { class Switch : public AccessoryBase, public LayoutItem, public LockableItem { public: Switch() = delete; Switch(const Switch&) = delete; Switch& operator=(const Switch&) = delete; Switch(const SwitchID switchID) : AccessoryBase(), LayoutItem(switchID), LockableItem() { } Switch(__attribute__((unused)) Manager* manager, const SwitchID switchID) : Switch(switchID) { } Switch(const std::string& serialized) { Deserialize(serialized); } ObjectType GetObjectType() const override { return ObjectTypeSwitch; } std::string GetLayoutType() const override { return Languages::GetText(Languages::TextSwitch); } std::string Serialize() const override; using HardwareHandle::Deserialize; bool Deserialize(const std::string& serialized) override; void SetAccessoryState(const AccessoryState state); virtual inline void SetAccessoryType(AccessoryType type) override { AccessoryBase::SetAccessoryType(type); SetSizeFromType(); } bool UsesAddress(Address address) const; std::map GetStateOptions() const; AccessoryState CalculateInvertedSwitchState(const Address address, const AccessoryState state) const; static DataModel::LayoutItem::LayoutItemSize CalculateHeightFromType(AccessoryType type); Switch& operator=(const Hardware::AccessoryCacheEntry& accessory); private: inline void SetSizeFromType() { SetWidth(Width1); SetHeight(CalculateHeightFromType(GetAccessoryType())); } }; } // namespace DataModel railcontrol-24+dfsg1/DataModel/Text.cpp000066400000000000000000000026061500456250600201220ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include #include "DataModel/Text.h" #include "Utils/Utils.h" using std::map; using std::string; namespace DataModel { string Text::Serialize() const { string str; str = "objectType=Text;"; str += LayoutItem::Serialize(); return str; } bool Text::Deserialize(const string& serialized) { map arguments; ParseArguments(serialized, arguments); string objectType = Utils::Utils::GetStringMapEntry(arguments, "objectType"); if (objectType.compare("Text") != 0) { return false; } LayoutItem::Deserialize(arguments); SetHeight(Height1); SetVisible(VisibleYes); return true; } } // namespace DataModel railcontrol-24+dfsg1/DataModel/Text.h000066400000000000000000000033361500456250600175700ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "DataModel/LayoutItem.h" #include "DataTypes.h" #include "Languages.h" class Manager; namespace DataModel { class Text : public LayoutItem { public: static const LayoutItemSize MinWidth = 1; static const LayoutItemSize MaxWidth = 100; Text() = delete; Text(const Text&) = delete; Text& operator=(const Text&) = delete; inline Text(__attribute__((unused)) const Manager* const manager, const TextID textID) : Text(textID) { } inline Text(const TextID textID) : LayoutItem(textID) { } inline Text(const std::string& serialized) : Text(TextNone) { Deserialize(serialized); } inline ObjectType GetObjectType() const override { return ObjectTypeText; } std::string Serialize() const override; bool Deserialize(const std::string& serialized) override; inline std::string GetLayoutType() const override { return Languages::GetText(Languages::TextText); } }; } // namespace DataModel railcontrol-24+dfsg1/DataModel/Track.cpp000066400000000000000000000352101500456250600202370ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include #include #include #include #include "DataModel/Feedback.h" #include "DataModel/Loco.h" #include "DataModel/Track.h" #include "Manager.h" #include "Utils/Integer.h" using std::deque; using std::map; using std::string; using std::to_string; using std::vector; namespace DataModel { std::string Track::Serialize() const { std::string str; str = "objectType=Track"; str += ";selectrouteapproach="; str += to_string(selectRouteApproach); str += ";trackstate="; str += to_string(trackState); str += ";trackstatedelayed="; str += to_string(trackStateDelayed); str += ";locoorientation="; str += to_string(locoBaseOrientation); str += ";blocked="; str += to_string(blocked); str += ";locodelayed="; str += to_string(locoBaseDelayed.GetObjectID()); str += ";locotypedelayed="; str += to_string(locoBaseDelayed.GetObjectType()); str += ";allowlocoturn="; str += to_string(allowLocoTurn); str += ";releasewhenfree="; str += to_string(releaseWhenFree); str += ";showname="; str += to_string(showName); str += ";displayname="; str += displayName; str += ";"; str += LayoutItem::Serialize(); str += ";"; str += LockableItem::Serialize(); str += ";tracktype="; str += to_string(trackType); str += ";main="; str += to_string(mainID); return str; } bool Track::Deserialize(const std::string& serialized) { map arguments; ParseArguments(serialized, arguments); string objectType = Utils::Utils::GetStringMapEntry(arguments, "objectType"); if (objectType.compare("Track") != 0) { return false; } LayoutItem::Deserialize(arguments); LockableItem::Deserialize(arguments); // FIXME: remove later: 2024-03-22 feedback vector has been replaced by relation string feedbackStrings = Utils::Utils::GetStringMapEntry(arguments, "feedbacks"); deque feedbackStringVector; Utils::Utils::SplitString(feedbackStrings, ",", feedbackStringVector); for (auto& feedbackString : feedbackStringVector) { const FeedbackID feedbackID = Utils::Integer::StringToInteger(feedbackString); Feedback* feedback = manager->GetFeedback(feedbackID); if (!feedback) { continue; } feedback->SetTrack(this); feedbacks.push_back(new Relation(manager, ObjectIdentifier(ObjectTypeTrack, GetID()), ObjectIdentifier(ObjectTypeFeedback, feedbackID), Relation::RelationTypeTrackFeedback)); } selectRouteApproach = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "selectrouteapproach", SelectRouteSystemDefault)); trackState = static_cast(Utils::Utils::GetBoolMapEntry(arguments, "trackstate", DataModel::Feedback::FeedbackStateFree)); trackStateDelayed = static_cast(Utils::Utils::GetBoolMapEntry(arguments, "trackstatedelayed", trackState)); locoBaseOrientation = static_cast(Utils::Utils::GetBoolMapEntry(arguments, "locoorientation", OrientationRight)); blocked = Utils::Utils::GetBoolMapEntry(arguments, "blocked", false); const ObjectIdentifier& locoBaseDelayedIdentifier = GetLocoBase(); locoBaseDelayed.SetObjectID(Utils::Utils::GetIntegerMapEntry(arguments, "locodelayed", locoBaseDelayedIdentifier.GetObjectID())); locoBaseDelayed.SetObjectType(static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "locotypedelayed", locoBaseDelayedIdentifier.GetObjectType()))); allowLocoTurn = Utils::Utils::GetBoolMapEntry(arguments, "allowlocoturn", true); releaseWhenFree = Utils::Utils::GetBoolMapEntry(arguments, "releasewhenfree", false); showName = Utils::Utils::GetBoolMapEntry(arguments, "showname", true); displayName = Utils::Utils::GetStringMapEntry(arguments, "displayname", GetName()); SetWidth(Width1); SetVisible(VisibleYes); trackType = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "tracktype", TrackTypeStraight)); switch (trackType) { case TrackTypeTurn: case TrackTypeTunnelEnd: SetHeight(Height1); break; case TrackTypeCrossingLeft: case TrackTypeCrossingRight: case TrackTypeCrossingSymetric: SetHeight(Height2); break; default: break; } mainID = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "master", TrackNone)); // FIXME: 2025-02-28 can be removed later mainID = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "main", mainID)); return true; } void Track::DeleteFeedbacks() { while (feedbacks.size() > 0) { Relation* feedbackRelation = feedbacks.back(); Feedback* feedback = manager->GetFeedback(feedbackRelation->ObjectID2()); if (feedback) { feedback->SetTrack(); } feedbacks.pop_back(); delete feedbackRelation; } } void Track::AssignFeedbacks(const std::vector& newFeedbacks) { DeleteFeedbacks(); feedbacks = newFeedbacks; for (auto feedbackRelation : feedbacks) { Feedback* feedback = manager->GetFeedback(feedbackRelation->ObjectID2()); if (feedback) { feedback->SetTrack(this); } } } void Track::DeleteSignals() { while (signals.size() > 0) { Relation* signalRelation = signals.back(); Signal* signal = manager->GetSignal(signalRelation->ObjectID2()); if (signal) { signal->SetTrack(); } signals.pop_back(); delete signalRelation; } } void Track::AssignSignals(const std::vector& newSignals) { DeleteSignals(); signals = newSignals; for (auto signalRelation : signals) { Signal* signal = manager->GetSignal(signalRelation->ObjectID2()); if (signal) { signal->SetTrack(this); } } } void Track::StopAllSignals(const ObjectIdentifier& locoBaseIdentifier) { for (auto signalRelation : signals) { Signal* signal = manager->GetSignal(signalRelation->ObjectID2()); if (!signal) { continue; } const ObjectIdentifier& locoBaseOfSignal = signal->GetLocoBase(); if (locoBaseOfSignal.IsSet() && (locoBaseIdentifier != locoBaseOfSignal)) { continue; } manager->SignalState(ControlTypeInternal, signal, SignalStateStop, true); } } bool Track::CanSetLocoBaseOrientation(const Orientation orientation, const ObjectIdentifier& locoBaseIdentifier) { if (locoBaseOrientation == orientation) { return true; } if (cluster) { return cluster->CanSetLocoBaseOrientation(static_cast(orientation ^ clusterInverted), locoBaseIdentifier); } const DataModel::LockableItem::LockState lockState = GetLockState(); return ((lockState == DataModel::LockableItem::LockStateFree) || ((lockState == DataModel::LockableItem::LockStateReserved) && (!locoBaseIdentifier.IsSet() || (locoBaseIdentifier == GetLocoBase())))); } bool Track::SetLocoBaseOrientation(const Orientation orientation) { if (locoBaseOrientation == orientation) { return true; } if (cluster) { return cluster->SetLocoBaseOrientation(static_cast(orientation ^ clusterInverted), GetLocoBase()); } locoBaseOrientation = orientation; return true; } bool Track::Reserve(Logger::Logger* logger, const ObjectIdentifier& locoBaseIdentifier) { std::lock_guard Guard(updateMutex); if (this->locoBaseDelayed.IsSet() && (this->locoBaseDelayed != locoBaseIdentifier)) { logger->Debug(Languages::TextTrackIsUsedByLoco, GetName(), manager->GetLocoBaseName(locoBaseDelayed)); return false; } if (blocked) { logger->Debug(Languages::TextTrackStatusIsBlocked, GetName()); return false; } if (trackState != DataModel::Feedback::FeedbackStateFree) { logger->Debug(Languages::TextIsNotFree, GetName()); return false; } return ReserveForce(logger, locoBaseIdentifier); } bool Track::ReserveForce(Logger::Logger* logger, const ObjectIdentifier& locoBaseIdentifier) { bool ret = LockableItem::Reserve(logger, locoBaseIdentifier); if (!ret) { return false; } this->locoBaseDelayed = locoBaseIdentifier; return true; } bool Track::Lock(Logger::Logger* logger, const ObjectIdentifier& locoBaseIdentifier) { bool ret = LockableItem::Lock(logger, locoBaseIdentifier); if (ret) { PublishState(); } return ret; } bool Track::Release(Logger::Logger* logger, const ObjectIdentifier& locoBaseIdentifier) { StopAllSignals(locoBaseIdentifier); { std::lock_guard Guard(updateMutex); bool ret = LockableItem::Release(logger, locoBaseIdentifier); if (!ret) { return false; } if (trackState != DataModel::Feedback::FeedbackStateFree) { return true; } this->locoBaseDelayed.Clear(); this->trackStateDelayed = DataModel::Feedback::FeedbackStateFree; } PublishState(); return true; } bool Track::ReleaseForce(Logger::Logger* logger, const ObjectIdentifier& locoBaseIdentifier) { StopAllSignals(locoBaseIdentifier); bool ret; { std::lock_guard Guard(updateMutex); ret = ReleaseForceUnlocked(logger, locoBaseIdentifier); } PublishState(); return ret; } bool Track::ReleaseForceUnlocked(Logger::Logger* logger, const ObjectIdentifier& locoBaseIdentifier) { bool ret = LockableItem::Release(logger, locoBaseIdentifier); this->trackState = DataModel::Feedback::FeedbackStateFree; this->locoBaseDelayed.Clear(); this->trackStateDelayed = DataModel::Feedback::FeedbackStateFree; return ret; } bool Track::SetFeedbackState(const FeedbackID feedbackID, const DataModel::Feedback::FeedbackState newTrackState) { { std::lock_guard Guard(updateMutex); const DataModel::Feedback::FeedbackState oldTrackState = this->trackState; const bool oldBlocked = blocked; const bool ret = FeedbackStateInternal(feedbackID, newTrackState); if (!ret) { return false; } if ((oldTrackState == newTrackState) && (oldBlocked == blocked)) { return true; } } PublishState(); return true; } bool Track::FeedbackStateInternal(const FeedbackID feedbackID, const DataModel::Feedback::FeedbackState newTrackState) { if (newTrackState == DataModel::Feedback::FeedbackStateOccupied) { LocoBase* locoBase = manager->GetLocoBase(GetLocoBaseDelayed()); if (!locoBase) { if ((!blocked) && manager->GetStopOnFeedbackInFreeTrack()) { manager->Booster(ControlTypeInternal, BoosterStateStop); blocked = true; } } else { locoBase->LocationReached(feedbackID); } this->trackState = DataModel::Feedback::FeedbackStateOccupied; this->trackStateDelayed = DataModel::Feedback::FeedbackStateOccupied; return true; } for (auto feedbackRelation : feedbacks) { DataModel::Feedback* feedback = manager->GetFeedbackUnlocked(feedbackRelation->ObjectID2()); if (!feedback) { continue; } if (feedback->GetState() != DataModel::Feedback::FeedbackStateFree) { // if another feedback is still occupied return false; } } this->trackState = DataModel::Feedback::FeedbackStateFree; const ObjectIdentifier& locoBaseIdentifier = GetLocoBase(); if (releaseWhenFree) { const LocoBase* locoBase = manager->GetLocoBase(locoBaseIdentifier); if (locoBase && locoBase->CheckFreeingTrack(GetID())) { StopAllSignals(locoBaseIdentifier); const bool ret = ReleaseForceUnlocked(locoBase->GetLogger(), locoBaseIdentifier); PublishState(); return ret; } } if (locoBaseIdentifier.IsSet()) { return true; } this->trackStateDelayed = DataModel::Feedback::FeedbackStateFree; this->locoBaseDelayed.Clear(); return true; } bool Track::AddRoute(Route* route) { std::lock_guard Guard(updateMutex); for (auto r : routes) { if (r == route) { return false; } } routes.push_back(route); return true; } bool Track::DeleteRoute(Route* route) { std::lock_guard Guard(updateMutex); size_t sizeBefore = routes.size(); routes.erase(std::remove(routes.begin(), routes.end(), route), routes.end()); size_t sizeAfter = routes.size(); return sizeBefore > sizeAfter; } bool Track::AddExtension(Track* track) { if (mainID != TrackNone) { return false; } std::lock_guard Guard(updateMutex); for (auto& extension : extensions) { if (extension == track) { return false; } } extensions.push_back(track); return true; } bool Track::DeleteExtension(const Track* track) { std::lock_guard Guard(updateMutex); size_t sizeBefore = extensions.size(); extensions.erase(std::remove(extensions.begin(), extensions.end(), track), extensions.end()); size_t sizeAfter = extensions.size(); return sizeBefore > sizeAfter; } void Track::UpdateMain() { mainTrack = (mainID == TrackNone ? nullptr : manager->GetTrack(mainID)); } SelectRouteApproach Track::GetSelectRouteApproachCalculated() const { if (selectRouteApproach == SelectRouteSystemDefault) { return manager->GetSelectRouteApproach(); } return selectRouteApproach; } bool Track::GetValidRoutes(Logger::Logger* logger, const LocoBase* loco, const bool allowLocoTurn, std::vector& validRoutes) const { { std::lock_guard Guard(updateMutex); for (auto route : routes) { if (route->FromTrackOrientation(logger, GetID(), locoBaseOrientation, loco, allowLocoTurn)) { validRoutes.push_back(route); } } } OrderValidRoutes(validRoutes); return true; } void Track::OrderValidRoutes(vector& validRoutes) const { switch (GetSelectRouteApproachCalculated()) { case SelectRouteRandom: { static std::random_device rd; static std::mt19937 g(rd()); std::shuffle(validRoutes.begin(), validRoutes.end(), g); return; } case SelectRouteMinTrackLength: std::sort(validRoutes.begin(), validRoutes.end(), Route::CompareShortest); return; case SelectRouteLongestUnused: std::sort(validRoutes.begin(), validRoutes.end(), Route::CompareLastUsed); return; case SelectRouteDoNotCare: default: // do nothing return; } } void Track::PublishState() const { manager->TrackPublishState(this); } } // namespace DataModel railcontrol-24+dfsg1/DataModel/Track.h000066400000000000000000000221351500456250600177060ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include #include "DataModel/Cluster.h" #include "DataModel/Feedback.h" #include "DataModel/LayoutItem.h" #include "DataModel/LockableItem.h" #include "DataModel/Relation.h" #include "DataTypes.h" #include "Logger/Logger.h" class Manager; namespace DataModel { class LocoBase; class Route; enum TrackType : unsigned char { TrackTypeStraight = 0, TrackTypeTurn = 1, TrackTypeEnd = 2, TrackTypeBridge = 3, TrackTypeTunnel = 4, TrackTypeTunnelEnd = 5, TrackTypeLink = 6, TrackTypeCrossingLeft = 7, TrackTypeCrossingRight = 8, TrackTypeCrossingSymetric = 9 }; enum SelectRouteApproach : unsigned char { SelectRouteSystemDefault = 0, SelectRouteDoNotCare = 1, SelectRouteRandom = 2, SelectRouteMinTrackLength = 3, SelectRouteLongestUnused = 4 }; class Track : public LayoutItem, public LockableItem { public: static const LayoutItem::LayoutItemSize MinLength = 1; static const LayoutItem::LayoutItemSize MaxLength = 100; inline Track(Manager* manager, const TrackID trackID) : LayoutItem(trackID), LockableItem(), manager(manager), trackType(TrackTypeStraight), mainID(TrackNone), mainTrack(nullptr), cluster(nullptr), clusterInverted(false), selectRouteApproach(SelectRouteSystemDefault), trackState(DataModel::Feedback::FeedbackStateFree), trackStateDelayed(DataModel::Feedback::FeedbackStateFree), locoBaseOrientation(OrientationRight), blocked(false), locoBaseDelayed(), allowLocoTurn(true), releaseWhenFree(false), showName(true) { } inline Track(Manager* manager, const std::string& serialized) : Track(manager, TrackNone) { Deserialize(serialized); } inline ~Track() { DeleteFeedbacks(); DeleteSignals(); } inline ObjectType GetObjectType() const override { return ObjectTypeTrack; } std::string Serialize() const override; bool Deserialize(const std::string& serialized) override; inline std::string GetLayoutType() const override { return Languages::GetText(Languages::TextTrack); } inline TrackType GetTrackType() const { return trackType; } inline void SetTrackType(const TrackType type) { this->trackType = type; } bool SetFeedbackState(const FeedbackID feedbackID, const DataModel::Feedback::FeedbackState state); inline DataModel::Feedback::FeedbackState GetStateDelayed() const { return trackStateDelayed; } bool AddRoute(Route* route); bool DeleteRoute(Route* route); inline SelectRouteApproach GetSelectRouteApproach() const { return selectRouteApproach; } inline void SetSelectRouteApproach(const SelectRouteApproach selectRouteApproach) { this->selectRouteApproach = selectRouteApproach; } inline const std::vector GetRoutes() const { std::vector out; std::lock_guard Guard(updateMutex); for (auto route : routes) { out.push_back(route); } return out; } bool GetValidRoutes(Logger::Logger* logger, const DataModel::LocoBase* loco, const bool allowLocoTurn, std::vector& validRoutes) const; inline Orientation GetLocoBaseOrientation() const { return locoBaseOrientation; } inline bool CanSetLocoBaseOrientation(const Orientation orientation) { return CanSetLocoBaseOrientation(orientation, GetLocoBase()); } bool CanSetLocoBaseOrientation(const Orientation orientation, const ObjectIdentifier& locoBaseIdentifier); bool SetLocoBaseOrientation(const Orientation orientation); inline void SetLocoBaseOrientationForce(const Orientation orientation) { locoBaseOrientation = orientation; } inline bool GetBlocked() const { return blocked; } inline void SetBlocked(const bool blocked) { this->blocked = blocked; } inline const ObjectIdentifier& GetLocoBaseDelayed() const { return this->locoBaseDelayed; } inline bool GetReleaseWhenFree() const { return releaseWhenFree; } inline void SetReleaseWhenFree(const bool releaseWhenFree) { this->releaseWhenFree = releaseWhenFree; } inline bool GetShowName() const { return this->showName; } inline void SetShowName(const bool showName) { this->showName = showName; } inline const std::string& GetDisplayName() const { return this->displayName; } inline void SetDisplayName(const std::string& displayName) { this->displayName = displayName; } inline DataModel::Cluster* GetCluster() const { return cluster; } inline void SetCluster(DataModel::Cluster* const cluster, const bool inverted) { this->cluster = cluster; this->clusterInverted = inverted; } inline void DeleteCluster() { this->cluster = nullptr; this->clusterInverted = false; } inline bool GetAllowLocoTurn() const { return allowLocoTurn; } inline void SetAllowLocoTurn(bool allowLocoTurn) { this->allowLocoTurn = allowLocoTurn; } bool Reserve(Logger::Logger* logger, const ObjectIdentifier& locoBaseIdentifier) override; bool Lock(Logger::Logger* logger, const ObjectIdentifier& locoBaseIdentifier) override; bool Release(Logger::Logger* logger, const ObjectIdentifier& locoBaseIdentifier) override; bool ReserveForce(Logger::Logger* logger, const ObjectIdentifier& locoBaseIdentifier); bool ReleaseForce(Logger::Logger* logger, const ObjectIdentifier& locoBaseIdentifier); void DeleteFeedbacks(); void AssignFeedbacks(const std::vector& newFeedbacks); inline const std::vector& GetFeedbacks() const { return feedbacks; } void DeleteSignals(); void AssignSignals(const std::vector& newSignals); inline const std::vector& GetSignals() const { return signals; } bool AddExtension(Track* track); bool DeleteExtension(const Track* track); inline std::vector GetExtensions() const { return extensions; } inline Track* GetMain() const { return mainTrack; } inline TrackID GetOwnMainID() const { return mainID; } inline void SetMain(Track* main) { mainTrack = main; mainID = (mainTrack ? mainTrack->GetID() : TrackNone); } void UpdateMain(); inline TrackID GetMainID() const { return (mainTrack ? mainTrack->GetID() : GetID()); } inline const std::string& GetMainName() const { return (mainTrack ? mainTrack->GetName() : GetName()); } inline const std::string& GetMainDisplayName() const { return (mainTrack ? mainTrack->GetDisplayName() : GetDisplayName()); } inline DataModel::Feedback::FeedbackState GetMainStateDelayed() const { return (mainTrack ? mainTrack->GetStateDelayed() : GetStateDelayed()); } inline bool GetMainBlocked() const { return (mainTrack ? mainTrack->GetBlocked() : GetBlocked()); } inline Orientation GetMainLocoOrientation() const { return (mainTrack ? mainTrack->GetLocoBaseOrientation() : GetLocoBaseOrientation()); } inline const ObjectIdentifier& GetMainLocoBaseDelayed() const { return (mainTrack ? mainTrack->GetLocoBaseDelayed() : GetLocoBaseDelayed()); } private: void PublishState() const; void StopAllSignals(const ObjectIdentifier& locoBaseIdentifier); bool FeedbackStateInternal(const FeedbackID feedbackID, const DataModel::Feedback::FeedbackState state); void OrderValidRoutes(std::vector& validRoutes) const; SelectRouteApproach GetSelectRouteApproachCalculated() const; bool ReleaseForceUnlocked(Logger::Logger* logger, const ObjectIdentifier& locoBaseIdentifier); Manager* manager; TrackType trackType; TrackID mainID; Track* mainTrack; std::vector extensions; mutable std::mutex updateMutex; std::vector feedbacks; std::vector signals; Cluster* cluster; bool clusterInverted; SelectRouteApproach selectRouteApproach; DataModel::Feedback::FeedbackState trackState; DataModel::Feedback::FeedbackState trackStateDelayed; std::vector routes; Orientation locoBaseOrientation; bool blocked; ObjectIdentifier locoBaseDelayed; bool allowLocoTurn; bool releaseWhenFree; bool showName; std::string displayName; }; } // namespace DataModel railcontrol-24+dfsg1/DataTypes.h000066400000000000000000000206271500456250600167120ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include // common typedef uint8_t ControlID; // objects in db typedef uint16_t ObjectID; // loco typedef ObjectID LocoID; typedef uint16_t Address; typedef uint16_t Speed; typedef uint32_t Length; // multiple unit typedef ObjectID MultipleUnitID; // accessory typedef ObjectID AccessoryID; // feedback typedef ObjectID FeedbackID; typedef uint32_t FeedbackPin; // track typedef ObjectID TrackID; typedef ObjectID ClusterID; // switch typedef AccessoryID SwitchID; // signal typedef AccessoryID SignalID; // route typedef ObjectID RouteID; typedef uint16_t Delay; // layer typedef int16_t LayerID; // relations typedef uint16_t Priority; typedef uint8_t Pause; // text typedef ObjectID TextID; static const Address AddressNone = 0; static const Address AddressDefault = 3; static const LocoID LocoNone = 0; static const MultipleUnitID MultipleUnitNone = 0; static const MultipleUnitID MultipleUnitIdPrefix = 0x8000; static const ObjectID ObjectNone = 0; static const AccessoryID AccessoryNone = 0; static const FeedbackID FeedbackNone = 0; static const FeedbackPin FeedbackPinNone = 0; static const TrackID TrackNone = 0; static const SwitchID SwitchNone = 0; static const RouteID RouteNone = 0; static const RouteID RouteAuto = 0; static const RouteID RouteStop = 0xFFFF; static const ControlID ControlNone = 0; static const LayerID LayerNone = 0; static const LayerID LayerUndeletable = 1; static const SignalID SignalNone = 0; static const ClusterID ClusterNone = 0; static const TextID TextNone = 0; static const Speed MaxSpeed = 1023; static const Speed DefaultTravelSpeed = 700; static const Speed DefaultReducedSpeed = 400; static const Speed DefaultCreepingSpeed = 100; static const Speed MinSpeed = 0; enum ControlType : uint8_t { ControlTypeHardware = 0, ControlTypeInternal, ControlTypeWebServer, ControlTypeZ21Server, ControlTypeCS2Server }; enum ControlIDs : ControlID { ControlIdNone = 0, ControlIdWebServer, ControlIdZ21Server, ControlIdCS2Server, ControlIdFirstHardware = 10 }; enum BoosterState : bool { BoosterStateStop = false, BoosterStateGo = true }; enum Protocol : uint8_t { ProtocolNone = 0, ProtocolServer = 1, ProtocolMM1 = 2, ProtocolMM2 = 3, ProtocolMFX = 4, ProtocolDCC = 5, ProtocolDCC14 = 6, ProtocolDCC28 = 7, ProtocolDCC128 = 8, ProtocolMM = 9, ProtocolMM15 = 10, ProtocolSX1 = 11, ProtocolSX2 = 12, ProtocolFMZ = 13, ProtocolMultipleUnit = 14, ProtocolEnd = ProtocolMultipleUnit }; static const std::string ProtocolSymbols[] = { "none", "all", "MM 1", "MM 2", "mfx", "DCC", "DCC 14", "DCC 28", "DCC 128", "MM", "MM 1.5", "SX1", "SX2", "FMZ", "Multi" }; enum AddressType : uint8_t { AddressTypeLoco = 0, AddressTypeMultipleUnit, AddressTypeAccessory }; enum AddressPort : uint8_t { AddressPortRed = 0, AddressPortGreen = 1 }; enum LocoType : uint8_t { LocoTypeLoco = 0, LocoTypeMultipleUnit }; enum ArgumentType : uint8_t { ArgumentTypeIpAddress = 1, ArgumentTypeSerialPort = 2, ArgumentTypeS88Modules = 3, ArgumentTypeMainSecundary = 4 }; enum HardwareType : uint8_t { HardwareTypeNone = 0, HardwareTypeVirtual = 1, HardwareTypeCS2Udp = 2, HardwareTypeM6051 = 3, // HardwareTypeRM485 = 4, // not used anymore, but still reserved for old configurations HardwareTypeOpenDcc = 5, HardwareTypeHsi88 = 6, HardwareTypeZ21 = 7, HardwareTypeCcSchnitte = 8, HardwareTypeEcos = 9, HardwareTypeCS2Tcp = 10, HardwareTypeIntellibox = 11, HardwareTypeMasterControl = 12, HardwareTypeTwinCenter = 13, HardwareTypeMasterControl2 = 14, HardwareTypeRedBox = 15, HardwareTypeRektor = 16, HardwareTypeDR5000 = 17, HardwareTypeCS1 = 18, HardwareTypeDccPpExTcp = 19, HardwareTypeDccPpExSerial = 20, HardwareTypeIntellibox2 = 21, HardwareTypeLocoNetAdapter63120 = 22, HardwareTypeLocoNetAdapter63820 = 23, HardwareTypeSystemControl7 = 24 }; enum Automode : bool { AutomodeNo = false, AutomodeYes = true }; enum ObjectType : uint8_t { ObjectTypeNone = 0, ObjectTypeLoco = 1, ObjectTypeTrack = 2, ObjectTypeFeedback = 3, ObjectTypeAccessory = 4, ObjectTypeSwitch = 5, ObjectTypeRoute = 6, ObjectTypeLayer = 7, ObjectTypeSignal = 8, ObjectTypeCluster = 9, ObjectTypeTimeTable = 10, ObjectTypeText = 11, ObjectTypePause = 12, ObjectTypeMultipleUnit = 13, ObjectTypeBooster = 14 }; enum Orientation : uint8_t { OrientationLeft = 0, OrientationRight = 1, OrientationChange = 2 }; enum StartupInitLocos : uint8_t { StartupInitLocosNone = 0, StartupInitLocosSpeed = 1, StartupInitLocosAll = 2, }; enum ProgramMode : uint8_t { ProgramModeNone, ProgramModeDccDirect, ProgramModeDccPomLoco, ProgramModeDccPomAccessory, ProgramModeMm, ProgramModeMmPom, ProgramModeMfx, ProgramModeDccPage, ProgramModeDccRegister }; typedef uint16_t CvNumber; typedef uint8_t CvValue; enum Propulsion : uint8_t { PropulsionUnknown = 0x00, PropulsionSteam = 0x01, PropulsionDiesel = 0x02, PropulsionGas = 0x04, PropulsionElectric = 0x08, PropulsionHydrogen = 0x10, PropulsionAccu = 0x20, PropulsionOther = 0x80, PropulsionAll = PropulsionSteam | PropulsionDiesel | PropulsionGas | PropulsionElectric | PropulsionHydrogen | PropulsionAccu | PropulsionOther }; enum TrainType : uint32_t { TrainTypeUnknown = 0x00000000, TrainTypeInternationalHighSpeed = 0x00000001, TrainTypeNationalHighSpeed = 0x00000002, TrainTypeInternationalLongDistance = 0x00000004, TrainTypeNationalLongDistance = 0x00000008, TrainTypeInternationalNight = 0x00000010, TrainTypeNationalNight = 0x00000020, TrainTypeLongDistanceFastLocal = 0x00000040, TrainTypeFastLocal = 0x00000080, TrainTypeLocal = 0x00000100, TrainTypeSuburban = 0x00000200, TrainTypeUnderground = 0x00000400, TrainTypeHistoric = 0x00001000, TrainTypeExtra = 0x00002000, TrainTypePassengerWithCargo = 0x00008000, TrainTypePassenger = TrainTypeInternationalHighSpeed | TrainTypeNationalHighSpeed | TrainTypeInternationalLongDistance | TrainTypeNationalLongDistance | TrainTypeInternationalNight | TrainTypeNationalNight | TrainTypeLongDistanceFastLocal | TrainTypeFastLocal | TrainTypeLocal | TrainTypeSuburban | TrainTypeUnderground | TrainTypeHistoric | TrainTypeExtra | TrainTypePassengerWithCargo, TrainTypeCargoLongDistance = 0x00010000, TrainTypeCargoLocal = 0x00020000, TrainTypeCargoBlock = 0x00040000, TrainTypeCargoTractor = 0x00080000, TrainTypeCargoExpress = 0x00100000, TrainTypeCargoWithPassenger = 0x00800000, TrainTypeCargo = TrainTypeCargoLongDistance | TrainTypeCargoLocal | TrainTypeCargoBlock | TrainTypeCargoTractor | TrainTypeCargoExpress | TrainTypeCargoWithPassenger, TrainTypeRescue = 0x01000000, TrainTypeConstruction = 0x02000000, TrainTypeEmpty = 0x04000000, TrainTypeLoco = 0x08000000, TrainTypeCleaning = 0x10000000, TrainTypeOther = 0x40000000, // Bit 32 (0x80000000) can not be used because of conversion functions that handle with signed int32_t TrainTypeAll = TrainTypePassenger | TrainTypeCargo | TrainTypeRescue | TrainTypeConstruction | TrainTypeEmpty | TrainTypeLoco | TrainTypeCleaning | TrainTypeOther }; railcontrol-24+dfsg1/Fallthrough.h000066400000000000000000000015301500456250600172630ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #if !defined(__clang__) && !defined(__INTEL_COMPILER) && defined(__GNUG__) && __GNUG__ >= 7 [[fallthrough]]; #endif railcontrol-24+dfsg1/Hardware/000077500000000000000000000000001500456250600163715ustar00rootroot00000000000000railcontrol-24+dfsg1/Hardware/AccessoryCache.cpp000066400000000000000000000117211500456250600217560ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include "DataModel/Accessory.h" #include "DataModel/ObjectIdentifier.h" #include "DataModel/Signal.h" #include "DataModel/Switch.h" #include "Hardware/AccessoryCache.h" #include "Manager.h" namespace Hardware { void AccessoryCache::Save(AccessoryCacheEntry& entry, const std::string& oldMatchKey) { const std::string& matchKey = entry.GetMatchKey(); bool ret = false; const bool matchKeyChanged = matchKey.compare(oldMatchKey) != 0; if (matchKeyChanged) { ret = UpdateData1(entry, oldMatchKey); } if (!ret) { ret = UpdateData2(entry, oldMatchKey); } if (!ret && matchKeyChanged) { ret = UpdateData2(entry, matchKey); } entries.emplace(matchKey, entry); } bool AccessoryCache::UpdateData1(AccessoryCacheEntry& entry, const std::string& matchKey) { const DataModel::ObjectIdentifier objectIdentifier = Delete(matchKey); switch (objectIdentifier.GetObjectType()) { case ObjectTypeAccessory: { DataModel::Accessory* accessory = manager->GetAccessory(objectIdentifier.GetObjectID()); if (accessory) { entry.SetObjectIdentifier(DataModel::ObjectIdentifier(ObjectTypeAccessory, accessory->GetID())); *accessory = entry; return true; } return false; } case ObjectTypeSignal: { DataModel::Signal* signal = manager->GetSignal(objectIdentifier.GetObjectID()); if (signal) { entry.SetObjectIdentifier(DataModel::ObjectIdentifier(ObjectTypeSignal, signal->GetID())); *signal = entry; return true; } return false; } case ObjectTypeSwitch: { DataModel::Switch* mySwitch = manager->GetSwitch(objectIdentifier.GetObjectID()); if (mySwitch) { entry.SetObjectIdentifier(DataModel::ObjectIdentifier(ObjectTypeSwitch, mySwitch->GetID())); *mySwitch = entry; return true; } return false; } default: return false; } } bool AccessoryCache::UpdateData2(AccessoryCacheEntry& entry, const std::string& matchKey) { DataModel::Accessory* accessory = manager->GetAccessoryByMatchKey(GetControlId(), matchKey); if (accessory != nullptr) { entry.SetObjectIdentifier(DataModel::ObjectIdentifier(ObjectTypeAccessory, accessory->GetID())); *accessory = entry; return true; } DataModel::Signal* signal = manager->GetSignalByMatchKey(GetControlId(), matchKey); if (signal != nullptr) { entry.SetObjectIdentifier(DataModel::ObjectIdentifier(ObjectTypeSignal, signal->GetID())); *signal = entry; return true; } DataModel::Switch* mySwitch = manager->GetSwitchByMatchKey(GetControlId(), matchKey); if (mySwitch != nullptr) { entry.SetObjectIdentifier(DataModel::ObjectIdentifier(ObjectTypeSwitch, mySwitch->GetID())); *mySwitch = entry; return true; } return false; } DataModel::ObjectIdentifier AccessoryCache::Delete(const std::string& matchKey) { DataModel::ObjectIdentifier objectIdentifier = Get(matchKey).GetObjectIdentifier(); entries.erase(matchKey); const ObjectType objectType = objectIdentifier.GetObjectType(); switch (objectType) { case ObjectTypeAccessory: { const AccessoryID accessoryId = objectIdentifier.GetObjectID(); manager->AccessoryRemoveMatchKey(accessoryId); objectIdentifier.SetObjectID(accessoryId); break; } case ObjectTypeSignal: { const SignalID signalId = objectIdentifier.GetObjectID(); manager->SignalRemoveMatchKey(signalId); objectIdentifier.SetObjectID(signalId); break; } case ObjectTypeSwitch: { const SwitchID switchId = objectIdentifier.GetObjectID(); manager->SwitchRemoveMatchKey(switchId); objectIdentifier.SetObjectID(switchId); break; } default: return DataModel::ObjectIdentifier(); } return objectIdentifier; } void AccessoryCache::SetObjectIdentifier(const DataModel::ObjectIdentifier objectIdentifier, const std::string& matchKey) { if (objectIdentifier.IsSet()) { for (auto& accessoryCacheEntry : entries) { AccessoryCacheEntry& entry = accessoryCacheEntry.second; if (entry.GetObjectIdentifier() == objectIdentifier) { entry.ClearObjectIdentifier(); } } } auto entry = entries.find(matchKey); if (entry == entries.end()) { return; } entry->second.SetObjectIdentifier(objectIdentifier); } } // namespace Hardware railcontrol-24+dfsg1/Hardware/AccessoryCache.h000066400000000000000000000075101500456250600214240ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include #include "DataTypes.h" #include "DataModel/ObjectIdentifier.h" class Manager; namespace Hardware { class AccessoryCacheEntry { AccessoryCacheEntry() = delete; public: inline AccessoryCacheEntry(const ControlID controlId) : controlId(controlId), protocol(ProtocolNone), address(AddressNone), matchKey("") { } inline ControlID GetControlID() const { return controlId; } inline DataModel::ObjectIdentifier GetObjectIdentifier() const { return objectIdentifier; } inline void SetObjectIdentifier(const DataModel::ObjectIdentifier objectIdentifier) { this->objectIdentifier = objectIdentifier; } inline void ClearObjectIdentifier() { this->objectIdentifier.Clear(); } inline const std::string& GetName() const { return name; } inline void SetName(const std::string& name) { this->name = name; } inline Protocol GetProtocol() const { return protocol; } inline void SetProtocol(const Protocol protocol) { this->protocol = protocol; } inline Address GetAddress() const { return address; } inline void SetAddress(const Address address) { this->address = address; } inline const std::string& GetMatchKey() const { return matchKey; } inline void SetMatchKey(const std::string& matchKey) { this->matchKey = matchKey; } inline void SetMatchKey(const unsigned int matchKey) { this->matchKey = std::to_string(matchKey); } private: const ControlID controlId; DataModel::ObjectIdentifier objectIdentifier; std::string name; Protocol protocol; Address address; std::string matchKey; }; class AccessoryCache { AccessoryCache() = delete; AccessoryCache(const AccessoryCache& rhs) = delete; AccessoryCache& operator= (const AccessoryCache& rhs) = delete; public: inline AccessoryCache(const ControlID controlId, Manager* const manager) : controlId(controlId), manager(manager) { } inline ControlID GetControlId() const { return controlId; } inline void Save(AccessoryCacheEntry& entry) { const std::string& oldMatchKey = entry.GetMatchKey(); Save(entry, oldMatchKey); } void Save(AccessoryCacheEntry& entry, const std::string& oldMatchKey); DataModel::ObjectIdentifier Delete(const std::string& matchKey); inline const AccessoryCacheEntry Get(const std::string& matchKey) const { return entries.count(matchKey) == 0 ? AccessoryCacheEntry(controlId) : entries.at(matchKey); } inline const std::map& GetAll() const { return entries; } void SetObjectIdentifier(const DataModel::ObjectIdentifier objectIdentifier, const std::string& matchKey); private: bool UpdateData1(AccessoryCacheEntry& entry, const std::string& matchKey); bool UpdateData2(AccessoryCacheEntry& entry, const std::string& matchKey); const ControlID controlId; Manager* const manager; std::map entries; }; } // namespace Hardware railcontrol-24+dfsg1/Hardware/CS1.h000066400000000000000000000020431500456250600171270ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include "Hardware/Protocols/EsuCAN.h" namespace Hardware { class CS1 : Protocols::EsuCAN { public: CS1() = delete; CS1(const CS1&) = delete; CS1& operator=(const CS1&) = delete; inline CS1(const HardwareParams* params) : Protocols::EsuCAN(params, "CS1") { } }; } // namespace railcontrol-24+dfsg1/Hardware/CS2Tcp.cpp000066400000000000000000000044561500456250600201440ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include "Hardware/CS2Tcp.h" #include "Utils/Utils.h" namespace Hardware { CS2Tcp::CS2Tcp(const HardwareParams* params) : MaerklinCAN(params, "Maerklin Central Station 2 (CS2) TCP / " + params->GetName() + " at IP " + params->GetArg1(), params->GetName()), connection(Network::TcpClient::GetTcpClientConnection(HardwareInterface::logger, params->GetArg1(), CS2Port)) { HardwareInterface::logger->Info(Languages::TextStarting, GetFullName()); if (connection.IsConnected() == false) { HardwareInterface::logger->Error(Languages::TextUnableToCreateTcpSocket, params->GetArg1(), CS2Port); } Init(); } void CS2Tcp::Send(const unsigned char* buffer) { if (connection.Send(buffer, CANCommandBufferLength) == -1) { HardwareInterface::logger->Error(Languages::TextUnableToSendDataToControl); } } void CS2Tcp::Receiver() { if (!connection.IsConnected()) { HardwareInterface::logger->Error(Languages::TextUnableToReceiveData); return; } unsigned char buffer[CANCommandBufferLength]; while(run) { ssize_t datalen = connection.ReceiveExact(buffer, sizeof(buffer)); if (!run) { break; } if (datalen == -1) { if (errno == ETIMEDOUT) { continue; } HardwareInterface::logger->Error(Languages::TextErrorReadingData, strerror(errno)); break; } if (datalen == 0) { // no data received continue; } if (datalen != 13) { HardwareInterface::logger->Error(Languages::TextErrorReadingData, strerror(errno)); continue; } Parse(buffer); } connection.Terminate(); } } // namespace railcontrol-24+dfsg1/Hardware/CS2Tcp.h000066400000000000000000000030761500456250600176060ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include "Hardware/HardwareParams.h" #include "Hardware/Protocols/MaerklinCAN.h" #include "Logger/Logger.h" #include "Network/TcpClient.h" namespace Hardware { class CS2Tcp : protected Protocols::MaerklinCAN { public: CS2Tcp() = delete; CS2Tcp(const CS2Tcp&) = delete; CS2Tcp& operator=(const CS2Tcp&) = delete; CS2Tcp(const HardwareParams* params); static void GetArgumentTypesAndHint(std::map& argumentTypes, std::string& hint) { argumentTypes[1] = ArgumentTypeIpAddress; argumentTypes[2] = ArgumentTypeMainSecundary; hint = Languages::GetText(Languages::TextHintCs2Tcp); } private: Network::TcpConnection connection; void Send(const unsigned char* buffer) override; void Receiver() override; static const unsigned short CS2Port = 15731; }; } // namespace railcontrol-24+dfsg1/Hardware/CS2Udp.cpp000066400000000000000000000052231500456250600201370ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include "Hardware/CS2Udp.h" #include "Utils/Utils.h" namespace Hardware { CS2Udp::CS2Udp(const HardwareParams* params) : MaerklinCAN(params, "Maerklin Central Station 2 (CS2) UDP / " + params->GetName() + " at IP " + params->GetArg1(), params->GetName()), senderConnection(HardwareInterface::logger, params->GetArg1(), CS2SenderPort), receiverConnection(HardwareInterface::logger, "0.0.0.0", CS2ReceiverPort) { HardwareInterface::logger->Info(Languages::TextStarting, GetFullName()); if (senderConnection.IsConnected()) { HardwareInterface::logger->Info(Languages::TextSenderSocketCreated); } else { HardwareInterface::logger->Error(Languages::TextUnableToCreateUdpSocketForSendingData); } Init(); } CS2Udp::~CS2Udp() { receiverConnection.Terminate(); HardwareInterface::logger->Info(Languages::TextTerminatingSenderSocket); } void CS2Udp::Send(const unsigned char* buffer) { if (senderConnection.Send(buffer, CANCommandBufferLength) == -1) { HardwareInterface::logger->Error(Languages::TextUnableToSendDataToControl); } } void CS2Udp::Receiver() { if (!receiverConnection.IsConnected()) { HardwareInterface::logger->Error(Languages::TextUnableToCreateUdpSocketForReceivingData); return; } bool ret = receiverConnection.Bind(); if (!ret) { HardwareInterface::logger->Error(Languages::TextUnableToBindUdpSocket); return; } unsigned char buffer[CANCommandBufferLength]; while(run) { ssize_t datalen = receiverConnection.Receive(buffer, sizeof(buffer)); if (!run) { break; } if (datalen < 0) { HardwareInterface::logger->Error(Languages::TextUnableToReceiveData); break; } if (datalen == 0) { // no data received continue; } if (datalen != 13) { HardwareInterface::logger->Error(Languages::TextInvalidDataReceived); continue; } Parse(buffer); } receiverConnection.Terminate(); } } // namespace railcontrol-24+dfsg1/Hardware/CS2Udp.h000066400000000000000000000033041500456250600176020ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include "Hardware/HardwareParams.h" #include "Hardware/Protocols/MaerklinCAN.h" #include "Logger/Logger.h" #include "Network/UdpConnection.h" namespace Hardware { class CS2Udp : protected Protocols::MaerklinCAN { public: CS2Udp() = delete; CS2Udp(const CS2Udp&) = delete; CS2Udp& operator=(const CS2Udp&) = delete; CS2Udp(const HardwareParams* params); ~CS2Udp(); static void GetArgumentTypesAndHint(std::map& argumentTypes, std::string& hint) { argumentTypes[1] = ArgumentTypeIpAddress; argumentTypes[2] = ArgumentTypeMainSecundary; hint = Languages::GetText(Languages::TextHintCs2Udp); } private: Network::UdpConnection senderConnection; Network::UdpConnection receiverConnection; void Send(const unsigned char* buffer) override; void Receiver() override; static const unsigned short CS2SenderPort = 15731; static const unsigned short CS2ReceiverPort = 15730; }; } // namespace railcontrol-24+dfsg1/Hardware/Capabilities.h000066400000000000000000000047621500456250600211440ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include namespace Hardware { typedef uint32_t Capability; enum Capabilities : Capability { CapabilityNone = 0x00000000, CapabilityLoco = 0x00000001, CapabilityMultipleUnit = 0x00000002, CapabilityAccessory = 0x00000004, CapabilityFeedback = 0x00000008, CapabilityProgram = 0x00000010, CapabilityProgramMmWrite = 0x00000020, CapabilityProgramMmPomWrite = 0x00000040, CapabilityProgramMfxRead = 0x00000080, CapabilityProgramMfxWrite = 0x00000100, CapabilityProgramDccRegisterRead = 0x00000200, CapabilityProgramDccRegisterWrite = 0x00000400, CapabilityProgramDccPageRead = 0x00000800, CapabilityProgramDccPageWrite = 0x00001000, CapabilityProgramDccDirectRead = 0x00002000, CapabilityProgramDccDirectWrite = 0x00004000, CapabilityProgramDccPomLocoRead = 0x00008000, CapabilityProgramDccPomLocoWrite = 0x00010000, CapabilityProgramDccPomAccessoryRead = 0x00020000, CapabilityProgramDccPomAccessoryWrite = 0x00040000, CapabilityLocoDatabase = 0x00080000, CapabilityMultipleUnitDatabase = 0x00100000, CapabilityAccessoryDatabase = 0x00200000, CapabilityFeedbackDatabase = 0x00400000, }; inline Capabilities operator& (const Capabilities c1, const Capabilities c2) { return static_cast(static_cast(c1) & static_cast(c2)); } inline Capabilities operator| (const Capabilities c1, const Capabilities c2) { return static_cast(static_cast(c1) | static_cast(c2)); } } railcontrol-24+dfsg1/Hardware/CcSchnitte.cpp000066400000000000000000000041041500456250600211230ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include "Hardware/CcSchnitte.h" #include "Utils/Utils.h" // OS X does not define B500000 #ifdef __APPLE__ #ifndef B500000 #define B500000 500000 #endif #endif namespace Hardware { CcSchnitte::CcSchnitte(const HardwareParams* params) : MaerklinCAN(params, "CC-Schnitte / " + params->GetName() + " at serial port " + params->GetArg1(), params->GetName()), serialLine(HardwareInterface::logger, params->GetArg1(), B500000, 8, 'N', 1, true) { HardwareInterface::logger->Info(Languages::TextStarting, GetFullName()); Init(); } void CcSchnitte::Send(const unsigned char* buffer) { if (!serialLine.IsConnected()) { return; } if (serialLine.Send(buffer, CANCommandBufferLength) == -1) { HardwareInterface::logger->Error(Languages::TextUnableToSendDataToControl); } } void CcSchnitte::Receiver() { while (run) { if (!serialLine.IsConnected()) { HardwareInterface::logger->Error(Languages::TextUnableToReceiveData); return; } unsigned char buffer[CANCommandBufferLength]; ssize_t datalen = serialLine.ReceiveExact(buffer, sizeof(buffer)); if (!run) { break; } if (datalen == 0) { // no data received continue; } if (datalen != sizeof(buffer)) { HardwareInterface::logger->Error(Languages::TextInvalidDataReceived); continue; } Parse(buffer); } } } // namespace railcontrol-24+dfsg1/Hardware/CcSchnitte.h000066400000000000000000000030311500456250600205660ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include "Hardware/HardwareParams.h" #include "Hardware/Protocols/MaerklinCAN.h" #include "Logger/Logger.h" #include "Network/Serial.h" namespace Hardware { class CcSchnitte : Protocols::MaerklinCAN { public: CcSchnitte() = delete; CcSchnitte(const CcSchnitte&) = delete; CcSchnitte& operator=(const CcSchnitte&) = delete; CcSchnitte(const HardwareParams* params); static void GetArgumentTypesAndHint(std::map& argumentTypes, std::string& hint) { argumentTypes[1] = ArgumentTypeSerialPort; argumentTypes[2] = ArgumentTypeMainSecundary; hint = Languages::GetText(Languages::TextHintCcSchnitte); } private: Network::Serial serialLine; void Send(const unsigned char* buffer) override; void Receiver() override; }; } // namespace railcontrol-24+dfsg1/Hardware/DR5000.h000066400000000000000000000020621500456250600173540ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include "Hardware/Protocols/Z21.h" namespace Hardware { class DR5000 : Protocols::Z21 { public: DR5000() = delete; DR5000(const DR5000&) = delete; DR5000& operator=(const DR5000&) = delete; inline DR5000(const HardwareParams* params) : Protocols::Z21(params, "DR5000") { } }; } // namespace railcontrol-24+dfsg1/Hardware/DccPpExSerial.cpp000066400000000000000000000030321500456250600215210ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include "Hardware/DccPpExSerial.h" #include "Utils/Utils.h" namespace Hardware { DccPpExSerial::DccPpExSerial(const HardwareParams* params) : DccPpEx(params, "DCC-EX Serial / " + params->GetName() + " at serial port " + params->GetArg1(), params->GetName()), serialLine(logger, params->GetArg1(), B115200, 8, 'N', 1, #ifdef __CYGWIN__ false #else true #endif ) { logger->Info(Languages::TextStarting, GetFullName()); Init(); } bool DccPpExSerial::Send(const std::string& buffer) { if (!serialLine.IsConnected()) { return false; } bool ret = serialLine.Send(buffer) != -1; if (!ret) { logger->Error(Languages::TextUnableToSendDataToControl); } return ret; } bool DccPpExSerial::Receive(std::string& buffer) { return serialLine.Receive(buffer); } } // namespace railcontrol-24+dfsg1/Hardware/DccPpExSerial.h000066400000000000000000000030331500456250600211670ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "Hardware/HardwareParams.h" #include "Hardware/Protocols/DccPpEx.h" #include "Logger/Logger.h" #include "Network/Serial.h" namespace Hardware { class DccPpExSerial : Protocols::DccPpEx { public: DccPpExSerial() = delete; DccPpExSerial(const DccPpExSerial&) = delete; DccPpExSerial& operator=(const DccPpExSerial&) = delete; DccPpExSerial(const HardwareParams* params); static void GetArgumentTypesAndHint(std::map& argumentTypes, std::string& hint) { argumentTypes[1] = ArgumentTypeSerialPort; hint = Languages::GetText(Languages::TextHintDccPpExSerial); } private: Network::Serial serialLine; bool Send(const std::string& buffer) override; bool Receive(std::string& buffer) override; }; } // namespace railcontrol-24+dfsg1/Hardware/DccPpExTcp.cpp000066400000000000000000000031001500456250600210240ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include "Hardware/DccPpExTcp.h" #include "Utils/Utils.h" using std::string; namespace Hardware { DccPpExTcp::DccPpExTcp(const HardwareParams* params) : DccPpEx(params, "DCC-EX TCP / " + params->GetName() + " at IP " + params->GetArg1(), params->GetName()), connection(Network::TcpClient::GetTcpClientConnection(logger, params->GetArg1(), Port)) { logger->Info(Languages::TextStarting, GetFullName()); if (connection.IsConnected() == false) { logger->Error(Languages::TextUnableToCreateTcpSocket, params->GetArg1(), Port); } Init(); } bool DccPpExTcp::Send(const string& buffer) { const bool ret = connection.Send(buffer) != -1; if (!ret) { logger->Error(Languages::TextUnableToSendDataToControl); } return ret; } bool DccPpExTcp::Receive(string& buffer) { return connection.Receive(buffer); } } // namespace railcontrol-24+dfsg1/Hardware/DccPpExTcp.h000066400000000000000000000031041500456250600204750ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "Hardware/HardwareParams.h" #include "Hardware/Protocols/DccPpEx.h" #include "Logger/Logger.h" #include "Network/TcpClient.h" namespace Hardware { class DccPpExTcp : protected Protocols::DccPpEx { public: DccPpExTcp() = delete; DccPpExTcp(const DccPpExTcp&) = delete; DccPpExTcp& operator=(const DccPpExTcp&) = delete; DccPpExTcp(const HardwareParams* params); static void GetArgumentTypesAndHint(std::map& argumentTypes, std::string& hint) { argumentTypes[1] = ArgumentTypeIpAddress; hint = Languages::GetText(Languages::TextHintDccPpExTcp); } private: Network::TcpConnection connection; bool Send(const std::string& buffer) override; bool Receive(std::string& buffer) override; static const unsigned short Port = 2560; }; } // namespace railcontrol-24+dfsg1/Hardware/Ecos.h000066400000000000000000000020531500456250600174330ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include "Hardware/Protocols/EsuCAN.h" namespace Hardware { class Ecos : Protocols::EsuCAN { public: Ecos() = delete; Ecos(const Ecos&) = delete; Ecos& operator=(const Ecos&) = delete; inline Ecos(const HardwareParams* params) : Protocols::EsuCAN(params, "ECoS") { } }; } // namespace railcontrol-24+dfsg1/Hardware/FeedbackCache.cpp000066400000000000000000000044501500456250600215100ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include "DataModel/Feedback.h" #include "Hardware/FeedbackCache.h" #include "Manager.h" namespace Hardware { void FeedbackCache::Save(FeedbackCacheEntry& entry, const std::string& oldMatchKey) { const std::string& matchKey = entry.GetMatchKey(); DataModel::Feedback* feedback = nullptr; const bool matchKeyChanged = matchKey.compare(oldMatchKey) != 0; if (matchKeyChanged) { const FeedbackID feedbackId = Delete(oldMatchKey); feedback = manager->GetFeedback(feedbackId); } if (feedback == nullptr) { feedback = manager->GetFeedbackByMatchKey(GetControlId(), oldMatchKey); } if (feedback == nullptr && matchKeyChanged) { feedback = manager->GetFeedbackByMatchKey(GetControlId(), matchKey); } if (feedback != nullptr) { entry.SetFeedbackID(feedback->GetID()); *feedback = entry; } entries.emplace(matchKey, entry); } FeedbackID FeedbackCache::Delete(const std::string& matchKey) { FeedbackID feedbackId = Get(matchKey).GetFeedbackID(); manager->FeedbackRemoveMatchKey(feedbackId); entries.erase(matchKey); return feedbackId; } void FeedbackCache::SetFeedbackId(const FeedbackID feedbackId, const std::string& matchKey) { if (feedbackId != FeedbackNone) { for (auto& feedbackCacheEntry : entries) { FeedbackCacheEntry& entry = feedbackCacheEntry.second; if (entry.GetFeedbackID() == feedbackId) { entry.SetFeedbackID(FeedbackNone); } } } auto entry = entries.find(matchKey); if (entry == entries.end()) { return; } entry->second.SetFeedbackID(feedbackId); } } // namespace Hardware railcontrol-24+dfsg1/Hardware/FeedbackCache.h000066400000000000000000000063071500456250600211600ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include #include "DataTypes.h" class Manager; namespace Hardware { class FeedbackCacheEntry { public: FeedbackCacheEntry() = delete; inline FeedbackCacheEntry(const ControlID controlId) : controlId(controlId), feedbackId(FeedbackNone), pin(FeedbackPinNone), matchKey("") { } inline ControlID GetControlID() const { return controlId; } inline FeedbackID GetFeedbackID() const { return feedbackId; } inline void SetFeedbackID(const FeedbackID feedbackId) { this->feedbackId = feedbackId; } inline const std::string& GetName() const { return name; } inline void SetName(const std::string& name) { this->name = name; } inline FeedbackPin GetPin() const { return pin; } inline void SetPin(const FeedbackPin pin) { this->pin = pin; } inline const std::string& GetMatchKey() const { return matchKey; } inline void SetMatchKey(const std::string& matchKey) { this->matchKey = matchKey; } inline void SetMatchKey(const unsigned int matchKey) { this->matchKey = std::to_string(matchKey); } private: const ControlID controlId; FeedbackID feedbackId; std::string name; FeedbackPin pin; std::string matchKey; }; class FeedbackCache { public: inline FeedbackCache(const ControlID controlId, Manager* const manager) : controlId(controlId), manager(manager) { } FeedbackCache() = delete; FeedbackCache(const FeedbackCache& rhs) = delete; FeedbackCache& operator= (const FeedbackCache& rhs) = delete; inline ControlID GetControlId() const { return controlId; } inline void Save(FeedbackCacheEntry& entry) { const std::string& oldMatchKey = entry.GetMatchKey(); Save(entry, oldMatchKey); } void Save(FeedbackCacheEntry& entry, const std::string& oldMatchKey); FeedbackID Delete(const std::string& matchKey); inline const FeedbackCacheEntry Get(const std::string& matchKey) const { return entries.count(matchKey) == 0 ? FeedbackCacheEntry(controlId) : entries.at(matchKey); } inline const std::map& GetAll() const { return entries; } void SetFeedbackId(const FeedbackID locoId, const std::string& matckKey); private: const ControlID controlId; Manager* const manager; std::map entries; }; } // namespace Hardware railcontrol-24+dfsg1/Hardware/HardwareHandler.cpp000066400000000000000000000572571500456250600221500ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include "DataModel/Loco.h" #include "DataModel/LocoFunctions.h" #include "DataTypes.h" #include "Logger/Logger.h" #include "Hardware/CS1.h" #include "Hardware/CS2Udp.h" #include "Hardware/CS2Tcp.h" #include "Hardware/CcSchnitte.h" #include "Hardware/DR5000.h" #include "Hardware/DccPpExSerial.h" #include "Hardware/DccPpExTcp.h" #include "Hardware/Ecos.h" #include "Hardware/HardwareHandler.h" #include "Hardware/Hsi88.h" #include "Hardware/Intellibox.h" #include "Hardware/Intellibox2.h" #include "Hardware/LocoNetAdapter63120.h" #include "Hardware/LocoNetAdapter63820.h" #include "Hardware/M6051.h" #include "Hardware/MasterControl.h" #include "Hardware/MasterControl2.h" #include "Hardware/OpenDcc.h" #include "Hardware/RedBox.h" #include "Hardware/Rektor.h" #include "Hardware/SystemControl7.h" #include "Hardware/TwinCenter.h" #include "Hardware/Virtual.h" #include "Hardware/Z21.h" #include "Utils/Utils.h" using std::string; namespace Hardware { const std::string HardwareHandler::Unknown = Languages::GetText(Languages::TextUnknownHardware); void HardwareHandler::Init(const HardwareParams* params) { this->params = params; HardwareType type = params->GetHardwareType(); switch(type) { case HardwareTypeNone: instance = nullptr; return; case HardwareTypeVirtual: instance = reinterpret_cast(new Virtual(params)); return; case HardwareTypeCS2Udp: instance = reinterpret_cast(new CS2Udp(params)); return; case HardwareTypeM6051: instance = reinterpret_cast(new M6051(params)); return; case HardwareTypeOpenDcc: instance = reinterpret_cast(new OpenDcc(params)); return; case HardwareTypeHsi88: instance = reinterpret_cast(new Hsi88(params)); return; case HardwareTypeZ21: instance = reinterpret_cast(new Z21(params)); return; case HardwareTypeCcSchnitte: instance = reinterpret_cast(new CcSchnitte(params)); return; case HardwareTypeEcos: instance = reinterpret_cast(new Ecos(params)); return; case HardwareTypeCS2Tcp: instance = reinterpret_cast(new CS2Tcp(params)); return; case HardwareTypeIntellibox: instance = reinterpret_cast(new Intellibox(params)); break; case HardwareTypeMasterControl: instance = reinterpret_cast(new MasterControl(params)); break; case HardwareTypeTwinCenter: instance = reinterpret_cast(new TwinCenter(params)); break; case HardwareTypeMasterControl2: instance = reinterpret_cast(new MasterControl2(params)); break; case HardwareTypeRedBox: instance = reinterpret_cast(new RedBox(params)); break; case HardwareTypeRektor: instance = reinterpret_cast(new Rektor(params)); return; case HardwareTypeDR5000: instance = reinterpret_cast(new DR5000(params)); return; case HardwareTypeCS1: instance = reinterpret_cast(new CS1(params)); return; case HardwareTypeDccPpExTcp: instance = reinterpret_cast(new DccPpExTcp(params)); return; case HardwareTypeDccPpExSerial: instance = reinterpret_cast(new DccPpExSerial(params)); return; case HardwareTypeIntellibox2: instance = reinterpret_cast(new Intellibox2(params)); break; case HardwareTypeLocoNetAdapter63120: instance = reinterpret_cast(new LocoNetAdapter63120(params)); break; case HardwareTypeLocoNetAdapter63820: instance = reinterpret_cast(new LocoNetAdapter63820(params)); break; case HardwareTypeSystemControl7: instance = reinterpret_cast(new SystemControl7(params)); break; } } void HardwareHandler::Close() { delete(instance); instance = nullptr; params = nullptr; } const std::string& HardwareHandler::GetName() const { if (!instance) { return Unknown; } return instance->GetFullName(); } const std::string& HardwareHandler::GetShortName() const { if (!instance) { return Unknown; } return instance->GetShortName(); } Hardware::Capabilities HardwareHandler::GetCapabilities() const { if (!instance) { return Hardware::CapabilityNone; } return instance->GetCapabilities(); } void HardwareHandler::LocoProtocols(std::vector& protocols) const { if (!instance) { protocols.push_back(ProtocolNone); return; } instance->GetLocoProtocols(protocols); } bool HardwareHandler::LocoProtocolSupported(Protocol protocol) const { if (!instance) { return false; } return instance->LocoProtocolSupported(protocol); } void HardwareHandler::AccessoryProtocols(std::vector& protocols) const { if (!instance) { protocols.push_back(ProtocolNone); return; } instance->GetAccessoryProtocols(protocols); } bool HardwareHandler::AccessoryProtocolSupported(Protocol protocol) const { if (!instance) { return false; } return instance->AccessoryProtocolSupported(protocol); } void HardwareHandler::Booster(const ControlType controlType, const BoosterState status) { if (controlType == ControlTypeHardware || !instance) { return; } instance->Booster(status); } void HardwareHandler::LocoBaseSpeed(const ControlType controlType, const DataModel::LocoBase* loco, const Speed speed) { if (controlType == ControlTypeHardware || !instance || loco->GetControlID() != GetControlID()) { return; } instance->LocoSpeed(loco->GetProtocol(), loco->GetAddress(), speed); } void HardwareHandler::LocoBaseOrientation(const ControlType controlType, const DataModel::LocoBase* loco, const Orientation orientation) { if (controlType == ControlTypeHardware || !instance || loco->GetControlID() != GetControlID()) { return; } instance->LocoOrientation(loco->GetProtocol(), loco->GetAddress(), orientation); } void HardwareHandler::LocoBaseFunction(const ControlType controlType, const DataModel::LocoBase* loco, const DataModel::LocoFunctionNr function, const DataModel::LocoFunctionState on) { if (controlType == ControlTypeHardware || !instance || loco->GetControlID() != GetControlID()) { return; } instance->LocoFunction(loco->GetProtocol(), loco->GetAddress(), function, on); } void HardwareHandler::LocoBaseSpeedOrientationFunctions(const DataModel::LocoBase* loco, const Speed speed, const Orientation orientation, std::vector& functions) { if (!instance || loco->GetControlID() != GetControlID()) { return; } instance->LocoSpeedOrientationFunctions(loco->GetProtocol(), loco->GetAddress(), speed, orientation, functions); } void HardwareHandler::LocoSettings(const LocoID locoId, __attribute__((unused)) const std::string& name, const std::string& matchKey) { if (!instance) { return; } instance->SetLocoIdOfMatchKey(locoId, matchKey); } void HardwareHandler::LocoDelete(__attribute__((unused)) const LocoID locoID, __attribute__((unused)) const std::string& name, const std::string& matchKey) { if (!instance) { return; } instance->SetLocoIdOfMatchKey(LocoNone, matchKey); } void HardwareHandler::AccessorySettings(const AccessoryID accessoryId, __attribute__((unused)) const std::string& name, const std::string& matchKey) { if (!instance) { return; } instance->SetAccessoryIdentifierOfMatchKey(DataModel::ObjectIdentifier(ObjectTypeAccessory, accessoryId), matchKey); } void HardwareHandler::AccessoryDelete(__attribute__((unused)) const AccessoryID accessoryId, __attribute__((unused)) const std::string& name, const std::string& matchKey) { if (!instance) { return; } instance->SetAccessoryIdentifierOfMatchKey(DataModel::ObjectIdentifier(ObjectTypeNone, ObjectNone), matchKey); } void HardwareHandler::AccessoryState(const ControlType controlType, const DataModel::Accessory* accessory) { if (controlType == ControlTypeHardware || !instance || !accessory || accessory->GetControlID() != this->GetControlID()) { return; } const Protocol protocol = accessory->GetProtocol(); const Address address = accessory->GetAddress(); const DataModel::AccessoryState state = accessory->GetInvertedAccessoryState(); const DataModel::AccessoryPulseDuration duration = accessory->GetAccessoryPulseDuration(); switch (accessory->GetAccessoryType() & DataModel::AccessoryTypeConnectionMask) { case DataModel::AccessoryTypeOnOn: { AccessoryBaseState(protocol, address, state, duration); break; } case DataModel::AccessoryTypeOnPush: case DataModel::AccessoryTypeOnOff: { const AddressPort port = accessory->GetPort(); const DataModel::AccessoryState portAsState = static_cast(port); if (state == DataModel::AccessoryStateOn) { instance->Accessory(protocol, address, portAsState, true, duration); } else { instance->Accessory(protocol, address, portAsState, false, 0); } break; } default: break; } } void HardwareHandler::AccessoryBaseState(const Protocol protocol, const Address address, const DataModel::AccessoryState state, const DataModel::AccessoryPulseDuration duration) { instance->Accessory(protocol, address, state, true, duration); __attribute((unused)) auto r = std::async(std::launch::async, AccessoryBaseStateStatic, instance, protocol, address, state, duration); } void HardwareHandler::SwitchSettings(const SwitchID switchId, __attribute__((unused)) const std::string& name, const std::string& matchKey) { if (!instance) { return; } instance->SetAccessoryIdentifierOfMatchKey(DataModel::ObjectIdentifier(ObjectTypeSwitch, switchId), matchKey); } void HardwareHandler::SwitchDelete(__attribute__((unused)) const SwitchID switchId, __attribute__((unused)) const std::string& name, const std::string& matchKey) { if (!instance) { return; } instance->SetAccessoryIdentifierOfMatchKey(DataModel::ObjectIdentifier(ObjectTypeNone, ObjectNone), matchKey); } void HardwareHandler::SwitchState(const ControlType controlType, const DataModel::Switch* mySwitch) { if (controlType == ControlTypeHardware || !instance || !mySwitch || mySwitch->GetControlID() != this->GetControlID()) { return; } const Protocol protocol = mySwitch->GetProtocol(); const Address address = mySwitch->GetAddress(); const DataModel::AccessoryPulseDuration duration = mySwitch->GetAccessoryPulseDuration(); if (mySwitch->GetAccessoryType() != DataModel::SwitchTypeThreeWay) { AccessoryBaseState(protocol, address, mySwitch->GetInvertedAccessoryState(), duration); return; } switch (mySwitch->GetAccessoryState()) { case DataModel::SwitchStateTurnout: AccessoryBaseState(protocol, address + 1, mySwitch->CalculateInvertedAccessoryState(DataModel::SwitchStateStraight), duration); AccessoryBaseState(protocol, address, mySwitch->CalculateInvertedAccessoryState(DataModel::SwitchStateTurnout), duration); break; case DataModel::SwitchStateStraight: AccessoryBaseState(protocol, address, mySwitch->CalculateInvertedAccessoryState(DataModel::SwitchStateStraight), duration); AccessoryBaseState(protocol, address + 1, mySwitch->CalculateInvertedAccessoryState(DataModel::SwitchStateStraight), duration); break; case DataModel::SwitchStateThird: AccessoryBaseState(protocol, address, mySwitch->CalculateInvertedAccessoryState(DataModel::SwitchStateStraight), duration); AccessoryBaseState(protocol, address + 1, mySwitch->CalculateInvertedAccessoryState(DataModel::SwitchStateTurnout), duration); break; default: break; } } void HardwareHandler::SignalSettings(const SignalID signalId, __attribute__((unused)) const std::string& name, const std::string& matchKey) { if (!instance) { return; } instance->SetAccessoryIdentifierOfMatchKey(DataModel::ObjectIdentifier(ObjectTypeSignal, signalId), matchKey); } void HardwareHandler::SignalDelete(__attribute__((unused)) const SignalID signalId, __attribute__((unused)) const std::string& name, const std::string& matchKey) { if (!instance) { return; } instance->SetAccessoryIdentifierOfMatchKey(DataModel::ObjectIdentifier(ObjectTypeNone, ObjectNone), matchKey); } void HardwareHandler::SignalState(const ControlType controlType, const DataModel::Signal* signal) { if (controlType == ControlTypeHardware || !instance || !signal || signal->GetControlID() != this->GetControlID()) { return; } const Address address = signal->GetMappedAddress(); if (address == 0) { return; } const DataModel::AccessoryState state = signal->GetMappedAccessoryState(); AccessoryBaseState(signal->GetProtocol(), address, state, signal->GetAccessoryPulseDuration()); } bool HardwareHandler::ProgramCheckValues(const ProgramMode mode, const CvNumber cv, const CvValue value) { if (cv == 0) { // CVs are one based, so CV zero is not allowed return false; } if (cv == 1 && value == 0) { // loco/accessory address zero is not allowed for DCC decoders and can not be undone. // It would destroy some DCC decoders. Therefore we do not allow this. return false; } CvNumber maxCv; switch (mode) { case ProgramModeMm: case ProgramModeMmPom: maxCv = 0x100; break; case ProgramModeDccRegister: case ProgramModeDccPage: case ProgramModeDccDirect: case ProgramModeDccPomLoco: case ProgramModeDccPomAccessory: maxCv = 0x400; break; case ProgramModeMfx: maxCv = 0x4000; break; default: return false; } return (cv <= maxCv); } void HardwareHandler::ProgramRead(const ProgramMode mode, const Address address, const CvNumber cv) { if (!ProgramCheckValues(mode, cv)) { return; } if (!instance) { return; } instance->ProgramRead(mode, address, cv); } void HardwareHandler::ProgramWrite(const ProgramMode mode, const Address address, const CvNumber cv, const CvValue value) { if (!ProgramCheckValues(mode, cv, value)) { return; } if (!instance) { return; } instance->ProgramWrite(mode, address, cv, value); } void HardwareHandler::FeedbackDelete(const FeedbackID feedbackID, const std::string& name) { if (!instance) { return; } instance->FeedbackDelete(feedbackID, name); } void HardwareHandler::FeedbackSettings(const FeedbackID feedbackID, const std::string& name) { if (!instance) { return; } instance->FeedbackSettings(feedbackID, name); } void HardwareHandler::AddUnmatchedLocos(std::map& list) const { if (!instance) { return; } const std::map& database = instance->GetLocoDatabase(); for (auto& entry : database) { const Hardware::LocoCacheEntry& loco = entry.second; if (loco.GetLocoID() != LocoNone) { continue; } if (loco.GetType() != LocoTypeLoco) { continue; } list[loco.GetName() + " (" + instance->GetShortName() + ")"] = loco; } } std::map HardwareHandler::GetUnmatchedLocos(const std::string& matchKey) const { std::map out; if (!instance) { return out; } const std::map& database = instance->GetLocoDatabase(); for (auto& entry : database) { const Hardware::LocoCacheEntry& loco = entry.second; if ((loco.GetType() != LocoTypeLoco) && (loco.GetLocoID() != LocoNone) && (loco.GetMatchKey().size()) && (loco.GetMatchKey() != matchKey)) { continue; } out[loco.GetName()] = loco; } return out; } DataModel::LocoConfig HardwareHandler::GetLocoByMatchKey(const std::string& match) const { if (!instance) { return DataModel::LocoConfig(LocoTypeLoco); } return instance->GetLocoByMatchKey(match); } void HardwareHandler::AddUnmatchedMultipleUnits(std::map& list) const { if (!instance) { return; } const std::map& database = instance->GetLocoDatabase(); for (auto& entry : database) { const Hardware::LocoCacheEntry& loco = entry.second; if (loco.GetLocoID() != LocoNone) { continue; } if (loco.GetType() != LocoTypeMultipleUnit) { continue; } list[loco.GetName() + " (" + instance->GetShortName() + ")"] = loco; } } std::map HardwareHandler::GetUnmatchedMultipleUnits(const std::string& matchKey) const { std::map out; if (!instance) { return out; } const std::map& database = instance->GetLocoDatabase(); for (auto& entry : database) { const Hardware::LocoCacheEntry& loco = entry.second; if ((loco.GetType() != LocoTypeMultipleUnit) && (loco.GetLocoID() != LocoNone) && (loco.GetMatchKey().size()) && (loco.GetMatchKey() != matchKey)) { continue; } out[loco.GetName()] = loco; } return out; } DataModel::LocoConfig HardwareHandler::GetMultipleUnitByMatchKey(const std::string& match) const { if (!instance) { return DataModel::LocoConfig(LocoTypeMultipleUnit); } return instance->GetMultipleUnitByMatchKey(match); } void HardwareHandler::AddUnmatchedAccessories(std::map& list) const { if (!instance) { return; } const std::map& database = instance->GetAccessoryDatabase(); for (auto& entry : database) { const Hardware::AccessoryCacheEntry& accessory = entry.second; const DataModel::ObjectIdentifier objectIdentifier = accessory.GetObjectIdentifier(); if (objectIdentifier.GetObjectType() != ObjectTypeNone) { continue; } list[accessory.GetName() + " (" + instance->GetShortName() + ")"] = accessory; } } std::map HardwareHandler::GetUnmatchedAccessories(const std::string& matchKey) const { std::map out; if (!instance) { return out; } const std::map& database = instance->GetAccessoryDatabase(); for (auto& entry : database) { const Hardware::AccessoryCacheEntry& accessory = entry.second; if (accessory.GetObjectIdentifier().GetObjectID() != ObjectNone && accessory.GetMatchKey().size() && accessory.GetMatchKey() != matchKey) { continue; } out[accessory.GetName()] = accessory; } return out; } DataModel::AccessoryConfig HardwareHandler::GetAccessoryByMatchKey(const std::string& match) const { if (!instance) { return DataModel::AccessoryConfig(); } return instance->GetAccessoryByMatchKey(match); } void HardwareHandler::AddUnmatchedFeedbacks(std::map& list) const { if (!instance) { return; } const std::map& database = instance->GetFeedbackDatabase(); for (auto& entry : database) { const Hardware::FeedbackCacheEntry& feedback = entry.second; if (feedback.GetFeedbackID() != FeedbackNone) { continue; } list[feedback.GetName() + " (" + instance->GetShortName() + ")"] = feedback; } } std::map HardwareHandler::GetUnmatchedFeedbacks(const std::string& matchKey) const { std::map out; if (!instance) { return out; } const std::map& database = instance->GetFeedbackDatabase(); for (auto& entry : database) { const Hardware::FeedbackCacheEntry& feedback = entry.second; if (feedback.GetFeedbackID() != FeedbackNone && feedback.GetMatchKey().size() && feedback.GetMatchKey() != matchKey) { continue; } out[feedback.GetName()] = feedback; } return out; } DataModel::FeedbackConfig HardwareHandler::GetFeedbackByMatchKey(const std::string& match) const { if (!instance) { return DataModel::FeedbackConfig(); } return instance->GetFeedbackByMatchKey(match); } void HardwareHandler::ArgumentTypesOfHardwareTypeAndHint(const HardwareType hardwareType, std::map& arguments, std::string& hint) { switch (hardwareType) { case HardwareTypeNone: hint = ""; return; case HardwareTypeVirtual: Hardware::Virtual::GetHint(hint); return; case HardwareTypeCS2Udp: Hardware::CS2Udp::GetArgumentTypesAndHint(arguments, hint); return; case HardwareTypeM6051: Hardware::M6051::GetArgumentTypesAndHint(arguments, hint); return; case HardwareTypeOpenDcc: Hardware::OpenDcc::GetArgumentTypesAndHint(arguments, hint); return; case HardwareTypeHsi88: Hardware::Hsi88::GetArgumentTypesAndHint(arguments, hint); return; case HardwareTypeDR5000: case HardwareTypeRektor: case HardwareTypeZ21: Hardware::Protocols::Z21::GetArgumentTypesAndHint(arguments, hint); return; case HardwareTypeCcSchnitte: Hardware::CcSchnitte::GetArgumentTypesAndHint(arguments, hint); return; case HardwareTypeEcos: Hardware::Protocols::EsuCAN::GetArgumentTypesAndHint(arguments, hint); return; case HardwareTypeCS2Tcp: Hardware::CS2Tcp::GetArgumentTypesAndHint(arguments, hint); return; case HardwareTypeIntellibox: Hardware::Intellibox::GetArgumentTypesAndHint(arguments, hint); return; case HardwareTypeMasterControl: Hardware::MasterControl::GetArgumentTypesAndHint(arguments, hint); return; case HardwareTypeTwinCenter: Hardware::TwinCenter::GetArgumentTypesAndHint(arguments, hint); return; case HardwareTypeMasterControl2: Hardware::MasterControl2::GetArgumentTypesAndHint(arguments, hint); return; case HardwareTypeRedBox: Hardware::RedBox::GetArgumentTypesAndHint(arguments, hint); return; case HardwareTypeCS1: Hardware::Protocols::EsuCAN::GetArgumentTypesAndHint(arguments, hint); return; case HardwareTypeDccPpExTcp: Hardware::DccPpExTcp::GetArgumentTypesAndHint(arguments, hint); return; case HardwareTypeDccPpExSerial: Hardware::DccPpExSerial::GetArgumentTypesAndHint(arguments, hint); return; case HardwareTypeIntellibox2: Hardware::Intellibox2::GetArgumentTypesAndHint(arguments, hint); return; case HardwareTypeLocoNetAdapter63120: Hardware::LocoNetAdapter63120::GetArgumentTypesAndHint(arguments, hint); return; case HardwareTypeLocoNetAdapter63820: Hardware::LocoNetAdapter63820::GetArgumentTypesAndHint(arguments, hint); return; case HardwareTypeSystemControl7: Hardware::SystemControl7::GetArgumentTypesAndHint(arguments, hint); return; } } } // namespace Hardware railcontrol-24+dfsg1/Hardware/HardwareHandler.h000066400000000000000000000153371500456250600216060ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "ControlInterface.h" #include "DataModel/LocoFunctions.h" #include "DataTypes.h" #include "Hardware/HardwareInterface.h" #include "Hardware/HardwareParams.h" #include "Hardware/LocoCache.h" #include "Manager.h" namespace Hardware { class HardwareHandler: public ControlInterface { public: HardwareHandler() = delete; HardwareHandler(const HardwareHandler&) = delete; HardwareHandler& operator=(const HardwareHandler&) = delete; inline HardwareHandler(const HardwareParams* params) : ControlInterface(ControlTypeHardware), instance(nullptr), params(nullptr) { Init(params); } inline ~HardwareHandler() { Close(); } virtual void Start() override { if (instance == nullptr) { return; } instance->Start(); } inline void ReInit(const HardwareParams* params) override { Close(); Init(params); Start(); } inline ControlID GetControlID() const { return params->GetControlID(); } const std::string& GetName() const override; const std::string& GetShortName() const override; void AccessoryProtocols(std::vector& protocols) const override; bool AccessoryProtocolSupported(Protocol protocol) const override; void AccessoryState(const ControlType controlType, const DataModel::Accessory* accessory) override; void Booster(const ControlType controlType, BoosterState status) override; Hardware::Capabilities GetCapabilities() const override; void LocoBaseOrientation(const ControlType controlType, const DataModel::LocoBase* loco, const Orientation orientation) override; void LocoBaseFunction(const ControlType controlType, const DataModel::LocoBase* loco, const DataModel::LocoFunctionNr function, const DataModel::LocoFunctionState on) override; void LocoProtocols(std::vector& protocols) const override; bool LocoProtocolSupported(Protocol protocol) const override; void LocoBaseSpeed(const ControlType controlType, const DataModel::LocoBase* loco, const Speed speed) override; void LocoBaseSpeedOrientationFunctions(const DataModel::LocoBase* loco, const Speed speed, const Orientation orientation, std::vector& functions) override; void LocoSettings(const LocoID locoId, const std::string& name, const std::string& matchKey) override; void LocoDelete(const LocoID locoId, const std::string& name, const std::string& matchKey) override; void AccessorySettings(const AccessoryID accessoryId, const std::string& name, const std::string& matchKey) override; void AccessoryDelete(const AccessoryID accessoryId, const std::string& name, const std::string& matchKey) override; void SwitchSettings(const SwitchID switchId, const std::string& name, const std::string& matchKey) override; void SwitchDelete(const SwitchID switchId, const std::string& name, const std::string& matchKey) override; void SwitchState(const ControlType controlType, const DataModel::Switch* mySwitch) override; void SignalSettings(const SignalID signalId, const std::string& name, const std::string& matchKey) override; void SignalDelete(const SignalID signalId, const std::string& name, const std::string& matchKey) override; void SignalState(const ControlType controlType, const DataModel::Signal* signal) override; void ProgramRead(const ProgramMode mode, const Address address, const CvNumber cv) override; void ProgramWrite(const ProgramMode mode, const Address address, const CvNumber cv, const CvValue value) override; void FeedbackDelete(const FeedbackID feedbackID, const std::string& name) override; // FIXME: matchKey is missing void FeedbackSettings(const FeedbackID feedbackID, const std::string& name) override; void AddUnmatchedLocos(std::map& list) const override; std::map GetUnmatchedLocos(const std::string& matchKey) const override; DataModel::LocoConfig GetLocoByMatchKey(const std::string& match) const override; void AddUnmatchedMultipleUnits(std::map& list) const override; std::map GetUnmatchedMultipleUnits(const std::string& matchKey) const override; DataModel::LocoConfig GetMultipleUnitByMatchKey(const std::string& match) const override; void AddUnmatchedAccessories(std::map& list) const override; std::map GetUnmatchedAccessories(const std::string& matchKey) const override; DataModel::AccessoryConfig GetAccessoryByMatchKey(const std::string& match) const override; void AddUnmatchedFeedbacks(std::map& list) const override; std::map GetUnmatchedFeedbacks(const std::string& matchKey) const override; DataModel::FeedbackConfig GetFeedbackByMatchKey(const std::string& match) const override; static void ArgumentTypesOfHardwareTypeAndHint(const HardwareType hardwareType, std::map& arguments, std::string& hint); private: Hardware::HardwareInterface* instance; const HardwareParams* params; static const std::string Unknown; void Init(const HardwareParams* params); void Close(); bool ProgramCheckValues(const ProgramMode mode, const CvNumber cv, const CvValue value = 1); void AccessoryBaseState(const Protocol protocol, const Address address, const DataModel::AccessoryState state, const DataModel::AccessoryPulseDuration duration); static inline void AccessoryBaseStateStatic(HardwareInterface* instance, const Protocol protocol, const Address address, const DataModel::AccessoryState state, const DataModel::AccessoryPulseDuration duration) { Utils::Utils::SleepForMilliseconds(duration); instance->Accessory(protocol, address, state, false, 0); } }; } // namespace Hardware railcontrol-24+dfsg1/Hardware/HardwareInterface.h000066400000000000000000000167321500456250600221310ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include #include "DataModel/AccessoryBase.h" #include "DataModel/Loco.h" #include "DataTypes.h" #include "Hardware/AccessoryCache.h" #include "Hardware/Capabilities.h" #include "Hardware/LocoCache.h" #include "Manager.h" #include "Utils/Utils.h" namespace Hardware { class HardwareInterface { public: HardwareInterface() = delete; HardwareInterface(const HardwareInterface&) = delete; HardwareInterface& operator=(const HardwareInterface&) = delete; // non virtual constructor is needed to prevent polymorphism inline HardwareInterface(Manager* manager, const ControlID controlID, const std::string& fullName, const std::string& shortName) : manager(manager), controlID(controlID), logger(Logger::Logger::GetLogger(shortName)), fullName(fullName), shortName(shortName) { } virtual ~HardwareInterface() { } virtual void Start() { } // get the full name of the hardware inline const std::string& GetFullName() const { return fullName; } // get the short name of the hardware inline const std::string& GetShortName() const { return shortName; } // get hardware capabilities virtual Hardware::Capabilities GetCapabilities() const { return Hardware::CapabilityNone; } // get available loco protocols of this control virtual void GetLocoProtocols(__attribute__((unused)) std::vector& protocols) const { } // is given loco protocol supported virtual bool LocoProtocolSupported(__attribute__((unused)) const Protocol protocol) const { return false; } // get available accessory protocols of this control virtual void GetAccessoryProtocols(__attribute__((unused)) std::vector& protocols) const { } // is given accessory protocol supported virtual bool AccessoryProtocolSupported(__attribute__((unused)) const Protocol protocol) const { return false; } // turn booster on or off virtual void Booster(__attribute__((unused)) const BoosterState status) { } // set loco speed virtual void LocoSpeed(__attribute__((unused)) const Protocol protocol, __attribute__((unused)) const Address address, __attribute__((unused)) const Speed speed) { } // set loco orientation virtual void LocoOrientation(__attribute__((unused)) const Protocol protocol, __attribute__((unused)) const Address address, __attribute__((unused)) const Orientation orientation) { } // set loco function virtual void LocoFunction(__attribute__((unused)) const Protocol protocol, __attribute__((unused)) const Address address, __attribute__((unused)) const DataModel::LocoFunctionNr function, __attribute__((unused)) const DataModel::LocoFunctionState on) { } // set loco virtual void LocoSpeedOrientationFunctions(const Protocol protocol, const Address address, const Speed speed, const Orientation orientation, std::vector& functions) { // sleeps are necessary to prevent command overflow in command stations (especially Märklin Gleisbox) LocoSpeed(protocol, address, speed); Utils::Utils::SleepForMilliseconds(25); LocoOrientation(protocol, address, orientation); Utils::Utils::SleepForMilliseconds(25); for (const DataModel::LocoFunctionEntry& functionEntry : functions) { LocoFunction(protocol, address, functionEntry.nr, functionEntry.state); Utils::Utils::SleepForMilliseconds(25); } } // accessory command virtual void Accessory(__attribute__((unused)) const Protocol protocol, __attribute__((unused)) const Address address, __attribute__((unused)) const DataModel::AccessoryState state, __attribute__((unused)) const bool on, __attribute__((unused)) const DataModel::AccessoryPulseDuration duration) { } // read CV value virtual void ProgramRead(__attribute__((unused)) const ProgramMode mode, __attribute__((unused)) const Address address, __attribute__((unused)) const CvNumber cv) { } // write CV value virtual void ProgramWrite(__attribute__((unused)) const ProgramMode mode, __attribute__((unused)) const Address address, __attribute__((unused)) const CvNumber cv, __attribute__((unused)) const CvValue value) { } virtual const std::map& GetLocoDatabase() const { return emptyLocoDatabase; } virtual DataModel::LocoConfig GetLocoByMatchKey(__attribute__((unused)) const std::string& matchKey) const { return DataModel::LocoConfig(LocoTypeLoco); } virtual DataModel::LocoConfig GetMultipleUnitByMatchKey(__attribute__((unused)) const std::string& matchKey) const { return DataModel::LocoConfig(LocoTypeMultipleUnit); } virtual void SetLocoIdOfMatchKey(__attribute__((unused)) const LocoID locoId, __attribute__((unused)) const std::string& matchKey) { } virtual void SetMultipleUnitIdOfMatchKey(__attribute__((unused)) const LocoID locoId, __attribute__((unused)) const std::string& matchKey) { } virtual const std::map& GetAccessoryDatabase() const { return emptyAccessoryDatabase; } virtual DataModel::AccessoryConfig GetAccessoryByMatchKey(__attribute__((unused)) const std::string& matchKey) const { return DataModel::AccessoryConfig(); } virtual void SetAccessoryIdentifierOfMatchKey(__attribute__((unused)) const DataModel::ObjectIdentifier objectIdentifier, __attribute__((unused)) const std::string& matchKey) { } virtual const std::map& GetFeedbackDatabase() const { return emptyFeedbackDatabase; } virtual DataModel::FeedbackConfig GetFeedbackByMatchKey(__attribute__((unused)) const std::string& matchKey) const { return DataModel::FeedbackConfig(); } virtual void SetFeedbackIdOfMatchKey(__attribute__((unused)) const FeedbackID feedbackId, __attribute__((unused)) const std::string& matchKey) { } virtual void FeedbackDelete(__attribute__((unused)) const FeedbackID feedbackID, __attribute__((unused)) const std::string& name) { } virtual void FeedbackSettings(__attribute__((unused)) const FeedbackID feedbackID, __attribute__((unused)) const std::string& name) { } protected: Manager* const manager; const ControlID controlID; Logger::Logger* const logger; private: const std::string fullName; const std::string shortName; std::map emptyLocoDatabase; std::map emptyAccessoryDatabase; std::map emptyFeedbackDatabase; }; } // namespace railcontrol-24+dfsg1/Hardware/HardwareParams.h000066400000000000000000000060401500456250600214430ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include "DataTypes.h" class Manager; namespace Hardware { class HardwareParams { public: HardwareParams() = delete; HardwareParams(const HardwareParams&) = delete; HardwareParams& operator=(const HardwareParams&) = delete; inline HardwareParams(ControlID controlID, HardwareType hardwareType, std::string name, std::string arg1, std::string arg2, std::string arg3, std::string arg4, std::string arg5) : manager(nullptr), controlID(controlID), hardwareType(hardwareType), name(name), arg1(arg1), arg2(arg2), arg3(arg3), arg4(arg4), arg5(arg5) { } inline HardwareParams(Manager* manager, ControlID controlID) : manager(manager), controlID(controlID), hardwareType(HardwareTypeNone) { } inline void SetManager(Manager* manager) { this->manager = manager; } inline Manager* GetManager() const { return manager; } inline ControlID GetControlID() const { return controlID; } inline void SetName(const std::string& name) { this->name = name; } inline const std::string& GetName() const { return name; } inline void SetHardwareType(const HardwareType hardwareType) { this->hardwareType = hardwareType; } inline HardwareType GetHardwareType() const { return hardwareType; } inline void SetArg1(const std::string& arg) { this->arg1 = arg; } inline std::string GetArg1() const { return arg1; } inline void SetArg2(const std::string& arg) { this->arg2 = arg; } inline std::string GetArg2() const { return arg2; } inline void SetArg3(const std::string& arg) { this->arg3 = arg; } inline std::string GetArg3() const { return arg3; } inline void SetArg4(const std::string& arg) { this->arg4 = arg; } inline std::string GetArg4() const { return arg4; } inline void SetArg5(const std::string& arg) { this->arg5 = arg; } inline std::string GetArg5() const { return arg5; } private: Manager* manager; ControlID controlID; HardwareType hardwareType; std::string name; std::string arg1; std::string arg2; std::string arg3; std::string arg4; std::string arg5; }; } // namespace Hardware railcontrol-24+dfsg1/Hardware/Hsi88.cpp000066400000000000000000000121621500456250600200020ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include //memset #include #include #include #include "Languages.h" #include "Network/Select.h" #include "Hardware/Hsi88.h" #include "Utils/Integer.h" namespace Hardware { Hsi88::Hsi88(const HardwareParams* params) : HardwareInterface(params->GetManager(), params->GetControlID(), "HSI-88 / " + params->GetName() + " at serial port " + params->GetArg1(), params->GetName()), serialLine(logger, params->GetArg1(), B9600, 8, 'N', 1), run(false) { logger->Info(Languages::TextStarting, GetFullName()); memset(s88Init, 0xFF, sizeof(s88Memory)); memset(s88Memory, 0x00, sizeof(s88Memory)); s88Modules1 = Utils::Integer::StringToInteger(params->GetArg2(), 0); s88Modules2 = Utils::Integer::StringToInteger(params->GetArg3(), 0); s88Modules3 = Utils::Integer::StringToInteger(params->GetArg4(), 0); s88Modules = s88Modules1 + s88Modules2 + s88Modules3; if (s88Modules > MaxS88Modules) { logger->Error(Languages::TextTooManyS88Modules, s88Modules, MaxS88Modules); return; } if (s88Modules == 0) { logger->Error(Languages::TextNoS88Modules); return; } std::string version = GetVersion(); logger->Info(Languages::TextVersion, version); unsigned char modulesConfigured = ConfigureS88(); if (s88Modules != modulesConfigured) { logger->Error(Languages::TextHsi88ErrorConfiguring, modulesConfigured); return; } logger->Info(Languages::TextHsi88Configured, s88Modules, s88Modules1, s88Modules2, s88Modules3); checkEventsThread = std::thread(&Hardware::Hsi88::CheckEventsWorker, this); } Hsi88::~Hsi88() { if (!run) { return; } run = false; checkEventsThread.join(); } std::string Hsi88::ReadUntilCR() { std::string data; unsigned char input = 0; while (true) { int ret = serialLine.Receive(&input, sizeof(input), 1, 0); if (ret < 0 || input == '\r') { logger->HexIn(data); return data; } data.append(reinterpret_cast(&input), ret); } } std::string Hsi88::GetVersion() { const unsigned char command[2] = { 'v', '\r' }; SendData(command, sizeof(command)); return ReadUntilCR(); } unsigned char Hsi88::ConfigureS88() { const unsigned char command [5] = { 's', static_cast(s88Modules1 >> 1), static_cast(s88Modules2 >> 1), static_cast(s88Modules3 >> 1), '\r' }; SendData(command, sizeof(command)); unsigned char input[3]; int ret = serialLine.ReceiveExact(input, sizeof(input)); logger->HexIn(input, sizeof(input)); if (ret <= 0) { return 0; } if (input[0] != 's') { return 0; } return input[1] << 1; } void Hsi88::ReadData() { std::string data; const unsigned char headerDataSize = 3; serialLine.ReceiveExact(data, headerDataSize); if (data.size() != headerDataSize) { return; } if (data[0] != 'i') { return; } const unsigned char modules = data[1]; const unsigned char moduleDataSize = modules * 3; const unsigned char commandSize = moduleDataSize + headerDataSize; serialLine.ReceiveExact(data, moduleDataSize); logger->HexIn(data); if (data.size() != commandSize) { return; } for (unsigned char module = 0; module < modules; ++module) { const unsigned char modulePosition = module * 3 + 2; const unsigned char* moduleData = reinterpret_cast(data.c_str()) + modulePosition; if (*moduleData > (MaxS88Modules >> 1)) { continue; } const unsigned char memoryPosition = (data[modulePosition] - 1) * 2; CheckFeedbackByte(moduleData[2], memoryPosition); CheckFeedbackByte(moduleData[1], memoryPosition + 1); } } void Hsi88::CheckFeedbackByte(const unsigned char dataByte, const unsigned char module) { unsigned char diff = (s88Memory[module] ^ dataByte) | s88Init[module]; if (!diff) { return; } unsigned char pin = 0; while (diff) { if (diff & 0x01) { DataModel::Feedback::FeedbackState state = static_cast((dataByte >> pin) & 0x01); logger->Info(Languages::TextFeedbackChange, pin + 1, module, Languages::GetOnOff(state)); manager->FeedbackState(controlID, module * 8 + pin + 1, state); } diff >>= 1; ++pin; } s88Memory[module] = dataByte; s88Init[module] = 0; } void Hsi88::CheckEventsWorker() { Utils::Utils::SetThreadName("HSI-88"); run = true; while (run) { ReadData(); std::this_thread::yield(); } } } // namespace railcontrol-24+dfsg1/Hardware/Hsi88.h000066400000000000000000000045001500456250600174440ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include "HardwareInterface.h" #include "HardwareParams.h" #include "Logger/Logger.h" #include "Network/Serial.h" namespace Hardware { class Hsi88 : HardwareInterface { public: Hsi88() = delete; Hsi88(const Hsi88&) = delete; Hsi88& operator=(const Hsi88&) = delete; Hsi88(const HardwareParams* params); ~Hsi88(); inline Hardware::Capabilities GetCapabilities() const override { return Hardware::CapabilityFeedback; } static void GetArgumentTypesAndHint(std::map& argumentTypes, std::string& hint) { argumentTypes[1] = ArgumentTypeSerialPort; argumentTypes[2] = ArgumentTypeS88Modules; argumentTypes[3] = ArgumentTypeS88Modules; argumentTypes[4] = ArgumentTypeS88Modules; hint = Languages::GetText(Languages::TextHintHsi88); } private: static const unsigned char MaxS88Modules = 62; Network::Serial serialLine; volatile bool run; unsigned char s88Modules1; unsigned char s88Modules2; unsigned char s88Modules3; unsigned short s88Modules; std::thread checkEventsThread; unsigned char s88Init[MaxS88Modules]; unsigned char s88Memory[MaxS88Modules]; std::string ReadUntilCR(); std::string GetVersion(); unsigned char ConfigureS88(); void ReadData(); inline ssize_t SendData(const unsigned char* data, const size_t size) { logger->HexOut(data, size); return serialLine.Send(data, size); } void CheckFeedbackByte(const unsigned char dataByte, const unsigned char module); void CheckEventsWorker(); }; } // namespace railcontrol-24+dfsg1/Hardware/Intellibox.h000066400000000000000000000030301500456250600206470ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include "Languages.h" #include "Hardware/Protocols/P50xUhlenbrock.h" namespace Hardware { class HardwareParams; class Intellibox : public Protocols::P50xUhlenbrock { public: Intellibox() = delete; Intellibox(const Intellibox&) = delete; Intellibox& operator=(const Intellibox&) = delete; inline Intellibox(const HardwareParams* params) : Protocols::P50xUhlenbrock(params, "Intellibox") { Init(); } virtual ~Intellibox() { } static inline void GetArgumentTypesAndHint(std::map& argumentTypes, std::string& hint) { argumentTypes[1] = ArgumentTypeSerialPort; argumentTypes[2] = ArgumentTypeS88Modules; hint = Languages::GetText(Languages::TextHintIntellibox); } }; } // namespace railcontrol-24+dfsg1/Hardware/Intellibox2.h000066400000000000000000000027351500456250600207440ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include "Languages.h" #include "Hardware/Protocols/LocoNet.h" namespace Hardware { class HardwareParams; class Intellibox2 : public Protocols::LocoNet { public: Intellibox2() = delete; Intellibox2(const Intellibox2&) = delete; Intellibox2& operator=(const Intellibox2&) = delete; inline Intellibox2(const HardwareParams* params) : Protocols::LocoNet(params, "Intellibox II", B115200) { } virtual ~Intellibox2() { } static inline void GetArgumentTypesAndHint(std::map& argumentTypes, std::string& hint) { argumentTypes[1] = ArgumentTypeSerialPort; hint = Languages::GetText(Languages::TextHintIntellibox2); } }; } // namespace railcontrol-24+dfsg1/Hardware/LocoCache.cpp000066400000000000000000000045361500456250600207250ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include "Hardware/LocoCache.h" #include "Manager.h" namespace Hardware { void LocoCacheEntry::UpdateSlaves(Manager* manager) { for (auto & slave : slaves) { DataModel::Loco* loco = manager->GetLocoByMatchKey(controlId, slave.GetMatchKey()); if (!loco) { continue; } slave.SetLocoID(loco->GetID()); } } void LocoCache::Save(LocoCacheEntry& entry, const std::string& oldMatchKey) { const std::string& matchKey = entry.GetMatchKey(); DataModel::Loco* loco = nullptr; const bool matchKeyChanged = matchKey.compare(oldMatchKey) != 0; if (matchKeyChanged) { const LocoID locoId = Delete(oldMatchKey); loco = manager->GetLoco(locoId); } if (loco == nullptr) { loco = manager->GetLocoByMatchKey(GetControlId(), oldMatchKey); } if (loco == nullptr && matchKeyChanged) { loco = manager->GetLocoByMatchKey(GetControlId(), matchKey); } if (loco != nullptr) { entry.SetLocoID(loco->GetID()); *loco = entry; } entries.emplace(matchKey, entry); } LocoID LocoCache::Delete(const std::string& matchKey) { LocoID locoId = Get(matchKey).GetLocoID(); manager->LocoRemoveMatchKey(locoId); entries.erase(matchKey); return locoId; } void LocoCache::SetLocoId(const LocoID locoId, const std::string& matchKey) { if (locoId != LocoNone) { for (auto& locoCacheEntry : entries) { LocoCacheEntry& entry = locoCacheEntry.second; if (entry.GetLocoID() == locoId) { entry.SetLocoID(LocoNone); } } } auto entry = entries.find(matchKey); if (entry == entries.end()) { return; } entry->second.SetLocoID(locoId); } } // namespace Hardware railcontrol-24+dfsg1/Hardware/LocoCache.h000066400000000000000000000127301500456250600203650ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include #include #include "DataTypes.h" #include "DataModel/LocoFunctions.h" class Manager; namespace Hardware { class LocoCacheEntrySlave { public: LocoCacheEntrySlave() = delete; inline LocoCacheEntrySlave(const Protocol protocol, const Address address, const std::string matchKey) : protocol(protocol), address(address), matchKey(matchKey), locoID(LocoNone) { } inline Protocol GetProtocol() const { return protocol; } inline Address GetAddress() const { return address; } inline const std::string& GetMatchKey() const { return matchKey; } inline void SetLocoID(LocoID locoID) { this->locoID = locoID; } inline LocoID GetLocoID() const { return locoID; } private: Protocol protocol; Address address; std::string matchKey; LocoID locoID; }; class LocoCacheEntry { public: LocoCacheEntry() = delete; inline LocoCacheEntry(const ControlID controlId) : controlId(controlId), locoId(LocoNone), protocol(ProtocolNone), address(AddressNone), type(LocoTypeLoco), matchKey("") { } inline ControlID GetControlID() const { return controlId; } inline LocoID GetLocoID() const { return locoId; } inline void SetLocoID(const LocoID locoId) { this->locoId = locoId; } inline const std::string& GetName() const { return name; } inline void SetName(const std::string& name) { this->name = name; } inline Protocol GetProtocol() const { return protocol; } inline void SetProtocol(const Protocol protocol) { this->protocol = protocol; } inline Address GetAddress() const { return address; } inline void SetAddress(const Address address) { this->address = address; } inline LocoType GetType() const { return type; } inline void SetType(LocoType type) { this->type = type; } inline void SetFunction(const DataModel::LocoFunctionNr nr, const DataModel::LocoFunctionType type, const DataModel::LocoFunctionIcon icon, const DataModel::LocoFunctionTimer timer) { functions.SetFunction(nr, type, icon, timer); } inline void ClearFunction(const DataModel::LocoFunctionNr nr) { functions.ClearFunction(nr); } inline std::vector GetFunctionStates() const { return functions.GetFunctionStates(); } inline const std::string& GetMatchKey() const { return matchKey; } inline void SetMatchKey(const std::string& matchKey) { this->matchKey = matchKey; } inline void SetMatchKey(const unsigned int matchKey) { this->matchKey = std::to_string(matchKey); } inline void AddSlave(const Protocol protocol, const Address address, const std::string& matchKey) { slaves.push_back(LocoCacheEntrySlave(protocol, address, matchKey)); } void UpdateSlaves(Manager* manager); inline std::vector GetSlaveIDs() const { std::vector out; for (auto const & slave : slaves) { const LocoID slaveID = slave.GetLocoID(); if (slaveID == LocoNone) { continue; } out.push_back(slaveID); } return out; } private: const ControlID controlId; LocoID locoId; std::string name; Protocol protocol; Address address; LocoType type; std::string matchKey; DataModel::LocoFunctions functions; std::vector slaves; }; class LocoCache { public: inline LocoCache(const ControlID controlId, Manager* const manager) : controlId(controlId), manager(manager) { } LocoCache() = delete; LocoCache(const LocoCache& rhs) = delete; LocoCache& operator= (const LocoCache& rhs) = delete; inline ControlID GetControlId() const { return controlId; } inline void Save(LocoCacheEntry& entry) { const std::string& oldMatchKey = entry.GetMatchKey(); Save(entry, oldMatchKey); } void Save(LocoCacheEntry& entry, const std::string& oldMatchKey); LocoID Delete(const std::string& matchKey); inline const LocoCacheEntry Get(const std::string& matchKey) const { return entries.count(matchKey) == 0 ? LocoCacheEntry(controlId) : entries.at(matchKey); } inline const std::map& GetAll() const { return entries; } void SetLocoId(const LocoID locoId, const std::string& matckKey); inline void UpdateSlaves() { for (auto & entry : entries) { entry.second.UpdateSlaves(manager); } } private: const ControlID controlId; Manager* const manager; std::map entries; }; } // namespace Hardware railcontrol-24+dfsg1/Hardware/LocoNetAdapter63120.h000066400000000000000000000030551500456250600220050ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include "Languages.h" #include "Hardware/Protocols/LocoNet.h" namespace Hardware { class HardwareParams; class LocoNetAdapter63120 : public Protocols::LocoNet { public: LocoNetAdapter63120() = delete; LocoNetAdapter63120(const LocoNetAdapter63120&) = delete; LocoNetAdapter63120& operator=(const LocoNetAdapter63120&) = delete; inline LocoNetAdapter63120(const HardwareParams* params) : Protocols::LocoNet(params, "LocoNet Adapter 63120", B115200) { } virtual ~LocoNetAdapter63120() { } static inline void GetArgumentTypesAndHint(std::map& argumentTypes, std::string& hint) { argumentTypes[1] = ArgumentTypeSerialPort; hint = Languages::GetText(Languages::TextHintLocoNetAdapter63120); } }; } // namespace railcontrol-24+dfsg1/Hardware/LocoNetAdapter63820.h000066400000000000000000000030551500456250600220140ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include "Languages.h" #include "Hardware/Protocols/LocoNet.h" namespace Hardware { class HardwareParams; class LocoNetAdapter63820 : public Protocols::LocoNet { public: LocoNetAdapter63820() = delete; LocoNetAdapter63820(const LocoNetAdapter63820&) = delete; LocoNetAdapter63820& operator=(const LocoNetAdapter63820&) = delete; inline LocoNetAdapter63820(const HardwareParams* params) : Protocols::LocoNet(params, "LocoNet Adapter 63820", B115200) { } virtual ~LocoNetAdapter63820() { } static inline void GetArgumentTypesAndHint(std::map& argumentTypes, std::string& hint) { argumentTypes[1] = ArgumentTypeSerialPort; hint = Languages::GetText(Languages::TextHintLocoNetAdapter63820); } }; } // namespace railcontrol-24+dfsg1/Hardware/M6051.cpp000066400000000000000000000137171500456250600176160ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include //memset #include #include #include "Network/Select.h" #include "Hardware/M6051.h" #include "Utils/Integer.h" using std::string; namespace Hardware { M6051::M6051(const HardwareParams* params) : HardwareInterface(params->GetManager(), params->GetControlID(), "Maerklin Interface (6050/6051) / " + params->GetName() + " at serial port " + params->GetArg1(), params->GetName()), serialLine(logger, params->GetArg1(), B2400, 8, 'N', 2), run(true) { logger->Info(Languages::TextStarting, GetFullName()); s88Modules = Utils::Integer::StringToInteger(params->GetArg2(), 0, MaxS88Modules); if (s88Modules == 0) { logger->Info(Languages::TextNoS88Modules); return; } logger->Info(Languages::TextNrOfS88Modules, s88Modules); s88Thread = std::thread(&Hardware::M6051::S88Worker, this); } M6051::~M6051() { run = false; if (s88Modules > 0) { s88Thread.join(); } } void M6051::Booster(const BoosterState status) { if (!serialLine.IsConnected()) { return; } unsigned char c; if (status) { logger->Info(Languages::TextTurningBoosterOn); c = 96; } else { logger->Info(Languages::TextTurningBoosterOn); c = 97; } serialLine.Send(c); } void M6051::LocoSpeed(__attribute__((unused)) const Protocol protocol, const Address address, const Speed speed) { if (!serialLine.IsConnected()) { return; } unsigned char speedMM = (speed / 69) + (GetSpeedMapEntry(address) & 16); speedMap[address] = speedMM; unsigned char addressMM = static_cast(address); logger->Info(Languages::TextSettingSpeed, address, speedMM); SendTwoBytes(speedMM, addressMM); } void M6051::LocoOrientation(__attribute__((unused)) const Protocol protocol, const Address address, __attribute__((unused)) const Orientation orientation) { if (!serialLine.IsConnected()) { return; } logger->Info(Languages::TextSettingDirectionOfTravel, address); unsigned char speedMM = 15 + (GetSpeedMapEntry(address) & 16); unsigned char addressMM = static_cast(address); SendTwoBytes(speedMM, addressMM); } void M6051::LocoFunction(__attribute__((unused)) const Protocol protocol, const Address address, const DataModel::LocoFunctionNr function, const DataModel::LocoFunctionState on) { if (function > 4) { return; } if (!serialLine.IsConnected()) { return; } logger->Info(Languages::TextSettingFunction, static_cast(function), address, Languages::GetOnOff(on)); unsigned char addressMM = static_cast(address); if (function == 0) { unsigned char speedMM = (GetSpeedMapEntry(address) & 15) + (static_cast(on) << 4); speedMap[address] = speedMM; SendTwoBytes(speedMM, addressMM); return; } unsigned char functionMM = GetFunctionMapEntry(address); unsigned char position = function - 1; functionMM &= (~(1 << position)); // mask out related function functionMM |= (static_cast(on == DataModel::LocoFunctionStateOn) << position); // add related function functionMap[address] = functionMM; functionMM += 64; SendTwoBytes(functionMM, addressMM); } void M6051::Accessory(__attribute__((unused)) const Protocol protocol, const Address address, const DataModel::AccessoryState state, const bool on, __attribute__((unused)) const DataModel::AccessoryPulseDuration duration) { if (!serialLine.IsConnected()) { return; } logger->Info(Languages::TextSettingAccessoryOnOff, address, Languages::GetGreenRed(state), Languages::GetOnOff(on)); const unsigned char stateMM = (on ? (state == DataModel::AccessoryStateOn ? 33 : 34) : 32); const unsigned char addressMM = static_cast(address); SendTwoBytes(stateMM, addressMM); } void M6051::S88Worker() { Utils::Utils::SetThreadName("M6051"); const unsigned char s88DoubleModules = ((s88Modules + 1) / 2); const unsigned char command = 128 + s88DoubleModules; const unsigned char s88SingleModules = (s88DoubleModules * 2); while(run && serialLine.IsConnected()) { serialLine.ClearBuffers(); serialLine.Send(command); for (unsigned char module = 0; module < s88SingleModules; ++module) { string data; bool ret = serialLine.Receive(data, 1); if (ret <= 0) { logger->Error(Languages::TextUnableToReceiveData); break; } unsigned char byte = data[0]; if (byte != s88Memory[module]) { unsigned char xorByte = byte ^ s88Memory[module]; for (unsigned char pin = 1; pin <= 8; ++pin) { unsigned char shift = 8 - pin; if (((xorByte >> shift) & 0x01) == 0) { continue; } const char* onOff; DataModel::Feedback::FeedbackState state; if ((byte >> shift) & 0x01) { onOff = Languages::GetText(Languages::TextOn); state = DataModel::Feedback::FeedbackStateOccupied; } else { onOff = Languages::GetText(Languages::TextOff); state = DataModel::Feedback::FeedbackStateFree; } logger->Info(Languages::TextFeedbackChange, pin, module, onOff); Address address = (module * 8) + pin; manager->FeedbackState(controlID, address, state); } s88Memory[module] = byte; } } std::this_thread::yield(); } } } // namespace railcontrol-24+dfsg1/Hardware/M6051.h000066400000000000000000000066251500456250600172630ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "HardwareInterface.h" #include "HardwareParams.h" #include "Logger/Logger.h" #include "Network/Serial.h" namespace Hardware { class M6051 : HardwareInterface { public: M6051() = delete; M6051(const M6051&) = delete; M6051& operator=(const M6051&) = delete; M6051(const HardwareParams* params); ~M6051(); inline Hardware::Capabilities GetCapabilities() const override { return Hardware::CapabilityLoco | Hardware::CapabilityAccessory | Hardware::CapabilityFeedback; } void GetLocoProtocols(std::vector& protocols) const override { protocols.push_back(ProtocolMM2); } bool LocoProtocolSupported(Protocol protocol) const override { return (protocol == ProtocolMM2); } void GetAccessoryProtocols(std::vector& protocols) const override { protocols.push_back(ProtocolMM2); } bool AccessoryProtocolSupported(Protocol protocol) const override { return (protocol == ProtocolMM2); } static void GetArgumentTypesAndHint(std::map& argumentTypes, std::string& hint) { argumentTypes[1] = ArgumentTypeSerialPort; argumentTypes[2] = ArgumentTypeS88Modules; hint = Languages::GetText(Languages::TextHintM6051); } void Booster(const BoosterState status) override; void LocoSpeed(const Protocol protocol, const Address address, const Speed speed) override; void LocoOrientation(const Protocol protocol, const Address address, const Orientation orientation) override; void LocoFunction(const Protocol protocol, const Address address, const DataModel::LocoFunctionNr function, const DataModel::LocoFunctionState on) override; void Accessory(const Protocol protocol, const Address address, const DataModel::AccessoryState state, const bool on, const DataModel::AccessoryPulseDuration duration) override; private: static const unsigned char MaxS88Modules = 62; Network::Serial serialLine; volatile bool run; unsigned char s88Modules; std::thread s88Thread; unsigned char s88Memory[MaxS88Modules]; std::map speedMap; std::map functionMap; unsigned char GetSpeedMapEntry(Address address) { return speedMap.count(address) == 0 ? 0 : speedMap[address]; } unsigned char GetFunctionMapEntry(Address address) { return functionMap.count(address) == 0 ? 0 : functionMap[address]; } void SendTwoBytes(const unsigned char byte1, const unsigned char byte2) { char dataArray[2]; dataArray[0] = byte1; dataArray[1] = byte2; std::string data(dataArray, 2); serialLine.Send(data); } void S88Worker(); }; } // namespace railcontrol-24+dfsg1/Hardware/MasterControl.h000066400000000000000000000031671500456250600213450ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "Hardware/Protocols/P50xSerial.h" #include "Languages.h" namespace Hardware { class HardwareParams; class MasterControl : public Protocols::P50xSerial { public: MasterControl() = delete; MasterControl(const MasterControl&) = delete; MasterControl& operator=(const MasterControl&) = delete; inline MasterControl(const HardwareParams* const params) : Protocols::P50xSerial(params, "MasterControl", TypeTams) { Init(); } virtual ~MasterControl() { } static inline void GetArgumentTypesAndHint(std::map& argumentTypes, std::string& hint) { argumentTypes[1] = ArgumentTypeSerialPort; argumentTypes[2] = ArgumentTypeS88Modules; hint = Languages::GetText(Languages::TextHintMasterControl); } private: static const unsigned char MaxS88ModulesMasterControl = 104; }; } // namespace railcontrol-24+dfsg1/Hardware/MasterControl2.h000066400000000000000000000032001500456250600214130ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "Hardware/Protocols/P50xEthernet.h" #include "Languages.h" namespace Hardware { class HardwareParams; class MasterControl2 : public Protocols::P50xEthernet { public: MasterControl2() = delete; MasterControl2(const MasterControl2&) = delete; MasterControl2& operator=(const MasterControl2&) = delete; inline MasterControl2(const HardwareParams* params) : Protocols::P50xEthernet(params, "MasterControl2", TypeTams) { Init(); } virtual ~MasterControl2() { } static inline void GetArgumentTypesAndHint(std::map& argumentTypes, std::string& hint) { argumentTypes[1] = ArgumentTypeIpAddress; argumentTypes[2] = ArgumentTypeS88Modules; hint = Languages::GetText(Languages::TextHintMasterControl2); } private: static const unsigned char MaxS88ModulesMasterControl = 104; }; } // namespace railcontrol-24+dfsg1/Hardware/OpenDcc.h000066400000000000000000000031311500456250600200530ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include "Hardware/Protocols/P50xSerial.h" #include "Languages.h" namespace Hardware { class HardwareParams; class OpenDcc : public Protocols::P50xSerial { public: OpenDcc() = delete; OpenDcc(const OpenDcc&) = delete; OpenDcc& operator=(const OpenDcc&) = delete; inline OpenDcc(const HardwareParams* params) : Protocols::P50xSerial(params, "OpenDCC", TypeOpenDcc) { Init(); } virtual ~OpenDcc() { } static inline void GetArgumentTypesAndHint(std::map& argumentTypes, std::string& hint) { argumentTypes[1] = ArgumentTypeSerialPort; argumentTypes[2] = ArgumentTypeS88Modules; argumentTypes[3] = ArgumentTypeS88Modules; argumentTypes[4] = ArgumentTypeS88Modules; hint = Languages::GetText(Languages::TextHintOpenDcc); } }; } // namespace railcontrol-24+dfsg1/Hardware/Protocols/000077500000000000000000000000001500456250600203555ustar00rootroot00000000000000railcontrol-24+dfsg1/Hardware/Protocols/DccPpEx.cpp000066400000000000000000000167611500456250600223620ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include "DataModel/Feedback.h" #include "Manager.h" #include "Hardware/Protocols/DccPpEx.h" using std::string; using std::to_string; namespace Hardware { namespace Protocols { void DccPpEx::Booster(const BoosterState status) { string buffer("<"); logger->Info(status ? Languages::TextTurningBoosterOn : Languages::TextTurningBoosterOff); buffer += to_string(static_cast(status)); buffer += ">"; SendInternal(buffer); } void DccPpEx::LocoSpeed(__attribute__((unused)) const Protocol protocol, const Address address, const Speed speed) { locoCache.SetSpeed(address, speed); Orientation orientation = locoCache.GetOrientation(address); LocoSpeedOrientation(address, speed, orientation); } void DccPpEx::LocoOrientation(__attribute__((unused)) const Protocol protocol, const Address address, const Orientation orientation) { Speed speed = locoCache.GetSpeed(address); locoCache.SetOrientation(address, orientation); LocoSpeedOrientation(address, speed, orientation); } void DccPpEx::LocoFunction(__attribute__((unused)) const Protocol protocol, const Address address, const DataModel::LocoFunctionNr function, const DataModel::LocoFunctionState on) { string buffer("= 1008 ? 126 : speed >> 3); buffer += " "; buffer += to_string(orientation); buffer += ">"; SendInternal(buffer); } void DccPpEx::Receiver() { Utils::Utils::SetThreadName("DCC-EX"); logger->Info(Languages::TextReceiverThreadStarted); while (true) { string buffer; const bool ret = ReceiveInternal(buffer); if (!ret) { break; } Parse(buffer); } logger->Info(Languages::TextTerminatingReceiverThread); } bool DccPpEx::ReceiveInternal(string& buffer) { size_t size = 0; while (size == 0 || ((buffer[size - 1] != '>') && (buffer[size - 1] != 0x0a))) { if (!run) { return false; } Receive(buffer); size = buffer.size(); } if (size == 0) { return false; } logger->HexIn(buffer); return true; } void DccPpEx::Parse(const string& buffer) { unsigned int pos = 0; while (buffer.size() > pos) { if (buffer[pos] != '<') { ++pos; continue; } ++pos; if (buffer.size() <= pos) { return; } switch(buffer[pos++]) { case 'Q': { // pin is high const FeedbackPin pin = ParseInt(buffer, pos); logger->Info(Languages::TextFeedbackChange, pin & 0x000F, pin >> 4, Languages::GetText(Languages::TextOn)); manager->FeedbackState(controlID, pin, DataModel::Feedback::FeedbackStateOccupied); break; } case 'q': { // pin is low const FeedbackPin pin = ParseInt(buffer, pos); logger->Info(Languages::TextFeedbackChange, pin & 0x000F, pin >> 4, Languages::GetText(Languages::TextOff)); manager->FeedbackState(controlID, pin, DataModel::Feedback::FeedbackStateFree); break; } case 'r': { // read CV value ParseInt(buffer, pos); // should be 8, sent in ProgramReadProgram ParsePipe(buffer, pos); ParseInt(buffer, pos); // should be 9, sent in ProgramReadProgram ParsePipe(buffer, pos); const CvNumber cv = ParseInt(buffer, pos); const int value = ParseInt(buffer, pos); if (value < 0) { // read error break; } logger->Info(Languages::TextProgramReadValue, cv, static_cast(value)); manager->ProgramValue(cv, value); break; } default: // other answer does not matter break; } do { ++pos; } while (buffer.size() > pos && buffer[pos] != '>'); } } int DccPpEx::ParseInt(const string& buffer, unsigned int& pos) { ParseSpace(buffer, pos); bool minus = false; int out = 0; while (buffer.size() > pos) { const char c = buffer[pos]; if (c == '-') { minus = true; ++pos; continue; } if (c < '0' || c > '9') { break; } out *= 10; out += c - '0'; ++pos; } return minus ? -out : out; } void DccPpEx::ParseSpace(const string& buffer, unsigned int& pos) { while (buffer.size() > pos) { const char c = buffer[pos]; if (c != ' ') { return; } ++pos; } } bool DccPpEx::ParsePipe(const string& buffer, unsigned int& pos) { if (buffer.size() <= pos) { return false; } const char c = buffer[pos]; if (c != '|') { return false; } ++pos; return true; } } // namespace } // namespace railcontrol-24+dfsg1/Hardware/Protocols/DccPpEx.h000066400000000000000000000104541500456250600220200ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include "Hardware/Capabilities.h" #include "Hardware/HardwareInterface.h" #include "Hardware/HardwareParams.h" #include "Hardware/Protocols/DccPpExLocoCache.h" #include "Logger/Logger.h" namespace Hardware { namespace Protocols { class DccPpEx: protected HardwareInterface { public: DccPpEx() = delete; DccPpEx(const DccPpEx&) = delete; DccPpEx& operator=(const DccPpEx&) = delete; inline Hardware::Capabilities GetCapabilities() const override { return CapabilityLoco | CapabilityAccessory | CapabilityFeedback | CapabilityProgram | CapabilityProgramDccDirectWrite | CapabilityProgramDccDirectRead | CapabilityProgramDccPomLocoWrite; } void GetLocoProtocols(std::vector& protocols) const override { protocols.push_back(ProtocolDCC128); } inline bool LocoProtocolSupported(Protocol protocol) const override { return (protocol == ProtocolDCC128); } inline void GetAccessoryProtocols(std::vector& protocols) const override { protocols.push_back(ProtocolDCC); } inline bool AccessoryProtocolSupported(Protocol protocol) const override { return (protocol == ProtocolDCC); } void Booster(const BoosterState status) override; void LocoSpeed(const Protocol protocol, const Address address, const Speed speed) override; void LocoOrientation(const Protocol protocol, const Address address, const Orientation orientation) override; void LocoFunction(const Protocol protocol, const Address address, const DataModel::LocoFunctionNr function, const DataModel::LocoFunctionState on) override; void Accessory(const Protocol protocol, const Address address, const DataModel::AccessoryState state, const bool on, const DataModel::AccessoryPulseDuration duration) override; void ProgramWrite(const ProgramMode mode, const Address address, const CvNumber cv, const CvValue value) override; void ProgramRead(const ProgramMode mode, const Address address, const CvNumber cv) override; protected: inline DccPpEx(const HardwareParams* params, const std::string& fullName, const std::string& shortName) : HardwareInterface(params->GetManager(), params->GetControlID(), fullName, shortName), run(true) { } inline void Init() { receiverThread = std::thread(&DccPpEx::Receiver, this); } virtual ~DccPpEx() { run = false; receiverThread.join(); } private: void LocoSpeedOrientation(const Address address, const Speed speed, const Orientation orientation); void ProgramWriteMain(const Address address, const CvNumber cv, const CvValue value); void ProgramWriteProgram(const CvNumber cv, const CvValue value); void ProgramReadProgram(const CvNumber cv); inline bool SendInternal(const std::string& buffer) { logger->HexOut(buffer); return Send(buffer); } bool ReceiveInternal(std::string& buffer); virtual bool Send(const std::string& buffer) = 0; virtual bool Receive(std::string& buffer) = 0; void Receiver(); void Parse(const std::string& buffer); int ParseInt(const std::string& buffer, unsigned int& pos); void ParseSpace(const std::string& buffer, unsigned int& pos); bool ParsePipe(const std::string& buffer, unsigned int& pos); DccPpExLocoCache locoCache; volatile bool run; std::thread receiverThread; }; } // namespace } // namespace railcontrol-24+dfsg1/Hardware/Protocols/DccPpExLocoCache.h000066400000000000000000000044341500456250600235620ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "DataTypes.h" namespace Hardware { namespace Protocols { class DccPpExLocoCacheEntry { public: DccPpExLocoCacheEntry() : speed(MinSpeed), orientation(OrientationRight) { } DccPpExLocoCacheEntry(const Speed speed, const Orientation orientation) : speed(speed), orientation(orientation) { } Speed speed; Orientation orientation; }; class DccPpExLocoCache { public: DccPpExLocoCache& operator=(const DccPpExLocoCache&) = delete; DccPpExLocoCacheEntry GetData(const Address address) { if (cache.count(address) == 1) { return cache[address]; } DccPpExLocoCacheEntry entry; return entry; } void SetSpeed(const Address address, const Speed speed) { DccPpExLocoCacheEntry entry = GetData(address); entry.speed = speed; cache[address] = entry; } Speed GetSpeed(const Address address) { if (cache.count(address) == 0) { return MinSpeed; } return cache[address].speed; } void SetOrientation(const Address address, const Orientation orientation) { DccPpExLocoCacheEntry entry = GetData(address); entry.orientation = orientation; cache[address] = entry; } Orientation GetOrientation(const Address address) { if (cache.count(address) == 0) { return OrientationRight; } return cache[address].orientation; } private: std::map cache; }; } // namespace } // namespace railcontrol-24+dfsg1/Hardware/Protocols/EsuCAN.cpp000066400000000000000000000403751500456250600221500ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include #include "DataModel/AccessoryBase.h" #include "Hardware/HardwareInterface.h" #include "Hardware/HardwareParams.h" #include "Hardware/Protocols/EsuCAN.h" #include "Utils/Integer.h" using std::deque; using std::string; using std::strlen; using std::to_string; namespace Hardware { namespace Protocols { const char* const EsuCAN::CommandActivateBoosterUpdates = "request(1,view)\n"; const char* const EsuCAN::CommandQueryLocos = "queryObjects(10,name)\n"; const char* const EsuCAN::CommandQueryAccessories = "queryObjects(11,name1,name2,name3)\n"; const char* const EsuCAN::CommandQueryFeedbacks = "queryObjects(26,ports)\n"; EsuCAN::EsuCAN(const HardwareParams* params, const std::string& controlName) : HardwareInterface(params->GetManager(), params->GetControlID(), controlName + " / " + params->GetName() + " at IP " + params->GetArg1(), params->GetName()), run(false), tcp(Network::TcpClient::GetTcpClientConnection(logger, params->GetArg1(), EsuCANPort)), readBufferLength(0), readBufferPosition(0), locoCache(params->GetControlID(), params->GetManager()), accessoryCache(params->GetControlID(), params->GetManager()), feedbackCache(params->GetControlID(), params->GetManager()) { logger->Info(Languages::TextStarting, GetFullName()); std::memset(feedbackMemory, 0, MaxFeedbackModules); receiverThread = std::thread(&Hardware::Protocols::EsuCAN::Receiver, this); if (!tcp.IsConnected()) { return; } SendActivateBoosterUpdates(); SendQueryLocos(); SendQueryAccessories(); SendQueryFeedbacks(); } EsuCAN::~EsuCAN() { run = false; receiverThread.join(); logger->Info(Languages::TextTerminatingSenderSocket); } void EsuCAN::LocoSpeed(__attribute__((unused)) const Protocol protocol, const Address address, const Speed speed) { const unsigned int locoId = address + OffsetLocoAddress; SendGetHandle(locoId); const string command = "set(" + to_string(locoId) + ",speed[" + to_string(speed >> 3) + "])\n"; Send(command.c_str()); } void EsuCAN::LocoOrientation(__attribute__((unused)) const Protocol protocol, const Address address, const Orientation orientation) { const unsigned int locoId = address + OffsetLocoAddress; SendGetHandle(locoId); const string command = "set(" + to_string(locoId) + ",dir[" + (orientation == OrientationRight ? "0" : "1") + "])\n"; Send(command.c_str()); } void EsuCAN::LocoFunction(__attribute__((unused)) const Protocol protocol, const Address address, const DataModel::LocoFunctionNr function, const DataModel::LocoFunctionState on) { const unsigned int locoId = address + OffsetLocoAddress; SendGetHandle(locoId); const string command = "set(" + to_string(locoId) + ",func[" + to_string(function) + "," + (on == DataModel::LocoFunctionStateOn ? "1" : "0") + "])\n"; Send(command.c_str()); } void EsuCAN::Accessory(__attribute__((unused)) const Protocol protocol, const Address address, const DataModel::AccessoryState state, const bool on, __attribute__((unused)) const DataModel::AccessoryPulseDuration duration) { if (!on) { return; } const unsigned int accessoryId = address + OffsetAccessoryAddress; SendGetHandle(accessoryId); const string command = "set(" + to_string(accessoryId) + ",state[" + (state ? "0" : "1") + "])\n"; Send(command.c_str()); } void EsuCAN::Send(const char* data) { int ret = tcp.Send(data); if (ret < 0) { logger->Error(Languages::TextUnableToSendDataToControl); } } void EsuCAN::Receiver() { Utils::Utils::SetThreadName("EsuCAN"); if (!tcp.IsConnected()) { return; } logger->Info(Languages::TextReceiverThreadStarted); run = true; while (run) { ReadLine(); if (run == false) { break; } if (readBufferLength == 0) { if (errno == ETIMEDOUT) { continue; } logger->Error(Languages::TextUnableToReceiveData); break; } Parser(); } tcp.Terminate(); logger->Info(Languages::TextTerminatingReceiverThread); } void EsuCAN::ReadLine() { readBufferPosition = 0; readBufferLength = -1; while (true) { int dataLength = tcp.Receive(readBuffer + readBufferPosition, 1); if (dataLength != 1) { readBufferLength = 0; break; } if (readBuffer[readBufferPosition] == '\r') { continue; } if (readBuffer[readBufferPosition] == '\n') { readBufferLength = readBufferPosition; break; } ++readBufferPosition; if (readBufferPosition >= MaxMessageSize) { break; } } readBufferPosition = 0; if (readBufferLength == 0) { return; } logger->HexIn(reinterpret_cast(readBuffer), readBufferLength); } void EsuCAN::Parser() { if (!CheckAndConsumeChar('<')) { logger->Error(Languages::TextInvalidDataReceived); return; } char type = ReadAndConsumeChar(); switch (type) { case 'R': ParseReply(); return; case 'E': ParseEvent(); return; default: logger->Error(Languages::TextInvalidDataReceived); return; } } void EsuCAN::ParseReply() { if (CompareAndConsume("EPLY", 4) == false) { logger->Error(Languages::TextInvalidDataReceived); return; } SkipWhiteSpace(); if (Compare(CommandQueryLocos, strlen(CommandQueryLocos) - 1)) { ParseQueryLocos(); return; } if (Compare(CommandQueryAccessories, strlen(CommandQueryAccessories) - 1)) { ParseQueryAccessories(); return; } if (Compare(CommandQueryFeedbacks, strlen(CommandQueryFeedbacks) - 1)) { ParseQueryFeedbacks(); return; } ReadLine(); while (GetChar() != '<') { // ParseLine() ReadLine(); } ParseEndLine(); } void EsuCAN::ParseQueryLocos() { ReadLine(); while (GetChar() != '<') { ParseLocoData(); ReadLine(); } ParseEndLine(); } void EsuCAN::ParseQueryAccessories() { ReadLine(); while (GetChar() != '<') { ParseAccessoryData(); ReadLine(); } ParseEndLine(); } void EsuCAN::ParseQueryFeedbacks() { ReadLine(); while (GetChar() != '<') { ParseFeedbackData(); ReadLine(); } ParseEndLine(); } void EsuCAN::ParseLocoData() { const unsigned int locoId = ParseInt(); const Address address = locoId - OffsetLocoAddress; SendActivateUpdates(locoId); string name; while (true) { string option; string value; ParseOption(option, value); if (option.size() == 0) { break; } if (option.compare("name") == 0) { name = value; } } LocoCacheEntry cacheEntry(locoCache.GetControlId()); cacheEntry.SetMatchKey(locoId); cacheEntry.SetProtocol(ProtocolServer); cacheEntry.SetAddress(address); cacheEntry.SetName(name); locoCache.Save(cacheEntry); logger->Info(Languages::TextFoundLocoInEcosDatabase, address, name); } void EsuCAN::ParseAccessoryData() { const unsigned int accessoryId = ParseInt(); const Address address = accessoryId - OffsetAccessoryAddress; SendActivateUpdates(accessoryId); string name; string name2; string name3; while (true) { string option; string value; ParseOption(option, value); if (option.size() == 0) { break; } if (option.compare("name1") == 0) { name = value; } else if (option.compare("name2") == 0) { name2 = value; } else if (option.compare("name3") == 0) { name3 = value; } } AccessoryCacheEntry cacheEntry(accessoryCache.GetControlId()); cacheEntry.SetMatchKey(accessoryId); cacheEntry.SetProtocol(ProtocolServer); cacheEntry.SetAddress(address); if (name2.size()) { name += " " + name2; } if (name3.size()) { name += " " + name3; } cacheEntry.SetName(name); accessoryCache.Save(cacheEntry); logger->Info(Languages::TextFoundAccessoryInEcosDatabase, address, name, name2, name3); } void EsuCAN::ParseFeedbackData() { const int moduleId = ParseInt(); const int moduleIdHuman = moduleId - OffsetFeedbackModuleAddress; const int moduleIdMachine = moduleId - OffsetFeedbackModuleAddress - 1; SendActivateUpdates(moduleId); int ports = 0; string name; while (true) { string option; string value; ParseOption(option, value); if (option.size() == 0) { break; } if (option.compare("ports") == 0) { ports = Utils::Integer::StringToInteger(value, 16); name = "Module " + Utils::Utils::ToStringWithLeadingZeros(moduleIdHuman, 3) + " Pin "; } } FeedbackCacheEntry cacheEntry(feedbackCache.GetControlId()); for (int pinOfModule = 1; pinOfModule <= ports; ++pinOfModule) { int pin = (moduleIdMachine * 16) + pinOfModule; const string pinAsText = Utils::Utils::ToStringWithLeadingZeros(pinOfModule, 2); cacheEntry.SetMatchKey(to_string(moduleId) + "/" + pinAsText); cacheEntry.SetFeedbackID(FeedbackNone); cacheEntry.SetPin(pin); cacheEntry.SetName(name + pinAsText); feedbackCache.Save(cacheEntry); } logger->Info(Languages::TextFoundFeedbackModuleInEcosDatabase, moduleIdHuman); } void EsuCAN::ParseOption(string& option, string& value) { SkipWhiteSpace(); option = ReadUntilChar('['); if (!CheckAndConsumeChar('[')) { return; } if (GetChar() == '"') { CheckAndConsumeChar('"'); value = ReadUntilChar('"'); CheckAndConsumeChar('"'); } else { value = ReadUntilChar(']'); } CheckAndConsumeChar(']'); } void EsuCAN::ParseOptionInt(string& option, int& value) { string stringValue; ParseOption(option, stringValue); value = Utils::Integer::StringToInteger(stringValue, 0); } void EsuCAN::ParseOptionHex(string& option, int& value) { string stringValue; ParseOption(option, stringValue); value = Utils::Integer::HexToInteger(stringValue, 0); } void EsuCAN::ParseEvent() { if (CompareAndConsume("VENT", 4) == false) { logger->Error(Languages::TextInvalidDataReceived); return; } ParseInt(); if (CheckGraterThenAtLineEnd() == false) { logger->Error(Languages::TextInvalidDataReceived); return; } while (true) { ReadLine(); if (GetChar() == '<') { ParseEndLine(); return; } ParseEventLine(); } } void EsuCAN::ParseEventLine() { int object = ParseInt(); SkipWhiteSpace(); if (object >= MinAccessoryId) { ParseAccessoryEvent(object); return; } if (object >= MinLocoId) { ParseLocoEvent(object); return; } if (object >= MinFeedbackModuleId) { ParseFeedbackEvent(object); return; } if (object == 1) { ParseBoosterEvent(); return; } string event = ReadUntilLineEnd(); logger->HexIn(event); } void EsuCAN::ParseBoosterEvent() { if (Compare("status[GO]", 10)) { manager->Booster(ControlTypeHardware, BoosterStateGo); } else if (Compare("status[STOP]", 12)) { manager->Booster(ControlTypeHardware, BoosterStateStop); } } void EsuCAN::ParseLocoEvent(int loco) { const Address address = loco - OffsetLocoAddress; string option; string value; ParseOption(option, value); if (option.compare("speed") == 0) { Speed speed = Utils::Integer::StringToInteger(value) << 3; manager->LocoSpeed(ControlTypeHardware, controlID, ProtocolServer, address, speed); return; } if (option.compare("dir") == 0) { Orientation orientation = (Utils::Integer::StringToInteger(value) == 1 ? OrientationLeft : OrientationRight); manager->LocoOrientation(ControlTypeHardware, controlID, ProtocolServer, address, orientation); return; } if (option.compare("func") == 0) { deque < string > valueList; Utils::Utils::SplitString(value, ",", valueList); if (valueList.size() < 2) { logger->Error(Languages::TextInvalidDataReceived); return; } DataModel::LocoFunctionNr function = Utils::Integer::StringToInteger(valueList[0], 0); DataModel::LocoFunctionState on = Utils::Utils::StringToBool(valueList[1]) ? DataModel::LocoFunctionStateOn : DataModel::LocoFunctionStateOff; manager->LocoFunctionState(ControlTypeHardware, controlID, ProtocolServer, address, function, on); return; } } void EsuCAN::ParseAccessoryEvent(int accessory) { const Address address = accessory - OffsetAccessoryAddress; string option; int value; ParseOptionInt(option, value); if (option.compare("state") == 0) { DataModel::AccessoryState state = ( value == 0 ? DataModel::AccessoryStateOn : DataModel::AccessoryStateOff); manager->AccessoryBaseState(ControlTypeHardware, controlID, ProtocolServer, address, state); return; } } void EsuCAN::ParseFeedbackEvent(int feedback) { string option; int value; ParseOptionHex(option, value); if (option.compare("state") == 0) { unsigned int module1 = (feedback - MinFeedbackModuleId) << 1; unsigned int module2 = module1 + 1; uint8_t moduleData1 = value & 0xFF; uint8_t moduleData2 = (value >> 8) & 0xFF; CheckFeedbackDiff(module1, moduleData1); CheckFeedbackDiff(module2, moduleData2); return; } } void EsuCAN::CheckFeedbackDiff(unsigned int module, uint8_t data) { uint8_t oldData = feedbackMemory[module]; if (oldData == data) { return; } for (unsigned int pin = 0; pin < 8; ++pin) { uint8_t oldPinData = (oldData >> pin) & 0x01; uint8_t pinData = (data >> pin) & 0x01; if (oldPinData == pinData) { continue; } const unsigned int address = (module << 3) + pin + 1; const DataModel::Feedback::FeedbackState state = pinData == 1 ? DataModel::Feedback::FeedbackStateOccupied : DataModel::Feedback::FeedbackStateFree; manager->FeedbackState(controlID, address, state); logger->Info(Languages::TextFeedbackChange, address & 0x000F, address >> 4, state); } feedbackMemory[module] = data; } void EsuCAN::ParseEndLine() { if (CompareAndConsume("Error(Languages::TextInvalidDataReceived); return; } int i = ParseInt(); if (i == 0) { return; } SkipWhiteSpace(); string error = ReadUntilChar('>'); logger->Error(Languages::TextControlReturnedError, error); } string EsuCAN::ReadUntilChar(const char c) { string out; while (true) { const char readChar = GetChar(); if (readChar == c || readChar == 0) { return out; } out.append(1, ReadAndConsumeChar()); } } string EsuCAN::ReadUntilLineEnd() { string out = ReadUntilChar('\n'); return out; } bool EsuCAN::Compare(const char* reference, const size_t size) const { for (size_t index = 0; index < size; ++index) { if (GetChar(index) != reference[index]) { return false; } } return true; } bool EsuCAN::CompareAndConsume(const char* reference, const size_t size) { for (size_t index = 0; index < size; ++index) { if (ReadAndConsumeChar() != reference[index]) { return false; } } return true; } bool EsuCAN::SkipOptionalChar(const char charToSkip) { if (charToSkip == GetChar()) { ++readBufferPosition; return true; } return false; } bool EsuCAN::IsNumber() const { const char c = GetChar(); return (c >= '0' && c <= '9'); } int EsuCAN::ParseInt() { SkipWhiteSpace(); int out = 0; while (IsNumber()) { out *= 10; out += ReadAndConsumeChar() - '0'; } return out; } } // namespace } // namespace railcontrol-24+dfsg1/Hardware/Protocols/EsuCAN.h000066400000000000000000000207731500456250600216150ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include "DataModel/AccessoryConfig.h" #include "DataModel/FeedbackConfig.h" #include "DataModel/LocoConfig.h" #include "Hardware/Capabilities.h" #include "Hardware/AccessoryCache.h" #include "Hardware/FeedbackCache.h" #include "Hardware/HardwareInterface.h" #include "Hardware/HardwareParams.h" #include "Hardware/LocoCache.h" #include "Network/TcpClient.h" namespace Hardware { namespace Protocols { class EsuCAN: protected HardwareInterface { public: EsuCAN() = delete; EsuCAN(const EsuCAN&) = delete; EsuCAN& operator=(const EsuCAN&) = delete; EsuCAN(const HardwareParams* params, const std::string& controlName); virtual ~EsuCAN(); inline Hardware::Capabilities GetCapabilities() const override { return Hardware::CapabilityLoco | Hardware::CapabilityAccessory | Hardware::CapabilityFeedback | Hardware::CapabilityLocoDatabase | Hardware::CapabilityAccessoryDatabase | Hardware::CapabilityFeedbackDatabase ; } void GetLocoProtocols(std::vector& protocols) const override { protocols.push_back(ProtocolServer); } bool LocoProtocolSupported(Protocol protocol) const override { return (protocol == ProtocolServer); } void GetAccessoryProtocols(std::vector& protocols) const override { protocols.push_back(ProtocolServer); } bool AccessoryProtocolSupported(Protocol protocol) const override { return (protocol == ProtocolServer); } static void GetArgumentTypesAndHint(std::map& argumentTypes, std::string& hint) { argumentTypes[1] = ArgumentTypeIpAddress; hint = Languages::GetText(Languages::TextHintEcos); } void Booster(const BoosterState status) override { Send(status == BoosterStateGo ? "set(1,go)\n" : "set(1,stop)\n"); } void LocoSpeed(const Protocol protocol, const Address address, const Speed speed) override; void LocoOrientation(const Protocol protocol, const Address address, const Orientation orientation) override; void LocoFunction(const Protocol protocol, const Address address, const DataModel::LocoFunctionNr function, const DataModel::LocoFunctionState on) override; void Accessory(const Protocol protocol, const Address address, const DataModel::AccessoryState state, const bool on, const DataModel::AccessoryPulseDuration duration) override; inline virtual const std::map& GetLocoDatabase() const override { return locoCache.GetAll(); } inline virtual DataModel::LocoConfig GetLocoByMatchKey(const std::string& matchKey) const override { return DataModel::LocoConfig(locoCache.Get(matchKey)); } inline virtual void SetLocoIdOfMatchKey(const LocoID locoId, const std::string& matchKey) override { locoCache.SetLocoId(locoId, matchKey); } inline virtual const std::map& GetAccessoryDatabase() const override { return accessoryCache.GetAll(); } inline virtual DataModel::AccessoryConfig GetAccessoryByMatchKey(const std::string& matchKey) const override { return DataModel::AccessoryConfig(accessoryCache.Get(matchKey)); } inline virtual void SetAccessoryIdentifierOfMatchKey(const DataModel::ObjectIdentifier objectIdentifier, const std::string& matchKey) override { accessoryCache.SetObjectIdentifier(objectIdentifier, matchKey); } inline virtual const std::map& GetFeedbackDatabase() const override { return feedbackCache.GetAll(); } inline virtual DataModel::FeedbackConfig GetFeedbackByMatchKey(const std::string& matchKey) const override { return DataModel::FeedbackConfig(feedbackCache.Get(matchKey)); } inline virtual void SetFeedbackIdOfMatchKey(const FeedbackID feedbackId, const std::string& matchKey) override { feedbackCache.SetFeedbackId(feedbackId, matchKey); } static const char* const CommandActivateBoosterUpdates; static const char* const CommandQueryLocos; static const char* const CommandQueryAccessories; static const char* const CommandQueryFeedbacks; private: static const unsigned short MaxMessageSize = 1024; static const unsigned short EsuCANPort = 15471; void Send(const char* data); void Receiver(); void ReadLine(); void Parser(); void ParseReply(); void ParseQueryLocos(); void ParseQueryAccessories(); void ParseQueryFeedbacks(); void ParseLocoData(); void ParseAccessoryData(); void ParseFeedbackData(); void ParseEvent(); void ParseEventLine(); void ParseBoosterEvent(); void ParseLocoEvent(int loco); void ParseAccessoryEvent(int accessory); void ParseFeedbackEvent(int feedback); void CheckFeedbackDiff(unsigned int module, uint8_t data); void ParseOption(std::string& option, std::string& value); void ParseOptionInt(std::string& option, int& value); void ParseOptionHex(std::string& option, int& value); void ParseEndLine(); std::string ReadUntilChar(const char c); std::string ReadUntilLineEnd(); inline void SendActivateBoosterUpdates() { Send(CommandActivateBoosterUpdates); } inline void SendQueryLocos() { Send(CommandQueryLocos); } inline void SendQueryAccessories() { Send(CommandQueryAccessories); } inline void SendQueryFeedbacks() { Send(CommandQueryFeedbacks); } inline void SendActivateUpdates(const int id) { std::string command = "request(" + std::to_string(id) + ",view)\n"; Send(command.c_str()); } inline void SendGetHandle(const int id) { std::string command = "request(" + std::to_string(id) + ",control,force)\n"; Send(command.c_str()); } inline char GetChar(const size_t offset = 0) const { size_t position = readBufferPosition + offset; if (position >= MaxMessageSize) { return 0; } return readBuffer[position]; } inline char ReadAndConsumeChar() { if (readBufferPosition >= MaxMessageSize) { return 0; } return readBuffer[readBufferPosition++]; } inline bool CheckAndConsumeChar(const char charToCheck) { if (readBufferPosition >= MaxMessageSize) { return false; } return charToCheck == readBuffer[readBufferPosition++]; } inline bool CheckChar(const char charToCheck) { if (readBufferPosition >= MaxMessageSize) { return false; } return charToCheck == readBuffer[readBufferPosition]; } bool SkipOptionalChar(const char charToSkip); inline void SkipWhiteSpace() { while (SkipOptionalChar(' ')); } bool Compare(const char* reference, const size_t size) const; bool CompareAndConsume(const char* reference, const size_t size); bool IsNumber() const; int ParseInt(); inline bool CheckGraterThenAtLineEnd() { SkipWhiteSpace(); return CompareAndConsume(">\n", 2); } volatile bool run; std::thread receiverThread; Network::TcpConnection tcp; unsigned char readBuffer[MaxMessageSize]; ssize_t readBufferLength; size_t readBufferPosition; static const unsigned int MaxFeedbackModules = 128; uint8_t feedbackMemory[MaxFeedbackModules]; LocoCache locoCache; AccessoryCache accessoryCache; FeedbackCache feedbackCache; static const int MinLocoId = 1000; static const int OffsetLocoAddress = 999; static const int MinAccessoryId = 20000; static const int OffsetAccessoryAddress = 19999; static const int MinFeedbackModuleId = 100; static const int OffsetFeedbackModuleAddress = 99; }; } // namespace } // namespace railcontrol-24+dfsg1/Hardware/Protocols/LocoNet.cpp000066400000000000000000000703031500456250600224270ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include #include "DataModel/LocoFunctions.h" #include "Hardware/Protocols/LocoNet.h" #include "Utils/Integer.h" namespace Hardware { namespace Protocols { LocoNet::LocoNet(const HardwareParams* params, const std::string& controlName, const unsigned int dataSpeed) : HardwareInterface(params->GetManager(), params->GetControlID(), controlName + " / " + params->GetName() + " at serial port " + params->GetArg1(), params->GetName()), run(true), serialLine(logger, params->GetArg1(), dataSpeed, 8, 'N', 1), lastCv(0), isProgramming(false) { receiverThread = std::thread(&Hardware::Protocols::LocoNet::Receiver, this); senderThread = std::thread(&Hardware::Protocols::LocoNet::Sender, this); } LocoNet::~LocoNet() { run = false; sendingQueue.EnqueueBack(SendingQueueEntry()); senderThread.join(); receiverThread.join(); logger->Info(Languages::TextTerminatingSenderSocket); } void LocoNet::Booster(const BoosterState status) { if (BoosterStateGo == status) { Send2ByteCommand(OPC_GPON); logger->Info(Languages::TextTurningBoosterOn); } else { Send2ByteCommand(OPC_GPOFF); logger->Info(Languages::TextTurningBoosterOff); } } void LocoNet::LocoSpeed(__attribute__((unused)) const Protocol protocol, const Address address, const Speed speed) { unsigned char slot = locoCache.GetSlotOfAddress(address); if (0 == slot) { SendLocoAddress(address); return; } SendLocoSpeed(slot, speed); } void LocoNet::LocoOrientation(__attribute__((unused)) const Protocol protocol, const Address address, const Orientation orientation) { unsigned char slot = locoCache.GetSlotOfAddress(address); if (0 == slot) { SendLocoAddress(address); return; } const unsigned char orientationF0F4 = SetOrientationF0F4Bit(slot, orientation, 5); SendLocoOrientationF0F4(slot, orientationF0F4); } void LocoNet::LocoFunction(__attribute__((unused)) const Protocol protocol, const Address address, const DataModel::LocoFunctionNr function, const DataModel::LocoFunctionState on) { unsigned char slot = locoCache.GetSlotOfAddress(address); if (0 == slot) { SendLocoAddress(address); return; } switch (function) { case 0: { const unsigned char shift = 4; const unsigned char orientationF0F4 = SetOrientationF0F4Bit(slot, on, shift); SendLocoOrientationF0F4(slot, orientationF0F4); return; } case 1: case 2: case 3: case 4: { const unsigned char shift = function - 1; const unsigned char orientationF0F4 = SetOrientationF0F4Bit(slot, on, shift); SendLocoOrientationF0F4(slot, orientationF0F4); return; } case 5: case 6: case 7: case 8: { const unsigned char shift = function - 5; const unsigned char f5F8 = SetF5F8Bit(slot, on, shift); SendLocoF5F8(slot, f5F8); return; } case 9: case 10: case 11: case 12: { const unsigned char shift = function - 9; const unsigned char f9F12 = SetF9F12Bit(slot, on, shift); SendLocoF9F12(slot, f9F12); return; } default: break; } // else function > 12 // Sending function numbers bigger than 12 is an Uhlenbrock Intellibox II extension if (function > 28) { // even Uhlenbrock Intellibox does not interpret functions > 28 correct return; } const unsigned char shift = function - 13; const uint32_t data = SetF13F44Bit(slot, on, shift); if (function < 20) { const uint8_t data4 = static_cast(data & 0x7F); Send6ByteCommand(OPC_EXP_CMD, 0x20, 0x01, 0x08, data4); return; } if (function == 20 || function == 28) { const uint8_t data4 = static_cast(((data >> 2) & 0x20) | ((data >> 9) & 0x40)); Send6ByteCommand(OPC_EXP_CMD, 0x20, 0x01, 0x05, data4); return; } // else function > 21 && function < 28 const uint8_t data4 = static_cast((data >> 8) & 0x7F); Send6ByteCommand(OPC_EXP_CMD, 0x20, 0x01, 0x09, data4); return; // else function > 28 // Actually Uhlenbrock Intellibox II does not interpret and store these values. // Therefore we don't send the values. // const uint8_t data1 = static_cast((data >> (function - 17 )) & 0x10); // const uint8_t data4 = static_cast(function - 29); // Send6ByteCommand(OPC_LOCO_FUNC2, data1, 0x01, 0x1D, data4); // return; } void LocoNet::Accessory(__attribute__((unused)) const Protocol protocol, const Address address, const DataModel::AccessoryState state, const bool on, __attribute__((unused)) const DataModel::AccessoryPulseDuration duration) { logger->Info(Languages::TextSettingAccessoryOnOff, address, Languages::GetGreenRed(state), Languages::GetOnOff(on)); const Address addressLocoNet = address - 1; unsigned char addressLow = static_cast(addressLocoNet & 0x007F); unsigned char addressHigh = static_cast(((addressLocoNet >> 7) & 0x000F) | ((state & 0x01) << 5) | ((on & 0x01) << 4)); Send4ByteCommand(OPC_SW_REQ, addressLow, addressHigh); } void LocoNet::ProgramWrite(const ProgramMode mode, __attribute__((unused)) const Address address, const CvNumber cv, const CvValue value) { switch(mode) { case ProgramModeMm: case ProgramModeDccRegister: case ProgramModeDccPage: case ProgramModeDccDirect: ProgramStart(); ProgramPT(true, mode, cv, value); lastCv = cv; break; /** * actually not used case ProgramModeDccPomLoco: ProgramMain(address, cv, value); lastCv = cv; break; */ default: break; } } void LocoNet::ProgramRead(const ProgramMode mode, __attribute__((unused)) const Address address, const CvNumber cv) { switch(mode) { case ProgramModeDccRegister: case ProgramModeDccPage: case ProgramModeDccDirect: ProgramStart(); ProgramPT(false, mode, cv); lastCv = cv; break; default: break; } } void LocoNet::ProgramStart() { unsigned char data[0x07]; data[0] = 0xE5; data[1] = 0x07; data[2] = 0x01; data[3] = 0x49; data[4] = 0x42; data[5] = 0x41; // data[6] is set in SendXByteCommand SendXByteCommand(data, sizeof(data)); isProgramming = true; } void LocoNet::ProgramEnd() { unsigned char data[0x07]; data[0] = 0xE5; data[1] = 0x07; data[2] = 0x01; data[3] = 0x49; data[4] = 0x42; data[5] = 0x40; // data[6] is set in SendXByteCommand SendXByteCommand(data, sizeof(data)); isProgramming = false; } /** * Reverse Engineered with a KM1 System control 7, but it does not work properly. void LocoNet::ProgramMain(const Address address, const CvNumber cv, const CvValue value) { unsigned char data[0x1F]; // 31 bytes data[0] = OPC_PROGRAM; data[1] = 0x1F; data[2] = 0x01; data[3] = 0x49; data[4] = 0x42; data[5] = 0x71 | ((cv >> 4) & 0x08) | ((address >> 6) & 0x02); data[6] = 0x5E; data[7] = (address & 0x7F); data[8] = ((address >> 1) & 0x7F); data[9] = (cv & 0x7F); data[10] = 0x00; data[11] = ((cv >> 8) & 0x7F); data[12] = (value & 0x7F); data[13] = 0x00; data[14] = 0x00; data[15] = 0x10; data[16] = 0x00; data[17] = 0x00; data[18] = 0x00; data[19] = 0x00; data[20] = 0x00; data[21] = 0x00; data[22] = 0x00; data[23] = 0x00; data[24] = 0x00; data[25] = 0x00; data[26] = 0x00; data[27] = 0x00; data[28] = 0x00; data[29] = 0x00; data[30] = 0x00; //data[31] is set in SendXByteCommand SendXByteCommand(data, sizeof(data)); } */ void LocoNet::ProgramPT(const bool write, const ProgramMode mode, const CvNumber cv, const CvValue value) { unsigned char data[0x1F]; // 31 bytes switch(mode) { case ProgramModeMm: data[6] = 0x62; break; case ProgramModeDccRegister: data[6] = 0x6C; break; case ProgramModeDccPage: data[6] = 0x6E; break; case ProgramModeDccDirect: data[6] = 0x70; break; default: return; } data[0] = OPC_PROGRAM; data[1] = 0x1F; data[2] = 0x01; data[3] = 0x49; data[4] = 0x42; data[5] = 0x71 | ((value >> 4) & 0x08) | ((cv >> 6) & 0x02); data[6] |= static_cast(write); data[7] = (cv & 0x7F); data[8] = (cv >> 8); data[9] = (value & 0x7F); data[10] = 0x00; data[11] = 0x00; data[12] = 0x00; data[13] = 0x00; data[14] = 0x00; data[15] = 0x10; data[16] = 0x00; data[17] = 0x00; data[18] = 0x00; data[19] = 0x00; data[20] = 0x00; data[21] = 0x00; data[22] = 0x00; data[23] = 0x00; data[24] = 0x00; data[25] = 0x00; data[26] = 0x00; data[27] = 0x00; data[28] = 0x00; data[29] = 0x00; data[30] = 0x00; //data[31] is set in SendXByteCommand SendXByteCommand(data, sizeof(data)); } /* * This code is written according the Digitrax LocoNet Persional Use Edition 1.0 * It does not work with Uhlenbrock controls... void LocoNet::Program(const bool write, const ProgramMode mode, const Address address, const CvNumber cv, const CvValue value) { unsigned char data[0x0E]; // 14 bytes data[0] = OPC_WR_SL_DATA; // data[1] is set in SendXByteCommand with sizeof(data) data[2] = ProgrammingSlot; // slot 124 // data[3] switch(mode) { case ProgramModeDccRegister: data[3] = 0x33; // reserved D0 and D1 must be set to 1 data[5] = 0x00; data[6] = 0x00; break; case ProgramModeDccPage: data[3] = 0x23; // reserved D0 and D1 must be set to 1 data[5] = 0x00; data[6] = 0x00; case ProgramModeDccDirect: data[3] = 0x2B; // reserved D0 and D1 must be set to 1 data[5] = 0x00; data[6] = 0x00; break; case ProgramModeDccPomLoco: data[3] = 0x27; data[5] = static_cast((address >> 7) & 0x7F); data[6] = static_cast(address & 0x7F); break; default: return; } data[3] |= (static_cast(write) << 6); data[4] = 0x00; // TRK data[7] = 0x00; // CVH data[8] = ((cv >> 4) & 0x30) | ((cv >> 7) & 0x01) | ((value >> 6) & 0x02); // CVL data[9] = static_cast(cv & 0x7F); // DATA7 data[10] = static_cast(value & 0x7F); data[11] = 0x00; data[12] = 0x00; // data[13] is checksum and is calculated in SendXByteCommand SendXByteCommand(data, sizeof(data)); } */ void LocoNet::Sender() { Utils::Utils::SetThreadName("LocoNet Sender"); logger->Info(Languages::TextSenderThreadStarted); while (run) { { std::unique_lock lock(entryToVerifyMutex); while (entryToVerify.GetSize() > 0) { // we wait 1s for the response from LocoNet if (entryToVerifyCV.wait_for(lock, std::chrono::seconds(1)) == std::cv_status::timeout) { break; } } } SendingQueueEntry temp = sendingQueue.Dequeue(); { std::unique_lock lock(entryToVerifyMutex); entryToVerify = temp; } const unsigned char size = temp.GetSize(); if (!size) { continue; } const unsigned char* data = temp.GetData(); logger->HexOut(data, size); serialLine.Send(data, size); } logger->Info(Languages::TextTerminatingSenderThread); } void LocoNet::Receiver() { Utils::Utils::SetThreadName("LocoNet Receiver"); logger->Info(Languages::TextReceiverThreadStarted); const unsigned char BufferSize = 128u; unsigned char buffer[BufferSize]; unsigned char checkSumCalculated; unsigned char commandLength; while (run) { if (!serialLine.IsConnected()) { logger->Error(Languages::TextUnableToReceiveData); logger->Info(Languages::TextTerminatingReceiverThread); return; } ssize_t dataLength = serialLine.ReceiveExact(buffer, 1); if (dataLength != 1) { continue; } unsigned char commandType = buffer[0] & 0xE0; switch (commandType) { case 0x80: dataLength = serialLine.ReceiveExact(buffer + 1, 1); if (dataLength != 1) { continue; } commandLength = 2; break; case 0xA0: { dataLength = serialLine.ReceiveExact(buffer + 1, 3); if (dataLength != 3) { continue; } commandLength = 4; break; } case 0xC0: { dataLength = serialLine.ReceiveExact(buffer + 1, 5); if (dataLength != 5) { continue; } commandLength = 6; break; } case 0xE0: { dataLength = serialLine.ReceiveExact(buffer + 1, 1); if (dataLength != 1) { continue; } commandLength = buffer[1]; if (commandLength < 3) { continue; } unsigned char dataToRead = commandLength - 2; dataLength = serialLine.ReceiveExact(buffer + 2, dataToRead); if (dataLength != dataToRead) { continue; } break; } default: continue; } CalcCheckSum(buffer, commandLength - 1, &checkSumCalculated); if (checkSumCalculated != buffer[commandLength - 1]) { continue; } { std::unique_lock lock(entryToVerifyMutex); const unsigned char size = entryToVerify.GetSize(); if (size && memcmp(buffer, entryToVerify.GetData(), size) == 0) { // response of sent command received entryToVerify.Reset(); entryToVerifyCV.notify_all(); continue; } } if (run == false) { break; } logger->HexIn(buffer, commandLength); Parse(buffer); } logger->Info(Languages::TextTerminatingReceiverThread); } void LocoNet::CalcCheckSum(unsigned char* data, const unsigned char length, unsigned char* checkSum) { *checkSum = 0xFF; for (unsigned char i = 0; i < length; ++i) { (*checkSum) ^= data[i]; } } void LocoNet::Parse(unsigned char* data) { switch (data[0]) { case OPC_GPON: // 0x83 logger->Info(Languages::TextTurningBoosterOn); manager->Booster(ControlTypeHardware, BoosterStateGo); return; case OPC_GPOFF: // 0x82 logger->Info(Languages::TextTurningBoosterOff); manager->Booster(ControlTypeHardware, BoosterStateStop); return; case OPC_LOCO_SPD: // 0xA0 { const Address address = CheckSlot(data[1]); if (!address) { return; } ParseSpeed(address, data[2]); return; } case OPC_LOCO_DIRF: // 0xA1 { const unsigned char slot = data[1]; const Address address = CheckSlot(slot); if (!address) { return; } ParseOrientationF0F4(slot, address, data[2]); return; } case OPC_LOCO_SND: // 0xA2 { const unsigned char slot = data[1]; const Address address = CheckSlot(slot); if (!address) { return; } ParseF5F8(slot, address, data[2]); return; } case OPC_LOCO_FUNC: // 0xA3 { const unsigned char slot = data[1]; const Address address = CheckSlot(slot); if (!address) { return; } ParseF9F12(slot, address, data[2]); return; } case OPC_SW_REQ: // 0xB0 { const bool on = static_cast((data[2] & 0x10) >> 4); if (!on) { return; } const DataModel::AccessoryState state = static_cast((data[2] & 0x20) >> 5); const Address address = (static_cast
(data[1] & 0x7F) | (static_cast
(data[2] & 0x0F) << 7)) + 1; logger->Info(Languages::TextSettingAccessory, address, Languages::GetGreenRed(state)); manager->AccessoryBaseState(ControlTypeHardware, controlID, ProtocolServer, address, state); return; } case OPC_INPUT_REP: // 0xB2 { ParseSensorData(data); return; } case OPC_LONG_ACK: // 0xB4 // Long ACK is not parsed return; case OPC_EXP_CMD: // 0xD4 { // This is an Uhlenbrock Intellibox II extension const unsigned char slot = data[2]; const Address address = CheckSlot(slot); if (!address) { return; } ParseF13F44(slot, address, data); return; } case OPC_PEER_XFER: // 0xE5 // Peer Xfer is not parsed return; case OPC_SL_RD_DATA: // 0xE7 ParseSlotReadData(data); return; case OPC_PROGRAM: // 0xED ParseProgram(data); return; default: logger->Debug(Languages::TextCommandUnknown, Utils::Integer::IntegerToHex(data[0])); return; } } Address LocoNet::CheckSlot(const unsigned char slot) { const Address address = locoCache.GetAddressOfSlot(slot); if (0 == address) { SendRequestLocoData(slot); return 0; } return address; } void LocoNet::ParseSlotReadData(const unsigned char* data) { const unsigned char dataLength = data[1]; if (dataLength != 0x0E) { return; } const unsigned char slot = data[2]; switch (slot) { case SlotHardwareType: ParseSlotHardwareType(data); return; case SlotProgramming: ParseSlotProgramming(data); return; default: ParseSlotLocoData(data); return; } } void LocoNet::ParseSlotHardwareType(const unsigned char* data) { const unsigned char ib[] = { 0x00, 0x00, 0x02, 0x00, 0x07, 0x00, 0x00, 0x00, 0x49, 0x42, 0x18 }; if (memcmp(ib, data + 3, 11) == 0) { logger->Info(Languages::TextConnectedTo, "Intellibox / TwinCenter"); return; } const unsigned char ib2[] = { 0x02, 0x42, 0x03, 0x00, 0x07, 0x00, 0x00, 0x15, 0x49, 0x42, 0x4C }; if (memcmp(ib2, data + 3, 11) == 0) { logger->Info(Languages::TextConnectedTo, "Intellibox II / IB-Basic / IB-Com"); return; } const unsigned char sc7[] = { 0x02, 0x42, 0x03, 0x00, 0x06, 0x00, 0x00, 0x15, 0x49, 0x42, 0x4D }; if (memcmp(sc7, data + 3, 11) == 0) { logger->Info(Languages::TextConnectedTo, "System Control 7"); return; } const unsigned char daisy[] = { 0x00, 0x44, 0x02, 0x00, 0x07, 0x00, 0x59, 0x01, 0x49, 0x42, 0x04 }; if (memcmp(daisy, data + 3, 11) == 0) { logger->Info(Languages::TextConnectedTo, "Daisy"); return; } const unsigned char adapter63820[] = { 0x00, 0x4C, 0x01, 0x00, 0x07, 0x00, 0x49, 0x02, 0x49, 0x42, 0x1C }; if (memcmp(adapter63820, data + 3, 11) == 0) { logger->Info(Languages::TextConnectedTo, "Adapter 63820"); return; } const unsigned char digitraxChief[] = { 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11 }; if (memcmp(digitraxChief, data + 3, 11) == 0) { logger->Info(Languages::TextConnectedTo, "Digitrax Chief"); return; } logger->Info(Languages::TextUnknownHardware); } void LocoNet::ParseSlotProgramming(const unsigned char* data) { if (data[4] != 0) { return; } CvNumber cv = (static_cast(data[9]) & 0x7F) | ((static_cast(data[8]) & 0x01) << 7) | ((static_cast(data[8]) & 0x30) << 5); if (cv == 0) { cv = lastCv; } const CvValue value = (static_cast(data[10]) & 0x7F) | ((static_cast(data[8]) & 0x02) << 6); logger->Info(Languages::TextProgramReadValue, cv, value); manager->ProgramValue(cv, value); if (isProgramming) { ProgramEnd(); } } void LocoNet::ParseSlotLocoData(const unsigned char* data) { const unsigned char slot = data[2]; const Address address = ParseLocoAddress(data[4], data[9]); logger->Debug(Languages::TextSlotHasAddress, slot, address); locoCache.SetAddress(slot, address); ParseSpeed(address, data[5]); ParseOrientationF0F4(slot, address, data[6]); ParseF5F8(slot, address, data[10]); } void LocoNet::ParseProgram(const unsigned char* data) { if (data[1] != 0x1F) { return; } switch (data[6]) { /* * actually not used because it can't be verified case 0x5E: // PoM lastCv = static_cast(data[9] & 0x7F) | static_cast((data[5] & 0x08) << 4) | static_cast((data[11] & 0x7F) << 8); break; */ case 0x6C: // register read case 0x6D: // register write case 0x6E: // page read case 0x6F: // page write case 0x70: // direct read case 0x71: // direct write // Program track lastCv = static_cast(data[7] & 0x7F) | static_cast((data[5] & 0x08) << 4) | static_cast((data[8] & 0x7F) << 8); break; default: // unknown programming method break; } } void LocoNet::ParseSpeed(const Address address, const unsigned char data) { Speed speed = static_cast(data); if (speed) { --speed; } speed <<= 3; logger->Info(Languages::TextSettingSpeed, address, speed); manager->LocoSpeed(ControlTypeHardware, controlID, ProtocolServer, address, speed); } unsigned char LocoNet::CalcSpeed(const Speed speed) { Speed calculatedSpeed = speed; calculatedSpeed >>= 3; if (calculatedSpeed) { ++calculatedSpeed; } if (calculatedSpeed > 127) { calculatedSpeed = 127; } return static_cast(calculatedSpeed); } void LocoNet::ParseOrientationF0F4(const unsigned char slot, const Address address, const uint8_t data) { const uint8_t oldData = locoCache.GetOrientationF0F4(slot); const uint8_t dataDiff = data ^ oldData; locoCache.SetOrientationF0F4(slot, data); if (dataDiff & 0x20) { Orientation orientation = static_cast((data >> 5) & 0x01); logger->Info(Languages::TextSettingOrientation, address, orientation); manager->LocoOrientation(ControlTypeHardware, controlID, ProtocolServer, address, orientation); } if (dataDiff & 0x10) { ParseFunction(address, data, 0, 4); } if (dataDiff & 0x01) { ParseFunction(address, data, 1, 0); } if (dataDiff & 0x02) { ParseFunction(address, data, 2, 1); } if (dataDiff & 0x04) { ParseFunction(address, data, 3, 2); } if (dataDiff & 0x08) { ParseFunction(address, data, 4, 3); } } void LocoNet::ParseF5F8(const unsigned char slot, const Address address, const uint8_t data) { const uint8_t oldData = locoCache.GetF5F8(slot); const uint8_t dataDiff = data ^ oldData; locoCache.SetF5F8(slot, data); if (dataDiff & 0x01) { ParseFunction(address, data, 5, 0); } if (dataDiff & 0x02) { ParseFunction(address, data, 6, 1); } if (dataDiff & 0x04) { ParseFunction(address, data, 7, 2); } if (dataDiff & 0x08) { ParseFunction(address, data, 8, 3); } } void LocoNet::ParseF9F12(const unsigned char slot, const Address address, const uint8_t data) { const uint8_t oldData = locoCache.GetF9F12(slot); const uint8_t dataDiff = data ^ oldData; locoCache.SetF9F12(slot, data); if (dataDiff & 0x01) { ParseFunction(address, data, 9, 0); } if (dataDiff & 0x02) { ParseFunction(address, data, 10, 1); } if (dataDiff & 0x04) { ParseFunction(address, data, 11, 2); } if (dataDiff & 0x08) { ParseFunction(address, data, 12, 3); } } void LocoNet::ParseFunction(const Address address, const uint32_t data, const DataModel::LocoFunctionNr nr, const uint8_t shift) { DataModel::LocoFunctionState state = static_cast((data >> shift) & 0x01); logger->Info(Languages::TextSettingFunction, nr, address, state); manager->LocoFunctionState(ControlTypeHardware, controlID, ProtocolServer, address, nr, state); } void LocoNet::ParseF13F44(const unsigned char slot, const Address address, const unsigned char* data) { // This is an Uhlenbrock Intellibox II extension const uint32_t oldData = locoCache.GetF13F44(slot); uint32_t newData = oldData; if (data[1] == 0x20) { if (data[3] == 0x08) { newData &= 0xFFFFFF80; newData |= (static_cast(data[4]) & 0x7F); } else if (data[3] == 0x09) { newData &= 0xFFFF80FF; newData |= ((static_cast(data[4]) << 8) & 0x7F00); } else if (data[3] == 0x05) { newData &= 0xFFFFFF7F; newData |= ((static_cast(data[4]) << 2) & 0x80); newData &= 0xFFFF7FFF; newData |= ((static_cast(data[4]) << 9) & 0x8000); } } else if ((data[1] & 0xEF) == 0) { const uint8_t nr = data[3]; if (nr < 29 || nr > 44) { return; } newData &= ~(1 << (nr - 13)); newData |= (static_cast(data[1]) << (nr - 17)); } const uint32_t dataDiff = newData ^ oldData; locoCache.SetF13F44(slot, newData); for (int i = 0; i < 32; ++i) { if ((dataDiff >> i) & 0x01) { ParseFunction(address, newData, i + 13, i); } } } void LocoNet::ParseSensorData(const unsigned char* data) { if (!(data[2] & 0x40)) { // control bit not set return; } const FeedbackPin pin = (((static_cast(data[2]) >> 5) & 0x0001) | ((static_cast(data[1]) <<1) & 0x00FE) | ((static_cast(data[2]) << 8) & 0x7F)) + 1; // LocoNet is 0-based, RailControl is 1-based DataModel::Feedback::FeedbackState state = static_cast((data[2] >> 4) & 0x01); logger->Info(Languages::TextFeedbackChange, pin & 0x000F, pin >> 4, Languages::GetText(state ? Languages::TextOn : Languages::TextOff)); manager->FeedbackState(controlID, pin, state); } void LocoNet::Send2ByteCommand(const unsigned char data) { unsigned char buffer[2]; buffer[0] = data; CalcCheckSum(buffer, 1, buffer + 1); sendingQueue.EnqueueBack(SendingQueueEntry(sizeof(buffer), buffer)); } void LocoNet::Send4ByteCommand(const unsigned char data0, const unsigned char data1, const unsigned char data2) { unsigned char buffer[4]; buffer[0] = data0; buffer[1] = data1; buffer[2] = data2; CalcCheckSum(buffer, 3, buffer + 3); sendingQueue.EnqueueBack(SendingQueueEntry(sizeof(buffer), buffer)); } void LocoNet::Send6ByteCommand(const unsigned char data0, const unsigned char data1, const unsigned char data2, const unsigned char data3, const unsigned char data4) { unsigned char buffer[6]; buffer[0] = data0; buffer[1] = data1; buffer[2] = data2; buffer[3] = data3; buffer[4] = data4; CalcCheckSum(buffer, 5, buffer + 5); sendingQueue.EnqueueBack(SendingQueueEntry(sizeof(buffer), buffer)); } void LocoNet::SendXByteCommand(unsigned char* data, unsigned char dataLength) { data[1] = dataLength; CalcCheckSum(data, dataLength - 1, data + dataLength - 1); sendingQueue.EnqueueBack(SendingQueueEntry(dataLength, data)); } uint8_t LocoNet::SetOrientationF0F4Bit(const unsigned char slot, const bool on, const unsigned char shift) { uint8_t data = locoCache.GetOrientationF0F4(slot); data &= (~(0x01u << shift)); data |= (on << shift); locoCache.SetOrientationF0F4(slot, data); return data; } uint8_t LocoNet::SetF5F8Bit(const unsigned char slot, const bool on, const unsigned char shift) { uint8_t data = locoCache.GetF5F8(slot); data &= (~(0x01u << shift)); data |= (on << shift); locoCache.SetF5F8(slot, data); return data; } uint8_t LocoNet::SetF9F12Bit(const unsigned char slot, const bool on, const unsigned char shift) { uint8_t data = locoCache.GetF9F12(slot); data &= (~(0x01u << shift)); data |= (on << shift); locoCache.SetF9F12(slot, data); return data; } uint32_t LocoNet::SetF13F44Bit(const unsigned char slot, const bool on, const unsigned char shift) { uint32_t data = locoCache.GetF13F44(slot); data &= (~(0x00000001u << shift)); data |= (on << shift); locoCache.SetF13F44(slot, data); return data; } } // namespace } // namespace railcontrol-24+dfsg1/Hardware/Protocols/LocoNet.h000066400000000000000000000231341500456250600220740ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include "Hardware/HardwareInterface.h" #include "Hardware/HardwareParams.h" #include "Hardware/Protocols/LocoNetLocoCache.h" #include "Logger/Logger.h" #include "Network/Serial.h" // Protocol specification at https://www.digitrax.com/static/apps/cms/media/documents/loconet/loconetpersonaledition.pdf // Programming specification does not fit for Uhlenbrock Intellibox II namespace Hardware { namespace Protocols { class LocoNet: Hardware::HardwareInterface { public: LocoNet() = delete; LocoNet(const LocoNet&) = delete; LocoNet& operator=(const LocoNet&) = delete; LocoNet(const HardwareParams* params, const std::string& controlName, const unsigned int dataSpeed); virtual ~LocoNet(); inline void Start() override { SendRequestLocoData(0x7F); Utils::Utils::SleepForMilliseconds(25); SendRequestLocoData(0); Utils::Utils::SleepForMilliseconds(25); } inline Hardware::Capabilities GetCapabilities() const override { return Hardware::CapabilityLoco | Hardware::CapabilityAccessory | Hardware::CapabilityFeedback | Hardware::CapabilityProgram | Hardware::CapabilityProgramMmWrite | Hardware::CapabilityProgramDccRegisterRead | Hardware::CapabilityProgramDccRegisterWrite | Hardware::CapabilityProgramDccPageRead | Hardware::CapabilityProgramDccPageWrite | Hardware::CapabilityProgramDccDirectRead | Hardware::CapabilityProgramDccDirectWrite; } void GetLocoProtocols(std::vector& protocols) const override { protocols.push_back(ProtocolServer); } bool LocoProtocolSupported(Protocol protocol) const override { return (protocol == ProtocolServer); } void GetAccessoryProtocols(std::vector& protocols) const override { protocols.push_back(ProtocolServer); } bool AccessoryProtocolSupported(Protocol protocol) const override { return (protocol == ProtocolServer); } void Booster(const BoosterState status) override; void LocoSpeed(const Protocol protocol, const Address address, const Speed speed) override; void LocoOrientation(const Protocol protocol, const Address address, const Orientation orientation) override; void LocoFunction(const Protocol protocol, const Address address, const DataModel::LocoFunctionNr function, const DataModel::LocoFunctionState on) override; void Accessory(const Protocol protocol, const Address address, const DataModel::AccessoryState state, const bool on, __attribute__((unused)) const DataModel::AccessoryPulseDuration duration) override; void ProgramWrite(const ProgramMode mode, const Address address, const CvNumber cv, const CvValue value) override; void ProgramRead(const ProgramMode mode, const Address address, const CvNumber cv) override; private: // longest known LocoNet-Command is OPC_START_PROGRAM (Uhlenbrock Extension) static const unsigned char MaxDataLength = 0x1F; enum OpCodes : unsigned char { OPC_BUSY = 0x81, OPC_GPOFF = 0x82, OPC_GPON = 0x83, OPC_IDLE = 0x85, OPC_LOCO_SPD = 0xA0, OPC_LOCO_DIRF = 0xA1, OPC_LOCO_SND = 0xA2, OPC_LOCO_FUNC = 0xA3, OPC_SW_REQ = 0xB0, OPC_SW_REP = 0xB1, OPC_INPUT_REP = 0xB2, OPC_LONG_ACK = 0xB4, OPC_SLOT_STAT1 = 0xB5, OPC_CONSIST_FUNC = 0xB6, OPC_UNLINK_SLOTS = 0xB8, OPC_LINK_SLOTS = 0xB9, OPC_MOVE_SLOTS = 0xBA, OPC_RQ_SL_DATA = 0xBB, OPC_SW_STATE = 0xBC, OPC_SW_ACK = 0xBD, OPC_LOCO_ADR = 0xBF, OPC_PEER_XFER = 0xE5, // Intellibox-II see below OPC_SL_RD_DATA = 0xE7, OPC_IMM_PACKET = 0xED, // Intellibox-II see below OPC_WR_SL_DATA = 0xEF, // Intellibox-II codes OPC_LOCO_XADR = 0xBE, OPC_EXP_CMD = 0xD4, OPC_START_PROGRAM = 0xE5, // not documented OPC_SL_RD_DATA_EXT = 0xE6, OPC_PROGRAM = 0xED, // not documented OPC_WR_SL_DATA_EXT = 0xEE }; static const unsigned char SlotHardwareType = 0; static const unsigned char SlotProgramming = 124; class SendingQueueEntry { public: inline SendingQueueEntry() : size(0) { } inline SendingQueueEntry(unsigned char size, const unsigned char* data) : size(std::min(size, static_cast(MaxDataLength))) { memcpy(this->data, data, this->size); } inline SendingQueueEntry(const SendingQueueEntry& rhs) = default; inline SendingQueueEntry& operator=(const SendingQueueEntry& rhs) = default; inline unsigned char GetSize() const { return size; } inline const unsigned char* GetData() const { return data; } inline void Reset() { size = 0; } private: volatile unsigned char size; unsigned char data[MaxDataLength]; }; void Sender(); void Receiver(); static void CalcCheckSum(unsigned char* data, const unsigned char length, unsigned char* checkSum); void Parse(unsigned char* data); Address CheckSlot(const unsigned char slot); void ParseSlotReadData(const unsigned char* data); void ParseSlotHardwareType(const unsigned char* data); void ParseSlotProgramming(const unsigned char* data); void ParseSlotLocoData(const unsigned char* data); void ParseProgram(const unsigned char* data); inline Address ParseLocoAddress(const unsigned char data1, const unsigned char data2) { return static_cast
(data1 & 0x7F) | (static_cast
(data2 & 0x3F) << 7); } void ParseSpeed(const Address address, const unsigned char data); static unsigned char CalcSpeed(const Speed speed); void ParseOrientationF0F4(const unsigned char slot, const Address address, const uint8_t data); void ParseF5F8(const unsigned char slot, const Address address, const uint8_t data); void ParseF9F12(const unsigned char slot, const Address address, const uint8_t data); void ParseFunction(const Address address, const uint32_t data, const DataModel::LocoFunctionNr nr, const uint8_t shift); void ParseF13F44(const unsigned char slot, const Address address, const unsigned char* data); void ParseSensorData(const unsigned char* data); void Send2ByteCommand(const unsigned char data0); void Send4ByteCommand(const unsigned char data0, const unsigned char data1, const unsigned char data2); void Send6ByteCommand(const unsigned char data0, const unsigned char data1, const unsigned char data2, const unsigned char data3, const unsigned char data4); void SendXByteCommand(unsigned char* data, unsigned char dataLength); inline void SendRequestLocoData(const unsigned char slot) { Send4ByteCommand(OPC_RQ_SL_DATA, slot, 0); } inline void SendLocoAddress(const Address address) { Send4ByteCommand(OPC_LOCO_ADR, static_cast((address >> 7) & 0x7F), static_cast(address & 0x7F)); } inline void SendLocoSpeed(const unsigned char slot, const Speed speed) { Send4ByteCommand(OPC_LOCO_SPD, slot, CalcSpeed(speed)); } inline void SendLocoOrientationF0F4(const unsigned char slot, const uint8_t orientationF0F4) { Send4ByteCommand(OPC_LOCO_DIRF, slot, orientationF0F4); } inline void SendLocoF5F8(const unsigned char slot, const uint8_t f5F8) { Send4ByteCommand(OPC_LOCO_SND, slot, f5F8); } inline void SendLocoF9F12(const unsigned char slot, const uint8_t f9F12) { Send4ByteCommand(OPC_LOCO_FUNC, slot, f9F12); } uint8_t SetOrientationF0F4Bit(const unsigned char slot, const bool on, const unsigned char shift); uint8_t SetF5F8Bit(const unsigned char slot, const bool on, const unsigned char shift); uint8_t SetF9F12Bit(const unsigned char slot, const bool on, const unsigned char shift); uint32_t SetF13F44Bit(const unsigned char slot, const bool on, const unsigned char shift); void ProgramStart(); void ProgramEnd(); // void ProgramMain(const Address address, // const CvNumber cv, // const CvValue value); void ProgramPT(const bool write, const ProgramMode mode, const CvNumber cv, const CvValue value = 0); volatile bool run; mutable Network::Serial serialLine; std::thread senderThread; std::thread receiverThread; LocoNetLocoCache locoCache; Utils::ThreadSafeQueue sendingQueue; SendingQueueEntry entryToVerify; mutable std::mutex entryToVerifyMutex; std::condition_variable entryToVerifyCV; CvNumber lastCv; bool isProgramming; }; } // namespace } // namespace railcontrol-24+dfsg1/Hardware/Protocols/LocoNetLocoCache.h000066400000000000000000000110231500456250600236270ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "DataTypes.h" namespace Hardware { namespace Protocols { class LocoNetLocoCacheEntry { public: inline LocoNetLocoCacheEntry() : address(0), orientationF0F4(0), f5F8(0), f9F12(0), f13F44(0) { } inline void SetAddress(const Address address) { this->address = address; } inline Address GetAddress() const { return address; } inline void SetOrientationF0F4(const uint8_t orientationF0F4) { this->orientationF0F4 = orientationF0F4; } inline uint8_t GetOrientationF0F4() { return orientationF0F4; } inline void SetF5F8(const uint8_t f5F8) { this->f5F8 = f5F8; } inline unsigned char GetF5F8() { return f5F8; } inline void SetF9F12(const uint8_t f9F12) { this->f9F12 = f9F12; } inline uint8_t GetF9F12() { return f9F12; } inline void SetF13F44(const uint32_t f13F44) { this->f13F44 = f13F44; } inline uint32_t GetF13F44() { return f13F44; } Address address; uint8_t orientationF0F4; uint8_t f5F8; uint8_t f9F12; uint32_t f13F44; }; class LocoNetLocoCache { public: LocoNetLocoCache(const LocoNetLocoCache&) = delete; LocoNetLocoCache& operator=(const LocoNetLocoCache&) = delete; LocoNetLocoCache() { } virtual ~LocoNetLocoCache() { } inline void SetAddress(const unsigned char slot, const Address address) { if (slot == 0 || slot > MaxLocoNetSlot) { return; } entries[slot].SetAddress(address); } inline unsigned char GetSlotOfAddress(const Address address) { for (unsigned char slot = MinLocoNetSlot; slot <= MaxLocoNetSlot; ++slot) { if (address == entries[slot].GetAddress()) { return slot; } } return 0; } inline Address GetAddressOfSlot(unsigned char slot) { if (slot == 0 || slot > MaxLocoNetSlot) { return 0; } return entries[slot].GetAddress(); } inline void SetOrientationF0F4(const unsigned char slot, const uint8_t orientationF0F4) { if (slot == 0 || slot > MaxLocoNetSlot) { return; } entries[slot].SetOrientationF0F4(orientationF0F4); } inline uint8_t GetOrientationF0F4(const unsigned char slot) { if (slot == 0 || slot > MaxLocoNetSlot) { return 0; } return entries[slot].GetOrientationF0F4(); } inline void SetF5F8(const unsigned char slot, const uint8_t f5F8) { if (slot == 0 || slot > MaxLocoNetSlot) { return; } entries[slot].SetF5F8(f5F8); } inline uint8_t GetF5F8(const unsigned char slot) { if (slot == 0 || slot > MaxLocoNetSlot) { return 0; } return entries[slot].GetF5F8(); } inline void SetF9F12(const unsigned char slot, const uint8_t f9F12) { if (slot == 0 || slot > MaxLocoNetSlot) { return; } entries[slot].SetF9F12(f9F12); } inline uint8_t GetF9F12(const unsigned char slot) { if (slot == 0 || slot > MaxLocoNetSlot) { return 0; } return entries[slot].GetF9F12(); } inline void SetF13F44(const unsigned char slot, const uint32_t f13F44) { if (slot == 0 || slot > MaxLocoNetSlot) { return; } entries[slot].SetF13F44(f13F44); } inline uint32_t GetF13F44(const unsigned char slot) { if (slot == 0 || slot > MaxLocoNetSlot) { return 0; } return entries[slot].GetF13F44(); } static const unsigned char MinLocoNetSlot = 1; static const unsigned char MaxLocoNetSlot = 119; private: LocoNetLocoCacheEntry entries[MaxLocoNetSlot + 1]; }; } // namespace } // namespace railcontrol-24+dfsg1/Hardware/Protocols/MaerklinCAN.h000066400000000000000000000135601500456250600226170ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include "DataModel/AccessoryBase.h" #include "DataModel/LocoFunctions.h" #include "Hardware/Capabilities.h" #include "Hardware/HardwareInterface.h" #include "Hardware/HardwareParams.h" #include "Hardware/LocoCache.h" #include "Hardware/Protocols/MaerklinCANCommon.h" #include "Logger/Logger.h" #include "Utils/Integer.h" // CAN protocol specification at http://streaming.maerklin.de/public-media/cs2/cs2CAN-Protokoll-2_0.pdf // Very interesting is also http://www.mbernstein.de/modellbahn/can/bem.htm namespace Hardware { namespace Protocols { class MaerklinCAN: protected HardwareInterface, protected MaerklinCANCommon { public: MaerklinCAN() = delete; MaerklinCAN(const MaerklinCAN&) = delete; MaerklinCAN& operator=(const MaerklinCAN&) = delete; inline Hardware::Capabilities GetCapabilities() const override { return Hardware::CapabilityLoco | Hardware::CapabilityAccessory | Hardware::CapabilityFeedback | Hardware::CapabilityProgram | Hardware::CapabilityProgramMmWrite | Hardware::CapabilityProgramMfxRead | Hardware::CapabilityProgramMfxWrite | Hardware::CapabilityProgramDccDirectRead | Hardware::CapabilityProgramDccDirectWrite | Hardware::CapabilityProgramDccPomLocoWrite | Hardware::CapabilityProgramDccPomAccessoryWrite | Hardware::CapabilityLocoDatabase; } void GetLocoProtocols(std::vector& protocols) const override { protocols.push_back(ProtocolMM); protocols.push_back(ProtocolMFX); protocols.push_back(ProtocolDCC); } inline bool LocoProtocolSupported(Protocol protocol) const override { return ((protocol == ProtocolMM) || (protocol == ProtocolMFX) || (protocol == ProtocolDCC)); } inline void GetAccessoryProtocols(std::vector& protocols) const override { protocols.push_back(ProtocolMM); protocols.push_back(ProtocolDCC); } inline bool AccessoryProtocolSupported(Protocol protocol) const override { return ((protocol == ProtocolMM) || (protocol == ProtocolDCC)); } void Booster(const BoosterState status) override { MaerklinCANCommon::Booster(status); } void LocoSpeed(const Protocol protocol, const Address address, const Speed speed) override { MaerklinCANCommon::LocoSpeed(protocol, address, speed); } void LocoOrientation(const Protocol protocol, const Address address, const Orientation orientation) override { MaerklinCANCommon::LocoOrientation(protocol, address, orientation); } void LocoFunction(const Protocol protocol, const Address address, const DataModel::LocoFunctionNr function, const DataModel::LocoFunctionState on) override { MaerklinCANCommon::LocoFunction(protocol, address, function, on); } void Accessory(const Protocol protocol, const Address address, const DataModel::AccessoryState state, const bool on, __attribute__((unused)) const DataModel::AccessoryPulseDuration duration) override { MaerklinCANCommon::Accessory(protocol, address, state, on); } void ProgramRead(const ProgramMode mode, const Address address, const CvNumber cv) override { MaerklinCANCommon::ProgramRead(mode, address, cv); } void ProgramWrite(const ProgramMode mode, const Address address, const CvNumber cv, const CvValue value) override { MaerklinCANCommon::ProgramWrite(mode, address, cv, value); } const std::map& GetLocoDatabase() const override { return locoCache.GetAll(); } DataModel::LocoConfig GetLocoByMatchKey(const std::string& matchKey) const override { return DataModel::LocoConfig(locoCache.Get(matchKey)); } DataModel::LocoConfig GetMultipleUnitByMatchKey(const std::string& matchKey) const override { return DataModel::LocoConfig(locoCache.Get(matchKey)); } void SetLocoIdOfMatchKey(const LocoID locoId, const std::string& matchKey) override { locoCache.SetLocoId(locoId, matchKey); } void SetMultipleUnitIdOfMatchKey(const LocoID locoId, const std::string& matchKey) override { locoCache.SetLocoId(locoId, matchKey); } protected: inline MaerklinCAN(const HardwareParams* params, const std::string& fullName, const std::string& shortName) : HardwareInterface(params->GetManager(), params->GetControlID(), fullName, shortName), MaerklinCANCommon("affeaffe", params->GetControlID(), params->GetManager(), Utils::Utils::StringToBool(params->GetArg2()), HardwareInterface::logger), locoCache(params->GetControlID(), params->GetManager()) { } virtual ~MaerklinCAN() { } protected: virtual void CacheSave(LocoCacheEntry& entry) override { locoCache.Save(entry); } virtual void CacheSave(LocoCacheEntry& entry, const std::string& oldMatchKey) override { locoCache.Save(entry, oldMatchKey); } virtual LocoID CacheDelete(const std::string& matchKey) override { return locoCache.Delete(matchKey); } virtual void CacheUpdateSlaves() override { locoCache.UpdateSlaves(); } private: LocoCache locoCache; }; }} // namespace railcontrol-24+dfsg1/Hardware/Protocols/MaerklinCANCommon.cpp000066400000000000000000001243761500456250600243330ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include #include "Hardware/Protocols/MaerklinCANCommon.h" #include "Hardware/ZLib.h" using std::deque; using std::string; using std::vector; namespace Hardware { namespace Protocols { void MaerklinCANCommon::Init() { run = true; receiverThread = std::thread(&MaerklinCANCommon::ReceiverInternal, this); pingThread = std::thread(&MaerklinCANCommon::PingSender, this); } MaerklinCANCommon::~MaerklinCANCommon() { run = false; receiverThread.join(); pingThread.join(); if (canFileData != nullptr) { free(canFileData); canFileData = nullptr; } } void MaerklinCANCommon::Wait(const unsigned int duration) const { unsigned int wait = duration; while (run && (!hasCs2Main || isCs2Main) && wait) { Utils::Utils::SleepForSeconds(1); --wait; } } void MaerklinCANCommon::ReceiverInternal() { Utils::Utils::SetThreadName("Maerklin CAN Receiver"); logger->Info(Languages::TextReceiverThreadStarted); Receiver(); logger->Info(Languages::TextTerminatingReceiverThread); } void MaerklinCANCommon::PingSender() { Utils::Utils::SetThreadName("Maerklin CAN Ping"); logger->Info(Languages::TextPingSenderStarted); Wait(1); while (run && (!hasCs2Main || isCs2Main)) { Ping(); Wait(10); } if (run && hasCs2Main && !isCs2Main) { RequestLoks(); } logger->Info(Languages::TextTerminatingPingSender); } void MaerklinCANCommon::CreateCommandHeader(unsigned char* const buffer, const CanCommand command, const CanResponse response, const CanLength length) { const CanPrio prio = 0; buffer[0] = (prio << 1) | (command >> 7); buffer[1] = (command << 1) | (response & 0x01); Utils::Integer::ShortToDataBigEndian(hash, buffer + 2); buffer[4] = length; buffer[5] = 0; buffer[6] = 0; buffer[7] = 0; buffer[8] = 0; buffer[9] = 0; buffer[10] = 0; buffer[11] = 0; buffer[12] = 0; } void MaerklinCANCommon::ParseAddressProtocol(const Address input, Address& address, Protocol& protocol, LocoType& type) { address = input; Address maskedAddress = address & 0xFC00; type = LocoTypeLoco; if ((maskedAddress == 0x0000) // MM Loco || (maskedAddress == 0x1000) // MM Loco (unused by CS2) || (maskedAddress == 0x2000) // MM Accessory (unused by CS2) || (maskedAddress == 0x3000)) // MM Accessory { protocol = ProtocolMM; address &= 0x03FF; return; } // 0x0800 is SX1 Loco (unused by CS2) // 0x2800 is SX1 Accessory (unused by CS2) if (maskedAddress == 0x2C00) // Multiple Unit { protocol = ProtocolMultipleUnit; type = LocoTypeMultipleUnit; address &= 0x03FF; return; } if ((maskedAddress == 0x3800) // DCC Accessory || (maskedAddress == 0x3C00)) // DCC Accessory { protocol = ProtocolDCC; address &= 0x03FF; return; } maskedAddress = address & 0xC000; address &= 0x3FFF; if (maskedAddress == 0x4000) // MFX Loco { protocol = ProtocolMFX; return; } if (maskedAddress == 0xC000) // DCC Loco { protocol = ProtocolDCC; return; } protocol = ProtocolNone; address = 0; } MaerklinCANCommon::CanFileCrc MaerklinCANCommon::CalcCrc(const unsigned char *data, const size_t length) { // Maerklin uses CRC-16-CCITT with seed 0xffff and with polynom 0x1021 -> x^16 + x^12 +x^5 + 1 static const CanFileCrc CrcTable[256] = { 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 }; CanFileCrc crc = 0xffff; // seed for (size_t count = 0; count < length; ++count) { const CanFileCrc temp = (*data++ ^ (crc >> 8)) & 0xff; crc = CrcTable[temp] ^ (crc << 8); } return crc; } MaerklinCANCommon::CanHash MaerklinCANCommon::CalcHash(const CanUid uid) { const CanHash calc = (uid >> 16) ^ (uid & 0xFFFF); CanHash hash = ((calc << 3) | 0x0300) & 0xFF00; hash |= (calc & 0x007F); return hash; } void MaerklinCANCommon::GenerateUidHash() { uid = rand(); string uidString = Utils::Integer::IntegerToHex(uid); // FIXME: params->SetArg5(uidString); hash = CalcHash(uid); logger->Info(Languages::TextMyUidHash, uidString, Utils::Integer::IntegerToHex(hash)); } void MaerklinCANCommon::CreateLocalIDLoco(unsigned char* buffer, const Protocol protocol, const Address address) const { uint32_t localID = address; if (protocol == ProtocolDCC) { localID |= 0xC000; } else if (protocol == ProtocolMFX) { localID |= 0x4000; } else if (protocol == ProtocolMultipleUnit) { localID |= 0x2000; } // else expect PROTOCOL_MM2: do nothing Utils::Integer::IntToDataBigEndian(localID, buffer + 5); } void MaerklinCANCommon::CreateLocalIDAccessory(unsigned char* buffer, const Protocol protocol, const Address address) const { uint32_t localID = address - 1; // GUI-address is 1-based, protocol-address is 0-based if (protocol == ProtocolDCC) { localID |= 0x3800; } else // ProtocolMM { localID |= 0x3000; } Utils::Integer::IntToDataBigEndian(localID, buffer + 5); } void MaerklinCANCommon::Booster(const BoosterState status) { unsigned char buffer[CANCommandBufferLength]; logger->Info(status ? Languages::TextTurningBoosterOn : Languages::TextTurningBoosterOff); CreateCommandHeader(buffer, CanCommandSystem, CanResponseCommand, 5); buffer[9] = status; SendInternal(buffer); } void MaerklinCANCommon::LocoSpeed(const Protocol protocol, const Address address, const Speed speed) { unsigned char buffer[CANCommandBufferLength]; logger->Info(Languages::TextSettingSpeedWithProtocol, Utils::Utils::ProtocolToString(protocol), address, speed); CreateCommandHeader(buffer, CanCommandLocoSpeed, CanResponseCommand, 6); CreateLocalIDLoco(buffer, protocol, address); Utils::Integer::ShortToDataBigEndian(speed, buffer + 9); SendInternal(buffer); } void MaerklinCANCommon::LocoOrientation(const Protocol protocol, const Address address, const Orientation orientation) { unsigned char buffer[CANCommandBufferLength]; logger->Info(Languages::TextSettingDirectionOfTravelWithProtocol, Utils::Utils::ProtocolToString(protocol), address, Languages::GetLeftRight(orientation)); CreateCommandHeader(buffer, CanCommandLocoDirection, CanResponseCommand, 5); CreateLocalIDLoco(buffer, protocol, address); buffer[9] = (orientation ? 1 : 2); SendInternal(buffer); } void MaerklinCANCommon::LocoFunction(const Protocol protocol, const Address address, const DataModel::LocoFunctionNr function, const DataModel::LocoFunctionState on) { unsigned char buffer[CANCommandBufferLength]; logger->Info(Languages::TextSettingFunctionWithProtocol, static_cast(function), Utils::Utils::ProtocolToString(protocol), address, Languages::GetOnOff(on)); CreateCommandHeader(buffer, CanCommandLocoFunction, CanResponseCommand, 6); CreateLocalIDLoco(buffer, protocol, address); buffer[9] = function; buffer[10] = (on == DataModel::LocoFunctionStateOn); SendInternal(buffer); } void MaerklinCANCommon::Accessory(const Protocol protocol, const Address address, const DataModel::AccessoryState state, const bool on) { unsigned char buffer[CANCommandBufferLength]; logger->Info(Languages::TextSettingAccessoryWithProtocol, Utils::Utils::ProtocolToString(protocol), address, Languages::GetGreenRed(state), Languages::GetOnOff(on)); CreateCommandHeader(buffer, CanCommandAccessory, CanResponseCommand, 6); CreateLocalIDAccessory(buffer, protocol, address); buffer[9] = state & 0x03; buffer[10] = static_cast(on); SendInternal(buffer); } void MaerklinCANCommon::ProgramRead(const ProgramMode mode, const Address address, const CvNumber cv) { Address addressInternal = address; Protocol protocol = ProtocolNone; switch (mode) { case ProgramModeDccDirect: logger->Info(Languages::TextProgramDccDirectRead, cv); protocol = ProtocolDCC; break; case ProgramModeMfx: logger->Info(Languages::TextProgramMfxRead, address, cv); protocol = ProtocolMFX; break; default: return; } unsigned char buffer[CANCommandBufferLength]; CreateCommandHeader(buffer, CanCommandReadConfig, CanResponseCommand, 7); CreateLocalIDLoco(buffer, protocol, addressInternal); Utils::Integer::ShortToDataBigEndian(cv, buffer + 9); buffer[11] = 1; SendInternal(buffer); } void MaerklinCANCommon::ProgramWrite(const ProgramMode mode, const Address address, const CvNumber cv, const CvValue value) { Address addressInternal = address; Protocol protocol = ProtocolNone; unsigned char controlFlags = 0; switch (mode) { case ProgramModeMm: logger->Info(Languages::TextProgramMm, cv, value); protocol = ProtocolMM; addressInternal = 80; break; case ProgramModeMmPom: logger->Info(Languages::TextProgramMmPom, address, cv, value); protocol = ProtocolMM; controlFlags = 1 << 7; break; case ProgramModeDccDirect: logger->Info(Languages::TextProgramDccDirectWrite, cv, value); protocol = ProtocolDCC; addressInternal = 0; break; case ProgramModeDccPomLoco: logger->Info(Languages::TextProgramDccPomLocoWrite, address, cv, value); protocol = ProtocolDCC; controlFlags = 1 << 7; break; case ProgramModeMfx: logger->Info(Languages::TextProgramMfxWrite, address, cv, value); protocol = ProtocolMFX; controlFlags = 1 << 7; break; default: return; } unsigned char buffer[CANCommandBufferLength]; CreateCommandHeader(buffer, CanCommandWriteConfig, CanResponseCommand, 8); CreateLocalIDLoco(buffer, protocol, addressInternal); Utils::Integer::ShortToDataBigEndian(cv, buffer + 9); buffer[11] = value; buffer[12] = controlFlags; SendInternal(buffer); } void MaerklinCANCommon::Ping() { unsigned char buffer[CANCommandBufferLength]; CreateCommandHeader(buffer, CanCommandPing, CanResponseCommand, 0); SendInternal(buffer); } void MaerklinCANCommon::RequestLoks() { unsigned char buffer[CANCommandBufferLength]; CreateCommandHeader(buffer, CanCommandRequestConfigData, CanResponseCommand, 8); buffer[5] = 'l'; buffer[6] = 'o'; buffer[7] = 'k'; buffer[8] = 's'; buffer[9] = 0; buffer[10] = 0; buffer[11] = 0; buffer[12] = 0; SendInternal(buffer); } void MaerklinCANCommon::Parse(const unsigned char* buffer) { CanResponse response = ParseResponse(buffer); CanCommand command = ParseCommand(buffer); CanLength length = ParseLength(buffer); logger->HexIn(buffer, 5 + length); const CanHash receivedHash = ParseHash(buffer); if (receivedHash == hash) { uint16_t deviceType = Utils::Integer::DataBigEndianToShort(buffer + 11); if (command == CanCommandPing && response == true) { if (deviceType == CanDeviceCs2Main) { hasCs2Main = true; logger->Info(Languages::TextCs2MainFound); } } else if (command != CanCommandConfigData) { GenerateUidHash(); } } if (response) { switch (command) { case CanCommandS88Event: ParseResponseS88Event(buffer); return; case CanCommandReadConfig: ParseResponseReadConfig(buffer); return; case CanCommandPing: ParseResponsePing(buffer); return; case CanCommandLocoSpeed: ParseResponseLocoSpeed(buffer); return; case CanCommandLocoDirection: ParseResponseLocoDirection(buffer); return; case CanCommandLocoFunction: ParseResponseLocoFunction(buffer); return; case CanCommandAccessory: ParseResponseAccessory(buffer); return; default: return; } } switch (command) { case CanCommandSystem: ParseCommandSystem(buffer); return; case CanCommandRequestConfigData: ParseCommandRequestConfigData(buffer); return; case CanCommandConfigData: ParseCommandConfigData(buffer); return; case CanCommandPing: ParseCommandPing(buffer); return; default: return; } } void MaerklinCANCommon::ParseCommandSystem(const unsigned char* const buffer) { if (ParseLength(buffer) != 5) { return; } CanSubCommand subcmd = ParseSubCommand(buffer); switch (subcmd) { case CanSubCommandStop: // system stop manager->Booster(ControlTypeHardware, BoosterStateStop); return; case CanSubCommandGo: // system go manager->Booster(ControlTypeHardware, BoosterStateGo); return; } } void MaerklinCANCommon::ParseResponseLocoSpeed(const unsigned char* const buffer) { if (ParseLength(buffer) != 6) { return; } Address address; Protocol protocol; LocoType type; ParseAddressProtocol(buffer, address, protocol, type); Speed speed = Utils::Integer::DataBigEndianToShort(buffer + 9); logger->Info(Languages::TextReceivedSpeedCommand, Utils::Utils::ProtocolToString(protocol), address, speed); manager->LocoSpeed(ControlTypeHardware, controlID, protocol, address, speed); } void MaerklinCANCommon::ParseResponseLocoDirection(const unsigned char* const buffer) { if (ParseLength(buffer) != 5) { return; } Address address; Protocol protocol; LocoType type; ParseAddressProtocol(buffer, address, protocol, type); Orientation orientation = (buffer[9] == 1 ? OrientationRight : OrientationLeft); logger->Info(Languages::TextReceivedDirectionCommand, Utils::Utils::ProtocolToString(protocol), address, orientation); // changing direction implies speed = 0 manager->LocoSpeed(ControlTypeHardware, controlID, protocol, address, MinSpeed); manager->LocoOrientation(ControlTypeHardware, controlID, protocol, address, orientation); } void MaerklinCANCommon::ParseResponseLocoFunction(const unsigned char* const buffer) { if (ParseLength(buffer) != 6) { return; } Address address; Protocol protocol; LocoType type; ParseAddressProtocol(buffer, address, protocol, type); DataModel::LocoFunctionNr function = buffer[9]; DataModel::LocoFunctionState on = (buffer[10] != 0 ? DataModel::LocoFunctionStateOn : DataModel::LocoFunctionStateOff); logger->Info(Languages::TextReceivedFunctionCommand, Utils::Utils::ProtocolToString(protocol), address, function, on); manager->LocoFunctionState(ControlTypeHardware, controlID, protocol, address, function, on); } void MaerklinCANCommon::ParseResponseAccessory(const unsigned char* const buffer) { if (ParseLength(buffer) != 6 || buffer[10] != 1) { return; } Address address; Protocol protocol; LocoType type; ParseAddressProtocol(buffer, address, protocol, type); DataModel::AccessoryState state = (buffer[9] ? DataModel::AccessoryStateOn : DataModel::AccessoryStateOff); // GUI-address is 1-based, protocol-address is 0-based ++address; logger->Info(Languages::TextReceivedAccessoryCommand, Utils::Utils::ProtocolToString(protocol), address, state); manager->AccessoryBaseState(ControlTypeHardware, controlID, protocol, address, state); } void MaerklinCANCommon::ParseCommandPing(const unsigned char* const buffer) { if ((buffer[4] == 8) && (Utils::Integer::DataBigEndianToInt(buffer + 5) != uid)) { return; } unsigned char sendBuffer[CANCommandBufferLength]; CreateCommandHeader(sendBuffer, CanCommandPing, CanResponseResponse, 8); Utils::Integer::IntToDataBigEndian(uid, sendBuffer + 5); // version 4.3 sendBuffer[9] = 4; sendBuffer[10] = 3; // CS2 device type: Main = 0xffff, Secondary = 0xfff0 sendBuffer[11] = 0xff; sendBuffer[12] = isCs2Main ? 0xff : 0xf0; SendInternal(sendBuffer); } void MaerklinCANCommon::ParseCommandRequestConfigData(const unsigned char* const buffer) { if ((buffer[5] == 'l') && (buffer[6] == 'o') && (buffer[7] == 'k') && (buffer[8] == 's') && (buffer[9] == 0x00) && (buffer[10] == 0x00) && (buffer[11] == 0x00) && (buffer[12] == 0x00)) { const string dataPlain = manager->GetCs2Lokomotive(); SendCompressedFile(dataPlain, buffer + 5); } else if ((buffer[5] == 'm') && (buffer[6] == 'a') && (buffer[7] == 'g') && (buffer[8] == 's') && (buffer[9] == 0x00) && (buffer[10] == 0x00) && (buffer[11] == 0x00) && (buffer[12] == 0x00)) { const string dataPlain = manager->GetCs2Magnetartikel(); SendCompressedFile(dataPlain, buffer + 5); } else if ((buffer[5] == 'g') && (buffer[6] == 'b') && (buffer[7] == 's') && (buffer[12] == 0x00)) // we need a null here to prevent parsing errors { if ((buffer[8] == 0x00) && (buffer[9] == 0x00) && (buffer[10] == 0x00) && (buffer[11] == 0x00)) { const string dataPlain = manager->GetCs2GBS(); SendCompressedFile(dataPlain, buffer + 5); } else if (buffer[8] == '-') { const string gbsAsString(reinterpret_cast(buffer + 9)); const unsigned int gbs = Utils::Integer::StringToInteger(gbsAsString); const string dataPlain = manager->GetCs2GBS(gbs); SendCompressedFile(dataPlain, buffer + 5); } } else if ((buffer[5] == 'f') && (buffer[6] == 's') && (buffer[7] == 0x00) && (buffer[8] == 0x00) && (buffer[9] == 0x00) && (buffer[10] == 0x00) && (buffer[11] == 0x00) && (buffer[12] == 0x00)) { // we send an empty configuration const string dataPlain = "[fahrstrassen]\nversion\n .minor=4\n"; SendCompressedFile(dataPlain, buffer + 5); } } void MaerklinCANCommon::SendCompressedFile(const string& dataPlain, const unsigned char* fileName) { logger->Debug(dataPlain); const uint32_t dataPlainSize = dataPlain.size(); unsigned char sendBuffer[CANCommandBufferLength]; if (fileName) { // send filename as response CreateCommandHeader(sendBuffer, CanCommandRequestConfigData, CanResponseResponse, 8); Utils::Utils::Copy8Bytes(fileName, sendBuffer + 5); SendInternal(sendBuffer); } const string dataCompressed = ZLib::Compress(dataPlain); const unsigned char* const dataCompressedPtr = reinterpret_cast(dataCompressed.c_str()); const uint32_t dataCompressedSize = dataCompressed.size(); // prepare data const uint32_t dataToSendSize = (4 + dataCompressedSize + 8) & 0xFFFFFFF8; unsigned char* const dataToSend = reinterpret_cast(malloc(dataToSendSize)); // set last 8 bytes to 0 (fill up last data packet) const uint64_t eightBytes = 0; Utils::Utils::Copy8Bytes(&eightBytes, dataToSend + dataToSendSize - 8); // set first 4 bytes to length of uncompressed data size Utils::Integer::IntToDataBigEndian(dataPlainSize, dataToSend); // copy compressed data to out buffer std::memcpy(dataToSend + 4, dataCompressedPtr, dataCompressedSize); const CanFileCrc crc = CalcCrc(dataToSend, dataToSendSize); // send first data packet CreateCommandHeader(sendBuffer, CanCommandConfigData, CanResponseCommand, 6); Utils::Integer::IntToDataBigEndian(dataCompressedSize + 4, sendBuffer + 5); Utils::Integer::ShortToDataBigEndian(crc, sendBuffer + 9); SendInternal(sendBuffer); // send following data packets CreateCommandHeader(sendBuffer, CanCommandConfigData, CanResponseCommand, 8); for(size_t sent = 0; sent < dataToSendSize; sent += 8) { Utils::Utils::Copy8Bytes(dataToSend + sent, sendBuffer + 5); Utils::Utils::SleepForMilliseconds(2); // do not overload CS2 with too much data SendInternal(sendBuffer); } free(dataToSend); } void MaerklinCANCommon::ParseCommandConfigData(const unsigned char* const buffer) { CanLength length = ParseLength(buffer); switch (length) { case 6: case 7: ParseCommandConfigDataFirst(buffer); return; case 8: ParseCommandConfigDataNext(buffer); return; default: return; } return; } void MaerklinCANCommon::ParseCommandConfigDataFirst(const unsigned char* const buffer) { if (canFileData != nullptr) { free(canFileData); } canFileDataSize = Utils::Integer::DataBigEndianToInt(buffer + 5); canFileCrc = Utils::Integer::DataBigEndianToShort(buffer + 9); canFileCrcSize = (canFileDataSize + 8) & 0xFFFFFFF8; canFileData = reinterpret_cast(malloc(canFileCrcSize)); canFileDataPointer = canFileData; } void MaerklinCANCommon::ParseCommandConfigDataNext(const unsigned char* const buffer) { if (canFileData == nullptr) { return; } Utils::Utils::Copy8Bytes(buffer + 5, canFileDataPointer); canFileDataPointer += 8; if (canFileDataSize > static_cast(canFileDataPointer - canFileData)) { return; } const CanFileCrc calculatedCrc = CalcCrc(canFileData, canFileCrcSize); if (canFileCrc != calculatedCrc) { logger->Info(Languages::TextCrcMissmatch, Utils::Integer::IntegerToHex(canFileCrc), Utils::Integer::IntegerToHex(calculatedCrc)); CleanUpCanFileData(); return; } size_t canFileUncompressedSize = Utils::Integer::DataBigEndianToInt(canFileData); logger->Info(Languages::TextConfigFileReceivedWithSize, canFileUncompressedSize); string file = ZLib::UnCompress(reinterpret_cast(canFileData + 4), canFileDataSize, canFileUncompressedSize); deque lines; Utils::Utils::SplitString(file, "\n", lines); for (std::string& line : lines) { logger->Debug(line); } ParseCs2File(lines); CleanUpCanFileData(); } void MaerklinCANCommon::ParseResponseS88Event(const unsigned char* const buffer) { if (ParseLength(buffer) != 8) { return; } const char* onOff; DataModel::Feedback::FeedbackState state; if (buffer[10]) { onOff = Languages::GetText(Languages::TextOn); state = DataModel::Feedback::FeedbackStateOccupied; } else { onOff = Languages::GetText(Languages::TextOff); state = DataModel::Feedback::FeedbackStateFree; } FeedbackPin pin = ParseFeedbackPin(buffer); logger->Info(Languages::TextFeedbackChange, pin & 0x000F, pin >> 4, onOff); manager->FeedbackState(controlID, pin, state); } void MaerklinCANCommon::ParseResponseReadConfig(const unsigned char* const buffer) { if (ParseLength(buffer) != 7) { return; } CvNumber cv = Utils::Integer::DataBigEndianToShort(buffer + 9); CvValue value = buffer[11]; logger->Info(Languages::TextProgramReadValue, cv, value); manager->ProgramValue(cv, value); } void MaerklinCANCommon::ParseResponsePing(const unsigned char* const buffer) { const uint16_t deviceType = Utils::Integer::DataBigEndianToShort(buffer + 11); char* deviceString = nullptr; switch (deviceType) { case CanDeviceGfp: deviceString = const_cast("Gleisformat Prozessor"); break; case CanDeviceGleisbox: case CanDeviceGleisbox_2: deviceString = const_cast("Gleisbox"); break; case CanDeviceConnect6021: deviceString = const_cast("Connect 6021"); break; case CanDeviceMs2: case CanDeviceMs2_2: case CanDeviceMs2_3: case CanDeviceMs2_4: deviceString = const_cast("MS2"); break; case CanDeviceWireless: deviceString = const_cast("Wireless"); break; case CanDeviceCs2Main: deviceString = const_cast("CS2 Main"); hasCs2Main = true; break; case CanDeviceCs2Secondary: case CanDeviceCs2Secondary_2: deviceString = const_cast("CS2 Secondary"); break; case CanDeviceLinkS88: deviceString = const_cast("Link S88"); hasCs2Main = true; break; default: deviceString = const_cast("unknown"); break; } const string hash = Utils::Integer::IntegerToHex(Utils::Integer::DataBigEndianToShort(buffer + 2)); const unsigned char deviceId = buffer[8]; const unsigned char majorVersion = buffer[9]; const unsigned char minorVersion = buffer[10]; logger->Debug(Languages::TextDeviceOnCanBus, deviceString, hash, deviceId, majorVersion, minorVersion); } bool MaerklinCANCommon::ParseCs2FileKeyValue(const string& line, string& key, string& value) { if (line.length() < 4 || line[0] != ' ' || line[1] != '.') { return false; } const string stripedLine = line.substr(2); Utils::Utils::SplitString(stripedLine, "=", key, value); return (key.compare(stripedLine) != 0); } bool MaerklinCANCommon::ParseCs2FileSubkeyValue(const string& line, string& key, string& value) { if (line.length() < 5 || line[0] != ' ' || line[1] != '.' || line[2] != '.') { return false; } const string stripedLine = line.substr(3); Utils::Utils::SplitString(stripedLine, "=", key, value); return (key.compare(stripedLine) != 0); } void MaerklinCANCommon::ParseCs2FileLocomotiveFunction(deque& lines, LocoCacheEntry& cacheEntry) { lines.pop_front(); DataModel::LocoFunctionNr nr = 0; DataModel::LocoFunctionType type = DataModel::LocoFunctionTypeNone; DataModel::LocoFunctionIcon icon = DataModel::LocoFunctionIconNone; DataModel::LocoFunctionTimer timer = 0; while (lines.size()) { string& line = lines.front(); string key; string value; bool ok = ParseCs2FileSubkeyValue(line, key, value); if (ok == false) { break; } if (key.compare("nr") == 0) { nr = Utils::Integer::StringToInteger(value); } else if (key.compare("typ") == 0 || key.compare("typ2") == 0) { uint8_t valueInt = Utils::Integer::StringToInteger(value); icon = MapLocoFunctionCs2ToRailControl(static_cast(valueInt & 0x7F)); type = static_cast((valueInt >> 7) + 1); // CS2: 1 = permanent, 2 = once } else if (key.compare("dauer") == 0 || key.compare("dauer2") == 0) { type = DataModel::LocoFunctionTypeTimer; timer = Utils::Integer::StringToInteger(value); } lines.pop_front(); } if (type == DataModel::LocoFunctionTypeNone) { icon = DataModel::LocoFunctionIconNone; timer = 0; cacheEntry.ClearFunction(nr); return; } cacheEntry.SetFunction(nr, type, icon, timer); if (type == DataModel::LocoFunctionTypeTimer) { logger->Info(Languages::TextCs2MainLocoFunctionIconTypeTimer, nr, icon, timer); } else { logger->Info(Languages::TextCs2MainLocoFunctionIconType, nr, icon, type); } } void MaerklinCANCommon::ParseCs2FileLocomotiveTraktion(deque& lines, LocoCacheEntry& cacheEntry) { lines.pop_front(); Protocol protocol = ProtocolNone; Address address = AddressNone; string name; while (lines.size()) { string& line = lines.front(); string key; string value; bool ok = ParseCs2FileSubkeyValue(line, key, value); if (ok == false) { break; } if (key.compare("lok") == 0) { Address input = Utils::Integer::HexToInteger(value); LocoType type; ParseAddressProtocol(input, address, protocol, type); logger->Info(Languages::TextCs2MainLocoSlaveProtocolAddress, Utils::Utils::ProtocolToString(protocol), address); } else if (key.compare("lokname") == 0) { name = value; logger->Info(Languages::TextCs2MainLocoSlaveName, name); } lines.pop_front(); } cacheEntry.AddSlave(protocol, address, name); } void MaerklinCANCommon::ParseCs2FileLocomotive(deque& lines) { lines.pop_front(); LocoCacheEntry cacheEntry(controlID); std::string name; std::string oldName; bool remove = false; while (lines.size()) { string& line = lines.front(); if ((line.length() == 0) || (line[0] != ' ')) { break; } string key; string value; ParseCs2FileKeyValue(line, key, value); if (key.compare("name") == 0) { name = value; cacheEntry.SetName(value); cacheEntry.SetMatchKey(value); logger->Info(Languages::TextCs2MainLocoName, value); } else if (key.compare("vorname") == 0) { oldName = value; logger->Info(Languages::TextCs2MainLocoOldName, value); } else if (key.compare("toRemove") == 0) { remove = true; } else if (key.compare("uid") == 0) { Address input = Utils::Integer::HexToInteger(value); Address address = AddressNone; Protocol protocol = ProtocolNone; LocoType type; ParseAddressProtocol(input, address, protocol, type); cacheEntry.SetAddress(address); cacheEntry.SetProtocol(protocol); cacheEntry.SetType(type); logger->Info(Languages::TextCs2MainLocoProtocolAddress, Utils::Utils::ProtocolToString(protocol), address); } else if ((key.compare("funktionen") == 0) || (key.compare("funktionen_2") == 0) || (key.compare("fkt") == 0) || (key.compare("fkt2") == 0)) { ParseCs2FileLocomotiveFunction(lines, cacheEntry); continue; } else if (key.compare("traktion") == 0) { ParseCs2FileLocomotiveTraktion(lines, cacheEntry); continue; } lines.pop_front(); } if (remove) { logger->Info(Languages::TextCs2MainLocoRemove, name); LocoID locoId = CacheDelete(name); manager->LocoDelete(locoId); } else if (oldName.size() > 0) { CacheSave(cacheEntry, oldName); } else { CacheSave(cacheEntry); } } void MaerklinCANCommon::ParseCs2FileLocomotivesSession(deque& lines) { lines.pop_front(); while (lines.size()) { string& line = lines.front(); string key; string value; bool ok = ParseCs2FileKeyValue(line, key, value); if (ok == false) { return; } // we do not parse any data in session lines.pop_front(); } } void MaerklinCANCommon::ParseCs2FileLocomotivesVersion(deque& lines) { lines.pop_front(); while (lines.size()) { string& line = lines.front(); string key; string value; bool ok = ParseCs2FileKeyValue(line, key, value); if (ok == false) { return; } if ((key.compare("minor") == 0) && (value.compare("3") != 0) && (value.compare("4") != 0)) { logger->Warning(Languages::TextCs2MinorVersionIsUnknown); } lines.pop_front(); } } void MaerklinCANCommon::ParseCs2FileLocomotives(deque& lines) { lines.pop_front(); while (lines.size()) { string& line = lines.front(); if (line.length() == 0) { return; } if (line.compare("version") == 0) { ParseCs2FileLocomotivesVersion(lines); continue; } else if (line.compare("session") == 0) { ParseCs2FileLocomotivesSession(lines); continue; } else if (line.compare("lokomotive") == 0) { ParseCs2FileLocomotive(lines); continue; } return; } } void MaerklinCANCommon::ParseCs2File(deque& lines) { while (lines.size()) { string& line = lines.front(); if (line.length() == 0 || line[0] != '[') { return; } if (line.compare("[lokomotive]") == 0) { ParseCs2FileLocomotives(lines); CacheUpdateSlaves(); continue; } return; } } const DataModel::LocoFunctionIcon MaerklinCANCommon::LocoFunctionMapCs2ToRailControl[MaxNrOfCs2FunctionIcons] = { DataModel::LocoFunctionIconNone, DataModel::LocoFunctionIconLight, DataModel::LocoFunctionIconInteriorLight1, DataModel::LocoFunctionIconBacklightForward, DataModel::LocoFunctionIconHeadlightHighBeamForward, DataModel::LocoFunctionIconSoundGeneral, DataModel::LocoFunctionIconPanto12, DataModel::LocoFunctionIconSmokeGenerator, DataModel::LocoFunctionIconShuntingMode, DataModel::LocoFunctionIconTelex12, DataModel::LocoFunctionIconHorn1, DataModel::LocoFunctionIconWhistle1, DataModel::LocoFunctionIconWhistle2, DataModel::LocoFunctionIconBell, DataModel::LocoFunctionIconLeftRight, DataModel::LocoFunctionIconUpDown1, DataModel::LocoFunctionIconTurnLeft, DataModel::LocoFunctionIconUpDown2, DataModel::LocoFunctionIconInertia, DataModel::LocoFunctionIconFan2, DataModel::LocoFunctionIconBreak1, DataModel::LocoFunctionIconGearBox, DataModel::LocoFunctionIconGenerator, DataModel::LocoFunctionIconRunning1, DataModel::LocoFunctionIconEngine1, DataModel::LocoFunctionIconStationAnnouncement1, DataModel::LocoFunctionIconShovelCoal, DataModel::LocoFunctionIconCloseDoor, DataModel::LocoFunctionIconOpenDoor, DataModel::LocoFunctionIconFan1, DataModel::LocoFunctionIconFan, DataModel::LocoFunctionIconFireBox, DataModel::LocoFunctionIconInteriorLight2, DataModel::LocoFunctionIconTableLight3, DataModel::LocoFunctionIconTableLight2, DataModel::LocoFunctionIconTableLight1, DataModel::LocoFunctionIconShakingRust, DataModel::LocoFunctionIconRailJoint, DataModel::LocoFunctionIconLocomotiveNumberIndicator, DataModel::LocoFunctionIconMusic1, DataModel::LocoFunctionIconTrainDestinationIndicator, DataModel::LocoFunctionIconCabLight2, DataModel::LocoFunctionIconCabLight1, DataModel::LocoFunctionIconCoupler, DataModel::LocoFunctionIconBufferPush, DataModel::LocoFunctionIconStationAnnouncement3, DataModel::LocoFunctionIconCraneHook, DataModel::LocoFunctionIconBlinkingLight, DataModel::LocoFunctionIconCabLight12, DataModel::LocoFunctionIconCompressedAir, DataModel::LocoFunctionIconDefault, DataModel::LocoFunctionIconDefault, DataModel::LocoFunctionIconDefault, DataModel::LocoFunctionIconDefault, DataModel::LocoFunctionIconDefault, DataModel::LocoFunctionIconDefault, DataModel::LocoFunctionIconDefault, DataModel::LocoFunctionIconDefault, DataModel::LocoFunctionIconDefault, DataModel::LocoFunctionIconDefault, DataModel::LocoFunctionIconDefault, DataModel::LocoFunctionIconDefault, DataModel::LocoFunctionIconDefault, DataModel::LocoFunctionIconDefault, DataModel::LocoFunctionIconDefault, DataModel::LocoFunctionIconDefault, DataModel::LocoFunctionIconDefault, DataModel::LocoFunctionIconDefault, DataModel::LocoFunctionIconDefault, DataModel::LocoFunctionIconDefault, DataModel::LocoFunctionIconDefault, DataModel::LocoFunctionIconDefault, DataModel::LocoFunctionIconDefault, DataModel::LocoFunctionIconDefault, DataModel::LocoFunctionIconDefault, DataModel::LocoFunctionIconDefault, DataModel::LocoFunctionIconDefault, DataModel::LocoFunctionIconDefault, DataModel::LocoFunctionIconDefault, DataModel::LocoFunctionIconDefault, DataModel::LocoFunctionIconDefault, DataModel::LocoFunctionIconDefault, DataModel::LocoFunctionIconTelex2, DataModel::LocoFunctionIconTelex1, DataModel::LocoFunctionIconPanto2, DataModel::LocoFunctionIconPanto1, DataModel::LocoFunctionIconHeadlightLowBeamReverse, DataModel::LocoFunctionIconHeadlightLowBeamForward, DataModel::LocoFunctionIconUp, DataModel::LocoFunctionIconFan3, DataModel::LocoFunctionIconEngineLight, DataModel::LocoFunctionIconSteamBlowOut, DataModel::LocoFunctionIconSteamBlow, DataModel::LocoFunctionIconCrane, DataModel::LocoFunctionIconUp, DataModel::LocoFunctionIconDown, DataModel::LocoFunctionIconLeft, DataModel::LocoFunctionIconRight, DataModel::LocoFunctionIconTurnRight, DataModel::LocoFunctionIconMagnet, DataModel::LocoFunctionIconDefault, DataModel::LocoFunctionIconPanto, DataModel::LocoFunctionIconDefault, DataModel::LocoFunctionIconRadio, DataModel::LocoFunctionIconStationAnnouncement2, DataModel::LocoFunctionIconBacklightReverse, DataModel::LocoFunctionIconAirPump, DataModel::LocoFunctionIconSpeak, DataModel::LocoFunctionIconEngine2, DataModel::LocoFunctionIconNoSound, DataModel::LocoFunctionIconStairsLight, DataModel::LocoFunctionIconFillWater, DataModel::LocoFunctionIconBreak2, DataModel::LocoFunctionIconDefault, DataModel::LocoFunctionIconDefault, DataModel::LocoFunctionIconDefault, DataModel::LocoFunctionIconDefault, DataModel::LocoFunctionIconDefault, DataModel::LocoFunctionIconDefault, DataModel::LocoFunctionIconDefault, DataModel::LocoFunctionIconDefault, DataModel::LocoFunctionIconDefault, DataModel::LocoFunctionIconDefault, DataModel::LocoFunctionIconDefault, DataModel::LocoFunctionIconDefault, DataModel::LocoFunctionIconDefault, DataModel::LocoFunctionIconDefault, DataModel::LocoFunctionIconDefault }; const MaerklinCANCommon::LocoFunctionCs2Icon MaerklinCANCommon::LocoFunctionMapRailControlToCs2[DataModel::MaxLocoFunctionIcons] = { LocoFunctionCs2IconNone, LocoFunctionCs2IconDefault, LocoFunctionCs2IconShuntingMode, LocoFunctionCs2IconInertia, LocoFunctionCs2IconDefault, LocoFunctionCs2IconDefault, LocoFunctionCs2IconDefault, LocoFunctionCs2IconDefault, LocoFunctionCs2IconDefault, LocoFunctionCs2IconDefault, LocoFunctionCs2IconDefault, LocoFunctionCs2IconDefault, LocoFunctionCs2IconDefault, LocoFunctionCs2IconDefault, LocoFunctionCs2IconDefault, LocoFunctionCs2IconDefault, LocoFunctionCs2IconDefault, LocoFunctionCs2IconDefault, LocoFunctionCs2IconDefault, LocoFunctionCs2IconDefault, LocoFunctionCs2IconDefault, LocoFunctionCs2IconDefault, LocoFunctionCs2IconDefault, LocoFunctionCs2IconDefault, LocoFunctionCs2IconDefault, LocoFunctionCs2IconDefault, LocoFunctionCs2IconDefault, LocoFunctionCs2IconDefault, LocoFunctionCs2IconDefault, LocoFunctionCs2IconDefault, LocoFunctionCs2IconDefault, LocoFunctionCs2IconDefault, LocoFunctionCs2IconLight, LocoFunctionCs2IconLight, LocoFunctionCs2IconLight, LocoFunctionCs2IconHeadlightHighBeamForward, LocoFunctionCs2IconLight, LocoFunctionCs2IconHeadlightForward, LocoFunctionCs2IconHeadlightReverse, LocoFunctionCs2IconBackLightForward, LocoFunctionCs2IconBackLightReverse, LocoFunctionCs2IconLight, LocoFunctionCs2IconBlinkingLight, LocoFunctionCs2IconInteriorLight1, LocoFunctionCs2IconInteriorLight2, LocoFunctionCs2IconTableLight1, LocoFunctionCs2IconTableLight2, LocoFunctionCs2IconTableLight3, LocoFunctionCs2IconCabLight1, LocoFunctionCs2IconCabLight2, LocoFunctionCs2IconCabLight12, LocoFunctionCs2IconDefault, LocoFunctionCs2IconTrainDestinationIndicator, LocoFunctionCs2IconLocomotiveNumberIndicator, LocoFunctionCs2IconEngineLight, LocoFunctionCs2IconFireBox, LocoFunctionCs2IconStairsLight, LocoFunctionCs2IconDefault, LocoFunctionCs2IconDefault, LocoFunctionCs2IconDefault, LocoFunctionCs2IconDefault, LocoFunctionCs2IconDefault, LocoFunctionCs2IconDefault, LocoFunctionCs2IconDefault, LocoFunctionCs2IconSmokeGenerator, LocoFunctionCs2IconTelex1, LocoFunctionCs2IconTelex2, LocoFunctionCs2IconTelex12, LocoFunctionCs2IconPanto1, LocoFunctionCs2IconPanto2, LocoFunctionCs2IconPanto12, LocoFunctionCs2IconUp1, LocoFunctionCs2IconDown, LocoFunctionCs2IconUpDown1, LocoFunctionCs2IconUpDown2, LocoFunctionCs2IconLeft, LocoFunctionCs2IconRight, LocoFunctionCs2IconLeftRight, LocoFunctionCs2IconTurnLeft, LocoFunctionCs2IconTurnRight, LocoFunctionCs2IconDefault, LocoFunctionCs2IconCrane, LocoFunctionCs2IconMagnet, LocoFunctionCs2IconCraneHook, LocoFunctionCs2IconFan, LocoFunctionCs2IconDefault, LocoFunctionCs2IconDefault, LocoFunctionCs2IconDefault, LocoFunctionCs2IconDefault, LocoFunctionCs2IconDefault, LocoFunctionCs2IconDefault, LocoFunctionCs2IconDefault, LocoFunctionCs2IconDefault, LocoFunctionCs2IconDefault, LocoFunctionCs2IconDefault, LocoFunctionCs2IconDefault, LocoFunctionCs2IconNoSound, LocoFunctionCs2IconSoundGeneral, LocoFunctionCs2IconRunning1, LocoFunctionCs2IconSoundGeneral, LocoFunctionCs2IconEngine1, LocoFunctionCs2IconEngine2, LocoFunctionCs2IconBreak1, LocoFunctionCs2IconBreak2, LocoFunctionCs2IconSoundGeneral, LocoFunctionCs2IconHorn1, LocoFunctionCs2IconSoundGeneral, LocoFunctionCs2IconWhistle1, LocoFunctionCs2IconWhistle2, LocoFunctionCs2IconBell, LocoFunctionCs2IconStationAnnouncement1, LocoFunctionCs2IconStationAnnouncement2, LocoFunctionCs2IconStationAnnouncement3, LocoFunctionCs2IconSpeak, LocoFunctionCs2IconRadio, LocoFunctionCs2IconMusic1, LocoFunctionCs2IconSoundGeneral, LocoFunctionCs2IconOpenDoor, LocoFunctionCs2IconCloseDoor, LocoFunctionCs2IconFan1, LocoFunctionCs2IconFan2, LocoFunctionCs2IconFan3, LocoFunctionCs2IconShovelCoal, LocoFunctionCs2IconCompressedAir, LocoFunctionCs2IconSoundGeneral, LocoFunctionCs2IconSteamBlowOut, LocoFunctionCs2IconSteamBlow, LocoFunctionCs2IconSoundGeneral, LocoFunctionCs2IconShakingRust, LocoFunctionCs2IconAirPump, LocoFunctionCs2IconSoundGeneral, LocoFunctionCs2IconBufferPush, LocoFunctionCs2IconGenerator, LocoFunctionCs2IconGearBox, LocoFunctionCs2IconSoundGeneral, LocoFunctionCs2IconSoundGeneral, LocoFunctionCs2IconFillWater, LocoFunctionCs2IconSoundGeneral, LocoFunctionCs2IconSoundGeneral, LocoFunctionCs2IconSoundGeneral, LocoFunctionCs2IconRailJoint, LocoFunctionCs2IconCoupler, LocoFunctionCs2IconPanto, LocoFunctionCs2IconSoundGeneral, LocoFunctionCs2IconSoundGeneral, LocoFunctionCs2IconNoSound, LocoFunctionCs2IconNoSound }; }} // namespace railcontrol-24+dfsg1/Hardware/Protocols/MaerklinCANCommon.h000066400000000000000000000326771500456250600240020ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include "DataModel/AccessoryBase.h" #include "DataModel/LocoFunctions.h" #include "Hardware/Capabilities.h" #include "Hardware/HardwareInterface.h" #include "Hardware/HardwareParams.h" #include "Hardware/LocoCache.h" #include "Logger/Logger.h" #include "Utils/Integer.h" // CAN protocol specification at http://streaming.maerklin.de/public-media/cs2/cs2CAN-Protokoll-2_0.pdf // Very interesting is also http://www.mbernstein.de/modellbahn/can/bem.htm namespace Hardware { namespace Protocols { class MaerklinCANCommon { public: MaerklinCANCommon() = delete; MaerklinCANCommon(const MaerklinCANCommon&) = delete; MaerklinCANCommon& operator=(const MaerklinCANCommon&) = delete; void Booster(const BoosterState status); void LocoSpeed(const Protocol protocol, const Address address, const Speed speed); void LocoOrientation(const Protocol protocol, const Address address, const Orientation orientation); void LocoFunction(const Protocol protocol, const Address address, const DataModel::LocoFunctionNr function, const DataModel::LocoFunctionState on); void Accessory(const Protocol protocol, const Address address, const DataModel::AccessoryState state, const bool on); void ProgramRead(const ProgramMode mode, const Address address, const CvNumber cv); void ProgramWrite(const ProgramMode mode, const Address address, const CvNumber cv, const CvValue value); static inline DataModel::LocoFunctionIcon MapLocoFunctionCs2ToRailControl(const uint8_t input) { return LocoFunctionMapCs2ToRailControl[input]; } static inline uint8_t MapLocofunctionRailControlToCs2(const DataModel::LocoFunctionNr nr, const DataModel::LocoFunctionIcon railcontrolIcon) { LocoFunctionCs2Icon icon = LocoFunctionMapRailControlToCs2[railcontrolIcon]; if (icon == LocoFunctionCs2IconDefault) { icon = static_cast(icon + nr); } return icon; } protected: inline MaerklinCANCommon(const std::string& uid, ControlID controlID, Manager* manager, const bool isCs2Main, Logger::Logger* logger) : run(true), manager(manager), logger(logger), uid(Utils::Integer::HexToInteger(uid, 0)), hash(CalcHash(this->uid)), hasCs2Main(false), isCs2Main(isCs2Main), canFileData(nullptr), canFileDataPointer(nullptr), canFileDataSize(0), canFileCrc(0), canFileCrcSize(0), controlID(controlID) { logger->Debug(Languages::TextMyUidHash, uid, Utils::Integer::IntegerToHex(hash)); } void Init(); virtual ~MaerklinCANCommon(); void Parse(const unsigned char* buffer); void Ping(); void RequestLoks(); void ReceiverInternal(); virtual void Receiver() = 0; virtual void CacheSave(LocoCacheEntry& entry) = 0; virtual void CacheSave(LocoCacheEntry& entry, const std::string& oldMatchKey) = 0; virtual LocoID CacheDelete(const std::string& matchKey) = 0; virtual void CacheUpdateSlaves() = 0; static const unsigned char CANCommandBufferLength = 13; volatile bool run; Manager* manager; Logger::Logger* logger; private: enum CanCommand : unsigned char { CanCommandSystem = 0x00, CanCommandLocoSpeed = 0x04, CanCommandLocoDirection = 0x05, CanCommandLocoFunction = 0x06, CanCommandReadConfig = 0x07, CanCommandWriteConfig = 0x08, CanCommandAccessory = 0x0B, CanCommandS88Event = 0x11, CanCommandPing = 0x18, CanCommandRequestConfigData = 0x20, CanCommandConfigData = 0x21, CanCommandHello = 0x42 }; enum CanSubCommand : unsigned char { CanSubCommandStop = 0x00, CanSubCommandGo = 0x01 }; enum CanResponse : unsigned char { CanResponseCommand = 0x00, CanResponseResponse = 0x01 }; enum CanDeviceType : uint16_t { CanDeviceGfp = 0x0000, CanDeviceGleisbox = 0x0010, CanDeviceGleisbox_2 = 0x0011, // undocumented CanDeviceConnect6021 = 0x0020, CanDeviceMs2 = 0x0030, CanDeviceMs2_2 = 0x0032, // undocumented CanDeviceMs2_3 = 0x0033, // undocumented CanDeviceMs2_4 = 0x0034, // undocumented CanDeviceLinkS88 = 0x0040, // undocumented CanDeviceCs2Secondary_2 = 0xeeee, // undocumented CanDeviceWireless = 0xffe0, CanDeviceCs2Secondary = 0xfff0, // undocumented CanDeviceCs2Main = 0xffff }; // enum CanFileType : uint8_t // { // CanFileTypeNone, // CanFileTypeLokinfo, // CanFileTypeLoknamen, // CanFileTypeMaginfo, // CanFileTypeLokdb, // CanFileTypeLang, // CanFileTypeLdbver, // CanFileTypeLangver, // CanFileTypeLoks, // CanFileTypeMags, // CanFileTypeGbs, // CanFileTypeFs, // CanFileTypeLokstat, // CanFileTypeMagstat, // CanFileTypeGbsstat, // CanFileTypeFsstat // }; enum LocoFunctionCs2Icon : uint8_t { LocoFunctionCs2IconNone = 0, LocoFunctionCs2IconLight, LocoFunctionCs2IconInteriorLight1, LocoFunctionCs2IconBackLightForward, LocoFunctionCs2IconHeadlightHighBeamForward, LocoFunctionCs2IconSoundGeneral, LocoFunctionCs2IconPanto12, LocoFunctionCs2IconSmokeGenerator, LocoFunctionCs2IconShuntingMode, LocoFunctionCs2IconTelex12, LocoFunctionCs2IconHorn1, LocoFunctionCs2IconWhistle1, LocoFunctionCs2IconWhistle2, LocoFunctionCs2IconBell, LocoFunctionCs2IconLeftRight, LocoFunctionCs2IconUpDown1, LocoFunctionCs2IconTurnLeft, LocoFunctionCs2IconUpDown2, LocoFunctionCs2IconInertia, LocoFunctionCs2IconFan2, LocoFunctionCs2IconBreak1, LocoFunctionCs2IconGearBox, LocoFunctionCs2IconGenerator, LocoFunctionCs2IconRunning1, LocoFunctionCs2IconEngine1, LocoFunctionCs2IconStationAnnouncement1, LocoFunctionCs2IconShovelCoal, LocoFunctionCs2IconCloseDoor, LocoFunctionCs2IconOpenDoor, LocoFunctionCs2IconFan1, LocoFunctionCs2IconFan, LocoFunctionCs2IconFireBox, LocoFunctionCs2IconInteriorLight2, LocoFunctionCs2IconTableLight3, LocoFunctionCs2IconTableLight2, LocoFunctionCs2IconTableLight1, LocoFunctionCs2IconShakingRust, LocoFunctionCs2IconRailJoint, LocoFunctionCs2IconLocomotiveNumberIndicator, LocoFunctionCs2IconMusic1, LocoFunctionCs2IconTrainDestinationIndicator, LocoFunctionCs2IconCabLight2, LocoFunctionCs2IconCabLight1, LocoFunctionCs2IconCoupler, LocoFunctionCs2IconBufferPush, LocoFunctionCs2IconStationAnnouncement3, LocoFunctionCs2IconCraneHook, LocoFunctionCs2IconBlinkingLight, LocoFunctionCs2IconCabLight12, LocoFunctionCs2IconCompressedAir, LocoFunctionCs2IconDefault, LocoFunctionCs2IconTelex2 = 82, LocoFunctionCs2IconTelex1, LocoFunctionCs2IconPanto2, LocoFunctionCs2IconPanto1, LocoFunctionCs2IconHeadlightReverse, LocoFunctionCs2IconHeadlightForward, LocoFunctionCs2IconUp2, LocoFunctionCs2IconFan3, LocoFunctionCs2IconEngineLight, LocoFunctionCs2IconSteamBlowOut, LocoFunctionCs2IconSteamBlow, LocoFunctionCs2IconCrane, LocoFunctionCs2IconUp1, LocoFunctionCs2IconDown, LocoFunctionCs2IconLeft, LocoFunctionCs2IconRight, LocoFunctionCs2IconTurnRight, LocoFunctionCs2IconMagnet, LocoFunctionCs2IconPanto = 101, LocoFunctionCs2IconRadio = 103, LocoFunctionCs2IconStationAnnouncement2, LocoFunctionCs2IconBackLightReverse, LocoFunctionCs2IconAirPump, LocoFunctionCs2IconSpeak, LocoFunctionCs2IconEngine2, LocoFunctionCs2IconNoSound, LocoFunctionCs2IconStairsLight, LocoFunctionCs2IconFillWater, LocoFunctionCs2IconBreak2 }; typedef unsigned char CanPrio; typedef unsigned char CanLength; typedef uint32_t CanUid; typedef uint16_t CanHash; typedef uint16_t CanFileCrc; void CreateCommandHeader(unsigned char* const buffer, const CanCommand command, const CanResponse response, const CanLength length); inline void ParseAddressProtocol(const unsigned char* const buffer, Address& address, Protocol& protocol, LocoType& type) { Address input = ParseAddress(buffer); ParseAddressProtocol(input, address, protocol, type); } static void ParseAddressProtocol(const Address input, Address& address, Protocol& protocol, LocoType& type); static inline CanPrio ParsePrio(const unsigned char* const buffer) { return buffer[0] >> 1; } static inline CanCommand ParseCommand(const unsigned char* const buffer) { return static_cast((buffer[0] << 7) | (buffer[1] >> 1)); } static inline CanSubCommand ParseSubCommand(const unsigned char* const buffer) { return static_cast(buffer[9]); } static inline CanResponse ParseResponse(const unsigned char* const buffer) { return static_cast(buffer[1] & 0x01); } static inline CanLength ParseLength(const unsigned char* const buffer) { return buffer[4]; } static inline Address ParseAddress(const unsigned char* const buffer) { return static_cast
(Utils::Integer::DataBigEndianToInt(buffer + 5)); } static inline FeedbackPin ParseFeedbackPin(const unsigned char* const buffer) { FeedbackPin pin = static_cast(Utils::Integer::DataBigEndianToInt(buffer + 5)); pin = (pin & 0x00000FFF) | ((pin & 0x00FF0000) >> 4); return pin; } static inline CanHash ParseHash(const unsigned char* const buffer) { return Utils::Integer::DataBigEndianToShort(buffer + 2); } static inline CanUid ParseUid(const unsigned char* const buffer) { return Utils::Integer::DataBigEndianToInt(buffer + 5); } void ParseCommandSystem(const unsigned char* const buffer); void ParseResponseLocoSpeed(const unsigned char* const buffer); void ParseResponseLocoDirection(const unsigned char* const buffer); void ParseResponseLocoFunction(const unsigned char* const buffer); void ParseResponseAccessory(const unsigned char* const buffer); void ParseCommandPing(const unsigned char* const buffer); void ParseCommandRequestConfigData(const unsigned char* const buffer); void SendCompressedFile(const std::string& dataPlain, const unsigned char* fileName = nullptr); void ParseCommandConfigData(const unsigned char* const buffer); void ParseCommandConfigDataFirst(const unsigned char* const buffer); void ParseCommandConfigDataNext(const unsigned char* const buffer); void ParseResponseS88Event(const unsigned char* const buffer); void ParseResponseReadConfig(const unsigned char* const buffer); void ParseResponsePing(const unsigned char* const buffer); bool ParseCs2FileKeyValue(const std::string& line, std::string& key, std::string& value); bool ParseCs2FileSubkeyValue(const std::string& line, std::string& key, std::string& value); void ParseCs2FileLocomotiveFunction(std::deque& lines, LocoCacheEntry& cacheEntry); void ParseCs2FileLocomotiveTraktion(std::deque& lines, LocoCacheEntry& cacheEntry); void ParseCs2FileLocomotive(std::deque& lines); void ParseCs2FileLocomotivesSession(std::deque& lines); void ParseCs2FileLocomotivesVersion(std::deque& lines); void ParseCs2FileLocomotives(std::deque& lines); void ParseCs2File(std::deque& lines); static CanFileCrc CalcCrc(const unsigned char *data, const size_t length); static CanHash CalcHash(const CanUid uid); void GenerateUidHash(); void CreateLocalIDLoco(unsigned char* buffer, const Protocol protocol, const Address address) const; void CreateLocalIDAccessory(unsigned char* buffer, const Protocol protocol, const Address address) const; void Wait(const unsigned int duration) const; void PingSender(); inline void SendInternal(const unsigned char* buffer) { logger->HexOut(buffer, 5 + ParseLength(buffer)); Send(buffer); } virtual void Send(const unsigned char* buffer) = 0; inline void CleanUpCanFileData() { free(canFileData); canFileData = nullptr; canFileDataPointer = nullptr; canFileDataSize = 0; canFileCrc = 0; canFileCrcSize = 0; } CanUid uid; CanHash hash; volatile bool hasCs2Main; const bool isCs2Main; std::thread receiverThread; std::thread pingThread; unsigned char* canFileData; unsigned char* canFileDataPointer; size_t canFileDataSize; CanFileCrc canFileCrc; size_t canFileCrcSize; ControlID controlID; static const uint8_t MaxNrOfCs2FunctionIcons = 128; static const DataModel::LocoFunctionIcon LocoFunctionMapCs2ToRailControl[MaxNrOfCs2FunctionIcons]; static const MaerklinCANCommon::LocoFunctionCs2Icon LocoFunctionMapRailControlToCs2[DataModel::MaxLocoFunctionIcons]; }; }} // namespace railcontrol-24+dfsg1/Hardware/Protocols/P50x.cpp000066400000000000000000000462551500456250600216310ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include "Hardware/Protocols/P50x.h" #include "Languages.h" #include "Utils/Integer.h" namespace Hardware { namespace Protocols { P50x::P50x(const HardwareParams* const params, const std::string& controlName, const P50xType type) : HardwareInterface(params->GetManager(), params->GetControlID(), controlName, params->GetName()), s88Modules(0), params(params), type(type), run(false) { logger->Info(Languages::TextStarting, GetFullName()); } P50x::~P50x() { if (!run) { return; } run = false; checkEventsThread.join(); } void P50x::Init() { switch (type) { case TypeOpenDcc: InitOpenDcc(); break; case TypeUhlenbrock: case TypeTams: InitUhlenbrockTams(); break; } checkEventsThread = std::thread(&Hardware::Protocols::P50x::CheckEventsWorker, this); } void P50x::InitOpenDcc() { static const unsigned char MaxS88Modules = 128; SendP50XOnly(); bool ok = SendNop(); if (!ok) { logger->Error(Languages::TextControlDoesNotAnswer); return; } unsigned char s88Modules1 = Utils::Integer::StringToInteger(params->GetArg2(), 0); unsigned char s88Modules2 = Utils::Integer::StringToInteger(params->GetArg3(), 0); unsigned char s88Modules3 = Utils::Integer::StringToInteger(params->GetArg4(), 0); s88Modules = s88Modules1 + s88Modules2 + s88Modules3; if (s88Modules > MaxS88Modules) { logger->Error(Languages::TextTooManyS88Modules, s88Modules, MaxS88Modules); return; } if (s88Modules == 0) { logger->Info(Languages::TextNoS88Modules); return; } logger->Info(Languages::TextHsi88Configured, s88Modules, s88Modules1, s88Modules2, s88Modules3); bool restart = false; unsigned char modules = SendXP88Get(0); if (modules != s88Modules) { logger->Info(Languages::TextNrOfS88Modules, s88Modules); SendXP88Set(0, s88Modules); restart = true; } modules = SendXP88Get(1); if (modules != s88Modules1) { logger->Info(Languages::TextNrOfS88ModulesOnBus, s88Modules1, 1); SendXP88Set(1, s88Modules1); restart = true; } modules = SendXP88Get(2); if (modules != s88Modules2) { logger->Info(Languages::TextNrOfS88ModulesOnBus, s88Modules2, 2); SendXP88Set(2, s88Modules2); restart = true; } modules = SendXP88Get(3); if (modules != s88Modules3) { logger->Info(Languages::TextNrOfS88ModulesOnBus, s88Modules3, 3); SendXP88Set(3, s88Modules3); restart = true; } if (restart) { SendXP88Set(4, 0); SendRestart(); Utils::Utils::SleepForMilliseconds(100); SendP50XOnly(); ok = SendNop(); if (!ok) { logger->Error(Languages::TextControlDoesNotAnswer); return; } } } void P50x::InitUhlenbrockTams() { static const unsigned char MaxS88Modules = 104; SendP50XOnly(); const bool ok = SendNop(); if (!ok) { logger->Error(Languages::TextControlDoesNotAnswer); return; } s88Modules = Utils::Integer::StringToInteger(params->GetArg2(), 0); if (s88Modules > MaxS88Modules) { logger->Error(Languages::TextTooManyS88Modules, s88Modules, MaxS88Modules); return; } if (s88Modules == 0) { logger->Info(Languages::TextNoS88Modules); return; } logger->Info(Languages::TextNrOfS88Modules, s88Modules); unsigned char modules = SendXP88Get(0); if (modules != s88Modules) { SendXP88Set(0, s88Modules); } } void P50x::Booster(const BoosterState status) { if (status) { logger->Info(Languages::TextTurningBoosterOn); SendPowerOn(); } else { logger->Info(Languages::TextTurningBoosterOff); SendPowerOff(); } } void P50x::LocoSpeed(__attribute__((unused)) const Protocol protocol, const Address address, const Speed speed) { if (!CheckLocoAddress(address)) { return; } cache.SetSpeed(address, speed); SendXLok(address); } void P50x::LocoOrientation(__attribute__((unused)) const Protocol protocol, const Address address, const Orientation orientation) { if (!CheckLocoAddress(address)) { return; } cache.SetOrientation(address, orientation); SendXLok(address); } void P50x::LocoFunction(__attribute__((unused)) const Protocol protocol, const Address address, const DataModel::LocoFunctionNr function, const DataModel::LocoFunctionState on) { if (!CheckLocoAddress(address)) { return; } if (function > MaxLocoFunctions) { return; } cache.SetFunction(address, function, on); std::lock_guard < std::mutex > guard(communicationLock); if (function == 0) { SendXLok(address); return; } if (function <= 8) { SendXFunc(address); return; } if (function <= 16) { SendXFunc2(address); return; } SendXFunc34(address); } void P50x::LocoSpeedOrientationFunctions(__attribute__((unused)) const Protocol protocol, const Address address, const Speed speed, const Orientation orientation, std::vector& functions) { if (!CheckLocoAddress(address)) { return; } cache.SetSpeed(address, speed); cache.SetOrientation(address, orientation); unsigned char nrFunctions = functions.size(); for (const DataModel::LocoFunctionEntry& function : functions) { cache.SetFunction(address, function.nr, function.state); } std::lock_guard < std::mutex > guard(communicationLock); SendXLok(address); if (nrFunctions > 1) { SendXFunc(address); if (nrFunctions > 8) { SendXFunc2(address); if (nrFunctions > 16) { SendXFunc34(address); } } } } bool P50x::SendXLok(const Address address) const { P50xCacheEntry entry = cache.GetData(address); logger->Info(Languages::TextSettingSpeedOrientationLight, address, entry.speed, Languages::GetLeftRight(static_cast((entry.orientationF0 >> 5) & 0x01)), Languages::GetOnOff((entry.orientationF0 >> 4) & 0x01)); const unsigned char addressLSB = (address & 0xFF); const unsigned char addressMSB = (address >> 8); const unsigned char data[5] = { XLok, addressLSB, addressMSB, entry.speed, entry.orientationF0 }; SendInternal(data, sizeof(data)); unsigned char input; bool ret = ReceiveExactInternal(&input, 1); if (ret != 1) { logger->Warning(Languages::TextControlDoesNotAnswer); return false; } switch (input) { case OK: return true; case XBADPRM: logger->Warning(Languages::TextControlReturnedBadParameter); return false; case XLKHALT: logger->Warning(Languages::TextControlReturnedOnHalt); return false; case XLKPOFF: logger->Warning(Languages::TextControlReturnedPowerOff); return false; default: logger->Warning(Languages::TextControlReturnedUnknownErrorCode, static_cast(input)); return false; } } bool P50x::SendXFunc(const Address address) const { P50xCacheEntry entry = cache.GetData(address); logger->Info(Languages::TextSettingFunctions1_8, address, entry.function[0]); const unsigned char addressLSB = (address & 0xFF); const unsigned char addressMSB = (address >> 8); const unsigned char data[4] = { XFunc, addressLSB, addressMSB, entry.function[0] }; SendInternal(data, sizeof(data)); return ReceiveFunctionCommandAnswer(); } bool P50x::SendXFunc2(const Address address) const { P50xCacheEntry entry = cache.GetData(address); logger->Info(Languages::TextSettingFunctions9_16, address, entry.function[1]); const unsigned char addressLSB = (address & 0xFF); const unsigned char addressMSB = (address >> 8); const unsigned char data[4] = { XFunc2, addressLSB, addressMSB, entry.function[1] }; SendInternal(data, sizeof(data)); return ReceiveFunctionCommandAnswer(); } bool P50x::SendXFunc34(const Address address) const { P50xCacheEntry entry = cache.GetData(address); logger->Info(Languages::TextSettingFunctions17_28, address, entry.function[2], entry.function[3]); const unsigned char addressLSB = (address & 0xFF); const unsigned char addressMSB = (address >> 8); const unsigned char data[5] = { XFunc34, addressLSB, addressMSB, entry.function[2], entry.function[3] }; SendInternal(data, sizeof(data)); return ReceiveFunctionCommandAnswer(); } bool P50x::ReceiveFunctionCommandAnswer() const { unsigned char input; bool ret = ReceiveExactInternal(&input, 1); if (ret != 1) { logger->Warning(Languages::TextControlDoesNotAnswer); return false; } switch (input) { case OK: return true; case XBADPRM: logger->Warning(Languages::TextControlReturnedBadParameter); return false; case XNOSLOT: logger->Warning(Languages::TextControlReturnedQueueFull); return false; default: logger->Warning(Languages::TextControlReturnedUnknownErrorCode, static_cast(input)); return false; } } void P50x::Accessory(__attribute__((unused)) const Protocol protocol, const Address address, const DataModel::AccessoryState state, const bool on, __attribute__((unused)) const DataModel::AccessoryPulseDuration duration) { if (!CheckAccessoryAddress(address)) { return; } logger->Info(Languages::TextSettingAccessoryWithProtocol, ProtocolDCC, address, state, on); const unsigned char addressLSB = (address & 0xFF); const unsigned char addressMSB = (address >> 8); const unsigned char statusBits = ((state == DataModel::AccessoryStateOn) << 7) | (on << 6); const unsigned char addressStatus = addressMSB | statusBits; const unsigned char data[3] = { XTrnt, addressLSB, addressStatus }; std::lock_guard < std::mutex > guard(communicationLock); SendInternal(data, sizeof(data)); unsigned char input; bool ret = ReceiveExactInternal(&input, 1); if (ret != 1) { logger->Warning(Languages::TextControlDoesNotAnswer); return; } switch (input) { case OK: return; case XBADPRM: logger->Warning(Languages::TextControlReturnedBadParameter); return; case XLOWTSP: logger->Warning(Languages::TextControlReturnedQueueNearlyFull); return; default: logger->Warning(Languages::TextControlReturnedUnknownErrorCode, static_cast(input)); return; } } bool P50x::SendP50XOnly() const { unsigned char data[6] = { 'X', 'Z', 'z', 'A', '1', 0x0D }; SendInternal(data, sizeof(data)); const unsigned char dataLengthRead = 34; unsigned char input[dataLengthRead]; ReceiveExactInternal(input, dataLengthRead); return true; } bool P50x::SendOneByteCommand(const unsigned char data) const { std::lock_guard < std::mutex > guard(communicationLock); SendInternal(data); unsigned char input; int ret = ReceiveInternal(&input, 1); return ret > 0 && input == OK; } bool P50x::SendRestart() const { unsigned char data[3] = { '@', '@', 0x0D }; logger->Info(Languages::TextRestarting); std::lock_guard < std::mutex > guard(communicationLock); SendInternal(data, sizeof(data)); return true; } unsigned char P50x::SendXP88Get(unsigned char param) const { unsigned char data[2] = { XP88Get, param }; std::lock_guard < std::mutex > guard(communicationLock); SendInternal(data, sizeof(data)); unsigned char input; size_t ret = ReceiveExactInternal(&input, 1); if (ret == 0 || input != OK) { return 0xFF; } ret = ReceiveExactInternal(&input, 1); if (ret == 0) { return 0xFF; } return input; } bool P50x::SendXP88Set(unsigned char param, unsigned char value) const { unsigned char data[3] = { XP88Set, param, value }; std::lock_guard < std::mutex > guard(communicationLock); SendInternal(data, sizeof(data)); unsigned char input; size_t ret = ReceiveExactInternal(&input, 1); if (ret == 0) { return false; } return (input == OK); } void P50x::CheckSensorData(const unsigned char module, const unsigned char data) const { unsigned char diff = s88Memory[module] ^ data; s88Memory[module] = data; FeedbackPin pinOverAll = module; pinOverAll <<= 3; for (unsigned char pinOnModule = 1; pinOnModule <= 8; ++pinOnModule) { if ((diff >> (8 - pinOnModule)) & 0x01) { DataModel::Feedback::FeedbackState state = static_cast((data >> (8 - pinOnModule)) & 0x01); logger->Info(Languages::TextFeedbackChange, pinOnModule, module, Languages::GetText(state ? Languages::TextOn : Languages::TextOff)); manager->FeedbackState(controlID, pinOverAll + pinOnModule, state); } } } void P50x::SendXEvtSen() const { std::lock_guard < std::mutex > guard(communicationLock); unsigned char data[1] = { XEvtSen }; SendInternal(data, sizeof(data)); while (true) { unsigned char module; ssize_t ret = ReceiveExactInternal(&module, 1); if (ret != 1 || module == 0) { return; } --module; module <<= 1; unsigned char input[2]; ret = ReceiveExactInternal(input, sizeof(input)); if (ret < 2) { return; } if (s88Memory[module] != input[0]) { CheckSensorData(module, input[0]); } ++module; if (s88Memory[module] != input[1]) { CheckSensorData(module, input[1]); } } } void P50x::SendXEvtLok() const { std::queue commandQueue; std::queue functionQueue; { std::lock_guard < std::mutex > guard(communicationLock); unsigned char data[1] = { XEvtLok }; SendInternal(data, sizeof(data)); while (true) { unsigned char input[5]; ssize_t ret = ReceiveExactInternal(input, 1); if (ret < 1 || input[0] == 0x80) { break; } ret = ReceiveExactInternal(input + 1, sizeof(input) - 1); if (ret < 1) { break; } struct LocoCommand command; command.address = (input[3] & 0x3F); command.address <<= 8; command.address += input[2]; command.speed = input[0]; if (command.speed == 1) { command.speed = 0; } else if (command.speed > 1) { --command.speed; } command.speed <<= 3; command.orientation = static_cast(input[3] >> 7); commandQueue.push(command); struct LocoFunction function; function.address = command.address; uint16_t functions = input[1]; functions <<= 1; functions += (input[3] >> 6) & 0x01; for (DataModel::LocoFunctionNr nr = 0; nr <= 8; ++nr) { function.nr = nr; function.state = static_cast((functions >> nr) & 0x01); functionQueue.push(function); } } } while (commandQueue.size()) { struct LocoCommand command = commandQueue.front(); commandQueue.pop(); manager->LocoSpeed(ControlTypeHardware, controlID, ProtocolServer, command.address, command.speed); manager->LocoOrientation(ControlTypeHardware, controlID, ProtocolServer, command.address, command.orientation); } while (functionQueue.size()) { struct LocoFunction function = functionQueue.front(); functionQueue.pop(); manager->LocoFunctionState(ControlTypeHardware, controlID, ProtocolServer, function.address, function.nr, function.state); } } void P50x::SendXEvtTrn() const { std::queue commandQueue; { std::lock_guard < std::mutex > guard(communicationLock); unsigned char data[1] = { XEvtTrnt }; SendInternal(data, sizeof(data)); unsigned char number; ssize_t ret = ReceiveExactInternal(&number, 1); if (ret < 1) { return; } while (number) { unsigned char input[2]; ssize_t ret = ReceiveExactInternal(input, sizeof(input)); if (ret != sizeof(input)) { break; } --number; bool on = static_cast((input[1] >> 6) & 0x01); if (!on) { continue; } struct TurnoutCommand command; command.address = (input[1] & 0x07); command.address <<= 8; command.address += input[0]; command.state = static_cast((input[1] >> 7) & 0x01); commandQueue.push(command); } } while (commandQueue.size()) { struct TurnoutCommand command = commandQueue.front(); commandQueue.pop(); manager->AccessoryBaseState(ControlTypeHardware, controlID, ProtocolServer, command.address, command.state); } } void P50x::SendXStatus() const { std::queue < BoosterState > queue; { std::lock_guard < std::mutex > guard(communicationLock); unsigned char data[1] = { XStatus }; SendInternal(data, sizeof(data)); unsigned char byte = 1; while (true) { unsigned char input; ssize_t ret = ReceiveExactInternal(&input, sizeof(input)); if (ret < 1) { return; } if (byte == 1) { queue.push(static_cast((input >> 3) & 0x01)); } if ((input & 0x80) == 0x00) { break; } ++byte; } } while (queue.size()) { manager->Booster(ControlTypeHardware, queue.front()); queue.pop(); } } void P50x::SendXEvent() const { unsigned char input; bool statusEvent = false; { std::lock_guard < std::mutex > guard(communicationLock); unsigned char data[1] = { XEvent }; SendInternal(data, sizeof(data)); size_t ret = ReceiveExactInternal(&input, 1); if (ret != 1) { return; } bool moreData = (input >> 7) & 0x01; unsigned char byte = 1; while (true) { if (!moreData) { break; } unsigned char input2; size_t ret = ReceiveExactInternal(&input2, 1); if (ret != 1) { break; } if (byte == 1) { if ((input2 >> 6) & 0x01) { statusEvent = true; } } ++byte; moreData = (input2 >> 7) & 0x01; } } bool sensorEvent = (input >> 2) & 0x01; if (sensorEvent) { SendXEvtSen(); } bool locoEvent = input & 0x01; if (locoEvent) { SendXEvtLok(); } bool powerEvent = (input >> 3) & 0x01; if (powerEvent) { manager->Booster(ControlTypeHardware, BoosterStateStop); } bool switchEvent = (input >> 5) & 0x01; if (switchEvent) { SendXEvtTrn(); } if (statusEvent) { SendXStatus(); } } void P50x::CheckEventsWorker() { Utils::Utils::SetThreadName (GetShortName()); run= true; while (run) { SendXEvent(); Utils::Utils::SleepForMilliseconds(50); } } } // namespace }// namespace railcontrol-24+dfsg1/Hardware/Protocols/P50x.h000066400000000000000000000152201500456250600212620ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include "Hardware/HardwareInterface.h" #include "Hardware/HardwareParams.h" #include "Hardware/Protocols/P50xCache.h" #include "Logger/Logger.h" namespace Hardware { namespace Protocols { class P50x: protected Hardware::HardwareInterface { public: enum P50xType : unsigned char { TypeOpenDcc, TypeUhlenbrock, TypeTams }; P50x() = delete; P50x(const P50x&) = delete; P50x& operator=(const P50x&) = delete; P50x(const HardwareParams* const params, const std::string& controlName, const P50xType type); virtual ~P50x(); inline Hardware::Capabilities GetCapabilities() const override { return Hardware::CapabilityLoco | Hardware::CapabilityAccessory | Hardware::CapabilityFeedback; } inline void GetLocoProtocols(std::vector& protocols) const override { protocols.push_back(ProtocolServer); } inline bool LocoProtocolSupported(Protocol protocol) const override { return (protocol == ProtocolServer); } inline void GetAccessoryProtocols(std::vector& protocols) const override { protocols.push_back(ProtocolServer); } inline bool AccessoryProtocolSupported(Protocol protocol) const override { return (protocol == ProtocolServer); } void Booster(const BoosterState status) override; void LocoSpeed(const Protocol protocol, const Address address, const Speed speed) override; void LocoOrientation(const Protocol protocol, const Address address, const Orientation orientation) override; void LocoFunction(const Protocol protocol, const Address address, const DataModel::LocoFunctionNr function, const DataModel::LocoFunctionState on) override; void LocoSpeedOrientationFunctions(const Protocol protocol, const Address address, const Speed speed, const Orientation orientation, std::vector& functions) override; void Accessory(const Protocol protocol, const Address address, const DataModel::AccessoryState state, const bool on, const DataModel::AccessoryPulseDuration duration) override; void CheckEventsWorker(); protected: virtual int Send(const unsigned char* buffer, const size_t bufferLength) const = 0; virtual ssize_t Receive(unsigned char* data, const size_t length) const = 0; virtual ssize_t ReceiveExact(unsigned char* data, const size_t length) const = 0; void Init(); unsigned short s88Modules; std::thread checkEventsThread; private: enum Commands : unsigned char { XNop = 0xC4, XPwrOn = 0xA7, XPwrOff = 0xA6, XLok = 0x80, XFunc = 0x88, XFunc2 = 0x89, XFunc34 = 0x8A, XTrnt = 0x90, XP88Get = 0x9C, XP88Set = 0x9D, XStatus = 0xA2, XEvent = 0xC8, XEvtLok = 0xC9, XEvtTrnt = 0xCA, XEvtSen = 0xCB }; enum Answers : unsigned char { OK = 0x00, XBADPRM = 0x02, XPWOFF = 0x06, XNODATA = 0x0A, XNOSLOT = 0x0B, XLOWTSP = 0x40, XLKHALT = 0x41, XLKPOFF = 0x42 }; struct LocoCommand { Address address; Speed speed; Orientation orientation; }; struct LocoFunction { Address address; DataModel::LocoFunctionNr nr; DataModel::LocoFunctionState state; }; struct TurnoutCommand { Address address; DataModel::AccessoryState state; }; static const unsigned char MaxLocoFunctions = 28; static const unsigned short MaxLocoAddress = 10239; static const unsigned short MaxAccessoryAddress = 2043; void InitOpenDcc(); void InitUhlenbrockTams(); bool SendP50XOnly() const; bool SendRestart() const; unsigned char SendXP88Get(unsigned char param) const; bool SendXP88Set(unsigned char param, unsigned char value) const; inline bool SendNop() const { return SendOneByteCommand(XNop); } inline ssize_t SendInternal(const unsigned char data) const { return SendInternal(&data, 1); } inline ssize_t SendInternal(const unsigned char* data, const size_t dataLength) const { logger->HexOut(data, dataLength); return Send(data, dataLength); } inline ssize_t ReceiveInternal(unsigned char* data, const size_t length) const { const ssize_t ret = Receive(data, length); if (ret > 0) { logger->HexIn(data, ret); } return ret; } inline ssize_t ReceiveExactInternal(unsigned char* data, const size_t length) const { const ssize_t ret = ReceiveExact(data, length); if (ret > 0) { logger->HexIn(data, ret); } return ret; } static inline bool CheckLocoAddress(const Address address) { return 0 < address && address <= MaxLocoAddress; } static inline bool CheckAccessoryAddress(const Address address) { return 0 < address && address <= MaxAccessoryAddress; } bool SendOneByteCommand(const unsigned char data) const; inline bool SendPowerOn() const { return SendOneByteCommand(XPwrOn); } inline bool SendPowerOff() const { return SendOneByteCommand(XPwrOff); } bool SendXLok(const Address address) const; bool SendXFunc(const Address address) const; bool SendXFunc2(const Address address) const; bool SendXFunc34(const Address address) const; bool ReceiveFunctionCommandAnswer() const; void CheckSensorData(const unsigned char module, const unsigned char data) const; void SendXEvtSen() const; void SendXEvtLok() const; void SendXEvtTrn() const; void SendXEvent() const; void SendXStatus() const; const HardwareParams* const params; const P50xType type; volatile bool run; mutable unsigned char s88Memory[128]; P50xCache cache; mutable std::mutex communicationLock; }; } // namespace } // namespace railcontrol-24+dfsg1/Hardware/Protocols/P50xCache.h000066400000000000000000000052311500456250600222070ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once namespace Hardware { namespace Protocols { class P50xCacheEntry { public: inline P50xCacheEntry() : speed(0), orientationF0(0), functions(0) { } unsigned char speed; unsigned char orientationF0; union { uint32_t functions; unsigned char function[4]; }; }; class P50xCache { public: P50xCache(const P50xCache&) = delete; P50xCache& operator=(const P50xCache&) = delete; inline P50xCache() { } void SetSpeed(const Address address, const Speed speed) { P50xCacheEntry entry = GetData(address); if (speed == 0) { entry.speed = 0; } else if (speed > 1000) { entry.speed = 127; } else { entry.speed = (speed >> 3) + 2; } cache[address] = entry; } void SetOrientation(const Address address, const Orientation orientation) { P50xCacheEntry entry = GetData(address); entry.orientationF0 &= ~(1 << 5); entry.orientationF0 |= static_cast(orientation) << 5; cache[address] = entry; } void SetFunction(const Address address, const DataModel::LocoFunctionNr function, const DataModel::LocoFunctionState on) { bool onInternal = static_cast(on); P50xCacheEntry entry = GetData(address); if (function == 0) { entry.orientationF0 &= ~(1 << 4); entry.orientationF0 |= static_cast(onInternal) << 4; } else { unsigned char shift = function - 1; entry.functions &= ~(1 << shift); entry.functions |= static_cast(onInternal) << shift; } cache[address] = entry; } inline P50xCacheEntry GetData(const Address address) const { return cache.count(address) == 0 ? P50xCacheEntry() : cache.at(address); } private: std::map cache; }; } // namespace } // namespace railcontrol-24+dfsg1/Hardware/Protocols/P50xEthernet.h000066400000000000000000000040631500456250600227640ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "Hardware/Protocols/P50x.h" #include "Logger/Logger.h" #include "Network/TcpClient.h" namespace Hardware { class HardwareParams; namespace Protocols { class P50xEthernet: public P50x { public: P50xEthernet() = delete; P50xEthernet(const P50xEthernet&) = delete; P50xEthernet& operator=(const P50xEthernet&) = delete; inline P50xEthernet(const HardwareParams* params, const std::string& controlName, const P50xType type) : P50x(params, controlName + " / " + params->GetName() + " at serial port " + params->GetArg1(), type), connection(Network::TcpClient::GetTcpClientConnection(logger, params->GetArg1(), P50xPort)) { } virtual ~P50xEthernet() { } protected: inline ssize_t Receive(unsigned char* data, const size_t length) const override { return connection.Receive(data, length); } inline ssize_t ReceiveExact(unsigned char* data, const size_t length) const override { return connection.ReceiveExact(data, length); } private: inline int Send(const unsigned char* data, const size_t length) const override { return connection.Send(data, length); } static const unsigned short P50xPort = 8050; Network::TcpConnection connection; }; } // namespace } // namespace railcontrol-24+dfsg1/Hardware/Protocols/P50xSerial.h000066400000000000000000000037431500456250600224310ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "Hardware/Protocols/P50x.h" #include "Logger/Logger.h" #include "Network/Serial.h" namespace Hardware { class HardwareParams; namespace Protocols { class P50xSerial: public P50x { public: P50xSerial() = delete; P50xSerial(const P50xSerial&) = delete; P50xSerial& operator=(const P50xSerial&) = delete; inline P50xSerial(const HardwareParams* const params, const std::string& controlName, const P50xType type) : P50x(params, controlName + " / " + params->GetName() + " at serial port " + params->GetArg1(), type), serialLine(logger, params->GetArg1(), B19200, 8, 'N', 2) { } virtual ~P50xSerial() { } protected: inline int Send(const unsigned char* data, const size_t length) const override { return serialLine.Send(data, length); } inline ssize_t Receive(unsigned char* data, const size_t length) const override { return serialLine.Receive(data, length, 1, 0); } inline ssize_t ReceiveExact(unsigned char* data, const size_t length) const override { return serialLine.ReceiveExact(data, length, 1, 0); } private: mutable Network::Serial serialLine; }; } // namespace } // namespace railcontrol-24+dfsg1/Hardware/Protocols/P50xUhlenbrock.h000066400000000000000000000025111500456250600232760ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include "Hardware/Protocols/P50xSerial.h" #include "Languages.h" namespace Hardware { class HardwareParams; namespace Protocols { class P50xUhlenbrock: public P50xSerial { public: P50xUhlenbrock() = delete; P50xUhlenbrock(const P50xUhlenbrock&) = delete; P50xUhlenbrock& operator=(const P50xUhlenbrock&) = delete; P50xUhlenbrock(const HardwareParams* params, const std::string& controlName) : P50xSerial(params, controlName, TypeUhlenbrock) { } virtual ~P50xUhlenbrock() { } }; } // namespace } // namespace railcontrol-24+dfsg1/Hardware/Protocols/Z21.cpp000066400000000000000000000737061500456250600214520ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include //int64_t; #include //printf #include //exit(0); #include //memset #include #include #include #include #include "Hardware/Protocols/Z21.h" #include "Utils/Integer.h" #include "Utils/Utils.h" namespace Hardware { namespace Protocols { Z21::Z21(const HardwareParams* params, const std::string& controlName) : HardwareInterface(params->GetManager(), params->GetControlID(), controlName + " / " + params->GetName() + " at IP " + params->GetArg1(), params->GetName()), run(true), connection(logger, params->GetArg1(), Z21Port), lastProgramMode(ProgramModeMm), connected(false) { logger->Info(Languages::TextStarting, GetFullName()); if (connection.IsConnected()) { logger->Info(Languages::TextSenderSocketCreated); } else { logger->Error(Languages::TextUnableToCreateUdpSocket, params->GetArg1(), Z21Port); } receiverThread = std::thread(&Hardware::Protocols::Z21::Receiver, this); heartBeatThread = std::thread(&Hardware::Protocols::Z21::HeartBeatSender, this); accessorySenderThread = std::thread(&Hardware::Protocols::Z21::AccessorySender, this); } Z21::~Z21() { run = false; SendLogOff(); accessoryQueue.Terminate(); connection.Terminate(); accessorySenderThread.join(); heartBeatThread.join(); receiverThread.join(); logger->Info(Languages::TextTerminatingSenderSocket); } void Z21::Booster(const BoosterState status) { logger->Info(status ? Languages::TextTurningBoosterOn : Languages::TextTurningBoosterOff); unsigned char buffer[7] = { 0x07, 0x00, 0x40, 0x00, 0x21, 0x80, 0xA1 }; buffer[5] |= status; Send(buffer, sizeof(buffer)); } unsigned char Z21::EncodeSpeed14(const Speed speed) { Speed speedInternal = speed >> 6; switch (speedInternal) { case MinSpeed: return 0x00; case 0x0F: return 0x0F; default: return speedInternal + 1; } } Speed Z21::DecodeSpeed14(unsigned char data) { switch (data) { case 0x00: return MinSpeed; case 0x0F: return MaxSpeed; default: return (data - 1) << 6; } } unsigned char Z21::EncodeSpeed28(const Speed speed) { Speed speedInternal = speed >> 5; switch (speedInternal) { case MinSpeed: return 0x00; case 0x1F: return 0x1F; default: ++speedInternal; return (speedInternal >> 1 | (speedInternal & 0x01) << 4); } } Speed Z21::DecodeSpeed28(unsigned char data) { switch (data) { case 0x00: return MinSpeed; case 0x1F: return MaxSpeed; default: unsigned char internalData = ((data >> 4) & 0x01) | ((data & 0x0F) << 1); --internalData; return internalData << 5; } } unsigned char Z21::EncodeSpeed128(const Speed speed) { Speed speedInternal = speed >> 3; switch (speedInternal) { case MinSpeed: return 0x00; case 0x7F: return 0x7F; default: return speedInternal + 1; } } Speed Z21::DecodeSpeed128(unsigned char data) { switch (data) { case 0x00: return MinSpeed; case 0x7F: return MaxSpeed; default: return (data - 1) << 3; } } void Z21::LocoSpeed(const Protocol protocol, const Address address, const Speed speed) { if (!LocoProtocolSupported(protocol)) { return; } Orientation orientation = locoCache.GetOrientation(address); locoCache.SetSpeed(address, speed); LocoSpeedOrientation(protocol, address, speed, orientation); } void Z21::LocoOrientation(const Protocol protocol, const Address address, const Orientation orientation) { if (!LocoProtocolSupported(protocol)) { return; } Speed speed = locoCache.GetSpeed(address); locoCache.SetOrientation(address, orientation); LocoSpeedOrientation(protocol, address, speed, orientation); } void Z21::LocoSpeedOrientation(const Protocol protocol, const Address address, const Speed speed, const Orientation orientation) { if (!LocoProtocolSupported(protocol)) { return; } unsigned char buffer[10] = { 0x0A, 0x00, 0x40, 0x00, 0xE4 }; switch (protocol) { case ProtocolMM1: case ProtocolDCC14: buffer[5] = 0x10; buffer[8] = EncodeSpeed14(speed); break; case ProtocolMM15: case ProtocolDCC28: buffer[5] = 0x12; buffer[8] = EncodeSpeed28(speed); break; case ProtocolMM2: case ProtocolDCC128: buffer[5] = 0x13; buffer[8] = EncodeSpeed128(speed); break; default: return; } SendSetLocoMode(address, protocol); Utils::Integer::ShortToDataBigEndian(address | 0xC000, buffer + 6); buffer[8] |= static_cast(orientation) << 7; buffer[9] = buffer[4] ^ buffer[5] ^ buffer[6] ^ buffer[7] ^ buffer[8]; Send(buffer, sizeof(buffer)); } void Z21::LocoFunction(__attribute__ ((unused)) const Protocol protocol, const Address address, const DataModel::LocoFunctionNr function, const DataModel::LocoFunctionState on) { if (!LocoProtocolSupported(protocol)) { return; } locoCache.SetFunction(address, function, on); unsigned char buffer[10] = { 0x0A, 0x00, 0x40, 0x00, 0xE4, 0xF8 }; Utils::Integer::ShortToDataBigEndian(address | 0xC000, buffer + 6); buffer[8] = (static_cast(on == DataModel::LocoFunctionStateOn) << 6) | (function & 0x3F); buffer[9] = buffer[4] ^ buffer[5] ^ buffer[6] ^ buffer[7] ^ buffer[8]; Send(buffer, sizeof(buffer)); } void Z21::LocoSpeedOrientationFunctions(const Protocol protocol, const Address address, const Speed speed, const Orientation orientation, std::vector& functions) { if (!LocoProtocolSupported(protocol)) { return; } locoCache.SetSpeedOrientationProtocol(address, speed, orientation, protocol); LocoSpeedOrientation(protocol, address, speed, orientation); for (const DataModel::LocoFunctionEntry& function : functions) { LocoFunction(protocol, address, function.nr, function.state); } } void Z21::Accessory(const Protocol protocol, const Address address, const DataModel::AccessoryState state, __attribute__((unused)) const bool on, const DataModel::AccessoryPulseDuration duration) { AccessoryQueueEntry entry(protocol, address, state, duration); accessoryQueue.EnqueueBack(entry); } void Z21::AccessoryOnOrOff(const Address address, const DataModel::AccessoryState state, const bool on) { const Address zeroBasedAddress = address - 1; unsigned char buffer[9] = { 0x09, 0x00, 0x40, 0x00, 0x53 }; Utils::Integer::ShortToDataBigEndian(zeroBasedAddress, buffer + 5); buffer[7] = 0x80 | (static_cast(on) << 3) | static_cast(state); buffer[8] = buffer[4] ^ buffer[5] ^ buffer[6] ^ buffer[7]; Send(buffer, sizeof(buffer)); } void Z21::AccessorySender() { Utils::Utils::SetThreadName("Z21 Accessory Sender"); logger->Info(Languages::TextAccessorySenderThreadStarted); while (run) { AccessoryQueueEntry entry = accessoryQueue.Dequeue(); if (entry.protocol == ProtocolNone) { // ProtocolNone is in queue when we should quit continue; } SendSetTurnoutMode(entry.address, entry.protocol); AccessoryOnOrOff(entry.address, entry.state, true); Utils::Utils::SleepForMilliseconds(entry.duration); AccessoryOnOrOff(entry.address, entry.state, false); } logger->Info(Languages::TextTerminatingAccessorySenderThread); } void Z21::ProgramRead(const ProgramMode mode, const Address address, const CvNumber cv) { switch (mode) { case ProgramModeDccDirect: logger->Info(Languages::TextProgramDccDirectRead, cv); ProgramDccRead(cv); break; case ProgramModeDccPomLoco: logger->Info(Languages::TextProgramDccPomLocoRead, address, cv); ProgramDccPom(Z21Enums::PomLoco, Z21Enums::PomReadByte, address, cv); break; case ProgramModeDccPomAccessory: logger->Info(Languages::TextProgramDccPomAccessoryRead, address, cv); ProgramDccPom(Z21Enums::PomAccessory, Z21Enums::PomReadByte, address, cv); break; default: return; } lastProgramMode = mode; } void Z21::ProgramWrite(const ProgramMode mode, const Address address, const CvNumber cv, const CvValue value) { switch (mode) { case ProgramModeMm: logger->Info(Languages::TextProgramMm, cv, static_cast(value)); ProgramMm(cv, value); break; case ProgramModeDccDirect: logger->Info(Languages::TextProgramDccDirectWrite, cv, static_cast(value)); ProgramDccWrite(cv, value); break; case ProgramModeDccPomLoco: logger->Info(Languages::TextProgramDccPomLocoWrite, address, cv, value); ProgramDccPom(Z21Enums::PomLoco, Z21Enums::PomWriteByte, address, cv, value); break; case ProgramModeDccPomAccessory: logger->Info(Languages::TextProgramDccPomAccessoryWrite, address, cv, value); ProgramDccPom(Z21Enums::PomAccessory, Z21Enums::PomWriteByte, address, cv, value); break; default: return; } lastProgramMode = mode; } void Z21::ProgramMm(const CvNumber cv, const CvValue value) { const unsigned char zeroBasedCv = static_cast((cv - 1) & 0xFF); unsigned char buffer[10] = { 0x0A, 0x00, 0x40, 0x00, 0x24, 0xFF, 0x00, zeroBasedCv, value }; buffer[9] = buffer[4] ^ buffer[5] ^ buffer[6] ^ buffer[7] ^ buffer[8]; Send(buffer, sizeof(buffer)); } void Z21::ProgramDccRead(const CvNumber cv) { const CvNumber zeroBasedCv = cv - 1; unsigned char buffer[9] = { 0x09, 0x00, 0x40, 0x00, 0x23, 0x11 }; Utils::Integer::ShortToDataBigEndian(zeroBasedCv, buffer + 6); buffer[8] = buffer[4] ^ buffer[5] ^ buffer[6] ^ buffer[7]; Send(buffer, sizeof(buffer)); } void Z21::ProgramDccWrite(const CvNumber cv, const CvValue value) { const CvNumber zeroBasedCv = cv - 1; unsigned char buffer[10] = { 0x0A, 0x00, 0x40, 0x00, 0x24, 0x12 }; Utils::Integer::ShortToDataBigEndian(zeroBasedCv, buffer + 6); buffer[8] = value; buffer[9] = buffer[4] ^ buffer[5] ^ buffer[6] ^ buffer[7] ^ buffer[8]; Send(buffer, sizeof(buffer)); } void Z21::ProgramDccPom(const Z21Enums::PomDB0 db0, const Z21Enums::PomOption option, const Address address, const CvNumber cv, const CvValue value) { Address internalAddress = address; if (db0 == Z21Enums::PomAccessory) { internalAddress <<= 4; } const CvNumber OptionAndZeroBasedCv = option | ((cv - 1) & 0x03FF); unsigned char buffer[12] = { 0x0C, 0x00, 0x40, 0x00, 0xE6, db0 }; Utils::Integer::ShortToDataBigEndian(internalAddress, buffer + 6); Utils::Integer::ShortToDataBigEndian(OptionAndZeroBasedCv, buffer + 8); buffer[10] = value; buffer[11] = buffer[4] ^ buffer[5] ^ buffer[6] ^ buffer[7] ^ buffer[8] ^ buffer[9] ^ buffer[10]; Send(buffer, sizeof(buffer)); } void Z21::StartUpConnection() { SendGetSerialNumber(); SendGetHardwareInfo(); SendGetCode(); SendBroadcastFlags(static_cast(Z21Enums::BroadCastFlagBasic | Z21Enums::BroadCastFlagRBus | Z21Enums::BroadCastFlagAllLoco | Z21Enums::BroadCastFlagCanDetector)); SendGetDetectorState(); } void Z21::HeartBeatSender() { Utils::Utils::SetMinThreadPriority(); Utils::Utils::SetThreadName("Z21 Heartbeat Sender"); logger->Info(Languages::TextHeartBeatThreadStarted); const unsigned int counterMask = 0x07; unsigned int counter = counterMask; while (run) { Utils::Utils::SleepForSeconds(1); ++counter; counter &= counterMask; if (counter > 0) { continue; } if (connected) { connected = false; } else { StartUpConnection(); } SendGetStatus(); } logger->Info(Languages::TextTerminatingHeartBeatThread); } void Z21::Receiver() { Utils::Utils::SetThreadName("Z21 Receiver"); logger->Info(Languages::TextReceiverThreadStarted); unsigned char buffer[Z21CommandBufferLength]; while (run) { ssize_t dataLength = connection.Receive(buffer, sizeof(buffer)); if (run == false) { break; } if (dataLength < 0) { logger->Error(Languages::TextUnableToReceiveData); break; } if (dataLength == 0) { continue; } logger->HexIn(buffer, dataLength); ssize_t dataRead = 0; while (dataRead < dataLength) { ssize_t ret = ParseData(buffer + dataRead, dataLength - dataRead); if (ret == -1) { break; } dataRead += ret; } } logger->Info(Languages::TextTerminatingReceiverThread); } ssize_t Z21::ParseData(const unsigned char* buffer, size_t bufferLength) { unsigned short dataLength = Utils::Integer::DataLittleEndianToShort(buffer); if (dataLength < 4 || dataLength > bufferLength) { return -1; } Z21Enums::Header header = static_cast(Utils::Integer::DataLittleEndianToShort(buffer + 2)); switch (header) { case Z21Enums::HeaderSerialNumber: { unsigned int serialNumber = Utils::Integer::DataLittleEndianToInt(buffer + 4); logger->Info(Languages::TextSerialNumberIs, serialNumber); break; } case Z21Enums::HeaderGetCode: switch (buffer[4]) { case Z21Enums::FeaturesNotRestricted: logger->Debug(Languages::TextZ21NotRestricted); break; case Z21Enums::FeaturesStartLocked: logger->Error(Languages::TextZ21StartLocked); break; case Z21Enums::FeaturesStartUnlocked: logger->Debug(Languages::TextZ21StartUnlocked); break; default: logger->Debug(Languages::TextZ21RestrictionsUnknown); } break; case Z21Enums::HeaderGetHardwareInfo: { unsigned int hardwareType = Utils::Integer::DataLittleEndianToInt(buffer + 4); std::string hardwareTypeText; switch (hardwareType) { case 0x00000200: hardwareTypeText = Languages::GetText(Languages::TextZ21Black2012); break; case 0x00000201: hardwareTypeText = Languages::GetText(Languages::TextZ21Black2013); break; case 0x00000202: hardwareTypeText = Languages::GetText(Languages::TextZ21SmartRail2012); break; case 0x00000203: hardwareTypeText = Languages::GetText(Languages::TextZ21White2013); break; case 0x00000204: hardwareTypeText = Languages::GetText(Languages::TextZ21Start2016); break; default: hardwareTypeText = Languages::GetText(Languages::TextZ21Unknown); break; } unsigned char firmwareVersionMajor = buffer[9]; unsigned char firmwareVersionMinor = buffer[8]; std::string firmwareVersionText = Utils::Integer::IntegerToBCD(firmwareVersionMajor) + "." + Utils::Integer::IntegerToBCD(firmwareVersionMinor); logger->Info(Languages::TextZ21Type, hardwareTypeText, firmwareVersionText); break; } case Z21Enums::HeaderSeeXHeader: ParseXHeader(buffer); break; case Z21Enums::HeaderGetBroadcastFlags: logger->Warning(Languages::TextNotImplemented, __FILE__, __LINE__); break; case Z21Enums::HeaderGetLocoMode: logger->Warning(Languages::TextNotImplemented, __FILE__, __LINE__); break; case Z21Enums::HeaderGetTurnoutMode: logger->Warning(Languages::TextNotImplemented, __FILE__, __LINE__); break; case Z21Enums::HeaderRmBusData: ParseRmBusData(buffer); break; case Z21Enums::HeaderSystemData: logger->Warning(Languages::TextNotImplemented, __FILE__, __LINE__); break; case Z21Enums::HeaderRailComtData: logger->Warning(Languages::TextNotImplemented, __FILE__, __LINE__); break; case Z21Enums::HeaderLocoNetRx: case Z21Enums::HeaderLocoNetTx: case Z21Enums::HeaderLocoNetLan: case Z21Enums::HeaderLocoNetDispatch: // we do not parse LocoNet data break; case Z21Enums::HeaderLocoNetDetector: ParseLocoNetDetector(buffer); break; case Z21Enums::HeaderDetector: ParseDetectorData(buffer); break; default: logger->Warning(Languages::TextZ21DoesNotUnderstand); return -1; } return dataLength; } void Z21::ParseXHeader(const unsigned char* buffer) { Z21Enums::XHeader xHeader = static_cast(buffer[4]); switch (xHeader) { case Z21Enums::XHeaderTurnoutInfo: ParseTurnoutData(buffer); break; case Z21Enums::XHeaderSeeDB0_2: ParseDB0(buffer); break; case Z21Enums::XHeaderStatusChanged: connected = true; break; case Z21Enums::XHeaderVersion: logger->Warning(Languages::TextNotImplemented, __FILE__, __LINE__); break; case Z21Enums::XHeaderCvResult: ParseCvData(buffer); break; case Z21Enums::XHeaderBcStopped: logger->Warning(Languages::TextNotImplemented, __FILE__, __LINE__); break; case Z21Enums::XHeaderLocoInfo: ParseLocoData(buffer); break; case Z21Enums::XHeaderFirmwareVersion: logger->Warning(Languages::TextNotImplemented, __FILE__, __LINE__); break; default: break; } } void Z21::ParseDB0(const unsigned char* buffer) { switch (buffer[5]) { case Z21Enums::DB0PowerOff: if (buffer[6] != 0x61) { logger->Error(Languages::TextCheckSumError); } logger->Debug(Languages::TextBoosterIsTurnedOff); manager->Booster(ControlTypeHardware, BoosterStateStop); break; case Z21Enums::DB0PowerOn: if (buffer[6] != 0x60) { logger->Error(Languages::TextCheckSumError); } logger->Debug(Languages::TextBoosterIsTurnedOn); manager->Booster(ControlTypeHardware, BoosterStateGo); break; case Z21Enums::DB0ProgrammingMode: if (buffer[6] != 0x63) { logger->Error(Languages::TextCheckSumError); } logger->Debug(Languages::TextProgrammingMode); manager->Booster(ControlTypeHardware, BoosterStateStop); break; case Z21Enums::DB0ShortCircuit: if (buffer[6] != 0x69) { logger->Error(Languages::TextCheckSumError); } logger->Debug(Languages::TextShortCircuit); manager->Booster(ControlTypeInternal, BoosterStateStop); break; case Z21Enums::DB0CvShortCircuit: if (buffer[6] != 0x73) { logger->Error(Languages::TextCheckSumError); } logger->Debug(Languages::TextShortCircuit); manager->Booster(ControlTypeInternal, BoosterStateStop); break; case Z21Enums::DB0CvNack: if (buffer[6] != 0x72) { logger->Error(Languages::TextCheckSumError); } logger->Debug(Languages::TextNoAnswerFromDecoder); break; case Z21Enums::DB0UnknownCommand: logger->Warning(Languages::TextZ21DoesNotUnderstand); break; } } void Z21::ParseTurnoutData(const unsigned char* buffer) { DataModel::AccessoryState state; switch (buffer[7]) { case 0x01: state = DataModel::AccessoryStateOff; break; case 0x02: state = DataModel::AccessoryStateOn; break; default: return; } const Address zeroBasedAddress = Utils::Integer::DataBigEndianToShort(buffer + 5); const Address address = zeroBasedAddress + 1; const Protocol protocol = turnoutCache.GetProtocol(address); manager->AccessoryBaseState(ControlTypeHardware, controlID, protocol, address, state); } void Z21::ParseLocoData(const unsigned char* buffer) { const Address address = Utils::Integer::DataBigEndianToShort(buffer + 5) & 0x3FFF; const unsigned char protocolType = buffer[7] & 0x07; Protocol protocol; const unsigned char speedData = buffer[8] & 0x7F; Speed newSpeed; Protocol storedProtocol = locoCache.GetProtocol(address); switch (protocolType) { case 0: switch (storedProtocol) { case ProtocolDCC14: protocol = ProtocolDCC14; break; case ProtocolMM1: protocol = ProtocolMM1; break; default: locoCache.SetProtocol(address, ProtocolDCC14); protocol = ProtocolDCC14; logger->Warning(Languages::TextActualAndStoredProtocolsDiffer, ProtocolSymbols[ProtocolDCC14], ProtocolSymbols[storedProtocol]); break; } newSpeed = DecodeSpeed14(speedData); break; case 2: switch (storedProtocol) { case ProtocolDCC28: protocol = ProtocolDCC28; break; case ProtocolMM15: protocol = ProtocolMM15; break; default: locoCache.SetProtocol(address, ProtocolDCC28); protocol = ProtocolDCC28; logger->Warning(Languages::TextActualAndStoredProtocolsDiffer, ProtocolSymbols[ProtocolDCC28], ProtocolSymbols[storedProtocol]); break; } newSpeed = DecodeSpeed28(speedData); break; case 4: switch (storedProtocol) { case ProtocolDCC128: protocol = ProtocolDCC128; break; case ProtocolMM2: protocol = ProtocolMM2; break; default: locoCache.SetProtocol(address, ProtocolDCC128); protocol = ProtocolDCC128; logger->Warning(Languages::TextActualAndStoredProtocolsDiffer, ProtocolSymbols[ProtocolDCC128], ProtocolSymbols[storedProtocol]); break; } newSpeed = DecodeSpeed128(speedData); break; default: return; } const Speed oldSpeed = locoCache.GetSpeed(address); if (newSpeed != oldSpeed) { locoCache.SetSpeed(address, newSpeed); manager->LocoSpeed(ControlTypeHardware, controlID, protocol, address, newSpeed); } const Orientation newOrientation = (buffer[8] >> 7) ? OrientationRight : OrientationLeft; const Orientation oldOrientation = locoCache.GetOrientation(address); if (newOrientation != oldOrientation) { locoCache.SetOrientation(address, newOrientation); manager->LocoOrientation(ControlTypeHardware, controlID, protocol, address, newOrientation); } const uint32_t oldFunctions = locoCache.GetFunctions(address); const uint32_t f0 = (static_cast(buffer[9]) >> 4) & 0x01; const uint32_t f1_4 = (static_cast(buffer[9]) << 1) & 0x1E; const uint32_t f5_12 = static_cast(buffer[10]) << 5; const uint32_t f13_20 = static_cast(buffer[11]) << 13; const uint32_t f21_28 = static_cast(buffer[12]) << 21; const uint32_t newFunctions = f0 | f1_4 | f5_12 | f13_20 | f21_28; if (newFunctions == oldFunctions) { return; } const uint32_t functionsDiff = newFunctions ^ oldFunctions; for (DataModel::LocoFunctionNr function = 0; function <= 28; ++function) { const bool stateChange = (functionsDiff >> function) & 0x01; if (stateChange == false) { continue; } const DataModel::LocoFunctionState newState = ((newFunctions >> function) & 0x01 ? DataModel::LocoFunctionStateOn : DataModel::LocoFunctionStateOff); locoCache.SetFunction(address, function, newState); manager->LocoFunctionState(ControlTypeHardware, controlID, protocol, address, function, newState); } } void Z21::ParseCvData(const unsigned char* buffer) { if (buffer[5] != 0x14) { logger->Error(Languages::TextCheckSumError); return; } if (lastProgramMode == ProgramModeMm) { return; } const CvNumber cv = Utils::Integer::DataBigEndianToShort(buffer + 6) + 1; const CvValue value = buffer[8]; logger->Debug(Languages::TextProgramReadValue, cv, value); manager->ProgramValue(cv, value); } void Z21::ParseRmBusData(const unsigned char* buffer) { if (buffer[4] > 1) { return; } unsigned char moduleShift = buffer[4] * 10; for (unsigned char index = 0; index < 10; ++index) { const unsigned char module = index + moduleShift; const unsigned char newData = buffer[5 + index]; const unsigned char oldData = feedbackCache.Get(module); if (newData == oldData) { continue; } const unsigned char diff = newData ^ oldData; for (unsigned char pinOnModule = 0; pinOnModule < 8; ++pinOnModule) { const unsigned char pinDiff = (diff >> pinOnModule) & 0x01; if (pinDiff == 0) { continue; } const unsigned char pinData = newData >> pinOnModule; const unsigned char pin = module * 8 + pinOnModule + 1; logger->Info(Languages::TextFeedbackChange, pin, module, Languages::GetOnOff(static_cast(pinData))); manager->FeedbackState(controlID, pin, static_cast(pinData)); } feedbackCache.Set(module, newData); } } void Z21::ParseLocoNetDetector(const unsigned char* buffer) { if (buffer[4] != 0x01) { // we only parse simple detectors return; } FeedbackPin pin = Utils::Integer::DataLittleEndianToShort(buffer + 5); ++pin; DataModel::Feedback::FeedbackState state = buffer[7] > 0 ? DataModel::Feedback::FeedbackStateOccupied : DataModel::Feedback::FeedbackStateFree; manager->FeedbackState(controlID, pin, state); } void Z21::ParseDetectorData(const unsigned char* buffer) { FeedbackPin pin = Utils::Integer::DataLittleEndianToShort(buffer + 6); uint8_t port = buffer[8]; --pin; pin <<= 3; pin += port; ++pin; uint8_t type = buffer[9]; uint16_t value1 = Utils::Integer::DataLittleEndianToShort(buffer + 10); DataModel::Feedback::FeedbackState state; switch (type) { case 0x01: { value1 >>= 12; value1 &= 0x0001; state = static_cast(value1); break; } default: { state = (value1 > 0 ? DataModel::Feedback::FeedbackStateOccupied : DataModel::Feedback::FeedbackStateFree); break; } } manager->FeedbackState(controlID, pin, state); } void Z21::SendGetSerialNumber() { char buffer[4] = { 0x04, 0x00, 0x10, 0x00 }; Send(buffer, sizeof(buffer)); } void Z21::SendGetHardwareInfo() { char buffer[4] = { 0x04, 0x00, 0x1A, 0x00 }; Send(buffer, sizeof(buffer)); } void Z21::SendGetStatus() { char buffer[7] = { 0x07, 0x00, 0x40, 0x00, 0x21, 0x24, 0x05 }; Send(buffer, sizeof(buffer)); } void Z21::SendGetCode() { char buffer[4] = { 0x04, 0x00, 0x18, 0x00 }; Send(buffer, sizeof(buffer)); } void Z21::SendGetDetectorState() { unsigned char buffer[7] = { 0x07, 0x00, 0xC4, 0x00, 0x00, 0x00, 0xD0 }; Send(buffer, sizeof(buffer)); } void Z21::SendLogOff() { char buffer[4] = { 0x04, 0x00, 0x30, 0x00 }; Send(buffer, sizeof(buffer)); } void Z21::SendBroadcastFlags() { SendBroadcastFlags(static_cast(Z21Enums::BroadCastFlagBasic | Z21Enums::BroadCastFlagRBus | Z21Enums::BroadCastFlagAllLoco | Z21Enums::BroadCastFlagCanDetector)); } void Z21::SendBroadcastFlags(const Z21Enums::BroadCastFlags flags) { unsigned char buffer[8] = { 0x08, 0x00, 0x50, 0x00 }; Utils::Integer::IntToDataLittleEndian(flags, buffer + 4); Send(buffer, sizeof(buffer)); } void Z21::SendSetMode(const Address address, const Z21Enums::Command command, const Z21Enums::ProtocolMode mode) { switch (command) { case Z21Enums::CommandSetLocoMode: case Z21Enums::CommandSetTurnoutMode: break; default: return; } switch (mode) { case Z21Enums::ProtocolModeMM: if (address > MaxMMAddress) { return; } break; case Z21Enums::ProtocolModeDCC: break; default: return; } unsigned char buffer[7] = { 0x07, 0x00, command, 0x00, 0x00, 0x00, mode }; Utils::Integer::ShortToDataBigEndian(address, buffer + 4); Send(buffer, sizeof(buffer)); } void Z21::SendSetLocoMode(const Address address, const Protocol protocol) { const Protocol storedProtocol = locoCache.GetProtocol(address); if (storedProtocol == protocol) { return; } switch (protocol) { case ProtocolMM1: case ProtocolMM15: case ProtocolMM2: SendSetLocoModeMM(address); break; case ProtocolDCC14: case ProtocolDCC28: case ProtocolDCC128: SendSetLocoModeDCC(address); break; default: return; } locoCache.SetProtocol(address, protocol); } void Z21::SendSetLocoModeMM(const Address address) { SendSetMode(address, Z21Enums::CommandSetLocoMode, Z21Enums::ProtocolModeMM); } void Z21::SendSetLocoModeDCC(const Address address) { SendSetMode(address, Z21Enums::CommandSetLocoMode, Z21Enums::ProtocolModeDCC); } void Z21::SendSetTurnoutMode(const Address address, const Protocol protocol) { Protocol storedProtocol = turnoutCache.GetProtocol(address); if (storedProtocol == protocol) { return; } switch (protocol) { case ProtocolMM: SendSetTurnoutModeMM(address); break; case ProtocolDCC: SendSetTurnoutModeDCC(address); break; default: return; } turnoutCache.SetProtocol(address, protocol); } void Z21::SendSetTurnoutModeMM(const Address address) { const Address zeroBasedAddress = address - 1; SendSetMode(zeroBasedAddress, Z21Enums::CommandSetTurnoutMode, Z21Enums::ProtocolModeMM); } void Z21::SendSetTurnoutModeDCC(const Address address) { const Address zeroBasedAddress = address - 1; SendSetMode(zeroBasedAddress, Z21Enums::CommandSetTurnoutMode, Z21Enums::ProtocolModeDCC); } int Z21::Send(const unsigned char* buffer, const size_t bufferLength) { logger->HexOut(buffer, bufferLength); return connection.Send(buffer, bufferLength); } } // namespace } // namespace railcontrol-24+dfsg1/Hardware/Protocols/Z21.h000066400000000000000000000204451500456250600211070ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include #include #include "DataModel/AccessoryBase.h" #include "Hardware/HardwareInterface.h" #include "Hardware/HardwareParams.h" #include "Hardware/Protocols/Z21DataTypes.h" #include "Hardware/Protocols/Z21FeedbackCache.h" #include "Hardware/Protocols/Z21LocoCache.h" #include "Hardware/Protocols/Z21TurnoutCache.h" #include "Logger/Logger.h" #include "Network/UdpConnection.h" namespace Z21Enums = Hardware::Protocols::Z21Enums; // protocol specification at https://www.z21.eu/media/Kwc_Basic_DownloadTag_Component/47-1652-959-downloadTag/default/69bad87e/1558674980/z21-lan-protokoll.pdf namespace Hardware { namespace Protocols { class Z21: Hardware::HardwareInterface { public: Z21() = delete; Z21(const Z21&) = delete; Z21& operator=(const Z21&) = delete; Z21(const HardwareParams* params, const std::string& controlName); virtual ~Z21(); inline Hardware::Capabilities GetCapabilities() const override { return Hardware::CapabilityLoco | Hardware::CapabilityAccessory | Hardware::CapabilityFeedback | Hardware::CapabilityProgram | Hardware::CapabilityProgramMmWrite | Hardware::CapabilityProgramDccDirectRead | Hardware::CapabilityProgramDccDirectWrite | Hardware::CapabilityProgramDccPomLocoRead | Hardware::CapabilityProgramDccPomLocoWrite | Hardware::CapabilityProgramDccPomAccessoryRead | Hardware::CapabilityProgramDccPomAccessoryWrite; } void GetLocoProtocols(std::vector& protocols) const override { protocols.push_back(ProtocolMM1); protocols.push_back(ProtocolMM15); protocols.push_back(ProtocolMM2); protocols.push_back(ProtocolDCC14); protocols.push_back(ProtocolDCC28); protocols.push_back(ProtocolDCC128); } bool LocoProtocolSupported(Protocol protocol) const override { return (protocol == ProtocolMM1 || protocol == ProtocolMM15 || protocol == ProtocolMM2 || protocol == ProtocolDCC14 || protocol == ProtocolDCC28 || protocol == ProtocolDCC128); } void GetAccessoryProtocols(std::vector& protocols) const override { protocols.push_back(ProtocolMM); protocols.push_back(ProtocolDCC); } bool AccessoryProtocolSupported(Protocol protocol) const override { return (protocol == ProtocolMM || protocol == ProtocolDCC); } static void GetArgumentTypesAndHint(std::map& argumentTypes, std::string& hint) { argumentTypes[1] = ArgumentTypeIpAddress; hint = Languages::GetText(Languages::TextHintZ21); } void Booster(const BoosterState status) override; void LocoSpeed(const Protocol protocol, const Address address, const Speed speed) override; void LocoOrientation(const Protocol protocol, const Address address, const Orientation orientation) override; void LocoFunction(const Protocol protocol, const Address address, const DataModel::LocoFunctionNr function, const DataModel::LocoFunctionState on) override; void LocoSpeedOrientationFunctions(const Protocol protocol, const Address address, const Speed speed, const Orientation orientation, std::vector& functions) override; void Accessory(const Protocol protocol, const Address address, const DataModel::AccessoryState state, const bool on, const DataModel::AccessoryPulseDuration duration) override; void ProgramRead(const ProgramMode mode, const Address address, const CvNumber cv) override; void ProgramWrite(const ProgramMode mode, const Address address, const CvNumber cv, const CvValue value) override; static unsigned char EncodeSpeed14(const Speed speed); static unsigned char EncodeSpeed28(const Speed speed); static unsigned char EncodeSpeed128(const Speed speed); static Speed DecodeSpeed14(unsigned char data); static Speed DecodeSpeed28(unsigned char data); static Speed DecodeSpeed128(unsigned char data); private: static const unsigned short Z21Port = 21105; static const unsigned int Z21CommandBufferLength = 1472; // = Max Ethernet MTU static const Address MaxMMAddress = 255; class AccessoryQueueEntry { public: inline AccessoryQueueEntry() : protocol(ProtocolNone), address(AddressNone), state(DataModel::DefaultState), duration(DataModel::DefaultAccessoryPulseDuration) { } inline AccessoryQueueEntry(const Protocol protocol, const Address address, const DataModel::AccessoryState state, const DataModel::AccessoryPulseDuration duration) : protocol(protocol), address(address), state(state), duration(duration) { } Protocol protocol; Address address; DataModel::AccessoryState state; DataModel::AccessoryPulseDuration duration; }; volatile bool run; Network::UdpConnection connection; std::thread receiverThread; std::thread heartBeatThread; std::thread accessorySenderThread; Z21LocoCache locoCache; Z21TurnoutCache turnoutCache; Z21FeedbackCache feedbackCache; ProgramMode lastProgramMode; volatile bool connected; Utils::ThreadSafeQueue accessoryQueue; void ProgramMm(const CvNumber cv, const CvValue value); void ProgramDccRead(const CvNumber cv); void ProgramDccWrite(const CvNumber cv, const CvValue value); void ProgramDccPom(const Z21Enums::PomDB0 db0, const Z21Enums::PomOption option, const Address address, const CvNumber cv, const CvValue value = 0); void LocoSpeedOrientation(const Protocol protocol, const Address address, const Speed speed, const Orientation orientation); void AccessorySender(); void AccessoryOnOrOff(const Address address, const DataModel::AccessoryState state, const bool on); void HeartBeatSender(); void Receiver(); ssize_t ParseData(const unsigned char* buffer, size_t bufferLength); void ParseXHeader(const unsigned char* buffer); void ParseDB0(const unsigned char* buffer); void ParseTurnoutData(const unsigned char* buffer); void ParseLocoData(const unsigned char* buffer); void ParseCvData(const unsigned char* buffer); void ParseRmBusData(const unsigned char* buffer); void ParseLocoNetDetector(const unsigned char* buffer); void ParseDetectorData(const unsigned char* buffer); void StartUpConnection(); void SendGetSerialNumber(); void SendGetHardwareInfo(); void SendGetStatus(); void SendGetCode(); void SendGetDetectorState(); void SendLogOff(); void SendBroadcastFlags(); void SendBroadcastFlags(const Z21Enums::BroadCastFlags flags); void SendSetMode(const Address address, const Z21Enums::Command command, const Z21Enums::ProtocolMode mode); void SendSetLocoMode(const Address address, const Protocol protocol); void SendSetLocoModeMM(const Address address); void SendSetLocoModeDCC(const Address address); void SendSetTurnoutMode(const Address address, const Protocol protocol); void SendSetTurnoutModeMM(const Address address); void SendSetTurnoutModeDCC(const Address address); int Send(const unsigned char* buffer, const size_t bufferLength); inline int Send(const char* buffer, const size_t bufferLength) { return Send(reinterpret_cast(buffer), bufferLength); } }; } // namespace } // namespace railcontrol-24+dfsg1/Hardware/Protocols/Z21DataTypes.h000066400000000000000000000104731500456250600227260ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once // protocol specification at https://www.z21.eu/media/Kwc_Basic_DownloadTag_Component/47-1652-959-downloadTag/default/69bad87e/1558674980/z21-lan-protokoll.pdf namespace Hardware { namespace Protocols { namespace Z21Enums { enum BroadCastFlags : uint32_t { BroadCastFlagNone = 0x00000000, BroadCastFlagBasic = 0x00000001, BroadCastFlagRBus = 0x00000002, BroadCastFlagRailCom = 0x00000004, BroadCastFlagSystemState = 0x00000100, BroadCastFlagAllLoco = 0x00010000, BroadCastFlagRailComData = 0x00040000, BroadCastFlagCanDetector = 0x00080000, BroadCastFlagLocoNetBasic = 0x01000000, BroadCastFlagLocoNetLoco = 0x02000000, BroadCastFlagLocoNetSwitch = 0x04000000, BroadCastFlagLocoNetDetector = 0x08000000 }; enum Command : uint8_t { CommandSetLocoMode = 0x61, CommandSetTurnoutMode = 0x70 }; enum ProtocolMode : uint8_t { ProtocolModeDCC = 0, ProtocolModeMM = 1 }; enum FeatureSet : uint8_t { FeaturesNotRestricted = 0x00, FeaturesStartLocked = 0x01, FeaturesStartUnlocked = 0x02 }; enum Header : uint16_t { HeaderSerialNumber = 0x10, HeaderGetCode = 0x18, HeaderGetHardwareInfo = 0x1A, HeaderLogOff = 0x30, HeaderSeeXHeader = 0x40, HeaderSetBroadcastFlags = 0x50, HeaderGetBroadcastFlags = 0x51, HeaderGetLocoMode = 0x60, HeaderSetLocoMode = 0x61, HeaderGetTurnoutMode = 0x70, HeaderSetTurnoutMode = 0x71, HeaderRmBusData = 0x80, HeaderSystemData = 0x84, HeaderGetSystemState = 0x85, HeaderRailComtData = 0x88, HeaderLocoNetRx = 0xA0, HeaderLocoNetTx = 0xA1, HeaderLocoNetLan = 0xA2, HeaderLocoNetDispatch = 0xA3, HeaderLocoNetDetector = 0xA4, HeaderDetector = 0xC4 }; enum XHeader : uint8_t { XHeaderSeeDB0_1 = 0x21, XHeaderTurnoutInfo = 0x43, XHeaderGetTurnoutInfo = 0x43, XHeaderSetTurnout = 0x53, XHeaderSeeDB0_2 = 0x61, XHeaderStatusChanged = 0x62, XHeaderVersion = 0x63, XHeaderCvResult = 0x64, XHeaderSetStop = 0x80, XHeaderBcStopped = 0x81, XHeaderGetLocoInfo = 0xE3, XHeaderLocoDrive = 0xE4, XHeaderSetLocoBinaryState = 0xE5, XHeaderLocoInfo = 0xEF, XHeaderGetFirmwareVersion = 0xF1, XHeaderFirmwareVersion = 0xF3 }; enum DB0 : uint8_t { DB0PowerOff = 0x00, DB0PowerOn = 0x01, DB0ProgrammingMode = 0x02, DB0ShortCircuit = 0x08, DB0FirmwareVersion = 0x0A, DB0SetLocoDrive14 = 0x10, DB0SetLocoDrive28 = 0x12, DB0CvShortCircuit = 0x12, DB0SetLocoDrive128 = 0x13, DB0CvNack = 0x13, DB0Version = 0x21, DB0StatusChanged = 0x22, DB0Status = 0x24, DB0SetPowerOff = 0x80, DB0SetPowerOn = 0x81, DB0UnknownCommand = 0x82, DB0LocoFunction = 0xF8 }; enum PomDB0 : uint8_t { PomLoco = 0x30, PomAccessory = 0x31 }; enum PomOption : uint16_t { PomWriteByte = 0xEC00, PomWriteBit = 0xE800, PomReadByte = 0xE400 }; } } } railcontrol-24+dfsg1/Hardware/Protocols/Z21FeedbackCache.h000066400000000000000000000030421500456250600234320ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once namespace Hardware { namespace Protocols { class Z21FeedbackCache { public: Z21FeedbackCache(const Z21FeedbackCache&) = delete; Z21FeedbackCache& operator=(const Z21FeedbackCache&) = delete; inline Z21FeedbackCache() { for (unsigned char module = 0; module < MaxModules; ++module) { cache[module] = 0; } } inline void Set(const unsigned char module, const unsigned char data) { if (module >= MaxModules) { return; } cache[module] = data; } inline unsigned char Get(unsigned char module) { if (module >= MaxModules) { return 0; } return cache[module]; } private: static const unsigned char MaxModules = 20; unsigned char cache[MaxModules]; }; } // namespace } // namespace railcontrol-24+dfsg1/Hardware/Protocols/Z21LocoCache.h000066400000000000000000000067571500456250600226620ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "DataTypes.h" namespace Hardware { namespace Protocols { class Z21LocoCacheEntry { public: Z21LocoCacheEntry() : speed(MinSpeed), orientation(OrientationRight), functions(0), protocol(ProtocolNone) { } Z21LocoCacheEntry(const Speed speed, const Orientation orientation, const Protocol protocol) : speed(speed), orientation(orientation), functions(0), protocol(protocol) { } Speed speed; Orientation orientation; uint32_t functions; Protocol protocol; }; class Z21LocoCache { public: Z21LocoCache& operator=(const Z21LocoCache&) = delete; Z21LocoCacheEntry GetData(const Address address) { if (cache.count(address) == 1) { return cache[address]; } Z21LocoCacheEntry entry; return entry; } void SetSpeed(const Address address, const Speed speed) { Z21LocoCacheEntry entry = GetData(address); entry.speed = speed; cache[address] = entry; } Speed GetSpeed(const Address address) { if (cache.count(address) == 0) { return MinSpeed; } return cache[address].speed; } void SetOrientation(const Address address, const Orientation orientation) { Z21LocoCacheEntry entry = GetData(address); entry.orientation = orientation; cache[address] = entry; } Orientation GetOrientation(const Address address) { if (cache.count(address) == 0) { return OrientationRight; } return cache[address].orientation; } void SetSpeedOrientationProtocol(const Address address, const Speed speed, const Orientation orientation, const Protocol protocol) { Z21LocoCacheEntry entry(speed, orientation, protocol); cache[address] = entry; } void SetFunction(const Address address, const DataModel::LocoFunctionNr function, const bool on) { Z21LocoCacheEntry entry = GetData(address); uint32_t mask = ~(1 << function); entry.functions &= mask; entry.functions |= on << function; cache[address] = entry; } uint32_t GetFunctions(const Address address) { if (cache.count(address) == 0) { return 0; } return cache[address].functions; } void SetProtocol(const Address address, const Protocol protocol) { Z21LocoCacheEntry entry = GetData(address); entry.protocol = protocol; cache[address] = entry; } Protocol GetProtocol(const Address address) { if (cache.count(address) == 0) { return ProtocolNone; } return cache[address].protocol; } private: std::map cache; }; } // namespace } // namespace railcontrol-24+dfsg1/Hardware/Protocols/Z21TurnoutCache.h000066400000000000000000000033561500456250600234360ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "DataTypes.h" namespace Hardware { namespace Protocols { class Z21TurnoutCacheEntry { public: Z21TurnoutCacheEntry(const Z21TurnoutCacheEntry&) = delete; inline Z21TurnoutCacheEntry() : protocol(ProtocolNone) { } inline Z21TurnoutCacheEntry& operator=(const Protocol protocol) { this->protocol = protocol; return *this; } inline Z21TurnoutCacheEntry& operator=(const Z21TurnoutCacheEntry&) = delete; Protocol protocol; }; class Z21TurnoutCache { public: Z21TurnoutCache& operator=(const Z21TurnoutCache&) = delete; inline void SetProtocol(const Address address, const Protocol protocol) { cache[address] = protocol; } inline Protocol GetProtocol(const Address address) { if (cache.count(address) == 0) { return ProtocolDCC; } return cache[address].protocol; } private: std::map cache; }; } // namespace } // namespace railcontrol-24+dfsg1/Hardware/README000066400000000000000000000003571500456250600172560ustar00rootroot00000000000000In the folder hardware are all the classes used for command control specific belongings. The classes are loaded dynamically if they are used. To create your own command control copy the virtual.cpp and virtual.h and modify to your needs. railcontrol-24+dfsg1/Hardware/RedBox.h000066400000000000000000000030521500456250600177250ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "Hardware/Protocols/P50xSerial.h" #include "Languages.h" namespace Hardware { class HardwareParams; class RedBox : public Protocols::P50xSerial { public: RedBox() = delete; RedBox(const RedBox&) = delete; RedBox& operator=(const RedBox&) = delete; inline RedBox(const HardwareParams* const params) : Protocols::P50xSerial(params, "RedBox", TypeTams) { Init(); } virtual ~RedBox() { } static inline void GetArgumentTypesAndHint(std::map& argumentTypes, std::string& hint) { argumentTypes[1] = ArgumentTypeSerialPort; argumentTypes[2] = ArgumentTypeS88Modules; hint = Languages::GetText(Languages::TextHintRedBox); } private: static const unsigned char MaxS88ModulesRedBox = 104; }; } // namespace railcontrol-24+dfsg1/Hardware/Rektor.h000066400000000000000000000020621500456250600200100ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include "Hardware/Protocols/Z21.h" namespace Hardware { class Rektor : Protocols::Z21 { public: Rektor() = delete; Rektor(const Rektor&) = delete; Rektor& operator=(const Rektor&) = delete; inline Rektor(const HardwareParams* params) : Protocols::Z21(params, "Rektor") { } }; } // namespace railcontrol-24+dfsg1/Hardware/SystemControl7.h000066400000000000000000000027731500456250600214670ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include "Languages.h" #include "Hardware/Protocols/LocoNet.h" namespace Hardware { class HardwareParams; class SystemControl7 : public Protocols::LocoNet { public: SystemControl7() = delete; SystemControl7(const SystemControl7&) = delete; SystemControl7& operator=(const SystemControl7&) = delete; inline SystemControl7(const HardwareParams* params) : Protocols::LocoNet(params, "System Control 7", B115200) { } virtual ~SystemControl7() { } static inline void GetArgumentTypesAndHint(std::map& argumentTypes, std::string& hint) { argumentTypes[1] = ArgumentTypeSerialPort; hint = Languages::GetText(Languages::TextHintSystemControl7); } }; } // namespace railcontrol-24+dfsg1/Hardware/TwinCenter.h000066400000000000000000000030301500456250600206200ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include "Languages.h" #include "Hardware/Protocols/P50xUhlenbrock.h" namespace Hardware { class HardwareParams; class TwinCenter : public Protocols::P50xUhlenbrock { public: TwinCenter() = delete; TwinCenter(const TwinCenter&) = delete; TwinCenter& operator=(const TwinCenter&) = delete; inline TwinCenter(const HardwareParams* params) : Protocols::P50xUhlenbrock(params, "TwinCenter") { Init(); } virtual ~TwinCenter() { } static inline void GetArgumentTypesAndHint(std::map& argumentTypes, std::string& hint) { argumentTypes[1] = ArgumentTypeSerialPort; argumentTypes[2] = ArgumentTypeS88Modules; hint = Languages::GetText(Languages::TextHintTwinCenter); } }; } // namespace railcontrol-24+dfsg1/Hardware/Virtual.cpp000066400000000000000000000107451500456250600205320ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include #include #include "Hardware/Virtual.h" #include "Manager.h" #include "Utils/Utils.h" namespace Hardware { Virtual::Virtual(const HardwareParams* params) : HardwareInterface(params->GetManager(), params->GetControlID(), "Virtual Command Station / " + params->GetName(), params->GetName()) { } // turn booster on or off void Virtual::Booster(const BoosterState status) { logger->Info(status ? Languages::TextTurningBoosterOn : Languages::TextTurningBoosterOff); } // set loco speed void Virtual::LocoSpeed(const Protocol protocol, const Address address, const Speed speed) { logger->Info(Languages::TextSettingSpeedWithProtocol, Utils::Utils::ProtocolToString(protocol), address, speed); } // set the direction of a loco void Virtual::LocoOrientation(const Protocol protocol, const Address address, const Orientation orientation) { logger->Info(Languages::TextSettingDirectionOfTravelWithProtocol, Utils::Utils::ProtocolToString(protocol), address, Languages::GetLeftRight(orientation)); } // set loco function void Virtual::LocoFunction(const Protocol protocol, const Address address, const DataModel::LocoFunctionNr function, const DataModel::LocoFunctionState on) { logger->Info(Languages::TextSettingFunctionWithProtocol, static_cast(function), Utils::Utils::ProtocolToString(protocol), address, Languages::GetOnOff(on)); } // accessory command void Virtual::Accessory(const Protocol protocol, const Address address, const DataModel::AccessoryState state, const bool on, __attribute__((unused)) const DataModel::AccessoryPulseDuration duration) { logger->Info(Languages::TextSettingAccessoryWithProtocol, Utils::Utils::ProtocolToString(protocol), address, Languages::GetGreenRed(state), Languages::GetOnOff(on)); } // read CV value void Virtual::ProgramRead(const ProgramMode mode, const Address address, const CvNumber cv) { switch (mode) { case ProgramModeMfx: logger->Info(Languages::TextProgramMfxRead, address, cv); break; case ProgramModeDccRegister: logger->Info(Languages::TextProgramDccRegisterRead, cv); break; case ProgramModeDccPage: logger->Info(Languages::TextProgramDccPageRead, cv); break; case ProgramModeDccDirect: logger->Info(Languages::TextProgramDccDirectRead, cv); break; case ProgramModeDccPomLoco: logger->Info(Languages::TextProgramDccPomLocoRead, address, cv); break; case ProgramModeDccPomAccessory: logger->Info(Languages::TextProgramDccPomAccessoryRead, address, cv); break; default: return; } __attribute__((unused)) auto r = std::async(std::launch::async, Manager::ProgramDccValueStatic, manager, cv, cv & 0xFF); } // write DCC CV value void Virtual::ProgramWrite(const ProgramMode mode, const Address address, const CvNumber cv, const CvValue value) { switch (mode) { case ProgramModeMm: logger->Info(Languages::TextProgramMm, cv, value); break; case ProgramModeMmPom: logger->Info(Languages::TextProgramMmPom, address, cv, value); break; case ProgramModeMfx: logger->Info(Languages::TextProgramMfxWrite, address, cv, value); break; case ProgramModeDccRegister: logger->Info(Languages::TextProgramDccRegisterWrite, cv, value); break; case ProgramModeDccPage: logger->Info(Languages::TextProgramDccPageWrite, cv, value); break; case ProgramModeDccDirect: logger->Info(Languages::TextProgramDccDirectWrite, cv, value); break; case ProgramModeDccPomLoco: logger->Info(Languages::TextProgramDccPomLocoWrite, address, cv, value); break; case ProgramModeDccPomAccessory: logger->Info(Languages::TextProgramDccPomAccessoryWrite, address, cv, value); break; default: return; } } } // namespace railcontrol-24+dfsg1/Hardware/Virtual.h000066400000000000000000000065361500456250600202020ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "Hardware/HardwareInterface.h" #include "Hardware/HardwareParams.h" #include "Logger/Logger.h" namespace Hardware { class Virtual : HardwareInterface { public: Virtual() = delete; Virtual(const Virtual&) = delete; Virtual& operator=(const Virtual&) = delete; Virtual(const HardwareParams* params); inline Hardware::Capabilities GetCapabilities() const override { return Hardware::CapabilityLoco | Hardware::CapabilityAccessory | Hardware::CapabilityFeedback | Hardware::CapabilityProgram | Hardware::CapabilityProgramMmWrite | Hardware::CapabilityProgramMmPomWrite | Hardware::CapabilityProgramMfxRead | Hardware::CapabilityProgramMfxWrite | Hardware::CapabilityProgramDccRegisterRead | Hardware::CapabilityProgramDccRegisterWrite | Hardware::CapabilityProgramDccPageRead | Hardware::CapabilityProgramDccPageWrite | Hardware::CapabilityProgramDccDirectRead | Hardware::CapabilityProgramDccDirectWrite | Hardware::CapabilityProgramDccPomLocoRead | Hardware::CapabilityProgramDccPomLocoWrite | Hardware::CapabilityProgramDccPomAccessoryRead | Hardware::CapabilityProgramDccPomAccessoryWrite; } static void GetHint(std::string& hint) { hint = Languages::GetText(Languages::TextHintVirtual); } void GetLocoProtocols(std::vector& protocols) const override { protocols.push_back(ProtocolNone); } bool LocoProtocolSupported(const Protocol protocol) const override { return protocol == ProtocolNone; } void GetAccessoryProtocols(std::vector& protocols) const override { protocols.push_back(ProtocolNone); } bool AccessoryProtocolSupported(const Protocol protocol) const override { return protocol == ProtocolNone; } void Booster(const BoosterState status) override; void LocoSpeed(const Protocol protocol, const Address address, const Speed speed) override; void LocoOrientation(const Protocol protocol, const Address address, const Orientation orientation) override; void LocoFunction(const Protocol protocol, const Address address, const DataModel::LocoFunctionNr function, const DataModel::LocoFunctionState on) override; void Accessory(const Protocol protocol, const Address address, const DataModel::AccessoryState state, const bool on, const DataModel::AccessoryPulseDuration duration) override; void ProgramRead(const ProgramMode mode, const Address address, const CvNumber cv) override; void ProgramWrite(const ProgramMode mode, const Address address, const CvNumber cv, const CvValue value) override; }; } // namespace railcontrol-24+dfsg1/Hardware/Z21.h000066400000000000000000000020321500456250600171130ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include "Hardware/Protocols/Z21.h" namespace Hardware { class Z21 : Protocols::Z21 { public: Z21() = delete; Z21(const Z21&) = delete; Z21& operator=(const Z21&) = delete; inline Z21(const HardwareParams* params) : Protocols::Z21(params, "Z21") { } }; } // namespace railcontrol-24+dfsg1/Hardware/ZLib.cpp000066400000000000000000000041041500456250600177340ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include "Hardware/zlib/zlib.h" #include "Hardware/ZLib.h" #include "Utils/Utils.h" using std::string; string ZLib::Compress(const string& input) { long inputSize = input.size(); unsigned long outputSize = inputSize + 12; unsigned char* outputBuffer = reinterpret_cast(malloc(outputSize)); int nResult = compress2(outputBuffer, &outputSize, reinterpret_cast(input.c_str()), inputSize, 9); if (nResult != Z_OK) { return ""; } string outputData(reinterpret_cast(outputBuffer), outputSize); free(outputBuffer); return outputData; } string ZLib::UnCompress(const char* input, const size_t inputSize, const size_t outputSize) { z_stream strm; strm.zalloc = Z_NULL; strm.zfree = Z_NULL; strm.opaque = Z_NULL; strm.avail_in = 0; strm.next_in = Z_NULL; int ret = inflateInit(&strm); if (ret != Z_OK) { return ""; } strm.avail_in = inputSize; strm.avail_out = outputSize; strm.next_in = reinterpret_cast(const_cast(input)); char* outputBuffer = reinterpret_cast(malloc(outputSize)); strm.next_out = reinterpret_cast(outputBuffer); ret = inflate(&strm, Z_NO_FLUSH); inflateEnd(&strm); if (ret != Z_STREAM_END) { free(outputBuffer); return ""; } std::string output(outputBuffer, outputSize); free(outputBuffer); return output; } railcontrol-24+dfsg1/Hardware/ZLib.h000066400000000000000000000016731500456250600174110ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include class ZLib { public: static std::string Compress(const std::string& input); static std::string UnCompress(const char* input, const size_t inputSize, const size_t outputSize); }; railcontrol-24+dfsg1/LICENCE000066400000000000000000001045151500456250600156270ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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 3 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, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . railcontrol-24+dfsg1/Languages.cpp000066400000000000000000003251641500456250600172610ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include "Languages.h" #include "Utils/Utils.h" const char* Languages::languages[MaxTexts][MaxLanguages] = { /* Text180Deg */ { "180 degrees", "180 Grad", "180 grados" }, /* Text90DegAntiClockwise */ { "90 degrees anti-clockwise", "90 Grad gegen den Uhrzeigersinn", "90 grados en el sentido contrario de las agujas del reloj" }, /* Text90DegClockwise */ { "90 degrees clockwise", "90 Grad im Uhrzeigersinn", "90 grados en el sentido de las agujas del reloj" }, /* TextAccessories */ { "Accessories", "Zubehörartikel", "Accesorios" }, /* TextAccessory*/ { "accessory", "Zubehörartikel", "accesorio" }, /* TextAccessoryAddressDccTooHigh */ { "Addresses higher then 2044 are not supported by DCC", "Adressen grösser als 2044 werden nicht unterstützt von DCC", "Direcciones más grandes que 2044 no están compatibles con DCC" }, /* TextAccessoryAddressMmTooHigh */ { "Addresses higher then 320 are not supported by MM1/MM2", "Adressen grösser als 320 werden nicht unterstützt von MM1/MM2", "Direcciones más grandes que 320 no están compatibles con MM1/MM2" }, /* TextAccessoryControlProtocolAddressDoesNotExist */ { "Acessory for control {0} with protocol {1} and address {2} does not exist", "Zubehörartikel für Zentrale {0} mit Protokoll {1} und Adresse {2} existiert nicht", "Accesorio para control {0} con protocolo {1} y dirección {2} no existe" }, /* TextAccessoryDeleted */ { "Accessory {0} deleted", "Zubehörartikel {0} gelöscht", "Accessorio {0} eliminado" }, /* TextAccessoryDoesNotExist */ { "Acessory does not exist", "Zubehörartikel existiert nicht", "Accesorio no existe" }, /* TextAccessoryIsLocked */ { "Accessory {0} is locked", "Zubehörartikel {0} ist gesperrt", "Accesorio {0} está bloqueado" }, /* TextAccessoryIsUsedByRoute */ { "Accessory {0} is used by route {1}", "Zubehörartikel {0} wird von Fahrstrasse {1} benutzt", "Acesorio {0} está utilizado por itinerario {1}" }, /* TextAccessorySaved */ { "Accessory {0} saved", "Zubehörartikel {0} gespeichert", "Accesorio {0} guardado" }, /* TextAccessorySenderThreadStarted */ { "Accessory sender thread started", "Zubehörartikel Sender Thread gestartet", "Thread enviador accesorio creado" }, /* TextAccessoryStateIsGreen */ { "Accessory state of {0} is now green", "Der Status des Zubehörartikels ist nun grün", "El estado del accessorio está verde" }, /* TextAccessoryStateIsRed */ { "Accessory state of {0} is now red", "Der Status des Zubehörartikels ist nun rot", "El estado del accessorio está rojo" }, /* TextAccessoryTypeDecoupler */ { "decoupler", "Entkuppler", "desacoplador" }, /* TextAccessoryTypeLight */ { "light", "Licht", "luz" }, /* TextAccessoryTypeLightInhouse */ { "light in house", "Licht in Haus", "luz en casa" }, /* TextAccessoryTypeLightStreet */ { "street light", "Strassenlaterne", "farola" }, /* TextAccessoryTypeOnOff */ { "on-off", "ein-aus", "on-off" }, /* TextAccessoryTypeOnOn */ { "on-on", "ein-ein", "on-on" }, /* TextAccessoryTypeOnPush */ { "on push", "beim Drücken", "al pulsar" }, /* TextAccessoryUpdated */ { "Accessory {0} updated", "Zubehörartikel {0} aktualisiert", "Accessorio {0} actualizado" }, /* TextActualAndStoredProtocolsDiffer */ { "Actual ({0}) and stored ({1}) protocols differ", "Aktuelles ({0}) und gespeichertes ({1}) Protokoll unterscheiden sich", "protocolo reciente y guardado son diferentes" }, /* TextAddAccessory */ { "Add accessory", "Zubehörartikel hinzufügen", "Añadir accesorio" }, /* TextAddFeedback */ { "Add feedback", "Rückmelder hinzufügen", "Añadir retroseñal" }, /* TextAddRoute */ { "Add route", "Fahrstrasse hinzufügen", "Añadir itinerario" }, /* TextAddSignal */ { "Add signal", "Signal hinzufügen", "Añadir señal" }, /* TextAddSwitch */ { "Add switch", "Weiche hinzufügen", "Añadir desvío" }, /* TextAddText */ { "Add text", "Text hinzufügen", "Añadir texto" }, /* TextAddTrack */ { "Add track", "Gleis hinzufügen", "Añadir vía" }, /* TextAddingFeedback */ { "Adding feedback {0}", "Rückmelder {0} hinzugefügt", "Retroseñal añadido" }, /* TextAddingRouteToTimetable */ { "Adding route {0} to timetable", "Füge Fahrstrasse {0} zum Fahrplan hinzu", "Añadando itinerario {0} al horario" }, /* TextAddress */ { "Address", "Adresse", "Dirección" }, /* TextAddressMustBeHigherThen0 */ { "Address must be higher then 0", "Adresse muss grösser sein als 0", "Dirección tiene que ser mas que 0" }, /* TextAddresses */ { "Addresses", "Adressen", "Direcciónes" }, /* TextAllTrains */ { "all trains", "alle Züge", "todos los trenes" }, /* TextAllowLocoTurn */ { "Allow turn locomotive orientation", "Fahrtrichtung der Lokomotive drehen erlauben", "Permitir girar la dirección de viaje" }, /* TextAllowedPropulsions */ { "Allowed propulsions", "Erlaubte Antriebe", "Tipos de propulsiones permitios" }, /* TextAllowedPushPull */ { "Allowed push-pull trains", "Erlaubte Pendelzüge", "Trenes push-pull permitidos" }, /* TextAllowedTrainTypes */ { "Allowed train types", "Erlaubte Zugtypen", "Tipos de trenes permitidos" }, /* TextAlways */ { "always", "immer", "siempre" }, /* TextAreYouSureToDelete */ { "Are you sure you really want to delete {0}?", "Bist du sicher, dass du {0} löschen möchtest?", "Estás seguro, que quieras eliminar {0}?" }, /* TextAreYouSureToShutdown */ { "Are you sure you really want to shutdown RailControl server?", "Bist du sicher, dass du den RailControl Server herunterfahren möchtest?", "Estás seguro, que quieras apagar el servidor de RailControl?" }, /* TextAtLock */ { "At lock", "Beim Einstellen", "Durante bloquear" }, /* TextAtUnlock */ { "At unlock", "Beim Auflösen", "Durante liberar" }, /* TextAutomaticallyAddUnknownFeedbacks */ { "Automatically add unknown feedbacks", "Füge unbekannte Rückmelder automatisch hinzu", "Añadir retroseñales desconocidos automaticamente" }, /* TextAutomode */ { "Automode", "Automode", "Autómodo" }, /* TextBaseAddress */ { "Baseaddress", "Basisadresse", "Dirección basica" }, /* TextBasic */ { "Basic data", "Basisdaten", "Datos básicos" }, /* TextBlockTrack */ { "Block track", "Blockiere Gleis", "Bloquear vía" }, /* TextBooster */ { "Booster", "Booster", "Booster" }, /* TextBoosterIsTurnedOff */ { "Booster is turned off", "Booster ist ausgeschaltet", "Booster está apagado" }, /* TextBoosterIsTurnedOn */ { "Booster is turned on", "Booster ist eingeschaltet", "Booster está encendido" }, /* TextBridge */ { "Bridge", "Brücke", "Puente" }, /* TextBrowserInfo */ { "Please type one of the following links in your browser to connect to RailControl:{0}{1}{2}", "Bitte einer der folgenden Links im Browser eingeben um sich mit RailControl zu verbinden:{0}{1}{2}", "Por favor conectate a RailControl con el navegador internet con una de las enlaces siguientes:{0}{1}{2}" }, /* TextBufferStop */ { "End / Buffer Stop", "Ende / Prellbock", "Final / Tope" }, /* TextCS2ServerStarted */ { "CS2 server started", "CS2 Server wurde gestartet", "Servidor CS2 encendido" }, /* TextCS2ServerStopped */ { "CS2 server stopped", "CS2 Server wurde beendet", "Servidor CS2 apagado" }, /* TextCV */ { "CV", "CV", "CV" }, /* TextCanNotStartAlreadyRunning */ { "Can not start because it is already running", "Unmöglich zu starten weil schon gestartet", "Imposible poner en marcha porque ya está en marcha" }, /* TextCanNotStartInErrorState */ { "Can not start because it is in error state", "Unmöglich zu startein weil sie im Fehlerstatus ist", "Imposible poner en marcha perque está en estado error" }, /* TextCanNotStartNotOnTrack */ { "Can not start because it is not on a track", "Unmöglich zu starten weil sie nicht auf einem Gleis ist", "Imposible poner en marcha porque no está sobre una vía" }, /* TextChDwarf */ { "Swiss dwarf", "Schweizer Zwergsignal", "Suiza enana" }, /* TextChLDistant */ { "Swiss L distant", "Schweizer L Vorsignal", "Suiza L avanzada" }, /* TextChLMain */ { "Swiss L main", "Schweizer L Hauptsignal", "Suiza L principal" }, /* TextChNDistant */ { "Swiss N distant", "Schweizer N Vorsignal", "Suiza N avanzada" }, /* TextChNMain */ { "Swiss N main", "Schweizer N Hauptsignal", "Suiza N principal" }, /* TextChange */ { "change", "wechseln", "cambiar" }, /* TextCheckSumError */ { "Checksum error", "Checksummen Fehler", "Error de checksum" }, /* TextClosingSQLite */ { "Closing SQLite database", "Schliesse SQLite Datenbank", "Cerrando base de datos SQLite" }, /* TextCluster */ { "Track cluster", "Gleisgruppe", "Grupo de vías" }, /* TextClusterDeleted */ { "Track cluster {0} deleted", "Gleisgruppe {0} gelöscht", "Grupo de vías {0} eliminado" }, /* TextClusterDoesNotExist */ { "Track cluster does not exist", "Gleisgruppe existiert nicht", "Grupo de vías no existe" }, /* TextClusterSaved */ { "Track cluster {0} saved", "Gleisgruppe {0} gespeichert", "Grupo de vías {0} guardado" }, /* TextClusterUpdated */ { "Track cluster {0} updated", "Gleisgruppe {0} aktualisiert", "Grupo de vías {0} actualizado" }, /* TextClusters */ { "Track clusters", "Gleisgruppen", "Grupos de vías" }, /* TextCommandUnknown */ { "Command {0} unknown", "Kommando {0} nicht bekannt", "Comando {0} desconocido" }, /* TextCompileDate */ { "Compile date: {0}", "Kompilierdatum: 01}", "Fetcha compilada: {0}" }, /* TextConfigFileNotFound */ { "Config file {0} not found. Using default config file {1}.", "", "" }, /* TextConfigFileReceivedWithSize */ { "Configuration file with {0} bytes received", "Konfigurationsdatei mit {0} Bytes empfangen", "Archivo de configuración recibido con {0} bytes" }, /* TextConfigMenu */ { "Configuration menu", "Konfigurationsmenu", "Navegación de configuración" }, /* TextConfigureControlFirst */ { "Please configure a control first", "Bitte zuerst eine Zentrale konfigurieren", "Por favor configura un control antes" }, /* TextConnectedTo */ {"Connected to {0}", "Verbunden mit {0}", "Conectado con {0}" }, /* TextConnection */ { "Connection", "Anschluss", "Conexión" }, /* TextConnectionFailed */ { "Connection to {0}:{1} failed", "Verbindung zu {0}:{1} nicht möglich", "Imposible conectar a {0}:{1}" }, /* TextConnectionHint */ { "on-on:
Each decoder has a red and a green port. While clicking on the accessory, one or the other of the ports is activated and deactivated.

on push:
While clicking on the accessory, the selected port is activated and while releasing again the port is deactivated.

on-off:
While clicking on the accessory, the selected port is activated and while clicking the second time it is deactivated.
Some controls and some decoders can not handle this on-off mode, especially Z21, EcOS and DCC-EX.", "ein-ein:
Jeder Decoder hat einen roten und einen grünen Anschluss. Beim Anklicken des Zubehörartikels wird der eine oder der andere Anschluss aktiviert und deaktiviert.

beim Drücken:
Beim Anklicken des Zubehörartikels wird ein Anschluss aktiviert und beim Loslassen wird der Anschluss wieder deaktiviert.

ein-aus:
Beim Anklicken des Zubehörartikels wird der ausgewählte Anschluss aktiviert und beim zweiten Anklicken deaktiviert.
Einige Zentralen und Decoder können diesen ein-aus-Modus nicht verarbeiten, insbesondere Z21, EcOS und DCC-EX.", "on-on:
Cada decoder tiene un puerto rojo y otro verde. Al hacer click en el accesorio, se activa y desactiva uno u otro de los puertos.

al pulsar:
Al hacer clic en el accesorio, se activa el puerto seleccionado y al volver a soltar se desactiva el puerto.

on-off:
Al hacer clic en el accesorio se activa el puerto seleccionado y al pulsar por segunda vez se desactiva.
Algunos controls y decoders no pueden manejar esto, especialmente Z21, EcOS y DCC-EX." }, /* TextConnectionRefused */ { "Connection to {0}:{1} refused", "Verbindung zu {0}:{1} zurückgewiesen", "Falló intento de conectar a {0}:{1}" }, /* TextConnectionReset */ { "Connection reset", "Verbindung zurückgesetzt", "Conexión reajustado" }, /* TextControl */ { "Control", "Zentrale", "Control" }, /* TextControlDeleted */ { "Control {0} deleted", "Zentrale {0} gelöscht", "Control {0} eliminado" }, /* TextControlDoesNotAnswer */ { "Control does not answer", "Zentrale antwortet nicht", "Control no respuesta" }, /* TextControlDoesNotExist */ { "Control does not exist", "Zentrale existiert nicht", "Control no existe" }, /* TextControlReturnedBadParameter */ { "Control returned bad parameter", "Zentrale sendete falscher Parameter", "Control respondió malo parametro" }, /* TextControlReturnedError */ { "Control returned error: {0}", "Zentrale meldete Fehler: {0}", "Control contesta error: {0}" }, /* TextControlReturnedOnHalt */ { "Control returned on HALT", "Zentrale sendete auf HALT", "Control respondió en HALT" }, /* TextControlReturnedPowerOff */ { "Control returned Power Off", "Zentrale sendete Strom aus", "Control respondió codico booster apagado" }, /* TextControlReturnedQueueFull */ { "Control returned queue full", "Zentrale sendete Warteschlange voll", "Control respondió cola lleno" }, /* TextControlReturnedQueueNearlyFull */ { "Control returned queue nearly full", "Zentrale sendete Warteschlange fast voll", "Control respondió cola casi lleno" }, /* TextControlReturnedUnknownErrorCode */ { "Control returned unknown error code: {0}", "Zentrale sendete unbekannten Fehlercode: {0}", "Control respondió codico error disconocido: {0}" }, /* TextControlSaved */ { "Control {0} saved", "Zentrale {0} gespeichert", "Control {0} guardado" }, /* TextControls */ { "Controls", "Zentralen", "Controls" }, /* TextCopyingFromTo */ { "Copying from {0} to {1}", "Kopiere von {0} nach {1}", "Copiando de {0} a {1}" }, /* TextCrcMissmatch */ { "CRC missmatch: {0} / {1}", "CRC Fehler: {0} / {1}", "Error CRC: {0} / {1}" }, /* TextCreatingTable */ { "Creating table {0}", "Erstelle Tabelle {0}", "Creando table {0}" }, /* TextCreepAt */ { "Creep at", "Schleichgeschwindigkeit bei", "Velocidad a rastras a" }, /* TextCreepingSpeed */ { "Creeping speed", "Kriech Geschwindigkeit", "Velocidad a rastras" }, /* TextCrossingLeft */ { "Crossing left", "Kreuzung links", "Intersección izquierda" }, /* TextCrossingRight */ { "Crossing right", "Kreuzung rechts", "Intersección derecha" }, /* TextCrossingSymetric */ { "Crossing symetric", "Kreuzung symetrisch", "Intersección simétrico" }, /* TextCs2MainFound */ { "Main CS2 found on CAN bus", "CS2 Hauptgerät gefunden auf dem CAN bus", "Encontrado CS2 principal en el CAN bus" }, /* TextCs2MainLocoFunctionIconType */ { "Main CS2 has locomotive with function {0} with icon {1} of type {2}", "CS2 Hauptgerät hat eine Lokomotive mit Funktion {0} mit Bild {1} vom Typ {2}", "CS2 principal tiene una locomotora con la función {0} con el imagen {1} de tipo {2}" }, /* TextCs2MainLocoFunctionIconTypeTimer */ { "Main CS2 has locomotive with function {0} with icon {1} of type timer of {2} seconds", "CS2 Hauptgerät hat eine Lokomotive mit Funktion {0} mit Bild {1} vom Typ timer mit {2} Sekunden", "CS2 principal tiene una locomotora con la función {0} con el imagen {1} de tipo timer con {2} secundos" }, /* TextCs2MainLocoName */ { "Main CS2 has locomotive with name {0}", "CS2 Hauptgerät hat eine Lokomotive mit dem Namen {0}", "CS2 principal tiene una locomotora con el nombre {0}" }, /* TextCs2MainLocoOldName */ { "Main CS2 has locomotive with former name {0}", "CS2 Hauptgerät hat eine Lokomotive mit dem bisherigen Namen {0}", "CS2 principal tiene una locomotora con el nombre antiguo {0}" }, /* TextCs2MainLocoProtocolAddress */ { "Main CS2 has locomotive with address {0}/{1}", "CS2 Hauptgerät hat eine Lokomotive mit der Adresse {0}/{1}", "CS2 principal tiene una locomotora con la dirección {0}/{1}" }, /* TextCs2MainLocoRemove */ { "Main CS2 has removed locomotive with name {0}", "CS2 Hauptgerät hat eine Lokomotive mit dem Namen {0} gelöscht", "CS2 principal ha eliminado la locomotora con el nombre {0}" }, /* TextCs2MainLocoSlaveAddressProtocol */ { "Main CS2 has locomotive with a slave locomotive with address {0}/{1}", "CS2 Hauptgerät hat eine Lokomotive mit einer Slave Lokomotive mit der Adresse {0}/{1}", "CS2 principal tiene una locomotora con una locomotora slave con la dirección {0}/{1}" }, /* TextCs2MainLocoSlaveName */ { "Main CS2 has locomotive with a slave locomotive with name {0}", "CS2 Hauptgerät hat eine Lokomotive mit einer Slave Lokomotive mit Namen {0}", "CS2 principal tiene una locomotora con una locomotora slave con nombre {0}" }, /* TextCs2MinorVersionIsUnknown */ { "Minor version of received file unknown", "Minor Version des erhaltenen files ist nicht bekannt", "La versión menor del archivo recibido no está conocido" }, /* TextDcc */ { "DCC", "DCC", "DCC" }, /* TextDeBlock */ { "German Block", "Deutsches Sperrsignal", "Alemania bloqueo" }, /* TextDeCombined */ { "German Ks", "Deutsches Ks", "Alemania Ks" }, /* TextDeHVMain */ { "German H/V Main", "Deutsches H/V Hauptsignal", "Alemania H/V principal" }, /* TextDebounceThreadStarted */ { "Debounce thread started", "Entprellthread gestartet", "Antirebote thread encendido" }, /* TextDebounceThreadTerminated */ { "Debounce thread terminated", "Entprellthread beendet", "Antirebote thread apagado" }, /* TextDebouncer */ { "Debouncer", "Entpreller", "Antirebote" }, /* TextDebug */ { "debug", "Entkäfern", "depurar" }, /* TextDefault */ { "default", "standard", "predeterminado" }, /* TextDefaultSwitchingDuration */ { "Default switching duration (ms)", "Standard Schaltzeit (ms)", "Duración de conmutación por defecto (ms)" }, /* TextDelete */ { "Delete", "Löschen", "Eliminar" }, /* TextDeleteAccessory */ { "Delete accessory", "Zubehörartikel löschen", "Eliminar accesorio" }, /* TextDeleteCluster */ { "Delete track cluster", "Gleisgruppe löschen", "Eliminar grupo de vías" }, /* TextDeleteControl */ { "Delete control", "Zentrale löschen", "Eliminar control" }, /* TextDeleteFeedback */ { "Delete feedback", "Rückmelder löschen", "Eliminar retroseñal" }, /* TextDeleteLayer */ { "Delete layer", "Schicht löschen", "Eliminar capa" }, /* TextDeleteLoco */ { "Delete locomotive", "Lokomotive löschen", "Eliminar locomotora" }, /* TextDeleteMultipleUnit */ { "Delete multiple unit", "Mehrfachtraktion löschen", "Eliminar unidad múltiple" }, /* TextDeleteRoute */ { "Delete route", "Fahrstrasse löschen", "Eliminar itinerario" }, /* TextDeleteSignal */ { "Delete signal", "Signal löschen", "Eliminar señal" }, /* TextDeleteSwitch */ { "Delete switch", "Weiche löschen", "Eliminar desvío" }, /* TextDeleteText */ { "Delete text", "Text löschen", "Eliminar texto" }, /* TextDeleteTrack */ { "Delete track", "Gleis löschen", "Eliminar vía" }, /* TextDestinationTrack */ { "Destination track", "Zielgleis", "Vía de destino" }, /* TextDeviceOnCanBus */ { "CAN-device {0} with hash {1}, device ID {2} and software version {3}.{4} found.", "CAN-Gerät {0} mit Hash {1}, Geräte-Kennung {2} und Softwareversion {3}.{4} gefunden.", "Encontrado dispositivo {0} con hash {1}, identificador {2} y version {3}.{4}." }, /* TextDeviceType */ { "Device type", "Gerätetyp", "Tipo de dispositivo" }, /* TextDifferentOrientations */ { "Locomotive and route {0} have different running directions", "Lokomotive und Fahrstrasse {0} haben verschiedene Fahrtrichtungen", "Tren e itinerario {0} tienen sentidos de la marcha differentes" }, /* TextDifferentPropulsions */ { "Locomotive and route {0} have different propulsion types", "Lokomotive und Fahrstrasse {0} haben verschiedene Antriebsformen", "Tren e itinerario {0} tienen ajustes de propulsión differentes" }, /* TextDifferentPushpullTypes */ { "Locomotive and route {0} have different push-pull types", "Lokomotive und Fahrstrasse {0} haben verschiedene Wendezugeinstellungen", "Tren e itinerario {0} tienen ajustes de push-pull differentes" }, /* TextDifferentTrainTypes */ { "Locomotive and route {0} have different train types", "Lokomotive und Fahrstrasse {0} haben verschiedene Zugtypen", "Tren e itinerario {0} tienen ajustes de tipos de trenes differentes" }, /* TextDirect */ { "Direct", "Direkt", "Directo" }, /* TextDisplayName */ { "Display name", "Angezeigter Name", "Nombre mostrado" }, /* TextDoNotCare */ { "Do not care", "Egal", "No importa" }, /* TextDroppingTable */ { "Dropping table {0}", "Lösche Tabelle {0}", "Eliminando tabla {0}" }, /* TextDuration */ { "Switching duration (ms)", "Schaltzeit (ms)", "Duración de conmutación (ms)" }, /* TextEdit */ { "Edit", "Bearbeiten", "Editar" }, /* TextEditAccessories */ { "Edit accessories", "Zubehörartikel bearbeiten", "Editar accesorios" }, /* TextEditAccessory */ { "Edit accessory", "Zubehörartikel bearbeiten", "Editar accesorio" }, /* TextEditClusters */ { "Edit track clusters", "Gleisgruppen bearbeiten", "Editar grupos de vías" }, /* TextEditControls */ { "Edit controls", "Zentralen bearbeiten", "Editar controlos" }, /* TextEditFeedback */ { "Edit feedback", "Rückmelder bearbeiten", "Editar retroseñal" }, /* TextEditFeedbacks */ { "Edit feedbacks", "Rückmelder bearbeiten", "Editar retroseñales" }, /* TextEditLayers */ { "Edit layers", "Schichten bearbeiten", "Editar capas" }, /* TextEditLocos */ { "Edit locomotives", "Lokomotiven bearbeiten", "Editar locomotoras" }, /* TextEditLocos */ { "Edit multiple units", "Mehrfachtraktion bearbeiten", "Editar unidades múltiples" }, /* TextEditRoute */ { "Edit route", "Fahrstrasse bearbeiten", "Editar itinerario" }, /* TextEditRoutes */ { "Edit routes", "Fahrstrassen bearbeiten", "Editar itinerarios" }, /* TextEditSettings */ { "Edit settings", "Einstellungen bearbeiten", "Editar opciones" }, /* TextEditSignal */ { "Edit signal", "Signal bearbeiten", "Editar señal" }, /* TextEditSignals */ { "Edit signals", "Signale bearbeiten", "Editar señales" }, /* TextEditSwitch */ { "Edit switch", "Weiche bearbeiten", "Editar desvío" }, /* TextEditSwitches */ { "Edit switches", "Weichen bearbeiten", "Editar desvíos" }, /* TextEditText */ { "Edit text", "Text bearbeiten", "Editar texto" }, /* TextEditTexts */ { "Edit texts", "Texte bearbeiten", "Editar textos" }, /* TextEditTrack */ { "Edit track", "Gleis bearbeiten", "Editar vía" }, /* TextEditTracks */ { "Edit tracks", "Gleise bearbeiten", "Editar vías" }, /* TextEnglish */ { "English", "Englisch", "Ingles" }, /* TextError */ { "error", "Fehler", "error" }, /* TextErrorReadingData */ { "Error reading data: {0}", "Fehler beim Daten lesen {0}", "Error leer data: {0}" }, /* TextExecuteAccessory */ { "Execute accessories", "Schalte Zubehörartikel", "Cambia los accesorios" }, /* TextExecuteRoute */ { "Execute route", "Fahrstrasse ausführen", "Ejecuta itinerario" }, /* TextExecutingRoute */ { "Executing route {0}", "Führe Fahrstrasse {0} aus", "Ejecutando itinerario {0}" }, /* TextExitRailControl */ { "Exit RailControl", "RailControl beenden", "Apagar RailControl" }, /* TextFeedback */ { "feedback", "Rückmelder", "retroseñal" }, /* TextFeedbackChange */ { "State of pin {0} on S88 module {1} is {2}", "Status von Pin {0} an S88 Modul {1} ist {2}", "Estado de contacto {0} del S88 módulo {1} está {2}" }, /* TextFeedbackDeleted */ { "Feedback {0} deleted", "Rückmelder {0} gelöscht", "Retroseñal {0} eliminado" }, /* TextFeedbackDoesNotExist */ { "Feedback does not exist", "Rückmelder existiert nicht", "Retroseñal no existe" }, /* TextFeedbackIsUsedByTrack */ { "Feedback {0} is used by track {1}", "Rückmelder {0} wird gebraucht von Gleis {1}", "Retroseñal {0} está usado por vía {1}" }, /* TextFeedbackSaved */ { "Feedback {0} saved", "Rückmelder {0} gespeichert", "Retroseñal {0} guardado" }, /* TextFeedbackStateIsOff */ { "Feedback state of {0} is now off", "Der Status des Rückmelders {0} ist nun aus", "El estado de la retroseñal {0} está apagada" }, /* TextFeedbackStateIsOn */ { "Feedback state of {0} is now on", "Der Status des Rückmelders {0} ist nun ein", "El estado de la retroseñal {0} está encendida" }, /* TextFeedbackUpdated */ { "Feedback {0} updated", "Rückmelder {0} aktualisiert", "Retroseñal {0} actualizado" }, /* TextFeedbacks */ { "Feedbacks", "Rückmelder", "Retroseñales" }, /* TextFileNotFound */ { "File not found: {0}", "Datei nicht gefunden: {0}", "Archivo no encontrado {0}" }, /* TextFollowUpRoute */ { "Follow up route", "Folgefahrstrasse", "Itinerario sigiendo" }, /* TextFoundAccessoryInEcosDatabase */ { "Found accessory in ECoS database: Address: {0} Name: {1}/{2}/{3}", "Zubehörartikel in ECoS Datenbank gefunden: Adresse: {0} Name: {1}/{2}/{3}", "Encontrado un accessorio en la base de datos de ECoS: Dirección: {0} Nombre: {1}/{2}/{3}" }, /* TextFoundFeedbackModuleInEcosDatabase */ { "Found feedback module in ECoS database: ID: {0}", "Rückmeldemodule in ECoS Datenbank gefunden: ID: {0}", "Encontrado un modulo retroseñal en la base de datos de ECoS: ID: {0}" }, /* TextFoundLocoInEcosDatabase */ { "Found locomotive in ECoS database: Address: {0} Name: {1}", "Locomotive in ECoS Datenbank gefunden: Adresse: {0} Name: {1}", "Encontrado una locomotora en la base de datos de ECoS: Dirección: {0} Nombre: {1}" }, /* TextFullScreen */ { "Full screen", "Vollbild", "Pantalla completa" }, /* TextFunctions */ { "Functions", "Funktionen", "Functiones" }, /* TextGerman */ { "German", "Deutsch", "Alemán" }, /* TextGitDate */ { "Last GIT commit date: {0}", "Letztes GIT Commit Datum: {0}", "Último GIT commit fetcha: {0}" }, /* TextGitDirty */ { "GIT working directory has {0} changed files since last commit.", "Im GIT Arbeitsverzeichnis wurden seit dem letzten commit {0} Dateien geändert.", "En el directorio de trabajo de GIT han cambiado {0} archivos deste el último commit." }, /* TextGitHash */ { "Last GIT commit hash: {0}", "Letzter GIT Commit Hash: {0}", "Último GIT commit hash: {0};" }, /* TextGreen */ { "green", "grün", "verde" }, /* TextHasAlreadyReservedRoute */ { "Has already reserved a route. Setting error state.", "Hat schon eine Fahrstrasse reserviert. Setze Fehlerstatus.", "Ya ha reservado un itinerario. Poniando estado error." }, /* TextHasNotReachedDestination */ { "Has not yet reached its destination. Going to manual mode when it reached its destination.", "Hat ihr Ziel noch nicht erreicht. Setze sie in Modus manuell wenn sie ihr Ziel erreicht hat", "No ha llegado a su destino. Poniando en modo manual cuando ha llegado a su destino." }, /* TextHeadingToVia */ { "Heading to {0} via {1}", "Fahre via {1} nach {0}", "Circulando por {1} a {0}" }, /* TextHeadingToViaVia */ { "Heading to {0} via {1} and {2}", "Fahre via {1} und {2} nach {0}", "Circulando por {1} y {2} a {0}" }, /* TextHeartBeatThreadStarted */ { "Heartbeat thread started", "Heartbeat-Thread gestartet", "Thread heartbeat creado" }, /* TextHeightIs0 */ { "Height is zero", "Höhe ist null", "Altura está zero" }, /* TextHint */ { "Hint:", "Hinweis:", "Nota:" }, /* TextHintCcSchnitte */ { "Under Linux the virtual serial port is usually /dev/ttyUSB0.", "Unter Linux ist der erstellte virtuelle COM-Port üblicherweise /dev/ttyUSB0.", "Sobre Linux el puerto virtual normalmente es /dev/ttyUSB0." }, /* TextHintCs2Tcp */ { "If you have an outdated CS2 software you must use CS2/CS3 UDP.", "Wenn die CS2 software veraltet ist muss stattdessen CS2/CS3 UDP verwendet werden.", "Si la software de la CS2 no está reciente, se tiene que usar CS2/CS3 UDP." }, /* TextHintCs2Udp */ { "Try CS2/CS3 TCP (new protocol) first. Use the CS2/CS3 UDP only, if you have an outdated CS2 software version.
To connect to a Märklin Central Station 2 or 3, the firewall has to allow UDP-connections to the remote port 15731 and from the remote port 15730.
The CAN settings have to be "broadcast" and the IP of the RailControl server.", "Vorzugsweise wird das CS2/CS3 TCP Protokoll benutzt. CS2/CS3 UDP sollte nur verwendet werden, wenn die CS2 Software nicht mehr aktuell ist.
Um die Märklin Central Station 2 oder 3 mit RailControl zu verbinden, muss die Firewall UDP-Verbindungen zum Remote Port 15731, sowie UDP-Verbindungen zum lokalen Port 15730 zulassen.
Die CAN Einstellungen müssen auf "broadcast" und die IP auf die Adresse vom RailControl server gestellt werden.", "Está mejor utilzar CS2/CS3 TCP. Solamente use CS2/CS3 UDP si el software de CS2 no está reciente.
Para conectar a una Märklin Central Station 2 o 3, el cortafuegos tiene que permitir UDP-conexiones al puerto remoto 15731 y del puerto remote 15730.
Los ajustes CAN tienen que ser "broadcast" y la IP tiene que ser la direcctión del servidor RailControl." }, /* TextHintDccPpExSerial */ { "Under Linux the virtual serial port is usually /dev/ttyACM0.", "Unter Linux ist der erstellte virtuelle COM-Port üblicherweise /dev/ttyACM0.", "Sobre Linux el puerto virtual normalmente es /dev/ttyACM0." }, /* TextHintDccPpExTcp */ { "Standard IP is 192.168.1.200.", "Standard IP ist 192.168.1.200.", "IP por defecto es 192.168.1.200." }, /* TextHintEcos */ { "Locomotives and accessories must be accessed with an internal address. The easiest is to import the locomotives and accessories to get the correct address.", "Lokomotiven und Zubehörartikel werden mit einer internen Adresse angesprochen. Am Einfachsten ist es, die Lokomotiven und Zubehörartikel zu importieren, um die korrekte Adresse zu erhalten.", "Se puede acceder las locomotoras y los accesorios con una dirección interna. Lo más facil es importar las locomotoras y los accesorios, para obtener la dirección correcto." }, /* TextHintHsi88 */ { "HSI-88-USB is not supported, only RS-232 version.
One has to count the S88 modules as 8-bit modules. 16-bit modules count as two modules.", "HSI-88-USB wird nicht unterstützt, nur die RS-232-Variante.
Die S88 Module müssen als 8-Bit Module gezählt werden. 16-Bit Module zählen entsprechend als zwei Module.", "HSI-88-USB no es soportado, solamente la versión RS-232.
Se tiene que contar los modulo S88 como 8-bit modulos. Cada Modulo S88 con 16 bits vale como dos modulos." }, /* TextHintIntellibox */ { "Under Linux the virtual serial port is usually /dev/ttyUSB0.
The Intellibox does not forward very short feedbacks. It is not recommended to use the feedbacks of the Intellibox for automatic train control.", "Unter Linux ist der erstellte virtuelle COM-Port üblicherweise /dev/ttyUSB0.
Die Intellibox verschluckt sehr kurzzeitige Rückmelder. Ein Automatikbetrieb mit den Rückmeldern von Intellibox ist deshalb nicht zu empfehlen.", "Sobre Linux el puerto virtual normalmente es /dev/ttyUSB0.
El Intellibox no puede procesar las retroseñales muy cortas. No es recomendada de utilisar las retroseñales de Intellibox para modo automatico." }, /* TextHintIntellibox2 */ { "Under Linux the virtual serial port is usually /dev/ttyUSB0.
Please set the Intellibox II to a communication speed of 115200 (default).", "Unter Linux ist der erstellte virtuelle COM-Port üblicherweise /dev/ttyUSB0.
Die Kommunikationsgeschwindigkeit ist in der Intellibox II auf 115200 zu setzen (standard).", "Sobre Linux el puerto virtual normalmente es /dev/ttyUSB0.
Se tiene que usar una velocidad de comunicación de 115200 en Intellibox II (defecto)." }, /* TextHintLocoNetAdapter63120 */ { "Under Linux the virtual serial port is usually /dev/ttyUSB0.
Please set the adapter to a communication speed of 115200 (default).", "Unter Linux ist der erstellte virtuelle COM-Port üblicherweise /dev/ttyUSB0.
Die Kommunikationsgeschwindigkeit ist im Adapter auf 115200 zu setzen (standard).", "Sobre Linux el puerto virtual normalmente es /dev/ttyUSB0.
Se tiene que usar una velocidad de comunicación de 115200 en el adaptador (defecto)." }, /* TextHintLocoNetAdapter63820 */ { "Under Linux the virtual serial port is usually /dev/ttyUSB0.
Please set the adapter to a communication speed of 115200 (default).", "Unter Linux ist der erstellte virtuelle COM-Port üblicherweise /dev/ttyUSB0.
Die Kommunikationsgeschwindigkeit ist im Adapter auf 115200 zu setzen (standard).", "Sobre Linux el puerto virtual normalmente es /dev/ttyUSB0.
Se tiene que usar una velocidad de comunicación de 115200 en el adaptador (defecto)." }, /* TextHintM6051 */ { "Locomotives that are selected by a Central Control/Control 80/80f can not be controlled by RailControl.
The Interface 6050/6051 does not forward very short feedbacks. It is not recommended to use the feedbacks of the Interface 6050/6051 for automatic train control.", "Lokomotiven die von einer Central Control/Control 80/80f ausgewählt sind können von RailControl nicht gesteuert werden.
Das Interface 6050/6051 verschluckt sehr kurzzeitige Rückmelder. Ein Automatikbetrieb mit den Rückmeldern des Interface 6050/6051 ist deshalb nicht zu empfehlen.", "RailControl no puede controlar Locomotoras que estan selectionado por un Central Control/Control 80/80f.
El Interface 6050/6051 no puede procesar las retroseñales muy cortas. No es recomendada de utilisar las retroseñales del Interface 6050/6051 para modo automatico." }, /* TextHintMasterControl */ { "Under Linux the virtual serial port is usually /dev/ttyUSB0.
The MasterControl does not forward very short feedbacks. It is not recommended to use the feedbacks of the MasterControl for automatic train control.", "Unter Linux ist der erstellte virtuelle COM-Port üblicherweise /dev/ttyUSB0.
Die MasterControl verschluckt sehr kurzzeitige Rückmelder. Ein Automatikbetrieb mit den Rückmeldern von MasterControl ist deshalb nicht zu empfehlen.", "Sobre Linux el puerto virtual normalmente es /dev/ttyUSB0.
El MasterControl no puede procesar las retroseñales muy cortas. No es recomendada de utilisar las retroseñales de MasterControl para modo automatico." }, /* TextHintMasterControl2 */ { "", "", "" }, /* TextHintOpenDcc */ { "Under Linux the virtual serial port is usually /dev/ttyUSB0.
The OpenDCC does not forward very short feedbacks. It is not recommended to use the feedbacks of the OpenDCC for automatic train control.", "Unter Linux ist der erstellte virtuelle COM-Port üblicherweise /dev/ttyUSB0.
OpenDCC verschluckt sehr kurzzeitige Rückmelder. Ein Automatikbetrieb mit den Rückmeldern von OpenDCC ist deshalb nicht zu empfehlen.", "Sobre Linux el puerto virtual normalmente es /dev/ttyUSB0.
El OpenDCC no puede procesar las retroseñales muy cortas. No es recomendada de utilisar las retroseñales de OpenDCC para modo automatico." }, /* TextHintPositionMove */ { "Elements can be moved by drag and drop while shift-, control or alt key is pressed (key depends on the used browser).", "Elemente können mit Drag und Drop verschoben werden, während die Shift-, Ctrl- oder Alt-Taste gedrückt wird (Die Taste ist abhängig vom verwendeten Browser).", "Se puede mover elementos con Drag and Drop, cuando la tecla de mayúsculas, control o alt esté pulsada (La tecla depende del navegador usado)." }, /* TextHintPositionRotate */ { "Elements can be moved by clicking on it while shift-, control or alt key is pressed (key depends on the used browser).", "Elemente können mit einem Klick gedreht werden, während die Shift-, Ctrl- oder Alt-Taste gedrückt wird (Die Taste ist abhängig vom verwendeten Browser).", "Se puede rotar elementos con un click, cuando la tecla de mayúsculas, control o alt esté pulsada (La tecla depende del navegador usado)." }, /* TextHintRedBox */ { "Under Linux the virtual serial port is usually /dev/ttyUSB0.
The RedBox does not forward very short feedbacks. It is not recommended to use the feedbacks of the RedBox for automatic train control.", "Unter Linux ist der erstellte virtuelle COM-Port üblicherweise /dev/ttyUSB0.
Die RedBox verschluckt sehr kurzzeitige Rückmelder. Ein Automatikbetrieb mit den Rückmeldern von RedBox ist deshalb nicht zu empfehlen.", "Sobre Linux el puerto virtual normalmente es /dev/ttyUSB0.
El RedBox no puede procesar las retroseñales muy cortas. No es recomendada de utilisar las retroseñales de RedBox para modo automatico." }, /* TextHintSystemControl7 */ { "Under Linux the virtual serial port is usually /dev/ttyUSB0.
Please set the System Control 7 to a communication speed of 115200 (default).", "Unter Linux ist der erstellte virtuelle COM-Port üblicherweise /dev/ttyUSB0.
Die Kommunikationsgeschwindigkeit ist in der System Control 7 auf 115200 zu setzen (standard).", "Sobre Linux el puerto virtual normalmente es /dev/ttyUSB0.
Se tiene que usar una velocidad de comunicación de 115200 en System Control 7 (defecto)." }, /* TextHintTwinCenter */ { "Under Linux the virtual serial port is usually /dev/ttyUSB0.
The TwinCenter does not forward very short feedbacks. It is not recommended to use the feedbacks of the TwinCenter for automatic train control.", "Unter Linux ist der erstellte virtuelle COM-Port üblicherweise /dev/ttyUSB0.
Das TwinCenter verschluckt sehr kurzzeitige Rückmelder. Ein Automatikbetrieb mit den Rückmeldern vom TwinCenter ist deshalb nicht zu empfehlen.", "Sobre Linux el puerto virtual normalmente es /dev/ttyUSB0.
El TwinCenter no puede procesar las retroseñales muy cortas. No es recomendada de utilisar las retroseñales de TwinCenter para modo automatico." }, /* TextHintVirtual */ { "The virtual control does not have a physical representation. It is for testing only.", "Die virtuelle Zentrale hat keine physische Repräsentation. Sie ist ausschliesslich für Tests.", "El control virtual no tiene representation physica. Es solamente para tests." }, /* TextHintZ21 */ { "To connect to a control with Z21 protocol, the firewall has to allow UDP-connections from and to the remote port 21105.", "Um eine Zentrale mit Z21-Protokoll mit RailControl zu verbinden, muss die Firewall UDP-Verbindungen von und zum remote Port 21105 zulassen.", "Para conectar a un control con protocolo Z21, el cortafuego tiene que permitir UDP-conexiones del y al puerto remoto 21105." }, /* TextHitOverrun */ { "Hit overrun feedback {0}", "Erreichte Überfahr-Rückmelder {0}", "Ha pasado a {0}" }, /* TextHsi88Configured */ { "{0} ({1}/{2}/{3}) S88 modules configured.", "{0} ({1}/{2}/{3}) S88 Module konfiguriert", "{0} ({1}/{2}/{3}) S88 módulos configurado" }, /* TextHsi88ErrorConfiguring */ { "Unable to configure HSI-88. HSI-88 returned {0} configured modules", "HSI-88 kann nicht konfiguriert werden. HSI-88 meldet {0} module", "Imposible configurar HSI-88. HSI-88 denuncia {0} módulos" }, /* TextHttpRequest */ { "HTTP request: {0} {1}", "HTTP Anfrage: {0} {1}", "HTTP solicitud: {0} {1}" }, /* TextIPAddress */ { "IP address", "IP Adresse", "Dirección IP" }, /* TextImport */ { "Import", "Importieren", "Importar" }, /* TextIndependentOfControl */ { "Independent of control", "Unabhängig der Zentrale", "Independiente del control" }, /* TextIndex */ { "Index", "Index", "Index" }, /* TextInfo */ { "info", "Informationen", "informaciones" }, /* TextInvalidControlID */ { "Invalid control ID {0}", "Ungültige Control ID {0}", "Control ID {0} no valido" }, /* TextInvalidDataReceived */ { "Invalid data received", "Ungültige Daten empfangen", "Datos recibidos no validos" }, /* TextInverted */ { "Inverted", "Invertiert", "Invertido" }, /* TextInverted2 */ { "inverted", "invertiert", "invertido" }, /* TextIsInAutomodeWithoutRouteTrack */ { "Is in automode without a route or track set. Setting error state.", "Ist im Automodus ohne Fahrstrasse oder Gleis. Setze Fehlerstatus.", "Está en modo auto sin itinerario o vía. Poniando estado error." }, /* TextIsInErrorState */ { "Is in error state", "Ist im Fehlerstatus", "Está en estado error" }, /* TextIsInInvalidAutomodeState */ { "Is running in invalid automode state {0} while {1} is reached. Setting error state.", "Ist in unerlaubten Automode Status {0} während {1} erreicht wurde. Setze Fehlerstatus.", "Está en estado ilegal {0} mientras llegando {1}. Poniando estado error." }, /* TextIsInManualState */ { "Is in manual state while automode is running. Setting error state.", "Ist im Status manuell während der Auto-Modus eingeschaltet ist. Setze Fehlerstatus.", "Está en estado manual durante modo auto. Poniando estado error." }, /* TextIsInTerminatedState */ { "Is in terminated state while automode is running. Setting error state.", "Ist im Status beendet während der Auto-Modus eingeschaltet ist. Setze Fehlerstatus.", "Está en estado terminado durante modo auto. Poniando estado error." }, /* TextIsLocked */ { "{0} is locked", "{0} ist gesperrt", "{0} está bloqueado" }, /* TextIsNotFree */ { "{0} is not free", "{0} ist nicht frei", "{0} no está libre" }, /* TextIsNotOnTrack */ { "Is not on a track. Switching to manual mode.", "Ist nicht auf einem Gleis. Wechsle in den Modus manuell.", "No está sobre una vía. Poniando en modo manual." }, /* TextIsNowInAutoMode */ { "Is now in automode", "Ist jetzt im Auto-Modus", "Está en modo auto" }, /* TextIsNowInManualMode */ { "Is now in manual mode", "Ist nun im Modus manuell", "Está en modo manual" }, /* TextIsOnOcupiedTrack */ { "Thinks it is on track {0} but there is {1}. Setting error state.", "Meint sie sei auf Gleis {0} aber dort ist {1}. Setze Fehlerstatus.", "Cree que está sobre vía {0} pero allá está {1}. Poniando estado error." }, /* TextIsRunningWaitingUntilDestination */ { "Is actually running, waiting until reached its destination", "Fährt noch, warte bis sie das Ziel erreicht hat.", "Todavía está en marcha, esperando hasta ha llegado el destino" }, /* TextIsUpToDate */ { "{0} is up to date", "{0} ist aktuell", "{0} es actual" }, /* TextLanguage */ { "Language", "Sprache", "Idioma" }, /* TextLayer1 */ { "Layer 1", "Schicht 1", "Capa 1" }, /* TextLayer1IsUndeletable */ { "Layer 1 is undeletable", "Schicht 1 ist unlöschbar", "Capa 1 no se puede eliminar" }, /* TextLayerDeleted */ { "Layer {0} deleted", "Schicht {0} gelöscht", "Capa {0} eliminado" }, /* TextLayerDoesNotExist */ { "Layer does not exist", "Schicht existiert nicht", "Capa no existe" }, /* TextLayerIsUsedByAccessory */ { "Layer {0} is used by accessory {1}", "Schicht {0} wird benutzt von Zubehörartikel {1}", "Capa {0} está usado por accesorio {1}" }, /* TextLayerIsUsedByFeedback */ { "Layer {0} is used by feedback {1}", "Schicht {0} wird benutzt von Rückmelder {1}", "Capa {0} está usado por retroseñal {1}" }, /* TextLayerIsUsedByRoute */ { "Layer {0} is used by route {1}", "Schicht {0} wird benutzt von Fahrstrasse {1}", "Capa {0} está usado por itinerario {1}" }, /* TextLayerIsUsedBySignal */ { "Layer {0} is used by signal {1}", "Schicht {0} wird benutzt von Signal {1}", "Capa {0} está usado por señal {1}" }, /* TextLayerIsUsedBySwitch */ { "Layer {0} is used by switch {1}", "Schicht {0} wird benutzt von Weiche {1}", "Capa {0} está usado por desvío {1}" }, /* TextLayerIsUsedByText */ { "Layer {0} is used by text {1}", "Schicht {0} wird benutzt von Text {1}", "Capa {0} está usado por texto {1}" }, /* TextLayerIsUsedByTrack */ { "Layer {0} is used by track {1}", "Schicht {0} wird benutzt von Gleis {1}", "Capa {0} está usado por vía {1}" }, /* TextLayerSaved */ { "Layer {0} saved", "Schicht {0} gespeichert", "Capa {0} guardado" }, /* TextLayerUpdated */ { "Layer {0} updated", "Schicht {0} aktualisiert", "Capa {0} actualizado" }, /* TextLayers */ { "Layers", "Schichten", "Capas" }, /* TextLeft */ { "left", "links", "izquierda" }, /* TextLength */ { "Length", "Länge", "Longitud" }, /* TextLink */ { "Link", "Link", "Enlace" }, /* TextLoadedAccessory */ { "Loaded accessory {0}: {1}", "Zubehörartikel {0} geladen: {1}", "Cargado accesorio {0}: {1}" }, /* TextLoadedCluster */ { "Loaded track cluster {0}: {1}", "Gleisgruppe {0} geladen: {1}", "Cargado grupo de vías {0}: {1}" }, /* TextLoadedControl */ { "Loaded control {0}: {1}", "Zentrale {0} geladen: {1}", "Cargado control {0}: {1}" }, /* TextLoadedFeedback */ { "Loaded feedback {0}: {1}", "Rückmelder {0} geladen: {1}", "Cargado retroseñal {0}: {1}" }, /* TextLoadedLayer */ { "Loaded layer {0}: {1}", "Schicht {0} geladen: {1}", "Cargado capa {0}: {1}" }, /* TextLoadedLoco */ { "Loaded locomotive {0}: {1}", "Lokomotive {0} geladen: {1}", "Cargado locomotora {0}: {1}" }, /* TextLoadedMultipleUnit */ { "Loaded multiple unit {0}: {1}", "Mehrfachtraktion {0} geladen: {1}", "Cargado unidad múltiple {0}: {1}" }, /* TextLoadedRoute */ { "Loaded route {0}: {1}", "Fahrstrasse {0} geladen: {1}", "Cargado itinerario {0}: {1}" }, /* TextLoadedSignal */ { "Loaded signal {0}: {1}", "Signal {0} geladen: {1}", "Cargado señal {0}: {1}" }, /* TextLoadedSwitch */ { "Loaded switch {0}: {1}", "Weiche {0} geladen: {1}", "Cargado desvío {0}: {1}" }, /* TextLoadedText */ { "Loaded text {0}: {1}", "Text {0} geladen: {1}", "Cargado texto {0}: {1}" }, /* TextLoadedTrack */ { "Loaded track {0}: {1}", "Gleis {0} geladen: {1}", "Cargado vía {0}: {1}" }, /* TextLoco */ { "Locomotive", "Lokomotive", "Locomotora" }, /* TextLocoAddressDccTooHigh */ { "Addresses higher than 10239 are not supported by DCC", "Adressen grösser als 10239 werden nicht unterstützt von DCC", "Direcciones más grandes que 10239 no están compatibles con DCC" }, /* TextLocoAddressMm1TooHigh */ { "Addresses higher than 80 are not supported by MM1", "Adressen grösser als 80 werden nicht unterstützt von MM1", "Direcciones más grandes que 80 no están compatibles con MM1" }, /* TextLocoAddressMm2TooHigh */ { "Addresses higher than 255 are not supported by MM2", "Adressen grösser als 255 werden nicht unterstützt von MM2", "Direcciones más grandes que 255 no están compatibles con MM2" }, /* TextLocoDeleted */ { "Locomotive {0} deleted", "Lokomotive {0} gelöscht", "Locomotora {0} eliminado" }, /* TextLocoDirectionOfTravelIsLeft */ { "Direction of travel of {0} is now left", "Die Fahrtrichtung von {0} ist links", "La dirección de viaje de {0} está izquierda" }, /* TextLocoDirectionOfTravelIsRight */ { "Direction of travel of {0} is now right", "Die Fahrtrichtung von {0} ist rechts", "La dirección de viaje de {0} está derecha" }, /* TextLocoDoesNotExist */ { "Locomotive does not exist", "Lokomotive existiert nicht", "Locomotora no existe" }, /* TextLocoFunctionIconAirPump */ { "Air pump", "Luftpumpe", "Bomba de aire" }, /* TextLocoFunctionIconBacklightForward */ { "Backlight", "Rücklicht", "Luz trasera" }, /* TextLocoFunctionIconBacklightReverse */ { "Backlight reverse", "Rücklicht vorn", "Luz trasera adelante" }, /* TextLocoFunctionIconBell */ { "Bell", "Glocke", "Campana" }, /* TextLocoFunctionIconBlinkingLight */ { "Blinking light", "Blinklicht", "Luz parpadeando" }, /* TextLocoFunctionIconBreak1 */ { "Break", "Bremsen", "Frenar" }, /* TextLocoFunctionIconBreak1 */ { "Breaking noise 1", "Bremsgeräusch 1", "Ruido de frenos 1" }, /* TextLocoFunctionIconBreak2 */ { "Breaking noise 2", "Bremsgeräusch 2", "Ruido de frenos 2" }, /* TextLocoFunctionIconBufferPush */ { "Buffer push", "Pufferstoss", "Aumento del buffer" }, /* TextLocoFunctionIconCabLight1 */ { "Cab light 1", "Führerstandsbeleuchtung 1", "Luz de cabina 1" }, /* TextLocoFunctionIconCabLight12 */ { "Cab light", "Führerstandsbeleuchtung", "Luz de cabina" }, /* TextLocoFunctionIconCabLight2 */ { "Cab light 2", "Führerstandsbeleuchtung 2", "Luz de cabina 2" }, /* TextLocoFunctionIconCloseDoor */ { "Close door", "Tür schliessen", "Cerrar la puerta" }, /* TextLocoFunctionIconCompressedAir */ { "Compressed air", "Pressluft", "Aire comprimido" }, /* TextLocoFunctionIconCoupler */ { "Coupler", "Kuppeln", "Acoplar" }, /* TextLocoFunctionIconCrane */ { "Crane", "Kran", "Grúa" }, /* TextLocoFunctionIconCraneHook */ { "Crane hook", "Kranhaken", "Gancho de la grúa" }, /* TextLocoFunctionIconCurve */ { "Curve noise", "Kurvengeräusch", "" }, /* TextLocoFunctionIconDefault */ { "Default", "Standard", "Estándar" }, /* TextLocoFunctionIconDown */ { "Down", "Runter", "Abajo" }, /* TextLocoFunctionIconDrainValve */ { "Drain valve", "Abschlammen", "Válvula de drenaje" }, /* TextLocoFunctionIconDriversDeskLight */ { "Drivers desk light", "Fahrpultbeleuchtung", "Luz de la mesa de control" }, /* TextLocoFunctionIconEngine1 */ { "Engine noise 1", "Motorgeräusch 1", "Ruido de motor 1" }, /* TextLocoFunctionIconEngine2 */ { "Engine noise 2", "Motorgeräusch 2", "Ruido de motor 2" }, /* TextLocoFunctionIconEngineLight */ { "Engine light", "Motorlicht", "Luz de motor" }, /* TextLocoFunctionIconFan */ { "Fan", "Lüfter", "Ventilador" }, /* TextLocoFunctionIconFan1 */ { "Fan noise 1", "Lüftergeräusch 1", "Ruido de ventilador 1" }, /* TextLocoFunctionIconFan2 */ { "Fan noise 2", "Lüftergeräusch 2", "Ruido de ventilador 2" }, /* TextLocoFunctionIconFan3 */ { "Fan noise 3", "Lüftergeräusch 3", "Ruido de ventilador 3" }, /* TextLocoFunctionIconFillDiesel */ { "Fill diesel", "Diesel auffüllen", "Llenar diesel" }, /* TextLocoFunctionIconFillGas */ { "Fill gas", "Gas auffüllen", "Llenar gas" }, /* TextLocoFunctionIconFillWater */ { "Fill water", "Wasser auffüllen", "Llenar agua" }, /* TextLocoFunctionIconFireBox */ { "Firebox", "Feuerbüchse", "Fogón" }, /* TextLocoFunctionIconGearBox */ { "Gear box", "Getriebe", "Engranaje" }, /* TextLocoFunctionIconGearDown */ { "Gear down", "Runterschalten", "Reducir la marcha" }, /* TextLocoFunctionIconGearUp */ { "Gear up", "Hochschalten", "Subir la marcha" }, /* TextLocoFunctionIconGenerator */ { "Generator", "Generator", "Generador" }, /* TextLocoFunctionIconHeadlightHighBeamForward */ { "Headlight high beam", "Scheinwerfer", "Faros" }, /* TextLocoFunctionIconHeadlightHighBeamReverse */ { "Headlight high beam reverse", "Scheinwerfer hinten", "Faros traseros" }, /* TextLocoFunctionIconHeadlightLowBeamForward */ { "Headlight lwo beam", "Abblendlicht", "Luz de cruce" }, /* TextLocoFunctionIconHeadlightLowBeamReverse */ { "Headlight lwo beam reverse", "Abblendlicht hinten", "Luz de cruce trasera" }, /* TextLocoFunctionIconHorn1 */ { "Horn 1", "Horn 1", "Trompa 1" }, /* TextLocoFunctionIconHorn2 */ { "Horn 2", "Horn 2", "Trompa 2" }, /* TextLocoFunctionIconInertia */ { "Inertia", "Trägheit", "Inercia" }, /* TextLocoFunctionIconInteriorLight1 */ { "Interior light 1", "Innenbeleuchtung 1", "Luz interior 1" }, /* TextLocoFunctionIconInteriorLight2 */ { "Interior light 2", "Innenbeleuchtung 2", "Luz interior 2" }, /* TextLocoFunctionIconLeft */ { "Left", "Links", "Izquierda" }, /* TextLocoFunctionIconLeftRight */ { "Left and right", "Links und Rechts", "Izquierda y derecha" }, /* TextLocoFunctionIconLight */ { "Light", "Licht", "Luz" }, /* TextLocoFunctionIconLocomotiveNumberIndicator */ { "Locomotive number indicator", "Lokomotivnummerindikator", "Indicador del número de la locomotora" }, /* TextLocoFunctionIconMagnet */ { "Magnet", "Magnet", "Imán" }, /* TextLocoFunctionIconMainSwitch */ { "Main switch", "Hauptschalter", "Interruptor principal" }, /* TextLocoFunctionIconMusic1 */ { "Music 1", "Musik 1", "Musica 1" }, /* TextLocoFunctionIconMusic2 */ { "Music 2", "Musik 2", "Musica 2" }, /* TextLocoFunctionIconNoBreak */ { "No break noise", "Kein Bremsgeräusch", "Ningún ruido de frenos" }, /* TextLocoFunctionIconNoSound */ { "No sound", "Kein Sound", "Ningun sonido" }, /* TextLocoFunctionIconOpenDoor */ { "Open door", "Tür öffnen", "Abrir la puerta" }, /* TextLocoFunctionIconPanto */ { "Raise pantograph", "Pantograf heben", "Elevar el pantógrafo" }, /* TextLocoFunctionIconPanto1 */ { "Raise pantograph 1", "Pantograf 1 heben", "Elevar el pantógrafo 1" }, /* TextLocoFunctionIconPanto12 */ { "Raise pantograph", "Pantograf heben", "Elevar el pantógrafo" }, /* TextLocoFunctionIconPanto2 */ { "Raise pantograph 2", "Pantograf 2 heben", "Elevar el pantógrafo 2" }, /* TextLocoFunctionIconRadio */ { "Radio", "Funk", "Radio" }, /* TextLocoFunctionIconRailJoint */ { "Rail joint", "Schienenstoss", "Junta de rieles" }, /* TextLocoFunctionIconReliefValve */ { "Relief valve", "Überdruckventil", "Válvula de sobrepresión" }, /* TextLocoFunctionIconRight */ { "Right", "Rechts", "Derecha" }, /* TextLocoFunctionIconRunning1 */ { "Driving noise 1", "Fahrgeräusch 1", "Ruido de conducción 1" }, /* TextLocoFunctionIconRunning2 */ { "Driving noise 2", "Fahrgeräusch 2", "Ruido de conducción 2" }, /* TextLocoFunctionIconSand */ { "Sand", "Sanden", "Espolvorear arena" }, /* TextLocoFunctionIconShakingRust */ { "Shaking rust", "Schüttelrost", "Agitar parrilla" }, /* TextLocoFunctionIconShovelCoal */ { "Shovel coal", "Kohle schaufeln", "Palear carbón" }, /* TextLocoFunctionIconShuntingLight */ { "Shunting light", "Rangierbeleuchtung", "Luz de maniobras" }, /* TextLocoFunctionIconShuntingMode */ { "Shunting mode", "Rangiergang", "Modo de maniobras" }, /* TextLocoFunctionIconSmokeGenerator */ { "Smoke generator", "Rauchgenerator", "Generador de humo" }, /* TextLocoFunctionIconSoundGeneral */ { "Sound", "Sound", "Sonido" }, /* TextLocoFunctionIconSoundLouder */ { "Sound louder", "Sound lauter", "Sonido más fuerte" }, /* TextLocoFunctionIconSoundLower */ { "Sound lower", "Sound lower", "Sonido más bajo" }, /* TextLocoFunctionIconSpeak */ { "Speak", "Sprechen", "Hablar" }, /* TextLocoFunctionIconStairsLight */ { "Stairs light", "Treppenlicht", "Luz de escalera" }, /* TextLocoFunctionIconStationAnnouncement1 */ { "Station announcement 1", "Stationsansage 1", "Anuncio de la estación 1" }, /* TextLocoFunctionIconStationAnnouncement2 */ { "Station announcement 2", "Stationsansage 2", "Anuncio de la estación 2" }, /* TextLocoFunctionIconStationAnnouncement3 */ { "Station announcement 3", "Stationsansage 3", "Anuncio de la estación 3" }, /* TextLocoFunctionIconSteamBlow */ { "Steam blow", "Dampfstoss", "Empuje de vapor" }, /* TextLocoFunctionIconSteamBlowOut */ { "Steam blow out", "Zylinder ausblasen", "Soplar el cilindro" }, /* TextLocoFunctionIconTableLight1 */ { "Table light 1", "Tischlicht 1", "Luz de mesa 1" }, /* TextLocoFunctionIconTableLight2 */ { "Table light 2", "Tischlicht 2", "Luz de mesa 2" }, /* TextLocoFunctionIconTableLight3 */ { "Table light 3", "Tischlicht 3", "Luz de mesa 3" }, /* TextLocoFunctionIconTelex1 */ { "Telex 1", "Telex 1", "Telex 1" }, /* TextLocoFunctionIconTelex12 */ { "Telex", "Telex", "Telex" }, /* TextLocoFunctionIconTelex2 */ { "Telex 2", "Telex 2", "Telex 2" }, /* TextLocoFunctionIconTrainDestinationIndicator */ { "Train destination indicator", "Zugzielanzeige", "Indicador de destino del tren" }, /* TextLocoFunctionIconTurn */ { "Turn", "Drehen", "Girar" }, /* TextLocoFunctionIconTurnLeft */ { "Turn left", "Links drehen", "Girar a la izquierda" }, /* TextLocoFunctionIconTurnRight */ { "Turn right", "Rechts drehen", "Girar a la derecha" }, /* TextLocoFunctionIconUp */ { "Up", "Hoch", "Arriba" }, /* TextLocoFunctionIconUpDown1 */ { "Up and down 1", "Hoch und runter 1", "Arriba y abajo 1" }, /* TextLocoFunctionIconUpDown2 */ { "Up and down 2", "Hoch und runter 2", "Arriba y abajo 2" }, /* TextLocoFunctionIconWaterPump */ { "Water pump", "Wasserpumpe", "Bomba de agua" }, /* TextLocoFunctionIconWhistle1 */ { "Conductor's whistle", "Schaffnerpfeiffe", "Silbato del conductor" }, /* TextLocoFunctionIconWhistle2 */ { "Whistle", "Pfeiffe", "Silbato" }, /* TextLocoFunctionIsOff */ { "F{1} of {0} is now off", "F{1} von {0} ist aus", "F{1} de {0} está apagado" }, /* TextLocoFunctionIsOn */ { "F{1} of {0} is now on", "F{1} von {0} ist an", "F{1} de {0} está encendido" }, /* TextLocoFunctionTypeFlashing */ { "Flashing", "Blinkend", "Parpadeante" }, /* TextLocoFunctionTypeMoment */ { "Moment function", "Momentfunktion", "Función momentánea" }, /* TextLocoFunctionTypeNone */ { "Deactivated", "Deaktiviert", "Desactivado" }, /* TextLocoFunctionTypePermanent */ { "Permanent function", "Dauerfunktion", "Función continua" }, /* TextLocoFunctionTypeTimer */ { "Time controlled", "Zeitgesteuert", "Controlado por tiempo" }, /* TextLocoHasReachedDestination */ { "{0} has reached destination track {1} via {2}", "{0} hat das Zielgleis {1} über {2} erreicht.", "{0} ha llegado a su vía destino {1} vía {2}" }, /* TextLocoIsInAutoMode */ { "{0} is in auto mode", "{0} ist im Automatik-Betrieb", "{0} está en el modo automático" }, /* TextLocoIsInManualMode */ { "{0} is in manual mode", "{0} ist im manuellen Betrieb", "{0} está en el modo manual" }, /* TextLocoIsInUse */ { "Locomotive {0} is in use", "Lokomotive {0} ist in Gebrauch", "Locomotora {0} está en uso" }, /* TextLocoIsOnTrack */ { "{0} is on track {1}", "{0} ist auf Gleis {1}", "{0} está sobre vía {1}" } , /* TextLocoIsReleased */ { "{0} is released", " {0} ist freigegeben", "{0} está desbloqueada" }, /* TextLocoSaved */ { "Locomotive {0} saved", "Lokomotive {0} gespeichert", "Locomotora {0} guardado" }, /* TextLocoSpeedIs */ { "Speed of {0} is now {1}", "Die Geschwindigkeit von {0} ist {1}", "La velocidad de {0} está {1}" }, /* TextLocoUpdated */ { "Locomotive {0} updated", "Lokomotive {0} aktualisiert", "Locomotora {0} actualizado" }, /* TextLocos */ { "Locomotives", "Lokomotiven", "Locomotoras" }, /* TextLogLevel */ { "Log level", "Log Level", "Nivel de registro" }, /* TextLongestUnused */ { "Longest unused", "Am längsten ungenutzt", "El más largo sin usar" }, /* TextLookingForDestination */ {"Looking for new destination starting from {0}", "Suche von {0} aus neues Ziel", "Buscando nuevo destino deste {0}" }, /* TextMaerklinLeft */ { "Märklin DSS left", "Märklin DKW links", "Märklin DCD izquierda" }, /* TextMaerklinMotorola */ { "Märklin Motorola", "Märklin Motorola", "Märklin Motorola" }, /* TextMaerklinRight */ { "Märklin DSS right", "Märklin DKW rechts", "Märklin DCD derecha" }, /* TextMainDevice */ { "Main device", "Hauptgerät", "Dispositivo principal" }, /* TextMainTrack */ { "Main track", "Hauptgleis", "Vía principal" }, /* TextMainTrackHint */ { "If a main track is selected, then this track behaves identically to the main track and is its extension", "Wenn ein Hauptgleis ausgewählt wird, dann verhält sich dieses Gleis identisch wie das Hauptgleis und ist dessen Verlängerung", "Si se selecciona una vía principal, esta vía se comporta de forma idéntica a la vía principal y es su extensión" }, /* TextManager */ { "Manager", "Manager", "Manager" }, /* TextMaxSpeed */ { "Maximum speed", "Maximale Geschwindigkeit", "Velocidad máxima" }, /* TextMaxTrainLength */ { "Maximal train length", "Maximale Zuglänge", "Longitud de tren maxima" }, /* TextMembers */ { "Members", "Teilnehmer", "Miembros" }, /* TextMethodNotImplemented */ { "Method {0} not implemented", "Methode {0} nicht implementiert", "Methodo {0} no implementado" }, /* TextMinTrackLength */ { "Minimal track length", "Kürzestes Gleis", "Vía más corta" }, /* TextMinTrainLength */ { "Minimal train length", "Minimale Zuglänge", "Longitud de tren minima" }, /* TextMultipleUnit */ { "Multiple unit", "Mehrfachtraktion", "Unidad múltiple" }, /* TextMultipleUnitDeleted */ { "Multiple unit {0} deleted", "Mehrfachtraktion {0} gelöscht", "Unidad múltiple {0} eliminado" }, /* TextMultipleUnitDoesNotExist */ { "Multiple unit does not exist", "Mehrfachtraktion existiert nicht", "Unidad múltiple no existe" }, /* TextMultipleUnitIsInUse */ { "Multiple unit {0} is in use", "Mehrfrachtraktion {0} ist in Gebrauch", "Unidad múltiple {0} está en uso" }, /* TextMultipleUnitSaved */ { "Multiple unit {0} saved", "Mehrfachtraktion {0} gespeichert", "Unidad múltiple {0} guardado" }, /* TextMultipleUnits */ { "Multiple units", "Mehrfachtraktionen", "Unidades múltiples" }, /* TextMyUidHash */ { "My UID: {0}; My CAN hash: {1}", "Meine UID: {0}; Mein CAN hash: {1}", "Mi UID: {0}; Mi CAN hash: {1}" }, /* TextName */ { "Name", "Name", "Nombre" }, /* TextNameInControl */ { "Name in control's database", "Name in der Datenbank der Zentrale", "Nombre en el control" }, /* TextNetworkUnreachable */ { "Network of {0}:{1} unreachable", "Netzwerk von {0}:{1} nicht erreichbar", "Red de {0}:{1} inaccesible" }, /* TextNew */ { "New", "Neu", "Nuevo" }, /* TextNoAnswerFromDecoder */ { "No answer from decoder", "Keine Antwort vom Decoder", "Ninguna respuesta del decoder" }, /* TextNoControlSupportsProgramming */ { "No control supports decoder programming", "Keine Zentrale unterstützt die Decoder-Programmierung", "Ningun control está soportando la programación de decoders" }, /* TextNoPushPull */ { "no push-pull trains", "keine Wendezüge", "ningunos push-pull trenes" }, /* TextNoRotation */ { "No rotation", "Keine Drehung", "Ninguna rotación" }, /* TextNoS88Modules */ { "No S88 modules configured", "Keine S88 Module konfiguriert", "No hay módulos S88 configurado" }, /* TextNoValidRouteFound */ { "No valid route found", "Keine Fahrstrasse gefunden", "Ningun itinerario encontrado" }, /* TextNone */ { "none", "keine", "ningun" }, /* TextNotImplemented */ { "Not implemented at {0}:{1}", "Nicht implementiert bei {0}:{1}", "No implementado a {0}:{1}" }, /* TextNrOfFunctions */ { "Number of functions", "Anzahl der Funktionen", "Número de funciones" }, /* TextNrOfS88Modules */ { "# of S88 Modules (8 port)", "Anzahl S88 Module (8 Pin)", "Número de módulos S88 (8 contactos)" }, /* TextNrOfS88ModulesConfigured */ { "{0} S88 Modules (8 port)", "{0} S88 Module (8 Pin)", "{0} módulos S88 (8 contactos)" }, /* TextNrOfS88ModulesOnBus */ { "{0} S88 Modules (8 port) on bus {1}", "{0} S88 Module (8 Pin) an Bus {1}", "{0} módulos S88 (8 contactos) en bus {1}" }, /* TextNrOfTracksToReserve */ { "# of Tracks to reserve", "Anzahl der zu reservierenden Gleise", "Reservar numero de vías" }, /* TextObjectIsUsedByRoute */ { "Object {0} is used by route {1}", "Objekt {0} wird von Fahrstrasse {1} benutzt", "Objeto {0} está utilizado por itinerario {1}" }, /* TextOff */ { "off", "aus", "apagado" }, /* TextOn */ { "on", "ein", "encendido" }, /* TextOnPush */ { "on push", "beim Drücken", "al presionar" }, /* TextOpeningSQLite */ { "Opening SQLite database with filename {0}", "Öffne SQLite Datenbank mit Dateiname {0}", "Abriendo base de datos SQLite con nombre {0}" }, /* TextOrientation */ { "Orientation", "Ausrichtung", "Orientación" }, /* TextOverrunAt */ { "Overrun at", "Überfahrt bei", "Pasar a" }, /* TextParameterFoundInConfigFile */ { "Parameter found in config file: {0} = {1}", "Parameter gefunden in Konfigurationsdate: {0} = {1}", "Parametro encontrado en fila de configuración: {0} = {1}" }, /* TextPause */ { "Pause", "Pause", "Pausa" }, /* TextPin */ { "Pin", "Anschluss", "Contacto" }, /* TextPingSenderStarted */ { "Ping sender started", "Ping sender gestartet", "Ping enviador creado" }, /* TextPleaseSelectLoco */ { "Please select a locomotive", "Bitte eine Lokomotive wählen", "Por favor selecciona una locomotora" }, /* TextPosX */ { "Position X", "Position X", "Posición X" }, /* TextPosY */ { "Position Y", "Position Y", "Posición Y" }, /* TextPosZ */ { "Layer", "Schicht", "Capa" }, /* TextPosition */ { "Position", "Position", "Posición" }, /* TextPositionAlreadyInUse */ { "Position {0}/{1}/{2} is already used by {3} \"{4}\".", "Position {0}/{1}/{2} wird bereits verwendet von {3} \"{4}\".", "Positión {0}/{1}/{2} es usado por {3} \"{4}\"." }, /* TextProgramDccDirectRead */ { "Reading DCC CV {0} on programming track in direct mode", "Lese DCC CV {0} auf dem Programmiergleis im Direct Mode", "Leyendo DCC CV {0} en la vía de programación en modo directo" }, /* TextProgramDccDirectWrite */ { "Programming DCC CV {0} to value {1} on programming track in direct mode", "Programmiere DCC CV {0} auf Wert {1} auf dem Programmiergleis im Direct Mode", "Programando DCC CV {0} al valor {1} en modo directo" }, /* TextProgramDccPageRead */ { "Reading DCC CV {0} on programming track in page mode", "Lese DCC CV {0} auf dem Programmiergleis im Page Mode", "Leyendo DCC CV {0} en la vía de programación en modo pagina" }, /* TextProgramDccPageWrite */ { "Programming DCC CV {0} to value {1} on programming track in page mode", "Programmiere DCC CV {0} auf Wert {1} auf dem Programmiergleis im Page Mode", "Programando DCC CV {0} al valor {1} en la vía de programación en modo pagina" }, /* TextProgramDccPomAccessoryRead */ { "Reading DCC CV {1} of accessory with address {0} on main", "Lese DCC CV {1} des Zubehörartikels mit Adresse {0} auf dem Hauptgleis", "Leyendo DCC CV {1} del acessorio con dirección {0} en vía principal" }, /* TextProgramDccPomAccessoryWrite */ { "Programming DCC CV {1} of accessory with address {0} on main to value {2}", "Programmiere DCC CV {1} auf Wert {2} des Zubehörartikels mit Adresse {0} auf dem Hauptgleis", "Escribiendo DCC CV {1} al valor {2} del acessorio con dirección {0} en vía principal" }, /* TextProgramDccPomLocoRead */ { "Reading DCC CV {1} of locomotive with address {0} on main", "Lese DCC CV {1} der Lokomotive mit Adresse {0} auf dem Hauptgleis", "Leyendo DCC CV {1} de la locomotora con dirección {0} en vía principal" }, /* TextProgramDccPomLocoWrite */ { "Programming DCC CV {1} to value {2} of locomotive with address {0} on main", "Programmiere DCC CV {1} auf Wert {2} der Lokomotive mit Adresse {0} auf dem Hauptgleis", "Escribiendo DCC CV {1} al valor {2} de la locomotora con dirección {0} en vía principal" }, /* TextProgramDccRegisterRead */ { "Reading DCC CV {0} on programming track in register mode", "Lese DCC CV {0} auf dem Programmiergleis im Register Mode", "Leyendo DCC CV {0} en la vía de programación en modo registro" }, /* TextProgramDccRegisterWrite */ { "Programming DCC CV {0} to value {1} on programming track in register mode", "Programmiere DCC CV {0} auf Wert {1} auf dem Programmiergleis im Register Mode", "Programando DCC CV {0} al valor {1} en la vía de programación en modo registro" }, /* TextProgramMfxRead */ { "Reading mfx CV {1} of locomotive with address {0}", "Lese mfx CV {1} der Lokomotive mit Adresse {0}", "Leyendo mfx CV {0} de la locomotora con dirección {0}" }, /* TextProgramMfxWrite */ { "Writing mfx CV {1} to value {2} of locomotive with address {0}", "Schreibe mfx CV {1} auf Wert {2} der Lokomotive mit Adresse {0}", "Escribiendo mfx CV {0} al valor {2} de la locomotora con dirección {0} en la vía de programación" }, /* TextProgramMm */ { "Programming Märklin Motorola variable {0} to value {1} on programming track", "Programmiere Märklin Motorola Variable {0} auf Wert {1} auf dem Programmiergleis", "Programando Märklin Motorol variable {0} al valor {1} en la vía de programación" }, /* TextProgramMmPom */ { "Programming Märklin Motorola variable {1} to value {2} of locomotive with address {0} on main", "Programmiere Märklin Motorola Variable {1} auf Wert {2} der Lokomotive mit Adresse {0} auf dem Hauptgleis", "Programando Märklin Motorol variable {1} al valor {2} de la locomotora con dirección {0} en vía principal" }, /* TextProgramMode */ { "Mode", "Modus", "Modo" }, /* TextProgramModeDccDirect */ { "DCC direct (program track, all decoders)", "DCC direkt (Programmiergleis, alle Decoder)", "DCC directo (vía de programación, todos los decoders)" }, /* TextProgramModeDccPage */ { "DCC page (program track, all decoders)", "DCC Seite (Programmiergleis, alle Decoder)", "DCC pagina (vía de programación, todos los decoders)" }, /* TextProgramModeDccPomAccessory */ { "DCC POM accessory (main track)", "DCC POM Zubehörartikel (Hauptgleis)", "DCC POM accesorio (vía principal)" }, /* TextProgramModeDccPomLoco */ { "DCC POM locomotive (main track)", "DCC POM Lokomotive (Hauptgleis)", "DCC POM locomotora (vía principal)" }, /* TextProgramModeDccRegister */ { "DCC register (program track, all decoders)", "DCC Register (Programmiergleis, alle Decoder)", "DCC registro (vía de programación, todos los decoders)" }, /* TextProgramModeMfx */ { "Märklin mfx (program and main track)", "Märklin mfx (Programmier- und Hauptgleis)", "Märklin mfx (vía de programación y principal)" }, /* TextProgramModeMm */ { "Märklin Motorola (program track, all decoders)", "Märklin Motorola (Programmiergleis, alle Decoder)", "Märklin Motorola (vía de programación, todos los decoders)" }, /* TextProgramModeMmPom */ { "Märklin Motorola POM (main track)", "Märklin Motorola POM (Hauptgleis)", "Märklin Motorola POM (vía principal)" }, /* TextProgramReadValue */ { "CV {0} has value {1}", "CV {0} hat den Wert {1}", "CV {0} tiene valor {1}" }, /* TextProgrammer */ { "Programmer", "Programmierer", "Programador" }, /* TextProgrammingMode */ { "Programming mode", "Programmiermodus", "Modo programador" }, /* TextPropulsion */ { "Propulsion", "Antrieb", "Propulsión" }, /* TextPropulsionAccu */ { "Accu", "Akku", "Acumulador" }, /* TextPropulsionAll */ { "All propulsions", "Alle Antriebe", "Todas las propulsiones" }, /* TextPropulsionDiesel */ { "Diesel", "Diesel", "Diesel" }, /* TextPropulsionElectric */ { "Electric", "Elektrisch", "Electrico" }, /* TextPropulsionGas */ { "Gas", "Gas", "Gas" }, /* TextPropulsionHydrogen */ { "Hydrogen", "Wasserstoff", "Hidrógeno" }, /* TextPropulsionOther */ { "Other propulsion", "Anderer Antrieb", "Otra propulsión" }, /* TextPropulsionSteam */ { "Steam", "Dampf", "Vapor" }, /* TextPropulsionUnknown */ { "Unknown propulsion", "Unbekannter Antrieb", "Propulsión desconocida" }, /* TextProtocol */ { "Protocol", "Protokoll", "Protocolo" }, /* TextProtocolNotSupported */ { "Protocol {0} is not supported by control. Please use one of: {1}", "Protokoll {0} wird nicht unterstützt von der Zentrale. Verwende eines aus {1}", "Protocolo {0} no está compatible con ese control. Utilisa uno de {1}" }, /* TextPushPullOnly */ { "push-pull trains only", "nur Wendezüge", "solamente push-pull trenes" }, /* TextPushPullTrain */ { "Push-Pull train", "Wendezug", "Tren push-pull" }, /* TextQuery */ { "Query: {0}", "Abfrage: {0}", "Consulta: {0}" }, /* TextQueryAffected */ { "Query: {0} Rows affected {1}", "Abfrage: {0} Geänderte Datensätze: {1}", "Consulta: {0} Líneas afectados: {1}" }, /* TextRailControlStarted */ { "RailControl started", "RailControl wurde gestartet", "RailControl encendido" }, /* TextRailControlUpdateAvailable */ { "A new version of RailControl is available", "Eine neue Version von RailControl ist verfügbar", "Una nueva versión de RailControl es disponible" }, /* TextRandom */ { "Random", "Zufall", "Aleatorio" }, /* TextReachedItsDestination */ { "Reached its destination", "Erreichte ihr Ziel", "Llegó a su destino" }, /* TextRead */ { "read", "lesen", "leer" }, /* TextReadingConfigFile */ { "Reading config file {0}", "Lese Konfigurationsdatei {0}", "Leyendo fila de configuración {0}" }, /* TextReceivedAccessoryCommand */ { "Received command for accessory {0}/{1}: {2}", "Zubehörartikelkommando empfangen {0}/{1}: {2}", "Recibido comando para accesorio {0}/{1}: {2}" }, /* TextReceivedDirectionCommand */ { "Received direction command for locomotive {0}/{1}: {2}", "Richtungskommando empfangen für Lokomotive {0}/{1}: {2}", "Recibido comando de direccion para locomotora {0}/{1}: {2}" }, /* TextReceivedFunctionCommand */ { "Received function command for locomotive {0}/{1} and function {2}: {3}", "Funktionskommando empfangen für Lokomotive {0}/{1} und Funktion {2}: {3}", "Recibido comando de funcciona para locomotora {0}/{1} y funcciona {2}: {3}" }, /* TextReceivedSignalKill */ { "Received a signal kill {0} times. Exiting without saving.", "Signal Kill {0} mal erhalten. Beende RailControl ohne zu speichern.", "Señal Kill recibido {0} veces. Apagando RailControl sin guardar." }, /* TextReceivedSpeedCommand */ { "Received speed command for locomotive {0}/{1}: {2}", "Geschwindigkeitskommando empfangen für Lokomotive {0}/{1}: {2}", "Recibido comando de velocidad para locomotora {0}/{1}: {2}" }, /* TextReceiverThreadStarted */ { "Receiver thread started", "Empfangs-Thread gestartet", "Thread recibiendo creado" }, /* TextRed */ { "red", "rot", "rojo" }, /* TextReducedSpeed */ { "Reduced speed", "Reduzierte Geschwindigkeit", "Velocidad reducido" }, /* TextReducedSpeedAt */ { "Reduce speed at", "Reduziere Geschwindigkeit bei", "Reducir velocidad a" }, /* TextRegister */ { "Register", "Register", "Registro" }, /* TextRelationTargetNotFound */ { "Relation target not found", "Ziel der Relation nicht gefunden", "Relación no encontrado" }, /* TextRelease */ { "Release", "Freigeben", "Liberar" }, /* TextReleaseAccessory */ { "Release accessory", "Zubehörartikel freigeben", "Liberar accesorio" }, /* TextReleaseLoco */ { "Release locomotive", "Lokomotive freigeben", "Liberar locomotora" }, /* TextReleaseRoute */ { "Release route", "Fahrstrasse auflösen", "Liberar itinerario" }, /* TextReleaseSignal */ { "Release signal", "Signal freigeben", "Liberar señal" }, /* TextReleaseSwitch */ { "Release switch", "Weiche freigeben", "Liberar desvío" }, /* TextReleaseTrack */ { "Release track", "Gleis freigeben", "Liberar vía" }, /* TextReleaseTrackAndLoco */ { "Release track and locomotive", "Gleis und Lokomotive freigeben", "Liberar vía y locomotora" }, /* TextReleaseWhenFree */ { "Release when free", "Freigeben wenn nicht besetzt", "Liberar si no está ocupado" }, /* TextReleasingLoco */ { "Releasing locomotive", "Lokomotive wird freigeben", "Liberando locomotora" }, /* TextReleasingMultipleUnit */ { "Releasing multiple unit", "Mehrfachtraktion wird freigeben", "Liberando unidad múltiple" }, /* TextRemoveBackupFile */ { "Removing backup file {0}", "Lösche Sicherungskopie {0}", "Eliminando copia de respaldo {0}" }, /* TextRenamingFromTo */ { "Renaming from {0} to {1}", "Benenne von {0} nach {1} um", "Renombrando de {0} a {1}" }, /* TextReservingRoute */ { "Reserving route {0}", "Reserviere Fahrstrasse {0}", "Reservando itinerario {0}" }, /* TextRestarting */ { "Restarting", "Neustart", "Reiniciando" }, /* TextRight */ { "right", "rechts", "derecha" }, /* TextRotation */ { "Rotation", "Drehung", "Rotación", }, /* TextRoute*/ { "route", "Fahrstrasse", "itinerario" }, /* TextRouteDeleted */ { "Route {0} deleted", "Fahrstrasse {0} gelöscht", "Itinerario {0} eliminado" }, /* TextRouteDoesNotExist */ { "Route does not exist", "Fahrstrasse existiert nicht", "Itinerario no existe" }, /* TextRouteIsInUse */ { "Route {0} is in use", "Fahrstrasse {0} ist in Gebrauch", "Itinerario {0} está en uso" }, /* TextRouteIsLocked */ { "Route {0} is locked", "Fahrstrasse {0} ist gesperrt", "Itinerario {0} está bloqueado" }, /* TextRouteIsReleased */ { "Route {0} is released", "Fahrstrasse {0} ist aufgelöst", "Itinerario {0} está desbloqueado" }, /* TextRouteIsUsedByRoute */ { "Route {0} is used by route {1}", "Fahrstrasse {0} wird von Fahrstrasse {1} benutzt", "Itinerario {0} está utilizado por itinerario {1}" }, /* TextRouteSaved */ { "Route {0} saved", "Fahrstrasse {0} gespeichert", "Itinerario {0} guardado" }, /* TextRouteUpdated */ { "Route {0} updated", "Fahrstrasse {0} aktualisiert", "Itinerario {0} actualizado" }, /* TextRoutes */ { "Routes", "Fahrstrassen", "Itinerarios" }, /* TextSQLiteErrorQuery */ { "SQLite error: {0} Query: {1}", "SQLite Fehler: {0} Query: {1}", "Error de SQLite: {0} Query: {1}" }, /* TextSaving */ { "Saving {0}", "Speichere {0}", "Guardando {0}" }, /* TextSecondaryDevice */ { "Secondary device", "Zweitgerät", "Dispositivo secundario" }, /* TextSelectAutomatically */ { "Select automatically", "Wähle automatisch", "Selectiona automaticamente" }, /* TextSelectLocoForTrack */ {"Select locomotive for track {0}", "Wähle Lokomotive für Gleis {0}", "Selectione locomotora para vía {0}" }, /* TextSelectRouteBy */ { "Select route by", "Wähle die Fahrstrasse nach", "Selecctionar itinerario por" }, /* TextSenderSocketCreated */ { "Sender socket created", "Sender socket erstellt", "Socket para enviar datos creado" }, /* TextSenderThreadStarted */ { "Sender thread started", "Sender Thread gestartet", "Thread enviador creado" }, /* TextSendingLocoInfo */ { "Sending LocoInfo with address {0}", "Sende LocoInfo mit Adresse {0}", "Enviando LocoInfo con dirección {0}" }, /* TextSendingTurnoutInfo */ { "Sending TurnoutInfo with address {0}", "Sende TurnoutInfo mit Adresse {0}", "Enviando TurnoutInfo con dirección {0}" }, /* TextSeparator */ { "
", "
", "
" }, /* TextSerialNumberIs */ { "Serialnumber is {0}", "Seriennummer ist {0}", "Número de serie es {0}" }, /* TextSerialPort */ { "Serial port", "Serieller Anschluss", "Puerto serie" }, /* TextServerAddress */ { "Z21 Server Address", "Z21 Server Adresse", "Dirección de servidor Z21" }, /* TextSetAllLocosToAutomode */ { "Set all locomotives to automode", "Versetze alle Lokomotiven in den Automatikmodus", "Poner todas las locomotora en modo automatico" }, /* TextSetAllLocosToManualMode */ { "Set all locomotives to manual mode", "Versetze alle Lokomotiven in den manuellen Modus", "Poner todas las locomotora en modo manual" }, /* TextSetLoco */ { "Set locomotive", "Setze Lokomotive", "Poner locomotora sobre vía" }, /* TextSettingAccessory */ { "Setting accessory {0} to {1}", "Setze Zubehörartikel {0} auf {1}", "Ajusto accesorio {0} a {1}" }, /* TextSettingAccessoryOnOff */ { "Setting accessory {0}/{1} to {2}", "Setze Zubehörartikel {0}/{1} auf {2}", "Ajusto accesorio {0}/{1} a {2}" }, /* TextSettingAccessoryWithProtocol */ { "Setting accessory {0}/{1}/{2} to {3}", "Setze Zubehörartikel {0}/{1}/{2} auf {3}", "Ajusto accesorio {0}/{1}/{2} a {3}" }, /* TextSettingDirectionOfTravel */ { "Changing direction of travel of locomotive {0}", "Wechsle die Fahrtrichtung der Lokomotive {0}", "Cambio la direccion de viaje de la locomotora {0}" }, /* TextSettingDirectionOfTravelWithProtocol */ { "Setting locomotive {0}/{1} to direction of travel {2}", "Setze Lokomotive {0}/{1} in Fahrtrichtung {2}", "Ajusto la direccion de viaje de la locomotora {0}/{1} a {2}" }, /* TextSettingFunction */ { "Setting function {0} of locomotive {1} to {2}", "Setze Funktion {0} der Lokomotive {1} auf {2}", "Ajusto la function {0} de la locomotora {1} a {2}" }, /* TextSettingFunctionWithProtocol */ { "Setting function {0} of locomotive {1}/{2} to {3}", "Setze Funktion {0} der Lokomotive {1}/{2} auf {3}", "Ajusto la function {0} de la locomotora {1}/{2} a {3}" }, /* TextSettingFunctions17_28 */ { "Setting functions 17-28 of locomotive {0} to {1} and {2}", "Setze Funktionen 17-28 der Lokomotive {0} auf {1} und {2}", "Ajusto las functionas 17-28 de la locomotora {0} a {1} y {2}" }, /* TextSettingFunctions1_8 */ { "Setting functions 1-8 of locomotive {0} to {1}", "Setze Funktionen 1-8 der Lokomotive {0} auf {1}", "Ajusto las functionas 1-8 de la locomotora {0} a {1}" }, /* TextSettingFunctions9_16 */ { "Setting functions 9-16 of locomotive {0} to {1}", "Setze Funktionen 9-16 der Lokomotive {0} auf {1}", "Ajusto las functionas 9-16 de la locomotora {0} a {1}" }, /* TextSettingOrientation */ { "Setting locomotive {0} orientation {1}", "Setze Richtung {1} der Lokomotive {0}", "Ajusto orientación a {1} de la locomotora {0}" }, /* TextSettingSpeed */ { "Setting locomotive {0} to speed {1}", "Setze Lokomotive {0} auf Geschwindigkeit {1}", "Ajusto la velocidad de la locomotora {0} a {1}" }, /* TextSettingSpeedOrientationLight */ { "Setting locomotive {0} to speed {1} and direction {2} and light {3}", "Setze Geschwindigkeit {1}, Richtung {2} und Licht {3} der Lokomotive {0}", "Ajusto velocidad a {1}, orientación {2} y luz {3} de la locomotora {0}" }, /* TextSettingSpeedWithProtocol */ { "Setting locomotive {0}/{1} to speed {2}", "Setze Lokomotive {0}/{1} auf Geschwindigkeit {2}", "Ajusto la velocidad de la locomotora {0}/{1} a {2}" }, /* TextSettings */ { "Settings", "Einstellungen", "Opciones" }, /* TextSettingsSaved */ { "Settings saved", "Einstellungen gespeichert", "Opciones guardado" }, /* TextSeveral */ { "several", "mehrere", "varios" }, /* TextShortCircuit */ { "Short circuit", "Kurzschluss", "Cortocircuit" }, /* TextShowName */ { "Show name", "Name sichtbar", "Nombre visible" }, /* TextShutdown */ { "Shutdown RailControl", "RailControl herunterfahren", "Apagar RailControl" }, /* TextShutdownRailControl */ { "Shutting down RailControl", "Beende RailControl", "Apagando RailControl" }, /* TextShutdownRequestedBySignal */ { "Shutting down RailControl requested by signal {0}", "Beenden von RailControl angefordert mit Signal {0}", "Apagar RailControl pedido con señal {0}" }, /* TextShutdownRequestedByWebClient */ { "Shutting down RailControl requested by webclient", "Beenden von RailControl angefordert von Webclient", "Apagar RailControl pedido del webclient" }, /* TextSignal*/ { "signal ", "Signal", "señal" }, /* TextSignalDeleted */ { "Signal {0} deleted", "Signal {0} gelöscht", "Señal {0} eliminado" }, /* TextSignalDoesNotExist */ { "Signal does not exist", "Signal existiert nicht", "Señal no existe" }, /* TextSignalHasAssociatedFeedback */ { "Signal {0} has associated feedback {1}", "Signal {0} hat verknüpften Rückmelder {1}", "Señal {0} tiene enlaze a retroseñal {1}" }, /* TextSignalIsLocked */ { "Signal {0} is locked", "Signal {0} ist gesperrt", "Señal {0} está bloqueado" }, /* TextSignalIsUsedByLoco */ { "Signal {0} is used by locomotive {1}", "Signal {0} wird von Lokomotive {1} benutzt", "Señal {0} está utilizado por locomotora {1}" }, /* TextSignalIsUsedByRoute */ { "Signal {0} is used by route {1}", "Signal {0} wird von Fahrstrasse {1} benutzt", "Señal {0} está utilizado por itinerario {1}" }, /* TextSignalIsUsedByTrack */ { "Signal {0} is used by track {1}", "Signal {0} wird von Gleis {1} benutzt", "Señal {0} está utilizado por vía {1}" }, /* TextSignalSaved */ { "Signal {0} saved", "Signale {0} gespeichert", "Señal {0} guardado" }, /* TextSignalStateCaution */ { "Caution", "Vorsicht", "Cuidado" }, /* TextSignalStateClear */ { "Clear", "Fahrt", "Vía libre" }, /* TextSignalStateClear100 */ { "Clear 100 km/h", "Fahrt 100 km/h", "Vía libre 100 km/h" }, /* TextSignalStateClear100Expected */ { "Clear 100 km/h expected", "Fahrt 100 km/h erwarten", "Vía libre 100 km/h esperado" }, /* TextSignalStateClear110 */ { "Clear 110 km/h", "Fahrt 110 km/h", "Vía libre 110 km/h" }, /* TextSignalStateClear110Expected */ { "Clear 110 km/h expected", "Fahrt 110 km/h erwarten", "Vía libre 110 km/h esperado" }, /* TextSignalStateClear120 */ { "Clear 120 km/h", "Fahrt 120 km/h", "Vía libre 120 km/h" }, /* TextSignalStateClear120Expected */ { "Clear 120 km/h expected", "Fahrt 120 km/h erwarten", "Vía libre 120 km/h esperado" }, /* TextSignalStateClear40 */ { "Clear 40 km/h", "Fahrt 40 km/h", "Vía libre 40 km/h" }, /* TextSignalStateClear40Expected */ { "Clear 40 km/h expected", "Fahrt 40 km/h erwarten", "Vía libre 40 km/h esperado" }, /* TextSignalStateClear50 */ { "Clear 50 km/h", "Fahrt 50 km/h", "Vía libre 50 km/h" }, /* TextSignalStateClear50Expected */ { "Clear 50 km/h expected", "Fahrt 50 km/h erwarten", "Vía libre 50 km/h esperado" }, /* TextSignalStateClear60 */ { "Clear 60 km/h", "Fahrt 60 km/h", "Vía libre 60 km/h" }, /* TextSignalStateClear60Expected */ { "Clear 60 km/h expected", "Fahrt 60 km/h erwarten", "Vía libre 60 km/h esperado" }, /* TextSignalStateClear70 */ { "Clear 70 km/h", "Fahrt 70 km/h", "Vía libre 70 km/h" }, /* TextSignalStateClear70Expected */ { "Clear 70 km/h expected", "Fahrt 70 km/h erwarten", "Vía libre 70 km/h esperado" }, /* TextSignalStateClear80 */ { "Clear 80 km/h", "Fahrt 80 km/h", "Vía libre 80 km/h" }, /* TextSignalStateClear80Expected */ { "Clear 80 km/h expected", "Fahrt 80 km/h erwarten", "Vía libre 80 km/h esperado" }, /* TextSignalStateClear90 */ { "Clear 90 km/h", "Fahrt 90 km/h", "Vía libre 90 km/h" }, /* TextSignalStateClear90Expected */ { "Clear 90 km/h expected", "Fahrt 90 km/h erwarten", "Vía libre 90 km/h esperado" }, /* TextSignalStateClearExpected */ { "Clear expected", "Fahrt erwarten", "Vía libre esperado" }, /* TextSignalStateDark */ { "Dark", "Dunkel", "Oscuro" }, /* TextSignalStateIsAspect10 */ { "Signal {0} shows aspect 10", "Signal {0} zeigt Fahrtbegriff 10", "Señal {0} muestra aspecto 10" }, /* TextSignalStateIsAspect10Expected */ { "Signal {0} shows aspect 10 expected", "Signal {0} zeigt Fahrtbegriff 10 erwarten", "Señal {0} muestra aspecto 10 esperado" }, /* TextSignalStateIsAspect2 */ { "Signal {0} shows aspect 2", "Signal {0} zeigt Fahrtbegriff 2", "Señal {0} muestra aspecto 2" }, /* TextSignalStateIsAspect2Expected */ { "Signal {0} shows aspect 2 expected", "Signal {0} zeigt Fahrtbegriff 2 erwarten", "Señal {0} muestra aspecto 2 esperado" }, /* TextSignalStateIsAspect3 */ { "Signal {0} shows aspect 3", "Signal {0} zeigt Fahrtbegriff 3", "Señal {0} muestra aspecto 3" }, /* TextSignalStateIsAspect3Expected */ { "Signal {0} shows aspect 3 expected", "Signal {0} zeigt Fahrtbegriff 3 erwarten", "Señal {0} muestra aspecto 3 esperado" }, /* TextSignalStateIsAspect4 */ { "Signal {0} shows aspect 4", "Signal {0} zeigt Fahrtbegriff 4", "Señal {0} muestra aspecto 4" }, /* TextSignalStateIsAspect4Expected */ { "Signal {0} shows aspect 4 expected", "Signal {0} zeigt Fahrtbegriff 4 erwarten", "Señal {0} muestra aspecto 4 esperado" }, /* TextSignalStateIsAspect5 */ { "Signal {0} shows aspect 5", "Signal {0} zeigt Fahrtbegriff 5", "Señal {0} muestra aspecto 5" }, /* TextSignalStateIsAspect5Expected */ { "Signal {0} shows aspect 5 expected", "Signal {0} zeigt Fahrtbegriff 5 erwarten", "Señal {0} muestra aspecto 5 esperado" }, /* TextSignalStateIsAspect6 */ { "Signal {0} shows aspect 6", "Signal {0} zeigt Fahrtbegriff 6", "Señal {0} muestra aspecto 6" }, /* TextSignalStateIsAspect6Expected */ { "Signal {0} shows aspect 6 expected", "Signal {0} zeigt Fahrtbegriff 6 erwarten", "Señal {0} muestra aspecto 6 esperado" }, /* TextSignalStateIsAspect7 */ { "Signal {0} shows aspect 7", "Signal {0} zeigt Fahrtbegriff 7", "Señal {0} muestra aspecto 7" }, /* TextSignalStateIsAspect7Expected */ { "Signal {0} shows aspect 7 expected", "Signal {0} zeigt Fahrtbegriff 7 erwarten", "Señal {0} muestra aspecto 7 esperado" }, /* TextSignalStateIsAspect8 */ { "Signal {0} shows aspect 8", "Signal {0} zeigt Fahrtbegriff 8", "Señal {0} muestra aspecto 8" }, /* TextSignalStateIsAspect8Expected */ { "Signal {0} shows aspect 8 expected", "Signal {0} zeigt Fahrtbegriff 8 erwarten", "Señal {0} muestra aspecto 8 esperado" }, /* TextSignalStateIsAspect9 */ { "Signal {0} shows aspect 9", "Signal {0} zeigt Fahrtbegriff 9", "Señal {0} muestra aspecto 9" }, /* TextSignalStateIsAspect9Expected */ { "Signal {0} shows aspect 9 expected", "Signal {0} zeigt Fahrtbegriff 9 erwarten", "Señal {0} muestra aspecto 9 esperado" }, /* TextSignalStateIsClear */ { "Signal {0} shows clear", "Signal {0} zeigt Fahrt", "Señal {0} muestra vía libre" }, /* TextSignalStateIsClearExpected */ { "Signal {0} shows clear expected", "Signal {0} zeigt Fahrt erwarten", "Señal {0} muestra vía libre esperado" }, /* TextSignalStateIsDark */ { "Signal {0} is dark", "Signal {0} ist dunkel", "Señal {0} está oscura" }, /* TextSignalStateIsStop */ { "Signal {0} shows stop", "Signal {0} zeigt Halt", "Señal {0} muestra parada" }, /* TextSignalStateIsStopExpected */ { "Signal {0} shows stop expected", "Signal {0} zeigt Halt erwarten", "Señal {0} muestra parada esperado" }, /* TextSignalStateShortClear */ { "Short Clear", "Kurze Fahrt", "Vía libre corta" }, /* TextSignalStateShunting */ { "Shunting", "Rangieren", "Maniobra" }, /* TextSignalStateSlow */ { "Slow", "Langsamfahrt", "Marcha lenta" }, /* TextSignalStateStop */ { "Stop", "Halt", "Parada" }, /* TextSignalStateStopExpected */ { "Stop expected", "Halt erwarten", "Parada esperado" }, /* TextSignalStateZs7 */ { "Caution Zs 7", "Vorsichtssignal Zs 7", "Cuidado Zs 7" }, /* TextSignalUpdated */ { "Signal {0} updated", "Signal {0} aktualisiert", "Señal {0} actualizado" }, /* TextSignals */ { "Signals", "Signale", "Señales" }, /* TextSimpleLeft */ { "simple left", "einfach links", "simple izquierda" }, /* TextSimpleRight */ { "simple right", "einfach rechts", "simple derecha" }, /* TextSlotHasAddress */ { "Slot {0} has address {1}", "Slot {0} hat Adresse {1}", "Slot {0} tiene dirección {1}" }, /* TextSpanish */ { "Spanish", "Spanisch", "Español" }, /* TextSpeed */ { "Speed", "Geschwindigkeit", "Velocidad" }, /* TextStartArgument */ { "Start argument: {0}", "Startargument: {0}", "Argumento de inicio: {0}" }, /* TextStartLocoAutomode */ { "Start locomotive in automode", "Starte Lokomotive im Automodus", "Poner locomotora en marcha en autómodo" }, /* TextStartTrack */ { "Start track", "Startgleis", "Vía de inicio" }, /* TextStarting */ { "Starting {0}", "Starte {0}", "Iniciendo {0}" }, /* TextStartup */ { "Startup", "Start", "Iniciar" }, /* TextStartupInitLocos */ { "Initialization of locos", "Initialisierung der Lokomotiven", "Inicialicación de las locomotoras" }, /* TextStartupInitLocosAll */ { "Speed and functions", "Geschwindigkeit und Funktionen", "Velocidad y funciones" }, /* TextStartupInitLocosNone */ { "No initialization", "Keine Initialisierung", "Sin inicialización" }, /* TextStartupInitLocosSpeed */ { "Speed only", "Nur Geschwindigkeit", "Sólo velocidad" }, /* TextStop */ { "stop", "anhalten", "parar" }, /* TextStopAllLocos */ { "Stop all locomotives", "Stoppe alle Lokomotiven", "Detener todas las locomotoras" }, /* TextStopAt */ { "Stop at", "Anhalten bei", "Parar a" }, /* TextStopLoco */ { "Stop locomotive", "Stoppe Lokomotive", "Parar locomotora" }, /* TextStopOnFeedbackInFreeTrack */ { "Turn off current on feedback in free track", "Schalte Strom aus bei Rückmelder in freiem Gleis", "Apagar la corriente en una retroseñal en una vía libre" }, /* TextStraight */ { "straight", "gerade", "recto" }, /* TextSwitch */ { "switch", "Weiche", "desvío" }, /* TextSwitchDeleted */ { "Switch {0} deleted", "Weiche {0} gelöscht", "Desvío {0} eliminado" }, /* TextSwitchDoesNotExist */ { "Switch does not exist", "Weiche existiert nicht", "Desvío no existe" }, /* TextSwitchIsLocked */ { "Switch {0} is locked", "Weiche {0} ist gesperrt", "Desvío {0} está bloqueado" }, /* TextSwitchIsUsedByRoute */ { "Switch {0} is used by route {1}", "Weiche {0} wird von Fahrstrasse {1} benutzt", "Desvío {0} está utilizado por itinerario {1}" }, /* TextSwitchSaved */ { "Switch {0} saved", "Weiche {0} gespeichert", "Desvío {0} guardado" }, /* TextSwitchStateIsStraight */ { "Switch {0} is straight", "Weiche {0} ist geradeaus", "Desvío {0} está directo" }, /* TextSwitchStateIsThird */ { "Switch {0} is third way", "Weiche {0} ist drittes Gleis", "Desvío {0} está tercer vía" }, /* TextSwitchStateIsTurnout */ { "Switch {0} is turnout", "Weiche {0} ist ablenkend", "Desvío {0} está desviado" }, /* TextSwitchStateLeft */ { "Left", "Links", "Izquierda" }, /* TextSwitchStateRight */ { "Right", "Rechts", "Derecha" }, /* TextSwitchStateStraight */ { "Straight", "Geradeaus", "Directo" }, /* TextSwitchUpdated */ { "Switch {0} updated", "Weiche {0} aktualisiert", "Desvío {0} actualizado" }, /* TextSwitches */ { "Switches", "Weichen", "Desvíos" }, /* TextSystemDefault */ { "System setting", "Systemeinstellung", "Configuración del sistema" }, /* TextTcpConnectionClosed */ { "TCP connection to {0} closed", "TCP Verbindung zu {0} geschlossen", "Connectión TCP a {0} cerrado" }, /* TextTcpConnectionEstablished */ { "TCP connection to {0} established", "TCP Verbindung zu {0} hergestellt", "Connectión TCP a {0} establecido" }, /* TextTerminatingAccessorySenderThread */ { "Terminating accessory sender thread", "Beende Zubehörartikel Sender Thread", "Apagando thread enviador accesorio" }, /* TextTerminatingHeartBeatThread */ { "Terminating heartbeat thread", "Beende Heartbeat-Thread", "Apagando thread heartbeat" }, /* TextTerminatingPingSender */ { "Terminating ping sender", "Beende Ping Sender", "apagando ping enviador" }, /* TextTerminatingReceiverThread */ { "Terminating receiver thread", "Beende Empfangs-Thread", "Apagando thread recibiendo" }, /* TextTerminatingSenderSocket */ { "Terminating sender socket", "Beende Sende Socket", "Apagando socket enviando" }, /* TextTerminatingSenderThread */ { "Terminating sender thread", "Beende Sende-Thread", "Apagando thread enviando" }, /* TextText */ { "text", "Text", "texto" }, /* TextTextDeleted */ { "Text {0} deleted", "Text {0} gelöscht", "Texto {0} eliminado" }, /* TextTextDoesNotExist */ { "Text does not exist", "Text existiert nicht", "texto no existe" }, /* TextTextSaved */ { "Text {0} saved", "Text {0} gespeichert", "Texto {0} guardado" }, /* TextTextUpdated */ { "Text {0} updated", "Text {0} aktualisiert", "Texto {0} actualizado" }, /* TextTexts */ { "Texts", "Texte", "Textos" }, /* TextThreeWay */ { "three way", "Dreiweg", "tres vías" }, /* TextTimestampAlreadySet */ { "Timestamp already set", "Zeit schon gesetzt", "Hora ya ajustado" }, /* TextTimestampNotSet */ { "Timestamp not set", "Zeit nicht gesetzt", "Hora no ajustado" }, /* TextTimestampSet */ { "Timestamp set", "Zeit gesetzt", "Hora ajustado" }, /* TextTooManyS88Modules */ { "Too many S88 modules configured: {0}. Max is {1}", "Zu viele S88 Module konfiguriert: {0}. Maximum ist {1}", "Demasiado módulos S88 configurado: {0}. El maximo es {1}" }, /* TextTrack */ { "track", "Gleis", "vía" }, /* TextTrackDeleted */ { "Track {0} deleted", "Gleis {0} gelöscht", "Vía {0} eliminado" }, /* TextTrackDoesNotExist */ { "Track does not exist", "Gleis existiert nicht", "Vía no existe" }, /* TextTrackIsUsedByLoco */ { "Track {0} is used by locomotive {1}", "Gleis {0} wird von Lokomotive {1} benutzt", "Vía {0} está utilizado por locomotora {1}" }, /* TextTrackIsUsedByRoute */ { "Track {0} is used by route {1}", "Gleis {0} wird von Fahrstrasse {1} benutzt", "Vía {0} está utilizado por itinerario {1}" }, /* TextTrackSaved */ { "Track {0} saved", "Gleis {0} gespeichert", "Vía {0} guardado" }, /* TextTrackStatusIsBlocked */ { "{0} is blocked", "{0} ist blockiert", "{0} está bloqueado" }, /* TextTrackStatusIsBlockedAndOccupied */ { "{0} is blocked and occupied", "{0} ist blockiert und besetzt", "{0} está bloqueado y ocupado" }, /* TextTrackStatusIsBlockedAndReserved */ { "{0} is blocked and reserved by {1}", "{0} ist blockiert und reserviert von {1}", "{0} está bloqueado y reservado por {1}" }, /* TextTrackStatusIsFree */ { "{0} is free", "{0} ist frei", "{0} está libre" }, /* TextTrackStatusIsOccupied */ { "{0} is occupied", "{0} ist besetzt", "{0} está ocupada" }, /* TextTrackStatusIsReserved */ { "{0} is reserved by {1}", "{0} ist reserviert von {1}", "{0} está reservada por {1}" }, /* TextTrackUpdated */ { "Track {0} updated", "Gleis {0} aktualisiert", "Vía {0} actualizado" }, /* TextTracks */ { "Tracks", "Gleise", "Vías" }, /* TextTrainIsToLong */ { "Train is to long for route {0}", "Zug ist zu lang für die Fahrstrasse {0}", "El tren es demasiado largo para el itinerario {0}" }, /* TextTrainIsToShort */ { "Train is to short for route {0}", "Zug ist zu kurz für die Fahrstrasse {0}", "El tren es demasiado corto para el itinerario {0}" }, /* TextTrainLenght */ { "Train length", "Zuglänge", "Longitud de tren" }, /* TextTrainType */ { "Train type", "Zugtyp", "Tipo de tren" }, /* TextTrainTypeAll */ { "All train types", "Alle Zugtypen", "Todos tipos de trens" }, /* TextTrainTypeCargo */ { "All cargo trains", "Alle Güterzüge", "Todos tipos de carga" }, /* TextTrainTypeCargoBlock */ { "Cargo block train/unit train", "Blockzug/Ganzzug", "Tren bloque/tren completo (carga)" }, /* TextTrainTypeCargoExpress */ { "Express cargo train", "Express Güterzug", "Tren carga expreso" }, /* TextTrainTypeCargoLocal */ { "Local cargo train", "Lokaler Güterzug", "Tren carga local" }, /* TextTrainTypeCargoLongDistance */ { "Long distance cargo train", "Langdistanzgüterzug", "Tren carga de larga distancia" }, /* TextTrainTypeCargoTractor */ { "Tractor cargo train", "Güterzug mit Traktor", "Tren carga con tractor" }, /* TextTrainTypeCargoWithPassenger */ { "Cargo train with passenger", "Güterzug mit Personenbeförderung", "Tren carga con pasageros" }, /* TextTrainTypeCleaning */ { "Cleaning train", "Reinigungszug", "Tren de limpieza" }, /* TextTrainTypeConstruction */ { "Construction train", "Bauzug", "Tren de construcción" }, /* TextTrainTypeEmpty */ { "Empty train", "Leerzug", "Tren vacío" }, /* TextTrainTypeExtra */ { "Extra train", "Extrazug", "Tren extra" }, /* TextTrainTypeFastLocal */ { "Fast local train", "RegioExpress", "Tren local rápido" }, /* TextTrainTypeHistoric */ { "Historic train", "Historischer Zug", "Tren histórico" }, /* TextTrainTypeInternationalHighSpeed */ { "International high speed train", "Internat. Hochgeschwindigkeitszug", "Tren de alta velocidad internac." }, /* TextTrainTypeInternationalLongDistance */ { "International long distance train", "EuroCity", "Tren de larga distancia internac." }, /* TextTrainTypeInternationalNight */ { "International night train", "Internationaler Nachtzug", "Tren nocturo internacional" }, /* TextTrainTypeLocal */ { "Local train", "Regionalzug", "Tren local" }, /* TextTrainTypeLoco */ { "Locomotive train", "Lokzug", "Tren locomotora" }, /* TextTrainTypeLongDistanceFastLocal */ { "Long distance fast local train", "InterRegio", "Tren local rápido de larga dist." }, /* TextTrainTypeNationalHighSpeed */ { "National high speed train", "Nat. Hochgeschwindigkeitszug", "Tren de alta velocidad nacional" }, /* TextTrainTypeNationalLongDistance */ { "National long distance train", "InterCity", "Tren de larga distancia nacional" }, /* TextTrainTypeNationalNight */ { "National night train", "Nationaler Nachtzug", "Tren nocturo nacional" }, /* TextTrainTypeOther */ { "Other train type", "Anderer Zugtyp", "Otro tipo de tren" }, /* TextTrainTypePassenger */ { "All passenger trains", "Alle Personenzüge", "Todos tipos de personas" }, /* TextTrainTypePassengerWithCargo */ { "Passenger train with Cargo", "Personenzug mit Güterbeförderung", "Tren pasageros con carga" }, /* TextTrainTypeRescue */ { "Rescue train", "Lösch- oder Rettungszug", "Tren de rescate" }, /* TextTrainTypeSuburban */ { "Suburban train", "S-Bahn", "Tren suburbano" }, /* TextTrainTypeUnderground */ { "Underground", "U-Bahn", "Metro" }, /* TextTrainTypeUnknown */ { "Unknown train type", "Unbekannter Zugtyp", "Tipo de tren desconocido" }, /* TextTravelSpeed */ { "Travel speed", "Reisegeschwindigkeit", "Velocidad de viaje" }, /* TextTunnelOneSide */ { "Tunnel (one side)", "Tunnel (eine Seite)", "Túnel (un lado)" }, /* TextTunnelTwoSides */ { "Tunnel (two sides)", "Tunnel (zwei Seiten)", "Túnel (dos lados)" }, /* TextTurn */ { "turn", "gebogen", "curvo" }, /* TextTurnDirectionOfTravelToLeft */ { "Turn direction of travel to left", "Drehe Fahrtrichtung nach links", "Gire la dirección de viaje hacia la izquierda" }, /* TextTurnDirectionOfTravelToRight */ { "Turn direction of travel to right", "Drehe Fahrtrichtung nach rechts", "Gire la dirección de viaje hacia la dereja" }, /* TextTurningBoosterOff */ { "Turning booster off", "Deaktiviere Booster", "Apagando booster" }, /* TextTurningBoosterOn */ { "Turning booster on", "Aktiviere Booster", "Encendiendo booster" }, /* TextTurningBoosterOnOrOff */ { "Turning booster on or off", "Schalte Fahrstrom ein oder aus", "Encender o apagar la energiá" }, /* TextTurnout */ { "turnout", "abzweigend", "desviando" }, /* TextType */ { "Type", "Typ", "Typo" }, /* TextUdpConnectionClosed */ { "UDP connection to {0} closed", "UDP Verbindung zu {0} geschlossen", "Connectión UDP a {0} cerrado" }, /* TextUdpConnectionEstablished */ { "UDP connection to {0} established", "UDP Verbindung zu {0} hergestellt", "Connectión UDP a {0} establecido" }, /* TextUnableToAddAccessory */ { "Unable to add accessory", "Nicht möglich den Zubehörartikel hinzuzufügen ", "Imposible añadir el accesorio" }, /* TextUnableToAddCluster */ { "Unable to add track cluster", "Nicht möglich die Gleisgruppe hinzuzufügen ", "Imposible añadir el grupo de vías" }, /* TextUnableToAddControl */ { "Unable to add control", "Nicht möglich die Zentrale hinzuzufügen ", "Imposible añadir el control" }, /* TextUnableToAddFeedback */ { "Unable to add feedback", "Nicht möglich den Rückmelder hinzuzufügen ", "Imposible añadir la retroseñal" }, /* TextUnableToAddLayer */ { "Unable to add layer", "Nicht möglich die Schicht hinzuzufügen ", "Imposible añadir la capa" }, /* TextUnableToAddLayer1 */ { "Unable to add initial layer 1", "Nicht möglich die Schicht 1 hinzuzufügen", "Imposible añadir capa 1" }, /* TextUnableToAddLoco */ { "Unable to add locomotive", "Nicht möglich die Lokomotive hinzuzufügen", "Impsible ańadir la locomotora" }, /* TextUnableToAddLocoToTrack */ { "Unable to add locomotive {0} to track {1}", "Nicht möglich die Lokomotive {0} auf Gleis {1} zu setzen", "Impsible poner la locomotora sobre vía {1}" }, /* TextUnableToAddMultipleUnit */ { "Unable to add multiple unit", "Nicht möglich die Mehrfachtraktion hinzuzufügen", "Impsible ańadir la unidad múltiple" }, /* TextUnableToAddRoute */ { "Unable to add route", "Nicht möglich die Fahrstrasse hinzuzufügen ", "Imposible añadir el itinerario" }, /* TextUnableToAddSignal */ { "Unable to add signal", "Nicht möglich das Signal hinzuzufügen ", "Imposible añadir la señal" }, /* TextUnableToAddSwitch */ { "Unable to add switch", "Nicht möglich die Weiche hinzuzufügen ", "Imposible añadir el desvío" }, /* TextUnableToAddText */ { "Unable to add text", "Nicht möglich den Text hinzuzufügen ", "Imposible añadir el texto" }, /* TextUnableToAddTrack */ { "Unable to add track", "Nicht möglich das Gleis hinzuzufügen ", "Imposible añadir la vía" }, /* TextUnableToBindSocketToPort */ { "Unable to bind connection to port {0}", "Binden der Verbindung an Port {0} fehlgeschlagen", "Imposible vincular la conexión al puerto {0}" }, /* TextUnableToBindUdpSocket */ { "Unable to bind UDP socket to address", "Nicht möglich den UDP socket an eine Adresse zu binden", "Imposible enlazar el UDP socket a la dirección" }, /* TextUnableToCalculatePosition */ { "Unable to calculate position", "Unmöglich die Position zu berechnen", "Imposible calcular la posición" }, /* TextUnableToCreateStorageHandler */ { "Unable to create storage handler", "Unmöglich den Speicher Handler zu erstellen", "Imposible crear manipulador de almacenamiento" }, /* TextUnableToCreateTcpSocket */ { "Unable to create TCP socket for {0}:{1}", "TCP Socket für {0}:{1} erstellen fehlgeschlagen", "Imposible crear socket TCP para {0}:{1}" }, /* TextUnableToCreateUdpSocket */ { "Unable to create UDP socket for {0}:{1}", "UDP Socket für {0}:{1} erstellen fehlgeschlagen", "Imposible crear socket UDP para {0}:{1}" }, /* TextUnableToCreateUdpSocketForReceivingData */ { "Unable to create UDP socket to receive data from control", "Nicht möglich ein UDP socket zu erstellen um Daten von der Zentrale zu empfangen", "Imposible crear UDP socket para recibir datos del control" }, /* TextUnableToCreateUdpSocketForSendingData */ { "Unable to create UDP socket to send data to control", "Nicht möglich ein UDP socket zu erstellen um Daten an die Zentrale zu senden", "Imposible crear UDP socket para enviar datos al control" }, /* TextUnableToFindSymbol */ { "Unable to find symbol {0}", "Symbol {0} nicht gefunden", "Imposible encontrar simbolo {0}" }, /* TextUnableToLock */ { "Unable to lock {0}", "Sperren von {0} nicht möglich", "Imposible bloquear {0}" }, /* TextUnableToOpenFile */ { "Unable to open file {0}", "Öffnen der Datei {0} nicht möglich", "Imposible abrir fila {0}" }, /* TextUnableToOpenSQLite */ { "Unable to load SQLite database: {0}", "Öffnen von SQLite Datenbank nicht möglich: {0}", "Imposible abrir base de datos SQLite: {0}" }, /* TextUnableToOpenSerial */ { "Unable to open serial {0}", "Serieller Anschluss {0} kann nicht geöffnet werden", "Imposible abrir conexión serial {0}" }, /* TextUnableToReceiveData */ { "Unable to receive data", "Daten empfangen nicht möglich", "Imposible recibir datos" }, /* TextUnableToReserve */ { "Unable to reserve {0}", "Reservieren von {0} nicht möglich", "Imposible reservar {0}" }, /* TextUnableToReserveRouteElement */ { "Unable to reserve element {1} of route {0}", "Reservieren von Element {1} der Fahrstrasse {0} nicht möglich", "Imposible reservar el elemento {1} de la ruta {0}" }, /* TextUnableToReserveRouteToTrack */ { "Unable to reserve destination track {1} of route {0}", "Reservieren von Zielgleis {1} der Fahrstrasse {0} nicht möglich", "Imposible reservar la vía de destino {1} de la ruta {0}" }, /* TextUnableToResolveAddress */ { "Unable to resolve address {0}", "Adresse {0} auflösen fehlgeschalgen", "Imposible resolver la dirección {0}" }, /* TextUnableToSendDataToControl */ { "Unable to send data to control", "Nicht möglich Daten an die Zentrale zu senden", "Imposible enviar datos al control" }, /* TextUnblockTrack */ { "Unblock track", "Deblockere Gleis", "Desbloquear vía" }, /* TextUnknownElement */ { "Unknown element", "Unbekanntes element", "Elemento desconocido" }, /* TextUnknownHardware */ { "Unknown control", "Unbekannte Zentrale", "Tipo de control desconocido" }, /* TextUnknownObjectType */ { "Unknown object type", "Unbekannter Objekttyp", "Tipo de objeto desconocido" }, /* TextUnloadingControl */ { "Unloading control {0}: {1}", "Entlade Zentrale {0}: {1}", "Descargando control {0}: {1}" }, /* TextUsingRouteFromTimetable */ { "Using route {0} from timetable", "Verwende Fahrstrasse {0} aus dem Fahrplan", "Usando itinerario {0} del horario" }, /* TextValue */ { "Value", "Wert", "Valor" }, /* TextVersion */ { "Version: {0}", "Version: {0}", "Versión: {0}" }, /* TextVisible */ { "Visible", "Sichtbar", "Visible" }, /* TextWaitAfterRelease */ { "Wait after release (s)", "Wartezeit nach Auflösen (s)", "Tiempo de espera despues liberar (s)" }, /* TextWaitingTimeBetweenMembers */ { "Waiting time between members (ms)", "Wartezeit zwischen Teilnehmern (ms)", "Tiempo de esprera entre los miembros (ms)" }, /* TextWaitingUntilHasStopped */ { "Waiting until it has stopped", "Warte bis sie angehalten hat", "Esperando hasta ha parado" }, /* TextWarning */ { "warning", "Warnungen", "advertencias" }, /* TextWebServerStarted */ { "Webserver started", "Webserver wurde gestartet", "Servidor web encendido" }, /* TextWebServerStopped */ { "Webserver stopped", "Webserver wurde beendet", "Servidor web apagado" }, /* TextWhenWrongPosition */ { "when in wrong position", "wenn in falscher Lage", "si en la posición falsa" }, /* TextWidth */ { "Width", "Breite", "Anchura" }, /* TextWidthIs0 */ { "Width is zero", "Breite ist null", "Anchura está zero" }, /* TextWrite */ { "write", "schreiben", "escribir" }, /* TextZ21Black2012 */ { "black Z21, hardware 2012", "schwarzen Z21, Hardware 2012", "Z21 negro, hardware 2012" }, /* TextZ21Black2013 */ { "black Z21, hardware 2013", "schwarzen Z21, Hardware 2013", "Z21 negro, hardware 2013" }, /* TextZ21DoesNotUnderstand */ { "Z21 does not understand our command", "Z21 versteht unser Kommando nicht", "Z21 no ha endendido nuestro comando" }, /* TextZ21NotRestricted */ { "Z21 does not have any restrictions", "Z21 hat keine Restriktionen", "Z21 no tiene restricctiones" }, /* TextZ21RestrictionsUnknown */ { "Z21 restrictions unknown", "Z21 Restriktionen unbekannt", "Z21 restricctiones desconocidos" }, /* TextZ21ServerStarted */ { "Z21 server started", "Z21 Server wurde gestartet", "Servidor Z21 encendido" }, /* TextZ21ServerStopped */ { "Z21 server stopped", "Z21 Server wurde beendet", "Servidor Z21 apagado" }, /* TextZ21SmartRail2012 */ { "SmartRail", "SmartRail", "SmartRail" }, /* TextZ21Start2016 */ { "z21 start", "z21 start", "z21 start" }, /* TextZ21StartLocked */ { "Z21 start is locked. Impssible to use this Z21!", "Z21 start ist gesperrt. Unmöglich diese Z21 zu benutzen!", "Z21 start está limitado. Imposible usar este Z21!" }, /* TextZ21StartUnlocked */ { "Z21 start is unlocked", "Z21 start ist offen", "Z21 start está abierto" }, /* TextZ21Type */ { "Connected to a {0} with firmware version {1}", "Verbunden mit einer {0} mit Firmware version {1}", "Conectado a {0} con firmware versión {1}" }, /* TextZ21Unknown */ { "unknown Z21", "unbekannten Z21", "Z21 desconocido" }, /* TextZ21White2013 */ { "white z21", "weissen z21", "z21 blanco" }, // /* Text */ { "", "", "" }, }; Languages::Language Languages::defaultLanguage = Languages::EN; const char* Languages::GetText(const Language language, const TextSelector selector) { if (language >= MaxLanguages || selector >= MaxTexts) { static const char* unknownText = ""; return unknownText; } return languages[selector][language]; } const std::string Languages::GetText(const TextSelector label, const TextSelector tooltip) { std::string out(GetText(label)); out += " 
 ? 
"; out += GetText(tooltip); out += "
"; return out; } railcontrol-24+dfsg1/Languages.h000066400000000000000000000563071500456250600167260ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include "DataModel/AccessoryBase.h" #include "DataTypes.h" class Languages { public: enum TextSelector : unsigned short { Text180Deg, Text90DegAntiClockwise, Text90DegClockwise, TextAccessories, TextAccessory, TextAccessoryAddressDccTooHigh, TextAccessoryAddressMmTooHigh, TextAccessoryControlProtocolAddressDoesNotExist, TextAccessoryDeleted, TextAccessoryDoesNotExist, TextAccessoryIsLocked, TextAccessoryIsUsedByRoute, TextAccessorySaved, TextAccessorySenderThreadStarted, TextAccessoryStateIsGreen, TextAccessoryStateIsRed, TextAccessoryTypeDecoupler, TextAccessoryTypeLight, TextAccessoryTypeLightInhouse, TextAccessoryTypeLightStreet, TextAccessoryTypeOnOff, TextAccessoryTypeOnOn, TextAccessoryTypeOnPush, TextAccessoryUpdated, TextActualAndStoredProtocolsDiffer, TextAddAccessory, TextAddFeedback, TextAddRoute, TextAddSignal, TextAddSwitch, TextAddText, TextAddTrack, TextAddingFeedback, TextAddingRouteToTimetable, TextAddress, TextAddressMustBeHigherThen0, TextAddresses, TextAllTrains, TextAllowLocoTurn, TextAllowedPropulsions, TextAllowedPushPull, TextAllowedTrainTypes, TextAlways, TextAreYouSureToDelete, TextAreYouSureToShutdown, TextAtLock, TextAtUnlock, TextAutomaticallyAddUnknownFeedbacks, TextAutomode, TextBaseAddress, TextBasic, TextBlockTrack, TextBooster, TextBoosterIsTurnedOff, TextBoosterIsTurnedOn, TextBridge, TextBrowserInfo, TextBufferStop, TextCS2ServerStarted, TextCS2ServerStopped, TextCV, TextCanNotStartAlreadyRunning, TextCanNotStartInErrorState, TextCanNotStartNotOnTrack, TextChDwarf, TextChLDistant, TextChLMain, TextChNDistant, TextChNMain, TextChange, TextCheckSumError, TextClosingSQLite, TextCluster, TextClusterDeleted, TextClusterDoesNotExist, TextClusterSaved, TextClusterUpdated, TextClusters, TextCommandUnknown, TextCompileDate, TextConfigFileNotFound, TextConfigFileReceivedWithSize, TextConfigMenu, TextConfigureControlFirst, TextConnectedTo, TextConnection, TextConnectionFailed, TextConnectionHint, TextConnectionRefused, TextConnectionReset, TextControl, TextControlDeleted, TextControlDoesNotAnswer, TextControlDoesNotExist, TextControlReturnedBadParameter, TextControlReturnedError, TextControlReturnedOnHalt, TextControlReturnedPowerOff, TextControlReturnedQueueFull, TextControlReturnedQueueNearlyFull, TextControlReturnedUnknownErrorCode, TextControlSaved, TextControls, TextCopyingFromTo, TextCrcMissmatch, TextCreatingTable, TextCreepAt, TextCreepingSpeed, TextCrossingLeft, TextCrossingRight, TextCrossingSymetric, TextCs2MainFound, TextCs2MainLocoFunctionIconType, TextCs2MainLocoFunctionIconTypeTimer, TextCs2MainLocoName, TextCs2MainLocoOldName, TextCs2MainLocoProtocolAddress, TextCs2MainLocoRemove, TextCs2MainLocoSlaveName, TextCs2MainLocoSlaveProtocolAddress, TextCs2MinorVersionIsUnknown, TextDcc, TextDeBlock, TextDeCombined, TextDeHVMain, TextDebounceThreadStarted, TextDebounceThreadTerminated, TextDebouncer, TextDebug, TextDefault, TextDefaultSwitchingDuration, TextDelete, TextDeleteAccessory, TextDeleteCluster, TextDeleteControl, TextDeleteFeedback, TextDeleteLayer, TextDeleteLoco, TextDeleteMultipleUnit, TextDeleteRoute, TextDeleteSignal, TextDeleteSwitch, TextDeleteText, TextDeleteTrack, TextDestinationTrack, TextDeviceOnCanBus, TextDeviceType, TextDifferentOrientations, TextDifferentPropulsions, TextDifferentPushpullTypes, TextDifferentTrainTypes, TextDirect, TextDisplayName, TextDoNotCare, TextDroppingTable, TextDuration, TextEdit, TextEditAccessories, TextEditAccessory, TextEditClusters, TextEditControls, TextEditFeedback, TextEditFeedbacks, TextEditLayers, TextEditLocos, TextEditMultipleUnits, TextEditRoute, TextEditRoutes, TextEditSettings, TextEditSignal, TextEditSignals, TextEditSwitch, TextEditSwitches, TextEditText, TextEditTexts, TextEditTrack, TextEditTracks, TextEnglish, TextError, TextErrorReadingData, TextExecuteAccessory, TextExecuteRoute, TextExecutingRoute, TextExitRailControl, TextFeedback, TextFeedbackChange, TextFeedbackDeleted, TextFeedbackDoesNotExist, TextFeedbackIsUsedByTrack, TextFeedbackSaved, TextFeedbackStateIsOff, TextFeedbackStateIsOn, TextFeedbackUpdated, TextFeedbacks, TextFileNotFound, TextFollowUpRoute, TextFoundAccessoryInEcosDatabase, TextFoundFeedbackModuleInEcosDatabase, TextFoundLocoInEcosDatabase, TextFullScreen, TextFunctions, TextGerman, TextGitDate, TextGitDirty, TextGitHash, TextGreen, TextHasAlreadyReservedRoute, TextHasNotReachedDestination, TextHeadingToVia, TextHeadingToViaVia, TextHeartBeatThreadStarted, TextHeightIs0, TextHint, TextHintCcSchnitte, TextHintCs2Tcp, TextHintCs2Udp, TextHintDccPpExSerial, TextHintDccPpExTcp, TextHintEcos, TextHintHsi88, TextHintIntellibox, TextHintIntellibox2, TextHintLocoNetAdapter63120, TextHintLocoNetAdapter63820, TextHintM6051, TextHintMasterControl, TextHintMasterControl2, TextHintOpenDcc, TextHintPositionMove, TextHintPositionRotate, TextHintRedBox, TextHintSystemControl7, TextHintTwinCenter, TextHintVirtual, TextHintZ21, TextHitOverrun, TextHsi88Configured, TextHsi88ErrorConfiguring, TextHttpRequest, TextIPAddress, TextImport, TextIndependentOfControl, TextIndex, TextInfo, TextInvalidControlID, TextInvalidDataReceived, TextInverted, TextInverted2, TextIsInAutomodeWithoutRouteTrack, TextIsInErrorState, TextIsInInvalidAutomodeState, TextIsInManualState, TextIsInTerminatedState, TextIsLockedBy, TextIsNotFree, TextIsNotOnTrack, TextIsNowInAutoMode, TextIsNowInManualMode, TextIsOnOcupiedTrack, TextIsRunningWaitingUntilDestination, TextIsUpToDate, TextLanguage, TextLayer1, TextLayer1IsUndeletable, TextLayerDeleted, TextLayerDoesNotExist, TextLayerIsUsedByAccessory, TextLayerIsUsedByFeedback, TextLayerIsUsedByRoute, TextLayerIsUsedBySignal, TextLayerIsUsedBySwitch, TextLayerIsUsedByText, TextLayerIsUsedByTrack, TextLayerSaved, TextLayerUpdated, TextLayers, TextLeft, TextLength, TextLink, TextLoadedAccessory, TextLoadedCluster, TextLoadedControl, TextLoadedFeedback, TextLoadedLayer, TextLoadedLoco, TextLoadedMultipleUnit, TextLoadedRoute, TextLoadedSignal, TextLoadedSwitch, TextLoadedText, TextLoadedTrack, TextLoco, TextLocoAddressDccTooHigh, TextLocoAddressMm1TooHigh, TextLocoAddressMm2TooHigh, TextLocoDeleted, TextLocoDirectionOfTravelIsLeft, TextLocoDirectionOfTravelIsRight, TextLocoDoesNotExist, TextLocoFunctionIconAirPump, TextLocoFunctionIconBacklightForward, TextLocoFunctionIconBacklightReverse, TextLocoFunctionIconBell, TextLocoFunctionIconBlinkingLight, TextLocoFunctionIconBreak, TextLocoFunctionIconBreak1, TextLocoFunctionIconBreak2, TextLocoFunctionIconBufferPush, TextLocoFunctionIconCabLight1, TextLocoFunctionIconCabLight12, TextLocoFunctionIconCabLight2, TextLocoFunctionIconCloseDoor, TextLocoFunctionIconCompressedAir, TextLocoFunctionIconCoupler, TextLocoFunctionIconCrane, TextLocoFunctionIconCraneHook, TextLocoFunctionIconCurve, TextLocoFunctionIconDefault, TextLocoFunctionIconDown, TextLocoFunctionIconDrainValve, TextLocoFunctionIconDriversDeskLight, TextLocoFunctionIconEngine1, TextLocoFunctionIconEngine2, TextLocoFunctionIconEngineLight, TextLocoFunctionIconFan, TextLocoFunctionIconFan1, TextLocoFunctionIconFan2, TextLocoFunctionIconFan3, TextLocoFunctionIconFillDiesel, TextLocoFunctionIconFillGas, TextLocoFunctionIconFillWater, TextLocoFunctionIconFireBox, TextLocoFunctionIconGearBox, TextLocoFunctionIconGearDown, TextLocoFunctionIconGearUp, TextLocoFunctionIconGenerator, TextLocoFunctionIconHeadlightHighBeamForward, TextLocoFunctionIconHeadlightHighBeamReverse, TextLocoFunctionIconHeadlightLowBeamForward, TextLocoFunctionIconHeadlightLowBeamReverse, TextLocoFunctionIconHorn1, TextLocoFunctionIconHorn2, TextLocoFunctionIconInertia, TextLocoFunctionIconInteriorLight1, TextLocoFunctionIconInteriorLight2, TextLocoFunctionIconLeft, TextLocoFunctionIconLeftRight, TextLocoFunctionIconLight, TextLocoFunctionIconLocomotiveNumberIndicator, TextLocoFunctionIconMagnet, TextLocoFunctionIconMainSwitch, TextLocoFunctionIconMusic1, TextLocoFunctionIconMusic2, TextLocoFunctionIconNoBreak, TextLocoFunctionIconNoSound, TextLocoFunctionIconOpenDoor, TextLocoFunctionIconPanto, TextLocoFunctionIconPanto1, TextLocoFunctionIconPanto12, TextLocoFunctionIconPanto2, TextLocoFunctionIconRadio, TextLocoFunctionIconRailJoint, TextLocoFunctionIconReliefValve, TextLocoFunctionIconRight, TextLocoFunctionIconRunning1, TextLocoFunctionIconRunning2, TextLocoFunctionIconSand, TextLocoFunctionIconShakingRust, TextLocoFunctionIconShovelCoal, TextLocoFunctionIconShuntingLight, TextLocoFunctionIconShuntingMode, TextLocoFunctionIconSmokeGenerator, TextLocoFunctionIconSoundGeneral, TextLocoFunctionIconSoundLouder, TextLocoFunctionIconSoundLower, TextLocoFunctionIconSpeak, TextLocoFunctionIconStairsLight, TextLocoFunctionIconStationAnnouncement1, TextLocoFunctionIconStationAnnouncement2, TextLocoFunctionIconStationAnnouncement3, TextLocoFunctionIconSteamBlow, TextLocoFunctionIconSteamBlowOut, TextLocoFunctionIconTableLight1, TextLocoFunctionIconTableLight2, TextLocoFunctionIconTableLight3, TextLocoFunctionIconTelex1, TextLocoFunctionIconTelex12, TextLocoFunctionIconTelex2, TextLocoFunctionIconTrainDestinationIndicator, TextLocoFunctionIconTurn, TextLocoFunctionIconTurnLeft, TextLocoFunctionIconTurnRight, TextLocoFunctionIconUp, TextLocoFunctionIconUpDown1, TextLocoFunctionIconUpDown2, TextLocoFunctionIconWaterPump, TextLocoFunctionIconWhistle1, TextLocoFunctionIconWhistle2, TextLocoFunctionIsOff, TextLocoFunctionIsOn, TextLocoFunctionTypeFlashing, TextLocoFunctionTypeMoment, TextLocoFunctionTypeNone, TextLocoFunctionTypePermanent, TextLocoFunctionTypeTimer, TextLocoHasReachedDestination, TextLocoIsInAutoMode, TextLocoIsInManualMode, TextLocoIsInUse, TextLocoIsOnTrack, TextLocoIsReleased, TextLocoSaved, TextLocoSpeedIs, TextLocoUpdated, TextLocos, TextLogLevel, TextLongestUnused, TextLookingForDestination, TextMaerklinLeft, TextMaerklinMotorola, TextMaerklinRight, TextMainDevice, TextMainTrack, TextMainTrackHint, TextManager, TextMaxSpeed, TextMaxTrainLength, TextMembers, TextMethodNotImplemented, TextMinTrackLength, TextMinTrainLength, TextMultipleUnit, TextMultipleUnitDeleted, TextMultipleUnitDoesNotExist, TextMultipleUnitIsInUse, TextMultipleUnitSaved, TextMultipleUnits, TextMyUidHash, TextName, TextNameInControl, TextNetworkUnreachable, TextNew, TextNoAnswerFromDecoder, TextNoControlSupportsProgramming, TextNoPushPull, TextNoRotation, TextNoS88Modules, TextNoValidRouteFound, TextNone, TextNotImplemented, TextNrOfFunctions, TextNrOfS88Modules, TextNrOfS88ModulesConfigured, TextNrOfS88ModulesOnBus, TextNrOfTracksToReserve, TextObjectIsUsedByRoute, TextOff, TextOn, TextOnPush, TextOpeningSQLite, TextOrientation, TextOverrunAt, TextParameterFoundInConfigFile, TextPause, TextPin, TextPingSenderStarted, TextPleaseSelectLoco, TextPosX, TextPosY, TextPosZ, TextPosition, TextPositionAlreadyInUse, TextProgramDccDirectRead, TextProgramDccDirectWrite, TextProgramDccPageRead, TextProgramDccPageWrite, TextProgramDccPomAccessoryRead, TextProgramDccPomAccessoryWrite, TextProgramDccPomLocoRead, TextProgramDccPomLocoWrite, TextProgramDccRegisterRead, TextProgramDccRegisterWrite, TextProgramMfxRead, TextProgramMfxWrite, TextProgramMm, TextProgramMmPom, TextProgramMode, TextProgramModeDccDirect, TextProgramModeDccPage, TextProgramModeDccPomAccessory, TextProgramModeDccPomLoco, TextProgramModeDccRegister, TextProgramModeMfx, TextProgramModeMm, TextProgramModeMmPom, TextProgramReadValue, TextProgrammer, TextProgrammingMode, TextPropulsion, TextPropulsionAccu, TextPropulsionAll, TextPropulsionDiesel, TextPropulsionElectric, TextPropulsionGas, TextPropulsionHydrogen, TextPropulsionOther, TextPropulsionSteam, TextPropulsionUnknown, TextProtocol, TextProtocolNotSupported, TextPushPullOnly, TextPushPullTrain, TextQuery, TextQueryAffected, TextRailControlStarted, TextRailControlUpdateAvailable, TextRandom, TextReachedItsDestination, TextRead, TextReadingConfigFile, TextReceivedAccessoryCommand, TextReceivedDirectionCommand, TextReceivedFunctionCommand, TextReceivedSignalKill, TextReceivedSpeedCommand, TextReceiverThreadStarted, TextRed, TextReducedSpeed, TextReducedSpeedAt, TextRegister, TextRelationTargetNotFound, TextRelease, TextReleaseAccessory, TextReleaseLoco, TextReleaseRoute, TextReleaseSignal, TextReleaseSwitch, TextReleaseTrack, TextReleaseTrackAndLoco, TextReleaseWhenFree, TextReleasingLoco, TextReleasingMultipleUnit, TextRemoveBackupFile, TextRenamingFromTo, TextReservingRoute, TextRestarting, TextRight, TextRotation, TextRoute, TextRouteDeleted, TextRouteDoesNotExist, TextRouteIsInUse, TextRouteIsLocked, TextRouteIsReleased, TextRouteIsUsedByRoute, TextRouteSaved, TextRouteUpdated, TextRoutes, TextSQLiteErrorQuery, TextSaving, TextSecondaryDevice, TextSelectAutomatically, TextSelectLocoForTrack, TextSelectRouteBy, TextSenderSocketCreated, TextSenderThreadStarted, TextSendingLocoInfo, TextSendingTurnoutInfo, TextSeparator, TextSerialNumberIs, TextSerialPort, TextServerAddress, TextSetAllLocosToAutomode, TextSetAllLocosToManualMode, TextSetLoco, TextSettingAccessory, TextSettingAccessoryOnOff, TextSettingAccessoryWithProtocol, TextSettingDirectionOfTravel, TextSettingDirectionOfTravelWithProtocol, TextSettingFunction, TextSettingFunctionWithProtocol, TextSettingFunctions17_28, TextSettingFunctions1_8, TextSettingFunctions9_16, TextSettingOrientation, TextSettingSpeed, TextSettingSpeedOrientationLight, TextSettingSpeedWithProtocol, TextSettings, TextSettingsSaved, TextSeveral, TextShortCircuit, TextShowName, TextShutdown, TextShutdownRailControl, TextShutdownRequestedBySignal, TextShutdownRequestedByWebClient, TextSignal, TextSignalDeleted, TextSignalDoesNotExist, TextSignalHasAssociatedFeedback, TextSignalIsLocked, TextSignalIsUsedByLoco, TextSignalIsUsedByRoute, TextSignalIsUsedByTrack, TextSignalSaved, TextSignalStateCaution, TextSignalStateClear, TextSignalStateClear100, TextSignalStateClear100Expected, TextSignalStateClear110, TextSignalStateClear110Expected, TextSignalStateClear120, TextSignalStateClear120Expected, TextSignalStateClear40, TextSignalStateClear40Expected, TextSignalStateClear50, TextSignalStateClear50Expected, TextSignalStateClear60, TextSignalStateClear60Expected, TextSignalStateClear70, TextSignalStateClear70Expected, TextSignalStateClear80, TextSignalStateClear80Expected, TextSignalStateClear90, TextSignalStateClear90Expected, TextSignalStateClearExpected, TextSignalStateDark, TextSignalStateIsAspect10, TextSignalStateIsAspect10Expected, TextSignalStateIsAspect2, TextSignalStateIsAspect2Expected, TextSignalStateIsAspect3, TextSignalStateIsAspect3Expected, TextSignalStateIsAspect4, TextSignalStateIsAspect4Expected, TextSignalStateIsAspect5, TextSignalStateIsAspect5Expected, TextSignalStateIsAspect6, TextSignalStateIsAspect6Expected, TextSignalStateIsAspect7, TextSignalStateIsAspect7Expected, TextSignalStateIsAspect8, TextSignalStateIsAspect8Expected, TextSignalStateIsAspect9, TextSignalStateIsAspect9Expected, TextSignalStateIsClear, TextSignalStateIsClearExpected, TextSignalStateIsDark, TextSignalStateIsStop, TextSignalStateIsStopExpected, TextSignalStateShortClear, TextSignalStateShunting, TextSignalStateSlow, TextSignalStateStop, TextSignalStateStopExpected, TextSignalStateZs7, TextSignalUpdated, TextSignals, TextSimpleLeft, TextSimpleRight, TextSlotHasAddress, TextSpanish, TextSpeed, TextStartArgument, TextStartLocoAutomode, TextStartTrack, TextStarting, TextStartup, TextStartupInitLocos, TextStartupInitLocosAll, TextStartupInitLocosNone, TextStartupInitLocosSpeed, TextStop, TextStopAllLocos, TextStopAt, TextStopLoco, TextStopOnFeedbackInFreeTrack, TextStraight, TextSwitch, TextSwitchDeleted, TextSwitchDoesNotExist, TextSwitchIsLocked, TextSwitchIsUsedByRoute, TextSwitchSaved, TextSwitchStateIsStraight, TextSwitchStateIsThird, TextSwitchStateIsTurnout, TextSwitchStateLeft, TextSwitchStateRight, TextSwitchStateStraight, TextSwitchUpdated, TextSwitches, TextSystemDefault, TextTcpConnectionClosed, TextTcpConnectionEstablished, TextTerminatingAccessorySenderThread, TextTerminatingHeartBeatThread, TextTerminatingPingSender, TextTerminatingReceiverThread, TextTerminatingSenderSocket, TextTerminatingSenderThread, TextText, TextTextDeleted, TextTextDoesNotExist, TextTextSaved, TextTextUpdated, TextTexts, TextThreeWay, TextTimestampAlreadySet, TextTimestampNotSet, TextTimestampSet, TextTooManyS88Modules, TextTrack, TextTrackDeleted, TextTrackDoesNotExist, TextTrackIsUsedByLoco, TextTrackIsUsedByRoute, TextTrackSaved, TextTrackStatusIsBlocked, TextTrackStatusIsBlockedAndOccupied, TextTrackStatusIsBlockedAndReserved, TextTrackStatusIsFree, TextTrackStatusIsOccupied, TextTrackStatusIsReserved, TextTrackUpdated, TextTracks, TextTrainIsToLong, TextTrainIsToShort, TextTrainLength, TextTrainType, TextTrainTypeAll, TextTrainTypeCargo, TextTrainTypeCargoBlock, TextTrainTypeCargoExpress, TextTrainTypeCargoLocal, TextTrainTypeCargoLongDistance, TextTrainTypeCargoTractor, TextTrainTypeCargoWithPassenger, TextTrainTypeCleaning, TextTrainTypeConstruction, TextTrainTypeEmpty, TextTrainTypeExtra, TextTrainTypeFastLocal, TextTrainTypeHistoric, TextTrainTypeInternationalHighSpeed, TextTrainTypeInternationalLongDistance, TextTrainTypeInternationalNight, TextTrainTypeLocal, TextTrainTypeLoco, TextTrainTypeLongDistanceFastLocal, TextTrainTypeNationalHighSpeed, TextTrainTypeNationalLongDistance, TextTrainTypeNationalNight, TextTrainTypeOther, TextTrainTypePassenger, TextTrainTypePassengerWithCargo, TextTrainTypeRescue, TextTrainTypeSuburban, TextTrainTypeUnderground, TextTrainTypeUnknown, TextTravelSpeed, TextTunnelOneSide, TextTunnelTwoSides, TextTurn, TextTurnDirectionOfTravelToLeft, TextTurnDirectionOfTravelToRight, TextTurningBoosterOff, TextTurningBoosterOn, TextTurningBoosterOnOrOff, TextTurnout, TextType, TextUdpConnectionClosed, TextUdpConnectionEstablished, TextUnableToAddAccessory, TextUnableToAddCluster, TextUnableToAddControl, TextUnableToAddFeedback, TextUnableToAddLayer, TextUnableToAddLayer1, TextUnableToAddLoco, TextUnableToAddLocoToTrack, TextUnableToAddMultipleUnit, TextUnableToAddRoute, TextUnableToAddSignal, TextUnableToAddSwitch, TextUnableToAddText, TextUnableToAddTrack, TextUnableToBindSocketToPort, TextUnableToBindUdpSocket, TextUnableToCalculatePosition, TextUnableToCreateStorageHandler, TextUnableToCreateTcpSocket, TextUnableToCreateUdpSocket, TextUnableToCreateUdpSocketForReceivingData, TextUnableToCreateUdpSocketForSendingData, TextUnableToFindSymbol, TextUnableToLock, TextUnableToOpenFile, TextUnableToOpenSQLite, TextUnableToOpenSerial, TextUnableToReceiveData, TextUnableToReserve, TextUnableToReserveRouteElement, TextUnableToReserveRouteToTrack, TextUnableToResolveAddress, TextUnableToSendDataToControl, TextUnblockTrack, TextUnknownElement, TextUnknownHardware, TextUnknownObjectType, TextUnloadingControl, TextUsingRouteFromTimetable, TextValue, TextVersion, TextVisible, TextWaitAfterRelease, TextWaitingTimeBetweenMembers, TextWaitingUntilHasStopped, TextWarning, TextWebServerStarted, TextWebServerStopped, TextWhenWrongPosition, TextWidth, TextWidthIs0, TextWrite, TextZ21Black2012, TextZ21Black2013, TextZ21DoesNotUnderstand, TextZ21NotRestricted, TextZ21RestrictionsUnknown, TextZ21ServerStarted, TextZ21ServerStopped, TextZ21SmartRail2012, TextZ21Start2016, TextZ21StartLocked, TextZ21StartUnlocked, TextZ21Type, TextZ21Unknown, TextZ21White2013, MaxTexts }; enum Language : unsigned char { EN = 0, DE, ES, MaxLanguages }; static inline void SetDefaultLanguage(Language language) { defaultLanguage = language >= MaxLanguages ? EN : language; } static inline Language GetDefaultLanguage() { return defaultLanguage; } static inline const char* GetText(const TextSelector selector) { return GetText(defaultLanguage, selector); } static const char* GetText(const Language language, const TextSelector selector); static const std::string GetText(const TextSelector text, const TextSelector tooltip); static inline const char* GetOnOff(const bool on) { return GetText(on ? TextOn : TextOff); } static inline const char* GetLeftRight(const Orientation direction) { return GetText(direction == OrientationRight ? TextRight : TextLeft); } static inline const char* GetGreenRed(const DataModel::AccessoryState state) { return GetText(state == DataModel::AccessoryStateOn ? TextGreen : TextRed); } static const char* languages[MaxTexts][MaxLanguages]; static Language defaultLanguage; }; railcontrol-24+dfsg1/Logger/000077500000000000000000000000001500456250600160535ustar00rootroot00000000000000railcontrol-24+dfsg1/Logger/Logger.cpp000066400000000000000000000057121500456250600200030ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include #include // gettimeofday #include "Logger/Logger.h" using std::string; namespace Logger { Logger::Level Logger::logLevel = Logger::LevelInfo; string Logger::DateTime() { char buffer[27]; struct timeval timestamp; gettimeofday(×tamp, NULL); struct tm tm; gmtime_r(×tamp.tv_sec, &tm); strftime(buffer, sizeof(buffer), "%F %T.", &tm); snprintf(buffer + 20, sizeof(buffer) - 20, "%06li", static_cast(timestamp.tv_usec)); return string(buffer); } void Logger::Replace(string& workString, const unsigned char argument, const std::string& value) { std::string needle = "{" + std::to_string(argument) + "}"; size_t pos = workString.find(needle); if (pos == std::string::npos) { return; } workString.replace(pos, needle.size(), value); } void Logger::AsciiPart(std::stringstream& output, const unsigned char* input, const size_t size) { output << " "; if (size < 8) { output << " "; } for (size_t index = size; index < 16; ++index) { output << " "; } for (size_t index = 0; index < size; ++index) { if (index == 8) { output << " "; } if (input[index] >= 0x20 && input[index] < 127) { output << input[index]; } else { output << "."; } } } void Logger::Hex(const unsigned char direction, const unsigned char* input, const size_t size) { std::stringstream output; size_t index; for (index = 0; index < size; ++index) { if ((index & 0x0F) == 0) { if (direction == 1) { output << "<< "; } else if (direction == 2) { output << " >> "; } output << "0x" << std::setfill('0') << std::setw(4) << std::hex << index << " "; } output << std::setfill('0') << std::setw(2) << std::hex << static_cast(input[index]) << " "; size_t next = index + 1; if ((next & 0x0F) == 0) { AsciiPart(output, input + index - 15, 16); Debug(output.str()); output.str(std::string()); if (next == size) { return; } continue; } if ((next & 0x07) == 0) { output << " "; } } size_t reminder = (index & 0x0F); AsciiPart(output, input + index - reminder, reminder); Debug(output.str()); } } railcontrol-24+dfsg1/Logger/Logger.h000066400000000000000000000131641500456250600174500ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "Languages.h" #include "Logger/LoggerServer.h" #include "Network/TcpServer.h" namespace Logger { class Logger { public: enum Level : unsigned char { LevelOff = 0, LevelError, LevelWarning, LevelInfo, LevelDebug }; Logger(LoggerServer& server, const std::string& component) : server(server), component(component) {} ~Logger() {}; static Logger* GetLogger(const std::string& component) { return LoggerServer::Instance().GetLogger(component); } void AddFileLogger(const std::string& fileName) { LoggerServer::Instance().AddFileLogger(fileName); } void AddConsoleLogger() { LoggerServer::Instance().AddConsoleLogger(); } static void SetLogLevel(Level level) { logLevel = level; } static Level GetLogLevel() { return logLevel; } bool IsComponent(const std::string& component) { return component.compare(this->component) == 0; } static std::string Format(const std::string& input) { return input; } static std::string Format(char* input) { const char* constInput = input; return Format(constInput); } static std::string Format(const char* input) { if (input == nullptr) { return std::string(""); } return std::string(input); } template static std::string Format(const std::string& input, Args... args) { std::string output = input; FormatInternal(output, 0, args...); return output; } template static std::string Format(char* input, Args... args) { const char* constInput = input; return Format(constInput, args...); } template static std::string Format(const char* input, Args... args) { if (input == nullptr) { return std::string(""); } return Format(std::string(input), args...); } template void Error(const Languages::TextSelector text, Args... args) { if (logLevel < LevelError) { return; } Log("Error", Languages::GetText(text), args...); } template void Warning(const Languages::TextSelector text, Args... args) { if (logLevel < LevelWarning) { return; } Log("Warning", Languages::GetText(text), args...); } template void Info(const Languages::TextSelector text, Args... args) { if (logLevel < LevelInfo) { return; } Log("Info", Languages::GetText(text), args...); } template void Debug(const Languages::TextSelector text, Args... args) { Debug(Languages::GetText(text), args...); } template void Debug(const std::string& text, Args... args) { if (logLevel < LevelDebug) { return; } Log("Debug", text, args...); } inline void HexIn(const std::string& input) { Hex(1, reinterpret_cast(input.c_str()), input.size()); } inline void HexOut(const std::string& input) { Hex(2, reinterpret_cast(input.c_str()), input.size()); } void Hex(const unsigned char direction, const unsigned char* input, const size_t size); inline void HexIn(const unsigned char* input, const size_t size) { Hex(1, input, size); } inline void HexOut(const unsigned char* input, const size_t size) { Hex(2, input, size); } private: static Level logLevel; LoggerServer& server; const std::string component; static void AsciiPart(std::stringstream& output, const unsigned char* input, const size_t size); static std::string DateTime(); static void Replace(std::string& workString, const unsigned char argument, const std::string& value); static void Replace(std::string& workString, const unsigned char argument, char* value) { const char* constValue = value; Replace(workString, argument, std::string(constValue)); } static void Replace(std::string& workString, const unsigned char argument, const char* value) { Replace(workString, argument, std::string(value == nullptr ? "" : value)); } template static void Replace(std::string& workString, const unsigned char argument, T value) { Replace(workString, argument, std::to_string(value)); } template static void FormatInternal(std::string& workString, const unsigned char argument, T value) { Replace(workString, argument, value); } template static void FormatInternal(std::string& workString, const unsigned char argument, T value, Args... args) { Replace(workString, argument, value); FormatInternal(workString, argument + 1, args...); } template void Log(const std::string& type, const std::string& text, Args... args) { server.Send(DateTime() + ": " + type + ": " + component + ": " + Format(text, args...) + "\n"); } }; } railcontrol-24+dfsg1/Logger/LoggerClient.h000066400000000000000000000016151500456250600206050ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include namespace Logger { class LoggerClient { public: virtual ~LoggerClient() {} virtual void Send(const std::string& s) = 0; }; } railcontrol-24+dfsg1/Logger/LoggerClientConsole.h000066400000000000000000000017511500456250600221310ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include "Logger/LoggerClient.h" namespace Logger { class LoggerClientConsole : public LoggerClient { public: void Send(const std::string& s) override { std::cout << s << std::flush; } }; } railcontrol-24+dfsg1/Logger/LoggerClientFile.h000066400000000000000000000027511500456250600214070ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include "Logger/LoggerClient.h" #include "Utils/Utils.h" namespace Logger { class LoggerClientFile : public LoggerClient { public: LoggerClientFile(const std::string& logFileName) : logFileName(logFileName) { logFile.open(logFileName, std::fstream::out | std::fstream::app); } ~LoggerClientFile() { Utils::Utils::RenameFile(nullptr, logFileName, logFileName + "." + std::to_string(time(0))); if (!logFile.is_open()) { return; } logFile.close(); } void Send(const std::string& s) override { if (!logFile.is_open()) { return; } logFile << s << std::flush; } private: const std::string logFileName; std::ofstream logFile; }; } railcontrol-24+dfsg1/Logger/LoggerClientTcp.h000066400000000000000000000023321500456250600212510ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "Logger/LoggerClient.h" #include "Network/TcpServer.h" namespace Logger { class LoggerClientTcp : public LoggerClient { public: // connection must be deleted after using! LoggerClientTcp(Network::TcpConnection* connection) : connection(connection) {} ~LoggerClientTcp() { delete connection; } void Send(const std::string& s) override { connection->Send(s); } private: Network::TcpConnection* connection; }; } railcontrol-24+dfsg1/Logger/LoggerServer.cpp000066400000000000000000000031101500456250600211600ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include "Logger/Logger.h" #include "Logger/LoggerServer.h" #include "Utils/Utils.h" using std::string; namespace Logger { LoggerServer::~LoggerServer() { // delete all client memory while (clients.size() > 0) { LoggerClient* client = clients.back(); clients.pop_back(); delete client; } // delete all logger memory while (loggers.size() > 0) { Logger* logger = loggers.back(); loggers.pop_back(); delete logger; } } Logger* LoggerServer::GetLogger(const std::string& component) { for (auto logger : loggers) { if (logger->IsComponent(component)) { return logger; } } Logger* logger = new Logger(*this, component); loggers.push_back(logger); return logger; } void LoggerServer::Send(const std::string& text) { for (auto client : clients) { client->Send(text); } } } railcontrol-24+dfsg1/Logger/LoggerServer.h000066400000000000000000000037171500456250600206420ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include #include "Logger/LoggerClient.h" #include "Logger/LoggerClientConsole.h" #include "Logger/LoggerClientFile.h" #include "Logger/LoggerClientTcp.h" namespace Logger { class Logger; class LoggerServer { public: LoggerServer(const LoggerServer&) = delete; LoggerServer& operator=(const LoggerServer&) = delete; Logger* GetLogger(const std::string& component); void Send(const std::string& text); static inline LoggerServer& Instance() { static LoggerServer server; return server; } inline void AddFileLogger(const std::string& fileName) { if (fileLoggerStarted == true) { return; } clients.push_back(new LoggerClientFile(fileName)); fileLoggerStarted = true; } inline void AddConsoleLogger() { if (consoleLoggerStarted == true) { return; } clients.push_back(new LoggerClientConsole()); consoleLoggerStarted = true; } private: inline LoggerServer() : fileLoggerStarted(false), consoleLoggerStarted(false) { } ~LoggerServer(); bool fileLoggerStarted; bool consoleLoggerStarted; std::vector clients; std::vector loggers; }; } railcontrol-24+dfsg1/Makefile000066400000000000000000000060441500456250600163000ustar00rootroot00000000000000 RAILCONTROL_VERSION := 24 CFLAGSSQLITE=-g -O2 -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_RTREE -DHAVE_USLEEP CFLAGSZLIB=-g -O2 -Wno-implicit-function-declaration -Wno-\#warnings -Wno-deprecated -Wno-deprecated-non-prototype CXXFLAGS=-I. -g -O2 -Wall -Wextra -pedantic -Werror -Wno-missing-braces -std=c++11 -D_GNU_SOURCE CXXFLAGSAMALGAMATION=-I. -g -O2 -Wall -Wextra -pedantic -Werror -Wno-missing-braces -std=c++11 LDFLAGS=-g LIBS=-lpthread -ldl LIBSAMALGAMATION=-lpthread -ldl ifeq ($(OS),Windows_NT) LDFLAGS+= -static endif TMPDIR=/tmp/RailControl TMPDIRCYGWIN=/RailControl CXXOBJ= $(patsubst %.cpp,%.o,$(sort Version.cpp $(wildcard *.cpp)) $(wildcard Server/Web/*.cpp Server/CS2/*.cpp Server/Z21/*.cpp DataModel/*.cpp Hardware/*.cpp Hardware/Protocols/*.cpp Logger/*.cpp Network/*.cpp Storage/*.cpp Utils/*.cpp)) COBJ= $(patsubst %.c,%.o,$(wildcard Hardware/zlib/*.c)) OBJ=Storage/sqlite/sqlite3.o $(CXXOBJ) $(COBJ) all: $(OBJ) $(CXX) $(LDFLAGS) $(OBJ) -o railcontrol $(LIBS) strip: all strip railcontrol dist: all strip railcontrol mkdir $(TMPDIR) cp -r \ html \ railcontrol.conf.dist \ railcontrol \ $(TMPDIR) ( cd $(TMPDIR)/.. && tar cvJf railcontrol.`date +"%Y%m%d"`.tar.xz RailControl/* ) rm -r $(TMPDIR) dist-cygwin: all strip railcontrol.exe mkdir $(TMPDIRCYGWIN) cp -r \ /cygdrive/c/Windows/SYSTEM32/ntdll.dll \ /cygdrive/c/Windows/system32/KERNELBASE.dll \ /cygdrive/c/Windows/system32/kernel32.dll \ /usr/bin/cygwin1.dll \ html \ railcontrol.conf.dist \ railcontrol.exe \ $(TMPDIRCYGWIN) zip -9 railcontrol.windows.`date +"%Y%m%d"`.zip $(TMPDIRCYGWIN)/* $(TMPDIRCYGWIN)/html/* rm -r $(TMPDIRCYGWIN) amalgamation.cpp: ./amalgamation.bash amalgamation: amalgamation.o Version.cpp Storage/sqlite/sqlite3.o $(COBJ) $(CXX) -g amalgamation.o Storage/sqlite/sqlite3.o Hardware/zlib/*.o -o railcontrol $(LIBSAMALGAMATION) strip railcontrol rm -f amalgamation.o rm -f amalgamation.cpp sqlite-shell: make -C Storage/sqlite Version.o: Version.cpp Version.h $(CXX) $(CXXFLAGS) -c -o $@ $< Hardware/zlib/%.o: Hardware/zlib/%.c Hardware/zlib/*.h $(CC) $(CFLAGSZLIB) -c -o $@ $< Storage/sqlite/sqlite3.o: Storage/sqlite/sqlite3.c Storage/sqlite/sqlite3.h $(CC) $(CFLAGSSQLITE) -c -o $@ $< %.o: %.cpp *.h DataModel/*.h Hardware/*.h Hardware/Protocols/*.h Logger/*.h Network/*.h Storage/*.h Utils/*.h Server/Web/*.h Server/CS2/*.h Server/Z21/*.h $(CXX) $(CXXFLAGS) -c -o $@ $< .PHONY: clean clean: rm -f *.o DataModel/*.o Hardware/*.o Hardware/Protocols/*.o Hardware/zlib/*.o Logger/*.o Network/*.o Storage/*.o Storage/sqlite/*.o Utils/*.o Server/Web/*.o Server/CS2/*.o Server/Z21/*.o rm -f railcontrol clean-sqlite-shell: make -C Storage/sqlite clean test: make -C test tools: make -C tools .PHONY: Version.cpp Version.cpp: Version.cpp.in sed s/@COMPILE_TIMESTAMP@/`date +%s`/ < Version.cpp.in \ | sed s/@GIT_HASH@/`git log -1 --format=%H`/ \ | sed s/@GIT_TIMESTAMP@/`git log -1 --format=%at`/ \ | sed "s/@GIT_DIRTY@/`git status -s| wc -l`/" \ | sed s/@RAILCONTROL_VERSION@/$(RAILCONTROL_VERSION)/ \ > Version.cpp railcontrol-24+dfsg1/Manager.cpp000066400000000000000000003541031500456250600167200ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include #include #include #include "DataModel/LayoutItem.h" #include "Languages.h" #include "Hardware/HardwareHandler.h" #include "Hardware/HardwareParams.h" #include "Hardware/Protocols/MaerklinCANCommon.h" #include "Manager.h" #include "RailControl.h" #include "Storage/TransactionGuard.h" #include "Utils/Integer.h" #include "Utils/Utils.h" #include "Server/CS2/CS2Server.h" #include "Server/Web/WebServer.h" #include "Server/Z21/Z21Server.h" using namespace DataModel; using LayoutPosition = DataModel::LayoutItem::LayoutPosition; using LayoutItemSize = DataModel::LayoutItem::LayoutItemSize; using LayoutRotation = DataModel::LayoutItem::LayoutRotation; using Visible = DataModel::LayoutItem::Visible; using Hardware::HardwareHandler; using Hardware::HardwareParams; using std::map; using std::to_string; using std::string; using std::stringstream; using std::vector; using Storage::TransactionGuard; using Storage::StorageHandler; using Storage::StorageParams; Manager::Manager(Config& config) : logger(Logger::Logger::GetLogger(Languages::GetText(Languages::TextManager))), boosterState(BoosterStateStop), storage(nullptr), startupInitLocos(StartupInitLocosAll), defaultAccessoryDuration(DataModel::DefaultAccessoryPulseDuration), autoAddFeedback(false), stopOnFeedbackInFreeTrack(true), executeAccessory(true), selectRouteApproach(DataModel::SelectRouteRandom), nrOfTracksToReserve(DataModel::Loco::ReserveOne), run(false), debounceRun(false), initLocosDone(false), serverEnabled(false), unknownControl(Languages::GetText(Languages::TextControlDoesNotExist)), unknownLoco(Languages::GetText(Languages::TextLocoDoesNotExist)), unknownMultipleUnit(Languages::GetText(Languages::TextMultipleUnitDoesNotExist)), unknownAccessory(Languages::GetText(Languages::TextAccessoryDoesNotExist)), unknownFeedback(Languages::GetText(Languages::TextFeedbackDoesNotExist)), unknownTrack(Languages::GetText(Languages::TextTrackDoesNotExist)), unknownSwitch(Languages::GetText(Languages::TextSwitchDoesNotExist)), unknownRoute(Languages::GetText(Languages::TextRouteDoesNotExist)), unknownSignal(Languages::GetText(Languages::TextSignalDoesNotExist)) { StorageParams storageParams; storageParams.module = "Sqlite"; storageParams.filename = config.getStringValue("dbfilename", "railcontrol.sqlite"); storageParams.keepBackups = config.getIntValue("dbkeepbackups", 10); storage = new StorageHandler(this, &storageParams); if (!storage) { logger->Info(Languages::TextUnableToCreateStorageHandler); return; } Logger::Logger::SetLogLevel(static_cast(Utils::Integer::StringToInteger(storage->GetSetting("LogLevel"), Logger::Logger::LevelInfo))); Languages::SetDefaultLanguage(static_cast(Utils::Integer::StringToInteger(storage->GetSetting("Language"), Languages::EN))); startupInitLocos = static_cast(Utils::Integer::StringToInteger(storage->GetSetting("StartupInitLocos"), StartupInitLocosAll)); defaultAccessoryDuration = Utils::Integer::StringToInteger(storage->GetSetting("DefaultAccessoryDuration"), 250); autoAddFeedback = Utils::Utils::StringToBool(storage->GetSetting("AutoAddFeedback")); stopOnFeedbackInFreeTrack = Utils::Utils::StringToBool(storage->GetSetting("StopOnFeedbackInFreeTrack"), true); executeAccessory = Utils::Utils::StringToBool(storage->GetSetting("ExecuteAccessory"), true); selectRouteApproach = static_cast(Utils::Integer::StringToInteger(storage->GetSetting("SelectRouteApproach"))); nrOfTracksToReserve = static_cast(Utils::Integer::StringToInteger(storage->GetSetting("NrOfTracksToReserve"), 2)); controls[ControlIdWebServer] = new Server::Web::WebServer(*this, config.getStringValue("webserveraddress", "any"), config.getIntValue("webserverport", 8082)); if (config.getBoolValue("z21server", false)) { controls[ControlIdZ21Server] = new Server::Z21::Z21Server(*this, Server::Z21::Z21Server::Z21Port); serverEnabled = true; } if (config.getBoolValue("cs2server", false)) { controls[ControlIdCS2Server] = new Server::CS2::CS2Server(*this); serverEnabled = true; } storage->AllHardwareParams(hardwareParams); for (auto& hardwareParam : hardwareParams) { hardwareParam.second->SetManager(this); controls[hardwareParam.second->GetControlID()] = new HardwareHandler(hardwareParam.second); logger->Info(Languages::TextLoadedControl, hardwareParam.first, hardwareParam.second->GetName()); } storage->AllLayers(layers); for (auto& layer : layers) { logger->Info(Languages::TextLoadedLayer, layer.second->GetID(), layer.second->GetName()); } if (layers.count(LayerUndeletable) != 1) { string result; const bool initLayer1 = LayerSave(0, Languages::GetText(Languages::TextLayer1), result); if (!initLayer1) { logger->Error(Languages::TextUnableToAddLayer1); } } storage->AllTexts(texts); for (auto& text : texts) { logger->Info(Languages::TextLoadedText, text.second->GetID(), text.second->GetName()); } storage->AllAccessories(accessories); for (auto& accessory : accessories) { logger->Info(Languages::TextLoadedAccessory, accessory.second->GetID(), accessory.second->GetName()); } storage->AllFeedbacks(feedbacks); for (auto& feedback : feedbacks) { logger->Info(Languages::TextLoadedFeedback, feedback.second->GetID(), feedback.second->GetName()); } storage->AllSignals(signals); for (auto& signal : signals) { logger->Info(Languages::TextLoadedSignal, signal.second->GetID(), signal.second->GetName()); } storage->AllTracks(tracks); for (auto& t : tracks) { Track* track = t.second; track->UpdateMain(); Track* main = track->GetMain(); if (main) { main->AddExtension(track); } logger->Info(Languages::TextLoadedTrack, track->GetID(), track->GetName()); } storage->AllSwitches(switches); for (auto& mySwitch : switches) { logger->Info(Languages::TextLoadedSwitch, mySwitch.second->GetID(), mySwitch.second->GetName()); } storage->AllClusters(clusters); for (auto& cluster : clusters) { logger->Info(Languages::TextLoadedCluster, cluster.second->GetID(), cluster.second->GetName()); } storage->AllRoutes(routes); for (auto& route : routes) { logger->Info(Languages::TextLoadedRoute, route.second->GetID(), route.second->GetName()); } storage->AllLocos(locos); for (auto& loco : locos) { logger->Info(Languages::TextLoadedLoco, loco.second->GetID(), loco.second->GetName()); } storage->AllMultipleUnits(multipleUnits); for (auto& multipleUnit : multipleUnits) { logger->Info(Languages::TextLoadedMultipleUnit, multipleUnit.second->GetID(), multipleUnit.second->GetName()); } debounceRun = true; debounceThread = std::thread(&Manager::DebounceWorker, this); { std::lock_guard guard(controlMutex); for (auto& control : controls) { control.second->Start(); } } run = true; InitLocos(); } Manager::~Manager() { while (!LocoBaseStopAll()) { Utils::Utils::SleepForSeconds(1); } debounceRun = false; debounceThread.join(); Booster(ControlTypeInternal, BoosterStateStop); run = false; { std::lock_guard guard(controlMutex); for (auto& control : controls) { control.second->Stop(); } } Utils::Utils::SleepForSeconds(1); { std::lock_guard guard(controlMutex); for (auto& control : controls) { ControlID controlID = control.first; if (controlID < ControlIdFirstHardware) { delete control.second; continue; } if (hardwareParams.count(controlID) != 1) { continue; } HardwareParams* params = hardwareParams.at(controlID); if (!params) { continue; } logger->Info(Languages::TextUnloadingControl, controlID, params->GetName()); delete control.second; hardwareParams.erase(controlID); if (storage) { logger->Info(Languages::TextSaving, params->GetName()); TransactionGuard guard(storage); storage->Save(*params); } delete params; } } { Storage::TransactionGuard guard(storage); DeleteAllMapEntries(multipleUnits, multipleUnitMutex); DeleteAllMapEntries(locos, locoMutex); DeleteAllMapEntries(routes, routeMutex); DeleteAllMapEntries(clusters, clusterMutex); DeleteAllMapEntries(switches, switchMutex); DeleteAllMapEntries(tracks, trackMutex); DeleteAllMapEntries(signals, signalMutex); DeleteAllMapEntries(feedbacks, feedbackMutex); DeleteAllMapEntries(accessories, accessoryMutex); DeleteAllMapEntries(texts, textMutex); DeleteAllMapEntries(layers, layerMutex); } if (!storage) { return; } delete storage; storage = nullptr; } /*************************** * Booster * ***************************/ void Manager::Booster(const ControlType controlType, const BoosterState state) { if (!run) { return; } boosterState = state; { std::lock_guard guard(controlMutex); for (auto& control : controls) { control.second->Booster(controlType, state); } } if (boosterState != BoosterStateGo || initLocosDone) { return; } __attribute__((unused)) auto r = std::async(std::launch::async, InitLocosStatic, this); initLocosDone = true; } void Manager::InitLocos() { if (GetStartupInitLocos() == StartupInitLocosNone) { return; } Utils::Utils::SleepForSeconds(1); map locoIds = LocoIdsByName(); for (auto locoId : locoIds) { if (!run) { return; } Loco* loco = GetLoco(locoId.second); if (!loco) { continue; } if (GetStartupInitLocos() == StartupInitLocosSpeed) { std::lock_guard guard(controlMutex); for (auto& control : controls) { control.second->LocoBaseSpeed(ControlTypeInternal, loco, loco->GetSpeed()); control.second->LocoBaseOrientation(ControlTypeInternal, loco, loco->GetOrientation()); } } else { std::vector functions = loco->GetFunctionStates(); std::lock_guard guard(controlMutex); for (auto& control : controls) { control.second->LocoBaseSpeedOrientationFunctions(loco, loco->GetSpeed(), loco->GetOrientation(), functions); } } Utils::Utils::SleepForMilliseconds(10); } } /*************************** * Control * ***************************/ bool Manager::ControlSave(ControlID controlID, const HardwareType& hardwareType, const std::string& name, const std::string& arg1, const std::string& arg2, const std::string& arg3, const std::string& arg4, const std::string& arg5, string& result) { if (controlID != ControlIdNone && controlID < ControlIdFirstHardware) { result = Languages::GetText(Languages::TextInvalidControlID); return false; } HardwareParams* params = GetHardware(controlID); bool newControl = false; if (!params) { params = CreateAndAddControl(); newControl = true; } if (!params) { result = Languages::GetText(Languages::TextUnableToAddControl); return false; } // if we have a new object we have to update controlID controlID = params->GetControlID(); params->SetName(CheckObjectName(hardwareParams, hardwareMutex, controlID, name.size() == 0 ? "C" : name)); params->SetHardwareType(hardwareType); params->SetArg1(arg1); params->SetArg2(arg2); params->SetArg3(arg3); params->SetArg4(arg4); params->SetArg5(arg5); if (storage) { TransactionGuard guard(storage); storage->Save(*params); } if (newControl) { std::lock_guard Guard(controlMutex); ControlInterface* control = new HardwareHandler(params); if (!control) { return false; } controls[controlID] = control; return true; } ControlInterface* control = GetControl(controlID); if (!control) { return false; } control->ReInit(params); return true; } bool Manager::ControlDelete(ControlID controlID) { { std::lock_guard guard(hardwareMutex); if (controlID < ControlIdFirstHardware || hardwareParams.count(controlID) != 1) { return false; } HardwareParams* params = hardwareParams.at(controlID); if (!params) { return false; } hardwareParams.erase(controlID); delete params; } { std::lock_guard guard(controlMutex); if (controls.count(controlID) != 1) { return false; } ControlInterface* control = controls.at(controlID); if (!control) { return false; } controls.erase(controlID); delete control; } if (storage) { TransactionGuard guard(storage); storage->DeleteHardwareParams(controlID); } return true; } HardwareParams* Manager::GetHardware(const ControlID controlID) { std::lock_guard guard(hardwareMutex); if (hardwareParams.count(controlID) != 1) { return nullptr; } return hardwareParams.at(controlID); } unsigned int Manager::ControlsOfHardwareType(const HardwareType hardwareType) { std::lock_guard guard(hardwareMutex); unsigned int counter = 0; for (auto& hardwareParam : hardwareParams) { if (hardwareParam.second->GetHardwareType() == hardwareType) { ++counter; } } return counter; } bool Manager::ControlIsOfHardwareType(const ControlID controlID, const HardwareType hardwareType) { std::lock_guard guard(hardwareMutex); for (auto& hardwareParam : hardwareParams) { if (hardwareParam.second->GetControlID() != controlID) { continue; } return hardwareParam.second->GetHardwareType() == hardwareType; } return false; } ControlInterface* Manager::GetControl(const ControlID controlID) const { std::lock_guard guard(controlMutex); if (controls.count(controlID) != 1) { return nullptr; } return controls.at(controlID); } const std::string Manager::GetControlName(const ControlID controlID) { std::lock_guard guard(controlMutex); if (controls.count(controlID) != 1) { return unknownControl; } ControlInterface* control = controls.at(controlID); return control->GetName(); } const std::map Manager::ControlListNames(const Hardware::Capabilities capability) const { std::map ret; std::lock_guard guard(hardwareMutex); for (auto& hardware : hardwareParams) { std::lock_guard guard2(controlMutex); if (controls.count(hardware.second->GetControlID()) != 1) { continue; } ControlInterface* c = controls.at(hardware.second->GetControlID()); if (!c->CanHandle(capability)) { continue; } ret[hardware.first] = hardware.second->GetName(); } return ret; } const map Manager::ControlListByName() const { map out; std::lock_guard guard(hardwareMutex); for (auto& hardware : hardwareParams) { out[hardware.second->GetName()] = hardware.second; } return out; } const std::map Manager::ProtocolsOfControl(const AddressType type, const ControlID controlID) const { std::map ret; { const ControlInterface* control = GetControl(controlID); if (!control || control->GetControlType() != ControlTypeHardware) { ret[ProtocolSymbols[ProtocolNone]] = ProtocolNone; return ret; } const HardwareHandler* hardware = static_cast(control); if (hardware->GetControlID() != controlID) { ret[ProtocolSymbols[ProtocolNone]] = ProtocolNone; return ret; } std::vector protocols; if (type == AddressTypeLoco) { hardware->LocoProtocols(protocols); } else { hardware->AccessoryProtocols(protocols); } for (auto protocol : protocols) { ret[ProtocolSymbols[protocol]] = protocol; } } return ret; } /*************************** * Loco * ***************************/ string Manager::GetLocoList() const { string out; std::lock_guard guard(locoMutex); for (auto& loco : locos) { Loco* l = loco.second; out += to_string(l->GetID()) + ";"; out += to_string(l->GetSpeed()) + ";"; out += to_string(l->GetOrientation()) + ";"; out += to_string(l->GetTrackId()) + ";"; out += l->GetName() + "\n"; } return out; } Loco* Manager::GetLoco(const LocoID locoID) const { std::lock_guard guard(locoMutex); if (locos.count(locoID) != 1) { return nullptr; } return locos.at(locoID); } LocoConfig Manager::GetLocoOfConfigByMatchKey(const ControlID controlId, const string& matchKey) const { ControlInterface* control = GetControl(controlId); if (!control) { return LocoConfig(LocoTypeLoco); } return control->GetLocoByMatchKey(matchKey); } Loco* Manager::GetLocoByMatchKey(const ControlID controlId, const string& matchKey) const { std::lock_guard guard(locoMutex); for (auto& loco : locos) { Loco* locoConfig = loco.second; if (locoConfig->GetControlID() == controlId && locoConfig->GetMatchKey().compare(matchKey) == 0) { return loco.second; } } return nullptr; } void Manager::LocoRemoveMatchKey(const LocoID locoId) { Loco* loco = GetLoco(locoId); if (!loco) { return; } loco->ClearMatchKey(); } void Manager::LocoReplaceMatchKey(const LocoID locoId, const std::string& newMatchKey) { Loco* loco = GetLoco(locoId); if (!loco) { return; } loco->SetMatchKey(newMatchKey); } const map Manager::GetUnmatchedLocosOfControl(const ControlID controlId, const std::string& matchKey) const { ControlInterface* control = GetControl(controlId); map out; if (!control || !control->CanHandle(Hardware::CapabilityLocoDatabase)) { return out; } out = control->GetUnmatchedLocos(matchKey); out[""].SetName(""); return out; } Loco* Manager::GetLoco(const ControlID controlID, const Protocol protocol, const Address address) const { std::lock_guard guard(locoMutex); for (auto& loco : locos) { if (loco.second->GetControlID() == controlID && loco.second->GetProtocol() == protocol && loco.second->GetAddress() == address) { return loco.second; } } return nullptr; } const map Manager::LocoBaseListFree() const { map out; { std::lock_guard guard(locoMutex); for (auto& loco : locos) { if (!loco.second->IsInUse()) { out[loco.second->GetName()] = loco.second->GetID(); } } } { std::lock_guard guard(multipleUnitMutex); for (auto& multipleUnit : multipleUnits) { if (!multipleUnit.second->IsInUse()) { out[multipleUnit.second->GetName()] = multipleUnit.second->GetID() + MultipleUnitIdPrefix; } } } return out; } const map Manager::LocoConfigByName() const { map out; { std::lock_guard guard(locoMutex); for (auto& loco : locos) { out[loco.second->GetName()] = *(loco.second); } } std::lock_guard guard(controlMutex); for (auto& control : controls) { control.second->AddUnmatchedLocos(out); } return out; } const map Manager::LocoIdsByName() const { map out; { std::lock_guard guard(locoMutex); for (auto& loco : locos) { Loco* locoEntry = loco.second; out[locoEntry->GetName()] = locoEntry->GetID(); } } return out; } bool Manager::LocoSave(LocoID locoID, const string& name, const ControlID controlID, const std::string& matchKey, const Protocol protocol, const Address address, const Address serverAddress, const Length length, const bool pushpull, const Speed maxSpeed, const Speed travelSpeed, const Speed reducedSpeed, const Speed creepingSpeed, const Propulsion propulsion, const TrainType type, const std::vector& locoFunctions, string& result) { if (!CheckControlLocoProtocolAddress(controlID, protocol, address, result)) { return false; } Loco* loco = GetLoco(locoID); if (!loco) { loco = CreateAndAddObject(locos, locoMutex); } if (!loco) { result = Languages::GetText(Languages::TextUnableToAddLoco); return false; } // if we have a new object we have to update locoID locoID = loco->GetID(); loco->SetName(CheckObjectName(multipleUnits, multipleUnitMutex, MultipleUnitNone, CheckObjectName(locos, locoMutex, locoID, name.size() == 0 ? "L" : name))); loco->SetControlID(controlID); loco->SetMatchKey(matchKey); loco->SetProtocol(protocol); loco->SetAddress(address); loco->SetServerAddress(serverAddress); loco->SetLength(length); loco->SetPushpull(pushpull); loco->SetMaxSpeed(maxSpeed); loco->SetTravelSpeed(travelSpeed); loco->SetReducedSpeed(reducedSpeed); loco->SetCreepingSpeed(creepingSpeed); loco->SetPropulsion(propulsion); loco->SetTrainType(type); loco->ConfigureFunctions(locoFunctions); // save in db LocoSave(loco); std::lock_guard guard(controlMutex); for (auto& control : controls) { control.second->LocoSettings(locoID, name, matchKey); } return true; } bool Manager::LocoDelete(const LocoID locoID, string& result) { Loco* loco = nullptr; { std::lock_guard guard(locoMutex); if (locoID == LocoNone || locos.count(locoID) != 1) { result = Languages::GetText(Languages::TextLocoDoesNotExist); return false; } loco = locos.at(locoID); if (!loco) { result = Languages::GetText(Languages::TextLocoDoesNotExist); return false; } if (loco->IsInUse()) { result = Logger::Logger::Format(Languages::GetText(Languages::TextLocoIsInUse), loco->GetName()); return false; } locos.erase(locoID); } if (storage) { TransactionGuard guard(storage); storage->DeleteLoco(locoID); } const string& name = loco->GetName(); const string& matchKey = loco->GetMatchKey(); std::lock_guard guard(controlMutex); for (auto& control : controls) { control.second->LocoDelete(locoID, name, matchKey); } delete loco; return true; } bool Manager::LocoProtocolAddress(const LocoID locoID, ControlID& controlID, Protocol& protocol, Address& address) const { std::lock_guard guard(locoMutex); if (locos.count(locoID) != 1) { controlID = 0; protocol = ProtocolNone; address = 0; return false; } Loco* loco = locos.at(locoID); if (!loco) { return false; } controlID = loco->GetControlID(); protocol = loco->GetProtocol(); address = loco->GetAddress(); return true; } bool Manager::LocoBaseSpeed(const ControlType controlType, LocoBase* loco, const Speed speed) { if (!loco) { return false; } Speed newSpeed = speed; if (speed > MaxSpeed) { newSpeed = MaxSpeed; } const string& locoName = loco->GetName(); logger->Info(Languages::TextLocoSpeedIs, locoName, newSpeed); const Speed oldSpeed = loco->GetSpeed(); if (oldSpeed == newSpeed) { return true; } loco->SetSpeed(newSpeed); std::lock_guard guard(controlMutex); for (auto& control : controls) { control.second->LocoBaseSpeed(controlType, loco, newSpeed); } return true; } Speed Manager::LocoSpeed(const LocoID locoID) const { Loco* loco = GetLoco(locoID); if (!loco) { return MinSpeed; } return loco->GetSpeed(); } void Manager::LocoBaseOrientation(const ControlType controlType, LocoBase* loco, Orientation orientation) { if (!loco) { return; } if (orientation == OrientationChange) { const Orientation oldOrientation = loco->GetOrientation(); orientation = (oldOrientation == OrientationLeft ? OrientationRight : OrientationLeft); } logger->Info(orientation ? Languages::TextLocoDirectionOfTravelIsRight : Languages::TextLocoDirectionOfTravelIsLeft, loco->GetName()); const Orientation oldOrientation = loco->GetOrientation(); if (oldOrientation == orientation) { return; } loco->SetOrientation(orientation); std::lock_guard guard(controlMutex); for (auto& control : controls) { control.second->LocoBaseOrientation(controlType, loco, orientation); } } void Manager::LocoBaseFunctionState(const ControlType controlType, const ObjectIdentifier& locoBaseIdentifier, const DataModel::LocoFunctionNr function, const DataModel::LocoFunctionState on) { LocoBase* loco = GetLocoBase(locoBaseIdentifier); if (!loco) { return; } DataModel::LocoFunctionNr functionInternal = function; if (function >= NumberOfLocoFunctions) { functionInternal = loco->GetFunctionNumberFromFunctionIcon(static_cast(function - 256)); } LocoBaseFunctionState(controlType, loco, functionInternal, on); } void Manager::LocoBaseFunctionState(const ControlType controlType, LocoBase* loco, const DataModel::LocoFunctionNr function, const DataModel::LocoFunctionState on) { if (!loco) { return; } logger->Info(on ? Languages::TextLocoFunctionIsOn : Languages::TextLocoFunctionIsOff, loco->GetName(), function); const DataModel::LocoFunctionState oldOn = loco->GetFunctionState(function); if (oldOn == on) { return; } loco->SetFunctionState(function, on); std::lock_guard guard(controlMutex); for (auto& control : controls) { control.second->LocoBaseFunction(controlType, loco, function, on); } } /*************************** * MultipleUnit * ***************************/ MultipleUnit* Manager::GetMultipleUnit(const MultipleUnitID multipleUnitId) const { std::lock_guard guard(multipleUnitMutex); if (multipleUnits.count(multipleUnitId) != 1) { return nullptr; } return multipleUnits.at(multipleUnitId); } LocoConfig Manager::GetMultipleUnitOfConfigByMatchKey(const ControlID controlId, const string& matchKey) const { ControlInterface* control = GetControl(controlId); if (!control) { return LocoConfig(LocoTypeMultipleUnit); } return control->GetMultipleUnitByMatchKey(matchKey); } const map Manager::MultipleUnitConfigByName() const { map out; { std::lock_guard guard(multipleUnitMutex); for (auto& multipleUnit : multipleUnits) { out[multipleUnit.second->GetName()] = *(multipleUnit.second); } } std::lock_guard guard(controlMutex); for (auto& control : controls) { control.second->AddUnmatchedMultipleUnits(out); } return out; } bool Manager::MultipleUnitSave(MultipleUnitID multipleUnitID, const string& name, const ControlID controlID, const std::string& matchKey, const Address address, const Address serverAddress, const Length length, const bool pushpull, const Speed maxSpeed, const Speed travelSpeed, const Speed reducedSpeed, const Speed creepingSpeed, const TrainType type, const std::vector& locoFunctions, const std::vector& slaves, string& result) { if (!CheckControlMultipleUnitProtocolAddress(controlID, address, result)) { return false; } MultipleUnit* multipleUnit = GetMultipleUnit(multipleUnitID); if (!multipleUnit) { multipleUnit = CreateAndAddObject(multipleUnits, multipleUnitMutex); } if (!multipleUnit) { result = Languages::GetText(Languages::TextUnableToAddMultipleUnit); return false; } // if we have a new object we have to update multipleUnitID if (multipleUnitID == 0) { multipleUnitID = multipleUnit->GetID(); for (auto slave : slaves) { slave->ObjectID1(multipleUnitID); } } // FIXME: replace "M" with language dependent word multipleUnit->SetName(CheckObjectName(locos, locoMutex, LocoNone, CheckObjectName(multipleUnits, multipleUnitMutex, multipleUnitID, name.size() == 0 ? "M" : name))); multipleUnit->SetControlID(controlID); multipleUnit->SetMatchKey(matchKey); multipleUnit->SetProtocol(ProtocolNone); multipleUnit->SetAddress(address); multipleUnit->SetServerAddress(serverAddress); multipleUnit->SetLength(length); multipleUnit->SetPushpull(pushpull); multipleUnit->SetMaxSpeed(maxSpeed); multipleUnit->SetTravelSpeed(travelSpeed); multipleUnit->SetReducedSpeed(reducedSpeed); multipleUnit->SetCreepingSpeed(creepingSpeed); multipleUnit->SetTrainType(type); multipleUnit->ConfigureFunctions(locoFunctions); multipleUnit->AssignSlaves(slaves); // save in db MultipleUnitSave(multipleUnit); std::lock_guard guard(controlMutex); for (auto& control : controls) { control.second->MultipleUnitSettings(multipleUnitID, name, matchKey); } return true; } bool Manager::MultipleUnitDelete(const MultipleUnitID multipleUnitID, string& result) { MultipleUnit* multipleUnit = nullptr; { std::lock_guard guard(multipleUnitMutex); if (multipleUnitID == MultipleUnitNone || multipleUnits.count(multipleUnitID) != 1) { result = Languages::GetText(Languages::TextMultipleUnitDoesNotExist); return false; } multipleUnit = multipleUnits.at(multipleUnitID); if (!multipleUnit) { result = Languages::GetText(Languages::TextMultipleUnitDoesNotExist); return false; } if (multipleUnit->IsInUse()) { result = Logger::Logger::Format(Languages::GetText(Languages::TextMultipleUnitIsInUse), multipleUnit->GetName()); return false; } multipleUnits.erase(multipleUnitID); } if (storage) { TransactionGuard guard(storage); storage->DeleteMultipleUnit(multipleUnitID); } const string& name = multipleUnit->GetName(); const string& matchKey = multipleUnit->GetMatchKey(); std::lock_guard guard(controlMutex); for (auto& control : controls) { control.second->MultipleUnitDelete(multipleUnitID, name, matchKey); } delete multipleUnit; return true; } const map Manager::LocoBaseIdsByName() const { map out = LocoIdsByName(); { std::lock_guard guard(multipleUnitMutex); for (auto& multipleUnit : multipleUnits) { MultipleUnit* multipleUnitEntry = multipleUnit.second; out[multipleUnitEntry->GetName()] = multipleUnitEntry->GetID() + MultipleUnitIdPrefix; } } return out; } const std::string& Manager::GetLocoBaseName(const ObjectIdentifier& locoBaseIdentifier) const { const LocoBase* locoBase = GetLocoBase(locoBaseIdentifier); if (!locoBase) { return unknownLoco; } return locoBase->GetName(); } /*************************** * Accessory Base * ***************************/ void Manager::AccessoryBaseState(const ControlType controlType, const ControlID controlID, const Protocol protocol, const Address address, const DataModel::AccessoryState state) { Accessory* accessory = GetAccessory(controlID, protocol, address, static_cast(state)); if (accessory) { AccessoryState(controlType, accessory, accessory->CalculateInvertedAccessoryState(state), true); return; } Switch* mySwitch = GetSwitch(controlID, protocol, address); if (mySwitch) { SwitchState(controlType, mySwitch, mySwitch->CalculateInvertedSwitchState(address, state), true); return; } Signal* signal = GetSignal(controlID, protocol, address); if (signal) { SignalState(controlType, signal, signal->CalculateMappedSignalState(address, state), true); return; } logger->Warning(Languages::TextAccessoryControlProtocolAddressDoesNotExist, controlID, Utils::Utils::ProtocolToString(protocol), address); } void Manager::AccessoryBaseState(const ControlType controlType, const ObjectIdentifier& identifier, const DataModel::AccessoryState state) { switch (identifier.GetObjectType()) { case ObjectTypeAccessory: AccessoryState(controlType, identifier.GetObjectID(), state); return; case ObjectTypeSwitch: SwitchState(controlType, identifier.GetObjectID(), state); return; case ObjectTypeSignal: SignalState(controlType, identifier.GetObjectID(), state); return; default: return; } } /*************************** * Accessory * ***************************/ bool Manager::AccessoryState(const ControlType controlType, const AccessoryID accessoryID, const DataModel::AccessoryState state, const bool force) { if (boosterState == BoosterStateStop) { return false; } Accessory* accessory = GetAccessory(accessoryID); AccessoryState(controlType, accessory, state, force); return true; } void Manager::AccessoryState(const ControlType controlType, Accessory* accessory, const DataModel::AccessoryState state, const bool force) { if (!accessory) { return; } if (!force && accessory->IsInUse()) { logger->Warning(Languages::TextAccessoryIsLocked, accessory->GetName()); return; } if (!GetExecuteAccessory()) { const DataModel::AccessoryState oldState = accessory->GetAccessoryState(); if (oldState == state) { return; } } accessory->SetAccessoryState(state); std::lock_guard guard(controlMutex); for (auto& control : controls) { control.second->AccessoryState(controlType, accessory); } } const map Manager::GetUnmatchedAccessoriesOfControl(const ControlID controlId, const std::string& matchKey) const { ControlInterface* control = GetControl(controlId); map out; if (!control || !control->CanHandle(Hardware::CapabilityAccessoryDatabase)) { return out; } out = control->GetUnmatchedAccessories(matchKey); out[""].SetName(""); return out; } Accessory* Manager::GetAccessory(const AccessoryID accessoryID) const { std::lock_guard guard(accessoryMutex); if (accessories.count(accessoryID) != 1) { return nullptr; } return accessories.at(accessoryID); } Accessory* Manager::GetAccessory(const ControlID controlID, const Protocol protocol, const Address address, const AddressPort port) const { std::lock_guard guard(accessoryMutex); for (auto& a : accessories) { Accessory* accessory = a.second; if (accessory->GetControlID() == controlID && accessory->GetProtocol() == protocol && accessory->GetAddress() == address) { switch (accessory->GetAccessoryType() & DataModel::AccessoryTypeConnectionMask) { case AccessoryTypeOnOn: return accessory; case AccessoryTypeOnPush: case AccessoryTypeOnOff: if (accessory->GetPort() == port) { return accessory; } break; default: break; } } } return nullptr; } const std::string& Manager::GetAccessoryName(const AccessoryID accessoryID) const { std::lock_guard guard(accessoryMutex); if (accessories.count(accessoryID) != 1) { return unknownAccessory; } return accessories.at(accessoryID)->GetName(); } bool Manager::AccessorySave(AccessoryID accessoryID, const string& name, const LayoutPosition posX, const LayoutPosition posY, const LayoutPosition posZ, const LayoutRotation rotation, const ControlID controlID, const std::string& matchKey, const Protocol protocol, const Address address, const AddressPort port, const Address serverAddress, const DataModel::AccessoryType type, const DataModel::AccessoryPulseDuration duration, const bool inverted, string& result) { if (!CheckControlAccessoryProtocolAddress(controlID, protocol, address, result)) { return false; } Accessory* accessory = GetAccessory(accessoryID); if (!CheckLayoutItemPosition(accessory, posX, posY, posZ, result)) { return false; } if (!accessory) { accessory = CreateAndAddObject(accessories, accessoryMutex); } if (!accessory) { result = Languages::GetText(Languages::TextUnableToAddAccessory); return false; } // if we have a new object we have to update accessoryID accessoryID = accessory->GetID(); // update existing accessory accessory->SetName(CheckObjectName(accessories, accessoryMutex, accessoryID, name.size() == 0 ? "A" : name)); accessory->SetPosX(posX); accessory->SetPosY(posY); accessory->SetPosZ(posZ); accessory->SetRotation(rotation); accessory->SetControlID(controlID); accessory->SetMatchKey(matchKey); accessory->SetProtocol(protocol); accessory->SetAddress(address); accessory->SetPort(port); accessory->SetServerAddress(serverAddress); accessory->SetAccessoryType(type); accessory->SetAccessoryPulseDuration(duration); accessory->SetInverted(inverted); AccessorySaveAndPublishSettings(accessory); return true; } bool Manager::AccessoryPosition(const AccessoryID accessoryID, const LayoutPosition posX, const LayoutPosition posY, string& result) { Accessory* accessory = GetAccessory(accessoryID); if (!accessory) { result = Languages::GetText(Languages::TextAccessoryDoesNotExist); return false; } if (!CheckLayoutItemPosition(accessory, posX, posY, accessory->GetPosZ(), result)) { return false; } accessory->SetPosX(posX); accessory->SetPosY(posY); AccessorySaveAndPublishSettings(accessory); return true; } bool Manager::AccessoryRotate(const AccessoryID accessoryID, string& result) { Accessory* accessory = GetAccessory(accessoryID); if (!accessory) { result = Languages::GetText(Languages::TextAccessoryDoesNotExist); return false; } LayoutRotation newRotation = accessory->GetRotation(); ++newRotation; if (!CheckLayoutItemPosition(accessory, accessory->GetPosX(), accessory->GetPosY(), accessory->GetPosZ(), LayoutItem::Width1, LayoutItem::Height1, newRotation, result)) { return false; } accessory->SetRotation(newRotation); AccessorySaveAndPublishSettings(accessory); return true; } void Manager::AccessorySaveAndPublishSettings(const Accessory* const accessory) { // save in db if (storage) { TransactionGuard guard(storage); storage->Save(*accessory); } std::lock_guard guard(controlMutex); for (auto& control : controls) { control.second->AccessorySettings(accessory->GetID(), accessory->GetName(), accessory->GetMatchKey()); } } const map Manager::AccessoryConfigByName() const { map out; { std::lock_guard guard(accessoryMutex); for (auto& accessory : accessories) { out[accessory.second->GetName()] = *(accessory.second); } } std::lock_guard guard(controlMutex); for (auto& control : controls) { control.second->AddUnmatchedAccessories(out); } return out; } bool Manager::AccessoryDelete(const AccessoryID accessoryID, string& result) { Accessory* accessory = nullptr; { std::lock_guard guard(accessoryMutex); if (accessoryID == AccessoryNone || accessories.count(accessoryID) != 1) { result = Languages::GetText(Languages::TextAccessoryDoesNotExist); return false; } accessory = accessories.at(accessoryID); if (!accessory) { result = Languages::GetText(Languages::TextAccessoryDoesNotExist); return false; } ObjectIdentifier accessoryIdentifier(ObjectTypeAccessory, accessoryID); if (ObjectIsPartOfRoute(accessoryIdentifier, accessory, result)) { return false; } accessories.erase(accessoryID); } if (storage) { TransactionGuard guard(storage); storage->DeleteAccessory(accessoryID); } const string& name = accessory->GetName(); const string& matchKey = accessory->GetMatchKey(); std::lock_guard guard(controlMutex); for (auto& control : controls) { control.second->AccessoryDelete(accessoryID, name, matchKey); } delete accessory; return true; } bool Manager::AccessoryRelease(const AccessoryID accessoryID) { Accessory* accessory = GetAccessory(accessoryID); if (!accessory) { return false; } return accessory->Release(logger, accessory->GetLocoBase()); } AccessoryConfig Manager::GetAccessoryOfConfigByMatchKey(const ControlID controlId, const string& matchKey) const { ControlInterface* control = GetControl(controlId); if (!control) { return AccessoryConfig(); } return control->GetAccessoryByMatchKey(matchKey); } Accessory* Manager::GetAccessoryByMatchKey(const ControlID controlId, const string& matchKey) const { std::lock_guard guard(accessoryMutex); for (auto& accessory : accessories) { Accessory* accessoryConfig = accessory.second; if (accessoryConfig->GetControlID() == controlId && accessoryConfig->GetMatchKey().compare(matchKey) == 0) { return accessoryConfig; } } return nullptr; } void Manager::AccessoryRemoveMatchKey(const AccessoryID accessoryId) { Accessory* accessory = GetAccessory(accessoryId); if (!accessory) { return; } accessory->ClearMatchKey(); } /*************************** * Feedback * ***************************/ void Manager::FeedbackState(const ControlID controlID, const FeedbackPin pin, const DataModel::Feedback::FeedbackState state) { Feedback* feedback = GetFeedback(controlID, pin); if (feedback) { FeedbackState(feedback, state); return; } if (!GetAutoAddFeedback()) { return; } string name = "Feedback auto added " + std::to_string(controlID) + "/" + std::to_string(pin); logger->Info(Languages::TextAddingFeedback, name); string result; FeedbackSave(FeedbackNone, name, DataModel::LayoutItem::VisibleNo, 0, 0, 0, DataModel::LayoutItem::Rotation0, controlID, "", pin, false, FeedbackTypeDefault, RouteNone, result); } void Manager::FeedbackState(const FeedbackID feedbackID, const DataModel::Feedback::FeedbackState state) { Feedback* feedback = GetFeedback(feedbackID); if (!feedback) { return; } FeedbackState(feedback, state); } void Manager::FeedbackPublishState(const Feedback* feedback) { if (!feedback) { return; } DataModel::Feedback::FeedbackState state = feedback->GetState(); const string& feedbackName = feedback->GetName(); logger->Info(state ? Languages::TextFeedbackStateIsOn : Languages::TextFeedbackStateIsOff, feedbackName); const FeedbackID feedbackID = feedback->GetID(); { std::lock_guard guard(controlMutex); for (auto& control : controls) { control.second->FeedbackState(feedbackName, feedbackID, state); } } } Feedback* Manager::GetFeedback(const FeedbackID feedbackID) const { std::lock_guard guard(feedbackMutex); return GetFeedbackUnlocked(feedbackID); } Feedback* Manager::GetFeedbackUnlocked(const FeedbackID feedbackID) const { if (feedbacks.count(feedbackID) != 1) { return nullptr; } return feedbacks.at(feedbackID); } Feedback* Manager::GetFeedback(const ControlID controlID, const FeedbackPin pin) const { std::lock_guard guard(feedbackMutex); for (auto& feedback : feedbacks) { if (feedback.second->GetControlID() == controlID && feedback.second->GetPin() == pin) { return feedback.second; } } return nullptr; } const std::string& Manager::GetFeedbackName(const FeedbackID feedbackID) const { std::lock_guard guard(feedbackMutex); if (feedbacks.count(feedbackID) != 1) { return unknownFeedback; } return feedbacks.at(feedbackID)->GetName(); } const map Manager::FeedbackConfigByName() const { map out; { std::lock_guard guard(feedbackMutex); for (auto& feedback : feedbacks) { out[feedback.second->GetName()] = *(feedback.second); } } std::lock_guard guard(controlMutex); for (auto& control : controls) { control.second->AddUnmatchedFeedbacks(out); } return out; } bool Manager::FeedbackSave(FeedbackID feedbackID, const std::string& name, const Visible visible, const LayoutPosition posX, const LayoutPosition posY, const LayoutPosition posZ, const LayoutRotation rotation, const ControlID controlID, const string& matchKey, const FeedbackPin pin, const bool inverted, const FeedbackType feedbackType, const RouteID routeId, string& result) { Feedback* feedback = GetFeedback(feedbackID); if (visible && !CheckLayoutItemPosition(feedback, posX, posY, posZ, result)) { return false; } if (!feedback) { feedback = CreateAndAddObject(feedbacks, feedbackMutex); } if (!feedback) { result = Languages::GetText(Languages::TextUnableToAddFeedback); return false; } // if we have a new object we have to update feedbackID feedbackID = feedback->GetID(); feedback->SetName(CheckObjectName(feedbacks, feedbackMutex, feedbackID, name.size() == 0 ? "F" : name)); feedback->SetVisible(visible); feedback->SetPosX(posX); feedback->SetPosY(posY); feedback->SetPosZ(posZ); feedback->SetRotation(rotation); feedback->SetControlID(controlID); feedback->SetMatchKey(matchKey); feedback->SetPin(pin); feedback->SetInverted(inverted); feedback->SetFeedbackType(feedbackType); feedback->SetRouteId(routeId); FeedbackSaveAndPublishSettings(feedback); return true; } bool Manager::FeedbackPosition(const FeedbackID feedbackID, const LayoutPosition posX, const LayoutPosition posY, string& result) { Feedback* feedback = GetFeedback(feedbackID); if (!feedback) { result = Languages::GetText(Languages::TextFeedbackDoesNotExist); return false; } if (!feedback->GetVisible()) { return false; } if (!CheckLayoutItemPosition(feedback, posX, posY, feedback->GetPosZ(), result)) { return false; } feedback->SetPosX(posX); feedback->SetPosY(posY); FeedbackSaveAndPublishSettings(feedback); return true; } bool Manager::FeedbackRotate(const FeedbackID feedbackID, string& result) { Feedback* feedback = GetFeedback(feedbackID); if (!feedback) { result = Languages::GetText(Languages::TextFeedbackDoesNotExist); return false; } LayoutRotation newRotation = feedback->GetRotation(); ++newRotation; if (!CheckLayoutItemPosition(feedback, feedback->GetPosX(), feedback->GetPosY(), feedback->GetPosZ(), LayoutItem::Width1, LayoutItem::Height1, newRotation, result)) { return false; } feedback->SetRotation(newRotation); FeedbackSaveAndPublishSettings(feedback); return true; } void Manager::FeedbackSaveAndPublishSettings(const Feedback* const feedback) { // save in db if (storage) { TransactionGuard guard(storage); storage->Save(*feedback); } std::lock_guard guard(controlMutex); for (auto& control : controls) { control.second->FeedbackSettings(feedback->GetID(), feedback->GetName()); } } const map Manager::FeedbackListByName() const { map out; std::lock_guard guard(feedbackMutex); for (auto& feedback : feedbacks) { out[feedback.second->GetName()] = feedback.second; } return out; } const map Manager::RoutesOfTrack(const TrackID trackID) const { map out; const Track* track = GetTrack(trackID); if (!track) { return out; } const vector routesOfTrack = track->GetRoutes(); for (auto route : routesOfTrack) { out[route->GetID()] = route->GetName(); } return out; } const map Manager::FeedbacksOfTrack(const TrackID trackID) const { map out; const Track* track = GetTrack(trackID); if (!track) { return out; } const vector feedbacksOfTrack = track->GetFeedbacks(); for (auto feedbackRelation: feedbacksOfTrack) { const FeedbackID feedbackID = feedbackRelation->ObjectID2(); Feedback* feedback = GetFeedback(feedbackID); if (!feedback) { continue; } out[feedback->GetName()] = feedbackID; } return out; } bool Manager::FeedbackDelete(const FeedbackID feedbackID, string& result) { Feedback* feedback = nullptr; { std::lock_guard guard(feedbackMutex); if (feedbackID == FeedbackNone || feedbacks.count(feedbackID) != 1) { result = Languages::GetText(Languages::TextFeedbackDoesNotExist); return false; } feedback = feedbacks.at(feedbackID); Track* track = feedback->GetTrack(); if (track) { result = Logger::Logger::Format(Languages::GetText(Languages::TextFeedbackIsUsedByTrack), feedback->GetName(), track->GetName()); return false; } feedbacks.erase(feedbackID); } if (storage) { TransactionGuard guard(storage); storage->DeleteFeedback(feedbackID); } const string& name = feedback->GetName(); std::lock_guard guard(controlMutex); for (auto& control : controls) { control.second->FeedbackDelete(feedbackID, name); } delete feedback; return true; } FeedbackConfig Manager::GetFeedbackOfConfigByMatchKey(const ControlID controlId, const string& matchKey) const { ControlInterface* control = GetControl(controlId); if (!control) { return FeedbackConfig(); } return control->GetFeedbackByMatchKey(matchKey); } Feedback* Manager::GetFeedbackByMatchKey(const ControlID controlId, const string& matchKey) const { std::lock_guard guard(feedbackMutex); for (auto& feedback : feedbacks) { Feedback* feedbackConfig = feedback.second; if (feedbackConfig->GetControlID() == controlId && feedbackConfig->GetMatchKey().compare(matchKey) == 0) { return feedback.second; } } return nullptr; } void Manager::FeedbackRemoveMatchKey(const FeedbackID feedbackId) { Feedback* feedback = GetFeedback(feedbackId); if (!feedback) { return; } feedback->ClearMatchKey(); } void Manager::FeedbackReplaceMatchKey(const FeedbackID feedbackId, const std::string& newMatchKey) { Feedback* feedback = GetFeedback(feedbackId); if (!feedback) { return; } feedback->SetMatchKey(newMatchKey); } const map Manager::GetUnmatchedFeedbacksOfControl(const ControlID controlId, const std::string& matchKey) const { ControlInterface* control = GetControl(controlId); map out; if (!control || !control->CanHandle(Hardware::CapabilityFeedbackDatabase)) { return out; } out = control->GetUnmatchedFeedbacks(matchKey); out[""].SetName(""); return out; } /*************************** * Track * ***************************/ Track* Manager::GetTrack(const TrackID trackID) const { std::lock_guard guard(trackMutex); if (tracks.count(trackID) != 1) { return nullptr; } return tracks.at(trackID); } const std::string& Manager::GetTrackName(const TrackID trackID) const { if (tracks.count(trackID) != 1) { return unknownTrack; } return tracks.at(trackID)->GetName(); } const map Manager::TrackListByName() const { map out; std::lock_guard guard(trackMutex); for (auto& track : tracks) { out[track.second->GetName()] = track.second; } return out; } const map Manager::TrackListMasterByName() const { map out; std::lock_guard guard(trackMutex); for (auto& t : tracks) { Track* track = t.second; if (track && !track->GetMain()) { out[track->GetName()] = track; } } return out; } const map Manager::TrackListIdByName(const TrackID excludeTrackID) const { map out; std::lock_guard guard(trackMutex); for (auto& t : tracks) { Track* track = t.second; const TrackID trackID = track->GetID(); if (trackID == excludeTrackID) { continue; } // exclude extension tracks if (track->GetMain()) { continue; } out[track->GetName()] = trackID; } return out; } bool Manager::TrackSave(TrackID trackID, const std::string& name, const bool showName, const std::string& displayName, const LayoutPosition posX, const LayoutPosition posY, const LayoutPosition posZ, const LayoutItemSize height, const LayoutRotation rotation, const DataModel::TrackType trackType, const TrackID main, const vector& newFeedbacks, const vector& newSignals, const DataModel::SelectRouteApproach selectRouteApproach, const bool allowLocoTurn, const bool releaseWhenFree, string& result) { Track* track = GetTrack(trackID); if (!CheckLayoutItemPosition(track, posX, posY, posZ, LayoutItem::Width1, height, rotation, result)) { return false; } if (!track) { track = CreateAndAddObject(tracks, trackMutex); } if (!track) { result = Languages::GetText(Languages::TextUnableToAddTrack); return false; } // if we have a new object we have to update trackID if (trackID == TrackNone) { trackID = track->GetID(); for (auto feedback : newFeedbacks) { feedback->ObjectID1(trackID); } for (auto signal : newSignals) { signal->ObjectID1(trackID); } } // update existing track track->SetName(CheckObjectName(tracks, trackMutex, trackID, name.size() == 0 ? "T" : name)); track->SetShowName(showName); track->SetDisplayName(displayName); track->SetHeight(height); track->SetRotation(rotation); track->SetPosX(posX); track->SetPosY(posY); track->SetPosZ(posZ); track->SetTrackType(trackType); Track* oldMain = track->GetMain(); if (oldMain) { oldMain->DeleteExtension(track); } Track* newMain = GetTrack(main); if (newMain && !newMain->GetMain() && newMain->AddExtension(track)) { track->SetMain(newMain); track->SetName(newMain->GetName() + "_ext_" + to_string(trackID)); } track->AssignFeedbacks(newFeedbacks); track->AssignSignals(newSignals); track->SetSelectRouteApproach(selectRouteApproach); track->SetAllowLocoTurn(allowLocoTurn); track->SetReleaseWhenFree(releaseWhenFree); TrackSaveAndPublishSettings(track); return true; } bool Manager::TrackPosition(const TrackID trackID, const LayoutPosition posX, const LayoutPosition posY, string& result) { Track* track = GetTrack(trackID); if (!track) { result = Languages::GetText(Languages::TextTrackDoesNotExist); return false; } if (!CheckLayoutItemPosition(track, posX, posY, track->GetPosZ(), LayoutItem::Width1, track->GetHeight(), track->GetRotation(), result)) { return false; } track->SetPosX(posX); track->SetPosY(posY); TrackSaveAndPublishSettings(track); return true; } bool Manager::TrackRotate(const TrackID trackID, string& result) { Track* track = GetTrack(trackID); if (!track) { result = Languages::GetText(Languages::TextTrackDoesNotExist); return false; } LayoutRotation newRotation = track->GetRotation(); ++newRotation; if (!CheckLayoutItemPosition(track, track->GetPosX(), track->GetPosY(), track->GetPosZ(), LayoutItem::Width1, track->GetHeight(), newRotation, result)) { return false; } track->SetRotation(newRotation); TrackSaveAndPublishSettings(track); return true; } void Manager::TrackSaveAndPublishSettings(const Track* const track) { TrackSave(track); std::lock_guard guard(controlMutex); for (auto& control : controls) { control.second->TrackSettings(track->GetID(), track->GetName()); } } bool Manager::TrackDelete(const TrackID trackID, string& result) { Track* track = nullptr; { std::lock_guard guard(trackMutex); if (trackID == TrackNone || tracks.count(trackID) != 1) { result = Languages::GetText(Languages::TextTrackDoesNotExist); return false; } track = tracks.at(trackID); if (!track) { result = Languages::GetText(Languages::TextTrackDoesNotExist); return false; } if (track->IsInUse()) { result = Logger::Logger::Format(Languages::GetText(Languages::TextTrackIsUsedByLoco), track->GetName(), GetLocoBaseName(track->GetLocoBase())); return false; } Route* route = GetFirstRouteFromOrToTrack(trackID); if (route) { result = Logger::Logger::Format(Languages::GetText(Languages::TextTrackIsUsedByRoute), track->GetName(), route->GetName()); return false; } ObjectIdentifier trackIdentifier(ObjectTypeTrack, trackID); if (ObjectIsPartOfRoute(trackIdentifier, track, result)) { return false; } tracks.erase(trackID); } if (storage) { Storage::TransactionGuard guard(storage); storage->DeleteTrack(trackID); } const string& name = track->GetName(); std::lock_guard guard(controlMutex); for (auto& control : controls) { control.second->TrackDelete(trackID, name); } Cluster* cluster = track->GetCluster(); if (cluster) { cluster->DeleteTrack(track); } delete track; return true; } /*************************** * Switch * ***************************/ bool Manager::SwitchState(const ControlType controlType, const SwitchID switchID, const DataModel::AccessoryState state, const bool force) { if (boosterState == BoosterStateStop) { return false; } Switch* mySwitch = GetSwitch(switchID); SwitchState(controlType, mySwitch, state, force); return true; } void Manager::SwitchState(const ControlType controlType, Switch* mySwitch, const DataModel::AccessoryState state, const bool force) { if (!mySwitch) { return; } if (!force && mySwitch->IsInUse()) { logger->Warning(Languages::TextSwitchIsLocked, mySwitch->GetName()); return; } if (!GetExecuteAccessory()) { const DataModel::AccessoryState oldState = mySwitch->GetAccessoryState(); if (oldState == state) { return; } } mySwitch->SetAccessoryState(state); std::lock_guard guard(controlMutex); for (auto& control : controls) { control.second->SwitchState(controlType, mySwitch); } } Switch* Manager::GetSwitch(const SwitchID switchID) const { std::lock_guard guard(switchMutex); if (switches.count(switchID) != 1) { return nullptr; } return switches.at(switchID); } Switch* Manager::GetSwitch(const ControlID controlID, const Protocol protocol, const Address address) const { std::lock_guard guard(switchMutex); for (auto& mySwitch : switches) { Switch* s = mySwitch.second; if ((s->GetControlID() == controlID) && (s->GetProtocol() == protocol) && (s->UsesAddress(address))) { return s; } } return nullptr; } const std::string& Manager::GetSwitchName(const SwitchID switchID) const { if (switches.count(switchID) != 1) { return unknownSwitch; } return switches.at(switchID)->GetName(); } bool Manager::SwitchSave(SwitchID switchID, const string& name, const LayoutPosition posX, const LayoutPosition posY, const LayoutPosition posZ, const LayoutRotation rotation, const ControlID controlID, const string& matchKey, const Protocol protocol, const Address address, const Address serverAddress, const DataModel::AccessoryType type, const DataModel::AccessoryPulseDuration duration, const bool inverted, string& result) { if (!CheckControlAccessoryProtocolAddress(controlID, protocol, address, result)) { return false; } Switch* mySwitch = GetSwitch(switchID); if (!CheckLayoutItemPosition(mySwitch, posX, posY, posZ, LayoutItem::Width1, Switch::CalculateHeightFromType(type), rotation, result)) { return false; } if (!mySwitch) { mySwitch = CreateAndAddObject(switches, switchMutex); } if (!mySwitch) { result = Languages::GetText(Languages::TextUnableToAddSwitch); return false; } // if we have a new object we have to update switchID switchID = mySwitch->GetID(); // update existing switch mySwitch->SetName(CheckObjectName(switches, switchMutex, switchID, name.size() == 0 ? "S" : name)); mySwitch->SetPosX(posX); mySwitch->SetPosY(posY); mySwitch->SetPosZ(posZ); mySwitch->SetRotation(rotation); mySwitch->SetControlID(controlID); mySwitch->SetMatchKey(matchKey); mySwitch->SetProtocol(protocol); mySwitch->SetAddress(address); mySwitch->SetServerAddress(serverAddress); mySwitch->SetAccessoryType(type); mySwitch->SetAccessoryPulseDuration(duration); mySwitch->SetInverted(inverted); SwitchSaveAndPublishSettings(mySwitch); return true; } bool Manager::SwitchPosition(const SwitchID switchID, const LayoutPosition posX, const LayoutPosition posY, string& result) { Switch* mySwitch = GetSwitch(switchID); if (!mySwitch) { result = Languages::GetText(Languages::TextSwitchDoesNotExist); return false; } if (!CheckLayoutItemPosition(mySwitch, posX, posY, mySwitch->GetPosZ(), LayoutItem::Width1, mySwitch->GetHeight(), mySwitch->GetRotation(), result)) { return false; } mySwitch->SetPosX(posX); mySwitch->SetPosY(posY); SwitchSaveAndPublishSettings(mySwitch); return true; } bool Manager::SwitchRotate(const SwitchID switchID, string& result) { Switch* mySwitch = GetSwitch(switchID); if (!mySwitch) { result = Languages::GetText(Languages::TextSwitchDoesNotExist); return false; } LayoutRotation newRotation = mySwitch->GetRotation(); ++newRotation; if (!CheckLayoutItemPosition(mySwitch, mySwitch->GetPosX(), mySwitch->GetPosY(), mySwitch->GetPosZ(), LayoutItem::Width1, mySwitch->GetHeight(), newRotation, result)) { return false; } mySwitch->Rotate(); SwitchSaveAndPublishSettings(mySwitch); return true; } void Manager::SwitchSaveAndPublishSettings(const Switch* const mySwitch) { if (storage) { TransactionGuard guard(storage); storage->Save(*mySwitch); } std::lock_guard guard(controlMutex); for (auto& control : controls) { control.second->SwitchSettings(mySwitch->GetID(), mySwitch->GetName(), mySwitch->GetMatchKey()); } } bool Manager::SwitchDelete(const SwitchID switchID, string& result) { Switch* mySwitch = nullptr; { std::lock_guard guard(switchMutex); if (switchID == SwitchNone || switches.count(switchID) != 1) { result = Languages::GetText(Languages::TextSwitchDoesNotExist); return false; } mySwitch = switches.at(switchID); if (!mySwitch) { result = Languages::GetText(Languages::TextSwitchDoesNotExist); return false; } ObjectIdentifier switchIdentifier(ObjectTypeSwitch, switchID); if (ObjectIsPartOfRoute(switchIdentifier, mySwitch, result)) { return false; } switches.erase(switchID); } if (storage) { TransactionGuard guard(storage); storage->DeleteSwitch(switchID); } const string& name = mySwitch->GetName(); const string& matchKey = mySwitch->GetMatchKey(); std::lock_guard guard(controlMutex); for (auto& control : controls) { control.second->SwitchDelete(switchID, name, matchKey); } delete mySwitch; return true; } const map Manager::SwitchConfigByName() const { map out; { std::lock_guard guard(switchMutex); for (auto& mySwitch : switches) { out[mySwitch.second->GetName()] = *(mySwitch.second); } } std::lock_guard guard(controlMutex); for (auto& control : controls) { control.second->AddUnmatchedAccessories(out); } return out; } bool Manager::SwitchRelease(const RouteID switchID) { Switch* mySwitch = GetSwitch(switchID); if (!mySwitch) { return false; } return mySwitch->Release(logger, mySwitch->GetLocoBase()); } Switch* Manager::GetSwitchByMatchKey(const ControlID controlId, const string& matchKey) const { std::lock_guard guard(switchMutex); for (auto& mySwitch : switches) { Switch* switchConfig = mySwitch.second; if (switchConfig->GetControlID() == controlId && switchConfig->GetMatchKey().compare(matchKey) == 0) { return switchConfig; } } return nullptr; } void Manager::SwitchRemoveMatchKey(const SwitchID switchId) { Switch* mySwitch = GetSwitch(switchId); if (!mySwitch) { return; } mySwitch->ClearMatchKey(); } /*************************** * Route * ***************************/ bool Manager::RouteExecute(Logger::Logger* logger, const ObjectIdentifier& locoBaseIdentifier, const RouteID routeID) { Route* route = GetRoute(routeID); if (!route) { return false; } return route->Execute(logger, locoBaseIdentifier); } void Manager::RouteExecuteAsync(Logger::Logger* logger, const RouteID routeID) { Route* route = GetRoute(routeID); if (!route) { return; } __attribute__((unused)) auto r = std::async(std::launch::async, Route::ExecuteStatic, logger, route); } string Manager::GetRouteList() const { string out; std::lock_guard guard(routeMutex); for (auto& route : routes) { Route* r = route.second; out += to_string(r->GetID()) + ";"; out += to_string(r->GetFromTrack()) + ";"; out += to_string(r->GetFromOrientation()) + ";"; out += to_string(r->GetToTrack()) + ";"; out += to_string(r->GetToOrientation()) + ";"; out += r->GetName() + "\n"; } return out; } Route* Manager::GetRoute(const RouteID routeID) const { std::lock_guard guard(routeMutex); if (routes.count(routeID) != 1) { return nullptr; } return routes.at(routeID); } const string& Manager::GetRouteName(const RouteID routeID) const { std::lock_guard guard(routeMutex); if (routes.count(routeID) != 1) { return unknownRoute; } return routes.at(routeID)->GetName(); } bool Manager::RouteSave(RouteID routeID, const std::string& name, const Delay delay, const Route::PushpullType pushpull, const Propulsion propulsion, const TrainType trainType, const Length minTrainLength, const Length maxTrainLength, const std::vector& relationsAtLock, const std::vector& relationsAtUnlock, const Visible visible, const LayoutPosition posX, const LayoutPosition posY, const LayoutPosition posZ, const Automode automode, const TrackID fromTrack, const Orientation fromOrientation, const TrackID toTrack, const Orientation toOrientation, const Route::Speed speed, const FeedbackID feedbackIdReduced, const Delay reducedDelay, const FeedbackID feedbackIdCreep, const Delay creepDelay, const FeedbackID feedbackIdStop, const Delay stopDelay, const FeedbackID feedbackIdOver, const Pause waitAfterRelease, const RouteID followUpRoute, string& result) { Route* route = GetRoute(routeID); if (visible && !CheckLayoutItemPosition(route, posX, posY, posZ, result)) { return false; } if (!route) { route = CreateAndAddObject(routes, routeMutex); } if (!route) { result = Languages::GetText(Languages::TextUnableToAddRoute); return false; } // remove route from old track Track* oldTrack = GetTrack(route->GetFromTrack()); if (oldTrack) { oldTrack->DeleteRoute(route); TrackSave(oldTrack); } // if we have a new object we have to update routeID if (routeID == 0) { routeID = route->GetID(); for (auto atLock : relationsAtLock) { atLock->ObjectID1(routeID); } for (auto atUnlock : relationsAtUnlock) { atUnlock->ObjectID1(routeID); } } // update existing route route->SetName(CheckObjectName(routes, routeMutex, routeID, name.size() == 0 ? "S" : name)); route->SetDelay(delay); route->AssignRelationsAtLock(relationsAtLock); route->AssignRelationsAtUnlock(relationsAtUnlock); route->SetVisible(visible); route->SetPosX(posX); route->SetPosY(posY); route->SetPosZ(posZ); route->SetAutomode(automode); if (automode == AutomodeYes) { route->SetFromTrack(fromTrack); route->SetFromOrientation(fromOrientation); route->SetToTrack(toTrack); route->SetToOrientation(toOrientation); route->SetSpeed(speed); route->SetFeedbackIdReduced(feedbackIdReduced); route->SetReducedDelay(reducedDelay); route->SetFeedbackIdCreep(feedbackIdCreep); route->SetCreepDelay(creepDelay); route->SetFeedbackIdStop(feedbackIdStop); route->SetStopDelay(stopDelay); route->SetFeedbackIdOver(feedbackIdOver); route->SetPushpull(pushpull); route->SetPropulsion(propulsion); route->SetTrainType(trainType); route->SetMinTrainLength(minTrainLength); route->SetMaxTrainLength(maxTrainLength); route->SetWaitAfterRelease(waitAfterRelease); route->SetFollowUpRoute(followUpRoute); } else { route->SetFromTrack(TrackNone); route->SetFromOrientation(OrientationRight); route->SetToTrack(TrackNone); route->SetToOrientation(OrientationLeft); route->SetSpeed(Route::SpeedTravel); route->SetFeedbackIdReduced(FeedbackNone); route->SetReducedDelay(0); route->SetFeedbackIdCreep(FeedbackNone); route->SetCreepDelay(0); route->SetFeedbackIdStop(FeedbackNone); route->SetStopDelay(0); route->SetFeedbackIdOver(FeedbackNone); route->SetPushpull(Route::PushpullTypeBoth); route->SetPropulsion(PropulsionAll); route->SetTrainType(TrainTypeAll); route->SetMinTrainLength(0); route->SetMaxTrainLength(0); route->SetWaitAfterRelease(0); route->SetFollowUpRoute(RouteNone); } // Add new route Track* newTrack = GetTrack(route->GetFromTrack()); if (newTrack) { newTrack->AddRoute(route); TrackSave(newTrack); } RouteSaveAndPublishSettings(route); return true; } bool Manager::RoutePosition(const RouteID routeID, const LayoutPosition posX, const LayoutPosition posY, string& result) { Route* route = GetRoute(routeID); if (!route) { result = Languages::GetText(Languages::TextRouteDoesNotExist); return false; } if (!route->GetVisible()) { return false; } if (!CheckLayoutItemPosition(route, posX, posY, route->GetPosZ(), result)) { return false; } route->SetPosX(posX); route->SetPosY(posY); RouteSaveAndPublishSettings(route); return true; } void Manager::RouteSaveAndPublishSettings(const Route* const route) { RouteSave(route); std::lock_guard guard(controlMutex); for (auto& control : controls) { control.second->RouteSettings(route->GetID(), route->GetName()); } } const map Manager::RouteListByName() const { map out; std::lock_guard guard(routeMutex); for (auto& route : routes) { out[route.second->GetName()] = route.second; } return out; } bool Manager::RouteDelete(const RouteID routeID, string& result) { Route* route = nullptr; { { std::lock_guard guard(routeMutex); if (routeID == RouteNone || routes.count(routeID) != 1) { result = Languages::GetText(Languages::TextRouteDoesNotExist); return false; } route = routes.at(routeID); if (!route) { result = Languages::GetText(Languages::TextRouteDoesNotExist); return false; } if (route->IsInUse()) { result = Logger::Logger::Format(Languages::GetText(Languages::TextRouteIsInUse), route->GetName()); return false; } } // this must not be in the lock_guard, it would be a deadlock. ObjectIdentifier routeIdentifier(ObjectTypeRoute, routeID); if (ObjectIsPartOfRoute(routeIdentifier, route, result)) { return false; } { std::lock_guard guard(routeMutex); routes.erase(routeID); } } if (storage) { TransactionGuard guard(storage); storage->DeleteRoute(routeID); } const string& routeName = route->GetName(); std::lock_guard guard(controlMutex); for (auto& control : controls) { control.second->RouteDelete(routeID, routeName); } delete route; return true; } Route* Manager::GetFirstRouteFromOrToTrack(const TrackID trackID) const { std::lock_guard guard(routeMutex); for (auto& route : routes) { if (route.second->GetToTrack() == trackID || route.second->GetFromTrack() == trackID) { return route.second; } } return nullptr; } Layer* Manager::GetLayer(const LayerID layerID) const { std::lock_guard guard(layerMutex); if (layers.count(layerID) != 1) { return nullptr; } return layers.at(layerID); } const map Manager::LayerListByName() const { map list; std::lock_guard guard(layerMutex); for (auto& layer : layers) { list[layer.second->GetName()] = layer.first; } return list; } const map Manager::LayerListByNameWithFeedback() const { map list = LayerListByName(); std::lock_guard guard(controlMutex); for (auto& control : controls) { if (!control.second->CanHandle(Hardware::CapabilityFeedback)) { continue; } list["| Feedbacks of " + control.second->GetShortName()] = -control.first; } return list; } bool Manager::LayerSave(LayerID layerID, const std::string&name, std::string& result) { Layer* layer = GetLayer(layerID); if (!layer) { layer = CreateAndAddObject(layers, layerMutex); } if (!layer) { result = Languages::GetText(Languages::TextUnableToAddLayer); return false; } // if we have a new object we have to update layerID layerID = layer->GetID(); // update existing layer layer->SetName(CheckObjectName(layers, layerMutex, layerID, name.size() == 0 ? "L" : name)); // save in db if (storage) { TransactionGuard guard(storage); storage->Save(*layer); } std::lock_guard guard(controlMutex); for (auto& control : controls) { control.second->LayerSettings(layerID, name); } return true; } bool Manager::LayerHasElements(const Layer* layer, string& result) { LayerID layerId = layer->GetID(); for (auto& track : tracks) { if (track.second->IsVisibleOnLayer(layerId)) { result = Logger::Logger::Format(Languages::GetText(Languages::TextLayerIsUsedByTrack), layer->GetName(), track.second->GetName()); return true; } } for (auto& mySwitch : switches) { if (mySwitch.second->IsVisibleOnLayer(layerId)) { result = Logger::Logger::Format(Languages::GetText(Languages::TextLayerIsUsedBySwitch), layer->GetName(), mySwitch.second->GetName()); return true; } } for (auto& signal : signals) { if (signal.second->IsVisibleOnLayer(layerId)) { result = Logger::Logger::Format(Languages::GetText(Languages::TextLayerIsUsedBySignal), layer->GetName(), signal.second->GetName()); return true; } } for (auto& accessory : accessories) { if (accessory.second->IsVisibleOnLayer(layerId)) { result = Logger::Logger::Format(Languages::GetText(Languages::TextLayerIsUsedByAccessory), layer->GetName(), accessory.second->GetName()); return true; } } for (auto& route : routes) { if (route.second->IsVisibleOnLayer(layerId)) { result = Logger::Logger::Format(Languages::GetText(Languages::TextLayerIsUsedByRoute), layer->GetName(), route.second->GetName()); return true; } } for (auto& feedback : feedbacks) { if (feedback.second->IsVisibleOnLayer(layerId)) { result = Logger::Logger::Format(Languages::GetText(Languages::TextLayerIsUsedByFeedback), layer->GetName(), feedback.second->GetName()); return true; } } for (auto& text : texts) { if (text.second->IsVisibleOnLayer(layerId)) { result = Logger::Logger::Format(Languages::GetText(Languages::TextLayerIsUsedByText), layer->GetName(), text.second->GetName()); return true; } } return false; } bool Manager::LayerDelete(const LayerID layerID, string& result) { if (layerID == LayerUndeletable) { result = Languages::GetText(Languages::TextLayer1IsUndeletable); return false; } if (layerID == LayerNone) { result = Languages::GetText(Languages::TextLayerDoesNotExist); return false; } Layer* layer = nullptr; { std::lock_guard guard(layerMutex); if (layers.count(layerID) != 1) { result = Languages::GetText(Languages::TextLayerDoesNotExist); return false; } layer = layers.at(layerID); if (!layer) { result = Languages::GetText(Languages::TextLayerDoesNotExist); return false; } if (LayerHasElements(layer, result)) { return false; } layers.erase(layerID); } if (storage) { TransactionGuard guard(storage); storage->DeleteLayer(layerID); } const string& layerName = layer->GetName(); std::lock_guard guard(controlMutex); for (auto& control : controls) { control.second->LayerDelete(layerID, layerName); } delete layer; return true; } /*************************** * Signal * ***************************/ bool Manager::SignalState(const ControlType controlType, const SignalID signalID, const DataModel::AccessoryState state, const bool force) { if (boosterState == BoosterStateStop) { return false; } Signal* signal = GetSignal(signalID); if (!signal) { return false; } return SignalState(controlType, signal, state, force); } bool Manager::SignalState(const ControlType controlType, Signal* signal, const DataModel::AccessoryState state, const bool force) { if (!signal) { return false; } if (!force && signal->IsInUse()) { logger->Warning(Languages::TextSignalIsLocked, signal->GetName()); return false; } const DataModel::AccessoryState oldState = signal->GetAccessoryState(); if (oldState == state) { return true; } signal->SetAccessoryState(state); SignalPublishState(controlType, signal); return true; } void Manager::SignalPublishState(const ControlType controlType, const DataModel::Signal* signal) { std::lock_guard guard(controlMutex); for (auto& control : controls) { control.second->SignalState(controlType, signal); } } Signal* Manager::GetSignal(const SignalID signalID) const { std::lock_guard guard(signalMutex); if (signals.count(signalID) != 1) { return nullptr; } return signals.at(signalID); } Signal* Manager::GetSignal(const ControlID controlID, const Protocol protocol, const Address address) const { std::lock_guard guard(signalMutex); for (auto& signal : signals) { Signal* s = signal.second; if ((s->GetControlID() == controlID) && (s->GetProtocol() == protocol) && (s->UsesAddress(address))) { return s; } } return nullptr; } const std::string& Manager::GetSignalName(const SignalID signalID) const { if (signals.count(signalID) != 1) { return unknownSignal; } return signals.at(signalID)->GetName(); } bool Manager::SignalSave(SignalID signalID, const string& name, const LayoutPosition posX, const LayoutPosition posY, const LayoutPosition posZ, const LayoutItemSize height, const LayoutRotation rotation, const ControlID controlID, const string& matchKey, const Protocol protocol, const Address address, const Address serverAddress, const DataModel::AccessoryType type, const std::map& offsets, const DataModel::AccessoryPulseDuration duration, const bool inverted, string& result) { if (!CheckControlAccessoryProtocolAddress(controlID, protocol, address, result)) { return false; } Signal* signal = GetSignal(signalID); if (!CheckLayoutItemPosition(signal, posX, posY, posZ, result)) { return false; } if (!signal) { signal = CreateAndAddObject(signals, signalMutex); } if (!signal) { result = Languages::GetText(Languages::TextUnableToAddSignal); return false; } // if we have a new object we have to update locoID signalID = signal->GetID(); signal->SetName(CheckObjectName(signals, signalMutex, signalID, name.size() == 0 ? "S" : name)); signal->SetPosX(posX); signal->SetPosY(posY); signal->SetPosZ(posZ); signal->SetHeight(height); signal->SetRotation(rotation); signal->SetControlID(controlID); signal->SetMatchKey(matchKey); signal->SetProtocol(protocol); signal->SetAddress(address); signal->SetServerAddress(serverAddress); signal->SetAccessoryType(type); signal->SetStateAddressOffsets(offsets); signal->SetAccessoryPulseDuration(duration); signal->SetInverted(inverted); SignalSaveAndPublishSettings(signal); return true; } bool Manager::SignalPosition(const SignalID signalID, const LayoutPosition posX, const LayoutPosition posY, string& result) { Signal* signal = GetSignal(signalID); if (!signal) { result = Languages::GetText(Languages::TextSignalDoesNotExist); return false; } if (!CheckLayoutItemPosition(signal, posX, posY, signal->GetPosZ(), result)) { return false; } signal->SetPosX(posX); signal->SetPosY(posY); SignalSaveAndPublishSettings(signal); return true; } bool Manager::SignalRotate(const SignalID signalID, string& result) { Signal* signal = GetSignal(signalID); if (!signal) { result = Languages::GetText(Languages::TextSignalDoesNotExist); return false; } signal->Rotate(); SignalSaveAndPublishSettings(signal); return true; } void Manager::SignalSaveAndPublishSettings(const Signal* const signal) { // save in db if (storage) { TransactionGuard guard(storage); storage->Save(*signal); } std::lock_guard guard(controlMutex); for (auto& control : controls) { control.second->SignalSettings(signal->GetID(), signal->GetName(), signal->GetMatchKey()); } } bool Manager::SignalDelete(const SignalID signalID, string& result) { Signal* signal = nullptr; { std::lock_guard guard(signalMutex); if (signalID == SignalNone || signals.count(signalID) != 1) { result = Languages::GetText(Languages::TextSignalDoesNotExist); return false; } signal = signals.at(signalID); if (!signal) { result = Languages::GetText(Languages::TextSignalDoesNotExist); return false; } if (signal->IsInUse()) { result = Logger::Logger::Format(Languages::GetText(Languages::TextSignalIsUsedByLoco), signal->GetName(), GetLocoBaseName(signal->GetLocoBase())); return false; } Track* track = signal->GetTrack(); if (track) { result = Logger::Logger::Format(Languages::GetText(Languages::TextSignalIsUsedByTrack), signal->GetName(), track->GetName()); return false; } ObjectIdentifier signalIdentifier(ObjectTypeSignal, signalID); if (ObjectIsPartOfRoute(signalIdentifier, signal, result)) { return false; } signals.erase(signalID); } if (storage) { TransactionGuard guard(storage); storage->DeleteSignal(signalID); } const string& name = signal->GetName(); const string& matchKey = signal->GetMatchKey(); std::lock_guard guard(controlMutex); for (auto& control : controls) { control.second->SignalDelete(signalID, name, matchKey); } delete signal; return true; } const map Manager::SignalListByName() const { map out; std::lock_guard guard(signalMutex); for (auto& signal : signals) { out[signal.second->GetName()] = signal.second; } return out; } const map Manager::SignalConfigByName() const { map out; { std::lock_guard guard(signalMutex); for (auto& signal : signals) { out[signal.second->GetName()] = *(signal.second); } } std::lock_guard guard(controlMutex); for (auto& control : controls) { control.second->AddUnmatchedAccessories(out); } return out; } Signal* Manager::GetSignalByMatchKey(const ControlID controlId, const string& matchKey) const { std::lock_guard guard(signalMutex); for (auto& signal : signals) { Signal* signalConfig = signal.second; if (signalConfig->GetControlID() == controlId && signalConfig->GetMatchKey().compare(matchKey) == 0) { return signalConfig; } } return nullptr; } void Manager::SignalRemoveMatchKey(const SignalID signalId) { Signal* signal = GetSignal(signalId); if (!signal) { return; } signal->ClearMatchKey(); } bool Manager::SignalRelease(const SignalID signalID) { Signal* signal = GetSignal(signalID); if (!signal) { return false; } return signal->Release(logger, ObjectIdentifier()); } /*************************** * Cluster * ***************************/ Cluster* Manager::GetCluster(const ClusterID clusterID) const { std::lock_guard guard(clusterMutex); if (clusters.count(clusterID) != 1) { return nullptr; } return clusters.at(clusterID); } const map Manager::ClusterListByName() const { map out; std::lock_guard guard(clusterMutex); for (auto& cluster : clusters) { out[cluster.second->GetName()] = cluster.second; } return out; } bool Manager::ClusterSave(ClusterID clusterID, const string& name, const vector& newTracks, string& result) { Cluster* cluster = GetCluster(clusterID); if (!cluster) { cluster = CreateAndAddObject(clusters, clusterMutex); } if (!cluster) { result = Languages::GetText(Languages::TextUnableToAddCluster); return false; } // if we have a new object we have to update clusterID if (clusterID == 0) { clusterID = cluster->GetID(); for (auto track : newTracks) { track->ObjectID1(clusterID); } } // update existing cluster cluster->SetName(CheckObjectName(clusters, clusterMutex, clusterID, name.size() == 0 ? "C" : name)); cluster->AssignTracks(newTracks); // save in db if (storage) { TransactionGuard guard(storage); storage->Save(*cluster); } return true; } bool Manager::ClusterDelete(const ClusterID clusterID) { if (clusterID == ClusterNone) { return false; } Cluster* cluster = nullptr; { std::lock_guard guard(clusterMutex); if (clusters.count(clusterID) != 1) { return false; } cluster = clusters.at(clusterID); clusters.erase(clusterID); } cluster->DeleteTracks(); if (storage) { TransactionGuard guard(storage); storage->DeleteCluster(clusterID); } const string& clusterName = cluster->GetName(); std::lock_guard guard(controlMutex); for (auto& control : controls) { control.second->ClusterDelete(clusterID, clusterName); } delete cluster; return true; } /*************************** * Text * ***************************/ Text* Manager::GetText(const TextID textID) const { std::lock_guard guard(textMutex); if (texts.count(textID) != 1) { return nullptr; } return texts.at(textID); } const map Manager::TextListByName() const { map out; std::lock_guard guard(textMutex); for (auto& text : texts) { out[text.second->GetName()] = text.second; } return out; } bool Manager::TextSave(TextID textID, const string& name, const LayoutPosition posX, const LayoutPosition posY, const LayoutPosition posZ, const LayoutItemSize width, const LayoutRotation rotation, string& result) { Text* text = GetText(textID); if (!CheckLayoutItemPosition(text, posX, posY, posZ, width, LayoutItem::Height1, rotation, result)) { return false; } if (!text) { text = CreateAndAddObject(texts, textMutex); } if (!text) { result = Languages::GetText(Languages::TextUnableToAddText); return false; } // if we have a new object we have to update textID textID = text->GetID(); // update existing text text->SetName(CheckObjectName(texts, textMutex, textID, name.size() == 0 ? "T" : name)); text->SetPosX(posX); text->SetPosY(posY); text->SetPosZ(posZ); text->SetWidth(width); text->SetHeight(LayoutItem::Height1); text->SetRotation(rotation); TextSaveAndPublishSettings(text); return true; } bool Manager::TextPosition(TextID textID, const LayoutPosition posX, const LayoutPosition posY, string& result) { Text* text = GetText(textID); if (!text) { result = Languages::GetText(Languages::TextTextDoesNotExist); return false; } if (!CheckLayoutItemPosition(text, posX, posY, text->GetPosZ(), text->GetWidth(), LayoutItem::Height1, text->GetRotation(), result)) { return false; } text->SetPosX(posX); text->SetPosY(posY); TextSaveAndPublishSettings(text); return true; } bool Manager::TextRotate(TextID textID, string& result) { Text* text = GetText(textID); if (!text) { result = Languages::GetText(Languages::TextTextDoesNotExist); return false; } LayoutRotation newRotation = text->GetRotation(); ++newRotation; if (!CheckLayoutItemPosition(text, text->GetPosX(), text->GetPosY(), text->GetPosZ(), text->GetWidth(), LayoutItem::Height1, newRotation, result)) { return false; } text->SetRotation(newRotation); TextSaveAndPublishSettings(text); return true; } void Manager::TextSaveAndPublishSettings(const Text* const text) { // save in db if (storage) { TransactionGuard guard(storage); storage->Save(*text); } std::lock_guard guard(controlMutex); for (auto& control : controls) { control.second->TextSettings(text->GetID(), text->GetName()); } } bool Manager::TextDelete(const TextID textID, string& result) { if (textID == TextNone) { result = Languages::GetText(Languages::TextTextDoesNotExist); return false; } Text* text = nullptr; { std::lock_guard guard(textMutex); if (texts.count(textID) != 1) { result = Languages::GetText(Languages::TextTextDoesNotExist); return false; } text = texts.at(textID); texts.erase(textID); } if (storage) { TransactionGuard guard(storage); storage->DeleteText(textID); } const string& textName = text->GetName(); std::lock_guard guard(controlMutex); for (auto& control : controls) { control.second->TextDelete(textID, textName); } delete text; return true; } /*************************** * Automode * ***************************/ bool Manager::LocoBaseIntoTrack(Logger::Logger* logger, const ObjectIdentifier& locoBaseIdentifier, const TrackID trackID) { Track* track = GetTrack(trackID); if (!track) { return false; } LocoBase* locoBase = GetLocoBase(locoBaseIdentifier); if (!locoBase) { return false; } bool reserved = track->ReserveForce(logger, locoBaseIdentifier); if (!reserved) { return false; } reserved = locoBase->SetTrack(trackID); if (!reserved) { track->Release(logger, locoBaseIdentifier); return false; } reserved = track->Lock(logger, locoBaseIdentifier); if (!reserved) { locoBase->Release(); track->Release(logger, locoBaseIdentifier); return false; } const string& locoName = locoBase->GetName(); const string& trackName = track->GetName(); logger->Info(Languages::TextLocoIsOnTrack, locoName, trackName); TrackPublishState(track); return true; } bool Manager::LocoRelease(const LocoID locoID) { Loco* loco = GetLoco(locoID); if (!loco) { return false; } return LocoBaseReleaseInternal(loco); } bool Manager::MultipleUnitRelease(const MultipleUnitID multipleUnitID) { MultipleUnit* multipleUnit = GetMultipleUnit(multipleUnitID); if (!multipleUnit) { return false; } return LocoBaseReleaseInternal(multipleUnit); } bool Manager::LocoBaseReleaseInternal(LocoBase* locoBase) { LocoBaseSpeed(ControlTypeInternal, locoBase, MinSpeed); bool ret = locoBase->Release(); if (!ret) { return false; } std::lock_guard guard(controlMutex); for (auto& control : controls) { control.second->LocoBaseRelease(locoBase); } return true; } bool Manager::TrackRelease(const TrackID trackID) { Track* track = GetTrack(trackID); if (!track) { return false; } return track->ReleaseForce(logger, ObjectIdentifier()); } bool Manager::LocoBaseReleaseOnTrack(const TrackID trackID) { Track* track = GetTrack(trackID); if (!track) { return false; } const ObjectIdentifier locoBaseIdentifier = track->GetLocoBase(); track->ReleaseForce(logger, locoBaseIdentifier); LocoBase* locoBase = GetLocoBase(locoBaseIdentifier); if (!locoBase) { return false; } return LocoBaseReleaseInternal(locoBase); } bool Manager::TrackStartLocoBase(const TrackID trackID) { Track* track = GetTrack(trackID); if (!track) { return false; } return LocoBaseStart(track->GetLocoBase()); } bool Manager::TrackStopLocoBase(const TrackID trackID) { Track* track = GetTrack(trackID); if (!track) { return false; } return LocoBaseStop(track->GetLocoBase()); } void Manager::TrackBlock(const TrackID trackID, const bool blocked) { Track* track = GetTrack(trackID); if (!track) { return; } track->SetBlocked(blocked); TrackPublishState(track); } void Manager::TrackSetLocoOrientation(const TrackID trackID, const Orientation orientation) { Track* track = GetTrack(trackID); if (!track) { return; } track->SetLocoBaseOrientation(orientation); TrackPublishState(track); } void Manager::TrackPublishState(const DataModel::Track* track) { { std::lock_guard guard(controlMutex); for (auto& control : controls) { control.second->TrackState(track); } } vector extensions = track->GetExtensions(); for (Track* extension : extensions) { TrackPublishState(extension); } } bool Manager::RouteRelease(const RouteID routeID) { Route* route = GetRoute(routeID); if (!route) { return false; } return route->Release(logger, route->GetLocoBase()); } bool Manager::LocoDestinationReached(const LocoBase* loco, const Route* route, const Track* track) { std::lock_guard guard(controlMutex); for (auto& control : controls) { control.second->LocoBaseDestinationReached(loco, route, track); } return true; } bool Manager::LocoBaseStart(const ObjectIdentifier& locoBaseIdentifier) { LocoBase* locoBase = GetLocoBase(locoBaseIdentifier); if (!locoBase) { return false; } bool ret = locoBase->GoToAutoMode(); if (!ret) { return false; } std::lock_guard guard(controlMutex); for (auto& control : controls) { control.second->LocoBaseStart(locoBase); } return true; } bool Manager::LocoBaseStartAll() { { std::lock_guard guard(locoMutex); for (auto& loco : locos) { const bool started = loco.second->GoToAutoMode(); if (!started) { continue; } Utils::Utils::SleepForMilliseconds(50); } } { std::lock_guard guard(multipleUnitMutex); for (auto& multipleUnit : multipleUnits) { const bool started = multipleUnit.second->GoToAutoMode(); if (!started) { continue; } Utils::Utils::SleepForMilliseconds(50); } } return true; } bool Manager::LocoBaseStop(const ObjectIdentifier& locoBaseIdentifier) { LocoBase* locoBase = GetLocoBase(locoBaseIdentifier); if (!locoBase) { return false; } if (locoBase->IsInManualMode()) { return true; } locoBase->RequestManualMode(); while (!locoBase->GoToManualMode()) { Utils::Utils::SleepForSeconds(1); } std::lock_guard guard(controlMutex); for (auto& control : controls) { control.second->LocoBaseStop(locoBase); } return true; } bool Manager::LocoBaseStopAll() { { std::lock_guard guard(multipleUnitMutex); for (auto& multipleUnit : multipleUnits) { if (multipleUnit.second->IsInManualMode()) { continue; } multipleUnit.second->RequestManualMode(); } } { std::lock_guard guard(locoMutex); for (auto& loco : locos) { if (!loco.second->IsInManualMode()) { continue; } loco.second->RequestManualMode(); } } bool anyLocosInAutoMode = true; while (anyLocosInAutoMode && !isKillRunning()) { Utils::Utils::SleepForSeconds(1); anyLocosInAutoMode = false; { std::lock_guard guard(multipleUnitMutex); for (auto& multipleUnit : multipleUnits) { if (multipleUnit.second->IsInManualMode()) { continue; } const bool multipleUnitInManualMode = multipleUnit.second->GoToManualMode(); if (!multipleUnitInManualMode) { multipleUnit.second->RequestManualMode(); } anyLocosInAutoMode |= !multipleUnitInManualMode; } } { std::lock_guard guard(locoMutex); for (auto& loco : locos) { if (loco.second->IsInManualMode()) { continue; } const bool locoInManualMode = loco.second->GoToManualMode(); if (!locoInManualMode) { loco.second->RequestManualMode(); } anyLocosInAutoMode |= !locoInManualMode; } } } for (auto& multipleUnit : multipleUnits) { if (multipleUnit.second->IsInManualMode()) { continue; } multipleUnit.second->GetLogger()->Info(Languages::Languages::TextReleasingMultipleUnit); multipleUnit.second->Release(); } for (auto& loco : locos) { if (loco.second->IsInManualMode()) { continue; } loco.second->GetLogger()->Info(Languages::Languages::TextReleasingLoco); loco.second->Release(); } return true; } void Manager::LocoBaseStopAllImmediately(const ControlType controlType) { { std::lock_guard guard(locoMutex); for (auto& loco : locos) { LocoBaseSpeed(controlType, loco.second, MinSpeed); } } { std::lock_guard guard(multipleUnitMutex); for (auto& multipleUnit : multipleUnits) { LocoBaseSpeed(controlType, multipleUnit.second, MinSpeed); } } } bool Manager::LocoBaseAddTimeTable(const ObjectIdentifier& locoBaseIdentifier, const RouteID routeID, const bool automode) { LocoBase* locoBase = GetLocoBase(locoBaseIdentifier); if (!locoBase) { return false; } Route* route = GetRoute(routeID); if (!route) { return false; } locoBase->AddTimeTable(route, automode ? RouteAuto : RouteStop); return true; } string Manager::GetCs2Lokomotive() const { string out("[lokomotive]\nversion\n .major=0\n .minor=3\nsession\n .id="); out += "1"; // FIXME: replace with mfx-neuanmeldezähler { std::lock_guard guard(locoMutex); for (auto& loco : locos) { out += "\nlokomotive"; out += "\n .name=" + loco.second->GetName(); out += "\n .typ="; const Address address = loco.second->GetAddress(); uint32_t uid; switch(loco.second->GetProtocol()) { case ProtocolMM: out += "mm2_prog"; uid = address; break; case ProtocolDCC: out += "dcc"; uid = address + 0xC000; break; case ProtocolMFX: out += "mfx"; uid = address + 0x4000; break; default: out += "unknown"; uid = 0; break; } out += "\n .uid=0x" + Utils::Integer::IntegerToHex(uid); out += "\n .adresse=0x" + Utils::Integer::IntegerToHex(address); out += "\n .icon="; out += "\n .symbol="; out += "\n .av=0"; out += "\n .bv=0"; out += "\n .velocity=" + to_string(loco.second->GetSpeed()); out += "\n .richtung=" + to_string(loco.second->GetOrientation()); out += "\n .tachomax=120"; out += "\n .vmax=255"; out += "\n .vmin=1"; for (LocoFunctionNr nr = 0; nr < 32; ++nr) { out += "\n .funktionen"; if (nr >= 16) { out += "_2"; } out += "\n ..nr=" + to_string(nr); out += "\n ..typ=" + to_string(Hardware::Protocols::MaerklinCANCommon::MapLocofunctionRailControlToCs2(nr, loco.second->GetFunctionIcon(nr))); out += "\n ..dauer=" + to_string(loco.second->GetFunctionType(nr)); out += "\n ..wert=" + to_string(loco.second->GetFunctionState(nr));; } } } return out + "\n"; } string Manager::GetCs2Magnetartikel(const AccessoryBase* accessoryBase) { string out = "\nartikel"; out += "\n .id=" + to_string(accessoryBase->GetAddress()); out += "\n .name="; const Object* object = dynamic_cast(accessoryBase); if (object) { out += object->GetName(); } else { out += to_string(accessoryBase->GetAddress()); } out += "\n .typ=std_rot_gruen"; out += "\n .schaltzeit=" + to_string(accessoryBase->GetAccessoryPulseDuration()); out += "\n .dectyp="; switch(accessoryBase->GetProtocol()) { case ProtocolMM: out += "mm2"; break; case ProtocolDCC: out += "dcc"; break; default: out += "unknown"; break; } return out + "\n"; } string Manager::GetCs2Magnetartikel() const { string out("[magnetartikel]\nversion\n. major=0\n. minor=1"); { std::lock_guard guard(accessoryMutex); for (auto& accessory : accessories) { out += GetCs2Magnetartikel(accessory.second); out += "\n .typ=std_rot_gruen"; } } { std::lock_guard guard(switchMutex); for (auto& mySwitch : switches) { out += GetCs2Magnetartikel(mySwitch.second); out += "\n .typ="; switch (mySwitch.second->GetAccessoryType()) { case SwitchTypeLeft: out += "linksweiche"; break; case SwitchTypeRight: out += "rechtsweiche"; break; case SwitchTypeThreeWay: out += "dreiwegweiche"; break; case SwitchTypeMaerklinLeft: case SwitchTypeMaerklinRight: out += "dkw_1antrieb"; break; default: out += "y_weiche"; break; } } } { std::lock_guard guard(signalMutex); for (auto& signal : signals) { out += GetCs2Magnetartikel(signal.second); out += "\n .typ="; switch (signal.second->GetAccessoryType()) { case SignalTypeChDwarf: case SignalTypeDeBlock: out += "lichtsignal_SH01"; break; case SignalTypeChLMain: case SignalTypeChLCombined: case SignalTypeChNMain: case SignalTypeDeCombined: case SignalTypeDeHVMain: out += "urc_lichtsignal_HP012_SH01"; break; case SignalTypeChLDistant: case SignalTypeChNDistant: case SignalTypeDeHVDistant: out += "lichtvorsignal_VR012"; break; case SignalTypeSimpleLeft: case SignalTypeSimpleRight: default: out += "lichtsignal_HP01"; break; } } } return out + "\n"; } string Manager::GetCs2GBS() const { string out("[gleisbild]\nversion\n. major=1"); { std::lock_guard guard(layerMutex); for (auto& layer : layers) { out += "\nseite"; if (layer.first != LayerUndeletable) { out += "\n .id=" + to_string(layer.first); } out += "\n .name=" + layer.second->GetName(); } } return out + "\n"; } string Manager::GetCs2GBS(const signed char gbs) const { string out("[gleisbildseite]\nversion\n. major=1"); unsigned int z = gbs; if (gbs == LayerUndeletable) { // default layer Z in CS2 is 0, in RailControl 1. So we map this. --z; } else { out += "\npage=" + to_string(gbs); } z <<= 16; { std::lock_guard guard(trackMutex); for (auto& track : tracks) { if (gbs == track.second->GetPosZ()) { out += "\nelement"; const unsigned int id = z + (track.second->GetPosX() << 8) + track.second->GetPosY(); out += "\n .id=0x" + Utils::Integer::IntegerToHex(id); out += "\n .typ="; switch(track.second->GetTrackType()) { case TrackTypeTurn: out += "bogen"; break; case TrackTypeEnd: case TrackTypeLink: out += "prellbock"; break; case TrackTypeTunnelEnd: out += "prellbock"; break; case TrackTypeStraight: case TrackTypeBridge: case TrackTypeTunnel: case TrackTypeCrossingLeft: case TrackTypeCrossingRight: case TrackTypeCrossingSymetric: default: out += "gerade"; break; } out += "\n .drehung=" + to_string(track.second->GetRotation() & 0x01); out += "\n .artikel=-1"; } } } { std::lock_guard guard(accessoryMutex); for (auto& accessory : accessories) { if (gbs == accessory.second->GetPosZ()) { out += "\nelement"; const unsigned int id = z + (accessory.second->GetPosX() << 8) + accessory.second->GetPosY(); out += "\n .id=0x" + Utils::Integer::IntegerToHex(id); out += "\n .typ=k84_einfach"; out += "\n .artikel=" + to_string(accessory.second->GetAddress()); } } } { std::lock_guard guard(switchMutex); for (auto& mySwitch : switches) { if (gbs == mySwitch.second->GetPosZ()) { out += "\nelement"; const unsigned int id = z + (mySwitch.second->GetPosX() << 8) + mySwitch.second->GetPosY(); out += "\n .id=0x" + Utils::Integer::IntegerToHex(id); out += "\n .typ="; switch(mySwitch.second->GetAccessoryType()) { case SwitchTypeLeft: out += "linksweiche"; break; case SwitchTypeRight: out += "rechtsweiche"; break; case SwitchTypeThreeWay: out += "dreiwegweiche"; break; case SwitchTypeMaerklinLeft: out += "dkw3_li"; break; case SwitchTypeMaerklinRight: out += "dkw3_re"; break; default: break; } out += "\n .drehung=" + to_string(mySwitch.second->GetRotation()); out += "\n .artikel=" + to_string(mySwitch.second->GetAddress()); out += "\n .zustand=" + to_string(mySwitch.second->GetAccessoryState()); } } } return out + "\n"; } /*************************** * Layout * ***************************/ bool Manager::CheckPositionFree(const LayoutPosition posX, const LayoutPosition posY, const LayoutPosition posZ, string& result) const { return CheckLayoutPositionFree(posX, posY, posZ, result, accessories, accessoryMutex) && CheckLayoutPositionFree(posX, posY, posZ, result, tracks, trackMutex) && CheckLayoutPositionFree(posX, posY, posZ, result, feedbacks, feedbackMutex) && CheckLayoutPositionFree(posX, posY, posZ, result, switches, switchMutex) && CheckLayoutPositionFree(posX, posY, posZ, result, routes, routeMutex) && CheckLayoutPositionFree(posX, posY, posZ, result, signals, signalMutex) && CheckLayoutPositionFree(posX, posY, posZ, result, texts, textMutex); } bool Manager::CheckPositionFree(const LayoutPosition posX, const LayoutPosition posY, const LayoutPosition posZ, const LayoutItemSize width, const LayoutItemSize height, const LayoutRotation rotation, string& result) const { if (width == 0) { result.assign(Languages::GetText(Languages::TextWidthIs0)); return false; } if (height == 0) { result.assign(Languages::GetText(Languages::TextHeightIs0)); return false; } LayoutPosition x; LayoutPosition y; LayoutPosition z = posZ; LayoutItemSize w; LayoutItemSize h; bool ret = DataModel::LayoutItem::MapPosition(posX, posY, width, height, rotation, x, y, w, h); if (!ret) { return false; } for (LayoutPosition ix = x; ix < x + w; ix++) { for (LayoutPosition iy = y; iy < y + h; iy++) { bool ret = CheckPositionFree(ix, iy, z, result); if (!ret) { return false; } } } return true; } bool Manager::CheckLayoutItemPosition(const LayoutItem* item, const LayoutPosition posX, const LayoutPosition posY, const LayoutPosition posZ, string& result) const { if (item) { if (item->IsVisibleOnLayer(posZ) == DataModel::LayoutItem::VisibleNo || item->HasPosition(posX, posY, posZ)) { return true; } } return CheckPositionFree(posX, posY, posZ, DataModel::LayoutItem::Width1, DataModel::LayoutItem::Height1, DataModel::LayoutItem::Rotation0, result); } bool Manager::CheckLayoutItemPosition(const LayoutItem* item, const LayoutPosition posX, const LayoutPosition posY, const LayoutPosition posZ, const LayoutItemSize width, const LayoutItemSize height, const LayoutRotation rotation, string& result) const { LayoutPosition x1; LayoutPosition y1; LayoutPosition z1 = posZ; LayoutItemSize w1; LayoutItemSize h1; bool ret = DataModel::LayoutItem::MapPosition(posX, posY, width, height, rotation, x1, y1, w1, h1); if (!ret) { result = Languages::GetText(Languages::TextUnableToCalculatePosition); return false; } LayoutPosition x2 = 0; LayoutPosition y2 = 0; LayoutPosition z2 = 0; LayoutItemSize w2 = 0; LayoutItemSize h2 = 0; if (item) { z2 = item->GetPosZ(); ret = DataModel::LayoutItem::MapPosition(item->GetPosX(), item->GetPosY(), width, item->GetHeight(), item->GetRotation(), x2, y2, w2, h2); if (!ret) { result = Languages::GetText(Languages::TextUnableToCalculatePosition); return false; } } for (LayoutPosition ix = x1; ix < x1 + w1; ++ix) { for (LayoutPosition iy = y1; iy < y1 + h1; ++iy) { ret = (ix >= x2 && ix < x2 + w2 && iy >= y2 && iy < y2 + h2 && z1 == z2); if (ret) { continue; } ret = CheckPositionFree(ix, iy, z1, result); if (!ret) { return false; } } } return true; } template bool Manager::CheckLayoutPositionFree(const LayoutPosition posX, const LayoutPosition posY, const LayoutPosition posZ, string& result, const map& layoutVector, std::mutex& mutex) const { std::lock_guard guard(mutex); for (auto& layout : layoutVector) { if (layout.second->CheckPositionFree(posX, posY, posZ)) { continue; } result.assign(Logger::Logger::Format(Languages::GetText(Languages::TextPositionAlreadyInUse), static_cast(posX), static_cast(posY), static_cast(posZ), layout.second->GetLayoutType(), layout.second->GetName())); return false; } return true; } bool Manager::CheckAddressLoco(const Protocol protocol, const Address address, string& result) { switch (protocol) { case ProtocolDCC: case ProtocolDCC14: case ProtocolDCC28: case ProtocolDCC128: if (address > 10239) { result.assign(Languages::GetText(Languages::TextLocoAddressDccTooHigh)); return false; } return true; case ProtocolMM1: case ProtocolMM15: if (address > 80) { result.assign(Languages::GetText(Languages::TextLocoAddressMm1TooHigh)); return false; } return true; case ProtocolMM: case ProtocolMM2: if (address > 255) { result.assign(Languages::GetText(Languages::TextLocoAddressMm2TooHigh)); return false; } return true; default: return true; } } bool Manager::CheckAddressAccessory(const Protocol protocol, const Address address, string& result) { switch (protocol) { case ProtocolDCC: if (address > 2044) { result.assign(Languages::GetText(Languages::TextAccessoryAddressDccTooHigh)); return false; } return true; case ProtocolMM1: case ProtocolMM2: if (address > 320) { result.assign(Languages::GetText(Languages::TextAccessoryAddressMmTooHigh)); return false; } return true; default: return true; } } bool Manager::CheckControlProtocolAddress(const AddressType type, const ControlID controlID, const Protocol protocol, const Address address, string& result) { if (address == AddressNone) { result.assign(Logger::Logger::Format(Languages::GetText(Languages::TextAddressMustBeHigherThen0))); return false; } if ((type == AddressTypeMultipleUnit) && (controlID == ControlNone)) { // multiple units do not need physical control return true; } { std::lock_guard guard(controlMutex); if (controlID < ControlIdFirstHardware || controls.count(controlID) != 1) { result.assign(Languages::GetText(Languages::TextControlDoesNotExist)); return false; } ControlInterface* control = controls.at(controlID); if (!control) { result.assign(Languages::GetText(Languages::TextControlDoesNotExist)); return false; } bool protocolSupported; switch (type) { case AddressTypeLoco: protocolSupported = control->LocoProtocolSupported(protocol); break; case AddressTypeMultipleUnit: protocolSupported = true; break; case AddressTypeAccessory: protocolSupported = control->AccessoryProtocolSupported(protocol); break; default: protocolSupported = false; break; } if (!protocolSupported) { string protocolText; if (protocol <= ProtocolEnd) { protocolText = ProtocolSymbols[protocol] + " "; } std::vector protocols; switch (type) { case AddressTypeLoco: control->LocoProtocols(protocols); break; case AddressTypeAccessory: control->AccessoryProtocols(protocols); break; case AddressTypeMultipleUnit: default: break; } string protocolsText; for (auto p : protocols) { if (protocolsText.size() > 0) { protocolsText.append(", "); } protocolsText.append(ProtocolSymbols[p]); } result.assign(Logger::Logger::Format(Languages::GetText(Languages::TextProtocolNotSupported), protocolText, protocolsText)); return false; } } // unlock controlMutex switch (type) { case AddressTypeLoco: return CheckAddressLoco(protocol, address, result); case AddressTypeMultipleUnit: return true; case AddressTypeAccessory: return CheckAddressAccessory(protocol, address, result); default: return false; } } bool Manager::SettingsSave(const Languages::Language language, const StartupInitLocos startupInitLocos, const DataModel::AccessoryPulseDuration duration, const bool autoAddFeedback, const bool stopOnFeedbackInFreeTrack, const bool executeAccessoryAlways, const DataModel::SelectRouteApproach selectRouteApproach, const DataModel::Loco::NrOfTracksToReserve nrOfTracksToReserve, const Logger::Logger::Level logLevel ) { Languages::SetDefaultLanguage(language); this->startupInitLocos = startupInitLocos; this->defaultAccessoryDuration = duration; this->autoAddFeedback = autoAddFeedback; this->stopOnFeedbackInFreeTrack = stopOnFeedbackInFreeTrack; this->executeAccessory = executeAccessoryAlways; this->selectRouteApproach = selectRouteApproach; this->nrOfTracksToReserve = nrOfTracksToReserve; Logger::Logger::SetLogLevel(logLevel); if (!storage) { return false; } Storage::TransactionGuard guard(storage); storage->SaveSetting("Language", std::to_string(static_cast(language))); storage->SaveSetting("StartupInitLocos", std::to_string(static_cast(startupInitLocos))); storage->SaveSetting("DefaultAccessoryDuration", std::to_string(duration)); storage->SaveSetting("AutoAddFeedback", std::to_string(autoAddFeedback)); storage->SaveSetting("StopOnFeedbackInFreeTrack", std::to_string(stopOnFeedbackInFreeTrack)); storage->SaveSetting("ExecuteAccessory", std::to_string(executeAccessory)); storage->SaveSetting("SelectRouteApproach", std::to_string(static_cast(selectRouteApproach))); storage->SaveSetting("NrOfTracksToReserve", std::to_string(static_cast(nrOfTracksToReserve))); storage->SaveSetting("LogLevel", std::to_string(static_cast(logLevel))); return true; } void Manager::DebounceWorker() { Utils::Utils::SetThreadName(Languages::GetText(Languages::TextDebouncer)); logger->Info(Languages::TextDebounceThreadStarted); while (debounceRun) { { std::lock_guard guard(feedbackMutex); for (auto& feedback : feedbacks) { feedback.second->Debounce(); } } Utils::Utils::SleepForMilliseconds(250); } logger->Info(Languages::TextDebounceThreadTerminated); } template T* Manager::CreateAndAddObject(std::map& objects, std::mutex& mutex) { std::lock_guard Guard(mutex); ID newObjectID = 0; for (auto& object : objects) { if (object.first > newObjectID) { newObjectID = object.first; } } ++newObjectID; T* newObject = new T(this, newObjectID); if (!newObject) { return nullptr; } objects[newObjectID] = newObject; return newObject; } ControlID Manager::GetPossibleControlForLoco() const { for (auto& control : controls) { if (control.second->CanHandle(Hardware::CapabilityLoco)) { return control.first; } } return ControlIdNone; } ControlID Manager::GetPossibleControlForAccessory() const { for (auto& control : controls) { if (control.second->CanHandle(Hardware::CapabilityAccessory)) { return control.first; } } return ControlIdNone; } ControlID Manager::GetPossibleControlForFeedback() const { for (auto& control : controls) { if (control.second->CanHandle(Hardware::CapabilityFeedback)) { return control.first; } } return ControlIdNone; } void Manager::ProgramCheckBooster(const ProgramMode mode) { switch (mode) { case ProgramModeDccPomLoco: case ProgramModeDccPomAccessory: if (boosterState == BoosterStateGo) { return; } Booster(ControlTypeInternal, BoosterStateGo); Utils::Utils::SleepForMilliseconds(100); return; default: return; } } void Manager::ProgramRead(const ControlID controlID, const ProgramMode mode, const Address address, const CvNumber cv) { ControlInterface* control = GetControl(controlID); if (!control) { return; } ProgramCheckBooster(mode); control->ProgramRead(mode, address, cv); } void Manager::ProgramWrite(const ControlID controlID, const ProgramMode mode, const Address address, const CvNumber cv, const CvValue value) { ControlInterface* control = GetControl(controlID); if (!control) { return; } ProgramCheckBooster(mode); control->ProgramWrite(mode, address, cv, value); } void Manager::ProgramValue(const CvNumber cv, const CvValue value) { std::lock_guard guard(controlMutex); for (auto& control : controls) { control.second->ProgramValue(cv, value);; } } bool Manager::CanHandle(const Hardware::Capabilities capability) const { std::lock_guard guard(controlMutex); for (auto& control : controls) { bool ret = control.second->CanHandle(capability); if (ret) { return true; } } return false; } bool Manager::CanHandle(const ControlID controlId, const Hardware::Capabilities capability) const { ControlInterface* control = GetControl(controlId); if (!control) { return false; } return control->CanHandle(capability); } Hardware::Capabilities Manager::GetCapabilities(const ControlID controlID) const { ControlInterface* control = GetControl(controlID); if (!control) { return Hardware::CapabilityNone; } return control->GetCapabilities(); } bool Manager::LayoutItemRotate(const DataModel::ObjectIdentifier& identifier, string& result) { ObjectType type = identifier.GetObjectType(); ObjectID id = identifier.GetObjectID(); switch (type) { case ObjectTypeAccessory: return AccessoryRotate(id, result); case ObjectTypeFeedback: return FeedbackRotate(id, result); case ObjectTypeSignal: return SignalRotate(id, result); case ObjectTypeSwitch: return SwitchRotate(id, result); case ObjectTypeText: return TextRotate(id, result); case ObjectTypeTrack: return TrackRotate(id, result); case ObjectTypeRoute: default: return false; } } bool Manager::LayoutItemNewPosition(const DataModel::ObjectIdentifier& identifier, const DataModel::LayoutItem::LayoutItemSize posX, const DataModel::LayoutItem::LayoutItemSize posY, string& result) { ObjectType type = identifier.GetObjectType(); ObjectID id = identifier.GetObjectID(); switch (type) { case ObjectTypeAccessory: return AccessoryPosition(id, posX, posY, result); case ObjectTypeFeedback: return FeedbackPosition(id, posX, posY, result); case ObjectTypeRoute: return RoutePosition(id, posX, posY, result); case ObjectTypeSignal: return SignalPosition(id, posX, posY, result); case ObjectTypeSwitch: return SwitchPosition(id, posX, posY, result); case ObjectTypeText: return TextPosition(id, posX, posY, result); case ObjectTypeTrack: return TrackPosition(id, posX, posY, result); default: return false; } } ObjectIdentifier Manager::GetIdentifierOfServerLocoAddress(const Address serverAddress) const { { std::lock_guard guard(locoMutex); for (auto& loco : locos) { if (loco.second->GetServerAddress() == serverAddress) { return ObjectIdentifier(ObjectTypeLoco, loco.second->GetID()); } } } { std::lock_guard guard(multipleUnitMutex); for (auto& multipleUnit : multipleUnits) { if (multipleUnit.second->GetServerAddress() == serverAddress) { return ObjectIdentifier(ObjectTypeMultipleUnit, multipleUnit.second->GetID()); } } } return ObjectIdentifier(ObjectTypeNone, ObjectNone); } ObjectIdentifier Manager::GetIdentifierOfServerAccessoryAddress(const Address serverAddress) const { { std::lock_guard guard(accessoryMutex); for (auto& accessory : accessories) { if (accessory.second->GetServerAddress() == serverAddress) { return ObjectIdentifier(ObjectTypeAccessory, accessory.second->GetID()); } } } { std::lock_guard guard(switchMutex); for (auto& mySwitch : switches) { if (mySwitch.second->GetServerAddress() == serverAddress) { return ObjectIdentifier(ObjectTypeSwitch, mySwitch.second->GetID()); } } } { std::lock_guard guard(signalMutex); for (auto& signal : signals) { if (signal.second->GetServerAddress() == serverAddress) { return ObjectIdentifier(ObjectTypeSignal, signal.second->GetID()); } } } return ObjectIdentifier(ObjectTypeNone, ObjectNone); } Hardware::HardwareParams* Manager::CreateAndAddControl() { std::lock_guard Guard(hardwareMutex); ControlID newObjectID = ControlIdFirstHardware - 1; for (auto& hardwareParam : hardwareParams) { if (hardwareParam.first > newObjectID) { newObjectID = hardwareParam.first; } } ++newObjectID; Hardware::HardwareParams* newParams = new Hardware::HardwareParams(this, newObjectID); if (!newParams) { return nullptr; } hardwareParams[newObjectID] = newParams; return newParams; } bool Manager::ObjectIsPartOfRoute(const ObjectIdentifier& identifier, const Object* object, string& result) { std::lock_guard Guard(routeMutex); for (auto& route : routes) { if (!route.second->ObjectIsPartOfRoute(identifier)) { continue; } Languages::TextSelector selector; switch (identifier.GetObjectType()) { case ObjectTypeTrack: selector = Languages::TextTrackIsUsedByRoute; break; case ObjectTypeAccessory: selector = Languages::TextAccessoryIsUsedByRoute; break; case ObjectTypeSwitch: selector = Languages::TextSwitchIsUsedByRoute; break; case ObjectTypeRoute: selector = Languages::TextRouteIsUsedByRoute; break; case ObjectTypeSignal: selector = Languages::TextSignalIsUsedByRoute; break; default: selector = Languages::TextObjectIsUsedByRoute; break; } result = Logger::Logger::Format(Languages::GetText(selector), object->GetName(), route.second->GetName()); return true; } return false; } railcontrol-24+dfsg1/Manager.h000066400000000000000000001103011500456250600163530ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include #include #include #include #include "Config.h" #include "ControlInterface.h" #include "DataModel/AccessoryConfig.h" #include "DataModel/DataModel.h" #include "DataModel/FeedbackConfig.h" #include "DataModel/LocoConfig.h" #include "DataModel/ObjectIdentifier.h" #include "Hardware/HardwareParams.h" #include "Hardware/LocoCache.h" #include "Logger/Logger.h" #include "Storage/StorageHandler.h" class Manager { public: Manager() = delete; Manager(const Manager&) = delete; Manager& operator=(const Manager&) = delete; Manager(Config& config); ~Manager(); // booster inline BoosterState Booster() const { return boosterState; } void Booster(const ControlType controlType, const BoosterState status); // hardware (virt, CS2, ...) bool ControlSave(ControlID controlID, const HardwareType& hardwareType, const std::string& name, const std::string& arg1, const std::string& arg2, const std::string& arg3, const std::string& arg4, const std::string& arg5, std::string& result); bool ControlDelete(ControlID controlID); Hardware::HardwareParams* GetHardware(const ControlID controlID); unsigned int ControlsOfHardwareType(const HardwareType hardwareType); // control (console, web, ...) const std::string GetControlName(const ControlID controlID); const std::map ControlListByName() const; const std::map ControlListNames(const Hardware::Capabilities capability) const; inline const std::map LocoProtocolsOfControl(const ControlID controlID) const { return ProtocolsOfControl(AddressTypeLoco, controlID); } inline const std::map AccessoryProtocolsOfControl(const ControlID controlID) const { return ProtocolsOfControl(AddressTypeAccessory, controlID); } // loco std::string GetLocoList() const; DataModel::Loco* GetLoco(const LocoID locoID) const; DataModel::LocoConfig GetLocoOfConfigByMatchKey(const ControlID controlId, const std::string& matchKey) const; DataModel::Loco* GetLocoByMatchKey(const ControlID controlId, const std::string& matchKey) const; void LocoRemoveMatchKey(const LocoID locoId); void LocoReplaceMatchKey(const LocoID locoId, const std::string& newMatchKey); const std::map GetUnmatchedLocosOfControl(const ControlID controlId, const std::string& matchKey) const; const std::map LocoBaseListFree() const; const std::map LocoConfigByName() const; const std::map LocoIdsByName() const; inline void LocoSave(const DataModel::Loco* loco) const { if (!storage) { return; } Storage::TransactionGuard guard(storage); storage->Save(*loco); } bool LocoSave(LocoID locoID, const std::string& name, const ControlID controlID, const std::string& matchKey, const Protocol protocol, const Address address, const Address serverAddress, const Length length, const bool pushpull, const Speed maxSpeed, const Speed travelSpeed, const Speed reducedSpeed, const Speed creepingSpeed, const Propulsion propulsion, const TrainType type, const std::vector& locoFunctions, std::string& result); bool LocoDelete(const LocoID locoID, std::string& result); inline bool LocoDelete(const LocoID locoId) { std::string result; return LocoDelete(locoId, result); } bool LocoProtocolAddress(const LocoID locoID, ControlID& controlID, Protocol& protocol, Address& address) const; inline void LocoSpeed(const ControlType controlType, const ControlID controlID, const Protocol protocol, const Address address, const Speed speed) { // nullptr check of loco is done within submethod LocoBaseSpeed(controlType, GetLoco(controlID, protocol, address), speed); } inline bool LocoBaseSpeed(const ControlType controlType, const DataModel::ObjectIdentifier& locoBaseIdentifier, const Speed speed) { // nullptr check of loco is done within submethod return LocoBaseSpeed(controlType, GetLocoBase(locoBaseIdentifier), speed); } bool LocoBaseSpeed(const ControlType controlType, DataModel::LocoBase* loco, const Speed speed); Speed LocoSpeed(const LocoID locoID) const; inline void LocoOrientation(const ControlType controlType, const ControlID controlID, const Protocol protocol, const Address address, const Orientation orientation) { // nullptr check of loco is done within submethod LocoBaseOrientation(controlType, GetLoco(controlID, protocol, address), orientation); } inline void LocoBaseOrientation(const ControlType controlType, const DataModel::ObjectIdentifier& locoBaseIdentifier, const Orientation orientation) { // nullptr check of loco is done within submethod LocoBaseOrientation(controlType, GetLocoBase(locoBaseIdentifier), orientation); } void LocoBaseOrientation(const ControlType controlType, DataModel::LocoBase* loco, Orientation orientation); void LocoFunctionState(const ControlType controlType, const ControlID controlID, const Protocol protocol, const Address address, const DataModel::LocoFunctionNr function, const DataModel::LocoFunctionState on) { // nullptr check of loco is done within submethod LocoBaseFunctionState(controlType, GetLoco(controlID, protocol, address), function, on); } void LocoBaseFunctionState(const ControlType controlType, const DataModel::ObjectIdentifier& locoBaseIdentifier, const DataModel::LocoFunctionNr function, const DataModel::LocoFunctionState on); void LocoBaseFunctionState(const ControlType controlType, DataModel::LocoBase* loco, const DataModel::LocoFunctionNr function, const DataModel::LocoFunctionState on); // multiple unit DataModel::MultipleUnit* GetMultipleUnit(const MultipleUnitID multipleUnitId) const; DataModel::LocoConfig GetMultipleUnitOfConfigByMatchKey(const ControlID controlId, const std::string& matchKey) const; const std::map MultipleUnitConfigByName() const; inline void MultipleUnitSave(const DataModel::MultipleUnit* multipleUnit) const { if (!storage) { return; } Storage::TransactionGuard guard(storage); storage->Save(*multipleUnit); } bool MultipleUnitSave(MultipleUnitID multipleUnitID, const std::string& name, const ControlID controlID, const std::string& matchKey, const Address address, const Address serverAddress, const Length length, const bool pushpull, const Speed maxSpeed, const Speed travelSpeed, const Speed reducedSpeed, const Speed creepingSpeed, const TrainType type, const std::vector& locoFunctions, const std::vector& slaves, std::string& result); bool MultipleUnitDelete(const MultipleUnitID multipleUnitID, std::string& result); inline bool MultipleUnitDelete(const MultipleUnitID multipleUnitId) { std::string result; return MultipleUnitDelete(multipleUnitId, result); } bool MultipleUnitRelease(const MultipleUnitID multipleUnitID); // locobase inline DataModel::LocoBase* GetLocoBase(const DataModel::ObjectIdentifier& locoBaseIdentifier) const { return locoBaseIdentifier.GetObjectType() == ObjectTypeLoco ? static_cast(GetLoco(locoBaseIdentifier.GetObjectID())) : static_cast(GetMultipleUnit(locoBaseIdentifier.GetObjectID())); } const std::map LocoBaseIdsByName() const; const std::string& GetLocoBaseName(const DataModel::ObjectIdentifier& locoBaseIdentifier) const; // accessory base inline DataModel::AccessoryBase* GetAccessoryBase(const DataModel::ObjectIdentifier& accessoryBaseIdentifier) const { switch (accessoryBaseIdentifier.GetObjectType()) { case ObjectTypeAccessory: return static_cast(GetAccessory(accessoryBaseIdentifier.GetObjectID())); case ObjectTypeSwitch: return static_cast(GetSwitch(accessoryBaseIdentifier.GetObjectID())); case ObjectTypeSignal: return static_cast(GetSignal(accessoryBaseIdentifier.GetObjectID())); default: return nullptr; } } void AccessoryBaseState(const ControlType controlType, const ControlID controlID, const Protocol protocol, const Address address, const DataModel::AccessoryState state); void AccessoryBaseState(const ControlType controlType, const DataModel::ObjectIdentifier& identifier, const DataModel::AccessoryState state); // accessory bool AccessoryState(const ControlType controlType, const AccessoryID accessoryID, const DataModel::AccessoryState state, const bool force = false); void AccessoryState(const ControlType controlType, const AccessoryID accessoryID, const DataModel::AccessoryState state, const bool inverted, const bool on); DataModel::Accessory* GetAccessory(const AccessoryID accessoryID) const; const std::map GetUnmatchedAccessoriesOfControl(const ControlID controlId, const std::string& matchKey) const; const std::string& GetAccessoryName(const AccessoryID accessoryID) const; inline const std::map& AccessoryList() const { return accessories; } const std::map AccessoryConfigByName() const; bool AccessorySave(AccessoryID accessoryID, const std::string& name, const DataModel::LayoutItem::LayoutPosition x, const DataModel::LayoutItem::LayoutPosition y, const DataModel::LayoutItem::LayoutPosition z, const DataModel::LayoutItem::LayoutRotation rotation, const ControlID controlID, const std::string& matchKey, const Protocol protocol, const Address address, const AddressPort port, const Address serverAddress, const DataModel::AccessoryType type, const DataModel::AccessoryPulseDuration duration, const bool inverted, std::string& result); bool AccessoryDelete(const AccessoryID accessoryID, std::string& result); bool AccessoryRelease(const AccessoryID accessoryID); DataModel::AccessoryConfig GetAccessoryOfConfigByMatchKey(const ControlID controlId, const std::string& matchKey) const; DataModel::Accessory* GetAccessoryByMatchKey(const ControlID controlId, const std::string& matchKey) const; void AccessoryRemoveMatchKey(const AccessoryID accessoryId); // feedback void FeedbackState(const ControlID controlID, const FeedbackPin pin, const DataModel::Feedback::FeedbackState state); void FeedbackState(const FeedbackID feedbackID, const DataModel::Feedback::FeedbackState state); void FeedbackPublishState(const DataModel::Feedback* feedback); DataModel::Feedback* GetFeedback(const FeedbackID feedbackID) const; DataModel::Feedback* GetFeedbackUnlocked(const FeedbackID feedbackID) const; const std::string& GetFeedbackName(const FeedbackID feedbackID) const; inline const std::map& FeedbackList() const { return feedbacks; } const std::map FeedbackListByName() const; const std::map RoutesOfTrack(const TrackID trackID) const; const std::map FeedbacksOfTrack(const TrackID trackID) const; const std::map FeedbackConfigByName() const; bool FeedbackSave(FeedbackID feedbackID, const std::string& name, const DataModel::LayoutItem::Visible visible, const DataModel::LayoutItem::LayoutPosition posX, const DataModel::LayoutItem::LayoutPosition posY, const DataModel::LayoutItem::LayoutPosition posZ, const DataModel::LayoutItem::LayoutRotation rotation, const ControlID controlID, const std::string& matchKey, const FeedbackPin pin, const bool inverted, const DataModel::FeedbackType feedbackType, const RouteID routeId, std::string& result); bool FeedbackDelete(const FeedbackID feedbackID, std::string& result); inline bool FeedbackExists(const FeedbackID feedbackID) const { return feedbacks.count(feedbackID) == 1; } DataModel::FeedbackConfig GetFeedbackOfConfigByMatchKey(const ControlID controlId, const std::string& matchKey) const; DataModel::Feedback* GetFeedbackByMatchKey(const ControlID controlId, const std::string& matchKey) const; void FeedbackRemoveMatchKey(const FeedbackID feedbackId); void FeedbackReplaceMatchKey(const FeedbackID feedbackId, const std::string& newMatchKey); const std::map GetUnmatchedFeedbacksOfControl(const ControlID controlId, const std::string& matchKey) const; // track DataModel::Track* GetTrack(const TrackID trackID) const; const std::string& GetTrackName(const TrackID trackID) const; inline const std::map& TrackList() const { return tracks; } const std::map TrackListByName() const; const std::map TrackListMasterByName() const; const std::map TrackListIdByName(const TrackID excludeTrackID = TrackNone) const; inline void TrackSave(const DataModel::Track* track) const { if (!storage) { return; } Storage::TransactionGuard guard(storage); storage->Save(*track); } bool TrackSave(const TrackID trackID, const std::string& name, const bool showName, const std::string& displayName, const DataModel::LayoutItem::LayoutPosition posX, const DataModel::LayoutItem::LayoutPosition posY, const DataModel::LayoutItem::LayoutPosition posZ, const DataModel::LayoutItem::LayoutItemSize width, const DataModel::LayoutItem::LayoutRotation rotation, const DataModel::TrackType trackType, const TrackID main, const std::vector& newFeedbacks, const std::vector& newSignals, const DataModel::SelectRouteApproach selectRouteApproach, const bool allowLocoTurn, const bool releaseWhenFree, std::string& result); bool TrackDelete(const TrackID trackID, std::string& result); // switch bool SwitchState(const ControlType controlType, const SwitchID switchID, const DataModel::AccessoryState state, const bool force = false); DataModel::Switch* GetSwitch(const SwitchID switchID) const; const std::string& GetSwitchName(const SwitchID switchID) const; inline const std::map& SwitchList() const { return switches; } const std::map SwitchConfigByName() const; bool SwitchSave(SwitchID switchID, const std::string& name, const DataModel::LayoutItem::LayoutPosition x, const DataModel::LayoutItem::LayoutPosition y, const DataModel::LayoutItem::LayoutPosition z, const DataModel::LayoutItem::LayoutRotation rotation, const ControlID controlID, const std::string& matchKey, const Protocol protocol, const Address address, const Address serverAddress, const DataModel::AccessoryType type, const DataModel::AccessoryPulseDuration duration, const bool inverted, std::string& result); bool SwitchDelete(const SwitchID switchID, std::string& result); bool SwitchRelease(const SwitchID switchID); DataModel::Switch* GetSwitchByMatchKey(const ControlID controlId, const std::string& matchKey) const; void SwitchRemoveMatchKey(const SwitchID switchId); // route bool RouteExecute(Logger::Logger* logger, const DataModel::ObjectIdentifier& locoBaseIdentifier, const RouteID routeID); void RouteExecuteAsync(Logger::Logger* logger, const RouteID routeID); std::string GetRouteList() const; DataModel::Route* GetRoute(const RouteID routeID) const; const std::string& GetRouteName(const RouteID routeID) const; inline const std::map& RouteList() const { return routes; } const std::map RouteListByName() const; inline void RouteSave(const DataModel::Route* route) const { if (!storage) { return; } Storage::TransactionGuard guard(storage); storage->Save(*route); } bool RouteSave(RouteID routeID, const std::string& name, const Delay delay, const DataModel::Route::PushpullType pushpull, const Propulsion propulsion, const TrainType trainType, const Length minTrainLength, const Length maxTrainLength, const std::vector& relationsAtLock, const std::vector& relationsAtUnlock, const DataModel::LayoutItem::Visible visible, const DataModel::LayoutItem::LayoutPosition posX, const DataModel::LayoutItem::LayoutPosition posY, const DataModel::LayoutItem::LayoutPosition posZ, const Automode automode, const TrackID fromTrack, const Orientation fromOrientation, const TrackID toTrack, const Orientation toOrientation, const DataModel::Route::Speed speed, const FeedbackID feedbackIdReduced, const Delay reducedDelay, const FeedbackID feedbackIdCreep, const Delay creepDelay, const FeedbackID feedbackIdStop, const Delay stopDelay, const FeedbackID feedbackIdOver, const Pause waitAfterRelease, const RouteID followUpRoute, std::string& result); bool RouteDelete(const RouteID routeID, std::string& result); DataModel::Route* GetFirstRouteFromOrToTrack(const TrackID trackID) const; inline bool HasRouteFromOrToTrack(const TrackID trackID) const { return (GetFirstRouteFromOrToTrack(trackID) != nullptr); } // layer DataModel::Layer* GetLayer(const LayerID layerID) const; const std::map LayerListByName() const; const std::map LayerListByNameWithFeedback() const; bool LayerSave(const LayerID layerID, const std::string&name, std::string& result); bool LayerDelete(const LayerID layerID, std::string& result); // signal bool SignalState(const ControlType controlType, const SignalID signalID, const DataModel::AccessoryState state, const bool force = false); bool SignalState(const ControlType controlType, DataModel::Signal* signal, const DataModel::AccessoryState state, const bool force = false); DataModel::Signal* GetSignal(const SignalID signalID) const; const std::string& GetSignalName(const SignalID signalID) const; inline const std::map& SignalList() const { return signals; } const std::map SignalListByName() const; const std::map SignalConfigByName() const; bool SignalSave(SignalID signalID, const std::string& name, const DataModel::LayoutItem::LayoutPosition x, const DataModel::LayoutItem::LayoutPosition y, const DataModel::LayoutItem::LayoutPosition z, const DataModel::LayoutItem::LayoutItemSize height, const DataModel::LayoutItem::LayoutRotation rotation, const ControlID controlID, const std::string& matchKey, const Protocol protocol, const Address address, const Address serverAddress, const DataModel::AccessoryType type, const std::map& offsets, const DataModel::AccessoryPulseDuration duration, const bool inverted, std::string& result); bool SignalDelete(const SignalID signalID, std::string& result); void SignalPublishState(const ControlType controlType, const DataModel::Signal* signal); DataModel::Signal* GetSignalByMatchKey(const ControlID controlId, const std::string& matchKey) const; void SignalRemoveMatchKey(const SignalID signalId); bool SignalRelease(const SignalID signalID); // cluster DataModel::Cluster* GetCluster(const ClusterID clusterID) const; const std::map ClusterListByName() const; bool ClusterSave(ClusterID clusterID, const std::string& name, const std::vector& newTracks, std::string& result); bool ClusterDelete(const ClusterID clusterID); // text DataModel::Text* GetText(const TextID textID) const; inline const std::map& TextList() const { return texts; } const std::map TextListByName() const; bool TextSave(TextID textID, const std::string& name, const DataModel::LayoutItem::LayoutPosition x, const DataModel::LayoutItem::LayoutPosition y, const DataModel::LayoutItem::LayoutPosition z, const DataModel::LayoutItem::LayoutItemSize width, const DataModel::LayoutItem::LayoutRotation rotation, std::string& result); bool TextDelete(const TextID textID, std::string& result); // automode bool LocoBaseIntoTrack(Logger::Logger* logger, const DataModel::ObjectIdentifier& locoBaseIdentifier, const TrackID trackID); bool LocoRelease(const LocoID locoID); bool TrackRelease(const TrackID trackID); bool LocoBaseReleaseOnTrack(const TrackID trackID); bool TrackStartLocoBase(const TrackID trackID); bool TrackStopLocoBase(const TrackID trackID); void TrackBlock(const TrackID trackID, const bool blocked); void TrackSetLocoOrientation(const TrackID trackID, const Orientation orientation); void TrackPublishState(const DataModel::Track* track); bool RouteRelease(const RouteID routeID); bool LocoDestinationReached(const DataModel::LocoBase* loco, const DataModel::Route* route, const DataModel::Track* track); bool LocoBaseStart(const DataModel::ObjectIdentifier& locoBaseIdentifier); bool LocoBaseStartAll(); bool LocoBaseStop(const DataModel::ObjectIdentifier& locoBaseIdentifier); bool LocoBaseStopAll(); void LocoBaseStopAllImmediately(const ControlType controlType); bool LocoBaseAddTimeTable(const DataModel::ObjectIdentifier& locoBaseIdentifier, const RouteID routeID, const bool automode); std::string GetCs2Lokomotive() const; static std::string GetCs2Magnetartikel(const DataModel::AccessoryBase* base); std::string GetCs2Magnetartikel() const; std::string GetCs2GBS() const; std::string GetCs2GBS(const signed char gbs) const; inline DataModel::ObjectIdentifier GetLocoBaseIdentifierOfTrack(const TrackID trackId) { const DataModel::Track* track = GetTrack(trackId); return (track ? track->GetLocoBase() : DataModel::ObjectIdentifier()); } // settings inline StartupInitLocos GetStartupInitLocos() const { return startupInitLocos; } inline DataModel::AccessoryPulseDuration GetDefaultAccessoryDuration() const { return defaultAccessoryDuration; } inline bool GetAutoAddFeedback() const { return autoAddFeedback; } inline bool GetStopOnFeedbackInFreeTrack() const { return stopOnFeedbackInFreeTrack; } inline bool GetExecuteAccessory() const { return executeAccessory; } inline DataModel::SelectRouteApproach GetSelectRouteApproach() const { return selectRouteApproach; } inline DataModel::Loco::NrOfTracksToReserve GetNrOfTracksToReserve() const { return nrOfTracksToReserve; } bool SettingsSave(const Languages::Language language, const StartupInitLocos startupInitLocos, const DataModel::AccessoryPulseDuration duration, const bool autoAddFeedback, const bool stopOnFeedbackInFreeTrack, const bool executeAccessoryAlways, const DataModel::SelectRouteApproach selectRouteApproach, const DataModel::Loco::NrOfTracksToReserve nrOfTracksToReserve, const Logger::Logger::Level logLevel ); ControlID GetPossibleControlForLoco() const; ControlID GetPossibleControlForAccessory() const; ControlID GetPossibleControlForFeedback() const; void ProgramRead(const ControlID controlID, const ProgramMode mode, const Address address, const CvNumber cv); void ProgramWrite(const ControlID controlID, const ProgramMode mode, const Address address, const CvNumber cv, const CvValue value); void ProgramValue(const CvNumber cv, const CvValue value); inline static void ProgramDccValueStatic(Manager* manager, const CvNumber cv, const CvValue value) { manager->ProgramValue(cv, value); } bool CanHandle(const Hardware::Capabilities capability) const; bool CanHandle(const ControlID controlId, const Hardware::Capabilities capability) const; Hardware::Capabilities GetCapabilities(const ControlID controlID) const; bool LayoutItemRotate(const DataModel::ObjectIdentifier& identifier, std::string& result); bool LayoutItemNewPosition(const DataModel::ObjectIdentifier& identifier, const DataModel::LayoutItem::LayoutItemSize posX, const DataModel::LayoutItem::LayoutItemSize posY, std::string& result); inline bool IsServerEnabled() const { return serverEnabled; } DataModel::ObjectIdentifier GetIdentifierOfServerLocoAddress(const Address serverAddress) const; DataModel::ObjectIdentifier GetIdentifierOfServerAccessoryAddress(const Address serverAddress) const; private: bool ControlIsOfHardwareType(const ControlID controlID, const HardwareType hardwareType); ControlInterface* GetControl(const ControlID controlID) const; DataModel::Loco* GetLoco(const ControlID controlID, const Protocol protocol, const Address address) const; DataModel::Accessory* GetAccessory(const ControlID controlID, const Protocol protocol, const Address address, const AddressPort port) const; DataModel::Switch* GetSwitch(const ControlID controlID, const Protocol protocol, const Address address) const; DataModel::Feedback* GetFeedback(const ControlID controlID, const FeedbackPin pin) const; DataModel::Signal* GetSignal(const ControlID controlID, const Protocol protocol, const Address address) const; void AccessoryState(const ControlType controlType, DataModel::Accessory* accessory, const DataModel::AccessoryState state, const bool force); void SwitchState(const ControlType controlType, DataModel::Switch* mySwitch, const DataModel::AccessoryState state, const bool force); static inline void FeedbackState(DataModel::Feedback* feedback, const DataModel::Feedback::FeedbackState state) { feedback->SetState(state); } bool AccessoryPosition(const AccessoryID accessoryID, const DataModel::LayoutItem::LayoutPosition posX, const DataModel::LayoutItem::LayoutPosition posY, std::string& result); bool FeedbackPosition(const FeedbackID feedbackID, const DataModel::LayoutItem::LayoutPosition posX, const DataModel::LayoutItem::LayoutPosition posY, std::string& result); bool RoutePosition(const RouteID routeID, const DataModel::LayoutItem::LayoutPosition posX, const DataModel::LayoutItem::LayoutPosition posY, std::string& result); bool SignalPosition(const SignalID signalID, const DataModel::LayoutItem::LayoutPosition posX, const DataModel::LayoutItem::LayoutPosition posY, std::string& result); bool SwitchPosition(const SwitchID switchID, const DataModel::LayoutItem::LayoutPosition posX, const DataModel::LayoutItem::LayoutPosition posY, std::string& result); bool TextPosition(const TextID textID, const DataModel::LayoutItem::LayoutPosition posX, const DataModel::LayoutItem::LayoutPosition posY, std::string& result); bool TrackPosition(const TrackID trackID, const DataModel::LayoutItem::LayoutPosition posX, const DataModel::LayoutItem::LayoutPosition posY, std::string& result); bool AccessoryRotate(const AccessoryID accessoryID, std::string& result); bool FeedbackRotate(const FeedbackID feedbackID, std::string& result); bool SignalRotate(const SignalID signalID, std::string& result); bool SwitchRotate(const SwitchID switchID, std::string& result); bool TextRotate(const TextID textID, std::string& result); bool TrackRotate(const TrackID trackID, std::string& result); void AccessorySaveAndPublishSettings(const DataModel::Accessory* const accessory); void FeedbackSaveAndPublishSettings(const DataModel::Feedback* const feedback); void RouteSaveAndPublishSettings(const DataModel::Route* const route); void SignalSaveAndPublishSettings(const DataModel::Signal* const signal); void SwitchSaveAndPublishSettings(const DataModel::Switch* const mySwitch); void TextSaveAndPublishSettings(const DataModel::Text* const text); void TrackSaveAndPublishSettings(const DataModel::Track* const track); // layout bool CheckPositionFree(const DataModel::LayoutItem::LayoutPosition posX, const DataModel::LayoutItem::LayoutPosition posY, const DataModel::LayoutItem::LayoutPosition posZ, std::string& result) const; bool CheckPositionFree(const DataModel::LayoutItem::LayoutPosition posX, const DataModel::LayoutItem::LayoutPosition posY, const DataModel::LayoutItem::LayoutPosition posZ, const DataModel::LayoutItem::LayoutItemSize width, const DataModel::LayoutItem::LayoutItemSize height, const DataModel::LayoutItem::LayoutRotation rotation, std::string& result) const; template bool CheckLayoutPositionFree(const DataModel::LayoutItem::LayoutPosition posX, const DataModel::LayoutItem::LayoutPosition posY, const DataModel::LayoutItem::LayoutPosition posZ, std::string& result, const std::map& layoutVector, std::mutex& mutex) const; bool CheckLayoutItemPosition(const DataModel::LayoutItem* layoutItem, const DataModel::LayoutItem::LayoutPosition posX, const DataModel::LayoutItem::LayoutPosition posY, const DataModel::LayoutItem::LayoutPosition posZ, std::string& result) const; bool CheckLayoutItemPosition(const DataModel::LayoutItem* item, const DataModel::LayoutItem::LayoutPosition posX, const DataModel::LayoutItem::LayoutPosition posY, const DataModel::LayoutItem::LayoutPosition posZ, const DataModel::LayoutItem::LayoutItemSize width, const DataModel::LayoutItem::LayoutItemSize height, const DataModel::LayoutItem::LayoutRotation rotation, std::string& result) const; bool CheckAddressLoco(const Protocol protocol, const Address address, std::string& result); bool CheckAddressAccessory(const Protocol protocol, const Address address, std::string& result); inline bool CheckControlLocoProtocolAddress(const ControlID controlID, const Protocol protocol, const Address address, std::string& result) { return CheckControlProtocolAddress(AddressTypeLoco, controlID, protocol, address, result); } inline bool CheckControlMultipleUnitProtocolAddress(const ControlID controlID, const Address address, std::string& result) { return CheckControlProtocolAddress(AddressTypeMultipleUnit, controlID, ProtocolNone, address, result); } inline bool CheckControlAccessoryProtocolAddress(const ControlID controlID, const Protocol protocol, const Address address, std::string& result) { return CheckControlProtocolAddress(AddressTypeAccessory, controlID, protocol, address, result); } bool CheckControlProtocolAddress(const AddressType type, const ControlID controlID, const Protocol protocol, const Address address, std::string& result); const std::map ProtocolsOfControl(const AddressType type, const ControlID) const; bool LocoBaseReleaseInternal(DataModel::LocoBase* locoBase); bool LayerHasElements(const DataModel::Layer* layer, std::string& result); template void DeleteAllMapEntries(std::map& m, std::mutex& x) { std::lock_guard Guard(x); while (m.size()) { auto it = m.begin(); Value* content = it->second; m.erase(it); if (storage != nullptr) { logger->Info(Languages::TextSaving, content->GetName()); storage->Save(*content); } delete content; } } void DebounceWorker(); template T* CreateAndAddObject(std::map& objects, std::mutex& mutex); Hardware::HardwareParams* CreateAndAddControl(); template bool CheckObjectName(std::map& objects, const std::string& name) { for (auto& object : objects) { if (object.second->GetName().compare(name) == 0) { return false; } } return true; } inline bool CheckIfNumber(const char& c) { return c >= '0' && c <= '9'; } inline bool CheckIfThreeNumbers(const std::string& s) { const size_t sSize = s.size(); return sSize >= 3 && CheckIfNumber(s.at(sSize - 1)) && CheckIfNumber(s.at(sSize - 2)) && CheckIfNumber(s.at(sSize - 3)); } template std::string CheckObjectName(std::map& objects, std::mutex& mutex, const ID objectID, const std::string& name) { std::lock_guard Guard(mutex); if (objects.count(objectID) == 1) { const T* o = objects.at(objectID); const std::string& oldName = o->GetName(); if (oldName.compare(name) == 0) { return name; } } if (CheckObjectName(objects, name)) { return name; } unsigned int counter = 0; const std::string baseName = CheckIfThreeNumbers(name) ? name.substr(0, name.size() - 3) : name; while (true) { ++counter; std::stringstream ss; ss << baseName << std::setw(3) << std::setfill('0') << counter; std::string newName = ss.str(); if (CheckObjectName(objects, newName)) { return newName; } } } bool LocoIntoTrack(Logger::Logger *logger, DataModel::Loco* loco, const ObjectType objectType, DataModel::Track* track); void InitLocos(); static inline void InitLocosStatic(Manager* manager) { manager->InitLocos(); } void ProgramCheckBooster(const ProgramMode mode); bool ObjectIsPartOfRoute(const DataModel::ObjectIdentifier& identifier, const DataModel::Object* object, std::string& result); Logger::Logger* logger; volatile BoosterState boosterState; // FIXME: check usage of all mutexes // controls (Webserver & hardwareHandler. So each hardware is also added here). std::map controls; mutable std::mutex controlMutex; // hardware (virt, CS2, ...) std::map hardwareParams; mutable std::mutex hardwareMutex; // loco std::map locos; mutable std::mutex locoMutex; // multiple unit std::map multipleUnits; mutable std::mutex multipleUnitMutex; // accessory std::map accessories; mutable std::mutex accessoryMutex; // feedback std::map feedbacks; mutable std::mutex feedbackMutex; // track std::map tracks; mutable std::mutex trackMutex; // switch std::map switches; mutable std::mutex switchMutex; // route std::map routes; mutable std::mutex routeMutex; // layer std::map layers; mutable std::mutex layerMutex; // signal std::map signals; mutable std::mutex signalMutex; // cluster std::map clusters; mutable std::mutex clusterMutex; // text std::map texts; mutable std::mutex textMutex; // storage Storage::StorageHandler* storage; StartupInitLocos startupInitLocos; DataModel::AccessoryPulseDuration defaultAccessoryDuration; bool autoAddFeedback; bool stopOnFeedbackInFreeTrack; bool executeAccessory; DataModel::SelectRouteApproach selectRouteApproach; DataModel::Loco::NrOfTracksToReserve nrOfTracksToReserve; volatile bool run; volatile bool debounceRun; std::thread debounceThread; volatile bool initLocosDone; bool serverEnabled; const std::string unknownControl; const std::string unknownLoco; const std::string unknownMultipleUnit; const std::string unknownAccessory; const std::string unknownFeedback; const std::string unknownTrack; const std::string unknownSwitch; const std::string unknownRoute; const std::string unknownSignal; }; railcontrol-24+dfsg1/Network/000077500000000000000000000000001500456250600162655ustar00rootroot00000000000000railcontrol-24+dfsg1/Network/Select.h000066400000000000000000000017431500456250600176620ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #ifndef TEMP_FAILURE_RETRY #define TEMP_FAILURE_RETRY(expression) \ (__extension__ \ ({ long int __result; \ do __result = (long int) (expression); \ while (__result == -1L && errno == EINTR); \ __result; })) #endif railcontrol-24+dfsg1/Network/Serial.cpp000066400000000000000000000102471500456250600202140ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include #include "Network/Select.h" #include "Network/Serial.h" namespace Network { void Serial::Init() { fileHandle = open(tty.c_str(), O_RDWR | O_NOCTTY); if (!IsConnected()) { logger->Error(Languages::TextUnableToOpenSerial, tty); return; } struct termios options; options.c_cflag = 0; options.c_cc[VMIN] = 1; // read one byte at least options.c_cc[VTIME] = 0; // timeout disabled options.c_lflag = 0; options.c_iflag = 0; options.c_oflag = 0; cfsetispeed(&options, dataSpeed); cfsetospeed(&options, dataSpeed); switch (dataBits) { case 5: options.c_cflag |= CS5; // 5 data bits break; case 6: options.c_cflag |= CS6; // 6 data bits break; case 7: options.c_cflag |= CS7; // 7 data bits break; case 8: default: options.c_cflag |= CS8; // 8 data bits break; } if (stopBits == 2) { options.c_cflag |= CSTOPB; // 2 stop bit } // else 1 stop bit switch (parity) { case 'E': case 'e': options.c_cflag |= PARENB; // even parity break; case 'O': case 'o': options.c_cflag |= PARENB; options.c_cflag |= PARODD; // odd parity break; // default: no parity } // CSIZE not set: no datasize if (hardwareFlowControl == true) { options.c_cflag |= CRTSCTS; // hardware flow control } options.c_cflag |= CLOCAL; // ignore control lines options.c_cflag |= CREAD; // enable receiver tcsetattr(fileHandle, TCSANOW, &options); // store options ClearBuffers(); } void Serial::Close() { if (!IsConnected()) { return; } close(fileHandle); fileHandle = -1; } bool Serial::Receive(std::string& data, const size_t maxData, const unsigned int timeoutS, const unsigned int timeoutUS) { const size_t dataBufferSize = 1024; unsigned char dataBuffer[dataBufferSize]; const size_t max = maxData > dataBufferSize ? dataBufferSize : maxData; ssize_t ret = Receive(dataBuffer, max, timeoutS, timeoutUS); if (ret <= 0) { return false; } data.append(reinterpret_cast(dataBuffer), ret); return true; } ssize_t Serial::Receive(unsigned char* data, const size_t maxData, const unsigned int timeoutS, const unsigned int timeoutUS) { if (!IsConnected()) { return -1; } fd_set set; FD_ZERO(&set); FD_SET(fileHandle, &set); struct timeval tvTimeout; tvTimeout.tv_sec = timeoutS; tvTimeout.tv_usec = timeoutUS; ssize_t ret = TEMP_FAILURE_RETRY(select(FD_SETSIZE, &set, NULL, NULL, &tvTimeout)); if (ret <= 0) { return -1; } ret = read(fileHandle, data, maxData); if (ret <= 0) // FIXME: why not (ret < 0)? { return -1; } return ret; } bool Serial::ReceiveExact(std::string& data, const size_t length, const unsigned int timeoutS, const unsigned int timeoutUS) { size_t startSize = data.length(); size_t endSize = startSize + length; while (endSize > data.length()) { bool ret = Receive(data, endSize - data.length(), timeoutS, timeoutUS); if (ret == false) { return false; } } return true; } ssize_t Serial::ReceiveExact(unsigned char* data, const size_t length, const unsigned int timeoutS, const unsigned int timeoutUS) { size_t actualSize = 0; size_t endSize = length; while (actualSize < endSize) { ssize_t ret = Receive(data + actualSize, endSize - actualSize, timeoutS, timeoutUS); if (ret <= 0) { return actualSize; } actualSize += ret; } return actualSize; } } railcontrol-24+dfsg1/Network/Serial.h000066400000000000000000000060141500456250600176560ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include #include //close & write; #include "Logger/Logger.h" namespace Network { class Serial { public: Serial() = delete; Serial(const Serial&) = delete; Serial& operator=(const Serial&) = delete; inline Serial(Logger::Logger* logger, const std::string& tty, const unsigned int dataSpeed, // from termio (ex. B9600) const unsigned char dataBits, const char parity, const unsigned char stopBits, const bool hardwareFlowControl = false) : logger(logger), tty(tty), dataSpeed(dataSpeed), dataBits(dataBits), parity(parity), stopBits(stopBits), hardwareFlowControl(hardwareFlowControl), fileHandle(-1) { Init(); } ~Serial() { Close(); } inline void ReInit() { Close(); Init(); } inline bool IsConnected() const { return fileHandle != -1; } inline void ClearBuffers() { tcflush(fileHandle, TCIOFLUSH); } inline ssize_t Send(const std::string& data) { return Send(reinterpret_cast(data.c_str()), data.length()); } inline ssize_t Send(const unsigned char data) { return Send(&data, 1); } inline ssize_t Send(const unsigned char* data, const size_t size) { if (!IsConnected()) { return 0; } std::lock_guard Guard(fileHandleMutex); return write(fileHandle, data, size); } bool Receive(std::string& data, const size_t maxData = 1024, const unsigned int timeoutS = 0, const unsigned int timeoutUS = 100000); ssize_t Receive(unsigned char* data, const size_t maxData, const unsigned int timeoutS = 0, const unsigned int timeoutUS = 100000); bool ReceiveExact(std::string& data, const size_t length, const unsigned int timeoutS = 0, const unsigned int timeoutUS = 100000); ssize_t ReceiveExact(unsigned char* data, const size_t length, const unsigned int timeoutS = 0, const unsigned int timeoutUS = 100000); private: void Init(); void Close(); Logger::Logger* logger; const std::string tty; const unsigned int dataSpeed; const unsigned char dataBits; const char parity; const unsigned char stopBits; const bool hardwareFlowControl; int fileHandle; mutable std::mutex fileHandleMutex; }; } railcontrol-24+dfsg1/Network/TcpClient.cpp000066400000000000000000000066521500456250600206670ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include #include #include #include "Network/Select.h" #include "Network/TcpClient.h" namespace Network { TcpConnection TcpClient::GetTcpClientConnection(Logger::Logger* logger, const std::string& host, const unsigned short port) { struct sockaddr_storage address; struct sockaddr_in* addressPointer = reinterpret_cast(&address); addressPointer->sin_family = AF_INET; addressPointer->sin_port = htons(port); int ok = inet_pton(AF_INET, host.c_str(), &(addressPointer->sin_addr)); if (ok <= 0) { logger->Error(Languages::TextUnableToResolveAddress, host); return TcpConnection(0); } int sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) { logger->Error(Languages::TextUnableToCreateTcpSocket, host, port); return TcpConnection(0); } ok = ConnectWithTimeout(sock, reinterpret_cast(addressPointer), sizeof(address)); if (ok < 0) { Languages::TextSelector text; switch (errno) { case ECONNREFUSED: text = Languages::TextConnectionRefused; break; case ENETUNREACH: text = Languages::TextNetworkUnreachable; break; default: text = Languages::TextConnectionFailed; } logger->Error(text, host, port); close(sock); return TcpConnection(0); } return TcpConnection(sock, &address); } int TcpClient::ConnectWithTimeout(int sock, struct sockaddr *addr, socklen_t length) { // Set non-blocking long arg = fcntl(sock, F_GETFL, nullptr); if (arg < 0) { return -1; } arg |= O_NONBLOCK; if (fcntl(sock, F_SETFL, arg) < 0) { return -1; } // Trying to connect with timeout int ret = connect(sock, addr, length); if (ret < 0) { if (errno != EINPROGRESS) { return -1; } while (true) { fd_set myset; int valopt; socklen_t lon; struct timeval tv; tv.tv_sec = 3; tv.tv_usec = 0; FD_ZERO(&myset); FD_SET(sock, &myset); ret = TEMP_FAILURE_RETRY(select(sock + 1, nullptr, &myset, nullptr, &tv)); if (ret < 0 && errno == EINTR) { continue; } if (ret <= 0) { return -1; } // Socket selected for write lon = sizeof(int); if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (void*) (&valopt), &lon) < 0) { return -1; } // Check the value returned... if (valopt) { return -1; } break; } } // Set to blocking mode again... if ((arg = fcntl(sock, F_GETFL, nullptr)) < 0) { return -1; } arg &= (~O_NONBLOCK); if (fcntl(sock, F_SETFL, arg) < 0) { return -1; } return ret; } } railcontrol-24+dfsg1/Network/TcpClient.h000066400000000000000000000024371500456250600203310ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include #include #include #include "Logger/Logger.h" #include "Network/TcpConnection.h" namespace Network { class TcpClient { public: TcpClient() = delete; TcpClient(const TcpClient&) = delete; TcpClient& operator=(const TcpClient&) = delete; static TcpConnection GetTcpClientConnection(Logger::Logger* logger, const std::string& host, const unsigned short port); private: static int ConnectWithTimeout(int sock, struct sockaddr *addr, socklen_t length); }; } railcontrol-24+dfsg1/Network/TcpConnection.cpp000066400000000000000000000062571500456250600215510ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include // close & TEMP_FAILURE_RETRY; #include "Network/Select.h" #include "Network/TcpConnection.h" #include "Utils/Utils.h" #ifndef MSG_NOSIGNAL #define MSG_NOSIGNAL 0 #endif namespace Network { void TcpConnection::Terminate() const { if (connected) { connected = false; close(connectionSocket); } } int TcpConnection::Send(const unsigned char* buffer, const size_t bufferLength, const int flags) const { if (connectionSocket == 0 || connected == false) { errno = ENOTCONN; return -1; } errno = 0; fd_set set; FD_ZERO(&set); FD_SET(connectionSocket, &set); struct timeval timeout; timeout.tv_sec = 5; timeout.tv_usec = 0; int ret = TEMP_FAILURE_RETRY(select(FD_SETSIZE, NULL, &set, NULL, &timeout)); if (ret < 0) { return ret; } if (ret == 0) { errno = ETIMEDOUT; return -1; } ret = send(connectionSocket, buffer, bufferLength, flags | MSG_NOSIGNAL); if (ret <= 0) { errno = ECONNRESET; Terminate(); return -1; } return ret; } int TcpConnection::Receive(unsigned char* buffer, const size_t bufferLength, const int flags) const { if (connectionSocket == 0 || connected == false) { errno = ENOTCONN; return -1; } errno = 0; fd_set set; FD_ZERO(&set); FD_SET(connectionSocket, &set); struct timeval timeout; timeout.tv_sec = 1; timeout.tv_usec = 0; int ret = TEMP_FAILURE_RETRY(select(FD_SETSIZE, &set, NULL, NULL, &timeout)); if (ret < 0) { return ret; } if (ret == 0) { errno = ETIMEDOUT; return -1; } ret = recv(connectionSocket, buffer, bufferLength, flags); if (ret <= 0) { errno = ECONNRESET; Terminate(); return -1; } return ret; } bool TcpConnection::Receive(std::string& data, const size_t maxData, const int flags) { const size_t dataBufferSize = 1024; unsigned char dataBuffer[dataBufferSize]; const size_t max = maxData > dataBufferSize ? dataBufferSize : maxData; ssize_t ret = Receive(dataBuffer, max, flags); if (ret <= 0) { return false; } data.append(reinterpret_cast(dataBuffer), ret); return true; } int TcpConnection::ReceiveExact(unsigned char* data, const size_t length, const int flags) const { int actualSize = 0; int endSize = length; while (actualSize < endSize) { int ret = Receive(data + actualSize, endSize - actualSize, flags); if (ret <= 0) { return actualSize; } actualSize += ret; } return actualSize; } } railcontrol-24+dfsg1/Network/TcpConnection.h000066400000000000000000000052231500456250600212060ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include "Utils/Network.h" namespace Network { class TcpConnection { public: TcpConnection() = delete; TcpConnection& operator=(const TcpConnection&) = delete; inline TcpConnection(int socket, const struct sockaddr_storage* address = nullptr) : connectionSocket(socket), connected(socket != 0) { if (address) { this->address = *address; } else { memset(&(this->address), 0, sizeof(struct sockaddr_storage)); } } inline TcpConnection(const TcpConnection& other) : connectionSocket(other.connectionSocket), connected(other.connected), address(other.address) { } inline ~TcpConnection() { Terminate(); } void Terminate() const; int Send(const unsigned char* buffer, const size_t bufferLength, const int flags = 0) const; inline int Send(const char* buffer, const size_t bufferLength, const int flags = 0) const { return Send(reinterpret_cast(buffer), bufferLength, flags); } inline int Send(const std::string& string, const int flags = 0) const { return Send(string.c_str(), string.size(), flags); } int Receive(unsigned char* buffer, const size_t bufferLength, const int flags = 0) const; inline int Receive(char* buffer, const size_t bufferLength, const int flags = 0) const { return Receive(reinterpret_cast(buffer), bufferLength, flags); } bool Receive(std::string& data, const size_t maxData = 1024, const int flags = 0); int ReceiveExact(unsigned char* buffer, const size_t bufferLength, const int flags = 0) const; inline bool IsConnected() const { return connected; } inline std::string AddressAsString() { return Utils::Network::AddressToString(&address); } private: mutable int connectionSocket; mutable volatile bool connected; struct sockaddr_storage address; }; } railcontrol-24+dfsg1/Network/TcpServer.cpp000066400000000000000000000107011500456250600207050ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include //memset #include #include #include #include #include #include "Network/Select.h" #include "Network/TcpServer.h" #include "Utils/Utils.h" namespace Network { TcpServer::TcpServer(const std::string& address, const unsigned short port, const std::string& threadName) : run(false), error(""), address(address), port(port), threadName(threadName) { } TcpServer::~TcpServer() { TerminateTcpServer(); } void TcpServer::StartTcpServer() { if (run) { return; } run = true; struct sockaddr_in6 serverAddr6; memset(reinterpret_cast(&serverAddr6), 0, sizeof(serverAddr6)); serverAddr6.sin6_family = AF_INET6; if (address.compare("localhost") == 0) { serverAddr6.sin6_addr = IN6ADDR_LOOPBACK_INIT; } else { serverAddr6.sin6_addr = in6addr_any; } serverAddr6.sin6_port = htons(port); SocketCreateBindListen(serverAddr6.sin6_family, reinterpret_cast(&serverAddr6)); #ifdef __CYGWIN__ struct sockaddr_in serverAddr4; memset(reinterpret_cast(&serverAddr4), 0, sizeof(serverAddr4)); serverAddr4.sin_family = AF_INET; if (address.compare("localhost") == 0) { serverAddr4.sin_addr.s_addr = inet_addr("127.0.0.1"); } else { serverAddr4.sin_addr.s_addr = htonl(INADDR_ANY); } serverAddr4.sin_port = htons(port); SocketCreateBindListen(serverAddr4.sin_family, reinterpret_cast(&serverAddr4)); #endif } void TcpServer::SocketCreateBindListen(int family, struct sockaddr* address) { int serverSocket = socket(family, SOCK_STREAM, 0); if (serverSocket < 0) { error = "Unable to create socket for tcp server. Unable to serve clients."; return; } int on = 1; int intResult = setsockopt(serverSocket, SOL_SOCKET, SO_REUSEADDR, (const void*) &on, sizeof(on)); if (intResult < 0) { error = "Unable to set tcp server socket option SO_REUSEADDR."; close(serverSocket); return; } intResult = bind(serverSocket, address, family == AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6)); if (intResult < 0) { error = "Unable to bind socket for tcp server to port. Unable to serve clients."; close (serverSocket); return; } const int MaxClientsInQueue = 5; intResult = listen(serverSocket, MaxClientsInQueue); if (intResult != 0) { error = "Unable to listen on socket for tcp server. Unable to serve clients."; close(serverSocket); return; } if (!run) { close(serverSocket); return; } serverThreads.push_back(std::thread(&Network::TcpServer::Worker, this, serverSocket)); } void TcpServer::TerminateTcpServer() { run = false; while (serverThreads.size()) { std::thread& serverThread = serverThreads.back(); serverThread.join(); serverThreads.pop_back(); } } void TcpServer::Worker(int socket) { Utils::Utils::SetThreadName(threadName); fd_set set; struct timeval tv; struct sockaddr_storage clientAddress; socklen_t clientAddressLength = sizeof(clientAddress); while (run == true) { // wait for connection and abort on shutdown int ret; do { FD_ZERO(&set); FD_SET(socket, &set); tv.tv_sec = 1; tv.tv_usec = 0; ret = TEMP_FAILURE_RETRY(select(FD_SETSIZE, &set, NULL, NULL, &tv)); if (run == false) { return; } } while (ret == 0); if (ret < 0) { continue; } // accept connection int socketClient = accept(socket, reinterpret_cast(&clientAddress), &clientAddressLength); if (socketClient < 0) { continue; } // create client TcpConnection* connection = new TcpConnection(socketClient, &clientAddress); Work(connection); } } } railcontrol-24+dfsg1/Network/TcpServer.h000066400000000000000000000030741500456250600203570ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include #include "Network/TcpConnection.h" struct sockaddr; namespace Network { class TcpServer { public: TcpServer() = delete; TcpServer(const TcpServer&) = delete; TcpServer& operator=(const TcpServer&) = delete; protected: TcpServer(const std::string& address, const unsigned short port, const std::string& threadName); virtual ~TcpServer(); void StartTcpServer(); void TerminateTcpServer(); virtual void Work(Network::TcpConnection* connection) = 0; private: void SocketCreateBindListen(int family, struct sockaddr* address); void Worker(int socket); volatile bool run; std::vector serverThreads; std::string error; const std::string& address; const unsigned short port; const std::string threadName; }; } railcontrol-24+dfsg1/Network/UdpClient.h000066400000000000000000000044341500456250600203320ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include #include #include "Logger/Logger.h" #include "Utils/Network.h" namespace Network { class UdpClient { public: UdpClient() = delete; UdpClient(const UdpClient&) = delete; UdpClient& operator=(const UdpClient&) = delete; inline UdpClient(const int serverSocket, const struct sockaddr_storage* clientAddress, const time_t timeout = 60) : run(true), lastUsed(time(nullptr)), timeout(timeout), serverSocket(serverSocket), clientAddress(*clientAddress) { } virtual ~UdpClient() { } inline bool IsClient(const struct sockaddr_storage* address) { return Utils::Network::CompareAddresses(&clientAddress, address); } inline void Update() { lastUsed = time(nullptr); } inline bool Terminated() { return !run; } inline bool TimedOut() { return lastUsed + timeout < time(nullptr); } virtual void Work(const unsigned char* buffer, const size_t size) = 0; protected: inline void Send(const unsigned char* buffer, const size_t size) { sendto(serverSocket, buffer, size, 0, reinterpret_cast(&clientAddress), sizeof(struct sockaddr_storage)); } inline void Terminate() { run = false; } inline std::string AddressAsString() { return Utils::Network::AddressToString(&clientAddress); } private: volatile bool run; time_t lastUsed; time_t timeout; const int serverSocket; const struct sockaddr_storage clientAddress; }; } railcontrol-24+dfsg1/Network/UdpConnection.cpp000066400000000000000000000066411500456250600215500ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include // memset #include // close & TEMP_FAILURE_RETRY; #include "Network/Select.h" #include "Network/UdpConnection.h" #include "Utils/Utils.h" namespace Network { UdpConnection::UdpConnection(Logger::Logger* logger, const std::string& server, const unsigned short port) : logger(logger), connected(false), server(server), port(port) { memset((char*)&sockaddr, 0, sizeof(sockaddr)); struct sockaddr_in* sockaddr_in = reinterpret_cast(&sockaddr); sockaddr_in->sin_family = AF_INET; sockaddr_in->sin_port = htons(port); int ok = inet_pton(AF_INET, server.c_str(), &sockaddr_in->sin_addr); if (ok <= 0) { logger->Error(Languages::TextUnableToResolveAddress, server); return; } CreateUdpSocket(); } UdpConnection::UdpConnection(Logger::Logger* logger, struct sockaddr* sockaddr) : logger(logger), connected(false), sockaddr(*sockaddr) { const struct sockaddr_in* sockaddr_in = reinterpret_cast(&(this->sockaddr)); char serverC[20]; inet_ntop(AF_INET, &sockaddr_in->sin_addr, serverC, sizeof(serverC)); server = serverC; port = ntohs(sockaddr_in->sin_port); CreateUdpSocket(); } void UdpConnection::CreateUdpSocket() { // create socket connectionSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (connectionSocket == -1) { logger->Error(Languages::TextUnableToCreateUdpSocket, server, port); return; } // setting receive timeout to 1s struct timeval tv; tv.tv_sec = 1; tv.tv_usec = 0; setsockopt(connectionSocket, SOL_SOCKET, SO_RCVTIMEO, (const char*) &tv, sizeof(struct timeval)); connected = true; } bool UdpConnection::Bind() { int ret = bind(connectionSocket, &sockaddr, sizeof(sockaddr)); if (ret == 0) { return true; } logger->Error(Languages::TextUnableToBindSocketToPort, port); connected = false; close(connectionSocket); return false; } void UdpConnection::Terminate() { if (connected) { connected = false; close(connectionSocket); } } int UdpConnection::Send(const char* buffer, const size_t bufferLength) { if (!connected) { logger->Error(Languages::TextConnectionReset); errno = ECONNRESET; return -1; } return sendto(connectionSocket, buffer, bufferLength, 0, &sockaddr, sizeof(sockaddr)); } int UdpConnection::Receive(char* buffer, const size_t bufferLength) { if (!connected) { logger->Error(Languages::TextConnectionReset); errno = ECONNRESET; return -1; } ssize_t ret; do { if (!connected) { return 0; } ret = recvfrom(connectionSocket, buffer, bufferLength, 0, NULL, NULL); } while(ret < 0 && errno == EAGAIN); return ret; } } railcontrol-24+dfsg1/Network/UdpConnection.h000066400000000000000000000036571500456250600212210ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include "Logger/Logger.h" namespace Network { class UdpConnection { public: UdpConnection() = delete; UdpConnection(Logger::Logger* logger, const std::string& server, const unsigned short port); UdpConnection(Logger::Logger* logger, struct sockaddr* sockaddr); inline ~UdpConnection() { Terminate(); } void Terminate(); bool Bind(); inline bool IsConnected() { return connected; } int Send(const char* buffer, const size_t bufferLength); inline int Send(const unsigned char* buffer, const size_t bufferLength) { return Send(reinterpret_cast(buffer), bufferLength); } inline int Send(const std::string& string) { return Send(string.c_str(), string.size()); } int Receive(char* buffer, const size_t bufferLength); inline int Receive(unsigned char* buffer, const size_t bufferLength) { return Receive(reinterpret_cast(buffer), bufferLength); } private: void CreateUdpSocket(); Logger::Logger* const logger; int connectionSocket; volatile bool connected; struct sockaddr sockaddr; std::string server; unsigned short port; }; } railcontrol-24+dfsg1/Network/UdpServer.cpp000066400000000000000000000120551500456250600207130ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include //memset #include #include #include #include #include #include "Network/Select.h" #include "Network/UdpClient.h" #include "Network/UdpServer.h" #include "Utils/Utils.h" namespace Network { UdpServer::UdpServer(const std::string& address, const unsigned short port, const std::string& threadName) : logger(Logger::Logger::GetLogger(threadName)), run(false), error(""), address(address), port(port), serverSocket(0), threadName(threadName) { } UdpServer::~UdpServer() { TerminateUdpServer(); } void UdpServer::StartUdpServer() { if (run) { return; } run = true; struct sockaddr_in6 serverAddr6; memset(reinterpret_cast(&serverAddr6), 0, sizeof(serverAddr6)); serverAddr6.sin6_family = AF_INET6; if (address.compare("localhost") == 0) { serverAddr6.sin6_addr = IN6ADDR_LOOPBACK_INIT; } else { serverAddr6.sin6_addr = in6addr_any; } serverAddr6.sin6_port = htons(port); SocketCreateBindListen(serverAddr6.sin6_family, reinterpret_cast(&serverAddr6)); #ifdef __CYGWIN__ struct sockaddr_in serverAddr4; memset(reinterpret_cast(&serverAddr4), 0, sizeof(serverAddr4)); serverAddr4.sin_family = AF_INET; if (address.compare("localhost") == 0) { serverAddr4.sin_addr.s_addr = inet_addr("127.0.0.1"); } else { serverAddr4.sin_addr.s_addr = htonl(INADDR_ANY); } serverAddr4.sin_port = htons(port); SocketCreateBindListen(serverAddr4.sin_family, reinterpret_cast(&serverAddr4)); #endif } void UdpServer::SocketCreateBindListen(int family, struct sockaddr* address) { serverSocket = socket(family, SOCK_DGRAM, 0); if (serverSocket < 0) { error = "Unable to create socket for udp server. Unable to serve clients."; return; } int on = 1; int intResult = setsockopt(serverSocket, SOL_SOCKET, SO_REUSEADDR, (const void*) &on, sizeof(on)); if (intResult < 0) { error = "Unable to set tcp server socket option SO_REUSEADDR."; close(serverSocket); return; } intResult = bind(serverSocket, address, family == AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6)); if (intResult < 0) { error = "Unable to bind socket for udp server to port. Unable to serve clients."; close (serverSocket); return; } if (!run) { close(serverSocket); return; } serverThreads.push_back(std::thread(&Network::UdpServer::Worker, this)); } void UdpServer::TerminateUdpServer() { run = false; while (serverThreads.size()) { std::thread& serverThread = serverThreads.back(); serverThread.join(); serverThreads.pop_back(); } for (auto client : clients) { delete client; } clients.clear(); } void UdpServer::Worker() { Utils::Utils::SetThreadName(threadName); fd_set set; struct timeval tv; struct sockaddr_storage clientAddress; socklen_t clientAddressLength = sizeof(clientAddress); while (run == true) { // wait for data and abort on shutdown int ret; do { FD_ZERO(&set); FD_SET(serverSocket, &set); tv.tv_sec = 1; tv.tv_usec = 0; ret = TEMP_FAILURE_RETRY(select(FD_SETSIZE, &set, NULL, NULL, &tv)); if (run == false) { return; } } while (ret == 0); if (ret < 0) { continue; } unsigned char buffer[1472]; // 1472 = Max UDP data size memset(reinterpret_cast(&clientAddress), 0, sizeof(clientAddressLength)); ssize_t size = recvfrom(serverSocket, buffer, sizeof(buffer), 0, reinterpret_cast(&clientAddress), &clientAddressLength); UdpClient* client = GetClient(&clientAddress); client->Work(buffer, size); CleanUpClients(); } } UdpClient* UdpServer::GetClient(const struct sockaddr_storage* clientAddress) { for (auto client : clients) { if (client->IsClient(clientAddress)) { client->Update(); return client; } } UdpClient* newClient = UdpClientFactory(serverSocket, clientAddress); clients.push_back(newClient); return newClient; } void UdpServer::CleanUpClients() { for (std::vector::iterator it = clients.begin(); it != clients.end();) { UdpClient* client = *it; if (client->Terminated() || client->TimedOut()) { clients.erase(it); delete client; } else { ++it; } } } } railcontrol-24+dfsg1/Network/UdpServer.h000066400000000000000000000035011500456250600203540ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include #include #include "Logger/Logger.h" namespace Network { class UdpClient; class UdpServer { public: UdpServer() = delete; UdpServer(const UdpServer&) = delete; UdpServer& operator=(const UdpServer&) = delete; protected: UdpServer(const std::string& address, const unsigned short port, const std::string& threadName); virtual ~UdpServer(); void StartUdpServer(); void TerminateUdpServer(); virtual UdpClient* UdpClientFactory(const int serverSocket, const struct sockaddr_storage* clientAddress) = 0; protected: std::vector clients; private: void SocketCreateBindListen(int family, struct sockaddr* address); void Worker(); UdpClient* GetClient(const struct sockaddr_storage* clientAddress); void CleanUpClients(); Logger::Logger* logger; volatile bool run; std::vector serverThreads; std::string error; const std::string address; const unsigned short port; int serverSocket; const std::string threadName; }; } railcontrol-24+dfsg1/README.md000066400000000000000000000002461500456250600161150ustar00rootroot00000000000000# RailControl RailControl is a software to control trains on a modelrailway. Please visit [RailControl Homepage](https://www.railcontrol.org/) for more information. railcontrol-24+dfsg1/RailControl.cpp000066400000000000000000000134461500456250600176000ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include //printf #include //exit(0); #include //memset #include #include #include #include //close; #include #include "ArgumentHandler.h" #include "Hardware/HardwareHandler.h" #include "Languages.h" #include "Logger/Logger.h" #include "Manager.h" #include "Network/Select.h" #include "RailControl.h" #include "Version.h" #include "Utils/Utils.h" using std::vector; using std::string; void killRailControlIfNeeded(Logger::Logger* logger) { if (++stopSignalCounter < MaxStopSignalCounter) { return; } logger->Info(Languages::TextReceivedSignalKill, MaxStopSignalCounter); exit(1); } void shutdownRailControlSignal(int signo) { Logger::Logger* logger = Logger::Logger::GetLogger("Main"); logger->Info(Languages::TextShutdownRequestedBySignal, signo); killRailControlIfNeeded(logger); } void shutdownRailControlWebserver() { Logger::Logger* logger = Logger::Logger::GetLogger("Main"); logger->Info(Languages::TextShutdownRequestedByWebClient); killRailControlIfNeeded(logger); } int main (int argc, char* argv[]) { std::map argumentMap; argumentMap["config"] = 'c'; argumentMap["daemonize"] = 'd'; argumentMap["logfile"] = 'l'; argumentMap["help"] = 'h'; argumentMap["silent"] = 's'; ArgumentHandler argumentHandler(argc, argv, argumentMap, 'c'); const bool help = argumentHandler.GetArgumentBool('h'); if (help) { std::cout << "Usage: " << argv[0] << " " << std::endl; std::cout << "Options:" << std::endl; std::cout << " --config=ConfigFile Read config file with file name ConfigFile (default ConfigFile: railcontrol.conf)" << std::endl; std::cout << "-d --daemonize Daemonize RailControl. Implies -s" << std::endl; std::cout << " --logfile=LogFile Write a logfile to file LogFile (default LogFile: railcontrol.log)" << std::endl; std::cout << "-h --help Show this help" << std::endl; std::cout << "-s --silent Omit writing to console" << std::endl; return EXIT_SUCCESS; } const bool daemonize = argumentHandler.GetArgumentBool('d'); if (daemonize) { pid_t pid = fork(); if (pid > 0) { return EXIT_SUCCESS; } close(STDERR_FILENO); close(STDOUT_FILENO); close(STDIN_FILENO); } stopSignalCounter = 0; signal(SIGINT, shutdownRailControlSignal); signal(SIGTERM, shutdownRailControlSignal); const string RailControl = "RailControl"; Utils::Utils::SetThreadName(RailControl); Logger::Logger* logger = Logger::Logger::GetLogger("Main"); const bool silent = daemonize || argumentHandler.GetArgumentBool('s'); if (!silent) { logger->AddConsoleLogger(); } const string logFileName = argumentHandler.GetArgumentString('l', "railcontrol.log"); if (logFileName.length() > 0) { logger->AddFileLogger(logFileName); } logger->Info(Languages::TextStarting, RailControl); logger->Info(Languages::TextVersion, GetVersionInfoRailControlVersion()); logger->Info(Languages::TextCompileDate, Utils::Utils::TimestampToDate(GetVersionInfoCompileTimestamp())); logger->Info(Languages::TextGitHash, GetVersionInfoGitHash()); logger->Info(Languages::TextGitDate, Utils::Utils::TimestampToDate(GetVersionInfoGitTimestamp())); const unsigned int changedFiles = GetVersionInfoGitDirty(); if (changedFiles) { logger->Info(Languages::TextGitDirty, changedFiles); } logger->Info(Languages::TextStartArgument, argv[0]); const string configFileDefaultName("railcontrol.conf"); string configFileName = argumentHandler.GetArgumentString('c', configFileDefaultName); if (configFileName.compare("") == 0) { configFileName = configFileDefaultName; } if (!Utils::Utils::FileExists(configFileName)) { logger->Warning(Languages::TextConfigFileNotFound, configFileName, configFileDefaultName); configFileName = configFileDefaultName; } if (configFileName.compare(configFileDefaultName) == 0 && !Utils::Utils::FileExists(configFileDefaultName)) { Utils::Utils::CopyFile(logger, "railcontrol.conf.dist", configFileDefaultName); } Config config(configFileName); unsigned int logKeepBackups = config.getIntValue("logkeepbackups", 10); Utils::Utils::RemoveOldBackupFiles(logger, logFileName, logKeepBackups); char input = 0; { // the main program is running in the manager. Manager m(config); // wait for q or r followed by \n or SIGINT or SIGTERM do { if (silent) { Utils::Utils::SleepForSeconds(1); } else { struct timeval tv; tv.tv_sec = 1; tv.tv_usec = 0; fd_set set; FD_ZERO(&set); FD_SET(STDIN_FILENO, &set); int ret = TEMP_FAILURE_RETRY(select(FD_SETSIZE, &set, NULL, NULL, &tv)); if (ret > 0 && FD_ISSET(STDIN_FILENO, &set)) { __attribute__((unused)) size_t unused = read(STDIN_FILENO, &input, sizeof(input)); } } } while ((input != 'q') && (input != 'r') && !isShutdownRunning()); logger->Info(Languages::TextShutdownRailControl); } // here the destructor of manager is called and RailControl is shut down if (input == 'r') { // restart RailControl return execv(argv[0], argv); } else { // exit RailControl return EXIT_SUCCESS; } } railcontrol-24+dfsg1/RailControl.h000066400000000000000000000021371500456250600172400ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once static volatile unsigned char stopSignalCounter; static const unsigned char MaxStopSignalCounter = 3; void killRailControlIfNeeded(Logger::Logger* logger); void shutdownRailControlSignal(int); void shutdownRailControlWebserver(); inline bool isShutdownRunning() { return stopSignalCounter > 0; } inline bool isKillRunning() { return stopSignalCounter > 1; } railcontrol-24+dfsg1/Server/000077500000000000000000000000001500456250600161025ustar00rootroot00000000000000railcontrol-24+dfsg1/Server/CS2/000077500000000000000000000000001500456250600164715ustar00rootroot00000000000000railcontrol-24+dfsg1/Server/CS2/CS2Client.cpp000066400000000000000000000040601500456250600207230ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include //memset #include "DataModel/ObjectIdentifier.h" #include "Manager.h" #include "Server/CS2/CS2Client.h" #include "Utils/Utils.h" using DataModel::ObjectIdentifier; namespace Server { namespace CS2 { // worker is the thread that handles client requests void CS2Client::Receiver() { Utils::Utils::SetThreadName("CS2Client # " + std::to_string(id) + " receiver"); logger->Debug(Languages::TextTcpConnectionEstablished, connection->AddressAsString()); ReceiverImpl(); logger->Debug(Languages::TextTcpConnectionClosed, connection->AddressAsString()); terminated = true; } void CS2Client::ReceiverImpl() { while (run) { unsigned char buffer[CANCommandBufferLength]; int ret = connection->ReceiveExact(buffer, sizeof(buffer), 0); if (!run) { return; } if (ret == -1) { if (errno != ETIMEDOUT) { logger->Error(Languages::TextErrorReadingData, strerror(errno)); return; } continue; } if (ret == 0) { continue; } if (ret != 13) { logger->Error(Languages::TextErrorReadingData, strerror(errno)); } Parse(buffer); } } void CS2Client::Send(const unsigned char* buffer) { if (connection->Send(buffer, CANCommandBufferLength) == -1) { logger->Error(Languages::TextUnableToSendDataToControl); } } }} // namespace Server::CS2 railcontrol-24+dfsg1/Server/CS2/CS2Client.h000066400000000000000000000046241500456250600203760ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "Hardware/LocoCache.h" #include "Hardware/Protocols/MaerklinCANCommon.h" #include "Network/TcpConnection.h" #include "Utils/Utils.h" namespace Server { namespace CS2 { class CS2Server; class CS2Client : protected Hardware::Protocols::MaerklinCANCommon { public: CS2Client() = delete; CS2Client(const CS2Client&) = delete; CS2Client& operator=(const CS2Client&) = delete; inline CS2Client(const unsigned int id, Network::TcpConnection* connection, // connection must be deleted after using! Manager& manager) : MaerklinCANCommon("affeaffe", ControlIdCS2Server, &manager, true, Logger::Logger::GetLogger("CS2Client # " + std::to_string(id))), id(id), connection(connection), terminated(false) { Init(); } virtual ~CS2Client() { } inline void Stop() { run = false; } inline bool IsTerminated() { return terminated; } protected: void Receiver() override; void Send(const unsigned char* buffer) override; private: void ReceiverImpl(); virtual void CacheSave(__attribute__((unused)) Hardware::LocoCacheEntry& entry) override { } virtual void CacheSave(__attribute__((unused)) Hardware::LocoCacheEntry& entry, __attribute__((unused)) const std::string& oldMatchKey) override { } virtual LocoID CacheDelete(__attribute__((unused)) const std::string& matchKey) override { return LocoNone; } virtual void CacheUpdateSlaves() override { } static const unsigned char CANCommandBufferLength = 13; unsigned int id; Network::TcpConnection* connection; bool terminated; }; }} // namespace Server::CS2 railcontrol-24+dfsg1/Server/CS2/CS2Server.cpp000066400000000000000000000171351500456250600207620ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include #include "DataModel/LocoBase.h" #include "DataModel/LocoFunctions.h" #include "Manager.h" #include "Network/Select.h" #include "Network/UdpConnection.h" #include "Server/CS2/CS2Client.h" #include "Server/CS2/CS2Server.h" #include "Utils/Network.h" using DataModel::Loco; using DataModel::LocoBase; using DataModel::LocoFunctionNr; using DataModel::LocoFunctionState; namespace Network { class UdpClient; } namespace Server { namespace CS2 { CS2Server::CS2Server(Manager& manager) : ControlInterface(ControlTypeCS2Server), Network::TcpServer("0.0.0.0", CS2ReceiverPort, "CS2Server"), logger(Logger::Logger::GetLogger("CS2Server")), manager(manager), lastClientID(0), runUdp(false) { } CS2Server::~CS2Server() { CleanUpClients(); logger->Info(Languages::TextCS2ServerStopped); } void CS2Server::Start() { StartTcpServer(); StartUdpServer(); logger->Info(Languages::TextCS2ServerStarted); } void CS2Server::Stop() { TerminateUdpServer(); TerminateTcpServer(); // stopping all clients for (auto client : clients) { client->Stop(); } } void CS2Server::StartUdpServer() { runUdp = true; // Only IPv4 is used by clients struct sockaddr_in serverAddr4; memset(reinterpret_cast(&serverAddr4), 0, sizeof(serverAddr4)); serverAddr4.sin_family = AF_INET; serverAddr4.sin_addr.s_addr = htonl(INADDR_ANY); serverAddr4.sin_port = htons(CS2ReceiverPort); UdpSocketCreateBindListen(serverAddr4.sin_family, reinterpret_cast(&serverAddr4)); } void CS2Server::TerminateUdpServer() { runUdp = false; udpServerThread.join(); } void CS2Server::UdpSocketCreateBindListen(int family, struct sockaddr* address) { udpServerSocket = socket(family, SOCK_DGRAM, 0); if (udpServerSocket < 0) { // error = "Unable to create socket for udp server. Unable to serve clients."; return; } int on = 1; int intResult = setsockopt(udpServerSocket, SOL_SOCKET, SO_REUSEADDR, (const void*) &on, sizeof(on)); if (intResult < 0) { // error = "Unable to set tcp server socket option SO_REUSEADDR."; close(udpServerSocket); return; } intResult = bind(udpServerSocket, address, sizeof(struct sockaddr_in)); if (intResult < 0) { // error = "Unable to bind socket for udp server to port. Unable to serve clients."; close(udpServerSocket); return; } if (!runUdp) { close(udpServerSocket); return; } udpServerThread = std::thread(&Server::CS2::CS2Server::UdpWorker, this); } void CS2Server::UdpWorker() { Utils::Utils::SetThreadName("CS2 UDP Server"); Logger::Logger* udpLogger = Logger::Logger::GetLogger("CS2 UDP Server"); fd_set set; struct timeval tv; struct sockaddr_storage clientAddress; socklen_t clientAddressLength = sizeof(clientAddress); while (runUdp) { // wait for data and abort on shutdown int ret; do { FD_ZERO(&set); FD_SET(udpServerSocket, &set); tv.tv_sec = 1; tv.tv_usec = 0; ret = TEMP_FAILURE_RETRY(select(FD_SETSIZE, &set, NULL, NULL, &tv)); if (!runUdp) { return; } } while (ret == 0); if (ret < 0) { continue; } static const int CANCommandBufferLength = 13; unsigned char buffer[CANCommandBufferLength]; memset(reinterpret_cast(&clientAddress), 0, sizeof(clientAddressLength)); ssize_t size = recvfrom(udpServerSocket, buffer, sizeof(buffer), 0, reinterpret_cast(&clientAddress), &clientAddressLength); udpLogger->HexIn(buffer, size); if (size != CANCommandBufferLength) { continue; } buffer[0] = 0x00; buffer[1] = 0x30; buffer[2] = 0x00; buffer[3] = 0x00; buffer[4] = 0x08; buffer[5] = 0x00; buffer[6] = 0x00; buffer[7] = 0x00; buffer[8] = 0x00; buffer[9] = 0x03; buffer[10] = 0x08; buffer[11] = 0xff; buffer[12] = 0xff; sendto(udpServerSocket, buffer, sizeof(buffer), 0, reinterpret_cast(&clientAddress), sizeof(clientAddress)); // sockaddr_in* clientAddress4 = reinterpret_cast(&clientAddress); // clientAddress4->sin_port = htons(CS2SenderPort); // Network::UdpConnection udpConnection(logger, reinterpret_cast(clientAddress4)); // udpConnection.Bind(); // udpConnection.Send(buffer, sizeof(buffer)); // udpConnection.Terminate(); } } void CS2Server::Booster(__attribute__((unused)) const ControlType controlType, const BoosterState status) { if (status == BoosterStateGo) { // for (auto client : clients) // { // reinterpret_cast(client)->SendPowerOn(); // } } else { // for (auto client : clients) // { // reinterpret_cast(client)->SendPowerOff(); // } } } void CS2Server::LocoBaseSpeed(__attribute__((unused)) const ControlType controlType, const LocoBase* locoBase, __attribute__((unused)) const Speed speed) { if (nullptr == locoBase) { return; } // for (auto client : clients) // { // reinterpret_cast(client)->SendLocoInfo(locoBase); // } } void CS2Server::LocoBaseOrientation(__attribute__((unused)) const ControlType controlType, const LocoBase* locoBase, __attribute__((unused)) const Orientation orientation) { if (nullptr == locoBase) { return; } // for (auto client : clients) // { // reinterpret_cast(client)->SendLocoInfo(locoBase); // } } void CS2Server::LocoBaseFunction(__attribute__((unused)) const ControlType controlType, const LocoBase* locoBase, __attribute__((unused)) const LocoFunctionNr function, __attribute__((unused)) const LocoFunctionState state) { if (!locoBase) { return; } // for (auto client : clients) // { // reinterpret_cast(client)->SendLocoInfo(locoBase); // } } void CS2Server::AccessoryState(__attribute__((unused)) const ControlType controlType, const DataModel::Accessory* accessory) { AccessoryBaseState(accessory); } void CS2Server::SwitchState(__attribute__((unused)) const ControlType controlType, const DataModel::Switch* mySwitch) { AccessoryBaseState(mySwitch); } void CS2Server::SignalState(__attribute__((unused)) const ControlType controlType, const DataModel::Signal* signal) { AccessoryBaseState(signal); } void CS2Server::AccessoryBaseState(const DataModel::AccessoryBase* accessoryBase) { if (!accessoryBase) { return; } // for (auto client : clients) // { // reinterpret_cast(client)->SendTurnoutInfo(accessoryBase); // } } void CS2Server::Work(Network::TcpConnection* connection) { clients.push_back(new CS2Client(++lastClientID, connection, manager)); CleanUpClients(); } void CS2Server::CleanUpClients() { for (auto iterator = clients.begin(); iterator != clients.end();) { CS2Client* client = *iterator; if (client->IsTerminated()) { iterator = clients.erase(iterator); delete client; } else { ++iterator; } } } }} // namespace Server::CS2 railcontrol-24+dfsg1/Server/CS2/CS2Server.h000066400000000000000000000056301500456250600204240ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include #include #include #include #include "ControlInterface.h" #include "Logger/Logger.h" #include "Manager.h" #include "Network/UdpServer.h" namespace Server { namespace CS2 { class CS2Client; class CS2Server : public ControlInterface, private Network::TcpServer { public: static const unsigned short CS2ReceiverPort = 15731; static const unsigned short CS2SenderPort = 15730; CS2Server() = delete; CS2Server(const CS2Server&) = delete; CS2Server& operator=(const CS2Server&) = delete; CS2Server(Manager& manager); ~CS2Server(); void Start() override; void Stop() override; void Work(Network::TcpConnection* connection) override; inline const std::string& GetName() const override { static const std::string CS2Name("CS2Server"); return CS2Name; } void Booster(const ControlType controlType, const BoosterState status) override; void LocoBaseOrientation(const ControlType controlType, const DataModel::LocoBase* loco, const Orientation direction) override; void LocoBaseFunction(const ControlType controlType, const DataModel::LocoBase* loco, const DataModel::LocoFunctionNr function, const DataModel::LocoFunctionState on) override; void LocoBaseSpeed(const ControlType controlType, const DataModel::LocoBase* loco, const Speed speed) override; void AccessoryState(const ControlType controlType, const DataModel::Accessory* accessory) override; void SwitchState(const ControlType controlType, const DataModel::Switch* mySwitch) override; void SignalState(const ControlType controlType, const DataModel::Signal* signal) override; private: Logger::Logger* logger; Manager& manager; std::vector clients; unsigned int lastClientID; bool runUdp; std::thread udpServerThread; int udpServerSocket; void AccessoryBaseState(const DataModel::AccessoryBase* accessoryBase); void CleanUpClients(); void StartUdpServer(); void TerminateUdpServer(); void UdpSocketCreateBindListen(int family, struct sockaddr* address); void UdpWorker(); }; }} // namespace Server::CS2 railcontrol-24+dfsg1/Server/Web/000077500000000000000000000000001500456250600166175ustar00rootroot00000000000000railcontrol-24+dfsg1/Server/Web/HtmlTag.cpp000066400000000000000000000037631500456250600206740ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include "Server/Web/HtmlTag.h" namespace Server { namespace Web { HtmlTag HtmlTag::AddAttribute(const std::string& name, const std::string& value) { if (name.size() > 0) { this->attributes[name] += value; } return *this; } std::ostream& operator<<(std::ostream& stream, const HtmlTag& tag) { if (tag.name.size() > 0) { stream << "<" << tag.name; if (tag.id.size() > 0) { stream << " id=\"" << tag.id << "\""; } if (tag.classes.size() > 0) { stream << " class=\""; for (auto& c : tag.classes) { stream << " " << c; } stream << "\""; } for (auto& attribute : tag.attributes) { stream << " " << attribute.first; if (attribute.second.size() > 0) { stream << "=\"" << attribute.second << "\""; } } stream << ">"; if (tag.childTags.size() == 0 && tag.content.size() == 0 && ( tag.name.compare("input") == 0 || tag.name.compare("link") == 0 || tag.name.compare("meta") == 0 || tag.name.compare("br") == 0)) { return stream; } } for (auto& child : tag.childTags) { stream << child; } stream << tag.content; if (tag.name.size() > 0) { stream << ""; } return stream; } }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTag.h000066400000000000000000000051511500456250600203320ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include #include #include #include "Languages.h" #include "Logger/Logger.h" namespace Server { namespace Web { class HtmlTag { public: HtmlTag& operator=(const HtmlTag&) = delete; inline HtmlTag() { } inline HtmlTag(const std::string& name) : name(name) { } inline virtual ~HtmlTag() { } virtual HtmlTag AddAttribute(const std::string& name, const std::string& value = ""); inline virtual bool IsAttributeSet(const std::string& name) { return attributes.count(name) == 1; } inline virtual HtmlTag AddChildTag(const HtmlTag& child) { this->childTags.push_back(child); return *this; } inline virtual HtmlTag AddContent(const std::string& content) { this->content += content; return *this; } template inline HtmlTag AddContent(const Languages::TextSelector text, Args... args) { return AddContent(Logger::Logger::Format(Languages::GetText(text), args...)); } inline virtual HtmlTag AddClass(const std::string& className) { if (className.length() > 0) { classes.push_back(className); } return *this; } inline virtual HtmlTag AddId(const std::string& id) { this->id = id; return *this; } inline virtual size_t ContentSize() const { return content.size(); } inline size_t ChildCount() const { return childTags.size(); } inline operator std::string () const { std::stringstream ss; ss << *this; return ss.str(); } friend std::ostream& operator<<(std::ostream& stream, const HtmlTag& tag); protected: std::string name; std::vector childTags; std::map attributes; std::vector classes; std::string content; std::string id; }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagAccessory.cpp000066400000000000000000000160471500456250600225470ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include "DataModel/Accessory.h" #include "Server/Web/HtmlTagAccessory.h" using std::string; using std::to_string; namespace Server { namespace Web { HtmlTagAccessory::HtmlTagAccessory(const DataModel::Accessory* accessory) : HtmlTagLayoutItem(dynamic_cast(accessory)) { const DataModel::AccessoryType accessoryType = static_cast(accessory->GetAccessoryType() & DataModel::AccessoryTypeMask); switch (accessoryType) { case DataModel::AccessoryTypeStraight: case DataModel::AccessoryTypeDecoupler: image = ""; break; case DataModel::AccessoryTypeTurn: image = ""; break; case DataModel::AccessoryTypeDefault: case DataModel::AccessoryTypeLight: default: break; } switch (accessoryType) { case DataModel::AccessoryTypeDefault: case DataModel::AccessoryTypeStraight: case DataModel::AccessoryTypeTurn: image += ""; break; case DataModel::AccessoryTypeDecoupler: image += ""; image += ""; break; case DataModel::AccessoryTypeLight: image = "" "" "" "" "" "" "" "" "" "" ""; break; case DataModel::AccessoryTypeLightInhouse: image = //"" "" "" "" "" "" "" "" ""; break; case DataModel::AccessoryTypeLightStreet: image = "" "" "" "" "" ""; break; default: break; } const DataModel::AccessoryState state = accessory->GetAccessoryState(); string accessoryIdString = to_string(accessory->GetID()); imageDiv.AddClass("accessory_item"); switch (accessory->GetAccessoryType() & DataModel::AccessoryTypeConnectionMask) { case DataModel::AccessoryTypeOnOn: case DataModel::AccessoryTypeOnOff: imageDiv.AddClass(state == DataModel::AccessoryStateOn ? "accessory_on" : "accessory_off"); imageDiv.AddAttribute("onclick", "return onClickAccessory(" + accessoryIdString + ");"); break; case DataModel::AccessoryTypeOnPush: imageDiv.AddClass("accessory_off"); imageDiv.AddAttribute("onpointerdown", "return onPointerDownAccessory(" + accessoryIdString + ");"); imageDiv.AddAttribute("onpointerup", "return onPointerUpAccessory(" + accessoryIdString + ");"); break; default: imageDiv.AddClass("accessory_off"); break; } const string& accessoryName = accessory->GetName(); AddToolTip(accessoryName + " (addr=" + to_string(accessory->GetAddress()) + ")"); AddContextMenuEntry(accessoryName); AddContextMenuEntry(Languages::TextReleaseAccessory, "fireRequestAndForget('/?cmd=accessoryrelease&accessory=" + accessoryIdString + "');"); AddContextMenuEntry(Languages::TextEditAccessory, "loadPopup('/?cmd=accessoryedit&accessory=" + accessoryIdString + "');"); AddContextMenuEntry(Languages::TextDeleteAccessory, "loadPopup('/?cmd=accessoryaskdelete&accessory=" + accessoryIdString + "');"); FinishInit(); } }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagAccessory.h000066400000000000000000000026111500456250600222040ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "Server/Web/HtmlTagLayoutItem.h" namespace DataModel { class Accessory; } namespace Server { namespace Web { class HtmlTagAccessory : public HtmlTagLayoutItem { public: HtmlTagAccessory() = delete; HtmlTagAccessory(const DataModel::Accessory* accessory); virtual ~HtmlTagAccessory() {} inline virtual HtmlTag AddAttribute(const std::string& name, const std::string& value) override { childTags[0].AddAttribute(name, value); return *this; } inline virtual bool IsAttributeSet(const std::string& name) override { return childTags[0].IsAttributeSet(name); } }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagButton.cpp000066400000000000000000000026571500456250600220710ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include "Server/Web/HtmlTagButton.h" #include "Server/Web/HtmlTagInput.h" namespace Server { namespace Web { HtmlTagButton::HtmlTagButton(const std::string& value, const std::string& command, const std::string& toolTipText) : HtmlTag(), commandID("skip_" + command) { HtmlTag buttonTag("button"); buttonTag.AddClass("button"); buttonTag.AddAttribute("name", commandID); buttonTag.AddAttribute("type", "button"); buttonTag.AddId(commandID); buttonTag.AddContent(value); if (toolTipText.length() > 0) { HtmlTag toolTip("span"); toolTip.AddClass("tooltip"); toolTip.AddContent(toolTipText); buttonTag.AddChildTag(toolTip); } AddChildTag(buttonTag); } }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagButton.h000066400000000000000000000041331500456250600215250ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include "Server/Web/HtmlTag.h" namespace Server { namespace Web { class HtmlTagButton : public HtmlTag { public: HtmlTagButton() = delete; HtmlTagButton(const HtmlTagButton&) = delete; HtmlTagButton& operator=(const HtmlTagButton&) = delete; HtmlTagButton(const std::string& value, const std::string& command, const std::string& tooltipText = ""); inline HtmlTagButton(const Languages::TextSelector value, const std::string& command, const std::string& tooltipText = "") : HtmlTagButton(Languages::GetText(value), command, tooltipText) { } inline HtmlTagButton(const std::string& value, const std::string& command, const Languages::TextSelector tooltipText) : HtmlTagButton(value, command, Languages::GetText(tooltipText)) { } virtual ~HtmlTagButton() { } virtual inline HtmlTag AddAttribute(const std::string& name, const std::string& value = "") override { childTags[0].AddAttribute(name, value); return *this; } virtual inline bool IsAttributeSet(const std::string& name) override { return childTags[0].IsAttributeSet(name); } virtual inline HtmlTag AddClass(const std::string& value) override { childTags[0].AddClass(value); return *this; } protected: const std::string commandID; }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagButtonCancel.h000066400000000000000000000022461500456250600226360ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "Server/Web/HtmlTagButton.h" namespace Server { namespace Web { class HtmlTagButtonCancel : public HtmlTagButton { public: inline HtmlTagButtonCancel() : HtmlTagButton(HtmlTag("span").AddContent("✘"), "popup_cancel") { AddAttribute("onclick", "document.getElementById('popup').style.display = 'none'; return false;"); AddClass("wide_button"); } }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagButtonCommand.cpp000066400000000000000000000030031500456250600233520ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include "Utils/Utils.h" #include "Server/Web/HtmlTagButtonCommand.h" namespace Server { namespace Web { HtmlTagButtonCommand::HtmlTagButtonCommand(const std::string& value, const std::string& command, const std::map& arguments, const std::string& tooltip, const std::string& additionalOnClick) : HtmlTagButton(value, command, tooltip) { std::string cmd = Utils::Utils::StringBeforeDelimiter(command, "_"); std::stringstream ss; ss << "var theUrl = '/?cmd=" << cmd; for (auto& argument : arguments) { ss << "&" << argument.first << "=" << argument.second; } ss <<"';" "fireRequestAndForget(theUrl);"; ss << additionalOnClick; ss << "return false;"; AddAttribute("onclick", ss.str()); } }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagButtonCommand.h000066400000000000000000000047461500456250600230360ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include "Server/Web/HtmlTagButton.h" namespace Server { namespace Web { class HtmlTagButtonCommand : public HtmlTagButton { public: HtmlTagButtonCommand() = delete; HtmlTagButtonCommand(const HtmlTagButtonCommand&) = delete; HtmlTagButtonCommand& operator=(const HtmlTagButtonCommand&) = delete; HtmlTagButtonCommand(const std::string& value, const std::string& command, const std::map& arguments = std::map(), const std::string& tooltip = "", const std::string& additionalOnClick = ""); inline HtmlTagButtonCommand(const Languages::TextSelector value, const std::string& command, const std::map& arguments = std::map(), const std::string& tooltip = "", const std::string& additionalOnClick = "") : HtmlTagButtonCommand(Languages::GetText(value), command, arguments, tooltip, additionalOnClick) { } inline HtmlTagButtonCommand(const std::string& value, const std::string& command, const std::map& arguments, const Languages::TextSelector tooltip) : HtmlTagButtonCommand(value, command, arguments, Languages::GetText(tooltip)) { } inline HtmlTagButtonCommand(const std::string& value, const std::string& command, const Languages::TextSelector tooltip) : HtmlTagButtonCommand(value, command, std::map(), Languages::GetText(tooltip)) { } inline HtmlTagButtonCommand(const std::string& value, const std::string& command, const std::string& tooltip) : HtmlTagButtonCommand(value, command, std::map(), tooltip) { } }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagButtonCommandFullScreen.cpp000066400000000000000000000031471500456250600253460ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include "Languages.h" #include "Server/Web/HtmlTagButtonCommandFullScreen.h" namespace Server { namespace Web { HtmlTagButtonCommandFullScreen::HtmlTagButtonCommandFullScreen() : HtmlTagButton("" "" "" "" "" "" "" "", "fullscreen", Languages::GetText(Languages::TextFullScreen)) { AddAttribute("onclick", "fullScreen(); return false;"); } }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagButtonCommandFullScreen.h000066400000000000000000000017121500456250600250070ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include "Server/Web/HtmlTagButton.h" namespace Server { namespace Web { class HtmlTagButtonCommandFullScreen : public HtmlTagButton { public: HtmlTagButtonCommandFullScreen(); }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagButtonCommandPressRelease.cpp000066400000000000000000000035031500456250600256750ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include "Utils/Utils.h" #include "Server/Web/HtmlTagButtonCommandPressRelease.h" namespace Server { namespace Web { HtmlTagButtonCommandPressRelease::HtmlTagButtonCommandPressRelease(const std::string& value, const std::string& command, const std::map& arguments, const std::string& tooltip) : HtmlTagButton(value, command, tooltip) { AddClass("button_off"); std::string cmd = Utils::Utils::StringBeforeDelimiter(command, "_"); std::stringstream begin; begin << "var url = '/?cmd=" << cmd << "&on="; std::stringstream end; for (auto& argument : arguments) { end << "&" << argument.first << "=" << argument.second; } end <<"';fireRequestAndForget(url);return false;"; const std::string beginString = begin.str(); const std::string endString = end.str(); const std::string down = beginString + "1" + endString; const std::string up = beginString + "0" + endString; AddAttribute("onmousedown", down); AddAttribute("onmouseup", up); AddAttribute("ontouchstart", down); AddAttribute("ontouchend", up); } }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagButtonCommandPressRelease.h000066400000000000000000000026751500456250600253530ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include "Server/Web/HtmlTagButton.h" namespace Server { namespace Web { class HtmlTagButtonCommandPressRelease : public HtmlTagButton { public: HtmlTagButtonCommandPressRelease(const std::string& value, const std::string& command, const std::map& arguments = std::map(), const std::string& tooltip = ""); // HtmlTagButtonCommandPressRelease(const std::string& value, // const std::string& command, // const Languages::TextSelector tooltip) // : HtmlTagButtonCommandPressRelease(value, command, std::map(), Languages::GetText(tooltip)) // {} }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagButtonCommandToggle.cpp000066400000000000000000000032171500456250600245230ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include "Utils/Utils.h" #include "Server/Web/HtmlTagButtonCommandToggle.h" namespace Server { namespace Web { HtmlTagButtonCommandToggle::HtmlTagButtonCommandToggle(const std::string& value, const std::string& command, const bool on, const std::map& arguments, const std::string& tooltip) : HtmlTagButton(value, command, tooltip) { AddClass(on == true ? "button_on" : "button_off"); std::string cmd = Utils::Utils::StringBeforeDelimiter(command, "_"); std::stringstream ss; ss << "var on = !document.getElementById('" << commandID << "').classList.contains('button_on');" "var url = '/?cmd=" << cmd << "&on=' + (on ? '1' : '0') + '"; for (auto& argument : arguments) { ss << "&" << argument.first << "=" << argument.second; } ss <<"';" "fireRequestAndForget(url);" "return false;"; AddAttribute("onclick", ss.str()); } }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagButtonCommandToggle.h000066400000000000000000000027051500456250600241710ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include "Server/Web/HtmlTagButton.h" namespace Server { namespace Web { class HtmlTagButtonCommandToggle : public HtmlTagButton { public: HtmlTagButtonCommandToggle(const std::string& value, const std::string& command, const bool on, const std::map& arguments = std::map(), const std::string& tooltip = ""); HtmlTagButtonCommandToggle(const std::string& value, const std::string& command, const bool on, const Languages::TextSelector tooltip) : HtmlTagButtonCommandToggle(value, command, on, std::map(), Languages::GetText(tooltip)) {} }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagButtonCommandWide.h000066400000000000000000000024551500456250600236420ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include "Server/Web/HtmlTagButtonCommand.h" namespace Server { namespace Web { class HtmlTagButtonCommandWide : public HtmlTagButtonCommand { public: HtmlTagButtonCommandWide() = delete; HtmlTagButtonCommandWide(const Languages::TextSelector value, const std::string& command, const std::map& arguments, const std::string& additionalOnClick) : HtmlTagButtonCommand(value, command, arguments, "", additionalOnClick) { AddClass("wide_button"); } }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagButtonMinus.h000066400000000000000000000025721500456250600225460ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include "Server/Web/HtmlTagButton.h" namespace Server { namespace Web { class HtmlTagButtonMinus : public HtmlTagButton { public: HtmlTagButtonMinus() = delete; HtmlTagButtonMinus(HtmlTagButtonMinus&) = delete; HtmlTagButtonMinus& operator=(HtmlTagButtonMinus&) = delete; inline HtmlTagButtonMinus(const std::string& field, const int min) : HtmlTagButton(HtmlTag("span").AddContent("-"), field + "_minus") { AddAttribute("onclick", "decrementIntegerValue('" + field + "', " + std::to_string(min) + "); return false;"); AddClass("small_button"); } }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagButtonOK.h000066400000000000000000000021541500456250600217600ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "Server/Web/HtmlTagButton.h" namespace Server { namespace Web { class HtmlTagButtonOK : public HtmlTagButton { public: HtmlTagButtonOK() : HtmlTagButton(HtmlTag("span").AddContent("✔"), "popup_ok") { AddAttribute("onclick", "submitEditForm(); return false;"); AddClass("wide_button"); } }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagButtonPlus.h000066400000000000000000000025621500456250600223750ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include "Server/Web/HtmlTagButton.h" namespace Server { namespace Web { class HtmlTagButtonPlus : public HtmlTagButton { public: HtmlTagButtonPlus() = delete; HtmlTagButtonPlus(HtmlTagButtonPlus&) = delete; HtmlTagButtonPlus& operator=(HtmlTagButtonPlus&) = delete; inline HtmlTagButtonPlus(const std::string& field, const int max) : HtmlTagButton(HtmlTag("span").AddContent("+"), field + "_plus") { AddAttribute("onclick", "incrementIntegerValue('" + field + "', " + std::to_string(max) + "); return false;"); AddClass("small_button"); } }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagButtonPopup.cpp000066400000000000000000000027111500456250600231040ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include "Utils/Utils.h" #include "Server/Web/HtmlTagButtonPopup.h" #include "Server/Web/HtmlTagInput.h" namespace Server { namespace Web { HtmlTagButtonPopup::HtmlTagButtonPopup(const std::string& value, const std::string& command, const std::map& arguments, const std::string& tooltip) : HtmlTagButton(value, command, tooltip) { std::string cmd = Utils::Utils::StringBeforeDelimiter(command, "_"); std::stringstream ss; ss << "var myUrl = '/?cmd=" << cmd; for (auto& argument : arguments) { ss << "&" << argument.first << "=" << argument.second; } ss <<"';" "loadPopup(myUrl);" "return false;"; AddAttribute("onclick", ss.str()); } }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagButtonPopup.h000066400000000000000000000037541500456250600225610ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include "Languages.h" #include "Server/Web/HtmlTagButton.h" namespace Server { namespace Web { class HtmlTagButtonPopup : public HtmlTagButton { public: HtmlTagButtonPopup() = delete; HtmlTagButtonPopup(const std::string& value, const std::string& command, const std::map& arguments = std::map(), const std::string& tooltip = ""); HtmlTagButtonPopup(const Languages::TextSelector value, const std::string& command, const std::map& arguments = std::map(), const std::string& tooltip = "") : HtmlTagButtonPopup(Languages::GetText(value), command, arguments, tooltip) {} HtmlTagButtonPopup(const std::string& value, const std::string& command, const std::map& arguments, const Languages::TextSelector tooltip) : HtmlTagButtonPopup(value, command, arguments, Languages::GetText(tooltip)) {} HtmlTagButtonPopup(const std::string& value, const std::string& command, const Languages::TextSelector tooltip) : HtmlTagButtonPopup(value, command, std::map(), Languages::GetText(tooltip)) {} }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagButtonPopupWide.h000066400000000000000000000025221500456250600233620ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include "Languages.h" #include "Server/Web/HtmlTagButtonPopup.h" namespace Server { namespace Web { class HtmlTagButtonPopupWide : public HtmlTagButtonPopup { public: HtmlTagButtonPopupWide() = delete; inline HtmlTagButtonPopupWide(const Languages::TextSelector value, const std::string& command, const std::map& arguments = std::map(), const std::string& tooltip = "") : HtmlTagButtonPopup(value, command, arguments, tooltip) { AddClass("wide_button"); } }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagButtonSwapRelation.h000066400000000000000000000030521500456250600240550ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include "Server/Web/HtmlTagButton.h" namespace Server { namespace Web { class HtmlTagButtonSwapRelation : public HtmlTagButton { public: HtmlTagButtonSwapRelation() = delete; HtmlTagButtonSwapRelation(HtmlTagButtonSwapRelation&) = delete; HtmlTagButtonSwapRelation& operator=(HtmlTagButtonSwapRelation&) = delete; inline HtmlTagButtonSwapRelation(const std::string& atlock, const std::string& priority, const bool up) : HtmlTagButton(HtmlTag("span").AddContent(up ? "↑" : "↓"), "swap_relation_" + priority + "_" + std::string(up ? "up" : "down")) { AddAttribute("onclick", "swapRelations('" + atlock + "', " + priority + ", '" + std::string(up ? "up" : "down") + "'); return false;"); AddClass("small_button"); } }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagFeedback.cpp000066400000000000000000000047651500456250600223040ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include "DataModel/Feedback.h" #include "DataModel/LayoutItem.h" #include "Server/Web/HtmlTagFeedback.h" using std::string; using std::to_string; namespace Server { namespace Web { HtmlTagFeedback::HtmlTagFeedback(const DataModel::Feedback* feedback, const DataModel::LayoutItem::LayoutPosition posX, const DataModel::LayoutItem::LayoutPosition posY, const DataModel::FeedbackType feedbackType) : HtmlTagLayoutItem(dynamic_cast(feedback), posX, posY) { switch (feedbackType) { case DataModel::FeedbackTypeStraight: image = ""; break; case DataModel::FeedbackTypeTurn: image = ""; break; case DataModel::FeedbackTypeDefault: default: break; } image += ""; const DataModel::Feedback::FeedbackState state = feedback->GetState(); const string& feedbackName = feedback->GetName(); string feedbackIdString = to_string(feedback->GetID()); imageDiv.AddClass("feedback_item"); imageDiv.AddClass(state == DataModel::Feedback::FeedbackStateOccupied ? "feedback_occupied" : "feedback_free"); imageDiv.AddAttribute("onclick", "return onClickFeedback(" + feedbackIdString + ");"); AddToolTip(feedbackName + " (pin=" + to_string(feedback->GetPin()) + ")"); AddContextMenuEntry(feedbackName); AddContextMenuEntry(Languages::TextEditFeedback, "loadPopup('/?cmd=feedbackedit&feedback=" + feedbackIdString + "');"); AddContextMenuEntry(Languages::TextDeleteFeedback, "loadPopup('/?cmd=feedbackaskdelete&feedback=" + feedbackIdString + "');"); FinishInit(); } }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagFeedback.h000066400000000000000000000034601500456250600217400ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "DataTypes.h" #include "Server/Web/HtmlTagLayoutItem.h" namespace DataModel { class Feedback; } namespace Server { namespace Web { class HtmlTagFeedback : public HtmlTagLayoutItem { public: HtmlTagFeedback() = delete; HtmlTagFeedback(const HtmlTagFeedback&) = delete; HtmlTagFeedback& operator=(const HtmlTagFeedback&) = delete; inline HtmlTagFeedback(const DataModel::Feedback* feedback, const bool onControlLayer = false) : HtmlTagFeedback(feedback, onControlLayer ? ((feedback->GetPin() - 1) & 0x0F) + (((feedback->GetPin() - 1) & 0x0F) >> 3): feedback->GetPosX(), onControlLayer ? (feedback->GetPin() - 1) >> 4 : feedback->GetPosY(), onControlLayer ? DataModel::FeedbackTypeDefault : feedback->GetFeedbackType()) { } private: HtmlTagFeedback(const DataModel::Feedback* feedback, const DataModel::LayoutItem::LayoutPosition posX, const DataModel::LayoutItem::LayoutPosition posY, const DataModel::FeedbackType feedbackType); }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagInput.cpp000066400000000000000000000025301500456250600217030ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include "Utils/Utils.h" #include "Server/Web/HtmlTagInput.h" using std::string; namespace Server { namespace Web { HtmlTagInput::HtmlTagInput(const string& type, const string& name, const string& value, const Style style) : HtmlTag("input") { AddAttribute("type", type); AddAttribute("name", name); AddId(name); if (value.size() > 0) { AddAttribute("value", Utils::Utils::HtmlEncode(value)); } switch(style) { case StyleDisabled: AddAttribute("disabled"); break; case StyleReadOnly: AddAttribute("readonly"); break; default: break; } } }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagInput.h000066400000000000000000000025131500456250600213510ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "Server/Web/HtmlTag.h" namespace Server { namespace Web { class HtmlTagInput : public HtmlTag { public: HtmlTagInput() = delete; HtmlTagInput(const HtmlTagInput&) = delete; HtmlTagInput& operator=(const HtmlTagInput&) = delete; enum Style { StyleNone = 0, StyleDisabled = 1, StyleReadOnly = 2 }; HtmlTagInput(const std::string& type, const std::string& name, const std::string& value = "", const Style style = StyleNone); private: std::string PrepareValue(const std::string& value); }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagInputCheckbox.h000066400000000000000000000023131500456250600230160ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "Server/Web/HtmlTagInput.h" namespace Server { namespace Web { class HtmlTagInputCheckbox : public HtmlTagInput { public: HtmlTagInputCheckbox() = delete; inline HtmlTagInputCheckbox(const std::string& name, const std::string& value, const bool checked = false) : HtmlTagInput("checkbox", name, value) { if (checked) { AddAttribute("checked"); } AddClass("checkbox"); }; }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagInputCheckboxWithLabel.h000066400000000000000000000032661500456250600246220ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "Server/Web/HtmlTag.h" #include "Server/Web/HtmlTagLabel.h" #include "Server/Web/HtmlTagInputCheckbox.h" namespace Server { namespace Web { class HtmlTagInputCheckboxWithLabel : public HtmlTag { public: HtmlTagInputCheckboxWithLabel() = delete; HtmlTagInputCheckboxWithLabel(const std::string& name, const Languages::TextSelector label, const std::string& value, const bool checked) : HtmlTag() { AddChildTag(HtmlTagLabel(label, name)); AddChildTag(HtmlTagInputCheckbox(name, value, checked)); AddChildTag(HtmlTag("br")); } virtual ~HtmlTagInputCheckboxWithLabel() { } virtual HtmlTag AddAttribute(const std::string& name, const std::string& value) override { childTags[1].AddAttribute(name, value); return *this; } virtual HtmlTag AddClass(const std::string& _class) override { childTags[1].AddClass(_class); return *this; } }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagInputHidden.h000066400000000000000000000027341500456250600224720ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "DataModel/ObjectIdentifier.h" #include "Server/Web/HtmlTagInput.h" namespace Server { namespace Web { class HtmlTagInputHidden : public HtmlTagInput { public: HtmlTagInputHidden() = delete; HtmlTagInputHidden(const HtmlTagInputHidden&) = delete; HtmlTagInputHidden& operator=(const HtmlTagInputHidden&) = delete; inline HtmlTagInputHidden(const DataModel::ObjectIdentifier& identifier) : HtmlTagInputHidden(identifier.GetObjectTypeAsString(), identifier.GetObjectIdAsString()) { } inline HtmlTagInputHidden(const std::string& name, const std::string& value = "") : HtmlTagInput("hidden", name, value) { AddClass("hidden"); } }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagInputInteger.cpp000066400000000000000000000030541500456250600232230ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include "Server/Web/HtmlTagButtonMinus.h" #include "Server/Web/HtmlTagButtonPlus.h" #include "Server/Web/HtmlTagInput.h" #include "Server/Web/HtmlTagInputInteger.h" namespace Server { namespace Web { HtmlTagInputInteger::HtmlTagInputInteger(const std::string& name, const int value, const int min, const int max) : HtmlTag("div") { std::string minString = std::to_string(min); std::string maxString = std::to_string(max); AddId("d_" + name); AddClass("div_integer"); HtmlTagInput input("text", name, std::to_string(value)); input.AddClass("integer"); input.AddAttribute("oninput", "checkIntegerValue('" + name + "', " + minString + ", " + maxString + ");"); AddChildTag(input); AddChildTag(HtmlTagButtonMinus(name, min)); AddChildTag(HtmlTagButtonPlus(name, max)); } }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagInputInteger.h000066400000000000000000000022371500456250600226720ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "Server/Web/HtmlTag.h" namespace Server { namespace Web { class HtmlTagInputInteger : public HtmlTag { public: HtmlTagInputInteger() = delete; HtmlTagInputInteger(HtmlTagInputInteger&) = delete; HtmlTagInputInteger& operator=(HtmlTagInputInteger&) = delete; HtmlTagInputInteger(const std::string& name, const int value, const int min, const int max); }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagInputIntegerWithLabel.h000066400000000000000000000053501500456250600244650ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "Languages.h" #include "Server/Web/HtmlTag.h" #include "Server/Web/HtmlTagLabel.h" #include "Server/Web/HtmlTagInputInteger.h" namespace Server { namespace Web { class HtmlTagInputIntegerWithLabel : public HtmlTag { public: HtmlTagInputIntegerWithLabel() = delete; HtmlTagInputIntegerWithLabel(const std::string& name, const Languages::TextSelector label, const int min, const int max) : HtmlTagInputIntegerWithLabel(name, label, 0, min, max) {} HtmlTagInputIntegerWithLabel(const std::string& name, const Languages::TextSelector label, const Languages::TextSelector tooltip, const int min, const int max) : HtmlTagInputIntegerWithLabel(name, label, tooltip, 0, min, max) {} template HtmlTagInputIntegerWithLabel(const std::string& name, const Languages::TextSelector label, const int value, const int min, const int max, TextArgs... textArgs) : HtmlTag("div") { HtmlTag::AddClass("input_integer_with_label"); AddChildTag(HtmlTagLabel(label, name, textArgs...)); AddChildTag(HtmlTagInputInteger(name, value, min, max)); } template HtmlTagInputIntegerWithLabel(const std::string& name, const Languages::TextSelector label, const Languages::TextSelector tooltip, const int value, const int min, const int max, TextArgs... textArgs) : HtmlTag("div") { HtmlTag::AddClass("input_integer_with_label"); AddChildTag(HtmlTagLabel(label, tooltip, name, textArgs...)); AddChildTag(HtmlTagInputInteger(name, value, min, max)); } virtual ~HtmlTagInputIntegerWithLabel() { } inline virtual HtmlTag AddAttribute(const std::string& name, const std::string& value) override { childTags[1].AddAttribute(name, value); return *this; } inline virtual HtmlTag AddClass(const std::string& _class) override { childTags[1].AddClass(_class); return *this; } }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagInputSlider.cpp000066400000000000000000000022061500456250600230460ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include "Server/Web/HtmlTagInputSlider.h" namespace Server { namespace Web { HtmlTagInputSlider::HtmlTagInputSlider(const std::string& name, const unsigned int min, const unsigned int max, const unsigned int value) : HtmlTagInput("range", name) { AddAttribute("min", std::to_string(min)); AddAttribute("max", std::to_string(max)); AddAttribute("value", std::to_string(value)); } }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagInputSlider.h000066400000000000000000000021601500456250600225120ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "Server/Web/HtmlTagInput.h" namespace Server { namespace Web { class HtmlTagInputSlider : public HtmlTagInput { public: HtmlTagInputSlider() = delete; HtmlTagInputSlider(const std::string& name, const unsigned int min, const unsigned int max, const unsigned int value = 0); virtual ~HtmlTagInputSlider() {} }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagInputSliderLocoSpeed.cpp000066400000000000000000000026061500456250600246500ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include "Server/Web/HtmlTagInputSliderLocoSpeed.h" namespace Server { namespace Web { HtmlTagInputSliderLocoSpeed::HtmlTagInputSliderLocoSpeed(const std::string& name, const unsigned int min, const unsigned int max, const unsigned int value, const LocoID locoID) : HtmlTagInputSlider(name, min, max, value) { const std::string locoIdString = std::to_string(locoID); const std::string reference = "locospeed_" + locoIdString; AddId(reference); AddClass("slider"); AddAttribute("onchange", "return locoSpeedSliderOnChange(" + locoIdString + ");"); AddAttribute("oninput", "return locoSpeedSliderOnInput(" + locoIdString + ");"); } }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagInputSliderLocoSpeed.h000066400000000000000000000022741500456250600243160ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include #include "DataTypes.h" #include "Server/Web/HtmlTagInputSlider.h" namespace Server { namespace Web { class HtmlTagInputSliderLocoSpeed : public HtmlTagInputSlider { public: HtmlTagInputSliderLocoSpeed() = delete; HtmlTagInputSliderLocoSpeed(const std::string& name, const unsigned int min, const unsigned int max, const unsigned int value, const LocoID locoID); }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagInputText.h000066400000000000000000000024311500456250600222150ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "Server/Web/HtmlTagInput.h" namespace Server { namespace Web { class HtmlTagInputText : public HtmlTagInput { public: HtmlTagInputText() = delete; HtmlTagInputText(const HtmlTagInputText&) = delete; HtmlTagInputText& operator=(const HtmlTagInputText&) = delete; HtmlTagInputText(const std::string& name, const std::string& value = "", const HtmlTagInput::Style style = HtmlTagInput::StyleNone) : HtmlTagInput("text", name, value, style) { } }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagInputTextWithLabel.h000066400000000000000000000037771500456250600240270ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "Languages.h" #include "Server/Web/HtmlTag.h" #include "Server/Web/HtmlTagLabel.h" #include "Server/Web/HtmlTagInputText.h" namespace Server { namespace Web { class HtmlTagInputTextWithLabel : public HtmlTag { public: HtmlTagInputTextWithLabel() = delete; HtmlTagInputTextWithLabel(const HtmlTagInputTextWithLabel&) = delete; HtmlTagInputTextWithLabel& operator=(const HtmlTagInputTextWithLabel&) = delete; inline HtmlTagInputTextWithLabel(const std::string& name, const Languages::TextSelector label, const std::string& value = "", const HtmlTagInput::Style style = HtmlTagInput::StyleNone) : HtmlTag() { AddChildTag(HtmlTagLabel(label, name)); AddChildTag(HtmlTagInputText(name, value, style)); } virtual ~HtmlTagInputTextWithLabel() { } virtual inline HtmlTag AddAttribute(const std::string& name, const std::string& value) override { childTags[1].AddAttribute(name, value); return *this; } virtual inline bool IsAttributeSet(const std::string& name) override { return childTags[0].IsAttributeSet(name); } virtual inline HtmlTag AddClass(const std::string& _class) override { childTags[1].AddClass(_class); return *this; } }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagLabel.h000066400000000000000000000035141500456250600212730ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "Logger/Logger.h" #include "Server/Web/HtmlTag.h" namespace Server { namespace Web { class HtmlTagLabel : public HtmlTag { public: HtmlTagLabel() = delete; inline HtmlTagLabel(const Languages::TextSelector label, const std::string& reference) : HtmlTagLabel(Languages::GetText(label), reference) { } inline HtmlTagLabel(const Languages::TextSelector label, const Languages::TextSelector tooltip, const std::string& reference) : HtmlTagLabel(Languages::GetText(label, tooltip), reference) { } inline HtmlTagLabel(const std::string& label, const std::string& reference) : HtmlTag("label") { AddContent(label + ":"); AddAttribute("for", reference); } template inline HtmlTagLabel(const Languages::TextSelector label, const std::string& reference, Args... args) : HtmlTag("label") { std::string stringLabel = Logger::Logger::Format(Languages::GetText(label), args...); stringLabel.append(":"); AddContent(stringLabel); AddAttribute("for", reference); } }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagLayoutItem.cpp000066400000000000000000000117411500456250600227040ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include "Server/Web/HtmlTagLayoutItem.h" using std::string; using std::to_string; namespace Server { namespace Web { const string HtmlTagLayoutItem::EdgeLengthString = std::to_string(HtmlTagLayoutItem::EdgeLength); HtmlTagLayoutItem::HtmlTagLayoutItem(const DataModel::LayoutItem* layout, const DataModel::LayoutItem::LayoutPosition posX, const DataModel::LayoutItem::LayoutPosition posY) : layout(layout), imageDiv("div"), onClickMenuDiv("div"), onClickMenuContentDiv("ul"), contextMenuDiv("div"), contextMenuContentDiv("ul"), layoutPosX(posX * EdgeLength), layoutPosY(posY * EdgeLength) { switch (layout->GetObjectType()) { case ObjectTypeAccessory: identifier = "a_"; break; case ObjectTypeFeedback: identifier = "f_"; break; case ObjectTypeRoute: identifier = "r_"; break; case ObjectTypeTrack: identifier = "t_"; break; case ObjectTypeSignal: identifier = "si_"; break; case ObjectTypeSwitch: identifier = "sw_"; break; case ObjectTypeText: identifier = "tx_"; break; default: identifier = "unknown_"; return; } identifier += to_string(layout->GetID()); imageDiv.AddId(identifier); imageDiv.AddClass("layout_item"); imageDiv.AddAttribute("style", "left:" + to_string(layoutPosX) + "px;top:" + to_string(layoutPosY) + "px;"); imageDiv.AddAttribute("draggable", "true"); imageDiv.AddAttribute("ondragstart", "drag(event);"); string menuPosition = "left:" + to_string(layoutPosX + 5) + "px;top:" + to_string(layoutPosY + 30) + "px;"; onClickMenuDiv.AddAttribute("style", menuPosition); contextMenuDiv.AddAttribute("style", menuPosition); } void HtmlTagLayoutItem::FinishInit() { const DataModel::LayoutItem::LayoutItemSize trackHeight = layout->GetHeight(); const DataModel::LayoutItem::LayoutItemSize trackWidth = layout->GetWidth(); int translate = 0; if (trackHeight > DataModel::LayoutItem::Height1 || trackWidth > DataModel::LayoutItem::Width1) { DataModel::LayoutItem::LayoutRotation rotation = layout->GetRotation(); if (rotation == DataModel::LayoutItem::Rotation90 || rotation == DataModel::LayoutItem::Rotation270) { translate = (((trackHeight - trackWidth) * EdgeLength) / 2); } if (rotation == DataModel::LayoutItem::Rotation90) { translate = -translate; } } const string layoutHeight = to_string(EdgeLength * trackHeight); const string layoutWidth = to_string(EdgeLength * trackWidth); const string translateText = to_string(translate); imageDiv.AddChildTag(HtmlTag().AddContent("GetRotation()) + "deg) translate(" + translateText + "px," + translateText + "px);\">" + image + "")); if (onClickMenuContentDiv.ChildCount()) { static const string OnClick("onclick"); if (!imageDiv.IsAttributeSet(OnClick)) { imageDiv.AddAttribute(OnClick, "return showOnClickMenu(event, '" + identifier + "');"); } onClickMenuContentDiv.AddClass("contextentries"); onClickMenuDiv.AddChildTag(onClickMenuContentDiv); onClickMenuDiv.AddClass("contextmenu"); onClickMenuDiv.AddId(identifier + "_onclick"); AddChildTag(onClickMenuDiv); } if (contextMenuContentDiv.ChildCount()) { imageDiv.AddAttribute("oncontextmenu", "return showContextMenu(event, '" + identifier + "');"); contextMenuContentDiv.AddClass("contextentries"); contextMenuDiv.AddChildTag(contextMenuContentDiv); contextMenuDiv.AddClass("contextmenu"); contextMenuDiv.AddId(identifier + "_context"); AddChildTag(contextMenuDiv); } AddChildTag(imageDiv); } void HtmlTagLayoutItem::AddMenuTitle(HtmlTag& menu, const string& text) { HtmlTag li("li"); li.AddClass("contexttitle"); li.AddContent(text); menu.AddChildTag(li); } void HtmlTagLayoutItem::AddMenuEntry(HtmlTag& menu, const string& text, const string& onClick, const string& className) { HtmlTag li("li"); li.AddClass("contextentry"); li.AddContent(text); if (onClick.length() > 0) { li.AddAttribute("onClick", onClick); } if (className.length() > 0) { li.AddClass(className); } menu.AddChildTag(li); } }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagLayoutItem.h000066400000000000000000000065621500456250600223560ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "DataModel/LayoutItem.h" #include "DataModel/ObjectIdentifier.h" #include "Server/Web/HtmlTag.h" using std::string; using std::to_string; namespace Server { namespace Web { class HtmlTagLayoutItem : public HtmlTag { protected: HtmlTagLayoutItem() = delete; HtmlTagLayoutItem(const HtmlTagLayoutItem&) = delete; HtmlTagLayoutItem& operator=(const HtmlTagLayoutItem&) = delete; inline HtmlTagLayoutItem(const DataModel::LayoutItem* layout) : HtmlTagLayoutItem(layout, layout->GetPosX(), layout->GetPosY()) { } HtmlTagLayoutItem(const DataModel::LayoutItem* layout, const DataModel::LayoutItem::LayoutPosition posX, const DataModel::LayoutItem::LayoutPosition posY); virtual ~HtmlTagLayoutItem() { } void FinishInit(); inline void AddOnClickMenuEntry(const std::string& text) { AddMenuTitle(onClickMenuContentDiv, text); } inline void AddOnClickMenuEntry(const Languages::TextSelector text, const std::string& onClick, const std::string& className = "") { AddOnClickMenuEntry(Languages::GetText(text), onClick, className); } inline void AddOnClickMenuEntry(const std::string& text, const std::string& onClick, const std::string& className = "") { AddMenuEntry(onClickMenuContentDiv, text, onClick, className); } inline void AddContextMenuEntry(const std::string& text) { AddMenuTitle(contextMenuContentDiv, text); } inline void AddContextMenuEntry(const Languages::TextSelector text, const std::string& onClick, const std::string& className = "") { AddMenuEntry(contextMenuContentDiv, text, onClick, className); } void AddToolTip(const std::string& toolTip) { imageDiv.AddChildTag(HtmlTag("span").AddClass("tooltip").AddContent(toolTip)); } static const unsigned char EdgeLength = 36; static const std::string EdgeLengthString; const DataModel::LayoutItem* layout; string identifier; std::string image; HtmlTag imageDiv; HtmlTag onClickMenuDiv; HtmlTag onClickMenuContentDiv; HtmlTag contextMenuDiv; HtmlTag contextMenuContentDiv; unsigned int layoutPosX; unsigned int layoutPosY; private: static void AddMenuTitle(HtmlTag& menu, const std::string& text); static inline void AddMenuEntry(HtmlTag& menu, const Languages::TextSelector text, const std::string& onClick, const std::string& className = "") { AddMenuEntry(menu, Languages::GetText(text), onClick, className); } static void AddMenuEntry(HtmlTag& menu, const std::string& text, const std::string& onClick, const std::string& className = ""); }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagRoute.cpp000066400000000000000000000037761500456250600217170ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include "DataModel/Route.h" #include "Server/Web/HtmlTagRoute.h" using std::string; using std::to_string; namespace Server { namespace Web { HtmlTagRoute::HtmlTagRoute(const DataModel::Route* route) : HtmlTagLayoutItem(dynamic_cast(route)) { image += ""; string routeIdString = to_string(route->GetID()); imageDiv.AddClass("route_item"); imageDiv.AddAttribute("onclick", "return onClickRoute(" + routeIdString + ");"); const string& routeName = route->GetName(); AddToolTip(routeName); AddContextMenuEntry(routeName); AddContextMenuEntry(Languages::TextReleaseRoute, "fireRequestAndForget('/?cmd=routerelease&route=" + routeIdString + "');"); AddContextMenuEntry(Languages::TextEditRoute, "loadPopup('/?cmd=routeedit&route=" + routeIdString + "');"); AddContextMenuEntry(Languages::TextDeleteRoute, "loadPopup('/?cmd=routeaskdelete&route=" + routeIdString + "');"); FinishInit(); } }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagRoute.h000066400000000000000000000025651500456250600213570ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "Server/Web/HtmlTagLayoutItem.h" namespace DataModel { class Route; } namespace Server { namespace Web { class HtmlTagRoute : public HtmlTagLayoutItem { public: HtmlTagRoute() = delete; HtmlTagRoute(const DataModel::Route* route); virtual ~HtmlTagRoute() { } virtual inline HtmlTag AddAttribute(const std::string& name, const std::string& value) override { childTags[0].AddAttribute(name, value); return *this; } virtual inline bool IsAttributeSet(const std::string& name) override { return childTags[0].IsAttributeSet(name); } }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagSelect.cpp000066400000000000000000000060311500456250600220230ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include "Server/Web/HtmlTagSelect.h" namespace Server { namespace Web { HtmlTagSelect::HtmlTagSelect(const std::string& name, const std::map& options, const std::string& defaultValue) : HtmlTagSelect(name) { for (auto& option : options) { AddOption(option.first, option.second, option.first == defaultValue); } CheckDefaultKeyValue(); } HtmlTagSelect::HtmlTagSelect(const std::string& name, const std::map& options, const DataModel::ObjectIdentifier& defaultValue) : HtmlTagSelect(name) { for (auto& option : options) { AddOption(option.second, option.first, option.second == defaultValue); } CheckDefaultKeyValue(); } HtmlTagSelect::HtmlTagSelect(const std::string& name) : HtmlTag("div"), commandID("s_" + name), defaultKeyValueFound(false) { AddId(commandID + "_container"); AddClass("dropdowncontainer"); HtmlTagInputHidden hidden(name, ""); hidden.AddId(commandID); AddChildTag(hidden); HtmlTagInputText text("skip_" + commandID); text.AddId("skip_" + commandID); text.AddAttribute("onclick", "toggleClass('d_" + commandID + "', 'show'); return false;"); text.AddAttribute("readonly"); AddChildTag(text); HtmlTag dropDown("div"); dropDown.AddClass("dropdown"); dropDown.AddId("d_" + commandID); AddChildTag(dropDown); } HtmlTag HtmlTagSelect::AddAttribute(const std::string& name, const std::string& value) { childTags[0].AddAttribute(name, value); return *this; } void HtmlTagSelect::AddOption(const std::string& key, const std::string& value, const bool defaultKeyValue) { defaultKeyValueFound |= defaultKeyValue; if (defaultKeyValue || defaultKey.length() == 0) { defaultKey = key; defaultValue = value; } HtmlTag optionTag("div"); optionTag.AddAttribute("key", key); optionTag.AddClass("dropdownentry"); optionTag.AddContent(value); if (defaultKeyValue) { SetDefaultKeyValue(key, value); optionTag.AddClass("selected_option"); } optionTag.AddAttribute("onclick", "selectValue('" + key + "', '" + value + "', '" + commandID + "');"); childTags[2].AddChildTag(optionTag); } void HtmlTagSelect::CheckDefaultKeyValue() { if (defaultKeyValueFound) { return; } SetDefaultKeyValue(defaultKey, defaultValue); } }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagSelect.h000066400000000000000000000062711500456250600214760ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include "DataModel/ObjectIdentifier.h" #include "Languages.h" #include "Server/Web/HtmlTag.h" #include "Server/Web/HtmlTagInputHidden.h" #include "Server/Web/HtmlTagInputText.h" namespace Server { namespace Web { class HtmlTagSelect : public HtmlTag { public: HtmlTagSelect() = delete; HtmlTagSelect(const std::string& name, const std::map& options, const std::string& defaultValue = ""); HtmlTagSelect(const std::string& name, const std::map& options, const DataModel::ObjectIdentifier& defaultValue = DataModel::ObjectIdentifier()); template HtmlTagSelect(const std::string& name, const std::map& options, const int defaultValue = 0) // This can not be Type T, because it would be ambiguous with previous declaration : HtmlTagSelect(name) { for (auto& option : options) { AddOption(std::to_string(option.second), option.first, option.second == defaultValue); } CheckDefaultKeyValue(); } // T2 must be implicitly convertible to Languages::TextSelector template HtmlTagSelect(const std::string& name, const std::map& options, const T1 defaultValue = 0) : HtmlTagSelect(name) { for (auto& option : options) { AddOption(std::to_string(option.first), Languages::GetText(option.second), option.first == defaultValue); } CheckDefaultKeyValue(); } template HtmlTagSelect(const std::string& name, const std::map& options, const T defaultValue = 0) : HtmlTagSelect(name) { for (auto& option : options) { AddOption(std::to_string(option.first), option.second, option.first == defaultValue); } CheckDefaultKeyValue(); } virtual HtmlTag AddAttribute(const std::string& name, const std::string& value = "") override; private: HtmlTagSelect(const std::string& name); void AddOption(const std::string& key, const std::string& value, const bool defaultKeyValue); void CheckDefaultKeyValue(); inline void SetDefaultKeyValue(const std::string& key, const std::string& value) { childTags[0].AddAttribute("value", key); childTags[1].AddAttribute("value", value); } const std::string commandID; bool defaultKeyValueFound; std::string defaultKey; std::string defaultValue; }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagSelectMultiple.cpp000066400000000000000000000043751500456250600235500ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include "Server/Web/HtmlTagSelectMultiple.h" namespace Server { namespace Web { HtmlTagSelectMultiple::HtmlTagSelectMultiple(const std::string& name, const std::string& defaultValue) : HtmlTag("div"), commandID("s_" + name), key(defaultValue) { AddId(commandID + "_container"); AddClass("dropdowncontainer"); HtmlTagInputHidden hidden(name, ""); hidden.AddId(commandID); AddChildTag(hidden); HtmlTagInputText text("skip_" + commandID); text.AddId("skip_" + commandID); text.AddAttribute("onclick", "toggleClass('d_" + commandID + "', 'show'); return false;"); text.AddAttribute("readonly"); AddChildTag(text); HtmlTag dropDown("div"); dropDown.AddClass("dropdown"); dropDown.AddId("d_" + commandID); AddChildTag(dropDown); } HtmlTag HtmlTagSelectMultiple::AddAttribute(const std::string& name, const std::string& value) { childTags[0].AddAttribute(name, value); return *this; } void HtmlTagSelectMultiple::AddOption(const std::string& key, const std::string& value, const bool isSet, const std::string& none, const std::string& several) { HtmlTag optionTag("div"); optionTag.AddAttribute("key", key); optionTag.AddClass("dropdownentry"); optionTag.AddContent(value); if (isSet) { this->value = (this->value.size() == 0 ? value : several); optionTag.AddClass("selected_option"); } optionTag.AddAttribute("onclick", "selectMultipleValue('" + key + "', '" + commandID + "', '" + none + "', '" + several + "');"); childTags[2].AddChildTag(optionTag); } }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagSelectMultiple.h000066400000000000000000000045231500456250600232100ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include #include "DataModel/ObjectIdentifier.h" #include "Languages.h" #include "Server/Web/HtmlTag.h" #include "Server/Web/HtmlTagInputHidden.h" #include "Server/Web/HtmlTagInputText.h" namespace Server { namespace Web { class HtmlTagSelectMultiple : public HtmlTag { public: HtmlTagSelectMultiple() = delete; // T must be an integer type template HtmlTagSelectMultiple(const std::string& name, const std::vector> options, const T defaultValue = 0) : HtmlTagSelectMultiple(name, std::to_string(defaultValue)) { const std::string none = Languages::GetText(Languages::TextNone); const std::string several = Languages::GetText(Languages::TextSeveral); for (auto& option : options) { AddOption(std::to_string(option.first), Languages::GetText(option.second), static_cast((defaultValue & option.first) == option.first), none, several); } childTags[0].AddAttribute("value", key); if (value.size() == 0) { value = none; } childTags[1].AddAttribute("value", value); } virtual HtmlTag AddAttribute(const std::string& name, const std::string& value = "") override; private: HtmlTagSelectMultiple(const std::string& name, const std::string& defaultValue); void AddOption(const std::string& key, const std::string& value, const bool isSet, const std::string& none, const std::string& several); const std::string commandID; std::string key; std::string value; }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagSelectMultipleWithLabel.cpp000066400000000000000000000031241500456250600253330ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include "Server/Web/HtmlTagSelectMultipleWithLabel.h" namespace Server { namespace Web { HtmlTag HtmlTagSelectMultipleWithLabel::AddAttribute(const std::string& name, const std::string& value) { HtmlTagSelectMultiple& select = reinterpret_cast(childTags.at(1)); select.HtmlTagSelectMultiple::AddAttribute(name, value); return *this; } bool HtmlTagSelectMultipleWithLabel::IsAttributeSet(const std::string& name) { HtmlTagSelectMultiple& select = reinterpret_cast(childTags.at(1)); return select.HtmlTagSelectMultiple::IsAttributeSet(name); } HtmlTag HtmlTagSelectMultipleWithLabel::AddClass(const std::string& _class) { HtmlTagSelectMultiple& select = reinterpret_cast(childTags.at(1)); select.HtmlTagSelectMultiple::AddClass(_class); return *this; } }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagSelectMultipleWithLabel.h000066400000000000000000000034341500456250600250040ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include #include "Server/Web/HtmlTag.h" #include "Server/Web/HtmlTagLabel.h" #include "Server/Web/HtmlTagSelectMultiple.h" namespace Server { namespace Web { class HtmlTagSelectMultipleWithLabel : public HtmlTag { public: HtmlTagSelectMultipleWithLabel() = delete; template HtmlTagSelectMultipleWithLabel(const std::string& name, const Languages::TextSelector label, const std::vector>& options, const T defaultValue = 0) : HtmlTag("div") { HtmlTag::AddClass("input_select_with_label"); AddChildTag(HtmlTagLabel(label, "s_" + name)); AddChildTag(HtmlTagSelectMultiple(name, options, defaultValue)); } virtual ~HtmlTagSelectMultipleWithLabel() { } virtual HtmlTag AddAttribute(const std::string& name, const std::string& value = "") override; virtual bool IsAttributeSet(const std::string& name) override; virtual HtmlTag AddClass(const std::string& _class) override; }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagSelectOrientation.h000066400000000000000000000035041500456250600237060ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "Languages.h" #include "Server/Web/HtmlTag.h" #include "Server/Web/HtmlTagSelect.h" namespace Server { namespace Web { class HtmlTagSelectOrientation : public HtmlTag { public: HtmlTagSelectOrientation() = delete; HtmlTagSelectOrientation(const std::string& name, const Orientation defaultValue) { std::map orientations; orientations[OrientationLeft] = Languages::TextLeft; orientations[OrientationRight] = Languages::TextRight; AddChildTag(HtmlTagSelect(name, orientations, defaultValue)); } virtual ~HtmlTagSelectOrientation() {} virtual inline HtmlTag AddAttribute(const std::string& name, const std::string& value) override { childTags[0].AddAttribute(name, value); return *this; } virtual inline bool IsAttributeSet(const std::string& name) override { return childTags[0].IsAttributeSet(name); } virtual inline HtmlTag AddClass(const std::string& className) override { childTags[0].AddClass(className); return *this; } }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagSelectOrientationWithLabel.h000066400000000000000000000034171500456250600255050ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "Server/Web/HtmlTag.h" #include "Server/Web/HtmlTagLabel.h" #include "Server/Web/HtmlTagSelectOrientation.h" namespace Server { namespace Web { class HtmlTagSelectOrientationWithLabel : public HtmlTag { public: HtmlTagSelectOrientationWithLabel(const std::string& name, const Languages::TextSelector label, const Orientation defaultValue = OrientationRight) : HtmlTag() { AddChildTag(HtmlTagLabel(label, "s_" + name)); AddChildTag(HtmlTagSelectOrientation(name, defaultValue)); } virtual ~HtmlTagSelectOrientationWithLabel() {} virtual inline HtmlTag AddAttribute(const std::string& name, const std::string& value) override { childTags[1].AddAttribute(name, value); return *this; } virtual inline bool IsAttributeSet(const std::string& name) override { return childTags[1].IsAttributeSet(name); } virtual inline HtmlTag AddClass(const std::string& _class) override { childTags[1].AddClass(_class); return *this; } }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagSelectWithLabel.cpp000066400000000000000000000036101500456250600236170ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include "Server/Web/HtmlTagSelectWithLabel.h" namespace Server { namespace Web { HtmlTagSelectWithLabel::HtmlTagSelectWithLabel(const std::string& name, const Languages::TextSelector label, const std::map& options, const DataModel::ObjectIdentifier& defaultValue) : HtmlTag("div") { HtmlTag::AddClass("input_select_with_label"); AddChildTag(HtmlTagLabel(label, "s_" + name)); AddChildTag(HtmlTagSelect(name, options, defaultValue)); } HtmlTag HtmlTagSelectWithLabel::AddAttribute(const std::string& name, const std::string& value) { HtmlTagSelect& select = reinterpret_cast(childTags.at(1)); select.HtmlTagSelect::AddAttribute(name, value); return *this; } bool HtmlTagSelectWithLabel::IsAttributeSet(const std::string& name) { HtmlTagSelect& select = reinterpret_cast(childTags.at(1)); return select.HtmlTagSelect::IsAttributeSet(name); } HtmlTag HtmlTagSelectWithLabel::AddClass(const std::string& _class) { HtmlTagSelect& select = reinterpret_cast(childTags.at(1)); select.HtmlTagSelect::AddClass(_class); return *this; } }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagSelectWithLabel.h000066400000000000000000000076661500456250600233030ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "DataModel/ObjectIdentifier.h" #include "Server/Web/HtmlTag.h" #include "Server/Web/HtmlTagLabel.h" #include "Server/Web/HtmlTagSelect.h" namespace Server { namespace Web { class HtmlTagSelectWithLabel : public HtmlTag { public: HtmlTagSelectWithLabel() = delete; HtmlTagSelectWithLabel(const std::string& name, const Languages::TextSelector label, const std::map& options, const DataModel::ObjectIdentifier& defaultValue = DataModel::ObjectIdentifier()); template HtmlTagSelectWithLabel(const std::string& name, const Languages::TextSelector label, const std::map& options, const int defaultValue = 0) : HtmlTag("div") { HtmlTag::AddClass("input_select_with_label"); AddChildTag(HtmlTagLabel(label, "s_" + name)); AddChildTag(HtmlTagSelect(name, options, defaultValue)); } template HtmlTagSelectWithLabel(const std::string& name, const Languages::TextSelector label, const Languages::TextSelector hint, const std::map& options, const int defaultValue = 0) : HtmlTag("div") { HtmlTag::AddClass("input_select_with_label"); AddChildTag(HtmlTagLabel(label, hint, "s_" + name)); AddChildTag(HtmlTagSelect(name, options, defaultValue)); } template HtmlTagSelectWithLabel(const std::string& name, const Languages::TextSelector label, const std::map& options, const T defaultValue = 0) : HtmlTag("div") { HtmlTag::AddClass("input_select_with_label"); AddChildTag(HtmlTagLabel(label, "s_" + name)); AddChildTag(HtmlTagSelect(name, options, defaultValue)); } template HtmlTagSelectWithLabel(const std::string& name, const Languages::TextSelector label, const Languages::TextSelector hint, const std::map& options, const T defaultValue = 0) : HtmlTag("div") { HtmlTag::AddClass("input_select_with_label"); AddChildTag(HtmlTagLabel(label, hint, "s_" + name)); AddChildTag(HtmlTagSelect(name, options, defaultValue)); } template HtmlTagSelectWithLabel(const std::string& name, const Languages::TextSelector label, const std::map& options, const T defaultValue = 0) : HtmlTag("div") { HtmlTag::AddClass("input_select_with_label"); AddChildTag(HtmlTagLabel(label, "s_" + name)); AddChildTag(HtmlTagSelect(name, options, defaultValue)); } template HtmlTagSelectWithLabel(const std::string& name, const std::string& label, const std::map& options, const T defaultValue = 0) : HtmlTag("div") { HtmlTag::AddClass("input_select_with_label"); AddChildTag(HtmlTagLabel(label, "s_" + name)); AddChildTag(HtmlTagSelect(name, options, defaultValue)); } virtual ~HtmlTagSelectWithLabel() { } virtual HtmlTag AddAttribute(const std::string& name, const std::string& value = "") override; virtual bool IsAttributeSet(const std::string& name) override; virtual HtmlTag AddClass(const std::string& _class) override; }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagSignal.cpp000066400000000000000000000452201500456250600220240ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include "Server/Web/HtmlTagSignal.h" using std::string; using std::to_string; namespace Server { namespace Web { HtmlTagSignal::HtmlTagSignal(__attribute__((unused)) const Manager& manager, const DataModel::Signal* const signal) : HtmlTagLayoutItem(dynamic_cast(signal)), signal(signal) { image += ""; image += GetSignalImagePlain(signal); const string idText = to_string(signal->GetID()); imageDiv.AddClass("signal_item"); const DataModel::AccessoryState signalState = signal->GetAccessoryState(); string stateClassText = GetStateClassText(signalState); imageDiv.AddClass(stateClassText); onClickMenuDiv.AddClass(stateClassText); const DataModel::AccessoryType type = signal->GetAccessoryType(); switch (type) { case DataModel::SignalTypeSimpleLeft: case DataModel::SignalTypeSimpleRight: case DataModel::SignalTypeDeBlock: imageDiv.AddAttribute("onclick", "return onClickSignal(" + idText + ");"); break; case DataModel::SignalTypeChLMain: MenuEntry(Languages::TextSignalStateStop, idText, DataModel::SignalStateStop, "stop"); MenuEntry(Languages::TextSignalStateClear, idText, DataModel::SignalStateClear, "clear"); MenuEntry(Languages::TextSignalStateClear40, idText, DataModel::SignalStateAspect2, "aspect2"); MenuEntry(Languages::TextSignalStateClear60, idText, DataModel::SignalStateAspect3, "aspect3"); MenuEntry(Languages::TextSignalStateClear90, idText, DataModel::SignalStateAspect5, "aspect5"); MenuEntry(Languages::TextSignalStateShortClear, idText, DataModel::SignalStateAspect6, "aspect6"); imageDiv.AddAttribute("onclick", "return onClickWithMenu(event, '" + identifier + "');"); break; case DataModel::SignalTypeChLDistant: MenuEntry(Languages::TextSignalStateStopExpected, idText, DataModel::SignalStateStopExpected, "stopexpected"); MenuEntry(Languages::TextSignalStateClearExpected, idText, DataModel::SignalStateClearExpected, "clearexpected"); MenuEntry(Languages::TextSignalStateClear40Expected, idText, DataModel::SignalStateAspect2Expected, "aspect2expected"); MenuEntry(Languages::TextSignalStateClear60Expected, idText, DataModel::SignalStateAspect3Expected, "aspect3expected"); MenuEntry(Languages::TextSignalStateClear90Expected, idText, DataModel::SignalStateAspect5Expected, "aspect5expected"); break; case DataModel::SignalTypeChNMain: MenuEntry(Languages::TextSignalStateStop, idText, DataModel::SignalStateStop, "stop"); MenuEntry(Languages::TextSignalStateClear, idText, DataModel::SignalStateClear, "clear"); MenuEntry(Languages::TextSignalStateClear40, idText, DataModel::SignalStateAspect2, "aspect2"); MenuEntry(Languages::TextSignalStateClear50, idText, DataModel::SignalStateAspect3, "aspect3"); MenuEntry(Languages::TextSignalStateClear60, idText, DataModel::SignalStateAspect4, "aspect4"); MenuEntry(Languages::TextSignalStateClear70, idText, DataModel::SignalStateAspect5, "aspect5"); MenuEntry(Languages::TextSignalStateClear80, idText, DataModel::SignalStateAspect6, "aspect6"); MenuEntry(Languages::TextSignalStateClear90, idText, DataModel::SignalStateAspect7, "aspect7"); MenuEntry(Languages::TextSignalStateClear100, idText, DataModel::SignalStateAspect8, "aspect8"); MenuEntry(Languages::TextSignalStateClear110, idText, DataModel::SignalStateAspect9, "aspect9"); MenuEntry(Languages::TextSignalStateClear120, idText, DataModel::SignalStateAspect10, "aspect10"); MenuEntry(Languages::TextSignalStateStopExpected, idText, DataModel::SignalStateStopExpected, "stopexpected"); MenuEntry(Languages::TextSignalStateClear40Expected, idText, DataModel::SignalStateAspect2Expected, "aspect2expected"); MenuEntry(Languages::TextSignalStateClear50Expected, idText, DataModel::SignalStateAspect3Expected, "aspect3expected"); MenuEntry(Languages::TextSignalStateClear60Expected, idText, DataModel::SignalStateAspect4Expected, "aspect4expected"); MenuEntry(Languages::TextSignalStateClear70Expected, idText, DataModel::SignalStateAspect5Expected, "aspect5expected"); MenuEntry(Languages::TextSignalStateClear80Expected, idText, DataModel::SignalStateAspect6Expected, "aspect6expected"); MenuEntry(Languages::TextSignalStateClear90Expected, idText, DataModel::SignalStateAspect7Expected, "aspect7expected"); MenuEntry(Languages::TextSignalStateClear100Expected, idText, DataModel::SignalStateAspect8Expected, "aspect8expected"); MenuEntry(Languages::TextSignalStateClear110Expected, idText, DataModel::SignalStateAspect9Expected, "aspect9expected"); MenuEntry(Languages::TextSignalStateClear120Expected, idText, DataModel::SignalStateAspect10Expected, "aspect10expected"); imageDiv.AddAttribute("onclick", "return onClickWithMenu(event, '" + identifier + "');"); break; case DataModel::SignalTypeChDwarf: MenuEntry(Languages::TextSignalStateStop, idText, DataModel::SignalStateStop, "stop"); MenuEntry(Languages::TextSignalStateClear, idText, DataModel::SignalStateClear, "clear"); MenuEntry(Languages::TextSignalStateCaution, idText, DataModel::SignalStateStopExpected, "stopexpected"); imageDiv.AddAttribute("onclick", "return onClickWithMenu(event, '" + identifier + "');"); break; case DataModel::SignalTypeDeCombined: MenuEntry(Languages::TextSignalStateStop, idText, DataModel::SignalStateStop, "stop"); MenuEntry(Languages::TextSignalStateClear, idText, DataModel::SignalStateClear, "clear"); MenuEntry(Languages::TextSignalStateStopExpected, idText, DataModel::SignalStateStopExpected, "stopexpected"); MenuEntry(Languages::TextSignalStateShunting, idText, DataModel::SignalStateAspect4, "aspect4"); MenuEntry(Languages::TextSignalStateZs7, idText, DataModel::SignalStateAspect7, "aspect7"); MenuEntry(Languages::TextSignalStateDark, idText, DataModel::SignalStateDark, "dark"); imageDiv.AddAttribute("onclick", "return onClickWithMenu(event, '" + identifier + "');"); break; case DataModel::SignalTypeDeHVMain: MenuEntry(Languages::TextSignalStateStop, idText, DataModel::SignalStateStop, "stop"); MenuEntry(Languages::TextSignalStateClear, idText, DataModel::SignalStateClear, "clear"); MenuEntry(Languages::TextSignalStateSlow, idText, DataModel::SignalStateAspect2, "aspect2"); MenuEntry(Languages::TextSignalStateShunting, idText, DataModel::SignalStateAspect3, "aspect3"); imageDiv.AddAttribute("onclick", "return onClickWithMenu(event, '" + identifier + "');"); break; default: break; } AddToolTip(signal->GetName() + " (addr=" + to_string(signal->GetAddress()) + ")"); AddContextMenuEntry(Languages::TextEditSignal, "loadPopup('/?cmd=signaledit&signal=" + idText + "');"); AddContextMenuEntry(Languages::TextDeleteSignal, "loadPopup('/?cmd=signalaskdelete&signal=" + idText + "');"); FinishInit(); } void HtmlTagSignal::MenuEntry(const Languages::TextSelector text, const string& id, const DataModel::AccessoryState state, const string& aspect) { const DataModel::AddressOffset offset = signal->GetStateAddressOffset(state); if (offset < 0) { return; } AddOnClickMenuEntry(text, "fireRequestAndForget('/?cmd=signalstate&signal=" + id + "&state=" + aspect + "');", "menu_" + aspect); } string HtmlTagSignal::GetSignalImage(const DataModel::AccessoryState state, const DataModel::Signal* signal) { string out = "
"; out += "" + GetSignalImagePlain(signal) + ""; out += "
"; return out; } string HtmlTagSignal::GetSignalImagePlain(const DataModel::Signal* const signal) { switch (signal->GetAccessoryType()) { case DataModel::SignalTypeSimpleLeft: return "" "" "" "" "" ""; case DataModel::SignalTypeSimpleRight: return "" "" "" "" "" ""; case DataModel::SignalTypeChDwarf: return "" "" "" "" "" "" ""; case DataModel::SignalTypeChLMain: return "" "" "" "" "" "" "" "" "" ""; case DataModel::SignalTypeChLDistant: return "" "" "" "" "" "" "" "" ""; case DataModel::SignalTypeChNMain: return "" "" "" "" "" "" "" "" "" "4" "5" "6" "7" "8" "9" "10" "11" "12"; case DataModel::SignalTypeDeCombined: return "" "" "" "" "" "" "" "" "" "" "" ""; case DataModel::SignalTypeDeHVMain: return "" "" "" "" "" "" "" "" "" ""; case DataModel::SignalTypeDeHVDistant: return ""; case DataModel::SignalTypeDeBlock: return "" "" "" "" "" "" "" ""; default: return ""; } } string HtmlTagSignal::GetStateClassText(const DataModel::AccessoryState state) { switch (state) { case DataModel::SignalStateStop: return "signal_stop"; case DataModel::SignalStateClear: return "signal_clear"; case DataModel::SignalStateAspect2: return "signal_aspect2"; case DataModel::SignalStateAspect3: return "signal_aspect3"; case DataModel::SignalStateAspect4: return "signal_aspect4"; case DataModel::SignalStateAspect5: return "signal_aspect5"; case DataModel::SignalStateAspect6: return "signal_aspect6"; case DataModel::SignalStateAspect7: return "signal_aspect7"; case DataModel::SignalStateAspect8: return "signal_aspect8"; case DataModel::SignalStateAspect9: return "signal_aspect9"; case DataModel::SignalStateAspect10: return "signal_aspect10"; case DataModel::SignalStateStopExpected: return "signal_stopexpected"; case DataModel::SignalStateAspect2Expected: return "signal_aspect2expected"; case DataModel::SignalStateAspect3Expected: return "signal_aspect3expected"; case DataModel::SignalStateAspect4Expected: return "signal_aspect4expected"; case DataModel::SignalStateAspect5Expected: return "signal_aspect5expected"; case DataModel::SignalStateAspect6Expected: return "signal_aspect6expected"; case DataModel::SignalStateAspect7Expected: return "signal_aspect7expected"; case DataModel::SignalStateAspect8Expected: return "signal_aspect8expected"; case DataModel::SignalStateAspect9Expected: return "signal_aspect9expected"; case DataModel::SignalStateAspect10Expected: return "signal_aspect10expected"; case DataModel::SignalStateDark: default: return "signal_dark"; } } }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagSignal.h000066400000000000000000000033751500456250600214760ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "DataModel/Signal.h" #include "Manager.h" #include "Server/Web/HtmlTagLayoutItem.h" class Manager; namespace DataModel { class Signal; } namespace Server { namespace Web { class HtmlTagSignal : public HtmlTagLayoutItem { public: HtmlTagSignal() = delete; HtmlTagSignal(const HtmlTagSignal&) = delete; HtmlTagSignal& operator=(const HtmlTagSignal&) = delete; HtmlTagSignal(const Manager& manager, const DataModel::Signal* const signal); virtual ~HtmlTagSignal() { } static std::string GetSignalImage(const DataModel::AccessoryState state, const DataModel::Signal* signal); private: void MenuEntry(const Languages::TextSelector text, const string& id, const DataModel::AccessoryState state, const string& aspect); static std::string GetSignalImagePlain(const DataModel::Signal* const signal); static std::string GetStateClassText(const DataModel::AccessoryState state); const DataModel::Signal* const signal; }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagSpace.h000066400000000000000000000017541500456250600213130ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "Server/Web/HtmlTag.h" namespace Server { namespace Web { class HtmlTagSpace : public HtmlTag { public: inline HtmlTagSpace() : HtmlTag("div") { AddContent(" "); } }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagSwitch.cpp000066400000000000000000000126551500456250600220560ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include "DataModel/Switch.h" #include "Server/Web/HtmlTagSwitch.h" using std::string; using std::to_string; namespace Server { namespace Web { HtmlTagSwitch::HtmlTagSwitch(const DataModel::Switch* mySwitch) : HtmlTagLayoutItem(dynamic_cast(mySwitch)) { const string& switchName = mySwitch->GetName(); string idText = to_string(mySwitch->GetID()); imageDiv.AddClass("switch_item"); const DataModel::AccessoryType type = mySwitch->GetAccessoryType(); switch (type) { case DataModel::SwitchTypeLeft: image = "" "" ""; imageDiv.AddAttribute("onclick", "return onClickSwitch(event, " + idText + ");"); break; case DataModel::SwitchTypeRight: image = "" "" ""; imageDiv.AddAttribute("onclick", "return onClickSwitch(event, " + idText + ");"); break; case DataModel::SwitchTypeThreeWay: image = "" "" "" "" "" ""; AddOnClickMenuEntry(switchName); AddOnClickMenuEntry(Languages::TextSwitchStateLeft, "fireRequestAndForget('/?cmd=switchstate&switch=" + idText + "&state=turnout');", "menu_turnout"); AddOnClickMenuEntry(Languages::TextSwitchStateStraight, "fireRequestAndForget('/?cmd=switchstate&switch=" + idText + "&state=straight');", "menu_straight"); AddOnClickMenuEntry(Languages::TextSwitchStateRight, "fireRequestAndForget('/?cmd=switchstate&switch=" + idText + "&state=third');", "menu_third"); break; case DataModel::SwitchTypeMaerklinLeft: image = "" "" "" "" "" "" ""; imageDiv.AddAttribute("onclick", "return onClickSwitch(event, " + idText + ");"); break; case DataModel::SwitchTypeMaerklinRight: image = "" "" "" "" "" "" ""; imageDiv.AddAttribute("onclick", "return onClickSwitch(event, " + idText + ");"); break; default: break; } string stateClass; const DataModel::AccessoryState state = mySwitch->GetAccessoryState(); switch (state) { case DataModel::SwitchStateTurnout: stateClass = "switch_turnout"; break; case DataModel::SwitchStateStraight: stateClass = "switch_straight"; break; case DataModel::SwitchStateThird: stateClass = "switch_third"; break; default: break; } imageDiv.AddClass(stateClass); onClickMenuDiv.AddClass(stateClass); AddToolTip(switchName + " (addr=" + to_string(mySwitch->GetAddress()) + ")"); AddContextMenuEntry(switchName); AddContextMenuEntry(Languages::TextReleaseSwitch, "fireRequestAndForget('/?cmd=switchrelease&switch=" + idText + "');"); AddContextMenuEntry(Languages::TextEditSwitch, "loadPopup('/?cmd=switchedit&switch=" + idText + "');"); AddContextMenuEntry(Languages::TextDeleteSwitch, "loadPopup('/?cmd=switchaskdelete&switch=" + idText + "');"); FinishInit(); } }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagSwitch.h000066400000000000000000000027541500456250600215220ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "Server/Web/HtmlTagLayoutItem.h" namespace DataModel { class Switch; } namespace Server { namespace Web { class HtmlTagSwitch : public HtmlTagLayoutItem { public: HtmlTagSwitch() = delete; HtmlTagSwitch(const HtmlTagSwitch&) = delete; HtmlTagSwitch& operator=(const HtmlTagSwitch&) = delete; HtmlTagSwitch(const DataModel::Switch* mySwitch); virtual ~HtmlTagSwitch() { } virtual inline HtmlTag AddAttribute(const std::string& name, const std::string& value) override { childTags[0].AddAttribute(name, value); return *this; } virtual inline bool IsAttributeSet(const std::string& name) override { return childTags[0].IsAttributeSet(name); } }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagText.cpp000066400000000000000000000031151500456250600215300ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include "DataModel/Text.h" #include "Server/Web/HtmlTagText.h" using std::string; using std::to_string; namespace Server { namespace Web { HtmlTagText::HtmlTagText(const DataModel::Text* text) : HtmlTagLayoutItem(dynamic_cast(text)) { const string& name = text->GetName(); image += "" + name + ""; imageDiv.AddClass("text_item"); imageDiv.AddAttribute("onclick", "return onClickWithoutMenu(event, '" + identifier + "');"); const string urlIdentifier = "text=" + to_string(text->GetID()); AddContextMenuEntry(name); AddContextMenuEntry(Languages::TextEditText, "loadPopup('/?cmd=textedit&" + urlIdentifier + "');"); AddContextMenuEntry(Languages::TextDeleteText, "loadPopup('/?cmd=textaskdelete&" + urlIdentifier + "');"); FinishInit(); } }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagText.h000066400000000000000000000022471500456250600212020ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "Server/Web/HtmlTagLayoutItem.h" namespace DataModel { class Text; } namespace Server { namespace Web { class HtmlTagText : public HtmlTagLayoutItem { public: HtmlTagText() = delete; HtmlTagText(const HtmlTagText&) = delete; HtmlTagText& operator=(const HtmlTagText&) = delete; HtmlTagText(const DataModel::Text* text); virtual ~HtmlTagText() { } }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagTrack.cpp000066400000000000000000000211101500456250600216430ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include "DataModel/ObjectIdentifier.h" #include "DataModel/Route.h" #include "DataModel/Track.h" #include "Manager.h" #include "Server/Web/HtmlTagTrack.h" using DataModel::ObjectIdentifier; using std::string; using std::to_string; namespace Server { namespace Web { HtmlTagTrack::HtmlTagTrack(const Manager& manager, const DataModel::Track* track) : HtmlTagLayoutItem(dynamic_cast(track)) { const bool occupied = track->GetMainStateDelayed() == DataModel::Feedback::FeedbackStateOccupied; const ObjectIdentifier locoBaseIdentifier = track->GetMainLocoBaseDelayed(); const bool reserved = locoBaseIdentifier.IsSet(); const bool blocked = track->GetMainBlocked(); onClickMenuDiv.AddClass(reserved ? "loco_known" : "loco_unknown"); onClickMenuDiv.AddClass(blocked ? "track_blocked" : "track_unblocked"); onClickMenuDiv.AddClass(track->GetLocoBaseOrientation() == OrientationRight ? "orientation_right" : "orientation_left"); const string& trackName = track->GetMainName(); AddOnClickMenuEntry(trackName); AddContextMenuEntry(trackName); const string urlIdentifier = "track=" + to_string(track->GetID()); const string urlMainIdentifier = "track=" + to_string(track->GetMainID()); imageDiv.AddClass("track_item"); string trackClass; if (reserved && occupied) { trackClass = "track_reserved_occupied"; } else if (reserved) { trackClass = "track_reserved"; } else if (occupied) { trackClass = "track_occupied"; } else if (blocked) { trackClass = "track_blocked"; } else { trackClass = "track_free"; } imageDiv.AddClass(trackClass); const DataModel::LayoutItem::LayoutItemSize trackHeight = layout->GetHeight(); const string layoutHeight = to_string(EdgeLength * trackHeight); switch (track->GetTrackType()) { case DataModel::TrackTypeTurn: image = ""; break; case DataModel::TrackTypeEnd: image = "" ""; break; case DataModel::TrackTypeBridge: { const string l1 = to_string(EdgeLength * trackHeight - 5); const string l2 = to_string(EdgeLength * trackHeight - 3); image = "" "" ""; break; } case DataModel::TrackTypeTunnel: { image = "" ""; const int lengthPixel = EdgeLength * trackHeight; TunnelEnd(layoutHeight, lengthPixel); break; } case DataModel::TrackTypeTunnelEnd: { const int lengthPixel = EdgeLength * trackHeight - 20; TunnelEnd(layoutHeight, lengthPixel); break; } case DataModel::TrackTypeLink: image = "" ""; break; case DataModel::TrackTypeCrossingLeft: image = ""; break; case DataModel::TrackTypeCrossingRight: image = ""; break; case DataModel::TrackTypeCrossingSymetric: image = ""; break; case DataModel::TrackTypeStraight: default: image = ""; if (track->GetShowName()) { const string& orientationSign = track->GetMainLocoOrientation() == OrientationRight ? "→ " : "← "; const string& locoName = reserved ? orientationSign + manager.GetLocoBaseName(locoBaseIdentifier) : ""; const string textPositionX = to_string(EdgeLength * trackHeight - 1); image += "" + locoName + ""; const string& displayName = track->GetMainDisplayName(); image += "" + (displayName.size() ? displayName : trackName) + ""; } break; } imageDiv.AddAttribute("onclick", "return onClickWithMenu(event, '" + identifier + "');"); AddOnClickMenuEntry(Languages::TextBlockTrack, "fireRequestAndForget('/?cmd=trackblock&" + urlMainIdentifier + "&blocked=true');", "track_block"); AddOnClickMenuEntry(Languages::TextUnblockTrack, "fireRequestAndForget('/?cmd=trackblock&" + urlMainIdentifier + "&blocked=false');", "track_unblock"); AddOnClickMenuEntry(Languages::TextTurnDirectionOfTravelToLeft, "fireRequestAndForget('/?cmd=trackorientation&orientation=false&" + urlMainIdentifier + "');", "track_left"); AddOnClickMenuEntry(Languages::TextTurnDirectionOfTravelToRight, "fireRequestAndForget('/?cmd=trackorientation&orientation=true&" + urlMainIdentifier + "');", "track_right"); AddOnClickMenuEntry(Languages::TextSetLoco, "loadPopup('/?cmd=tracksetloco&" + urlMainIdentifier + "');", "track_set"); AddOnClickMenuEntry(Languages::TextStartLocoAutomode, "fireRequestAndForget('/?cmd=trackstartloco&" + urlMainIdentifier + "');", "track_start_loco"); const std::string trackId = std::to_string(track->GetID()); const std::vector routes = track->GetRoutes(); for (auto route : routes) { const std::string cmd = "fireRequestAndForget('/?cmd=locoaddtimetable&track=" + trackId + "&route=" + std::to_string(route->GetID()) + "&followup="; AddOnClickMenuEntry(route->GetName() + " & " + Languages::GetText(Languages::TextStop), cmd + "manual');", "track_start_loco"); AddOnClickMenuEntry(route->GetName() + " & " + Languages::GetText(Languages::TextAutomode), cmd + "automode');", "track_start_loco"); } AddOnClickMenuEntry(Languages::TextStopLoco, "fireRequestAndForget('/?cmd=trackstoploco&" + urlMainIdentifier + "');", "track_stop_loco"); AddContextMenuEntry(Languages::TextReleaseTrack, "fireRequestAndForget('/?cmd=trackrelease&" + urlMainIdentifier + "');", "track_release"); AddContextMenuEntry(Languages::TextReleaseTrackAndLoco, "fireRequestAndForget('/?cmd=locorelease&" + urlMainIdentifier + "');", "track_loco_release"); AddContextMenuEntry(Languages::TextEditTrack, "loadPopup('/?cmd=trackedit&" + urlIdentifier + "');"); AddContextMenuEntry(Languages::TextDeleteTrack, "loadPopup('/?cmd=trackaskdelete&" + urlIdentifier + "');"); AddToolTip(trackName); FinishInit(); } void HtmlTagTrack::TunnelEnd(const string& layoutHeight, const unsigned int lengthPixel) { const string l14 = to_string(lengthPixel - 14); const string l12 = to_string(lengthPixel - 12); const string l11 = to_string(lengthPixel - 11); const string l10 = to_string(lengthPixel - 10); const string l3 = to_string(lengthPixel - 3); const string l1 = to_string(lengthPixel - 1); image += "" ""; } }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/HtmlTagTrack.h000066400000000000000000000031711500456250600213170ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "DataModel/Track.h" #include "DataModel/LayoutItem.h" #include "Server/Web/HtmlTagLayoutItem.h" class Manager; namespace Server { namespace Web { class HtmlTagTrack : public HtmlTagLayoutItem { public: HtmlTagTrack() = delete; HtmlTagTrack(const HtmlTagTrack&) = delete; HtmlTagTrack& operator=(const HtmlTagTrack&) = delete; HtmlTagTrack(const Manager& manager, const DataModel::Track* track); virtual ~HtmlTagTrack() { } virtual inline HtmlTag AddAttribute(const std::string& name, const std::string& value) override { childTags[0].AddAttribute(name, value); return *this; } virtual inline bool IsAttributeSet(const std::string& name) override { return childTags[0].IsAttributeSet(name); } private: void TunnelEnd(const string& layoutHeight, const unsigned int lengthPixel); }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/Response.cpp000066400000000000000000000031751500456250600211270ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include #include "Server/Web/Response.h" namespace Server { namespace Web { const Response::responseCodeMap Response::responseTexts = { { Response::OK, "OK" }, { Response::NotFound, "Not found"}, { Response::NotImplemented, "Not Implemented"} }; void Response::AddHeader(const std::string& key, const std::string& value) { headers[key] = value; } Response::operator std::string() { std::stringstream reply; reply << *this; return reply.str(); } std::ostream& operator<<(std::ostream& stream, const Response& response) { stream << "HTTP/1.1 " << std::to_string(response.responseCode) << " " << Response::responseTexts.at(response.responseCode) << "\r\n"; for (auto& header : response.headers) { stream << header.first << ": " << header.second << "\r\n"; } stream << "\r\n"; stream << response.content; return stream; } }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/Response.h000066400000000000000000000032201500456250600205630ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include #include "Server/Web/HtmlTag.h" namespace Server { namespace Web { class Response { public: enum ResponseCode : unsigned short { OK = 200, NotFound = 404, NotImplemented = 501 }; inline Response() : responseCode(OK) { } inline Response(const ResponseCode responseCode, const HtmlTag& content) : responseCode(responseCode), content(content) { } virtual ~Response() { } void AddHeader(const std::string& key, const std::string& value); operator std::string(); friend std::ostream& operator<<(std::ostream& stream, const Response& response); ResponseCode responseCode; typedef std::map responseCodeMap; static const responseCodeMap responseTexts; std::map headers; HtmlTag content; }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/ResponseCsv.cpp000066400000000000000000000034101500456250600215730ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include #include "Server/Web/ResponseCsv.h" namespace Server { namespace Web { ResponseCsv::ResponseCsv(const std::string& content) : Response(), csvContent(content) { AddHeader("Cache-Control", "no-cache, must-revalidate"); AddHeader("Pragma", "no-cache"); AddHeader("Expires", "Sun, 12 Feb 2016 00:00:00 GMT"); AddHeader("Content-Type", "text/csv; charset=utf-8"); AddHeader("Connection", "keep-alive"); } ResponseCsv::operator std::string() { std::stringstream reply; reply << *this; return reply.str(); } std::ostream& operator<<(std::ostream& stream, const ResponseCsv& response) { stream << "HTTP/1.1 " << std::to_string(response.responseCode) << " " << Response::responseTexts.at(response.responseCode) << "\r\n"; for (auto& header : response.headers) { stream << header.first << ": " << header.second << "\r\n"; } stream << "Content-Length: " << response.csvContent.size(); stream << "\r\n\r\n"; stream << response.csvContent; return stream; } }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/ResponseCsv.h000066400000000000000000000024341500456250600212450ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include "Server/Web/Response.h" namespace Server { namespace Web { class ResponseCsv : public Response { public: ResponseCsv() = delete; ResponseCsv(const ResponseCsv&) = delete; ResponseCsv& operator=(const ResponseCsv&) = delete; ResponseCsv(const std::string& content); virtual ~ResponseCsv() { } operator std::string(); friend std::ostream& operator<<(std::ostream& stream, const ResponseCsv& response); private: std::string csvContent; }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/ResponseHtml.cpp000066400000000000000000000045571500456250600217610ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include #include "Server/Web/ResponseHtml.h" namespace Server { namespace Web { ResponseHtml::ResponseHtml(const ResponseCode responseCode, const std::string& title, const HtmlTag body) : Response(responseCode, body), title(title) { AddHeader("Cache-Control", "no-cache, must-revalidate"); AddHeader("Pragma", "no-cache"); AddHeader("Expires", "Sun, 12 Feb 2016 00:00:00 GMT"); AddHeader("Content-Type", "text/html; charset=utf-8"); AddHeader("Connection", "keep-alive"); } void ResponseHtml::AddAttribute(const std::string name, const std::string value) { this->content.AddAttribute(name, value); } void ResponseHtml::AddChildTag(HtmlTag content) { this->content.AddChildTag(content); } ResponseHtml::operator std::string() { std::stringstream reply; reply << *this; return reply.str(); } std::ostream& operator<<(std::ostream& stream, const ResponseHtml& response) { stream << "HTTP/1.1 " << std::to_string(response.responseCode) << " " << ResponseHtml::responseTexts.at(response.responseCode) << "\r\n"; for (auto& header : response.headers) { stream << header.first << ": " << header.second << "\r\n"; } std::stringstream body; body << ""; HtmlTag html("html"); if (response.title.length() > 0) { HtmlTag head("head"); head.AddChildTag(HtmlTag("title").AddContent(response.title)); html.AddChildTag(head); } html.AddChildTag(response.content); body << html; std::string bodyString(body.str()); stream << "Content-Length: " << bodyString.size(); stream << "\r\n\r\n"; stream << bodyString; return stream; } }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/ResponseHtml.h000066400000000000000000000035301500456250600214140ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include "Server/Web/Response.h" namespace Server { namespace Web { class ResponseHtml : public Response { public: ResponseHtml() = delete; ResponseHtml(const ResponseHtml&) = delete; ResponseHtml& operator=(const ResponseHtml&) = delete; inline ResponseHtml(const ResponseCode responseCode) : ResponseHtml(responseCode, std::to_string(responseCode) + " " + ResponseHtml::responseTexts.at(responseCode), HtmlTag("body")) { } inline ResponseHtml(const HtmlTag body) : ResponseHtml("", body) { } inline ResponseHtml(const std::string& title, const HtmlTag body) : ResponseHtml(Response::OK, title, body) { } ResponseHtml(const ResponseCode responseCode, const std::string& title, const HtmlTag body); virtual ~ResponseHtml() { } void AddAttribute(const std::string name, const std::string value); void AddChildTag(HtmlTag content); operator std::string(); friend std::ostream& operator<<(std::ostream& stream, const ResponseHtml& response); protected: std::string title; }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/ResponseHtmlFull.cpp000066400000000000000000000045401500456250600225740ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include #include "Server/Web/ResponseHtmlFull.h" namespace Server { namespace Web { ResponseHtmlFull::operator std::string() { std::stringstream reply; reply << *this; return reply.str(); } std::ostream& operator<<(std::ostream& stream, const ResponseHtmlFull& response) { stream << "HTTP/1.1 " << std::to_string(response.responseCode) << " " << ResponseHtml::responseTexts.at(response.responseCode) << "\r\n"; for (auto& header : response.headers) { stream << header.first << ": " << header.second << "\r\n"; } std::stringstream body; body << ""; HtmlTag head("head"); head.AddChildTag(HtmlTag("title").AddId("title").AddContent(response.title)); head.AddChildTag(HtmlTag("link").AddAttribute("rel", "stylesheet").AddAttribute("type", "text/css").AddAttribute("href", "/style.css")); head.AddChildTag(HtmlTag("script").AddAttribute("type", "application/javascript").AddAttribute("src", "/nosleep.js")); head.AddChildTag(HtmlTag("script").AddAttribute("type", "application/javascript").AddAttribute("src", "/javascript.js")); head.AddChildTag(HtmlTag("meta").AddAttribute("name", "viewport").AddAttribute("content", "width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0")); head.AddChildTag(HtmlTag("meta").AddAttribute("name", "robots").AddAttribute("content", "noindex,nofollow")); body << HtmlTag("html").AddChildTag(head).AddChildTag(response.content); std::string bodyString(body.str()); stream << "Content-Length: " << bodyString.size(); stream << "\r\n\r\n"; stream << bodyString; return stream; } }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/ResponseHtmlFull.h000066400000000000000000000031551500456250600222420ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include "Server/Web/ResponseHtml.h" namespace Server { namespace Web { class ResponseHtmlFull : public ResponseHtml { public: ResponseHtmlFull() = delete; ResponseHtmlFull(const ResponseHtmlFull&) = delete; ResponseHtmlFull& operator=(const ResponseHtmlFull&) = delete; inline ResponseHtmlFull(const ResponseCode responseCode) : ResponseHtml(responseCode) { } inline ResponseHtmlFull(const std::string& title, const HtmlTag body) : ResponseHtml(title, body) { } ResponseHtmlFull(const ResponseCode responseCode, const std::string& title, const HtmlTag body) : ResponseHtml(responseCode, title, body) { } virtual ~ResponseHtmlFull() { } operator std::string(); friend std::ostream& operator<<(std::ostream& stream, const ResponseHtmlFull& response); }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/ResponseHtmlNotFound.cpp000066400000000000000000000021711500456250600234240ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include "Server/Web/ResponseHtmlNotFound.h" namespace Server { namespace Web { using std::string; ResponseHtmlNotFound::ResponseHtmlNotFound(const string& file) : ResponseHtml(ResponseHtml::NotFound) { content.AddChildTag(HtmlTag("h1").AddContent("File not found")); content.AddChildTag(HtmlTag("p").AddContent("File ").AddContent(file).AddContent(" not found")); } }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/ResponseHtmlNotFound.h000066400000000000000000000023161500456250600230720ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include "Server/Web/ResponseHtml.h" namespace Server { namespace Web { class ResponseHtmlNotFound : public ResponseHtml { public: ResponseHtmlNotFound() = delete; ResponseHtmlNotFound(const ResponseHtmlNotFound&) = delete; ResponseHtmlNotFound& operator=(const ResponseHtmlNotFound&) = delete; ResponseHtmlNotFound(const std::string& file); virtual ~ResponseHtmlNotFound() { } }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/ResponseHtmlNotImplemented.cpp000066400000000000000000000022451500456250600246160ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include "Server/Web/ResponseHtmlNotImplemented.h" namespace Server { namespace Web { using std::string; ResponseHtmlNotImplemented::ResponseHtmlNotImplemented(const string& method) : ResponseHtml(ResponseHtml::NotImplemented) { content.AddChildTag(HtmlTag("h1").AddContent("Method not implemented")); content.AddChildTag(HtmlTag("p").AddContent("Method ").AddContent(method).AddContent(" not implemented")); } }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/ResponseHtmlNotImplemented.h000066400000000000000000000024001500456250600242540ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include "Server/Web/ResponseHtml.h" namespace Server { namespace Web { class ResponseHtmlNotImplemented : public ResponseHtml { public: ResponseHtmlNotImplemented() = delete; ResponseHtmlNotImplemented(const ResponseHtmlNotImplemented&) = delete; ResponseHtmlNotImplemented& operator=(const ResponseHtmlNotImplemented&) = delete; ResponseHtmlNotImplemented(const std::string& method); virtual ~ResponseHtmlNotImplemented() { } }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/WebClient.cpp000066400000000000000000004534601500456250600212130ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include //memset #include #include #include #include #include #include #include #include "DataModel/DataModel.h" #include "DataModel/AccessoryConfig.h" #include "DataModel/LocoConfig.h" #include "DataModel/ObjectIdentifier.h" #include "Hardware/HardwareHandler.h" #include "RailControl.h" #include "Utils/Utils.h" #include "Version.h" #include "Server/Web/HtmlTagAccessory.h" #include "Server/Web/HtmlTagButtonCancel.h" #include "Server/Web/HtmlTagButtonCommand.h" #include "Server/Web/HtmlTagButtonCommandFullScreen.h" #include "Server/Web/HtmlTagButtonCommandPressRelease.h" #include "Server/Web/HtmlTagButtonCommandToggle.h" #include "Server/Web/HtmlTagButtonCommandWide.h" #include "Server/Web/HtmlTagButtonOK.h" #include "Server/Web/HtmlTagButtonPopup.h" #include "Server/Web/HtmlTagButtonPopupWide.h" #include "Server/Web/HtmlTagFeedback.h" #include "Server/Web/HtmlTagInputCheckbox.h" #include "Server/Web/HtmlTagInputCheckboxWithLabel.h" #include "Server/Web/HtmlTagInputHidden.h" #include "Server/Web/HtmlTagInputIntegerWithLabel.h" #include "Server/Web/HtmlTagInputSliderLocoSpeed.h" #include "Server/Web/HtmlTagInputTextWithLabel.h" #include "Server/Web/HtmlTagRoute.h" #include "Server/Web/HtmlTagSelectOrientation.h" #include "Server/Web/HtmlTagSelectWithLabel.h" #include "Server/Web/HtmlTagSignal.h" #include "Server/Web/HtmlTagSwitch.h" #include "Server/Web/HtmlTagText.h" #include "Server/Web/HtmlTagTrack.h" #include "Server/Web/ResponseCsv.h" #include "Server/Web/ResponseHtml.h" #include "Server/Web/ResponseHtmlFull.h" #include "Server/Web/ResponseHtmlNotFound.h" #include "Server/Web/ResponseHtmlNotImplemented.h" #include "Server/Web/WebClient.h" #include "Server/Web/WebClientStatic.h" #include "Server/Web/WebServer.h" #include "Utils/Integer.h" using namespace DataModel; using LayoutPosition = DataModel::LayoutItem::LayoutPosition; using LayoutItemSize = DataModel::LayoutItem::LayoutItemSize; using LayoutRotation = DataModel::LayoutItem::LayoutRotation; using Visible = DataModel::LayoutItem::Visible; using std::deque; using std::map; using std::string; using std::thread; using std::to_string; using std::vector; namespace Server { namespace Web { WebClient::~WebClient() { run = false; clientThread.join(); delete connection; } // worker is the thread that handles client requests void WebClient::Worker() { Utils::Utils::SetThreadName("WebClient"); logger->Debug(Languages::TextTcpConnectionEstablished, connection->AddressAsString()); WorkerImpl(); logger->Debug(Languages::TextTcpConnectionClosed, connection->AddressAsString()); terminated = true; } void WebClient::WorkerImpl() { run = true; bool keepalive = true; while (run && keepalive) { const int BufferSize = 8192; char buffer[BufferSize]; memset(buffer, 0, BufferSize); int pos = 0; string s; while (pos < BufferSize - 1 && s.find("\n\n") == string::npos && run) { int ret = connection->Receive(buffer + pos, BufferSize - 1 - pos, 0); if (ret == -1) { if (errno != ETIMEDOUT) { logger->Debug(Languages::TextErrorReadingData, strerror(errno)); return; } if (run == false) { return; } continue; } pos += ret; s = string(buffer); Utils::Utils::ReplaceString(s, string("\r\n"), string("\n")); Utils::Utils::ReplaceString(s, string("\r"), string("\n")); } deque lines; Utils::Utils::SplitString(s, string("\n"), lines); if (lines.size() <= 1) { return; } string method; string uri; string protocol; map arguments; map headers; InterpretClientRequest(lines, method, uri, protocol, arguments, headers); keepalive = (Utils::Utils::GetStringMapEntry(headers, "Connection", "close").compare("keep-alive") == 0); logger->Info(Languages::TextHttpRequest, method, uri); // if method is not implemented if ((method.compare("GET") != 0) && (method.compare("HEAD") != 0)) { logger->Info(Languages::TextMethodNotImplemented, id, method); ResponseHtmlNotImplemented response(method); connection->Send(response); return; } // handle requests if (uri.compare("/") == 0) { PrintMainHTML(); if (server.UpdateAvailable()) { server.AddUpdate("warning", Languages::TextRailControlUpdateAvailable); } } else if (arguments["cmd"].compare("askshutdown") == 0) { HandleAskShutdown(); } else if (arguments["cmd"].compare("shutdown") == 0) { ReplyResponse(ResponseInfo, Languages::TextShutdownRailControl); shutdownRailControlWebserver(); } else if (arguments["cmd"].compare("booster") == 0) { bool on = Utils::Utils::GetBoolMapEntry(arguments, "on"); if (on) { ReplyHtmlWithHeaderAndParagraph(Languages::TextTurningBoosterOn); manager.Booster(ControlTypeWebServer, BoosterStateGo); } else { ReplyHtmlWithHeaderAndParagraph(Languages::TextTurningBoosterOff); manager.Booster(ControlTypeWebServer, BoosterStateStop); } } else if (arguments["cmd"].compare("layeredit") == 0) { HandleLayerEdit(arguments); } else if (arguments["cmd"].compare("layersave") == 0) { HandleLayerSave(arguments); } else if (arguments["cmd"].compare("layerlist") == 0) { HandleLayerList(); } else if (arguments["cmd"].compare("layeraskdelete") == 0) { HandleLayerAskDelete(arguments); } else if (arguments["cmd"].compare("layerdelete") == 0) { HandleLayerDelete(arguments); } else if (arguments["cmd"].compare("controledit") == 0) { HandleControlEdit(arguments); } else if (arguments["cmd"].compare("controlsave") == 0) { HandleControlSave(arguments); } else if (arguments["cmd"].compare("controllist") == 0) { HandleControlList(); } else if (arguments["cmd"].compare("controlaskdelete") == 0) { HandleControlAskDelete(arguments); } else if (arguments["cmd"].compare("controldelete") == 0) { HandleControlDelete(arguments); } else if (arguments["cmd"].compare("loco") == 0) { HandleLoco(arguments); } else if (arguments["cmd"].compare("locospeed") == 0) { HandleLocoBaseSpeed(arguments); } else if (arguments["cmd"].compare("locoorientation") == 0) { HandleLocoBaseOrientation(arguments); } else if (arguments["cmd"].compare("locofunction") == 0) { HandleLocoFunction(arguments); } else if (arguments["cmd"].compare("locoedit") == 0) { HandleLocoEdit(arguments); } else if (arguments["cmd"].compare("locosave") == 0) { HandleLocoSave(arguments); } else if (arguments["cmd"].compare("locolist") == 0) { HandleLocoList(); } else if (arguments["cmd"].compare("locoaskdelete") == 0) { HandleLocoAskDelete(arguments); } else if (arguments["cmd"].compare("locodelete") == 0) { HandleLocoDelete(arguments); } else if (arguments["cmd"].compare("locorelease") == 0) { HandleLocoRelease(arguments); } else if (arguments["cmd"].compare("locoaddtimetable") == 0) { HandleLocoAddTimeTable(arguments); } else if (arguments["cmd"].compare("multipleunitedit") == 0) { HandleMultipleUnitEdit(arguments); } else if (arguments["cmd"].compare("multipleunitsave") == 0) { HandleMultipleUnitSave(arguments); } else if (arguments["cmd"].compare("multipleunitlist") == 0) { HandleMultipleUnitList(); } else if (arguments["cmd"].compare("multipleunitaskdelete") == 0) { HandleMultipleUnitAskDelete(arguments); } else if (arguments["cmd"].compare("multipleunitdelete") == 0) { HandleMultipleUnitDelete(arguments); } else if (arguments["cmd"].compare("multipleunitrelease") == 0) { HandleMultipleUnitRelease(arguments); } else if (arguments["cmd"].compare("accessoryedit") == 0) { HandleAccessoryEdit(arguments); } else if (arguments["cmd"].compare("accessorysave") == 0) { HandleAccessorySave(arguments); } else if (arguments["cmd"].compare("accessorystate") == 0) { HandleAccessoryState(arguments); } else if (arguments["cmd"].compare("accessorylist") == 0) { HandleAccessoryList(); } else if (arguments["cmd"].compare("accessoryaskdelete") == 0) { HandleAccessoryAskDelete(arguments); } else if (arguments["cmd"].compare("accessorydelete") == 0) { HandleAccessoryDelete(arguments); } else if (arguments["cmd"].compare("accessoryget") == 0) { HandleAccessoryGet(arguments); } else if (arguments["cmd"].compare("accessoryrelease") == 0) { HandleAccessoryRelease(arguments); } else if (arguments["cmd"].compare("switchedit") == 0) { HandleSwitchEdit(arguments); } else if (arguments["cmd"].compare("switchsave") == 0) { HandleSwitchSave(arguments); } else if (arguments["cmd"].compare("switchstate") == 0) { HandleSwitchState(arguments); } else if (arguments["cmd"].compare("switchstates") == 0) { route.HandleRelationSwitchStates(arguments); } else if (arguments["cmd"].compare("switchlist") == 0) { HandleSwitchList(); } else if (arguments["cmd"].compare("switchaskdelete") == 0) { HandleSwitchAskDelete(arguments); } else if (arguments["cmd"].compare("switchdelete") == 0) { HandleSwitchDelete(arguments); } else if (arguments["cmd"].compare("switchget") == 0) { HandleSwitchGet(arguments); } else if (arguments["cmd"].compare("switchrelease") == 0) { HandleSwitchRelease(arguments); } else if (arguments["cmd"].compare("signaladdresses") == 0) { signal.HandleSignalAddresses(arguments); } else if (arguments["cmd"].compare("signaledit") == 0) { signal.HandleSignalEdit(arguments); } else if (arguments["cmd"].compare("signalsave") == 0) { signal.HandleSignalSave(arguments); } else if (arguments["cmd"].compare("signalstate") == 0) { signal.HandleSignalState(arguments); } else if (arguments["cmd"].compare("signalstates") == 0) { signal.HandleSignalStates(arguments); } else if (arguments["cmd"].compare("signallist") == 0) { signal.HandleSignalList(); } else if (arguments["cmd"].compare("signalaskdelete") == 0) { signal.HandleSignalAskDelete(arguments); } else if (arguments["cmd"].compare("signaldelete") == 0) { signal.HandleSignalDelete(arguments); } else if (arguments["cmd"].compare("signalget") == 0) { signal.HandleSignalGet(arguments); } else if (arguments["cmd"].compare("signalrelease") == 0) { signal.HandleSignalRelease(arguments); } else if (arguments["cmd"].compare("routeedit") == 0) { route.HandleRouteEdit(arguments); } else if (arguments["cmd"].compare("routesave") == 0) { route.HandleRouteSave(arguments); } else if (arguments["cmd"].compare("routelist") == 0) { route.HandleRouteList(); } else if (arguments["cmd"].compare("routeaskdelete") == 0) { route.HandleRouteAskDelete(arguments); } else if (arguments["cmd"].compare("routedelete") == 0) { route.HandleRouteDelete(arguments); } else if (arguments["cmd"].compare("routeget") == 0) { route.HandleRouteGet(arguments); } else if (arguments["cmd"].compare("routeexecute") == 0) { route.HandleRouteExecute(arguments); } else if (arguments["cmd"].compare("routerelease") == 0) { route.HandleRouteRelease(arguments); } else if (arguments["cmd"].compare("textedit") == 0) { text.HandleTextEdit(arguments); } else if (arguments["cmd"].compare("textsave") == 0) { text.HandleTextSave(arguments); } else if (arguments["cmd"].compare("textlist") == 0) { text.HandleTextList(); } else if (arguments["cmd"].compare("textaskdelete") == 0) { text.HandleTextAskDelete(arguments); } else if (arguments["cmd"].compare("textdelete") == 0) { text.HandleTextDelete(arguments); } else if (arguments["cmd"].compare("textget") == 0) { text.HandleTextGet(arguments); } else if (arguments["cmd"].compare("trackedit") == 0) { track.HandleTrackEdit(arguments); } else if (arguments["cmd"].compare("tracksave") == 0) { track.HandleTrackSave(arguments); } else if (arguments["cmd"].compare("tracklist") == 0) { track.HandleTrackList(); } else if (arguments["cmd"].compare("trackaskdelete") == 0) { track.HandleTrackAskDelete(arguments); } else if (arguments["cmd"].compare("trackdelete") == 0) { track.HandleTrackDelete(arguments); } else if (arguments["cmd"].compare("trackget") == 0) { track.HandleTrackGet(arguments); } else if (arguments["cmd"].compare("tracksetloco") == 0) { track.HandleTrackSetLoco(arguments); } else if (arguments["cmd"].compare("trackrelease") == 0) { track.HandleTrackRelease(arguments); } else if (arguments["cmd"].compare("trackstartloco") == 0) { track.HandleTrackStartLoco(arguments); } else if (arguments["cmd"].compare("trackstoploco") == 0) { track.HandleTrackStopLoco(arguments); } else if (arguments["cmd"].compare("trackblock") == 0) { track.HandleTrackBlock(arguments); } else if (arguments["cmd"].compare("trackorientation") == 0) { track.HandleTrackOrientation(arguments); } else if (arguments["cmd"].compare("feedbackedit") == 0) { HandleFeedbackEdit(arguments); } else if (arguments["cmd"].compare("feedbacksave") == 0) { HandleFeedbackSave(arguments); } else if (arguments["cmd"].compare("feedbackstate") == 0) { HandleFeedbackState(arguments); } else if (arguments["cmd"].compare("feedbacklist") == 0) { HandleFeedbackList(); } else if (arguments["cmd"].compare("feedbackaskdelete") == 0) { HandleFeedbackAskDelete(arguments); } else if (arguments["cmd"].compare("feedbackdelete") == 0) { HandleFeedbackDelete(arguments); } else if (arguments["cmd"].compare("feedbackget") == 0) { HandleFeedbackGet(arguments); } else if (arguments["cmd"].compare("feedbacksoftrack") == 0) { route.HandleFeedbacksOfTrack(arguments); } else if (arguments["cmd"].compare("protocol") == 0) { HandleProtocol(arguments); } else if (arguments["cmd"].compare("accessoryaddress") == 0) { HandleAccessoryAddress(arguments); } else if (arguments["cmd"].compare("feedbackadd") == 0) { HandleFeedbackAdd(arguments); } else if (arguments["cmd"].compare("relationadd") == 0) { route.HandleRelationAdd(arguments); } else if (arguments["cmd"].compare("relationobject") == 0) { route.HandleRelationObject(arguments); } else if (arguments["cmd"].compare("layout") == 0) { HandleLayout(arguments); } else if (arguments["cmd"].compare("locoselector") == 0) { HandleLocoSelector(arguments); } else if (arguments["cmd"].compare("layerselector") == 0) { HandleLayerSelector(arguments); } else if (arguments["cmd"].compare("stopallimmediately") == 0) { manager.LocoBaseStopAllImmediately(ControlTypeWebServer); } else if (arguments["cmd"].compare("startall") == 0) { manager.LocoBaseStartAll(); } else if (arguments["cmd"].compare("stopall") == 0) { manager.LocoBaseStopAll(); } else if (arguments["cmd"].compare("settingsedit") == 0) { HandleSettingsEdit(); } else if (arguments["cmd"].compare("settingssave") == 0) { HandleSettingsSave(arguments); } else if (arguments["cmd"].compare("slaveadd") == 0) { HandleSlaveAdd(arguments); } else if (arguments["cmd"].compare("timestamp") == 0) { HandleTimestamp(arguments); } else if (arguments["cmd"].compare("controlarguments") == 0) { HandleControlArguments(arguments); } else if (arguments["cmd"].compare("program") == 0) { HandleProgram(); } else if (arguments["cmd"].compare("programmodeselector") == 0) { HandleProgramModeSelector(arguments); } else if (arguments["cmd"].compare("programread") == 0) { HandleProgramRead(arguments); } else if (arguments["cmd"].compare("programwrite") == 0) { HandleProgramWrite(arguments); } else if (arguments["cmd"].compare("getcvfields") == 0) { HandleCvFields(arguments); } else if (arguments["cmd"].compare("clusterlist") == 0) { cluster.HandleClusterList(); } else if (arguments["cmd"].compare("clusteredit") == 0) { cluster.HandleClusterEdit(arguments); } else if (arguments["cmd"].compare("clustersave") == 0) { cluster.HandleClusterSave(arguments); } else if (arguments["cmd"].compare("clusteraskdelete") == 0) { cluster.HandleClusterAskDelete(arguments); } else if (arguments["cmd"].compare("clusterdelete") == 0) { cluster.HandleClusterDelete(arguments); } else if (arguments["cmd"].compare("newposition") == 0) { HandleNewPosition(arguments); } else if (arguments["cmd"].compare("rotate") == 0) { HandleRotate(arguments); } else if (arguments["cmd"].compare("getlocolist") == 0) { string s = manager.GetLocoList(); connection->Send(ResponseCsv(s)); } else if (arguments["cmd"].compare("getroutelist") == 0) { string s = manager.GetRouteList(); connection->Send(ResponseCsv(s)); } else if (arguments["cmd"].compare("updater") == 0) { HandleUpdater(headers); } else { DeliverFile(uri); } } } void WebClient::InterpretClientRequest(const deque& lines, string& method, string& uri, string& protocol, map& arguments, map& headers) { if (lines.size() == 0) { return; } for (auto& line : lines) { if (line.find("HTTP/1.") == string::npos) { deque list; Utils::Utils::SplitString(line, string(": "), list); if (list.size() == 2) { headers[list[0]] = list[1]; } continue; } deque list; Utils::Utils::SplitString(line, string(" "), list); if (list.size() != 3) { continue; } method = list[0]; // transform method to uppercase std::transform(method.begin(), method.end(), method.begin(), ::toupper); // if method == HEAD set membervariable headOnly = method.compare("HEAD") == 0; // set uri and protocol uri = list[1]; protocol = list[2]; // read GET-arguments from uri deque uriParts; Utils::Utils::SplitString(uri, "?", uriParts); if (uriParts.size() != 2) { continue; } deque argumentStrings; Utils::Utils::SplitString(uriParts[1], "&", argumentStrings); for (auto& argument : argumentStrings) { if (argument.length() == 0) { continue; } string key; string value; Utils::Utils::SplitString(argument, "=", key, value); arguments[key] = Utils::Utils::UrlDecode(value); } } } void WebClient::DeliverFile(const string& virtualFile) { std::stringstream ss; char workingDir[128]; if (getcwd(workingDir, sizeof(workingDir))) { ss << workingDir << "/html" << virtualFile; } string sFile = ss.str(); const char* realFile = sFile.c_str(); FILE* f = fopen(realFile, "r"); if (f == nullptr) { ResponseHtmlNotFound response(virtualFile); connection->Send(response); logger->Info(Languages::TextFileNotFound, virtualFile); return; } DeliverFileInternal(f, realFile, virtualFile); fclose(f); } void WebClient::DeliverFileInternal(FILE* f, const char* realFile, const string& virtualFile) { struct stat s; int rc = stat(realFile, &s); if (rc != 0) { return; } size_t length = virtualFile.length(); const char* contentType = nullptr; if (length > 4 && virtualFile[length - 4] == '.') { if (virtualFile[length - 3] == 'i' && virtualFile[length - 2] == 'c' && virtualFile[length - 1] == 'o') { contentType = "image/x-icon"; } else if (virtualFile[length - 3] == 'c' && virtualFile[length - 2] == 's' && virtualFile[length - 1] == 's') { contentType = "text/css"; } else if (virtualFile[length - 3] == 'p' && virtualFile[length - 2] == 'n' && virtualFile[length - 1] == 'g') { contentType = "image/png"; } else if (virtualFile[length - 3] == 't' && virtualFile[length - 2] == 't' && virtualFile[length - 1] == 'f') { contentType = "application/x-font-ttf"; } } else if (length > 3 && virtualFile[length - 3] == '.' && virtualFile[length - 2] == 'j' && virtualFile[length - 1] == 's') { contentType = "application/javascript"; } Response response; response.AddHeader("Cache-Control", "no-cache, must-revalidate"); response.AddHeader("Pragma", "no-cache"); response.AddHeader("Expires", "Sun, 12 Feb 2016 00:00:00 GMT"); response.AddHeader("Content-Length", to_string(s.st_size)); if (contentType != nullptr) { response.AddHeader("Content-Type", contentType); } connection->Send(response); if (headOnly == true) { return; } char* buffer = static_cast(malloc(s.st_size)); if (buffer == nullptr) { return; } size_t r = fread(buffer, 1, s.st_size, f); connection->Send(buffer, r, 0); free(buffer); } void WebClient::HandleAskShutdown() { HtmlTag content; content.AddContent(HtmlTag("h1").AddContent(Languages::TextShutdown)); content.AddContent(HtmlTag("p").AddContent(Languages::TextAreYouSureToShutdown)); content.AddContent(HtmlTag("form").AddId("editform").AddContent(HtmlTagInputHidden("cmd", "shutdown"))); content.AddContent(HtmlTagButtonCancel()); content.AddContent(HtmlTagButtonOK()); ReplyHtmlWithHeader(content); } void WebClient::HandleLayerEdit(const map& arguments) { HtmlTag content; LayerID layerID = Utils::Utils::GetIntegerMapEntry(arguments, "layer", LayerNone); string name = Languages::GetText(Languages::TextNew); if (layerID != LayerNone) { Layer* layer = manager.GetLayer(layerID); if (layer != nullptr) { name = layer->GetName(); } } content.AddChildTag(HtmlTag("h1").AddContent(name).AddId("popup_title")); HtmlTag form("form"); form.AddId("editform"); form.AddChildTag(HtmlTagInputHidden("cmd", "layersave")); form.AddChildTag(HtmlTagInputHidden("layer", to_string(layerID))); form.AddChildTag(HtmlTagInputTextWithLabel("name", Languages::TextName, name).AddAttribute("onkeyup", "updateName();")); content.AddChildTag(HtmlTag("div").AddClass("popup_content").AddChildTag(form)); content.AddChildTag(HtmlTagButtonCancel()); content.AddChildTag(HtmlTagButtonOK()); ReplyHtmlWithHeader(content); } void WebClient::HandleLayerSave(const map& arguments) { LayerID layerID = Utils::Utils::GetIntegerMapEntry(arguments, "layer", LayerNone); string name = Utils::Utils::GetStringMapEntry(arguments, "name"); string result; if (!manager.LayerSave(layerID, name, result)) { ReplyResponse(ResponseError, result); return; } ReplyResponse(ResponseInfo, Languages::TextLayerSaved, name); } void WebClient::HandleLayerAskDelete(const map& arguments) { LayerID layerID = Utils::Utils::GetIntegerMapEntry(arguments, "layer", LayerNone); if (layerID == LayerNone) { ReplyHtmlWithHeaderAndParagraph(Languages::TextLayerDoesNotExist); return; } if (layerID == LayerUndeletable) { ReplyHtmlWithHeaderAndParagraph(Languages::TextLayer1IsUndeletable); return; } const Layer* layer = manager.GetLayer(layerID); if (layer == nullptr) { ReplyHtmlWithHeaderAndParagraph(Languages::GetText(Languages::TextLayerDoesNotExist)); return; } HtmlTag content; content.AddContent(HtmlTag("h1").AddContent(Languages::TextDeleteLayer)); content.AddContent(HtmlTag("p").AddContent(Languages::TextAreYouSureToDelete, layer->GetName())); content.AddContent(HtmlTag("form").AddId("editform") .AddContent(HtmlTagInputHidden("cmd", "layerdelete")) .AddContent(HtmlTagInputHidden("layer", to_string(layerID)) )); content.AddContent(HtmlTagButtonCancel()); content.AddContent(HtmlTagButtonOK()); ReplyHtmlWithHeader(content); } void WebClient::HandleLayerDelete(const map& arguments) { LayerID layerID = Utils::Utils::GetIntegerMapEntry(arguments, "layer", LayerNone); if (layerID == LayerNone) { ReplyResponse(ResponseError, Languages::TextLayerDoesNotExist); return; } if (layerID == LayerUndeletable) { ReplyResponse(ResponseError, Languages::TextLayer1IsUndeletable); return; } const Layer* layer = manager.GetLayer(layerID); if (layer == nullptr) { ReplyResponse(ResponseError, Languages::TextLayerDoesNotExist); return; } string name = layer->GetName(); string result; if (!manager.LayerDelete(layerID, result)) { ReplyResponse(ResponseError, result); return; } ReplyResponse(ResponseInfo, Languages::TextLayerDeleted, name); } void WebClient::HandleLayerList() { HtmlTag content; content.AddChildTag(HtmlTag("h1").AddContent(Languages::TextLayers)); HtmlTag table("table"); const map layerList = manager.LayerListByName(); map layerArgument; for (auto& layer : layerList) { HtmlTag row("tr"); row.AddChildTag(HtmlTag("td").AddContent(layer.first)); string layerIdString = to_string(layer.second); layerArgument["layer"] = layerIdString; row.AddChildTag(HtmlTag("td").AddChildTag(HtmlTagButtonPopupWide(Languages::TextEdit, "layeredit_list_" + layerIdString, layerArgument))); if (layer.second != LayerUndeletable) { row.AddChildTag(HtmlTag("td").AddChildTag(HtmlTagButtonPopupWide(Languages::TextDelete, "layeraskdelete_" + layerIdString, layerArgument))); } table.AddChildTag(row); } content.AddChildTag(HtmlTag("div").AddClass("popup_content").AddChildTag(table)); content.AddChildTag(HtmlTagButtonCancel()); content.AddChildTag(HtmlTagButtonPopupWide(Languages::TextNew, "layeredit_0")); ReplyHtmlWithHeader(content); } void WebClient::HandleControlEdit(const map& arguments) { HtmlTag content; ControlID controlID = Utils::Utils::GetIntegerMapEntry(arguments, "control", ControlIdNone); HardwareType hardwareType = HardwareTypeNone; string name = Languages::GetText(Languages::TextNew); string arg1; string arg2; string arg3; string arg4; string arg5; if (controlID != ControlIdNone) { Hardware::HardwareParams* params = manager.GetHardware(controlID); if (params != nullptr) { hardwareType = params->GetHardwareType(); name = params->GetName(); arg1 = params->GetArg1(); arg2 = params->GetArg2(); arg3 = params->GetArg3(); arg4 = params->GetArg4(); arg5 = params->GetArg5(); } } const std::map hardwareOptions = WebClientStatic::ListHardwareNames(); content.AddChildTag(HtmlTag("h1").AddContent(name).AddId("popup_title")); HtmlTag form("form"); form.AddId("editform"); form.AddChildTag(HtmlTagInputHidden("cmd", "controlsave")); form.AddChildTag(HtmlTagInputHidden("control", to_string(controlID))); form.AddChildTag(HtmlTagInputTextWithLabel("name", Languages::TextName, name).AddAttribute("onkeyup", "updateName();")); HtmlTagSelectWithLabel selectHardwareType("hardwaretype", Languages::TextType, hardwareOptions, hardwareType); selectHardwareType.AddAttribute("onchange", "getArgumentsOfHardwareType();"); form.AddChildTag(selectHardwareType); HtmlTag controlArguments("div"); controlArguments.AddId("controlarguments"); controlArguments.AddChildTag(WebClientStatic::HtmlTagControlArguments(hardwareType, arg1, arg2, arg3, arg4, arg5)); form.AddChildTag(controlArguments); content.AddChildTag(HtmlTag("div").AddClass("popup_content").AddChildTag(form)); content.AddChildTag(HtmlTagButtonCancel()); content.AddChildTag(HtmlTagButtonOK()); ReplyHtmlWithHeader(content); } void WebClient::HandleControlSave(const map& arguments) { ControlID controlID = Utils::Utils::GetIntegerMapEntry(arguments, "control", ControlIdNone); string name = Utils::Utils::GetStringMapEntry(arguments, "name"); HardwareType hardwareType = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "hardwaretype", HardwareTypeNone)); string arg1 = Utils::Utils::GetStringMapEntry(arguments, "arg1"); string arg2 = Utils::Utils::GetStringMapEntry(arguments, "arg2"); string arg3 = Utils::Utils::GetStringMapEntry(arguments, "arg3"); string arg4 = Utils::Utils::GetStringMapEntry(arguments, "arg4"); string arg5 = Utils::Utils::GetStringMapEntry(arguments, "arg5"); string result; if (!manager.ControlSave(controlID, hardwareType, name, arg1, arg2, arg3, arg4, arg5, result)) { ReplyResponse(ResponseError, result); return; } ReplyResponse(ResponseInfo, Languages::TextControlSaved, name); } void WebClient::HandleControlAskDelete(const map& arguments) { ControlID controlID = Utils::Utils::GetIntegerMapEntry(arguments, "control", ControlNone); if (controlID == ControlNone) { ReplyHtmlWithHeaderAndParagraph(Languages::TextControlDoesNotExist); return; } const Hardware::HardwareParams* control = manager.GetHardware(controlID); if (control == nullptr) { ReplyHtmlWithHeaderAndParagraph(Languages::TextControlDoesNotExist); return; } HtmlTag content; content.AddContent(HtmlTag("h1").AddContent(Languages::TextDeleteControl)); content.AddContent(HtmlTag("p").AddContent(Languages::TextAreYouSureToDelete, control->GetName())); content.AddContent(HtmlTag("form").AddId("editform") .AddContent(HtmlTagInputHidden("cmd", "controldelete")) .AddContent(HtmlTagInputHidden("control", to_string(controlID)) )); content.AddContent(HtmlTagButtonCancel()); content.AddContent(HtmlTagButtonOK()); ReplyHtmlWithHeader(content); } void WebClient::HandleControlDelete(const map& arguments) { ControlID controlID = Utils::Utils::GetIntegerMapEntry(arguments, "control", ControlNone); const Hardware::HardwareParams* control = manager.GetHardware(controlID); if (control == nullptr) { ReplyResponse(ResponseError, Languages::TextControlDoesNotExist); return; } string name = control->GetName(); if (!manager.ControlDelete(controlID)) { ReplyResponse(ResponseError, Languages::TextControlDoesNotExist); return; } ReplyResponse(ResponseInfo, Languages::TextControlDeleted, name); } void WebClient::HandleControlList() { HtmlTag content; content.AddChildTag(HtmlTag("h1").AddContent(Languages::TextControls)); HtmlTag table("table"); const map hardwareList = manager.ControlListByName(); map hardwareArgument; for (auto& hardware : hardwareList) { HtmlTag row("tr"); row.AddChildTag(HtmlTag("td").AddContent(hardware.first)); string controlIdString = to_string(hardware.second->GetControlID()); hardwareArgument["control"] = controlIdString; row.AddChildTag(HtmlTag("td").AddChildTag(HtmlTagButtonPopupWide(Languages::TextEdit, "controledit_list_" + controlIdString, hardwareArgument))); row.AddChildTag(HtmlTag("td").AddChildTag(HtmlTagButtonPopupWide(Languages::TextDelete, "controlaskdelete_" + controlIdString, hardwareArgument))); table.AddChildTag(row); } content.AddChildTag(HtmlTag("div").AddClass("popup_content").AddChildTag(table)); content.AddChildTag(HtmlTagButtonCancel()); content.AddChildTag(HtmlTagButtonPopupWide(Languages::TextNew, "controledit_0")); ReplyHtmlWithHeader(content); } void WebClient::HandleLocoBaseSpeed(const map& arguments) { const LocoID locoID = Utils::Utils::GetIntegerMapEntry(arguments, "loco", LocoNone); const Speed speed = Utils::Utils::GetIntegerMapEntry(arguments, "speed", MinSpeed); const ObjectIdentifier locoBaseIdentifier(WebClientStatic::LocoIdToObjectIdentifier(locoID)); manager.LocoBaseSpeed(ControlTypeWebServer, locoBaseIdentifier, speed); ReplyHtmlWithHeaderAndParagraph(Languages::TextLocoSpeedIs, manager.GetLocoBaseName(locoBaseIdentifier), speed); } void WebClient::HandleLocoBaseOrientation(const map& arguments) { const LocoID locoID = Utils::Utils::GetIntegerMapEntry(arguments, "loco", LocoNone); const Orientation orientation = (Utils::Utils::GetBoolMapEntry(arguments, "on") ? OrientationRight : OrientationLeft); const ObjectIdentifier locoBaseIdentifier(WebClientStatic::LocoIdToObjectIdentifier(locoID)); manager.LocoBaseOrientation(ControlTypeWebServer, locoBaseIdentifier, orientation); ReplyHtmlWithHeaderAndParagraph(orientation == OrientationLeft ? Languages::TextLocoDirectionOfTravelIsLeft : Languages::TextLocoDirectionOfTravelIsRight, manager.GetLocoBaseName(locoBaseIdentifier)); } void WebClient::HandleLocoFunction(const map& arguments) { const LocoID locoID = Utils::Utils::GetIntegerMapEntry(arguments, "loco", LocoNone); const DataModel::LocoFunctionNr function = Utils::Utils::GetIntegerMapEntry(arguments, "function", 0); const DataModel::LocoFunctionState state = static_cast(Utils::Utils::GetBoolMapEntry(arguments, "on")); const ObjectIdentifier locoBaseIdentifier(WebClientStatic::LocoIdToObjectIdentifier(locoID)); manager.LocoBaseFunctionState(ControlTypeWebServer, locoBaseIdentifier, function, state); ReplyHtmlWithHeaderAndParagraph(state ? Languages::TextLocoFunctionIsOn : Languages::TextLocoFunctionIsOff, manager.GetLocoBaseName(locoBaseIdentifier), function); } void WebClient::HandleLocoRelease(const map& arguments) { bool ret = false; const LocoID locoID = Utils::Utils::GetIntegerMapEntry(arguments, "loco", LocoNone); if (locoID != LocoNone) { ret = manager.LocoRelease(locoID); } else { TrackID trackID = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "track", TrackNone)); ret = manager.LocoBaseReleaseOnTrack(trackID); } ReplyHtmlWithHeaderAndParagraph(ret ? "Loco released" : "Loco not released"); } void WebClient::HandleMultipleUnitRelease(const map& arguments) { bool ret = false; MultipleUnitID multipleUnitID = Utils::Utils::GetIntegerMapEntry(arguments, "multipleunit", MultipleUnitNone); if (multipleUnitID != LocoNone) { ret = manager.MultipleUnitRelease(multipleUnitID); } else { TrackID trackID = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "track", TrackNone)); ret = manager.LocoBaseReleaseOnTrack(trackID); } ReplyHtmlWithHeaderAndParagraph(ret ? "Loco released" : "Loco not released"); } void WebClient::HandleLocoAddTimeTable(const map& arguments) { const TrackID trackID = Utils::Utils::GetIntegerMapEntry(arguments, "track", TrackNone); const ObjectIdentifier locoBaseIdentifier = manager.GetLocoBaseIdentifierOfTrack(trackID); const RouteID routeID = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "route")); const bool automode = Utils::Utils::GetStringMapEntry(arguments, "followup", "manual").compare("automode") == 0; bool ret = manager.LocoBaseAddTimeTable(locoBaseIdentifier, routeID, automode); manager.TrackStartLocoBase(trackID); ReplyHtmlWithHeaderAndParagraph(ret ? "Route added" : "Route not added"); } HtmlTag WebClient::HtmlTagMatchKeyProtocolLoco(const ControlID controlId, const string& selectedMatchKey, const Protocol selectedProtocol) const { HtmlTag content; map matchKeyMap = manager.GetUnmatchedLocosOfControl(controlId, selectedMatchKey); content.AddChildTag(WebClientStatic::HtmlTagMatchKey(matchKeyMap, selectedMatchKey)); map protocolMap = manager.LocoProtocolsOfControl(controlId); content.AddChildTag(WebClientStatic::HtmlTagProtocol(protocolMap, selectedProtocol)); return content; } HtmlTag WebClient::HtmlTagMatchKeyProtocolAccessory(const ControlID controlId, const string& selectedMatchKey, const Protocol selectedProtocol) const { HtmlTag content; map matchKeyMap = manager.GetUnmatchedAccessoriesOfControl(controlId, selectedMatchKey); content.AddChildTag(WebClientStatic::HtmlTagMatchKey(matchKeyMap, selectedMatchKey)); map protocolMap = manager.AccessoryProtocolsOfControl(controlId); content.AddChildTag(WebClientStatic::HtmlTagProtocol(protocolMap, selectedProtocol)); return content; } HtmlTag WebClient::HtmlTagMatchKeyFeedback(const ControlID controlId, const string& selectedMatchKey) const { HtmlTag content; map matchKeyMap = manager.GetUnmatchedFeedbacksOfControl(controlId, selectedMatchKey); content.AddChildTag(WebClientStatic::HtmlTagMatchKey(matchKeyMap, selectedMatchKey)); return content; } HtmlTag WebClient::HtmlTagProtocolAccessory(const ControlID controlID, const Protocol selectedProtocol) { map protocolMap = manager.AccessoryProtocolsOfControl(controlID); return WebClientStatic::HtmlTagProtocol(protocolMap, selectedProtocol); } void WebClient::HandleProtocol(const map& arguments) { ControlID controlId = Utils::Utils::GetIntegerMapEntry(arguments, "control", ControlIdNone); if (controlId == ControlIdNone) { ReplyHtmlWithHeaderAndParagraph(Languages::TextControlDoesNotExist); return; } if (Utils::Utils::IsMapEntrySet(arguments, "loco")) { LocoID locoId = Utils::Utils::GetIntegerMapEntry(arguments, "loco", LocoNone); string matchKey; Protocol protocol = ProtocolNone; Loco* loco = manager.GetLoco(locoId); if (loco != nullptr) { matchKey = loco->GetMatchKey(); protocol = loco->GetProtocol(); } ReplyHtmlWithHeader(HtmlTagMatchKeyProtocolLoco(controlId, matchKey, protocol)); return; } AccessoryID accessoryId = Utils::Utils::GetIntegerMapEntry(arguments, "accessory", AccessoryNone); if (accessoryId != AccessoryNone) { Accessory *accessory = manager.GetAccessory(accessoryId); ReplyHtmlWithHeader(HtmlTagProtocolAccessory(controlId, accessory == nullptr ? ProtocolNone : accessory->GetProtocol())); return; } SwitchID switchId = Utils::Utils::GetIntegerMapEntry(arguments, "switch", SwitchNone); if (switchId != SwitchNone) { Switch *mySwitch = manager.GetSwitch(switchId); ReplyHtmlWithHeader(HtmlTagProtocolAccessory(controlId, mySwitch == nullptr ? ProtocolNone : mySwitch->GetProtocol())); return; } SignalID signalId = Utils::Utils::GetIntegerMapEntry(arguments, "signal", SignalNone); if (signalId != SignalNone) { Signal *signal = manager.GetSignal(signalId); ReplyHtmlWithHeader(HtmlTagProtocolAccessory(controlId, signal == nullptr ? ProtocolNone : signal->GetProtocol())); return; } ReplyHtmlWithHeader(HtmlTagProtocolAccessory(controlId, ProtocolNone)); } void WebClient::HandleAccessoryAddress(const map& arguments) { const AccessoryType type = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "type", AccessoryTypeDefault)); const Address address = static_cast
(Utils::Utils::GetIntegerMapEntry(arguments, "address", AddressDefault)); const AddressPort port = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "port", AddressPortRed)); ReplyHtmlWithHeader(WebClientStatic::HtmlTagAccessoryAddress(type, address, port)); } HtmlTag WebClient::HtmlTagPosition(const LayoutPosition posx, const LayoutPosition posy, const LayoutPosition posz) const { HtmlTag content("div"); content.AddId("position");; content.AddChildTag(HtmlTagInputIntegerWithLabel("posx", Languages::TextPosX, Languages::TextHintPositionMove, posx, 0, 255)); content.AddChildTag(HtmlTagInputIntegerWithLabel("posy", Languages::TextPosY, Languages::TextHintPositionMove, posy, 0, 255)); map layerList = manager.LayerListByName(); content.AddChildTag(HtmlTagSelectWithLabel("posz", Languages::TextPosZ, layerList, posz)); return content; } HtmlTag WebClient::HtmlTagPosition(const LayoutPosition posx, const LayoutPosition posy, const LayoutPosition posz, const Visible visible) const { HtmlTag content; HtmlTagInputCheckboxWithLabel checkboxVisible("visible", Languages::TextVisible, "visible", static_cast(visible)); checkboxVisible.AddId("visible"); checkboxVisible.AddAttribute("onchange", "onChangeCheckboxShowHide('visible', 'position');"); content.AddChildTag(checkboxVisible); HtmlTag posDiv = HtmlTagPosition(posx, posy, posz); if (visible == DataModel::LayoutItem::VisibleNo) { posDiv.AddAttribute("hidden"); } content.AddChildTag(posDiv); return content; } HtmlTag WebClient::HtmlTagSelectSlave(const string& prefix, const vector& relations, const map& options, const bool allowNew) const { HtmlTag content("div"); content.AddId("tab_" + prefix + "s"); content.AddClass("tab_content"); content.AddClass("hidden"); HtmlTag div("div"); div.AddChildTag(HtmlTagInputHidden(prefix + "counter", to_string(relations.size()))); div.AddId(prefix + "s"); unsigned int counter = 1; for (auto relation : relations) { ObjectID objectID = relation->ObjectID2(); div.AddChildTag(WebClientStatic::HtmlTagSlaveEntry(prefix, to_string(counter), objectID, options)); ++counter; } div.AddChildTag(HtmlTag("div").AddId(prefix + "_new_" + to_string(counter))); content.AddChildTag(div); if (allowNew) { HtmlTagButton newTrackButton(Languages::TextNew, "new" + prefix); newTrackButton.AddAttribute("onclick", "addSlave('" + prefix + "');return false;"); newTrackButton.AddClass("wide_button"); content.AddChildTag(newTrackButton); } content.AddChildTag(HtmlTag("br")); return content; } HtmlTag WebClient::HtmlTagSelectFeedbackForTrack(const unsigned int counter, const TrackID trackID, const FeedbackID feedbackID) const { string counterString = to_string(counter); HtmlTag content("div"); content.AddId("feedback_container_" + counterString); HtmlTagButton deleteButton(Languages::TextDelete, "delete_feedback_" + counterString); deleteButton.AddAttribute("onclick", "deleteElement('feedback_container_" + counterString + "');return false;"); deleteButton.AddClass("wide_button"); content.AddChildTag(deleteButton); map feedbacks = manager.FeedbackListByName(); map feedbackOptions; for (auto& feedback : feedbacks) { const Track* track = feedback.second->GetTrack(); if ((!track) || (track->GetID() != trackID)) { continue; } feedbackOptions[feedback.first] = feedback.second->GetID(); } content.AddChildTag(HtmlTagSelect("feedback_" + counterString, feedbackOptions, feedbackID)); content.AddChildTag(HtmlTag("div").AddId("div_feedback_" + to_string(counter + 1))); return content; } void WebClient::HandleSlaveAdd(const map& arguments) { string priorityString = Utils::Utils::GetStringMapEntry(arguments, "priority", "1"); Priority priority = Utils::Integer::StringToInteger(priorityString, 1); string prefix = Utils::Utils::GetStringMapEntry(arguments, "prefix"); HtmlTag container; std::map options; if (prefix.compare("track") == 0) { options = cluster.GetTrackOptions(); } else if (prefix.compare("feedback") == 0) { options = track.GetFeedbackOptions(); } else if (prefix.compare("signal") == 0) { options = track.GetSignalOptions(); } else if (prefix.compare("slave") == 0) { options = GetMultipleUnitSlaveOptions(); } container.AddChildTag(WebClientStatic::HtmlTagSlaveEntry(prefix, priorityString, ObjectNone, options)); container.AddChildTag(HtmlTag("div").AddId(prefix + "_new_" + to_string(priority + 1))); ReplyHtmlWithHeader(container); } void WebClient::HandleFeedbackAdd(const map& arguments) { const unsigned int counter = Utils::Utils::GetIntegerMapEntry(arguments, "counter", 1); const TrackID trackID = Utils::Utils::GetIntegerMapEntry(arguments, "track", TrackNone); ReplyHtmlWithHeader(HtmlTagSelectFeedbackForTrack(counter, trackID)); } map WebClient::GetMultipleUnitSlaveOptions() const { map options; map allLocos = manager.LocoConfigByName(); for (auto& loco : allLocos) { options[loco.first] = loco.second.GetLocoId(); } return options; } void WebClient::HandleLocoEdit(const map& arguments) { HtmlTag content; const LocoID locoId = Utils::Utils::GetIntegerMapEntry(arguments, "loco", LocoNone); ControlID controlId = Utils::Utils::GetIntegerMapEntry(arguments, "control", ControlNone); if (controlId == ControlNone) { controlId = manager.GetPossibleControlForLoco(); } string matchKey = Utils::Utils::GetStringMapEntry(arguments, "matchkey"); Protocol protocol = ProtocolNone; Address address = AddressDefault; Address serverAddress = AddressNone; string name = Languages::GetText(Languages::TextNew); bool pushpull = false; Length length = 0; Speed maxSpeed = MaxSpeed; Speed travelSpeed = DefaultTravelSpeed; Speed reducedSpeed = DefaultReducedSpeed; Speed creepingSpeed = DefaultCreepingSpeed; Propulsion propulsion = PropulsionOther; TrainType trainType = TrainTypeOther; LocoFunctionEntry locoFunctions[NumberOfLocoFunctions]; vector slaves; if (locoId > LocoNone) { // existing loco const DataModel::Loco* loco = manager.GetLoco(locoId); if (loco != nullptr) { controlId = loco->GetControlID(); matchKey = loco->GetMatchKey(); protocol = loco->GetProtocol(); address = loco->GetAddress(); serverAddress = loco->GetServerAddress(); name = loco->GetName(); pushpull = loco->GetPushpull(); length = loco->GetLength(); maxSpeed = loco->GetMaxSpeed(); travelSpeed = loco->GetTravelSpeed(); reducedSpeed = loco->GetReducedSpeed(); creepingSpeed = loco->GetCreepingSpeed(); propulsion = loco->GetPropulsion(); trainType = loco->GetTrainType(); loco->GetFunctions(locoFunctions); } } else if (controlId > ControlNone) { // loco from hardware database DataModel::LocoConfig loco = manager.GetLocoOfConfigByMatchKey(controlId, matchKey); if ((loco.GetControlId() == controlId) && (loco.GetMatchKey() == matchKey)) { protocol = loco.GetProtocol(); address = loco.GetAddress(); name = loco.GetName(); loco.GetFunctions(locoFunctions); } } // else new loco content.AddChildTag(HtmlTag("h1").AddContent(name).AddId("popup_title")); HtmlTag tabMenu("div"); tabMenu.AddChildTag(WebClientStatic::HtmlTagTabMenuItem("basic", Languages::TextBasic, true)); tabMenu.AddChildTag(WebClientStatic::HtmlTagTabMenuItem("functions", Languages::TextFunctions)); tabMenu.AddChildTag(WebClientStatic::HtmlTagTabMenuItem("automode", Languages::TextAutomode)); content.AddChildTag(tabMenu); HtmlTag formContent("form"); formContent.AddId("editform"); formContent.AddChildTag(HtmlTagInputHidden("cmd", "locosave")); formContent.AddChildTag(HtmlTagInputHidden("loco", to_string(locoId))); HtmlTag basicContent("div"); basicContent.AddId("tab_basic"); basicContent.AddClass("tab_content"); basicContent.AddChildTag(HtmlTagInputTextWithLabel("name", Languages::TextName, name).AddAttribute("onkeyup", "updateName();")); basicContent.AddChildTag(HtmlTagControlLoco(controlId, "loco", locoId)); basicContent.AddChildTag(HtmlTag("div").AddId("select_protocol").AddChildTag(HtmlTagMatchKeyProtocolLoco(controlId, matchKey, protocol))); basicContent.AddChildTag(HtmlTagInputIntegerWithLabel("address", Languages::TextAddress, address, 1, 9999)); if (manager.IsServerEnabled()) { basicContent.AddChildTag(HtmlTagInputIntegerWithLabel("serveraddress", Languages::TextServerAddress, serverAddress, 0, 9999)); } basicContent.AddChildTag(HtmlTagInputIntegerWithLabel("length", Languages::TextTrainLength, length, 0, 99999)); basicContent.AddChildTag(WebClientStatic::HtmlTagSelectPropulsion(propulsion)); basicContent.AddChildTag(WebClientStatic::HtmlTagSelectTrainType(trainType)); formContent.AddChildTag(basicContent); formContent.AddChildTag(WebClientStatic::HtmlTagTabFunctions(locoFunctions)); formContent.AddChildTag(WebClientStatic::HtmlTagTabAutomode(pushpull, maxSpeed, travelSpeed, reducedSpeed, creepingSpeed)); content.AddChildTag(HtmlTag("div").AddClass("popup_content").AddChildTag(formContent)); content.AddChildTag(HtmlTagButtonCancel()); content.AddChildTag(HtmlTagButtonOK()); ReplyHtmlWithHeader(content); } void WebClient::HandleMultipleUnitEdit(const map& arguments) { HtmlTag content; const MultipleUnitID multipleUnitId = Utils::Utils::GetIntegerMapEntry(arguments, "multipleunit", MultipleUnitNone); ControlID controlId = Utils::Utils::GetIntegerMapEntry(arguments, "control", ControlNone); string matchKey = Utils::Utils::GetStringMapEntry(arguments, "matchkey"); string name = Languages::GetText(Languages::TextNew); bool pushpull = false; Address serverAddress = AddressNone; Length length = 0; Speed maxSpeed = MaxSpeed; Speed travelSpeed = DefaultTravelSpeed; Speed reducedSpeed = DefaultReducedSpeed; Speed creepingSpeed = DefaultCreepingSpeed; TrainType trainType = TrainTypeOther; LocoFunctionEntry locoFunctions[NumberOfLocoFunctions]; vector slaves; if (multipleUnitId > MultipleUnitNone) { // existing multiple unit const DataModel::MultipleUnit* multipleUnit = manager.GetMultipleUnit(multipleUnitId); if (multipleUnit != nullptr) { controlId = multipleUnit->GetControlID(); matchKey = multipleUnit->GetMatchKey(); name = multipleUnit->GetName(); pushpull = multipleUnit->GetPushpull(); serverAddress = multipleUnit->GetServerAddress(); length = multipleUnit->GetLength(); maxSpeed = multipleUnit->GetMaxSpeed(); travelSpeed = multipleUnit->GetTravelSpeed(); reducedSpeed = multipleUnit->GetReducedSpeed(); creepingSpeed = multipleUnit->GetCreepingSpeed(); trainType = multipleUnit->GetTrainType(); multipleUnit->GetFunctions(locoFunctions); slaves = multipleUnit->GetSlaves(); } } else if (controlId > ControlNone) { // multiple unit from hardware database DataModel::LocoConfig multipleUnit = manager.GetMultipleUnitOfConfigByMatchKey(controlId, matchKey); if ((multipleUnit.GetControlId() == controlId) && (multipleUnit.GetMatchKey() == matchKey)) { name = multipleUnit.GetName(); multipleUnit.GetFunctions(locoFunctions); slaves = WebClientStatic::ConvertSlaveIDVectorToRelation(manager, multipleUnitId, multipleUnit.GetSlaves()); } } // else new multiple unit content.AddChildTag(HtmlTag("h1").AddContent(name).AddId("popup_title")); HtmlTag tabMenu("div"); tabMenu.AddChildTag(WebClientStatic::HtmlTagTabMenuItem("basic", Languages::TextBasic, true)); tabMenu.AddChildTag(WebClientStatic::HtmlTagTabMenuItem("functions", Languages::TextFunctions)); tabMenu.AddChildTag(WebClientStatic::HtmlTagTabMenuItem("slaves", Languages::TextMultipleUnit)); tabMenu.AddChildTag(WebClientStatic::HtmlTagTabMenuItem("automode", Languages::TextAutomode)); content.AddChildTag(tabMenu); HtmlTag formContent("form"); formContent.AddId("editform"); formContent.AddChildTag(HtmlTagInputHidden("cmd", "multipleunitsave")); formContent.AddChildTag(HtmlTagInputHidden("multipleunit", to_string(multipleUnitId))); HtmlTag basicContent("div"); basicContent.AddId("tab_basic"); basicContent.AddClass("tab_content"); basicContent.AddChildTag(HtmlTagInputTextWithLabel("name", Languages::TextName, name).AddAttribute("onkeyup", "updateName();")); basicContent.AddChildTag(HtmlTagControlMultipleUnit(controlId, "multipleunit", multipleUnitId)); if (manager.IsServerEnabled()) { basicContent.AddChildTag(HtmlTagInputIntegerWithLabel("serveraddress", Languages::TextServerAddress, serverAddress, 0, 9999)); } basicContent.AddChildTag(HtmlTagInputIntegerWithLabel("length", Languages::TextTrainLength, length, 0, 99999)); basicContent.AddChildTag(WebClientStatic::HtmlTagSelectTrainType(trainType)); formContent.AddChildTag(basicContent); formContent.AddChildTag(WebClientStatic::HtmlTagTabFunctions(locoFunctions)); formContent.AddChildTag(HtmlTagSelectSlave("slave", slaves, GetMultipleUnitSlaveOptions())); formContent.AddChildTag(WebClientStatic::HtmlTagTabAutomode(pushpull, maxSpeed, travelSpeed, reducedSpeed, creepingSpeed)); content.AddChildTag(HtmlTag("div").AddClass("popup_content").AddChildTag(formContent)); content.AddChildTag(HtmlTagButtonCancel()); content.AddChildTag(HtmlTagButtonOK()); ReplyHtmlWithHeader(content); } void WebClient::HandleLocoSave(const map& arguments) { const LocoID locoId = Utils::Utils::GetIntegerMapEntry(arguments, "loco", LocoNone); const string name = Utils::Utils::GetStringMapEntry(arguments, "name", Languages::GetText(Languages::TextLoco)); const ControlID controlId = Utils::Utils::GetIntegerMapEntry(arguments, "control", ControlIdNone); const string matchKey = Utils::Utils::GetStringMapEntry(arguments, "matchkey"); const Protocol protocol = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "protocol", ProtocolNone)); const Address address = Utils::Utils::GetIntegerMapEntry(arguments, "address", AddressDefault); const Address serverAddress = Utils::Utils::GetIntegerMapEntry(arguments, "serveraddress", AddressNone); const Length length = Utils::Utils::GetIntegerMapEntry(arguments, "length", 0); const bool pushpull = Utils::Utils::GetBoolMapEntry(arguments, "pushpull", false); const Speed maxSpeed = Utils::Utils::GetIntegerMapEntry(arguments, "maxspeed", MaxSpeed); Speed travelSpeed = Utils::Utils::GetIntegerMapEntry(arguments, "travelspeed", DefaultTravelSpeed); if (travelSpeed > maxSpeed) { travelSpeed = maxSpeed; } Speed reducedSpeed = Utils::Utils::GetIntegerMapEntry(arguments, "reducedspeed", DefaultReducedSpeed); if (reducedSpeed > travelSpeed) { reducedSpeed = travelSpeed; } Speed creepingSpeed = Utils::Utils::GetIntegerMapEntry(arguments, "creepingspeed", DefaultCreepingSpeed); if (creepingSpeed > reducedSpeed) { creepingSpeed = reducedSpeed; } const Propulsion propulsion = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "propulsion", PropulsionOther)); const TrainType type = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "type", TrainTypeOther)); vector locoFunctions; DataModel::LocoFunctionEntry locoFunctionEntry; for (DataModel::LocoFunctionNr nr = 0; nr < NumberOfLocoFunctions; ++nr) { string nrString = "f" + to_string(nr) + "_"; locoFunctionEntry.nr = nr; locoFunctionEntry.type = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, nrString + "type", DataModel::LocoFunctionTypeNone)); if (locoFunctionEntry.type == DataModel::LocoFunctionTypeNone) { continue; } locoFunctionEntry.icon = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, nrString + "icon", DataModel::LocoFunctionIconNone)); if (locoFunctionEntry.type == DataModel::LocoFunctionTypeTimer) { locoFunctionEntry.timer = Utils::Utils::GetIntegerMapEntry(arguments, nrString + "timer", 1); if (locoFunctionEntry.timer == 0) { locoFunctionEntry.timer = 1; } } else { locoFunctionEntry.timer = 0; } locoFunctions.push_back(locoFunctionEntry); } string result; if (!manager.LocoSave(locoId, name, controlId, matchKey, protocol, address, serverAddress, length, pushpull, maxSpeed, travelSpeed, reducedSpeed, creepingSpeed, propulsion, type, locoFunctions, result)) { ReplyResponse(ResponseError, result); return; } ReplyResponse(ResponseInfo, Languages::TextLocoSaved, name); } void WebClient::HandleMultipleUnitSave(__attribute__((unused)) const map& arguments) { const MultipleUnitID multipleUnitId = Utils::Utils::GetIntegerMapEntry(arguments, "multipleunit", MultipleUnitNone); const string name = Utils::Utils::GetStringMapEntry(arguments, "name"); const ControlID controlId = Utils::Utils::GetIntegerMapEntry(arguments, "control", ControlIdNone); const string matchKey = Utils::Utils::GetStringMapEntry(arguments, "matchkey"); const Address address = Utils::Utils::GetIntegerMapEntry(arguments, "address", AddressDefault); const Address serverAddress = Utils::Utils::GetIntegerMapEntry(arguments, "serveraddress", AddressNone); const Length length = Utils::Utils::GetIntegerMapEntry(arguments, "length", 0); const bool pushpull = Utils::Utils::GetBoolMapEntry(arguments, "pushpull", false); const Speed maxSpeed = Utils::Utils::GetIntegerMapEntry(arguments, "maxspeed", MaxSpeed); Speed travelSpeed = Utils::Utils::GetIntegerMapEntry(arguments, "travelspeed", DefaultTravelSpeed); if (travelSpeed > maxSpeed) { travelSpeed = maxSpeed; } Speed reducedSpeed = Utils::Utils::GetIntegerMapEntry(arguments, "reducedspeed", DefaultReducedSpeed); if (reducedSpeed > travelSpeed) { reducedSpeed = travelSpeed; } Speed creepingSpeed = Utils::Utils::GetIntegerMapEntry(arguments, "creepingspeed", DefaultCreepingSpeed); if (creepingSpeed > reducedSpeed) { creepingSpeed = reducedSpeed; } const TrainType type = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "type", TrainTypeOther)); vector locoFunctions; DataModel::LocoFunctionEntry locoFunctionEntry; for (DataModel::LocoFunctionNr nr = 0; nr < NumberOfLocoFunctions; ++nr) { string nrString = "f" + to_string(nr) + "_"; locoFunctionEntry.nr = nr; locoFunctionEntry.type = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, nrString + "type", DataModel::LocoFunctionTypeNone)); if (locoFunctionEntry.type == DataModel::LocoFunctionTypeNone) { continue; } locoFunctionEntry.icon = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, nrString + "icon", DataModel::LocoFunctionIconNone)); if (locoFunctionEntry.type == DataModel::LocoFunctionTypeTimer) { locoFunctionEntry.timer = Utils::Utils::GetIntegerMapEntry(arguments, nrString + "timer", 1); if (locoFunctionEntry.timer == 0) { locoFunctionEntry.timer = 1; } } else { locoFunctionEntry.timer = 0; } locoFunctions.push_back(locoFunctionEntry); } vector slaveIDs = WebClientStatic::InterpretSlaveData("slave", arguments); vector slaves = WebClientStatic::ConvertSlaveIDVectorToRelation(manager, multipleUnitId, slaveIDs); string result; if (!manager.MultipleUnitSave(multipleUnitId, name, controlId, matchKey, address, serverAddress, length, pushpull, maxSpeed, travelSpeed, reducedSpeed, creepingSpeed, type, locoFunctions, slaves, result)) { ReplyResponse(ResponseError, result); return; } ReplyResponse(ResponseInfo, Languages::TextMultipleUnitSaved, name); } void WebClient::HandleLocoList() { HtmlTag content; content.AddChildTag(HtmlTag("h1").AddContent(Languages::TextLocos)); HtmlTag table("table"); const map locoList = manager.LocoConfigByName(); map locoArgument; for (auto& loco : locoList) { const LocoConfig& locoConfig = loco.second; HtmlTag row("tr"); row.AddChildTag(HtmlTag("td").AddContent(loco.first)); row.AddChildTag(HtmlTag("td").AddContent(ProtocolName(locoConfig.GetProtocol()))); row.AddChildTag(HtmlTag("td").AddContent(to_string(locoConfig.GetAddress()))); const LocoID locoId = locoConfig.GetLocoId(); const string& locoIdString = to_string(locoId); locoArgument["loco"] = locoIdString; if (locoId == LocoNone) { locoArgument["control"] = to_string(locoConfig.GetControlId()); locoArgument["matchkey"] = locoConfig.GetMatchKey(); row.AddChildTag(HtmlTag("td").AddChildTag(HtmlTagButtonPopupWide(Languages::TextImport, "locoedit_list_0", locoArgument))); row.AddChildTag(HtmlTag("td").AddContent(" ")); row.AddChildTag(HtmlTag("td").AddContent(" ")); } else { row.AddChildTag(HtmlTag("td").AddChildTag(HtmlTagButtonPopupWide(Languages::TextEdit, "locoedit_list_" + locoIdString, locoArgument))); row.AddChildTag(HtmlTag("td").AddChildTag(HtmlTagButtonPopupWide(Languages::TextDelete, "locoaskdelete_" + locoIdString, locoArgument))); if (loco.second.IsInUse()) { row.AddChildTag(HtmlTag("td").AddChildTag(HtmlTagButtonCommandWide(Languages::TextRelease, "locorelease_" + locoIdString, locoArgument, "hideElement('b_locorelease_" + locoIdString + "');"))); } else { row.AddChildTag(HtmlTag("td").AddContent(" ")); } } table.AddChildTag(row); } content.AddChildTag(HtmlTag("div").AddClass("popup_content").AddChildTag(table)); content.AddChildTag(HtmlTagButtonCancel()); content.AddChildTag(HtmlTagButtonPopupWide(Languages::TextNew, "locoedit_0")); ReplyHtmlWithHeader(content); } void WebClient::HandleMultipleUnitList() { HtmlTag content; content.AddChildTag(HtmlTag("h1").AddContent(Languages::TextMultipleUnits)); HtmlTag table("table"); const map multipleUnitList = manager.MultipleUnitConfigByName(); map multipleUnitArgument; for (auto& multipleUnit : multipleUnitList) { const LocoConfig& locoConfig = multipleUnit.second; HtmlTag row("tr"); row.AddChildTag(HtmlTag("td").AddContent(multipleUnit.first)); const MultipleUnitID multipleUnitId = locoConfig.GetLocoId(); const string& multipleUnitIdString = to_string(multipleUnitId); multipleUnitArgument["multipleunit"] = multipleUnitIdString; if (multipleUnitId == LocoNone) { multipleUnitArgument["control"] = to_string(locoConfig.GetControlId()); multipleUnitArgument["matchkey"] = locoConfig.GetMatchKey(); row.AddChildTag(HtmlTag("td").AddChildTag(HtmlTagButtonPopupWide(Languages::TextImport, "multipleunitedit_list_" + multipleUnitIdString, multipleUnitArgument))); row.AddChildTag(HtmlTag("td").AddContent(" ")); row.AddChildTag(HtmlTag("td").AddContent(" ")); } else { row.AddChildTag(HtmlTag("td").AddChildTag(HtmlTagButtonPopupWide(Languages::TextEdit, "multipleunitedit_list_" + multipleUnitIdString, multipleUnitArgument))); row.AddChildTag(HtmlTag("td").AddChildTag(HtmlTagButtonPopupWide(Languages::TextDelete, "multipleunitaskdelete_" + multipleUnitIdString, multipleUnitArgument))); if (multipleUnit.second.IsInUse()) { row.AddChildTag(HtmlTag("td").AddChildTag(HtmlTagButtonCommandWide(Languages::TextRelease, "multipleunitrelease_" + multipleUnitIdString, multipleUnitArgument, "hideElement('b_locorelease_" + multipleUnitIdString + "');"))); } else { row.AddChildTag(HtmlTag("td").AddContent(" ")); } } table.AddChildTag(row); } content.AddChildTag(HtmlTag("div").AddClass("popup_content").AddChildTag(table)); content.AddChildTag(HtmlTagButtonCancel()); content.AddChildTag(HtmlTagButtonPopupWide(Languages::TextNew, "multipleunitedit_0")); ReplyHtmlWithHeader(content); } void WebClient::HandleLocoAskDelete(const map& arguments) { LocoID locoID = Utils::Utils::GetIntegerMapEntry(arguments, "loco", LocoNone); if (locoID == LocoNone) { ReplyHtmlWithHeaderAndParagraph(Languages::TextLocoDoesNotExist); return; } const DataModel::Loco* loco = manager.GetLoco(locoID); if (loco == nullptr) { ReplyHtmlWithHeaderAndParagraph(Languages::TextLocoDoesNotExist); return; } HtmlTag content; const string& locoName = loco->GetName(); content.AddContent(HtmlTag("h1").AddContent(Languages::TextDeleteLoco)); content.AddContent(HtmlTag("p").AddContent(Languages::TextAreYouSureToDelete, locoName)); content.AddContent(HtmlTag("form").AddId("editform") .AddContent(HtmlTagInputHidden("cmd", "locodelete")) .AddContent(HtmlTagInputHidden("loco", to_string(locoID)) )); content.AddContent(HtmlTagButtonCancel()); content.AddContent(HtmlTagButtonOK()); ReplyHtmlWithHeader(content); } void WebClient::HandleMultipleUnitAskDelete(const map& arguments) { MultipleUnitID multipleUnitID = Utils::Utils::GetIntegerMapEntry(arguments, "multipleunit", MultipleUnitNone); if (multipleUnitID == MultipleUnitNone) { ReplyHtmlWithHeaderAndParagraph(Languages::TextMultipleUnitDoesNotExist); return; } const DataModel::MultipleUnit* multipleUnit = manager.GetMultipleUnit(multipleUnitID); if (multipleUnit == nullptr) { ReplyHtmlWithHeaderAndParagraph(Languages::TextMultipleUnitDoesNotExist); return; } HtmlTag content; const string& multipleUnitName = multipleUnit->GetName(); content.AddContent(HtmlTag("h1").AddContent(Languages::TextDeleteMultipleUnit)); content.AddContent(HtmlTag("p").AddContent(Languages::TextAreYouSureToDelete, multipleUnitName)); content.AddContent(HtmlTag("form").AddId("editform") .AddContent(HtmlTagInputHidden("cmd", "multipleunitdelete")) .AddContent(HtmlTagInputHidden("multipleunit", to_string(multipleUnitID)) )); content.AddContent(HtmlTagButtonCancel()); content.AddContent(HtmlTagButtonOK()); ReplyHtmlWithHeader(content); } void WebClient::HandleLocoDelete(const map& arguments) { LocoID locoID = Utils::Utils::GetIntegerMapEntry(arguments, "loco", LocoNone); const DataModel::Loco* loco = manager.GetLoco(locoID); if (loco == nullptr) { ReplyResponse(ResponseError, Languages::TextLocoDoesNotExist); return; } string name = loco->GetName(); string result; if (!manager.LocoDelete(locoID, result)) { ReplyResponse(ResponseError, result); return; } ReplyResponse(ResponseInfo, Languages::TextLocoDeleted, name); } void WebClient::HandleMultipleUnitDelete(const map& arguments) { MultipleUnitID multipleUnitID = Utils::Utils::GetIntegerMapEntry(arguments, "multipleunit", MultipleUnitNone); const DataModel::MultipleUnit* multipleUnit = manager.GetMultipleUnit(multipleUnitID); if (multipleUnit == nullptr) { ReplyResponse(ResponseError, Languages::TextMultipleUnitDoesNotExist); return; } string name = multipleUnit->GetName(); string result; if (!manager.MultipleUnitDelete(multipleUnitID, result)) { ReplyResponse(ResponseError, result); return; } ReplyResponse(ResponseInfo, Languages::TextMultipleUnitDeleted, name); } HtmlTag WebClient::HtmlTagLayerSelector(const LayerID layerID) const { map options = manager.LayerListByNameWithFeedback(); return HtmlTagSelect("layer", options, layerID).AddAttribute("onchange", "loadLayout();"); } void WebClient::HandleLayout(const map& arguments) { LayerID layer = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "layer", CHAR_MIN)); HtmlTag content; if (layer < LayerUndeletable) { const map& feedbacks = manager.FeedbackList(); for (auto& feedback : feedbacks) { if (feedback.second->GetControlID() != -layer) { continue; } content.AddChildTag(HtmlTagFeedback(feedback.second, true)); } ReplyHtmlWithHeader(content); return; } const map& texts = manager.TextList(); for (auto& text : texts) { if (text.second->IsVisibleOnLayer(layer) == false) { continue; } content.AddChildTag(HtmlTagText(text.second)); } const map& accessories = manager.AccessoryList(); for (auto& accessory : accessories) { if (accessory.second->IsVisibleOnLayer(layer) == false) { continue; } content.AddChildTag(HtmlTagAccessory(accessory.second)); } const map& switches = manager.SwitchList(); for (auto& mySwitch : switches) { if (mySwitch.second->IsVisibleOnLayer(layer) == false) { continue; } content.AddChildTag(HtmlTagSwitch(mySwitch.second)); } const map& tracks = manager.TrackList(); for (auto& track : tracks) { if (track.second->IsVisibleOnLayer(layer) == false) { continue; } content.AddChildTag(HtmlTagTrack(manager, track.second)); } const map& routes = manager.RouteList(); for (auto& route : routes) { if (route.second->IsVisibleOnLayer(layer) == false) { continue; } content.AddChildTag(HtmlTagRoute(route.second)); } const map& feedbacks = manager.FeedbackList(); for (auto& feedback : feedbacks) { if (feedback.second->IsVisibleOnLayer(layer) == false) { continue; } content.AddChildTag(HtmlTagFeedback(feedback.second)); } const map& signals = manager.SignalList(); for (auto& signal : signals) { if (signal.second->IsVisibleOnLayer(layer) == false) { continue; } content.AddChildTag(HtmlTagSignal(manager, signal.second)); } ReplyHtmlWithHeader(content); } HtmlTag WebClient::HtmlTagControlLoco(ControlID& controlId, const string& objectType, const ObjectID objectID) const { std::map controls = manager.ControlListNames(Hardware::CapabilityLoco); return WebClientStatic::HtmlTagControl(controls, controlId, objectType, objectID); } HtmlTag WebClient::HtmlTagControlMultipleUnit(ControlID& controlId, const string& objectType, const ObjectID objectID) const { std::map controls = manager.ControlListNames(Hardware::CapabilityMultipleUnit); controls[ControlNone] = Languages::GetText(Languages::TextIndependentOfControl); return WebClientStatic::HtmlTagControl(controls, controlId, objectType, objectID); } HtmlTag WebClient::HtmlTagControlAccessory(ControlID& controlID, const string& objectType, const ObjectID objectID) const { std::map controls = manager.ControlListNames(Hardware::CapabilityAccessory); return WebClientStatic::HtmlTagControl(controls, controlID, objectType, objectID); } HtmlTag WebClient::HtmlTagControlFeedback(ControlID& controlId, const string& objectType, const ObjectID objectID) const { std::map controls = manager.ControlListNames(Hardware::CapabilityFeedback); return WebClientStatic::HtmlTagControl(controls, controlId, objectType, objectID); } void WebClient::HandleAccessoryEdit(const map& arguments) { HtmlTag content; AccessoryID accessoryID = Utils::Utils::GetIntegerMapEntry(arguments, "accessory", AccessoryNone); string name = Languages::GetText(Languages::TextNew); ControlID controlId = Utils::Utils::GetIntegerMapEntry(arguments, "control", ControlNone); if (controlId == ControlNone) { controlId = manager.GetPossibleControlForAccessory(); } string matchKey = Utils::Utils::GetStringMapEntry(arguments, "matchkey"); Protocol protocol = ProtocolNone; Address address = AddressDefault; AddressPort port = AddressPortRed; Address serverAddress = AddressNone; DataModel::AccessoryType accessoryType = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "accessorytype", AccessoryTypeDefault)); DataModel::AccessoryType connectionType; LayoutPosition posx = Utils::Utils::GetIntegerMapEntry(arguments, "posx", 0); LayoutPosition posy = Utils::Utils::GetIntegerMapEntry(arguments, "posy", 0); LayoutPosition posz = Utils::Utils::GetIntegerMapEntry(arguments, "posz", LayerUndeletable); LayoutRotation rotation = Utils::Utils::GetIntegerMapEntry(arguments, "rotation", DataModel::LayoutItem::Rotation0); DataModel::AccessoryPulseDuration duration = manager.GetDefaultAccessoryDuration(); bool inverted = false; if (accessoryID > AccessoryNone) { const DataModel::Accessory* accessory = manager.GetAccessory(accessoryID); if (accessory != nullptr) { name = accessory->GetName(); controlId = accessory->GetControlID(); matchKey = accessory->GetMatchKey(); protocol = accessory->GetProtocol(); address = accessory->GetAddress(); port = accessory->GetPort(); serverAddress = accessory->GetServerAddress(); accessoryType = accessory->GetAccessoryType(); posx = accessory->GetPosX(); posy = accessory->GetPosY(); posz = accessory->GetPosZ(); rotation = accessory->GetRotation(); duration = accessory->GetAccessoryPulseDuration(); inverted = accessory->GetInverted(); } } else if (controlId > ControlNone) { // accessory from hardware database const DataModel::AccessoryConfig accessory = manager.GetAccessoryOfConfigByMatchKey(controlId, matchKey); if (accessory.GetControlId() == controlId && accessory.GetMatchKey() == matchKey) { protocol = accessory.GetProtocol(); address = accessory.GetAddress(); name = accessory.GetName(); } } // else new accessory connectionType = static_cast(accessoryType & DataModel::AccessoryTypeConnectionMask); accessoryType = static_cast(accessoryType & DataModel::AccessoryTypeMask); content.AddChildTag(HtmlTag("h1").AddContent(name).AddId("popup_title")); HtmlTag tabMenu("div"); tabMenu.AddChildTag(WebClientStatic::HtmlTagTabMenuItem("main", Languages::TextBasic, true)); tabMenu.AddChildTag(WebClientStatic::HtmlTagTabMenuItem("position", Languages::TextPosition)); content.AddChildTag(tabMenu); HtmlTag formContent; formContent.AddChildTag(HtmlTagInputHidden("cmd", "accessorysave")); formContent.AddChildTag(HtmlTagInputHidden("accessory", to_string(accessoryID))); std::map typeOptions; typeOptions[DataModel::AccessoryTypeDefault] = Languages::TextDefault; typeOptions[DataModel::AccessoryTypeStraight] = Languages::TextStraight; typeOptions[DataModel::AccessoryTypeTurn] = Languages::TextTurn; typeOptions[DataModel::AccessoryTypeDecoupler] = Languages::TextAccessoryTypeDecoupler; typeOptions[DataModel::AccessoryTypeLight] = Languages::TextAccessoryTypeLight; typeOptions[DataModel::AccessoryTypeLightInhouse] = Languages::TextAccessoryTypeLightInhouse; typeOptions[DataModel::AccessoryTypeLightStreet] = Languages::TextAccessoryTypeLightStreet; std::map connectionOptions; connectionOptions[DataModel::AccessoryTypeOnOn] = Languages::TextAccessoryTypeOnOn; connectionOptions[DataModel::AccessoryTypeOnPush] = Languages::TextAccessoryTypeOnPush; connectionOptions[DataModel::AccessoryTypeOnOff] = Languages::TextAccessoryTypeOnOff; HtmlTag mainContent("div"); mainContent.AddId("tab_main"); mainContent.AddClass("tab_content"); mainContent.AddChildTag(HtmlTagInputTextWithLabel("name", Languages::TextName, name).AddAttribute("onkeyup", "updateName();")); mainContent.AddChildTag(HtmlTagSelectWithLabel("accessorytype", Languages::TextType, typeOptions, static_cast(accessoryType & DataModel::AccessoryTypeMask))); mainContent.AddChildTag(HtmlTagSelectWithLabel("connectiontype", Languages::TextConnection, Languages::TextConnectionHint, connectionOptions, connectionType).AddAttribute("onchange", "loadAccessoryAddress('" + to_string(accessoryID) + "')")); mainContent.AddChildTag(HtmlTagControlAccessory(controlId, "accessory", accessoryID)); mainContent.AddChildTag(HtmlTag("div").AddId("select_protocol").AddChildTag(HtmlTagMatchKeyProtocolAccessory(controlId, matchKey, protocol))); mainContent.AddChildTag(HtmlTag("div").AddId("select_address").AddChildTag(WebClientStatic::HtmlTagAccessoryAddress(connectionType, address, port))); mainContent.AddChildTag(WebClientStatic::HtmlTagDuration(duration)); mainContent.AddChildTag(HtmlTagInputCheckboxWithLabel("inverted", Languages::TextInverted, "true", inverted)); if (manager.IsServerEnabled()) { mainContent.AddChildTag(HtmlTagInputIntegerWithLabel("serveraddress", Languages::TextServerAddress, serverAddress, 0, 2044)); } formContent.AddChildTag(mainContent); formContent.AddChildTag(HtmlTagTabPosition(posx, posy, posz, rotation)); content.AddChildTag(HtmlTag("div").AddClass("popup_content").AddChildTag(HtmlTag("form").AddId("editform").AddChildTag(formContent))); content.AddChildTag(HtmlTagButtonCancel()); content.AddChildTag(HtmlTagButtonOK()); ReplyHtmlWithHeader(content); } void WebClient::HandleAccessoryGet(const map& arguments) { AccessoryID accessoryID = Utils::Utils::GetIntegerMapEntry(arguments, "accessory"); const DataModel::Accessory* accessory = manager.GetAccessory(accessoryID); if (accessory == nullptr) { ReplyHtmlWithHeader(HtmlTag()); return; } ReplyHtmlWithHeader(HtmlTagAccessory(accessory)); } void WebClient::HandleAccessorySave(const map& arguments) { const AccessoryID accessoryID = Utils::Utils::GetIntegerMapEntry(arguments, "accessory", AccessoryNone); const string name = Utils::Utils::GetStringMapEntry(arguments, "name"); const ControlID controlId = Utils::Utils::GetIntegerMapEntry(arguments, "control", ControlIdNone); const string matchKey = Utils::Utils::GetStringMapEntry(arguments, "matchkey"); const Protocol protocol = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "protocol", ProtocolNone)); const Address address = Utils::Utils::GetIntegerMapEntry(arguments, "address", AddressDefault); const AddressPort port = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "port", AddressPortRed)); const Address serverAddress = Utils::Utils::GetIntegerMapEntry(arguments, "serveraddress", AddressNone); const DataModel::AccessoryType connectionType = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "connectiontype", AccessoryTypeOnOn)); const DataModel::AccessoryType accessoryType = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "accessorytype", AccessoryTypeDefault) + connectionType); const LayoutPosition posX = Utils::Utils::GetIntegerMapEntry(arguments, "posx", 0); const LayoutPosition posY = Utils::Utils::GetIntegerMapEntry(arguments, "posy", 0); const LayoutPosition posZ = Utils::Utils::GetIntegerMapEntry(arguments, "posz", 0); const LayoutRotation rotation = Utils::Utils::GetIntegerMapEntry(arguments, "rotation", DataModel::LayoutItem::Rotation0); const DataModel::AccessoryPulseDuration duration = Utils::Utils::GetIntegerMapEntry(arguments, "duration", manager.GetDefaultAccessoryDuration()); const bool inverted = Utils::Utils::GetBoolMapEntry(arguments, "inverted"); string result; if (!manager.AccessorySave(accessoryID, name, posX, posY, posZ, rotation, controlId, matchKey, protocol, address, port, serverAddress, accessoryType, duration, inverted, result)) { ReplyResponse(ResponseError, result); return; } ReplyResponse(ResponseInfo, Languages::TextAccessorySaved, name); } void WebClient::HandleAccessoryState(const map& arguments) { const AccessoryID accessoryID = Utils::Utils::GetIntegerMapEntry(arguments, "accessory", AccessoryNone); const DataModel::AccessoryState accessoryState = (Utils::Utils::GetStringMapEntry(arguments, "state", "off").compare("off") == 0 ? DataModel::AccessoryStateOff : DataModel::AccessoryStateOn); manager.AccessoryState(ControlTypeWebServer, accessoryID, accessoryState, false); ReplyHtmlWithHeaderAndParagraph(accessoryState ? Languages::TextAccessoryStateIsGreen : Languages::TextAccessoryStateIsRed, manager.GetAccessoryName(accessoryID)); } void WebClient::HandleAccessoryList() { HtmlTag content; content.AddChildTag(HtmlTag("h1").AddContent(Languages::TextAccessories)); HtmlTag table("table"); const map accessoryList = manager.AccessoryConfigByName(); map accessoryArgument; for (auto& accessory : accessoryList) { const AccessoryConfig& accessoryConfig = accessory.second; HtmlTag row("tr"); row.AddChildTag(HtmlTag("td").AddContent(accessory.first)); row.AddChildTag(HtmlTag("td").AddContent(ProtocolName(accessoryConfig.GetProtocol()))); row.AddChildTag(HtmlTag("td").AddContent(to_string(accessoryConfig.GetAddress()))); AccessoryID accessoryId = accessoryConfig.GetObjectIdentifier().GetObjectID(); const string& accessoryIdString = to_string(accessoryId); accessoryArgument["accessory"] = accessoryIdString; if (accessoryId == AccessoryNone) { accessoryArgument["control"] = to_string(accessoryConfig.GetControlId()); accessoryArgument["matchkey"] = accessoryConfig.GetMatchKey(); row.AddChildTag(HtmlTag("td").AddChildTag(HtmlTagButtonPopupWide(Languages::TextImport, "accessoryedit_list_" + accessoryIdString, accessoryArgument))); } else { row.AddChildTag(HtmlTag("td").AddChildTag(HtmlTagButtonPopupWide(Languages::TextEdit, "accessoryedit_list_" + accessoryIdString, accessoryArgument))); row.AddChildTag(HtmlTag("td").AddChildTag(HtmlTagButtonPopupWide(Languages::TextDelete, "accessoryaskdelete_" + accessoryIdString, accessoryArgument))); if (accessoryConfig.IsInUse()) { row.AddChildTag(HtmlTag("td").AddChildTag(HtmlTagButtonCommandWide(Languages::TextRelease, "accessoryrelease_" + accessoryIdString, accessoryArgument, "hideElement('b_accessoryrelease_" + accessoryIdString + "');"))); } } table.AddChildTag(row); } content.AddChildTag(HtmlTag("div").AddClass("popup_content").AddChildTag(table)); content.AddChildTag(HtmlTagButtonCancel()); content.AddChildTag(HtmlTagButtonPopupWide(Languages::TextNew, "accessoryedit_0")); ReplyHtmlWithHeader(content); } void WebClient::HandleAccessoryAskDelete(const map& arguments) { AccessoryID accessoryID = Utils::Utils::GetIntegerMapEntry(arguments, "accessory", AccessoryNone); if (accessoryID == AccessoryNone) { ReplyHtmlWithHeaderAndParagraph(Languages::TextAccessoryDoesNotExist); return; } const DataModel::Accessory* accessory = manager.GetAccessory(accessoryID); if (accessory == nullptr) { ReplyHtmlWithHeaderAndParagraph(Languages::TextAccessoryDoesNotExist); return; } HtmlTag content; const string& accessoryName = accessory->GetName(); content.AddContent(HtmlTag("h1").AddContent(Languages::TextDeleteAccessory)); content.AddContent(HtmlTag("p").AddContent(Languages::TextAreYouSureToDelete, accessoryName)); content.AddContent(HtmlTag("form").AddId("editform") .AddContent(HtmlTagInputHidden("cmd", "accessorydelete")) .AddContent(HtmlTagInputHidden("accessory", to_string(accessoryID)) )); content.AddContent(HtmlTagButtonCancel()); content.AddContent(HtmlTagButtonOK()); ReplyHtmlWithHeader(content); } void WebClient::HandleAccessoryDelete(const map& arguments) { AccessoryID accessoryID = Utils::Utils::GetIntegerMapEntry(arguments, "accessory", AccessoryNone); const DataModel::Accessory* accessory = manager.GetAccessory(accessoryID); if (accessory == nullptr) { ReplyResponse(ResponseError, Languages::TextAccessoryDoesNotExist); return; } string name = accessory->GetName(); string result; if (!manager.AccessoryDelete(accessoryID, result)) { ReplyResponse(ResponseError, result); return; } ReplyResponse(ResponseInfo, Languages::TextAccessoryDeleted, name); } void WebClient::HandleAccessoryRelease(const map& arguments) { AccessoryID accessoryID = Utils::Utils::GetIntegerMapEntry(arguments, "accessory"); bool ret = manager.AccessoryRelease(accessoryID); ReplyHtmlWithHeaderAndParagraph(ret ? "Accessory released" : "Accessory not released"); } void WebClient::HandleSwitchEdit(const map& arguments) { HtmlTag content; SwitchID switchID = Utils::Utils::GetIntegerMapEntry(arguments, "switch", SwitchNone); ControlID controlId = manager.GetPossibleControlForAccessory(); if (controlId == ControlNone) { controlId = manager.GetPossibleControlForAccessory(); } string matchKey = Utils::Utils::GetStringMapEntry(arguments, "matchkey"); Protocol protocol = ProtocolNone; Address address = AddressDefault; Address serverAddress = AddressNone; string name = Languages::GetText(Languages::TextNew); LayoutPosition posx = Utils::Utils::GetIntegerMapEntry(arguments, "posx", 0); LayoutPosition posy = Utils::Utils::GetIntegerMapEntry(arguments, "posy", 0); LayoutPosition posz = Utils::Utils::GetIntegerMapEntry(arguments, "posz", LayerUndeletable); LayoutRotation rotation = Utils::Utils::GetIntegerMapEntry(arguments, "rotation", DataModel::LayoutItem::Rotation0); DataModel::AccessoryType type = DataModel::SwitchTypeLeft; DataModel::AccessoryPulseDuration duration = manager.GetDefaultAccessoryDuration(); bool inverted = false; if (switchID > SwitchNone) { const DataModel::Switch* mySwitch = manager.GetSwitch(switchID); if (mySwitch != nullptr) { controlId = mySwitch->GetControlID(); matchKey = mySwitch->GetMatchKey(); protocol = mySwitch->GetProtocol(); address = mySwitch->GetAddress(); serverAddress = mySwitch->GetServerAddress(); name = mySwitch->GetName(); posx = mySwitch->GetPosX(); posy = mySwitch->GetPosY(); posz = mySwitch->GetPosZ(); rotation = mySwitch->GetRotation(); type = mySwitch->GetAccessoryType(); duration = mySwitch->GetAccessoryPulseDuration(); inverted = mySwitch->GetInverted(); } } else if (controlId > ControlNone) { // switch from hardware database const DataModel::AccessoryConfig switchConfig = manager.GetAccessoryOfConfigByMatchKey(controlId, matchKey); if (switchConfig.GetControlId() == controlId && switchConfig.GetMatchKey() == matchKey) { protocol = switchConfig.GetProtocol(); address = switchConfig.GetAddress(); name = switchConfig.GetName(); } } // else new switch std::map typeOptions; typeOptions[DataModel::SwitchTypeLeft] = Languages::TextLeft; typeOptions[DataModel::SwitchTypeRight] = Languages::TextRight; typeOptions[DataModel::SwitchTypeThreeWay] = Languages::TextThreeWay; typeOptions[DataModel::SwitchTypeMaerklinLeft] = Languages::TextMaerklinLeft; typeOptions[DataModel::SwitchTypeMaerklinRight] = Languages::TextMaerklinRight; content.AddChildTag(HtmlTag("h1").AddContent(name).AddId("popup_title")); HtmlTag tabMenu("div"); tabMenu.AddChildTag(WebClientStatic::HtmlTagTabMenuItem("main", Languages::TextBasic, true)); tabMenu.AddChildTag(WebClientStatic::HtmlTagTabMenuItem("position", Languages::TextPosition)); content.AddChildTag(tabMenu); HtmlTag formContent; formContent.AddChildTag(HtmlTagInputHidden("cmd", "switchsave")); formContent.AddChildTag(HtmlTagInputHidden("switch", to_string(switchID))); HtmlTag mainContent("div"); mainContent.AddId("tab_main"); mainContent.AddClass("tab_content"); mainContent.AddChildTag(HtmlTagInputTextWithLabel("name", Languages::TextName, name).AddAttribute("onkeyup", "updateName();")); mainContent.AddChildTag(HtmlTagSelectWithLabel("type", Languages::TextType, typeOptions, type)); mainContent.AddChildTag(HtmlTagControlAccessory(controlId, "switch", switchID)); mainContent.AddChildTag(HtmlTag("div").AddId("select_protocol").AddChildTag(HtmlTagMatchKeyProtocolAccessory(controlId, matchKey, protocol))); mainContent.AddChildTag(HtmlTagInputIntegerWithLabel("address", Languages::TextAddress, address, 1, 2044)); mainContent.AddChildTag(WebClientStatic::HtmlTagDuration(duration)); mainContent.AddChildTag(HtmlTagInputCheckboxWithLabel("inverted", Languages::TextInverted, "true", inverted)); if (manager.IsServerEnabled()) { mainContent.AddChildTag(HtmlTagInputIntegerWithLabel("serveraddress", Languages::TextServerAddress, serverAddress, 0, 2044)); } formContent.AddChildTag(mainContent); formContent.AddChildTag(HtmlTagTabPosition(posx, posy, posz, rotation)); content.AddChildTag(HtmlTag("div").AddClass("popup_content").AddChildTag(HtmlTag("form").AddId("editform").AddChildTag(formContent))); content.AddChildTag(HtmlTagButtonCancel()); content.AddChildTag(HtmlTagButtonOK()); ReplyHtmlWithHeader(content); } void WebClient::HandleSwitchSave(const map& arguments) { const SwitchID switchID = Utils::Utils::GetIntegerMapEntry(arguments, "switch", SwitchNone); const string name = Utils::Utils::GetStringMapEntry(arguments, "name"); const ControlID controlId = Utils::Utils::GetIntegerMapEntry(arguments, "control", ControlIdNone); const string matchKey = Utils::Utils::GetStringMapEntry(arguments, "matchkey"); const Protocol protocol = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "protocol", ProtocolNone)); const Address address = Utils::Utils::GetIntegerMapEntry(arguments, "address", AddressDefault); const Address serverAddress = Utils::Utils::GetIntegerMapEntry(arguments, "serveraddress", AddressNone); const LayoutPosition posX = Utils::Utils::GetIntegerMapEntry(arguments, "posx", 0); const LayoutPosition posY = Utils::Utils::GetIntegerMapEntry(arguments, "posy", 0); const LayoutPosition posZ = Utils::Utils::GetIntegerMapEntry(arguments, "posz", 0); const LayoutRotation rotation = Utils::Utils::GetIntegerMapEntry(arguments, "rotation", DataModel::LayoutItem::Rotation0); const DataModel::AccessoryType type = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "type", DataModel::SwitchTypeLeft)); const DataModel::AccessoryPulseDuration duration = Utils::Utils::GetIntegerMapEntry(arguments, "duration", manager.GetDefaultAccessoryDuration()); const bool inverted = Utils::Utils::GetBoolMapEntry(arguments, "inverted"); string result; if (!manager.SwitchSave(switchID, name, posX, posY, posZ, rotation, controlId, matchKey, protocol, address, serverAddress, type, duration, inverted, result)) { ReplyResponse(ResponseError, result); return; } ReplyResponse(ResponseInfo, Languages::TextSwitchSaved, name); } void WebClient::HandleSwitchState(const map& arguments) { SwitchID switchID = Utils::Utils::GetIntegerMapEntry(arguments, "switch", SwitchNone); string switchStateText = Utils::Utils::GetStringMapEntry(arguments, "state", "turnout"); DataModel::AccessoryState switchState; if (switchStateText.compare("turnout") == 0) { switchState = DataModel::SwitchStateTurnout; } else if (switchStateText.compare("third") == 0) { switchState = DataModel::SwitchStateThird; } else { switchState = DataModel::SwitchStateStraight; } manager.SwitchState(ControlTypeWebServer, switchID, switchState, false); ReplyHtmlWithHeaderAndParagraph(switchState ? Languages::TextSwitchStateIsStraight : Languages::TextSwitchStateIsTurnout, manager.GetSwitchName(switchID)); } void WebClient::HandleSwitchList() { HtmlTag content; content.AddChildTag(HtmlTag("h1").AddContent(Languages::TextSwitches)); HtmlTag table("table"); const map switchList = manager.SwitchConfigByName(); map switchArgument; for (auto& mySwitch : switchList) { const AccessoryConfig& switchConfig = mySwitch.second; HtmlTag row("tr"); row.AddChildTag(HtmlTag("td").AddContent(mySwitch.first)); row.AddChildTag(HtmlTag("td").AddContent(ProtocolName(switchConfig.GetProtocol()))); row.AddChildTag(HtmlTag("td").AddContent(to_string(switchConfig.GetAddress()))); SwitchID switchId = switchConfig.GetObjectIdentifier().GetObjectID(); const string& switchIdString = to_string(switchId); switchArgument["switch"] = switchIdString; if (switchId == SwitchNone) { switchArgument["control"] = to_string(switchConfig.GetControlId()); switchArgument["matchkey"] = switchConfig.GetMatchKey(); row.AddChildTag(HtmlTag("td").AddChildTag(HtmlTagButtonPopupWide(Languages::TextImport, "switchedit_list_" + switchIdString, switchArgument))); } else { row.AddChildTag(HtmlTag("td").AddChildTag(HtmlTagButtonPopupWide(Languages::TextEdit, "switchedit_list_" + switchIdString, switchArgument))); row.AddChildTag(HtmlTag("td").AddChildTag(HtmlTagButtonPopupWide(Languages::TextDelete, "switchaskdelete_" + switchIdString, switchArgument))); if (switchConfig.IsInUse()) { row.AddChildTag(HtmlTag("td").AddChildTag(HtmlTagButtonCommandWide(Languages::TextRelease, "switchrelease_" + switchIdString, switchArgument, "hideElement('b_switchrelease_" + switchIdString + "');"))); } } table.AddChildTag(row); } content.AddChildTag(HtmlTag("div").AddClass("popup_content").AddChildTag(table)); content.AddChildTag(HtmlTagButtonCancel()); content.AddChildTag(HtmlTagButtonPopupWide(Languages::TextNew, "switchedit_0")); ReplyHtmlWithHeader(content); } void WebClient::HandleSwitchAskDelete(const map& arguments) { SwitchID switchID = Utils::Utils::GetIntegerMapEntry(arguments, "switch", SwitchNone); if (switchID == SwitchNone) { ReplyHtmlWithHeaderAndParagraph(Languages::TextSwitchDoesNotExist); return; } const DataModel::Switch* mySwitch = manager.GetSwitch(switchID); if (mySwitch == nullptr) { ReplyHtmlWithHeaderAndParagraph(Languages::TextSwitchDoesNotExist); return; } HtmlTag content; const string& switchName = mySwitch->GetName(); content.AddContent(HtmlTag("h1").AddContent(Languages::TextDeleteSwitch)); content.AddContent(HtmlTag("p").AddContent(Languages::TextAreYouSureToDelete, switchName)); content.AddContent(HtmlTag("form").AddId("editform") .AddContent(HtmlTagInputHidden("cmd", "switchdelete")) .AddContent(HtmlTagInputHidden("switch", to_string(switchID)) )); content.AddContent(HtmlTagButtonCancel()); content.AddContent(HtmlTagButtonOK()); ReplyHtmlWithHeader(content); } void WebClient::HandleSwitchDelete(const map& arguments) { SwitchID switchID = Utils::Utils::GetIntegerMapEntry(arguments, "switch", SwitchNone); const DataModel::Switch* mySwitch = manager.GetSwitch(switchID); if (mySwitch == nullptr) { ReplyResponse(ResponseError, Languages::TextSwitchDoesNotExist); return; } string name = mySwitch->GetName(); string result; if (!manager.SwitchDelete(switchID, result)) { ReplyResponse(ResponseError, result); return; } ReplyResponse(ResponseInfo, Languages::TextSwitchDeleted, name); } void WebClient::HandleSwitchGet(const map& arguments) { SwitchID switchID = Utils::Utils::GetIntegerMapEntry(arguments, "switch"); const DataModel::Switch* mySwitch = manager.GetSwitch(switchID); if (mySwitch == nullptr) { ReplyHtmlWithHeader(HtmlTag()); return; } ReplyHtmlWithHeader(HtmlTagSwitch(mySwitch)); } void WebClient::HandleSwitchRelease(const map& arguments) { SwitchID switchID = Utils::Utils::GetIntegerMapEntry(arguments, "switch"); bool ret = manager.SwitchRelease(switchID); ReplyHtmlWithHeaderAndParagraph(ret ? "Switch released" : "Switch not released"); } HtmlTag WebClient::HtmlTagTabPosition(const LayoutPosition posx, const LayoutPosition posy, const LayoutPosition posz, const LayoutRotation rotation, const Visible visible) const { HtmlTag positionContent("div"); positionContent.AddId("tab_position"); positionContent.AddClass("tab_content"); positionContent.AddClass("hidden"); if (visible == DataModel::LayoutItem::VisibleNotRelevant) { positionContent.AddChildTag(HtmlTagPosition(posx, posy, posz)); } else { positionContent.AddChildTag(HtmlTagPosition(posx, posy, posz, visible)); } if (rotation != DataModel::LayoutItem::RotationNotRelevant) { positionContent.AddChildTag(WebClientStatic::HtmlTagRotation(rotation)); } return positionContent; } HtmlTag WebClient::HtmlTagRelationSignalState(const string& name, const SignalID signalId, const DataModel::Relation::Data data) { map stateOptions; Signal* signal = manager.GetSignal(signalId); if (signal != nullptr) { stateOptions = signal->GetStateOptions(); } return HtmlTagSelect(name + "_state", stateOptions, static_cast(data)).AddClass("select_relation_state"); } void WebClient::HandleFeedbackEdit(const map& arguments) { HtmlTag content; FeedbackID feedbackID = Utils::Utils::GetIntegerMapEntry(arguments, "feedback", FeedbackNone); string name = Languages::GetText(Languages::TextNew); ControlID controlId = Utils::Utils::GetIntegerMapEntry(arguments, "controlid", manager.GetPossibleControlForFeedback()); string matchKey = Utils::Utils::GetStringMapEntry(arguments, "matchkey"); FeedbackPin pin = Utils::Utils::GetIntegerMapEntry(arguments, "pin", FeedbackPinNone); DataModel::FeedbackType feedbackType = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "feedbacktype", FeedbackTypeDefault)); RouteID routeId = Utils::Utils::GetIntegerMapEntry(arguments, "route", RouteNone); LayoutPosition posx = Utils::Utils::GetIntegerMapEntry(arguments, "posx", 0); LayoutPosition posy = Utils::Utils::GetIntegerMapEntry(arguments, "posy", 0); LayoutPosition posz = Utils::Utils::GetIntegerMapEntry(arguments, "posz", LayerUndeletable); LayoutRotation rotation = Utils::Utils::GetIntegerMapEntry(arguments, "rotation", DataModel::LayoutItem::Rotation0); DataModel::LayoutItem::Visible visible = static_cast(Utils::Utils::GetBoolMapEntry(arguments, "visible", feedbackID == FeedbackNone && ((posx || posy) && posz >= LayerUndeletable) ? DataModel::LayoutItem::VisibleYes : DataModel::LayoutItem::VisibleNo)); if (posz < LayerUndeletable) { if (controlId == ControlNone) { controlId = -posz; } if (pin == 0) { pin = posy * 16 + posx + (posx > 8 ? 0 : 1); } } bool inverted = false; if (feedbackID > FeedbackNone) { // existing feedback const DataModel::Feedback* feedback = manager.GetFeedback(feedbackID); if (feedback != nullptr) { name = feedback->GetName(); matchKey = feedback->GetMatchKey(); controlId = feedback->GetControlID(); pin = feedback->GetPin(); inverted = feedback->GetInverted(); feedbackType = feedback->GetFeedbackType(); routeId = feedback->GetRouteId(); visible = feedback->GetVisible(); posx = feedback->GetPosX(); posy = feedback->GetPosY(); posz = feedback->GetPosZ(); rotation = feedback->GetRotation(); } } else if (controlId > ControlNone) { // feedback from hardware database const DataModel::FeedbackConfig feedback = manager.GetFeedbackOfConfigByMatchKey(controlId, matchKey); if (feedback.GetControlId() == controlId && feedback.GetMatchKey() == matchKey) { name = feedback.GetName(); pin = feedback.GetPin(); } } // else new feedback content.AddChildTag(HtmlTag("h1").AddContent(name).AddId("popup_title")); HtmlTag tabMenu("div"); tabMenu.AddChildTag(WebClientStatic::HtmlTagTabMenuItem("main", Languages::TextBasic, true)); tabMenu.AddChildTag(WebClientStatic::HtmlTagTabMenuItem("position", Languages::TextPosition)); content.AddChildTag(tabMenu); HtmlTag formContent("form"); formContent.AddId("editform"); formContent.AddChildTag(HtmlTagInputHidden("cmd", "feedbacksave")); formContent.AddChildTag(HtmlTagInputHidden("feedback", to_string(feedbackID))); std::map typeOptions; typeOptions[DataModel::FeedbackTypeDefault] = Languages::TextDefault; typeOptions[DataModel::FeedbackTypeStraight] = Languages::TextStraight; typeOptions[DataModel::FeedbackTypeTurn] = Languages::TextTurn; const std::map routes = manager.RouteListByName(); map routeOptions; routeOptions["-"] = RouteNone; for (auto& track : routes) { routeOptions[track.first] = track.second->GetID(); } HtmlTag mainContent("div"); mainContent.AddId("tab_main"); mainContent.AddClass("tab_content"); mainContent.AddChildTag(HtmlTagInputTextWithLabel("name", Languages::TextName, name).AddAttribute("onkeyup", "updateName();")); mainContent.AddChildTag(HtmlTagControlFeedback(controlId, "feedback", feedbackID)); mainContent.AddChildTag(HtmlTagMatchKeyFeedback(controlId, matchKey)); mainContent.AddChildTag(HtmlTagInputIntegerWithLabel("pin", Languages::TextPin, pin, 1, 4096)); mainContent.AddChildTag(HtmlTagInputCheckboxWithLabel("inverted", Languages::TextInverted, "true", inverted)); mainContent.AddChildTag(HtmlTagSelectWithLabel("feedbacktype", Languages::TextType, typeOptions, feedbackType)); mainContent.AddChildTag(HtmlTagSelectWithLabel("route", Languages::TextExecuteRoute, routeOptions, routeId)); formContent.AddChildTag(mainContent); formContent.AddChildTag(HtmlTagTabPosition(posx, posy, posz, rotation, visible)); content.AddChildTag(HtmlTag("div").AddClass("popup_content").AddChildTag(formContent)); content.AddChildTag(HtmlTagButtonCancel()); content.AddChildTag(HtmlTagButtonOK()); ReplyHtmlWithHeader(content); } void WebClient::HandleFeedbackSave(const map& arguments) { const FeedbackID feedbackID = Utils::Utils::GetIntegerMapEntry(arguments, "feedback", FeedbackNone); const string name = Utils::Utils::GetStringMapEntry(arguments, "name"); const ControlID controlId = Utils::Utils::GetIntegerMapEntry(arguments, "control", ControlIdNone); const string matchKey = Utils::Utils::GetStringMapEntry(arguments, "matchkey"); const FeedbackPin pin = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "pin", FeedbackPinNone)); const bool inverted = Utils::Utils::GetBoolMapEntry(arguments, "inverted"); const DataModel::FeedbackType feedbackType = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "feedbacktype", FeedbackTypeDefault)); const RouteID routeId = Utils::Utils::GetIntegerMapEntry(arguments, "route", RouteNone); const DataModel::LayoutItem::Visible visible = static_cast(Utils::Utils::GetBoolMapEntry(arguments, "visible", DataModel::LayoutItem::VisibleNo)); const LayoutPosition posX = Utils::Utils::GetIntegerMapEntry(arguments, "posx", 0); const LayoutPosition posY = Utils::Utils::GetIntegerMapEntry(arguments, "posy", 0); const LayoutPosition posZ = Utils::Utils::GetIntegerMapEntry(arguments, "posz", 0); const LayoutRotation rotation = Utils::Utils::GetIntegerMapEntry(arguments, "rotation", DataModel::LayoutItem::Rotation0); string result; if (!manager.FeedbackSave(feedbackID, name, visible, posX, posY, posZ, rotation, controlId, matchKey, pin, inverted, feedbackType, routeId, result)) { ReplyResponse(ResponseError, result); return; } ReplyResponse(ResponseInfo, Languages::TextFeedbackSaved, name); } void WebClient::HandleFeedbackState(const map& arguments) { FeedbackID feedbackID = Utils::Utils::GetIntegerMapEntry(arguments, "feedback", FeedbackNone); DataModel::Feedback::FeedbackState state = (Utils::Utils::GetStringMapEntry(arguments, "state", "occupied").compare("occupied") == 0 ? DataModel::Feedback::FeedbackStateOccupied : DataModel::Feedback::FeedbackStateFree); manager.FeedbackState(feedbackID, state); ReplyHtmlWithHeaderAndParagraph(state ? Languages::TextFeedbackStateIsOn : Languages::TextFeedbackStateIsOff, manager.GetFeedbackName(feedbackID)); } void WebClient::HandleFeedbackList() { HtmlTag content; content.AddChildTag(HtmlTag("h1").AddContent(Languages::TextFeedbacks)); HtmlTag table("table"); const map feedbackList = manager.FeedbackConfigByName(); map feedbackArgument; for (auto& feedback : feedbackList) { const DataModel::FeedbackConfig& feedbackConfig = feedback.second; HtmlTag row("tr"); row.AddChildTag(HtmlTag("td").AddContent(feedback.first)); const FeedbackID feedbackId = feedbackConfig.GetFeedbackId(); const string& feedbackIdString = to_string(feedbackId); feedbackArgument["feedback"] = feedbackIdString; if (feedbackId == FeedbackNone) { feedbackArgument["control"] = to_string(feedbackConfig.GetControlId()); feedbackArgument["matchkey"] = feedbackConfig.GetMatchKey(); row.AddChildTag(HtmlTag("td").AddChildTag(HtmlTagButtonPopupWide(Languages::TextImport, "feedbackedit_list_" + feedbackIdString, feedbackArgument))); } else { row.AddChildTag(HtmlTag("td").AddChildTag(HtmlTagButtonPopupWide(Languages::TextEdit, "feedbackedit_list_" + feedbackIdString, feedbackArgument))); row.AddChildTag(HtmlTag("td").AddChildTag(HtmlTagButtonPopupWide(Languages::TextDelete, "feedbackaskdelete_" + feedbackIdString, feedbackArgument))); } table.AddChildTag(row); } content.AddChildTag(HtmlTag("div").AddClass("popup_content").AddChildTag(table)); content.AddChildTag(HtmlTagButtonCancel()); content.AddChildTag(HtmlTagButtonPopupWide(Languages::TextNew, "feedbackedit_0")); ReplyHtmlWithHeader(content); } void WebClient::HandleFeedbackAskDelete(const map& arguments) { FeedbackID feedbackID = Utils::Utils::GetIntegerMapEntry(arguments, "feedback", FeedbackNone); if (feedbackID == FeedbackNone) { ReplyHtmlWithHeaderAndParagraph(Languages::TextFeedbackDoesNotExist); return; } const DataModel::Feedback* feedback = manager.GetFeedback(feedbackID); if (feedback == nullptr) { ReplyHtmlWithHeaderAndParagraph(Languages::TextFeedbackDoesNotExist); return; } HtmlTag content; const string& feedbackName = feedback->GetName(); content.AddContent(HtmlTag("h1").AddContent(Languages::TextDeleteFeedback)); content.AddContent(HtmlTag("p").AddContent(Languages::TextAreYouSureToDelete, feedbackName)); content.AddContent(HtmlTag("form").AddId("editform") .AddContent(HtmlTagInputHidden("cmd", "feedbackdelete")) .AddContent(HtmlTagInputHidden("feedback", to_string(feedbackID)) )); content.AddContent(HtmlTagButtonCancel()); content.AddContent(HtmlTagButtonOK()); ReplyHtmlWithHeader(content); } void WebClient::HandleFeedbackDelete(const map& arguments) { FeedbackID feedbackID = Utils::Utils::GetIntegerMapEntry(arguments, "feedback", FeedbackNone); const DataModel::Feedback* feedback = manager.GetFeedback(feedbackID); if (feedback == nullptr) { ReplyResponse(ResponseError, Languages::TextFeedbackDoesNotExist); return; } string name = feedback->GetName(); string result; if (!manager.FeedbackDelete(feedbackID, result)) { ReplyResponse(ResponseError, result); return; } ReplyResponse(ResponseInfo, Languages::TextFeedbackDeleted, name); } void WebClient::HandleFeedbackGet(const map& arguments) { FeedbackID feedbackID = Utils::Utils::GetIntegerMapEntry(arguments, "feedback", FeedbackNone); const DataModel::Feedback* feedback = manager.GetFeedback(feedbackID); if (feedback == nullptr) { ReplyHtmlWithHeader(HtmlTag()); return; } LayerID layer = Utils::Utils::GetIntegerMapEntry(arguments, "layer", LayerNone); if (feedback->GetControlID() == -layer) { ReplyHtmlWithHeader(HtmlTagFeedback(feedback, true)); return; } if (layer < LayerNone || feedback->GetVisible() == DataModel::LayoutItem::VisibleNo) { ReplyHtmlWithHeader(HtmlTag()); return; } ReplyHtmlWithHeader(HtmlTagFeedback(feedback)); } void WebClient::HandleLocoSelector(const map& arguments) { const unsigned int selector = Utils::Utils::GetIntegerMapEntry(arguments, "selector", 1); const LocoID locoID = Utils::Utils::GetIntegerMapEntry(arguments, "loco"); ReplyHtmlWithHeader(HtmlTagLocoSelector(to_string(selector), locoID)); } void WebClient::HandleLayerSelector(const map& arguments) { const LayerID layerID = Utils::Utils::GetIntegerMapEntry(arguments, "layer"); ReplyHtmlWithHeader(HtmlTagLayerSelector(layerID)); } void WebClient::HandleSettingsEdit() { const StartupInitLocos startupInitLocos = manager.GetStartupInitLocos(); const DataModel::AccessoryPulseDuration defaultAccessoryDuration = manager.GetDefaultAccessoryDuration(); const bool executeAccessory = manager.GetExecuteAccessory(); const bool autoAddFeedback = manager.GetAutoAddFeedback(); const bool stopOnFeedbackInFreeTrack = manager.GetStopOnFeedbackInFreeTrack(); const DataModel::SelectRouteApproach selectRouteApproach = manager.GetSelectRouteApproach(); const DataModel::LocoBase::NrOfTracksToReserve nrOfTracksToReserve = manager.GetNrOfTracksToReserve(); HtmlTag content; content.AddChildTag(HtmlTag("h1").AddContent(Languages::TextSettings).AddId("popup_title")); HtmlTag tabMenu("div"); tabMenu.AddChildTag(WebClientStatic::HtmlTagTabMenuItem("basic", Languages::TextBasic, true)); tabMenu.AddChildTag(WebClientStatic::HtmlTagTabMenuItem("startup", Languages::TextStartup)); tabMenu.AddChildTag(WebClientStatic::HtmlTagTabMenuItem("feedback", Languages::TextFeedback)); tabMenu.AddChildTag(WebClientStatic::HtmlTagTabMenuItem("accessory", Languages::TextAccessory)); tabMenu.AddChildTag(WebClientStatic::HtmlTagTabMenuItem("automode", Languages::TextAutomode)); content.AddChildTag(tabMenu); HtmlTag formContent("form"); formContent.AddId("editform"); formContent.AddChildTag(HtmlTagInputHidden("cmd", "settingssave")); HtmlTag basicContent("div"); basicContent.AddId("tab_basic"); basicContent.AddClass("tab_content"); basicContent.AddChildTag(WebClientStatic::HtmlTagLanguage()); basicContent.AddChildTag(WebClientStatic::HtmlTagLogLevel()); formContent.AddChildTag(basicContent); HtmlTag startupContent("div"); startupContent.AddId("tab_startup"); startupContent.AddClass("tab_content"); startupContent.AddClass("hidden"); startupContent.AddChildTag(WebClientStatic::HtmlTagStartupLocos(startupInitLocos)); formContent.AddChildTag(startupContent); HtmlTag accessoryContent("div"); accessoryContent.AddId("tab_accessory"); accessoryContent.AddClass("tab_content"); accessoryContent.AddClass("hidden"); accessoryContent.AddChildTag(WebClientStatic::HtmlTagDuration(defaultAccessoryDuration, Languages::TextDefaultSwitchingDuration)); accessoryContent.AddChildTag(WebClientStatic::HtmlTagSelectExecuteAccessory(executeAccessory)); formContent.AddChildTag(accessoryContent); HtmlTag feedbackContent("div"); feedbackContent.AddId("tab_feedback"); feedbackContent.AddClass("tab_content"); feedbackContent.AddClass("hidden"); feedbackContent.AddChildTag(HtmlTagInputCheckboxWithLabel("autoaddfeedback", Languages::TextAutomaticallyAddUnknownFeedbacks, "autoaddfeedback", autoAddFeedback)); feedbackContent.AddChildTag(HtmlTagInputCheckboxWithLabel("stoponfeedbackinfreetrack", Languages::TextStopOnFeedbackInFreeTrack, "stoponfeedbackinfreetrack", stopOnFeedbackInFreeTrack)); formContent.AddChildTag(feedbackContent); HtmlTag automodeContent("div"); automodeContent.AddId("tab_automode"); automodeContent.AddClass("tab_content"); automodeContent.AddClass("hidden"); automodeContent.AddChildTag(WebClientStatic::HtmlTagSelectSelectRouteApproach(selectRouteApproach, false)); automodeContent.AddChildTag(WebClientStatic::HtmlTagNrOfTracksToReserve(nrOfTracksToReserve)); formContent.AddChildTag(automodeContent); content.AddChildTag(HtmlTag("div").AddClass("popup_content").AddChildTag(formContent)); content.AddChildTag(HtmlTagButtonCancel()); content.AddChildTag(HtmlTagButtonOK()); ReplyHtmlWithHeader(content); } void WebClient::HandleSettingsSave(const map& arguments) { const Languages::Language language = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "language", Languages::EN)); const Logger::Logger::Level logLevel = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "loglevel", Logger::Logger::LevelInfo)); const StartupInitLocos startupInitLocos = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "startupinitlocos", StartupInitLocosAll)); const DataModel::AccessoryPulseDuration defaultAccessoryDuration = Utils::Utils::GetIntegerMapEntry(arguments, "duration", manager.GetDefaultAccessoryDuration()); const bool executeAccessory = Utils::Utils::GetBoolMapEntry(arguments, "executeaccessory", manager.GetExecuteAccessory()); const bool autoAddFeedback = Utils::Utils::GetBoolMapEntry(arguments, "autoaddfeedback", manager.GetAutoAddFeedback()); const bool stopOnFeedbackInFreeTrack = Utils::Utils::GetBoolMapEntry(arguments, "stoponfeedbackinfreetrack", manager.GetStopOnFeedbackInFreeTrack()); const DataModel::SelectRouteApproach selectRouteApproach = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "selectrouteapproach", DataModel::SelectRouteRandom)); const DataModel::Loco::NrOfTracksToReserve nrOfTracksToReserve = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "nroftrackstoreserve", DataModel::Loco::ReserveOne)); manager.SettingsSave(language, startupInitLocos, defaultAccessoryDuration, autoAddFeedback, stopOnFeedbackInFreeTrack, executeAccessory, selectRouteApproach, nrOfTracksToReserve, logLevel); ReplyResponse(ResponseInfo, Languages::TextSettingsSaved); } void WebClient::HandleTimestamp(__attribute__((unused)) const map& arguments) { #ifdef __CYGWIN__ ReplyHtmlWithHeaderAndParagraph(Languages::TextTimestampNotSet); #else const time_t timestamp = Utils::Utils::GetIntegerMapEntry(arguments, "timestamp", 0); if (timestamp == 0) { ReplyHtmlWithHeaderAndParagraph(Languages::TextTimestampNotSet); return; } struct timeval tv; int ret = gettimeofday(&tv, nullptr); if (ret != 0 || tv.tv_sec > GetVersionInfoCompileTimestamp()) { ReplyHtmlWithHeaderAndParagraph(Languages::TextTimestampAlreadySet); return; } tv.tv_sec = timestamp; ret = settimeofday(&tv, nullptr); if (ret != 0) { ReplyHtmlWithHeaderAndParagraph(Languages::TextTimestampNotSet); return; } ReplyHtmlWithHeaderAndParagraph(Languages::TextTimestampSet); #endif } void WebClient::HandleControlArguments(const map& arguments) { HardwareType hardwareType = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "hardwaretype")); ReplyHtmlWithHeader(WebClientStatic::HtmlTagControlArguments(hardwareType)); } HtmlTag WebClient::HtmlTagProgramModeSelector(const ControlID controlID, ProgramMode& mode) const { Hardware::Capabilities capabilities = manager.GetCapabilities(controlID); map programModeOptions; if (capabilities & (Hardware::CapabilityProgramDccDirectRead | Hardware::CapabilityProgramDccDirectWrite)) { programModeOptions[ProgramModeDccDirect] = Languages::TextProgramModeDccDirect; if (mode == ProgramModeNone) { mode = ProgramModeDccDirect; } } if (capabilities & (Hardware::CapabilityProgramDccPomLocoRead | Hardware::CapabilityProgramDccPomLocoWrite)) { programModeOptions[ProgramModeDccPomLoco] = Languages::TextProgramModeDccPomLoco; if (mode == ProgramModeNone) { mode = ProgramModeDccPomLoco; } } if (capabilities & (Hardware::CapabilityProgramDccPomAccessoryRead | Hardware::CapabilityProgramDccPomAccessoryWrite)) { programModeOptions[ProgramModeDccPomAccessory] = Languages::TextProgramModeDccPomAccessory; if (mode == ProgramModeNone) { mode = ProgramModeDccPomAccessory; } } if (capabilities & Hardware::CapabilityProgramMmWrite) { programModeOptions[ProgramModeMm] = Languages::TextProgramModeMm; if (mode == ProgramModeNone) { mode = ProgramModeMm; } } if (capabilities & Hardware::CapabilityProgramMmPomWrite) { programModeOptions[ProgramModeMmPom] = Languages::TextProgramModeMmPom; if (mode == ProgramModeNone) { mode = ProgramModeMmPom; } } if (capabilities & (Hardware::CapabilityProgramMfxRead | Hardware::CapabilityProgramMfxWrite)) { programModeOptions[ProgramModeMfx] = Languages::TextProgramModeMfx; if (mode == ProgramModeNone) { mode = ProgramModeMfx; } } if (capabilities & (Hardware::CapabilityProgramDccPageRead | Hardware::CapabilityProgramDccPageWrite)) { programModeOptions[ProgramModeDccPage] = Languages::TextProgramModeDccPage; if (mode == ProgramModeNone) { mode = ProgramModeDccPage; } } if (capabilities & (Hardware::CapabilityProgramDccRegisterRead | Hardware::CapabilityProgramDccRegisterWrite)) { programModeOptions[ProgramModeDccRegister] = Languages::TextProgramModeDccRegister; if (mode == ProgramModeNone) { mode = ProgramModeDccRegister; } } return HtmlTagSelectWithLabel("moderaw", Languages::TextProgramMode, programModeOptions, mode).AddAttribute("onchange", "onChangeProgramModeSelector();"); } HtmlTag WebClient::HtmlTagCvFields(const ControlID controlID, const ProgramMode programMode) const { HtmlTag content("div"); content.AddId("cv_fields"); switch (programMode) { case ProgramModeMmPom: content.AddChildTag(HtmlTagInputIntegerWithLabel("addressraw", Languages::TextAddress, 1, 1, 0xFF)); break; case ProgramModeMfx: case ProgramModeDccPomLoco: case ProgramModeDccPomAccessory: content.AddChildTag(HtmlTagInputIntegerWithLabel("addressraw", Languages::TextAddress, 1, 1, 0x4000)); break; default: content.AddChildTag(HtmlTagInputHidden("addressraw", "0")); break; } switch (programMode) { case ProgramModeMfx: content.AddChildTag(HtmlTagInputIntegerWithLabel("indexraw", Languages::TextIndex, 0, 0, 0x3F)); break; default: content.AddChildTag(HtmlTagInputHidden("indexraw", "0")); break; } switch (programMode) { case ProgramModeMm: case ProgramModeMmPom: content.AddChildTag(HtmlTagInputIntegerWithLabel("cvraw", Languages::TextCV, 1, 1, 256)); break; default: content.AddChildTag(HtmlTagInputIntegerWithLabel("cvraw", Languages::TextCV, 1, 1, 1024)); break; } content.AddChildTag(HtmlTagInputIntegerWithLabel("valueraw", Languages::TextValue, 0, 0, 255)); content.AddChildTag(HtmlTag("br")); content.AddChildTag(HtmlTagInput8BitValueWithLabel()); content.AddChildTag(HtmlTag("br")); Hardware::Capabilities capabilities = manager.GetCapabilities(controlID); if (((programMode == ProgramModeMm) && (capabilities & Hardware::CapabilityProgramMmWrite)) || ((programMode == ProgramModeMmPom) && (capabilities & Hardware::CapabilityProgramMmPomWrite)) || ((programMode == ProgramModeMfx) && (capabilities & Hardware::CapabilityProgramMfxWrite)) || ((programMode == ProgramModeDccRegister) && (capabilities & Hardware::CapabilityProgramDccRegisterWrite)) || ((programMode == ProgramModeDccPage) && (capabilities & Hardware::CapabilityProgramDccPageWrite)) || ((programMode == ProgramModeDccDirect) && (capabilities & Hardware::CapabilityProgramDccDirectWrite)) || ((programMode == ProgramModeDccPomLoco) && (capabilities & Hardware::CapabilityProgramDccPomLocoWrite)) || ((programMode == ProgramModeDccPomAccessory) && (capabilities & Hardware::CapabilityProgramDccPomAccessoryWrite))) { HtmlTagButton writeButton(Languages::TextWrite, "programwrite"); writeButton.AddAttribute("onclick", "onClickProgramWrite();return false;"); writeButton.AddClass("wide_button"); content.AddChildTag(writeButton); } if (((programMode == ProgramModeMfx) && (capabilities & Hardware::CapabilityProgramMfxRead)) || ((programMode == ProgramModeDccRegister) && (capabilities & Hardware::CapabilityProgramDccRegisterRead)) || ((programMode == ProgramModeDccPage) && (capabilities & Hardware::CapabilityProgramDccPageRead)) || ((programMode == ProgramModeDccDirect) && (capabilities & Hardware::CapabilityProgramDccDirectRead)) || ((programMode == ProgramModeDccPomLoco) && (capabilities & Hardware::CapabilityProgramDccPomLocoRead)) || ((programMode == ProgramModeDccPomAccessory) && (capabilities & Hardware::CapabilityProgramDccPomAccessoryRead))) { HtmlTagButton readButton(Languages::TextRead, "programread"); readButton.AddAttribute("onclick", "onClickProgramRead();return false;"); readButton.AddClass("wide_button"); content.AddChildTag(readButton); } return content; } void WebClient::HandleCvFields(const map& arguments) { ControlID controlID = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "control", ControlNone)); ProgramMode programMode = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "mode", ProgramModeNone)); ReplyHtmlWithHeader(HtmlTagCvFields(controlID, programMode)); } HtmlTag WebClient::HtmlTagInput8BitValueWithLabel() const { HtmlTag content; content.AddChildTag(HtmlTagLabel(Languages::TextValue, "valueraw0")); content.AddChildTag(HtmlTagInputBitValue("valueraw7")); content.AddChildTag(HtmlTagInputBitValue("valueraw6")); content.AddChildTag(HtmlTagInputBitValue("valueraw5")); content.AddChildTag(HtmlTagInputBitValue("valueraw4")); content.AddChildTag(HtmlTagInputBitValue("valueraw3")); content.AddChildTag(HtmlTagInputBitValue("valueraw2")); content.AddChildTag(HtmlTagInputBitValue("valueraw1")); content.AddChildTag(HtmlTagInputBitValue("valueraw0")); return content; } HtmlTag WebClient::HtmlTagInputBitValue(string name) const { HtmlTagInputCheckbox checkbox(name, name); checkbox.AddAttribute("onclick", "updateCvValue();"); return checkbox; } void WebClient::HandleProgram() { HtmlTag content; content.AddChildTag(HtmlTag("h1").AddContent(Languages::TextProgrammer)); HtmlTag tabMenu("div"); tabMenu.AddChildTag(WebClientStatic::HtmlTagTabMenuItem("raw", Languages::TextDirect, true)); std::map controls = manager.ControlListNames(Hardware::CapabilityProgram); if (controls.size() == 0) { ReplyHtmlWithHeader(HtmlTag("p").AddContent(Languages::TextNoControlSupportsProgramming)); return; } /* FIXME: Tab MM and Tab DCC incomplete unsigned int controlCountMm = 0; unsigned int controlCountDcc = 0; for (auto control : controls) { Hardware::Capabilities capabilities = manager.GetCapabilities(control.first); if (capabilities & Hardware::CapabilityProgramMmWrite) { ++controlCountMm; } if (capabilities & (Hardware::CapabilityProgramDccDirectRead | Hardware::CapabilityProgramDccDirectWrite)) { ++controlCountDcc; } } if (controlCountMm > 0) { tabMenu.AddChildTag(WebClientStatic::HtmlTagTabMenuItem("mm", Languages::TextMaerklinMotorola)); } if (controlCountDcc > 0) { tabMenu.AddChildTag(WebClientStatic::HtmlTagTabMenuItem("dcc", Languages::TextDcc)); } */ content.AddChildTag(tabMenu); HtmlTag programContent("div"); programContent.AddClass("popup_content"); HtmlTag rawContent("div"); rawContent.AddId("tab_raw"); rawContent.AddClass("tab_content"); rawContent.AddClass("narrow_label"); HtmlTag controlSelector = WebClientStatic::HtmlTagControl("controlraw", controls); rawContent.AddChildTag(controlSelector); const ControlID controlIdFirst = controls.begin()->first; HtmlTag programModeSelector("div"); programModeSelector.AddId("program_mode_selector"); ProgramMode programMode = ProgramModeNone; programModeSelector.AddChildTag(HtmlTagProgramModeSelector(controlIdFirst, programMode)); rawContent.AddChildTag(programModeSelector); rawContent.AddChildTag(HtmlTagCvFields(controlIdFirst, programMode)); programContent.AddChildTag(rawContent); /* FIXME: Tab MM incomplete HtmlTag mmContent("div"); mmContent.AddId("tab_mm"); mmContent.AddClass("tab_content"); mmContent.AddClass("hidden"); mmContent.AddContent("MM"); programContent.AddChildTag(mmContent); */ /* FIXME: Tab DCC incomplete HtmlTag dccContent("div"); dccContent.AddId("tab_dcc"); dccContent.AddClass("tab_content"); dccContent.AddClass("hidden"); dccContent.AddContent("DCC"); programContent.AddChildTag(dccContent); */ content.AddChildTag(programContent); content.AddChildTag(HtmlTagButtonCancel()); ReplyHtmlWithHeader(content); } void WebClient::HandleProgramModeSelector(const map& arguments) { ControlID controlID = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "control")); ProgramMode mode = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "mode")); return ReplyHtmlWithHeader(HtmlTagProgramModeSelector(controlID, mode)); } void WebClient::HandleProgramRead(const map& arguments) { ControlID controlID = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "control")); CvNumber cv = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "cv")); ProgramMode mode = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "mode")); switch (mode) { case ProgramModeDccRegister: case ProgramModeDccPage: case ProgramModeDccDirect: manager.ProgramRead(controlID, mode, 0, cv); break; case ProgramModeDccPomLoco: case ProgramModeDccPomAccessory: case ProgramModeMfx: { Address address = static_cast
(Utils::Utils::GetIntegerMapEntry(arguments, "address")); manager.ProgramRead(controlID, mode, address, cv); break; } default: break; } ReplyHtmlWithHeaderAndParagraph(Languages::TextProgramDccDirectRead, cv); } void WebClient::HandleProgramWrite(const map& arguments) { ControlID controlID = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "control")); ProgramMode mode = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "mode")); CvNumber cv = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "cv")); CvValue value = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "value")); switch (mode) { case ProgramModeMm: case ProgramModeDccRegister: case ProgramModeDccPage: case ProgramModeDccDirect: manager.ProgramWrite(controlID, mode, 0, cv, value); break; case ProgramModeMmPom: case ProgramModeDccPomLoco: case ProgramModeDccPomAccessory: case ProgramModeMfx: { Address address = static_cast
(Utils::Utils::GetIntegerMapEntry(arguments, "address")); manager.ProgramWrite(controlID, mode, address, cv, value); break; } default: break; } ReplyHtmlWithHeaderAndParagraph(Languages::TextProgramDccDirectWrite, cv, value); } void WebClient::HandleUpdater(const map& headers) { Response response; response.AddHeader("Cache-Control", "no-cache, must-revalidate"); response.AddHeader("Pragma", "no-cache"); response.AddHeader("Expires", "Sun, 12 Feb 2016 00:00:00 GMT"); response.AddHeader("Content-Type", "text/event-stream; charset=utf-8"); int ret = connection->Send(response); if (ret <= 0) { return; } unsigned int updateID = Utils::Utils::GetIntegerMapEntry(headers, "Last-Event-ID", 1); while(run) { string s; bool ok = server.NextUpdate(updateID, s); if (ok == false) { // FIXME: use signaling instead of sleep Utils::Utils::SleepForMilliseconds(100); continue; } string reply("id: "); reply += to_string(updateID); reply += "\r\n"; reply += s; reply += "\r\n\r\n"; ++updateID; ret = connection->Send(reply); if (ret < 0) { return; } } } HtmlTag WebClient::HtmlTagLocoSelector(const string& selector, const LocoID locoID) const { map options = manager.LocoBaseIdsByName(); if (options.size() != 1) { options["-"] = LocoNone; } return HtmlTagSelect("loco_" + selector, options, locoID).AddAttribute("onchange", "loadLoco(" + selector + ");"); } void WebClient::HandleLoco(const map& arguments) { string content; const LocoID locoID = Utils::Utils::GetIntegerMapEntry(arguments, "loco", LocoNone); const ObjectIdentifier locoBaseIdentifier(WebClientStatic::LocoIdToObjectIdentifier(locoID)); if (!locoBaseIdentifier.IsSet()) { ReplyHtmlWithHeaderAndParagraph(Languages::TextPleaseSelectLoco); return; } LocoBase* locoBase = manager.GetLocoBase(locoBaseIdentifier); if (locoBase == nullptr) { ReplyHtmlWithHeaderAndParagraph(Languages::TextLocoDoesNotExist); return; } HtmlTag container("div"); container.AddAttribute("class", "inner_loco"); container.AddChildTag(HtmlTag("p").AddId("loconame").AddContent(locoBase->GetName())); unsigned int speed = locoBase->GetSpeed(); map buttonArguments; buttonArguments["loco"] = to_string(locoID); string id = "locospeed_" + to_string(locoID); container.AddChildTag(HtmlTagInputSliderLocoSpeed(id, MinSpeed, locoBase->GetMaxSpeed(), speed, locoID)); buttonArguments["speed"] = to_string(MinSpeed); container.AddChildTag(HtmlTagButtonCommand("0", id + "_0", buttonArguments)); buttonArguments["speed"] = to_string(locoBase->GetCreepingSpeed()); container.AddChildTag(HtmlTagButtonCommand("I", id + "_1", buttonArguments)); buttonArguments["speed"] = to_string(locoBase->GetReducedSpeed()); container.AddChildTag(HtmlTagButtonCommand("II", id + "_2", buttonArguments)); buttonArguments["speed"] = to_string(locoBase->GetTravelSpeed()); container.AddChildTag(HtmlTagButtonCommand("III", id + "_3", buttonArguments)); buttonArguments["speed"] = to_string(locoBase->GetMaxSpeed()); container.AddChildTag(HtmlTagButtonCommand("IV", id + "_4", buttonArguments)); buttonArguments.erase("speed"); if (locoBaseIdentifier.GetObjectType() == ObjectTypeMultipleUnit) { id = "multipleunitedit_" + to_string(locoBaseIdentifier.GetObjectID()); buttonArguments["multipleunit"] = to_string(locoBaseIdentifier.GetObjectID()); } else { id = "locoedit_" + to_string(locoID); } container.AddChildTag(HtmlTagButtonPopup("", id, buttonArguments)); if (locoBaseIdentifier.GetObjectType() == ObjectTypeMultipleUnit) { buttonArguments.erase("multipleunit"); } id = "locoorientation_" + to_string(locoID); container.AddChildTag(HtmlTagButtonCommandToggle("" "" "" "" "", id, locoBase->GetOrientation(), buttonArguments).AddClass("button_orientation")); id = "locofunction_" + to_string(locoID); std::vector functions = locoBase->GetFunctionStates(); for (DataModel::LocoFunctionEntry& function : functions) { string nrText(to_string(function.nr)); buttonArguments["function"] = nrText; switch(function.type) { case DataModel::LocoFunctionTypeMoment: container.AddChildTag(HtmlTagButtonCommandPressRelease(DataModel::LocoFunctions::GetLocoFunctionIcon(function.nr, function.icon), id + "_" + nrText, buttonArguments)); break; default: container.AddChildTag(HtmlTagButtonCommandToggle(DataModel::LocoFunctions::GetLocoFunctionIcon(function.nr, function.icon), id + "_" + nrText, function.state, buttonArguments)); break; } } buttonArguments.erase("function"); container.AddChildTag(HtmlTagInputHidden("loco", to_string(locoID)).AddId("loco")); ReplyHtmlWithHeader(container); } void WebClient::HandleNewPosition(const map& arguments) { string result; HandleNewPositionInternal(arguments, result); ReplyHtmlWithHeaderAndParagraph(result); } void WebClient::HandleNewPositionInternal(const map& arguments, string& result) { const LayoutPosition posX = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "x", -1)); if (posX == -1) { return; } const LayoutPosition posY = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "y", -1)); if (posY == -1) { return; } manager.LayoutItemNewPosition(ObjectIdentifier(arguments), posX, posY, result); } void WebClient::HandleRotate(const map& arguments) { string result; manager.LayoutItemRotate(ObjectIdentifier(arguments), result); ReplyHtmlWithHeaderAndParagraph(result); } void WebClient::PrintMainHTML() { // handle base request HtmlTag body("body"); body.AddAttribute("onload", "startUp();"); body.AddId("body"); map buttonArguments; HtmlTag menu("div"); menu.AddClass("menu"); HtmlTag menuMain("div"); menuMain.AddClass("menu_main"); menuMain.AddChildTag(HtmlTagButtonPopup("", "askshutdown", Languages::TextExitRailControl)); menuMain.AddChildTag(HtmlTagButtonCommandToggle("", "booster", manager.Booster(), Languages::TextTurningBoosterOnOrOff).AddClass("button_booster")); menuMain.AddChildTag(HtmlTagButtonCommand("STOP", "stopallimmediately", Languages::TextStopAllLocos)); menuMain.AddChildTag(HtmlTagButtonCommand("", "stopall", Languages::TextSetAllLocosToManualMode)); menuMain.AddChildTag(HtmlTagButtonCommand("", "startall", Languages::TextSetAllLocosToAutomode)); menuMain.AddChildTag(HtmlTag().AddContent("   ")); menu.AddChildTag(menuMain); HtmlTag menuAdd("div"); menuAdd.AddClass("menu_add"); menuAdd.AddChildTag(HtmlTagButtonCommandFullScreen()); menuAdd.AddChildTag(HtmlTag().AddContent("   ")); if (manager.CanHandle(Hardware::CapabilityProgram)) { menuAdd.AddChildTag(HtmlTagButtonPopup("Prog", "program", Languages::TextProgrammer)); menuAdd.AddChildTag(HtmlTag().AddContent("   ")); } menu.AddChildTag(menuAdd); HtmlTag menuConfigButton("div"); menuConfigButton.AddClass("menu_configbutton"); menuConfigButton.AddId("menu_configbutton"); menuConfigButton.AddChildTag(HtmlTagButton("", "showmenuconfig", Languages::TextConfigMenu).AddAttribute("onclick", "showMenuConfig(); return false;").AddClass("button_menuconfig")); menuConfigButton.AddChildTag(HtmlTag().AddContent("   ")); menu.AddChildTag(menuConfigButton); HtmlTag menuConfig("div"); menuConfig.AddClass("menu_config"); menuConfig.AddId("menu_config"); menuConfig.AddChildTag(HtmlTagButtonPopup("", "settingsedit", Languages::TextEditSettings)); menuConfig.AddChildTag(HtmlTag().AddContent("   ")); menuConfig.AddChildTag(HtmlTagButtonPopup("", "controllist", Languages::TextEditControls)); menuConfig.AddChildTag(HtmlTagButtonPopup("", "locolist", Languages::TextEditLocos)); menuConfig.AddChildTag(HtmlTagButtonPopup("", "multipleunitlist", Languages::TextEditMultipleUnits)); menuConfig.AddChildTag(HtmlTagButtonPopup("", "layerlist", Languages::TextEditLayers)); menuConfig.AddChildTag(HtmlTagButtonPopup("", "tracklist", Languages::TextEditTracks)); menuConfig.AddChildTag(HtmlTagButtonPopup("", "clusterlist", Languages::TextEditClusters)); menuConfig.AddChildTag(HtmlTagButtonPopup("", "switchlist", Languages::TextEditSwitches)); menuConfig.AddChildTag(HtmlTagButtonPopup("", "signallist", Languages::TextEditSignals)); menuConfig.AddChildTag(HtmlTagButtonPopup("", "accessorylist", Languages::TextEditAccessories)); menuConfig.AddChildTag(HtmlTagButtonPopup("", "feedbacklist", Languages::TextEditFeedbacks)); menuConfig.AddChildTag(HtmlTagButtonPopup("", "routelist", Languages::TextEditRoutes)); menuConfig.AddChildTag(HtmlTagButtonPopup("Text", "textlist", Languages::TextEditTexts)); menu.AddChildTag(menuConfig); body.AddChildTag(menu); const unsigned int MaxNumberOfLocoControls = 5; for (unsigned int i = 1; i <= MaxNumberOfLocoControls; ++i) { const string iText = to_string(i); HtmlTag locoContainer("div"); locoContainer.AddClass("loco_container"); locoContainer.AddId("loco_container_" + iText); HtmlTag locoSelector("div"); locoSelector.AddClass("loco_selector"); locoSelector.AddClass("loco_selector_" + iText); locoSelector.AddId("loco_selector_" + iText); locoSelector.AddChildTag(HtmlTagLocoSelector(iText)); locoContainer.AddChildTag(locoSelector); HtmlTag loco("div"); loco.AddClass("loco"); loco.AddClass("loco_" + iText); loco.AddId("loco_" + iText); locoContainer.AddChildTag(loco); if (i > 1) { locoContainer.AddClass("hidden"); } body.AddChildTag(locoContainer); } body.AddChildTag(HtmlTag("div").AddClass("layer_selector").AddId("layer_selector").AddChildTag(HtmlTagLayerSelector())); body.AddChildTag(HtmlTag("div").AddClass("layout").AddId("layout") .AddAttribute("oncontextmenu", "loadLayoutContext(event);") .AddAttribute("ondragover", "allowDrop(event);") .AddAttribute("ondrop", "drop(event);") ); body.AddChildTag(HtmlTag("div").AddClass("reduce_locos").AddId("reduce_locos").AddContent("<").AddAttribute("onclick", "reduceLocos(); return false;")); body.AddChildTag(HtmlTag("div").AddClass("extend_locos").AddId("extend_locos").AddContent(">").AddAttribute("onclick", "extendLocos(); return false;")); body.AddChildTag(HtmlTag("div").AddClass("clock").AddId("clock").AddContent("")); body.AddChildTag(HtmlTag("div").AddClass("status").AddId("status")); body.AddChildTag(HtmlTag("div").AddClass("popup").AddId("popup")); body.AddChildTag(HtmlTag("div").AddClass("infobox").AddId("infobox")); body.AddChildTag(HtmlTag("div").AddClass("contextmenu").AddId("layout_context") .AddChildTag(HtmlTag("ul").AddClass("contextentries") .AddChildTag(HtmlTag("li").AddClass("contextentry").AddClass("real_layer_only").AddContent(Languages::GetText(Languages::TextAddTrack)).AddAttribute("onClick", "loadPopup('/?cmd=trackedit&track=0');")) .AddChildTag(HtmlTag("li").AddClass("contextentry").AddClass("real_layer_only").AddContent(Languages::GetText(Languages::TextAddSwitch)).AddAttribute("onClick", "loadPopup('/?cmd=switchedit&switch=0');")) .AddChildTag(HtmlTag("li").AddClass("contextentry").AddClass("real_layer_only").AddContent(Languages::GetText(Languages::TextAddSignal)).AddAttribute("onClick", "loadPopup('/?cmd=signaledit&signal=0');")) .AddChildTag(HtmlTag("li").AddClass("contextentry").AddClass("real_layer_only").AddContent(Languages::GetText(Languages::TextAddAccessory)).AddAttribute("onClick", "loadPopup('/?cmd=accessoryedit&accessory=0');")) .AddChildTag(HtmlTag("li").AddClass("contextentry").AddContent(Languages::GetText(Languages::TextAddFeedback)).AddAttribute("onClick", "loadPopup('/?cmd=feedbackedit&feedback=0');")) .AddChildTag(HtmlTag("li").AddClass("contextentry").AddClass("real_layer_only").AddContent(Languages::GetText(Languages::TextAddRoute)).AddAttribute("onClick", "loadPopup('/?cmd=routeedit&route=0');")) .AddChildTag(HtmlTag("li").AddClass("contextentry").AddClass("real_layer_only").AddContent(Languages::GetText(Languages::TextAddText)).AddAttribute("onClick", "loadPopup('/?cmd=textedit&text=0');")) )); connection->Send(ResponseHtmlFull("RailControl", body)); } }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/WebClient.h000066400000000000000000000316201500456250600206460ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include #include #include #include #include "DataModel/AccessoryBase.h" #include "DataModel/ObjectIdentifier.h" #include "Languages.h" #include "Manager.h" #include "Network/TcpConnection.h" #include "ResponseHtml.h" #include "Server/Web/WebClientCluster.h" #include "Server/Web/WebClientRoute.h" #include "Server/Web/WebClientSignal.h" #include "Server/Web/WebClientStatic.h" #include "Server/Web/WebClientText.h" #include "Server/Web/WebClientTrack.h" namespace DataModel { class ObjectIdentifier; class Cluster; } namespace Server { namespace Web { class WebServer; class WebClient { public: enum ResponseType : unsigned char { ResponseInfo = 'i', ResponseWarning = 'w', ResponseError = 'e' }; WebClient() = delete; WebClient(const WebClient&) = delete; WebClient& operator=(const WebClient&) = delete; inline WebClient(const unsigned int id, Network::TcpConnection* connection, // connection must be deleted after using! WebServer &webserver, Manager& manager) : logger(Logger::Logger::GetLogger("WebClient # " + std::to_string(id))), id(id), connection(connection), run(false), terminated(false), server(webserver), clientThread(&WebClient::Worker, this), manager(manager), cluster(manager, *this), track(manager, *this, logger), signal(manager, *this, logger), route(manager, *this, logger), text(manager, *this), headOnly(false), buttonID(0) { } ~WebClient(); void Worker(); inline void Stop() { run = false; } inline bool IsTerminated() { return terminated; } inline void ReplyHtmlWithHeader(const HtmlTag& tag) { connection->Send(ResponseHtml(tag)); } inline void ReplyResponse(const std::string& text) { ReplyHtmlWithHeader(HtmlTag().AddContent(text)); } inline void ReplyResponse(ResponseType type, const std::string& text) { std::string s(1, static_cast(type)); s.append(text); ReplyResponse(s); } template inline void ReplyResponse(ResponseType type, Languages::TextSelector text, Args... args) { std::string s(1, static_cast(type)); s.append(Logger::Logger::Format(Languages::GetText(text), args...)); ReplyResponse(s); } inline void ReplyHtmlWithHeaderAndParagraph(const std::string& content) { ReplyHtmlWithHeader(HtmlTag("p").AddContent(content)); } inline void ReplyHtmlWithHeaderAndParagraph(const char* content) { ReplyHtmlWithHeaderAndParagraph(std::string(content)); } template inline void ReplyHtmlWithHeaderAndParagraph(const Languages::TextSelector text, Args... args) { ReplyHtmlWithHeaderAndParagraph(Logger::Logger::Format(Languages::GetText(text), args...)); } HtmlTag HtmlTagSelectSlave(const std::string& prefix, const std::vector& relations, const std::map& options, const bool allowNew = true) const; inline HtmlTag HtmlTagTabPosition(const DataModel::LayoutItem::LayoutPosition posx, const DataModel::LayoutItem::LayoutPosition posy, const DataModel::LayoutItem::LayoutPosition posz, const DataModel::LayoutItem::Visible visible) const { return HtmlTagTabPosition(posx, posy, posz, DataModel::LayoutItem::RotationNotRelevant, visible); } HtmlTag HtmlTagTabPosition(const DataModel::LayoutItem::LayoutPosition posx, const DataModel::LayoutItem::LayoutPosition posy, const DataModel::LayoutItem::LayoutPosition posz, const DataModel::LayoutItem::LayoutRotation rotation = DataModel::LayoutItem::RotationNotRelevant, const DataModel::LayoutItem::Visible visible = DataModel::LayoutItem::VisibleNotRelevant) const ; HtmlTag HtmlTagControlLoco(ControlID& controlId, const std::string& objectType, const ObjectID objectID) const; HtmlTag HtmlTagControlMultipleUnit(ControlID& controlId, const std::string& objectType, const ObjectID objectID) const; HtmlTag HtmlTagControlAccessory(ControlID& controlID, const std::string& objectType, const ObjectID objectID) const; HtmlTag HtmlTagControlFeedback(ControlID& controlId, const std::string& objectType, const ObjectID objectID) const; HtmlTag HtmlTagProtocolAccessory(const ControlID controlID, const Protocol selectedProtocol); HtmlTag HtmlTagSelectFeedbackForTrack(const unsigned int counter, const TrackID trackID, const FeedbackID feedbackID = FeedbackNone) const; HtmlTag HtmlTagRelationSignalState(const std::string& name, const SignalID signalId, const DataModel::Relation::Data data = DataModel::SignalStateStop); static std::string ProtocolName(const Protocol protocol) { return ProtocolSymbols[protocol <= ProtocolEnd ? protocol : ProtocolNone]; } HtmlTag HtmlTagMatchKeyProtocolAccessory(const ControlID controlID, const std::string& selectedMatchKey, const Protocol selectedProtocol) const; HtmlTag HtmlTagMatchKeyFeedback(const ControlID controlID, const std::string& selectedMatchKey) const; private: void InterpretClientRequest(const std::deque& lines, std::string& method, std::string& uri, std::string& protocol, std::map& arguments, std::map& headers); void HandleLoco(const std::map& arguments); void PrintMainHTML(); void DeliverFile(const std::string& file); void DeliverFileInternal(FILE* f, const char* realFile, const std::string& file); HtmlTag HtmlTagLocoSelector(const std::string& selector, const LocoID locoID = LocoNone) const; HtmlTag HtmlTagLayerSelector(const LayerID layerID = LayerNone) const; HtmlTag HtmlTagMatchKeyProtocolLoco(const ControlID controlID, const std::string& selectedMatchKey, const Protocol selectedProtocol) const; HtmlTag HtmlTagPosition(const DataModel::LayoutItem::LayoutPosition posx, const DataModel::LayoutItem::LayoutPosition posy, const DataModel::LayoutItem::LayoutPosition posz) const; HtmlTag HtmlTagPosition(const DataModel::LayoutItem::LayoutPosition posx, const DataModel::LayoutItem::LayoutPosition posy, const DataModel::LayoutItem::LayoutPosition posz, const DataModel::LayoutItem::Visible visible) const; HtmlTag HtmlTagProgramModeSelector(const ControlID controlID, ProgramMode& mode) const; HtmlTag HtmlTagCvFields(const ControlID controlID, const ProgramMode programMode) const; HtmlTag HtmlTagInput8BitValueWithLabel() const; HtmlTag HtmlTagInputBitValue(std::string name) const; std::map GetMultipleUnitSlaveOptions() const; void HandleAskShutdown(); void HandleSelectLoco(const std::map& arguments); void HandleLayerEdit(const std::map& arguments); void HandleLayerSave(const std::map& arguments); void HandleLayerList(); void HandleLayerAskDelete(const std::map& arguments); void HandleLayerDelete(const std::map& arguments); void HandleControlEdit(const std::map& arguments); void HandleControlSave(const std::map& arguments); void HandleControlList(); void HandleControlAskDelete(const std::map& arguments); void HandleControlDelete(const std::map& arguments); void HandleLocoBaseSpeed(const std::map& arguments); void HandleLocoBaseOrientation(const std::map& arguments); void HandleLocoFunction(const std::map& arguments); void HandleLocoEdit(const std::map& arguments); void HandleLocoSave(const std::map& arguments); void HandleLocoList(); void HandleLocoAskDelete(const std::map& arguments); void HandleLocoDelete(const std::map& arguments); void HandleLocoRelease(const std::map& arguments); void HandleLocoAddTimeTable(const std::map& arguments); void HandleMultipleUnitEdit(const std::map& arguments); void HandleMultipleUnitSave(const std::map& arguments); void HandleMultipleUnitList(); void HandleMultipleUnitAskDelete(const std::map& arguments); void HandleMultipleUnitDelete(const std::map& arguments); void HandleMultipleUnitRelease(const std::map& arguments); void HandleProtocol(const std::map& arguments); void HandleAccessoryAddress(const std::map& arguments); void HandleLayout(const std::map& arguments); void HandleAccessoryEdit(const std::map& arguments); void HandleAccessorySave(const std::map& arguments); void HandleAccessoryState(const std::map& arguments); void HandleAccessoryList(); void HandleAccessoryAskDelete(const std::map& arguments); void HandleAccessoryDelete(const std::map& arguments); void HandleAccessoryGet(const std::map& arguments); void HandleAccessoryRelease(const std::map& arguments); void HandleSwitchEdit(const std::map& arguments); void HandleSwitchSave(const std::map& arguments); void HandleSwitchState(const std::map& arguments); void HandleSwitchList(); void HandleSwitchAskDelete(const std::map& arguments); void HandleSwitchDelete(const std::map& arguments); void HandleSwitchGet(const std::map& arguments); void HandleSwitchRelease(const std::map& arguments); void HandleFeedbackEdit(const std::map& arguments); void HandleFeedbackSave(const std::map& arguments); void HandleFeedbackState(const std::map& arguments); void HandleFeedbackList(); void HandleFeedbackAskDelete(const std::map& arguments); void HandleFeedbackDelete(const std::map& arguments); void HandleFeedbackGet(const std::map& arguments); void HandleLocoSelector(const std::map& arguments); void HandleLayerSelector(const std::map& arguments); void HandleFeedbackAdd(const std::map& arguments); void HandleSettingsEdit(); void HandleSettingsSave(const std::map& arguments); void HandleSlaveAdd(const std::map& arguments); void HandleTimestamp(const std::map& arguments); void HandleControlArguments(const std::map& arguments); void HandleProgram(); void HandleProgramModeSelector(const std::map& arguments); void HandleProgramRead(const std::map& arguments); void HandleProgramWrite(const std::map& arguments); void HandleCvFields(const std::map& arguments); void HandleNewPosition(const std::map& arguments); void HandleNewPositionInternal(const std::map& arguments, std::string& result); void HandleRotate(const std::map& arguments); void HandleUpdater(const std::map& headers); void WorkerImpl(); Logger::Logger* logger; unsigned int id; Network::TcpConnection* connection; volatile bool run; bool terminated; WebServer& server; std::thread clientThread; Manager& manager; WebClientCluster cluster; WebClientTrack track; WebClientSignal signal; WebClientRoute route; WebClientText text; bool headOnly; unsigned int buttonID; }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/WebClientCluster.cpp000066400000000000000000000233731500456250600225510ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include #include #include "DataModel/Cluster.h" #include "DataModel/ObjectIdentifier.h" #include "DataModel/Signal.h" #include "DataModel/Track.h" #include "Server/Web/HtmlTag.h" #include "Server/Web/HtmlTagButtonCancel.h" #include "Server/Web/HtmlTagButtonOK.h" #include "Server/Web/HtmlTagButtonPopupWide.h" #include "Server/Web/HtmlTagInputHidden.h" #include "Server/Web/HtmlTagInputTextWithLabel.h" #include "Server/Web/HtmlTagSelect.h" #include "Server/Web/WebClient.h" #include "Server/Web/WebClientCluster.h" #include "Server/Web/WebClientStatic.h" using DataModel::Cluster; using DataModel::ObjectIdentifier; using DataModel::Relation; using DataModel::Signal; using DataModel::Track; using std::map; using std::string; using std::to_string; using std::vector; namespace Server { namespace Web { void WebClientCluster::HandleClusterList() { HtmlTag content; content.AddChildTag(HtmlTag("h1").AddContent(Languages::TextCluster)); HtmlTag table("table"); map clusterArgument; const map clusterList = manager.ClusterListByName(); for (auto& cluster : clusterList) { HtmlTag row("tr"); row.AddChildTag(HtmlTag("td").AddContent(cluster.first)); const string& clusterIdString = to_string(cluster.second->GetID()); clusterArgument["cluster"] = clusterIdString; row.AddChildTag(HtmlTag("td").AddChildTag(HtmlTagButtonPopupWide(Languages::TextEdit, "clusteredit_list_" + clusterIdString, clusterArgument))); row.AddChildTag(HtmlTag("td").AddChildTag(HtmlTagButtonPopupWide(Languages::TextDelete, "clusteraskdelete_" + clusterIdString, clusterArgument))); table.AddChildTag(row); } content.AddChildTag(HtmlTag("div").AddClass("popup_content").AddChildTag(table)); content.AddChildTag(HtmlTagButtonCancel()); content.AddChildTag(HtmlTagButtonPopupWide(Languages::TextNew, "clusteredit_0")); client.ReplyHtmlWithHeader(content); } HtmlTag WebClientCluster::HtmlTagSelectTrackEntry(const string& priority, const TrackID trackID, const map& trackOptions, const Relation::Data inverted, const map& invertedOptions) { HtmlTag content("div"); content.AddId("track_priority_" + priority); HtmlTagButton deleteButton(Languages::TextDelete, "track_delete_" + priority); deleteButton.AddAttribute("onclick", "deleteElement('track_priority_" + priority + "');return false;"); deleteButton.AddClass("wide_button"); content.AddChildTag(deleteButton); HtmlTag contentObject("div"); contentObject.AddId("track_object_" + priority); contentObject.AddClass("inline-block"); contentObject.AddChildTag(HtmlTagSelect("track_id_" + priority, trackOptions, trackID).AddClass("select_relation_id")); contentObject.AddChildTag(HtmlTagSelect("track_inverted_" + priority, invertedOptions, inverted).AddClass("select_relation_data")); content.AddChildTag(contentObject); return content; } HtmlTag WebClientCluster::HtmlTagSelectTrack(const vector& relations, const ClusterID clusterID) const { HtmlTag content("div"); content.AddId("tab_tracks"); content.AddClass("tab_content"); content.AddClass("hidden"); HtmlTag div("div"); div.AddChildTag(HtmlTagInputHidden("trackcounter", to_string(relations.size()))); div.AddId("tracks"); const map trackOptions = GetTrackOptions(clusterID); map invertedOptions; invertedOptions[Languages::GetText(Languages::TextDefault)] = 0; invertedOptions[Languages::GetText(Languages::TextInverted2)] = 1; unsigned int counter = 1; for (auto relation : relations) { const TrackID trackID = relation->ObjectID2(); const Relation::Data inverted = relation->GetData(); div.AddChildTag(HtmlTagSelectTrackEntry(to_string(counter), trackID, trackOptions, inverted, invertedOptions)); ++counter; } div.AddChildTag(HtmlTag("div").AddId("track_new_" + to_string(counter))); content.AddChildTag(div); HtmlTagButton newTrackButton(Languages::TextNew, "newtrack"); newTrackButton.AddAttribute("onclick", "addSlave('track');return false;"); newTrackButton.AddClass("wide_button"); content.AddChildTag(newTrackButton); content.AddChildTag(HtmlTag("br")); return content; } void WebClientCluster::HandleClusterEdit(const map& arguments) { HtmlTag content; ClusterID clusterID = Utils::Utils::GetIntegerMapEntry(arguments, "cluster", ClusterNone); string name = Utils::Utils::GetStringMapEntry(arguments, "name"); vector tracks; if (clusterID != ClusterNone) { Cluster* cluster = manager.GetCluster(clusterID); if (cluster) { name = cluster->GetName(); tracks = cluster->GetTracks(); } } content.AddChildTag(HtmlTag("h1").AddContent(name).AddId("popup_title")); HtmlTag tabMenu("div"); tabMenu.AddChildTag(WebClientStatic::HtmlTagTabMenuItem("basic", Languages::TextBasic, true)); tabMenu.AddChildTag(WebClientStatic::HtmlTagTabMenuItem("tracks", Languages::TextTracks)); content.AddChildTag(tabMenu); HtmlTag formContent("form"); formContent.AddId("editform"); formContent.AddChildTag(HtmlTagInputHidden("cmd", "clustersave")); formContent.AddChildTag(HtmlTagInputHidden("cluster", to_string(clusterID))); HtmlTag basicContent("div"); basicContent.AddId("tab_basic"); basicContent.AddClass("tab_content"); basicContent.AddChildTag(HtmlTagInputTextWithLabel("name", Languages::TextName, name).AddAttribute("onkeyup", "updateName();")); formContent.AddChildTag(basicContent); formContent.AddChildTag(HtmlTagSelectTrack(tracks, clusterID)); content.AddChildTag(HtmlTag("div").AddClass("popup_content").AddChildTag(formContent)); content.AddChildTag(HtmlTagButtonCancel()); content.AddChildTag(HtmlTagButtonOK()); client.ReplyHtmlWithHeader(content); } void WebClientCluster::HandleClusterSave(const map& arguments) { ClusterID clusterID = Utils::Utils::GetIntegerMapEntry(arguments, "cluster", ClusterNone); string name = Utils::Utils::GetStringMapEntry(arguments, "name"); const unsigned int count = Utils::Utils::GetIntegerMapEntry(arguments, "trackcounter", 0); vector tracks; { for (unsigned int index = 1; index <= count; ++index) { const string indexAsString = to_string(index); const TrackID trackID = Utils::Utils::GetIntegerMapEntry(arguments, "track_id_" + indexAsString, TrackNone); if (trackID == TrackNone) { continue; } const bool inverted = Utils::Utils::GetBoolMapEntry(arguments, "track_inverted_" + indexAsString, false); tracks.push_back(new Relation(&manager, ObjectIdentifier(ObjectTypeCluster, clusterID), ObjectIdentifier(ObjectTypeTrack, trackID), Relation::RelationTypeClusterTrack, 0, static_cast(inverted))); } } string result; if (!manager.ClusterSave(clusterID, name, tracks, result)) { client.ReplyResponse(WebClient::ResponseError, result); return; } client.ReplyResponse(WebClient::ResponseInfo, Languages::TextClusterSaved, name); } void WebClientCluster::HandleClusterAskDelete(const map& arguments) { ClusterID clusterID = Utils::Utils::GetIntegerMapEntry(arguments, "cluster", ClusterNone); if (clusterID == ControlNone) { client.ReplyHtmlWithHeaderAndParagraph(Languages::TextClusterDoesNotExist); return; } const Cluster* cluster = manager.GetCluster(clusterID); if (!cluster) { client.ReplyHtmlWithHeaderAndParagraph(Languages::TextClusterDoesNotExist); return; } HtmlTag content; content.AddContent(HtmlTag("h1").AddContent(Languages::TextDeleteCluster)); content.AddContent(HtmlTag("p").AddContent(Languages::TextAreYouSureToDelete, cluster->GetName())); content.AddContent(HtmlTag("form").AddId("editform") .AddContent(HtmlTagInputHidden("cmd", "clusterdelete")) .AddContent(HtmlTagInputHidden("cluster", to_string(clusterID)) )); content.AddContent(HtmlTagButtonCancel()); content.AddContent(HtmlTagButtonOK()); client.ReplyHtmlWithHeader(content); } void WebClientCluster::HandleClusterDelete(const map& arguments) { ClusterID clusterID = Utils::Utils::GetIntegerMapEntry(arguments, "cluster", ClusterNone); const Cluster* cluster = manager.GetCluster(clusterID); if (!cluster) { client.ReplyResponse(WebClient::ResponseError, Languages::TextClusterDoesNotExist); return; } string name = cluster->GetName(); if (!manager.ClusterDelete(clusterID)) { client.ReplyResponse(WebClient::ResponseError, Languages::TextClusterDoesNotExist); return; } client.ReplyResponse(WebClient::ResponseInfo, Languages::TextClusterDeleted, name); } map WebClientCluster::GetTrackOptions(const ClusterID clusterId) const { map trackOptions; map tracks = manager.TrackListMasterByName(); for (auto& track : tracks) { Cluster* clusterOfTrack = track.second->GetCluster(); if (clusterOfTrack && clusterOfTrack->GetID() != clusterId) { continue; } trackOptions[track.first] = track.second->GetID(); } return trackOptions; } }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/WebClientCluster.h000066400000000000000000000037521500456250600222150ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include #include "DataModel/Relation.h" #include "Manager.h" namespace Server { namespace Web { class WebClient; class WebClientCluster { public: WebClientCluster() = delete; inline WebClientCluster(Manager& manager, WebClient& client) : manager(manager), client(client) { } void HandleClusterList(); static HtmlTag HtmlTagSelectTrackEntry(const std::string& priority, const TrackID trackID, const std::map& trackOptions, const DataModel::Relation::Data inverted, const std::map& invertedOptions); HtmlTag HtmlTagSelectTrack(const std::vector& relations, const ClusterID clusterID) const; void HandleClusterEdit(const std::map& arguments); void HandleClusterSave(const std::map& arguments); void HandleClusterAskDelete(const std::map& arguments); void HandleClusterDelete(const std::map& arguments); std::map GetTrackOptions(const ClusterID clusterId = ClusterNone) const; private: Manager& manager; WebClient& client; }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/WebClientRoute.cpp000066400000000000000000001620511500456250600222230ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include #include #include #include "DataModel/DataModel.h" #include "DataModel/ObjectIdentifier.h" #include "Server/Web/HtmlTagButton.h" #include "Server/Web/HtmlTagButtonCancel.h" #include "Server/Web/HtmlTagButtonCommandWide.h" #include "Server/Web/HtmlTagButtonOK.h" #include "Server/Web/HtmlTagButtonPopupWide.h" #include "Server/Web/HtmlTagButtonSwapRelation.h" #include "Server/Web/HtmlTagInputCheckboxWithLabel.h" #include "Server/Web/HtmlTagInputHidden.h" #include "Server/Web/HtmlTagInputIntegerWithLabel.h" #include "Server/Web/HtmlTagInputTextWithLabel.h" #include "Server/Web/HtmlTagRoute.h" #include "Server/Web/HtmlTagSelect.h" #include "Server/Web/HtmlTagSelectMultipleWithLabel.h" #include "Server/Web/HtmlTagSelectOrientation.h" #include "Server/Web/HtmlTagSelectWithLabel.h" #include "Server/Web/HtmlTagSpace.h" #include "Server/Web/WebClient.h" #include "Server/Web/WebClientRoute.h" #include "Server/Web/WebClientStatic.h" #include "Utils/Integer.h" using namespace DataModel; using LayoutPosition = DataModel::LayoutItem::LayoutPosition; using LayoutItemSize = DataModel::LayoutItem::LayoutItemSize; using LayoutRotation = DataModel::LayoutItem::LayoutRotation; using Visible = DataModel::LayoutItem::Visible; using std::map; using std::pair; using std::string; using std::to_string; using std::vector; namespace Server { namespace Web { void WebClientRoute::HandleRouteGet(const map& arguments) { RouteID routeID = Utils::Utils::GetIntegerMapEntry(arguments, "route"); const DataModel::Route* route = manager.GetRoute(routeID); if (route == nullptr || route->GetVisible() == DataModel::LayoutItem::VisibleNo) { client.ReplyHtmlWithHeader(HtmlTag()); return; } client.ReplyHtmlWithHeader(HtmlTagRoute(route)); } void WebClientRoute::HandleRouteEdit(const map& arguments) { HtmlTag content; RouteID routeID = Utils::Utils::GetIntegerMapEntry(arguments, "route", RouteNone); string name = Languages::GetText(Languages::TextNew); Delay delay = Route::DefaultDelay; Route::PushpullType pushpull = Route::PushpullTypeBoth; Propulsion propulsion = PropulsionAll; TrainType trainType = TrainTypeAll; Length minTrainLength = 0; Length maxTrainLength = 0; vector relationsAtLock; vector relationsAtUnlock; LayoutPosition posx = Utils::Utils::GetIntegerMapEntry(arguments, "posx", 0); LayoutPosition posy = Utils::Utils::GetIntegerMapEntry(arguments, "posy", 0); LayoutPosition posz = Utils::Utils::GetIntegerMapEntry(arguments, "posz", LayerUndeletable); DataModel::LayoutItem::Visible visible = static_cast(Utils::Utils::GetBoolMapEntry(arguments, "visible", routeID == RouteNone && ((posx || posy) && posz >= LayerUndeletable) ? DataModel::LayoutItem::VisibleYes : DataModel::LayoutItem::VisibleNo)); Automode automode = static_cast(Utils::Utils::GetBoolMapEntry(arguments, "automode", AutomodeNo)); TrackID fromTrack = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "fromtrack", TrackNone)); Orientation fromOrientation = static_cast(Utils::Utils::GetBoolMapEntry(arguments, "fromorientation", OrientationRight)); TrackID toTrack = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "totrack", TrackNone)); Orientation toOrientation = static_cast(Utils::Utils::GetBoolMapEntry(arguments, "toorientation", OrientationRight)); Route::Speed speed = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "speed", Route::SpeedTravel)); FeedbackID feedbackIdReduced = Utils::Utils::GetIntegerMapEntry(arguments, "feedbackreduced", FeedbackNone); Delay reducedDelay = Utils::Utils::GetIntegerMapEntry(arguments, "reduceddelay", 0); FeedbackID feedbackIdCreep = Utils::Utils::GetIntegerMapEntry(arguments, "feedbackcreep", FeedbackNone); Delay creepDelay = Utils::Utils::GetIntegerMapEntry(arguments, "creepdelay", 0); FeedbackID feedbackIdStop = Utils::Utils::GetIntegerMapEntry(arguments, "feedbackstop", FeedbackNone); Delay stopDelay = Utils::Utils::GetIntegerMapEntry(arguments, "stopdelay", 0); FeedbackID feedbackIdOver = Utils::Utils::GetIntegerMapEntry(arguments, "feedbackover", FeedbackNone); Pause waitAfterRelease = Utils::Utils::GetIntegerMapEntry(arguments, "waitafterrelease", 0); RouteID followUpRoute = Utils::Utils::GetIntegerMapEntry(arguments, "followuproute", RouteNone); if (routeID > RouteNone) { const DataModel::Route* route = manager.GetRoute(routeID); if (route != nullptr) { name = route->GetName(); delay = route->GetDelay(); pushpull = route->GetPushpull(); propulsion = route->GetPropulsion(); trainType = route->GetTrainType(); minTrainLength = route->GetMinTrainLength(); maxTrainLength = route->GetMaxTrainLength(); relationsAtLock = route->GetRelationsAtLock(); relationsAtUnlock = route->GetRelationsAtUnlock(); visible = route->GetVisible(); posx = route->GetPosX(); posy = route->GetPosY(); posz = route->GetPosZ(); automode = route->GetAutomode(); fromTrack = route->GetFromTrack(); fromOrientation = route->GetFromOrientation(); toTrack = route->GetToTrack(); toOrientation = route->GetToOrientation(); speed = route->GetSpeed(); feedbackIdReduced = route->GetFeedbackIdReduced(); reducedDelay = route->GetReducedDelay(); feedbackIdCreep = route->GetFeedbackIdCreep(); creepDelay = route->GetCreepDelay(); feedbackIdStop = route->GetFeedbackIdStop(); stopDelay = route->GetStopDelay(); feedbackIdOver = route->GetFeedbackIdOver(); waitAfterRelease = route->GetWaitAfterRelease(); followUpRoute = route->GetFollowUpRoute(); } } content.AddChildTag(HtmlTag("h1").AddContent(name).AddId("popup_title")); HtmlTag tabMenu("div"); tabMenu.AddChildTag(WebClientStatic::HtmlTagTabMenuItem("basic", Languages::TextBasic, true)); tabMenu.AddChildTag(WebClientStatic::HtmlTagTabMenuItem("relationatlock", Languages::TextAtLock)); tabMenu.AddChildTag(WebClientStatic::HtmlTagTabMenuItem("relationatunlock", Languages::TextAtUnlock, false, !automode)); tabMenu.AddChildTag(WebClientStatic::HtmlTagTabMenuItem("position", Languages::TextPosition)); tabMenu.AddChildTag(WebClientStatic::HtmlTagTabMenuItem("automode", Languages::TextAutomode)); content.AddChildTag(tabMenu); HtmlTag formContent("form"); formContent.AddId("editform"); formContent.AddChildTag(HtmlTagInputHidden("cmd", "routesave")); formContent.AddChildTag(HtmlTagInputHidden("route", to_string(routeID))); HtmlTag basicContent("div"); basicContent.AddId("tab_basic"); basicContent.AddClass("tab_content"); basicContent.AddChildTag(HtmlTagInputTextWithLabel("name", Languages::TextName, name).AddAttribute("onkeyup", "updateName();")); basicContent.AddChildTag(HtmlTagInputIntegerWithLabel("delay", Languages::TextWaitingTimeBetweenMembers, delay, 1, USHRT_MAX)); formContent.AddChildTag(basicContent); HtmlTag relationDivAtLock("div"); relationDivAtLock.AddId("relationatlock"); Priority priorityAtLock = 1; for (auto relation : relationsAtLock) { relationDivAtLock.AddChildTag(HtmlTagRelation("atlock", relation->GetPriority(), relation->ObjectType2(), relation->ObjectID2(), relation->GetData())); priorityAtLock = relation->GetPriority() + 1; } relationDivAtLock.AddChildTag(HtmlTagInputHidden("relationcounteratlock", to_string(priorityAtLock))); relationDivAtLock.AddChildTag(HtmlTag("div").AddId("new_atlock_priority_" + to_string(priorityAtLock))); HtmlTag relationContentAtLock("div"); relationContentAtLock.AddId("tab_relationatlock"); relationContentAtLock.AddClass("tab_content"); relationContentAtLock.AddClass("hidden"); relationContentAtLock.AddChildTag(relationDivAtLock); HtmlTagButton newButtonAtLock(Languages::TextNew, "newrelationatlock"); newButtonAtLock.AddAttribute("onclick", "addRelation('atlock');return false;"); newButtonAtLock.AddClass("wide_button"); relationContentAtLock.AddChildTag(newButtonAtLock); relationContentAtLock.AddChildTag(HtmlTag("br")); formContent.AddChildTag(relationContentAtLock); HtmlTag relationDivAtUnlock("div"); relationDivAtUnlock.AddId("relationatunlock"); Priority priorityAtUnlock = 1; for (auto relation : relationsAtUnlock) { relationDivAtUnlock.AddChildTag(HtmlTagRelation("atunlock", relation->GetPriority(), relation->ObjectType2(), relation->ObjectID2(), relation->GetData())); priorityAtUnlock = relation->GetPriority() + 1; } relationDivAtUnlock.AddChildTag(HtmlTagInputHidden("relationcounteratunlock", to_string(priorityAtUnlock))); relationDivAtUnlock.AddChildTag(HtmlTag("div").AddId("new_atunlock_priority_" + to_string(priorityAtUnlock))); HtmlTag relationContentAtUnlock("div"); relationContentAtUnlock.AddId("tab_relationatunlock"); relationContentAtUnlock.AddClass("tab_content"); relationContentAtUnlock.AddClass("hidden"); relationContentAtUnlock.AddChildTag(relationDivAtUnlock); HtmlTagButton newButtonAtUnlock(Languages::TextNew, "newrelationatunlock"); newButtonAtUnlock.AddAttribute("onclick", "addRelation('atunlock');return false;"); newButtonAtUnlock.AddClass("wide_button"); relationContentAtUnlock.AddChildTag(newButtonAtUnlock); relationContentAtUnlock.AddChildTag(HtmlTag("br")); formContent.AddChildTag(relationContentAtUnlock); formContent.AddChildTag(client.HtmlTagTabPosition(posx, posy, posz, visible)); HtmlTag automodeContent("div"); automodeContent.AddId("tab_automode"); automodeContent.AddClass("tab_content"); automodeContent.AddClass("hidden"); HtmlTagInputCheckboxWithLabel checkboxAutomode("automode", Languages::TextAutomode, "automode", static_cast(automode)); checkboxAutomode.AddId("automode"); checkboxAutomode.AddAttribute("onchange", "onChangeCheckboxShowHide('automode', 'tracks', 'tab_button_relationatunlock');"); automodeContent.AddChildTag(checkboxAutomode); HtmlTag tracksDiv("div"); tracksDiv.AddId("tracks"); if (automode == AutomodeNo) { tracksDiv.AddAttribute("hidden"); } tracksDiv.AddChildTag(HtmlTagSelectTrack("from", Languages::TextStartTrack, fromTrack, fromOrientation)); tracksDiv.AddChildTag(HtmlTagSelectTrack("to", Languages::TextDestinationTrack, toTrack, toOrientation, "updateFeedbacksOfTrack(); return false;")); map speedOptions; speedOptions[Route::SpeedTravel] = Languages::TextTravelSpeed; speedOptions[Route::SpeedReduced] = Languages::TextReducedSpeed; speedOptions[Route::SpeedCreeping] = Languages::TextCreepingSpeed; tracksDiv.AddChildTag(HtmlTagSelectWithLabel("speed", Languages::TextSpeed, speedOptions, speed)); HtmlTag feedbackDiv("div"); feedbackDiv.AddId("feedbacks"); feedbackDiv.AddChildTag(HtmlTagSelectFeedbacksOfTrack(toTrack, followUpRoute, feedbackIdReduced, reducedDelay, feedbackIdCreep, creepDelay, feedbackIdStop, stopDelay, feedbackIdOver)); tracksDiv.AddChildTag(feedbackDiv); map pushpullOptions; pushpullOptions[Route::PushpullTypeNo] = Languages::TextNoPushPull; pushpullOptions[Route::PushpullTypeBoth] = Languages::TextAllTrains; pushpullOptions[Route::PushpullTypeOnly] = Languages::TextPushPullOnly; tracksDiv.AddChildTag(HtmlTagSelectWithLabel("pushpull", Languages::TextAllowedPushPull, pushpullOptions, pushpull)); vector> propulsionOptions; propulsionOptions.push_back(pair(PropulsionAll,Languages::TextPropulsionAll)); propulsionOptions.push_back(pair(PropulsionSteam,Languages::TextPropulsionSteam)); propulsionOptions.push_back(pair(PropulsionDiesel,Languages::TextPropulsionDiesel)); propulsionOptions.push_back(pair(PropulsionGas,Languages::TextPropulsionGas)); propulsionOptions.push_back(pair(PropulsionElectric,Languages::TextPropulsionElectric)); propulsionOptions.push_back(pair(PropulsionHydrogen,Languages::TextPropulsionHydrogen)); propulsionOptions.push_back(pair(PropulsionAccu,Languages::TextPropulsionAccu)); propulsionOptions.push_back(pair(PropulsionOther,Languages::TextPropulsionOther)); tracksDiv.AddChildTag(HtmlTagSelectMultipleWithLabel("propulsion", Languages::TextAllowedPropulsions, propulsionOptions, propulsion)); vector> trainTypeOptions; trainTypeOptions.push_back(pair(TrainTypeAll,Languages::TextTrainTypeAll)); trainTypeOptions.push_back(pair(TrainTypePassenger,Languages::TextTrainTypePassenger)); trainTypeOptions.push_back(pair(TrainTypeInternationalHighSpeed,Languages::TextTrainTypeInternationalHighSpeed)); trainTypeOptions.push_back(pair(TrainTypeNationalHighSpeed,Languages::TextTrainTypeNationalHighSpeed)); trainTypeOptions.push_back(pair(TrainTypeInternationalLongDistance,Languages::TextTrainTypeInternationalLongDistance)); trainTypeOptions.push_back(pair(TrainTypeNationalLongDistance,Languages::TextTrainTypeNationalLongDistance)); trainTypeOptions.push_back(pair(TrainTypeInternationalNight,Languages::TextTrainTypeInternationalNight)); trainTypeOptions.push_back(pair(TrainTypeNationalNight,Languages::TextTrainTypeNationalNight)); trainTypeOptions.push_back(pair(TrainTypeLongDistanceFastLocal,Languages::TextTrainTypeLongDistanceFastLocal)); trainTypeOptions.push_back(pair(TrainTypeFastLocal,Languages::TextTrainTypeFastLocal)); trainTypeOptions.push_back(pair(TrainTypeLocal,Languages::TextTrainTypeLocal)); trainTypeOptions.push_back(pair(TrainTypeSuburban,Languages::TextTrainTypeSuburban)); trainTypeOptions.push_back(pair(TrainTypeUnderground,Languages::TextTrainTypeUnderground)); trainTypeOptions.push_back(pair(TrainTypeHistoric,Languages::TextTrainTypeHistoric)); trainTypeOptions.push_back(pair(TrainTypeExtra,Languages::TextTrainTypeExtra)); trainTypeOptions.push_back(pair(TrainTypePassengerWithCargo,Languages::TextTrainTypePassengerWithCargo)); trainTypeOptions.push_back(pair(TrainTypeCargo,Languages::TextTrainTypeCargo)); trainTypeOptions.push_back(pair(TrainTypeCargoLongDistance,Languages::TextTrainTypeCargoLongDistance)); trainTypeOptions.push_back(pair(TrainTypeCargoLocal,Languages::TextTrainTypeCargoLocal)); trainTypeOptions.push_back(pair(TrainTypeCargoBlock,Languages::TextTrainTypeCargoBlock)); trainTypeOptions.push_back(pair(TrainTypeCargoTractor,Languages::TextTrainTypeCargoTractor)); trainTypeOptions.push_back(pair(TrainTypeCargoExpress,Languages::TextTrainTypeCargoExpress)); trainTypeOptions.push_back(pair(TrainTypeCargoWithPassenger,Languages::TextTrainTypeCargoWithPassenger)); trainTypeOptions.push_back(pair(TrainTypeRescue,Languages::TextTrainTypeRescue)); trainTypeOptions.push_back(pair(TrainTypeConstruction,Languages::TextTrainTypeConstruction)); trainTypeOptions.push_back(pair(TrainTypeEmpty,Languages::TextTrainTypeEmpty)); trainTypeOptions.push_back(pair(TrainTypeLoco,Languages::TextTrainTypeLoco)); trainTypeOptions.push_back(pair(TrainTypeCleaning,Languages::TextTrainTypeCleaning)); trainTypeOptions.push_back(pair(TrainTypeOther,Languages::TextTrainTypeOther)); tracksDiv.AddChildTag(HtmlTagSelectMultipleWithLabel("traintype", Languages::TextAllowedTrainTypes, trainTypeOptions, trainType)); tracksDiv.AddChildTag(HtmlTagInputIntegerWithLabel("mintrainlength", Languages::TextMinTrainLength, minTrainLength, 0, 99999)); tracksDiv.AddChildTag(HtmlTagInputIntegerWithLabel("maxtrainlength", Languages::TextMaxTrainLength, maxTrainLength, 0, 99999)); tracksDiv.AddChildTag(HtmlTagInputIntegerWithLabel("waitafterrelease", Languages::TextWaitAfterRelease, waitAfterRelease, 0, 300)); automodeContent.AddChildTag(tracksDiv); formContent.AddChildTag(automodeContent); content.AddChildTag(HtmlTag("div").AddClass("popup_content").AddChildTag(formContent)); content.AddChildTag(HtmlTagButtonCancel()); content.AddChildTag(HtmlTagButtonOK()); client.ReplyHtmlWithHeader(content); } void WebClientRoute::HandleRouteSave(const map& arguments) { const RouteID routeID = Utils::Utils::GetIntegerMapEntry(arguments, "route", RouteNone); const string name = Utils::Utils::GetStringMapEntry(arguments, "name"); const Delay delay = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "delay")); const Route::PushpullType pushpull = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "pushpull", Route::PushpullTypeBoth)); Propulsion propulsion = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "propulsion", PropulsionAll)); if (propulsion == PropulsionUnknown) { propulsion = PropulsionAll; } TrainType trainType = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "traintype", TrainTypeAll)); if (trainType == TrainTypeUnknown) { trainType = TrainTypeAll; } const Length mintrainlength = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "mintrainlength", 0)); const Length maxtrainlength = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "maxtrainlength", 0)); const Visible visible = static_cast(Utils::Utils::GetBoolMapEntry(arguments, "visible")); const LayoutPosition posx = Utils::Utils::GetIntegerMapEntry(arguments, "posx", 0); const LayoutPosition posy = Utils::Utils::GetIntegerMapEntry(arguments, "posy", 0); const LayoutPosition posz = Utils::Utils::GetIntegerMapEntry(arguments, "posz", 0); const Automode automode = static_cast(Utils::Utils::GetBoolMapEntry(arguments, "automode")); const TrackID fromTrack = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "fromtrack", TrackNone)); const Orientation fromOrientation = static_cast(Utils::Utils::GetBoolMapEntry(arguments, "fromorientation", OrientationRight)); const TrackID toTrack = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "totrack", TrackNone)); const Orientation toOrientation = static_cast(Utils::Utils::GetBoolMapEntry(arguments, "toorientation", OrientationRight)); const Route::Speed speed = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "speed", Route::SpeedTravel)); const FeedbackID feedbackIdReduced = Utils::Utils::GetIntegerMapEntry(arguments, "feedbackreduced", FeedbackNone); const Delay reducedDelay = Utils::Utils::GetIntegerMapEntry(arguments, "reduceddelay", 0); const FeedbackID feedbackIdCreep = Utils::Utils::GetIntegerMapEntry(arguments, "feedbackcreep", FeedbackNone); const Delay creepDelay = Utils::Utils::GetIntegerMapEntry(arguments, "creepdelay", 0); const FeedbackID feedbackIdStop = Utils::Utils::GetIntegerMapEntry(arguments, "feedbackstop", FeedbackNone); const Delay stopDelay = Utils::Utils::GetIntegerMapEntry(arguments, "stopdelay", 0); const FeedbackID feedbackIdOver = Utils::Utils::GetIntegerMapEntry(arguments, "feedbackover", FeedbackNone); const Pause waitAfterRelease = Utils::Utils::GetIntegerMapEntry(arguments, "waitafterrelease", 0); const RouteID followUpRoute = Utils::Utils::GetIntegerMapEntry(arguments, "followuproute", RouteNone); Priority relationCountAtLock = Utils::Utils::GetIntegerMapEntry(arguments, "relationcounteratlock", 0); Priority relationCountAtUnlock = Utils::Utils::GetIntegerMapEntry(arguments, "relationcounteratunlock", 0); vector relationsAtLock; Priority priorityAtLock = 1; for (Priority relationId = 1; relationId <= relationCountAtLock; ++relationId) { string priorityString = to_string(relationId); ObjectType objectType = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "relation_atlock_" + priorityString + "_type")); ObjectID objectId = Utils::Utils::GetIntegerMapEntry(arguments, "relation_atlock_" + priorityString + "_id", ObjectNone); if (objectId == 0 && objectType != ObjectTypeLoco && objectType != ObjectTypePause && objectType != ObjectTypeMultipleUnit && objectType != ObjectTypeBooster) { continue; } if (objectId == fromTrack && objectType == ObjectTypeTrack) { continue; } if (objectId == toTrack && objectType == ObjectTypeTrack) { continue; } unsigned short state = Utils::Utils::GetIntegerMapEntry(arguments, "relation_atlock_" + priorityString + "_state"); relationsAtLock.push_back(new Relation(&manager, ObjectIdentifier(ObjectTypeRoute, routeID), ObjectIdentifier(objectType, objectId), Relation::RelationTypeRouteAtLock, priorityAtLock, state)); ++priorityAtLock; } vector relationsAtUnlock; Priority priorityAtUnlock = 1; for (Priority relationId = 1; relationId <= relationCountAtUnlock; ++relationId) { string priorityString = to_string(relationId); ObjectType objectType = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "relation_atunlock_" + priorityString + "_type")); ObjectID objectId = Utils::Utils::GetIntegerMapEntry(arguments, "relation_atunlock_" + priorityString + "_id", ObjectNone); if (objectId == 0 && objectType != ObjectTypeLoco && objectType != ObjectTypePause && objectType != ObjectTypeMultipleUnit && objectType != ObjectTypeBooster) { continue; } if (objectId == fromTrack && objectType == ObjectTypeTrack) { continue; } if (objectId == toTrack && objectType == ObjectTypeTrack) { continue; } unsigned char state = Utils::Utils::GetIntegerMapEntry(arguments, "relation_atunlock_" + priorityString + "_state"); relationsAtUnlock.push_back(new Relation(&manager, ObjectIdentifier(ObjectTypeRoute, routeID), ObjectIdentifier(objectType, objectId), Relation::RelationTypeRouteAtUnlock, priorityAtUnlock, state)); ++priorityAtUnlock; } string result; if (!manager.RouteSave(routeID, name, delay, pushpull, propulsion, trainType, mintrainlength, maxtrainlength, relationsAtLock, relationsAtUnlock, visible, posx, posy, posz, automode, fromTrack, fromOrientation, toTrack, toOrientation, speed, feedbackIdReduced, reducedDelay, feedbackIdCreep, creepDelay, feedbackIdStop, stopDelay, feedbackIdOver, waitAfterRelease, followUpRoute, result)) { client.ReplyResponse(client.ResponseError, result); return; } client.ReplyResponse(client.ResponseInfo, Languages::TextRouteSaved, name); } void WebClientRoute::HandleRouteAskDelete(const map& arguments) { RouteID routeID = Utils::Utils::GetIntegerMapEntry(arguments, "route", RouteNone); if (routeID == RouteNone) { client.ReplyHtmlWithHeaderAndParagraph(Languages::TextRouteDoesNotExist); return; } const DataModel::Route* route = manager.GetRoute(routeID); if (route == nullptr) { client.ReplyHtmlWithHeaderAndParagraph(Languages::TextRouteDoesNotExist); return; } HtmlTag content; const string& routeName = route->GetName(); content.AddContent(HtmlTag("h1").AddContent(Languages::TextDeleteRoute)); content.AddContent(HtmlTag("p").AddContent(Languages::TextAreYouSureToDelete, routeName)); content.AddContent(HtmlTag("form").AddId("editform") .AddContent(HtmlTagInputHidden("cmd", "routedelete")) .AddContent(HtmlTagInputHidden("route", to_string(routeID)) )); content.AddContent(HtmlTagButtonCancel()); content.AddContent(HtmlTagButtonOK()); client.ReplyHtmlWithHeader(content); } void WebClientRoute::HandleRouteDelete(const map& arguments) { RouteID routeID = Utils::Utils::GetIntegerMapEntry(arguments, "route", RouteNone); const DataModel::Route* route = manager.GetRoute(routeID); if (route == nullptr) { client.ReplyResponse(client.ResponseError, Languages::TextRouteDoesNotExist); return; } string name = route->GetName(); string result; if (!manager.RouteDelete(routeID, result)) { client.ReplyResponse(client.ResponseError, result); return; } client.ReplyResponse(client.ResponseInfo, Languages::TextRouteDeleted, name); } void WebClientRoute::HandleRouteList() { HtmlTag content; content.AddChildTag(HtmlTag("h1").AddContent(Languages::TextRoutes)); HtmlTag table("table"); const map routeList = manager.RouteListByName(); map routeArgument; for (auto& route : routeList) { HtmlTag row("tr"); row.AddChildTag(HtmlTag("td").AddContent(route.first)); const string& routeIdString = to_string(route.second->GetID()); routeArgument["route"] = routeIdString; row.AddChildTag(HtmlTag("td").AddChildTag(HtmlTagButtonPopupWide(Languages::TextEdit, "routeedit_list_" + routeIdString, routeArgument))); row.AddChildTag(HtmlTag("td").AddChildTag(HtmlTagButtonPopupWide(Languages::TextDelete, "routeaskdelete_" + routeIdString, routeArgument))); if (route.second->IsInUse()) { row.AddChildTag(HtmlTag("td").AddChildTag(HtmlTagButtonCommandWide(Languages::TextRelease, "routerelease_" + routeIdString, routeArgument, "hideElement('b_routerelease_" + routeIdString + "');"))); } table.AddChildTag(row); } content.AddChildTag(HtmlTag("div").AddClass("popup_content").AddChildTag(table)); content.AddChildTag(HtmlTagButtonCancel()); content.AddChildTag(HtmlTagButtonPopupWide(Languages::TextNew, "routeedit_0")); client.ReplyHtmlWithHeader(content); } void WebClientRoute::HandleRouteExecute(const map& arguments) { RouteID routeID = Utils::Utils::GetIntegerMapEntry(arguments, "route", RouteNone); manager.RouteExecuteAsync(logger, routeID); client.ReplyHtmlWithHeaderAndParagraph("Route executed"); } void WebClientRoute::HandleRouteRelease(const map& arguments) { RouteID routeID = Utils::Utils::GetIntegerMapEntry(arguments, "route"); bool ret = manager.RouteRelease(routeID); client.ReplyHtmlWithHeaderAndParagraph(ret ? "Route released" : "Route not released"); } void WebClientRoute::HandleRelationAdd(const map& arguments) { string priorityString = Utils::Utils::GetStringMapEntry(arguments, "priority", "1"); string type = Utils::Utils::GetStringMapEntry(arguments, "type", "atunlock"); if (type.compare("atunlock") != 0) { type = "atlock"; } Priority priority = Utils::Integer::StringToInteger(priorityString, 1); HtmlTag container; container.AddChildTag(HtmlTagRelation(type, priority)); container.AddChildTag(HtmlTag("div").AddId("new_" + type + "_priority_" + to_string(priority + 1))); client.ReplyHtmlWithHeader(container); } void WebClientRoute::HandleRelationObject(const map& arguments) { const string priority = Utils::Utils::GetStringMapEntry(arguments, "priority"); const string atlock = Utils::Utils::GetStringMapEntry(arguments, "atlock"); const ObjectType objectType = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "objecttype")); const ObjectID id = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "id")); const DataModel::Relation::Data state = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "state")); client.ReplyHtmlWithHeader(HtmlTagRelationObject(atlock, priority, objectType, id, state)); } void WebClientRoute::HandleRelationSwitchStates(const map& arguments) { const string name = Utils::Utils::GetStringMapEntry(arguments, "name"); const SwitchID switchId = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "switch")); client.ReplyHtmlWithHeader(HtmlTagRelationSwitchState(name, switchId)); } HtmlTag WebClientRoute::HtmlTagRelation(const string& atlock, const Priority priority, const ObjectType objectType, const ObjectID objectId, const DataModel::Relation::Data state) { HtmlTag content("div"); string priorityString = to_string(priority); string name = "relation_" + atlock + "_" + priorityString; content.AddId(name); HtmlTagButton deleteButton(Languages::TextDelete, "delete_" + name); deleteButton.AddAttribute("onclick", "deleteElement('" + name + "');return false;"); deleteButton.AddClass("wide_button"); content.AddChildTag(deleteButton); if (priority > 1) { content.AddChildTag(HtmlTagButtonSwapRelation(atlock, priorityString, true)); } else { content.AddChildTag(HtmlTagSpace().AddClass("small_button").AddClass("button_replacement")); } content.AddChildTag(HtmlTagButtonSwapRelation(atlock, priorityString, false)); HtmlTag contentObject("div"); contentObject.AddId(name + "_object"); contentObject.AddClass("inline-block"); contentObject.AddChildTag(HtmlTagRelationObject(atlock, priorityString, objectType, objectId, state)); content.AddChildTag(contentObject); return content; } HtmlTag WebClientRoute::HtmlTagRelationSwitchState(const string& name, const SwitchID switchId, const DataModel::Relation::Data data) { map stateOptions; Switch* mySwitch = manager.GetSwitch(switchId); if (mySwitch != nullptr) { stateOptions = mySwitch->GetStateOptions(); } return HtmlTagSelect(name + "_state", stateOptions, static_cast(data)).AddClass("select_relation_state"); } HtmlTag WebClientRoute::HtmlTagRelationObject(const string& atlock, const string& priorityString, const ObjectType objectType, const ObjectID objectId, const DataModel::Relation::Data state) { const string name = "relation_" + atlock + "_" + priorityString; HtmlTag content; map objectTypeOptions; objectTypeOptions[ObjectTypeAccessory] = Languages::TextAccessory; objectTypeOptions[ObjectTypeSignal] = Languages::TextSignal; objectTypeOptions[ObjectTypeSwitch] = Languages::TextSwitch; objectTypeOptions[ObjectTypeTrack] = Languages::TextTrack; objectTypeOptions[ObjectTypeRoute] = Languages::TextRoute; objectTypeOptions[ObjectTypeLoco] = Languages::TextLoco; objectTypeOptions[ObjectTypeMultipleUnit] = Languages::TextOrientation; objectTypeOptions[ObjectTypePause] = Languages::TextPause; objectTypeOptions[ObjectTypeBooster] = Languages::TextBooster; HtmlTagSelect select(name + "_type", objectTypeOptions, objectType); select.AddClass("select_relation_objecttype"); select.AddAttribute("onchange", "loadRelationObject('" + atlock + "', '" + priorityString + "');return false;"); content.AddChildTag(select); switch (objectType) { case ObjectTypeSwitch: { std::map switches = manager.SwitchConfigByName(); map switchOptions; for (auto& mySwitch : switches) { switchOptions[mySwitch.first] = mySwitch.second.GetObjectIdentifier().GetObjectID(); } SwitchID switchId = objectId; if (switchId == SwitchNone && switchOptions.size() > 0) { switchId = switchOptions.begin()->second; } HtmlTagSelect selectSwitch(name + "_id", switchOptions, switchId); selectSwitch.AddClass("select_relation_id"); selectSwitch.AddAttribute("onchange", "loadRelationObjectStates('switch', '" + name + "', '" + to_string(switchId) + "');return false;"); content.AddChildTag(selectSwitch); HtmlTag contentState("div"); contentState.AddId(name + "_state"); contentState.AddClass("inline-block"); contentState.AddChildTag(HtmlTagRelationSwitchState(name, switchId, state)); content.AddChildTag(contentState); return content; } case ObjectTypeSignal: { std::map signals = manager.SignalConfigByName(); map signalOptions; for (auto& signal : signals) { signalOptions[signal.first] = signal.second.GetObjectIdentifier().GetObjectID(); } SignalID signalId = objectId; if (signalId == SignalNone && signalOptions.size() > 0) { signalId = signalOptions.begin()->second; } HtmlTagSelect selectSignal(name + "_id", signalOptions, signalId); selectSignal.AddClass("select_relation_id"); selectSignal.AddAttribute("onchange", "loadRelationObjectStates('signal', '" + name + "', '" + to_string(signalId) + "');return false;"); content.AddChildTag(selectSignal); HtmlTag contentState("div"); contentState.AddId(name + "_state"); contentState.AddClass("inline-block"); contentState.AddChildTag(client.HtmlTagRelationSignalState(name, signalId, state)); content.AddChildTag(contentState); return content; } case ObjectTypeAccessory: { std::map accessories = manager.AccessoryConfigByName(); map accessoryOptions; for (auto& accessory : accessories) { accessoryOptions[accessory.first] = accessory.second.GetObjectIdentifier().GetObjectID(); } content.AddChildTag(HtmlTagSelect(name + "_id", accessoryOptions, objectId).AddClass("select_relation_id")); map stateOptions; stateOptions[DataModel::AccessoryStateOn] = Languages::TextOn; stateOptions[DataModel::AccessoryStateOff] = Languages::TextOff; content.AddChildTag(HtmlTagSelect(name + "_state", stateOptions, static_cast(state)).AddClass("select_relation_state")); return content; } case ObjectTypeTrack: { std::map tracks = manager.TrackListMasterByName(); map trackOptions; for (auto& track : tracks) { trackOptions[track.first] = track.second->GetID(); } content.AddChildTag(HtmlTagSelect(name + "_id", trackOptions, objectId).AddClass("select_relation_id")); content.AddChildTag(HtmlTagSelectOrientation(name + "_state", static_cast(state)).AddClass("select_relation_state")); return content; } case ObjectTypeRoute: { std::map routes = manager.RouteListByName(); map routeOptions; for (auto& track : routes) { routeOptions[track.first] = track.second->GetID(); } content.AddChildTag(HtmlTagSelect(name + "_id", routeOptions, objectId).AddClass("select_relation_id")); return content; } case ObjectTypeLoco: { map functionOptions; for (DataModel::LocoFunctionNr function = 0; function < NumberOfLocoFunctions; ++function) { functionOptions[Utils::Utils::ToStringWithLeadingZeros(function, 3)] = "F" + to_string(function); } functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconShuntingMode, 3)] = Languages::GetText(Languages::TextLocoFunctionIconShuntingMode); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconInertia, 3)] = Languages::GetText(Languages::TextLocoFunctionIconInertia); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconLight, 3)] = Languages::GetText(Languages::TextLocoFunctionIconLight); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconHeadlightLowBeamForward, 3)] = Languages::GetText(Languages::TextLocoFunctionIconHeadlightLowBeamForward); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconHeadlightLowBeamReverse, 3)] = Languages::GetText(Languages::TextLocoFunctionIconHeadlightLowBeamReverse); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconHeadlightHighBeamForward, 3)] = Languages::GetText(Languages::TextLocoFunctionIconHeadlightHighBeamForward); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconHeadlightHighBeamReverse, 3)] = Languages::GetText(Languages::TextLocoFunctionIconHeadlightHighBeamReverse); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconBacklightForward, 3)] = Languages::GetText(Languages::TextLocoFunctionIconBacklightForward); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconBacklightReverse, 3)] = Languages::GetText(Languages::TextLocoFunctionIconBacklightReverse); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconShuntingLight, 3)] = Languages::GetText(Languages::TextLocoFunctionIconShuntingLight); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconBlinkingLight, 3)] = Languages::GetText(Languages::TextLocoFunctionIconBlinkingLight); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconInteriorLight1, 3)] = Languages::GetText(Languages::TextLocoFunctionIconInteriorLight1); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconInteriorLight2, 3)] = Languages::GetText(Languages::TextLocoFunctionIconInteriorLight2); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconTableLight1, 3)] = Languages::GetText(Languages::TextLocoFunctionIconTableLight1); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconTableLight2, 3)] = Languages::GetText(Languages::TextLocoFunctionIconTableLight2); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconTableLight3, 3)] = Languages::GetText(Languages::TextLocoFunctionIconTableLight3); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconCabLight1, 3)] = Languages::GetText(Languages::TextLocoFunctionIconCabLight1); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconCabLight2, 3)] = Languages::GetText(Languages::TextLocoFunctionIconCabLight2); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconCabLight12, 3)] = Languages::GetText(Languages::TextLocoFunctionIconCabLight12); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconDriversDeskLight, 3)] = Languages::GetText(Languages::TextLocoFunctionIconDriversDeskLight); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconTrainDestinationIndicator, 3)] = Languages::GetText(Languages::TextLocoFunctionIconTrainDestinationIndicator); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconLocomotiveNumberIndicator, 3)] = Languages::GetText(Languages::TextLocoFunctionIconLocomotiveNumberIndicator); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconEngineLight, 3)] = Languages::GetText(Languages::TextLocoFunctionIconEngineLight); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconFireBox, 3)] = Languages::GetText(Languages::TextLocoFunctionIconFireBox); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconStairsLight, 3)] = Languages::GetText(Languages::TextLocoFunctionIconStairsLight); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconSmokeGenerator, 3)] = Languages::GetText(Languages::TextLocoFunctionIconSmokeGenerator); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconTelex1, 3)] = Languages::GetText(Languages::TextLocoFunctionIconTelex1); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconTelex2, 3)] = Languages::GetText(Languages::TextLocoFunctionIconTelex2); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconTelex12, 3)] = Languages::GetText(Languages::TextLocoFunctionIconTelex12); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconPanto1, 3)] = Languages::GetText(Languages::TextLocoFunctionIconPanto1); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconPanto2, 3)] = Languages::GetText(Languages::TextLocoFunctionIconPanto2); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconPanto12, 3)] = Languages::GetText(Languages::TextLocoFunctionIconPanto12); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconUp, 3)] = Languages::GetText(Languages::TextLocoFunctionIconUp); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconDown, 3)] = Languages::GetText(Languages::TextLocoFunctionIconDown); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconUpDown1, 3)] = Languages::GetText(Languages::TextLocoFunctionIconUpDown1); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconUpDown2, 3)] = Languages::GetText(Languages::TextLocoFunctionIconUpDown2); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconLeft, 3)] = Languages::GetText(Languages::TextLocoFunctionIconLeft); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconRight, 3)] = Languages::GetText(Languages::TextLocoFunctionIconRight); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconLeftRight, 3)] = Languages::GetText(Languages::TextLocoFunctionIconLeftRight); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconTurnLeft, 3)] = Languages::GetText(Languages::TextLocoFunctionIconTurnLeft); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconTurnRight, 3)] = Languages::GetText(Languages::TextLocoFunctionIconTurnRight); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconTurn, 3)] = Languages::GetText(Languages::TextLocoFunctionIconTurn); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconCrane, 3)] = Languages::GetText(Languages::TextLocoFunctionIconCrane); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconMagnet, 3)] = Languages::GetText(Languages::TextLocoFunctionIconMagnet); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconCraneHook, 3)] = Languages::GetText(Languages::TextLocoFunctionIconCraneHook); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconFan, 3)] = Languages::GetText(Languages::TextLocoFunctionIconFan); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconBreak, 3)] = Languages::GetText(Languages::TextLocoFunctionIconBreak); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconNoSound, 3)] = Languages::GetText(Languages::TextLocoFunctionIconNoSound); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconSoundGeneral, 3)] = Languages::GetText(Languages::TextLocoFunctionIconSoundGeneral); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconRunning1, 3)] = Languages::GetText(Languages::TextLocoFunctionIconRunning1); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconRunning2, 3)] = Languages::GetText(Languages::TextLocoFunctionIconRunning2); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconEngine1, 3)] = Languages::GetText(Languages::TextLocoFunctionIconEngine1); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconEngine2, 3)] = Languages::GetText(Languages::TextLocoFunctionIconEngine2); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconBreak1, 3)] = Languages::GetText(Languages::TextLocoFunctionIconBreak1); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconBreak2, 3)] = Languages::GetText(Languages::TextLocoFunctionIconBreak2); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconCurve, 3)] = Languages::GetText(Languages::TextLocoFunctionIconCurve); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconHorn1, 3)] = Languages::GetText(Languages::TextLocoFunctionIconHorn1); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconHorn2, 3)] = Languages::GetText(Languages::TextLocoFunctionIconHorn2); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconWhistle1, 3)] = Languages::GetText(Languages::TextLocoFunctionIconWhistle1); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconWhistle2, 3)] = Languages::GetText(Languages::TextLocoFunctionIconWhistle2); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconBell, 3)] = Languages::GetText(Languages::TextLocoFunctionIconBell); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconStationAnnouncement1, 3)] = Languages::GetText(Languages::TextLocoFunctionIconStationAnnouncement1); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconStationAnnouncement2, 3)] = Languages::GetText(Languages::TextLocoFunctionIconStationAnnouncement2); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconStationAnnouncement3, 3)] = Languages::GetText(Languages::TextLocoFunctionIconStationAnnouncement3); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconSpeak, 3)] = Languages::GetText(Languages::TextLocoFunctionIconSpeak); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconRadio, 3)] = Languages::GetText(Languages::TextLocoFunctionIconRadio); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconMusic1, 3)] = Languages::GetText(Languages::TextLocoFunctionIconMusic1); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconMusic2, 3)] = Languages::GetText(Languages::TextLocoFunctionIconMusic2); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconOpenDoor, 3)] = Languages::GetText(Languages::TextLocoFunctionIconOpenDoor); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconCloseDoor, 3)] = Languages::GetText(Languages::TextLocoFunctionIconCloseDoor); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconFan1, 3)] = Languages::GetText(Languages::TextLocoFunctionIconFan1); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconFan2, 3)] = Languages::GetText(Languages::TextLocoFunctionIconFan2); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconFan3, 3)] = Languages::GetText(Languages::TextLocoFunctionIconFan3); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconShovelCoal, 3)] = Languages::GetText(Languages::TextLocoFunctionIconShovelCoal); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconCompressedAir, 3)] = Languages::GetText(Languages::TextLocoFunctionIconCompressedAir); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconReliefValve, 3)] = Languages::GetText(Languages::TextLocoFunctionIconReliefValve); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconSteamBlowOut, 3)] = Languages::GetText(Languages::TextLocoFunctionIconSteamBlowOut); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconSteamBlow, 3)] = Languages::GetText(Languages::TextLocoFunctionIconSteamBlow); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconDrainValve, 3)] = Languages::GetText(Languages::TextLocoFunctionIconDrainValve); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconShakingRust, 3)] = Languages::GetText(Languages::TextLocoFunctionIconShakingRust); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconAirPump, 3)] = Languages::GetText(Languages::TextLocoFunctionIconAirPump); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconWaterPump, 3)] = Languages::GetText(Languages::TextLocoFunctionIconWaterPump); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconBufferPush, 3)] = Languages::GetText(Languages::TextLocoFunctionIconBufferPush); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconGenerator, 3)] = Languages::GetText(Languages::TextLocoFunctionIconGenerator); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconGearBox, 3)] = Languages::GetText(Languages::TextLocoFunctionIconGearBox); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconGearUp, 3)] = Languages::GetText(Languages::TextLocoFunctionIconGearUp); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconGearDown, 3)] = Languages::GetText(Languages::TextLocoFunctionIconGearDown); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconFillWater, 3)] = Languages::GetText(Languages::TextLocoFunctionIconFillWater); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconFillDiesel, 3)] = Languages::GetText(Languages::TextLocoFunctionIconFillDiesel); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconFillGas, 3)] = Languages::GetText(Languages::TextLocoFunctionIconFillGas); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconSand, 3)] = Languages::GetText(Languages::TextLocoFunctionIconSand); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconRailJoint, 3)] = Languages::GetText(Languages::TextLocoFunctionIconRailJoint); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconCoupler, 3)] = Languages::GetText(Languages::TextLocoFunctionIconCoupler); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconPanto, 3)] = Languages::GetText(Languages::TextLocoFunctionIconPanto); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconMainSwitch, 3)] = Languages::GetText(Languages::TextLocoFunctionIconMainSwitch); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconSoundLouder, 3)] = Languages::GetText(Languages::TextLocoFunctionIconSoundLouder); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconSoundLower, 3)] = Languages::GetText(Languages::TextLocoFunctionIconSoundLower); functionOptions[Utils::Utils::ToStringWithLeadingZeros(256 + LocoFunctionIconNoBreak, 3)] = Languages::GetText(Languages::TextLocoFunctionIconNoBreak); content.AddChildTag(HtmlTagSelect(name + "_id", functionOptions, Utils::Utils::ToStringWithLeadingZeros(objectId, 3)).AddClass("select_relation_id")); // FIXME: load available functions of loco map stateOptions; stateOptions[DataModel::LocoFunctionStateOff] = Languages::GetText(Languages::TextOff); stateOptions[DataModel::LocoFunctionStateOn] = Languages::GetText(Languages::TextOn); stateOptions[DataModel::LocoFunctionState0s5] = "0.5s"; stateOptions[DataModel::LocoFunctionState1s0] = "1s"; stateOptions[DataModel::LocoFunctionState1s5] = "1.5s"; stateOptions[DataModel::LocoFunctionState2s0] = "2s"; content.AddChildTag(HtmlTagSelect(name + "_state", stateOptions, static_cast(state)).AddClass("select_relation_state")); return content; } case ObjectTypePause: { map time; time[1u] = "0.1s"; time[2u] = "0.2s"; time[3u] = "0.3s"; time[4u] = "0.4s"; time[5u] = "0.5s"; time[6u] = "0.6s"; time[7u] = "0.7s"; time[8u] = "0.8s"; time[9u] = "0.9s"; time[10u] = "1s"; time[15u] = "1.5s"; time[20u] = "2s"; time[25u] = "2.5s"; time[30u] = "3s"; time[40u] = "4s"; time[50u] = "5s"; time[60u] = "6s"; time[70u] = "7s"; time[80u] = "8s"; time[90u] = "9s"; time[100u] = "10s"; content.AddChildTag(HtmlTagSelect(name + "_state", time, static_cast(state)).AddClass("select_relation_state")); return content; } case ObjectTypeMultipleUnit: // abused for loco orientation { map orientation; orientation[0] = Languages::Languages::GetText(Languages::Languages::TextLeft); orientation[1u] = Languages::Languages::GetText(Languages::Languages::TextRight); orientation[2u] = Languages::Languages::GetText(Languages::Languages::TextChange);; content.AddChildTag(HtmlTagSelect(name + "_state", orientation, static_cast(state)).AddClass("select_relation_state")); return content; } case ObjectTypeBooster: { map booster; booster[0] = Languages::Languages::GetText(Languages::Languages::TextOff); booster[1u] = Languages::Languages::GetText(Languages::Languages::TextOn); content.AddChildTag(HtmlTagSelect(name + "_state", booster, static_cast(state)).AddClass("select_relation_state")); return content; } default: { content.AddContent(Languages::TextUnknownObjectType); return content; } } } HtmlTag WebClientRoute::HtmlTagSelectTrack(const std::string& name, const Languages::TextSelector label, const TrackID trackID, const Orientation orientation, const string& onchange) const { HtmlTag tag; map tracks = manager.TrackListIdByName(); HtmlTagSelectWithLabel selectTrack(name + "track", label, tracks, trackID); selectTrack.AddClass("select_track"); if (onchange.size() > 0) { selectTrack.AddAttribute("onchange", onchange); } tag.AddChildTag(selectTrack); tag.AddChildTag(HtmlTagSelectOrientation(name + "orientation", orientation).AddClass("select_orientation")); return tag; } HtmlTag WebClientRoute::HtmlTagSelectFeedbacksOfTrack(const TrackID trackID, const RouteID followUpRoute, const FeedbackID feedbackIdReduced, const Delay reducedDelay, const FeedbackID feedbackIdCreep, const Delay creepDelay, const FeedbackID feedbackIdStop, const Delay stopDelay, const FeedbackID feedbackIdOver) const { HtmlTag tag; map followUpRouteOptions = manager.RoutesOfTrack(trackID); followUpRouteOptions[RouteStop] = Languages::Languages::GetText(Languages::TextNone); followUpRouteOptions[RouteAuto] = Languages::Languages::GetText(Languages::TextSelectAutomatically); tag.AddChildTag(HtmlTagSelectWithLabel("followuproute", Languages::TextFollowUpRoute, followUpRouteOptions, followUpRoute)); map feedbacks = manager.FeedbacksOfTrack(trackID); map feedbacksWithNone = feedbacks; feedbacksWithNone["-"] = FeedbackNone; map delayOptions; delayOptions["0.0s"] = 0; delayOptions["0.1s"] = 1; delayOptions["0.2s"] = 2; delayOptions["0.3s"] = 3; delayOptions["0.4s"] = 4; delayOptions["0.5s"] = 5; delayOptions["0.6s"] = 6; delayOptions["0.7s"] = 7; delayOptions["0.8s"] = 8; delayOptions["0.9s"] = 9; delayOptions["1.0s"] = 10; delayOptions["1.5s"] = 15; delayOptions["2.0s"] = 20; delayOptions["2.5s"] = 25; delayOptions["3.0s"] = 30; delayOptions["3.5s"] = 35; delayOptions["4.0s"] = 40; delayOptions["4.5s"] = 45; delayOptions["5.0s"] = 50; delayOptions["6.0s"] = 60; delayOptions["7.0s"] = 70; delayOptions["8.0s"] = 80; delayOptions["9.0s"] = 90; tag.AddChildTag(HtmlTagSelectWithLabel("feedbackreduced", Languages::TextReducedSpeedAt, feedbacksWithNone, feedbackIdReduced).AddClass("select_feedback")); tag.AddChildTag(HtmlTagSelect("reduceddelay", delayOptions, reducedDelay).AddClass("select_delay")); tag.AddChildTag(HtmlTagSelectWithLabel("feedbackcreep", Languages::TextCreepAt, feedbacksWithNone, feedbackIdCreep).AddClass("select_feedback")); tag.AddChildTag(HtmlTagSelect("creepdelay", delayOptions, creepDelay).AddClass("select_delay")); tag.AddChildTag(HtmlTagSelectWithLabel("feedbackstop", Languages::TextStopAt, feedbacks, feedbackIdStop).AddClass("select_feedback")); tag.AddChildTag(HtmlTagSelect("stopdelay", delayOptions, stopDelay).AddClass("select_delay")); tag.AddChildTag(HtmlTagSelectWithLabel("feedbackover", Languages::TextOverrunAt, feedbacksWithNone, feedbackIdOver).AddClass("select_feedback")); return tag; } void WebClientRoute::HandleFeedbacksOfTrack(const map& arguments) { const TrackID trackID = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "track", TrackNone)); client.ReplyHtmlWithHeader(HtmlTagSelectFeedbacksOfTrack(trackID)); } }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/WebClientRoute.h000066400000000000000000000067471500456250600217010ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include "Logger/Logger.h" #include "Manager.h" #include "Server/Web/WebClient.h" namespace Server { namespace Web { class WebClient; class WebClientRoute { public: WebClientRoute() = delete; WebClientRoute(const WebClientRoute&) = delete; WebClientRoute& operator=(const WebClientRoute&) = delete; inline WebClientRoute(Manager& manager, WebClient& client, Logger::Logger* logger) : manager(manager), client(client), logger(logger) { } void HandleRouteEdit(const std::map& arguments); void HandleRouteSave(const std::map& arguments); void HandleRouteList(); void HandleRouteAskDelete(const std::map& arguments); void HandleRouteDelete(const std::map& arguments); void HandleRouteGet(const std::map& arguments); void HandleRouteExecute(const std::map& arguments); void HandleRouteRelease(const std::map& arguments); void HandleRelationAdd(const std::map& arguments); void HandleRelationObject(const std::map& arguments); void HandleRelationSwitchStates(const std::map& arguments); void HandleFeedbacksOfTrack(const std::map& arguments); private: HtmlTag HtmlTagRelation(const std::string& atlock, const Priority priority, const ObjectType objectType = ObjectTypeSwitch, const ObjectID objectId = ObjectNone, const DataModel::Relation::Data = DataModel::Relation::DefaultData); HtmlTag HtmlTagRelationObject(const std::string& atlock, const std::string& priorityString, const ObjectType objectType, const ObjectID objectId = ObjectNone, const DataModel::Relation::Data state = DataModel::Relation::DefaultData); HtmlTag HtmlTagRelationSwitchState(const std::string& name, const SwitchID switchId, const DataModel::Relation::Data data = DataModel::SwitchStateStraight); HtmlTag HtmlTagSelectTrack(const std::string& name, const Languages::TextSelector label, const TrackID trackID, const Orientation orientation, const std::string& onchange = "") const; HtmlTag HtmlTagSelectFeedbacksOfTrack(const TrackID trackID, const RouteID followUpRoute = RouteNone, const FeedbackID feedbackIdReduced = FeedbackNone, const Delay reducedDelay = 0, const FeedbackID feedbackIdCreep = FeedbackNone, const Delay creepDelay = 0, const FeedbackID feedbackIdStop = FeedbackNone, const Delay stopDelay = 0, const FeedbackID feedbackIdOver = FeedbackNone) const; Manager& manager; WebClient& client; Logger::Logger* logger; }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/WebClientSignal.cpp000066400000000000000000000453671500456250600223540ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include "DataModel/ObjectIdentifier.h" #include "DataModel/Signal.h" #include "Utils/Utils.h" #include "Server/Web/HtmlTag.h" #include "Server/Web/HtmlTagButtonCancel.h" #include "Server/Web/HtmlTagButtonCommandWide.h" #include "Server/Web/HtmlTagButtonOK.h" #include "Server/Web/HtmlTagButtonPopupWide.h" #include "Server/Web/HtmlTagInputCheckboxWithLabel.h" #include "Server/Web/HtmlTagInputIntegerWithLabel.h" #include "Server/Web/HtmlTagInputHidden.h" #include "Server/Web/HtmlTagInputTextWithLabel.h" #include "Server/Web/HtmlTagSelect.h" #include "Server/Web/HtmlTagSelectWithLabel.h" #include "Server/Web/HtmlTagSelectOrientationWithLabel.h" #include "Server/Web/HtmlTagSignal.h" #include "Server/Web/WebClient.h" #include "Server/Web/WebClientCluster.h" #include "Server/Web/WebClientSignal.h" #include "Server/Web/WebClientStatic.h" using namespace DataModel; using LayoutPosition = DataModel::LayoutItem::LayoutPosition; using LayoutItemSize = DataModel::LayoutItem::LayoutItemSize; using LayoutRotation = DataModel::LayoutItem::LayoutRotation; using std::map; using std::string; using std::to_string; using std::vector; namespace Server { namespace Web { void WebClientSignal::HandleSignalEdit(const map& arguments) { HtmlTag content; SignalID signalID = Utils::Utils::GetIntegerMapEntry(arguments, "signal", SignalNone); ControlID controlId = manager.GetPossibleControlForAccessory(); if (controlId == ControlNone) { controlId = manager.GetPossibleControlForAccessory(); } string matchKey = Utils::Utils::GetStringMapEntry(arguments, "matchkey"); Protocol protocol = ProtocolNone; Address address = AddressDefault; Address serverAddress = AddressNone; string name = Languages::GetText(Languages::TextNew); LayoutPosition posx = Utils::Utils::GetIntegerMapEntry(arguments, "posx", 0); LayoutPosition posy = Utils::Utils::GetIntegerMapEntry(arguments, "posy", 0); LayoutPosition posz = Utils::Utils::GetIntegerMapEntry(arguments, "posz", LayerUndeletable); LayoutRotation rotation = Utils::Utils::GetIntegerMapEntry(arguments, "rotation", DataModel::LayoutItem::Rotation0); DataModel::AccessoryType signalType = DataModel::SignalTypeSimpleLeft; DataModel::AccessoryPulseDuration duration = manager.GetDefaultAccessoryDuration(); bool inverted = false; if (signalID > SignalNone) { const DataModel::Signal* signal = manager.GetSignal(signalID); if (signal != nullptr) { controlId = signal->GetControlID(); matchKey = signal->GetMatchKey(); protocol = signal->GetProtocol(); address = signal->GetAddress(); serverAddress = signal->GetServerAddress(); name = signal->GetName(); posx = signal->GetPosX(); posy = signal->GetPosY(); posz = signal->GetPosZ(); rotation = signal->GetRotation(); signalType = signal->GetAccessoryType(); duration = signal->GetAccessoryPulseDuration(); inverted = signal->GetInverted(); } } else if (controlId > ControlNone) { // signal from hardware database const DataModel::AccessoryConfig signalConfig = manager.GetAccessoryOfConfigByMatchKey(controlId, matchKey); if (signalConfig.GetControlId() == controlId && signalConfig.GetMatchKey() == matchKey) { protocol = signalConfig.GetProtocol(); address = signalConfig.GetAddress(); name = signalConfig.GetName(); } } // else new signal std::map signalTypeOptions; signalTypeOptions[DataModel::SignalTypeSimpleLeft] = Languages::TextSimpleLeft; signalTypeOptions[DataModel::SignalTypeSimpleRight] = Languages::TextSimpleRight; signalTypeOptions[DataModel::SignalTypeChLMain] = Languages::TextChLMain; signalTypeOptions[DataModel::SignalTypeChLDistant] = Languages::TextChLDistant; signalTypeOptions[DataModel::SignalTypeChDwarf] = Languages::TextChDwarf; signalTypeOptions[DataModel::SignalTypeChNMain] = Languages::TextChNMain; //signalTypeOptions[DataModel::SignalTypeChNDistant] = Languages::TextChNDistant; signalTypeOptions[DataModel::SignalTypeDeCombined] = Languages::TextDeCombined; signalTypeOptions[DataModel::SignalTypeDeHVMain] = Languages::TextDeHVMain; //signalTypeOptions[DataModel::SignalTypeDeHVDistant] = Languages::TextDeHVDistant; signalTypeOptions[DataModel::SignalTypeDeBlock] = Languages::TextDeBlock; content.AddChildTag(HtmlTag("h1").AddContent(name).AddId("popup_title")); HtmlTag tabMenu("div"); tabMenu.AddChildTag(WebClientStatic::HtmlTagTabMenuItem("main", Languages::TextBasic, true)); tabMenu.AddChildTag(WebClientStatic::HtmlTagTabMenuItem("address", Languages::TextAddresses).AddAttribute("onclick", "onClickAddresses(" + to_string(signalID) + ");")); tabMenu.AddChildTag(WebClientStatic::HtmlTagTabMenuItem("position", Languages::TextPosition)); content.AddChildTag(tabMenu); HtmlTag formContent; formContent.AddChildTag(HtmlTagInputHidden("cmd", "signalsave")); formContent.AddChildTag(HtmlTagInputHidden("signal", to_string(signalID))); HtmlTag mainContent("div"); mainContent.AddId("tab_main"); mainContent.AddClass("tab_content"); mainContent.AddChildTag(HtmlTagInputTextWithLabel("name", Languages::TextName, name).AddAttribute("onkeyup", "updateName();")); mainContent.AddChildTag(HtmlTagSelectWithLabel("signaltype", Languages::TextType, signalTypeOptions, signalType)); mainContent.AddChildTag(client.HtmlTagControlAccessory(controlId, "signal", signalID)); mainContent.AddChildTag(HtmlTag("div").AddId("select_protocol").AddChildTag(client.HtmlTagMatchKeyProtocolAccessory(controlId, matchKey, protocol))); mainContent.AddChildTag(HtmlTagInputIntegerWithLabel("address", Languages::TextBaseAddress, address, 1, 2044)); mainContent.AddChildTag(WebClientStatic::HtmlTagDuration(duration)); mainContent.AddChildTag(HtmlTagInputCheckboxWithLabel("inverted", Languages::TextInverted, "true", inverted)); if (manager.IsServerEnabled()) { mainContent.AddChildTag(HtmlTagInputIntegerWithLabel("serveraddress", Languages::TextServerAddress, serverAddress, 0, 2044)); } formContent.AddChildTag(mainContent); HtmlTag addressContent("div"); addressContent.AddId("tab_address"); addressContent.AddClass("tab_content"); addressContent.AddClass("hidden"); addressContent.AddChildTag(HtmlTag("div").AddId("addresses")); formContent.AddChildTag(addressContent); formContent.AddChildTag(client.HtmlTagTabPosition(posx, posy, posz, rotation)); content.AddChildTag(HtmlTag("div").AddClass("popup_content").AddChildTag(HtmlTag("form").AddId("editform").AddChildTag(formContent))); content.AddChildTag(HtmlTagButtonCancel()); content.AddChildTag(HtmlTagButtonOK()); client.ReplyHtmlWithHeader(content); } void WebClientSignal::HandleSignalAddresses(const map& arguments) { Signal signalDummy(&manager, SignalNone); AccessoryType type = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "type")); SignalID signalId = Utils::Utils::GetIntegerMapEntry(arguments, "signal", SignalNone); Signal* signal = manager.GetSignal(signalId); if (signal == nullptr || signal->GetAccessoryType() != type) { signalDummy.SetAccessoryType(type); signal = &signalDummy; } Address address = Utils::Utils::GetIntegerMapEntry(arguments, "address", AddressNone); const std::map stateOptions = signal->GetStateOptions(); map selectAddressOptions; selectAddressOptions[-1] = "-"; for (AddressOffset i = 0; i < static_cast(stateOptions.size()); ++i) { selectAddressOptions[i] = to_string(address + (i >> 1)) + " " + Languages::GetText(i & 0x01 ? Languages::TextGreen : Languages::TextRed); } HtmlTag addressContent; for (auto& stateOption : stateOptions) { AccessoryState state = stateOption.first; addressContent.AddChildTag(HtmlTagSelectWithLabel("address" + to_string(state), HtmlTagSignal::GetSignalImage(state, signal) + Languages::GetText(stateOption.second), selectAddressOptions, signal->GetStateAddressOffset(state) )); } client.ReplyHtmlWithHeader(addressContent); } void WebClientSignal::HandleSignalSave(const map& arguments) { const SignalID signalID = Utils::Utils::GetIntegerMapEntry(arguments, "signal", SignalNone); const string name = Utils::Utils::GetStringMapEntry(arguments, "name"); const ControlID controlId = Utils::Utils::GetIntegerMapEntry(arguments, "control", ControlIdNone); const string matchKey = Utils::Utils::GetStringMapEntry(arguments, "matchkey"); const Protocol protocol = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "protocol", ProtocolNone)); const Address address = Utils::Utils::GetIntegerMapEntry(arguments, "address", AddressDefault); const Address serverAddress = Utils::Utils::GetIntegerMapEntry(arguments, "serveraddress", AddressNone); const LayoutPosition posX = Utils::Utils::GetIntegerMapEntry(arguments, "posx", 0); const LayoutPosition posY = Utils::Utils::GetIntegerMapEntry(arguments, "posy", 0); const LayoutPosition posZ = Utils::Utils::GetIntegerMapEntry(arguments, "posz", 0); const LayoutItemSize height = Utils::Utils::GetIntegerMapEntry(arguments, "length", 1); const LayoutRotation rotation = Utils::Utils::GetIntegerMapEntry(arguments, "rotation", DataModel::LayoutItem::Rotation0); const DataModel::AccessoryType signalType = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "signaltype", DataModel::SignalTypeSimpleLeft)); std::map offsets; for (AddressOffset offset = 0; offset <= SignalStateMax; ++offset) { const AddressOffset address = Utils::Utils::GetIntegerMapEntry(arguments, "address" + to_string(offset), -1); if (address >= 0) { offsets[static_cast(offset)] = address; } } const DataModel::AccessoryPulseDuration duration = Utils::Utils::GetIntegerMapEntry(arguments, "duration", manager.GetDefaultAccessoryDuration()); const bool inverted = Utils::Utils::GetBoolMapEntry(arguments, "inverted"); string result; if (!manager.SignalSave(signalID, name, posX, posY, posZ, height, rotation, controlId, matchKey, protocol, address, serverAddress, signalType, offsets, duration, inverted, result)) { client.ReplyResponse(WebClient::ResponseError, result); return; } client.ReplyResponse(WebClient::ResponseInfo, Languages::TextSignalSaved, name); } void WebClientSignal::HandleSignalState(const map& arguments) { SignalID signalID = Utils::Utils::GetIntegerMapEntry(arguments, "signal", SignalNone); string signalStateText = Utils::Utils::GetStringMapEntry(arguments, "state", "stop"); DataModel::AccessoryState signalState = DataModel::SignalStateStop; if (signalStateText.compare("clear") == 0) { signalState = DataModel::SignalStateClear; } else if (signalStateText.compare("aspect2") == 0) { signalState = DataModel::SignalStateAspect2; } else if (signalStateText.compare("aspect3") == 0) { signalState = DataModel::SignalStateAspect3; } else if (signalStateText.compare("aspect4") == 0) { signalState = DataModel::SignalStateAspect4; } else if (signalStateText.compare("aspect5") == 0) { signalState = DataModel::SignalStateAspect5; } else if (signalStateText.compare("aspect6") == 0) { signalState = DataModel::SignalStateAspect6; } else if (signalStateText.compare("aspect7") == 0) { signalState = DataModel::SignalStateAspect7; } else if (signalStateText.compare("aspect8") == 0) { signalState = DataModel::SignalStateAspect8; } else if (signalStateText.compare("aspect9") == 0) { signalState = DataModel::SignalStateAspect9; } else if (signalStateText.compare("aspect10") == 0) { signalState = DataModel::SignalStateAspect10; } else if (signalStateText.compare("dark") == 0) { signalState = DataModel::SignalStateDark; } else if (signalStateText.compare("stopexpected") == 0) { signalState = DataModel::SignalStateStopExpected; } else if (signalStateText.compare("clearexpected") == 0) { signalState = DataModel::SignalStateClearExpected; } else if (signalStateText.compare("aspect2expected") == 0) { signalState = DataModel::SignalStateAspect2Expected; } else if (signalStateText.compare("aspect3expected") == 0) { signalState = DataModel::SignalStateAspect3Expected; } else if (signalStateText.compare("aspect4expected") == 0) { signalState = DataModel::SignalStateAspect4Expected; } else if (signalStateText.compare("aspect5expected") == 0) { signalState = DataModel::SignalStateAspect5Expected; } else if (signalStateText.compare("aspect6expected") == 0) { signalState = DataModel::SignalStateAspect6Expected; } else if (signalStateText.compare("aspect7expected") == 0) { signalState = DataModel::SignalStateAspect7Expected; } else if (signalStateText.compare("aspect8expected") == 0) { signalState = DataModel::SignalStateAspect8Expected; } else if (signalStateText.compare("aspect9expected") == 0) { signalState = DataModel::SignalStateAspect9Expected; } else if (signalStateText.compare("aspect10expected") == 0) { signalState = DataModel::SignalStateAspect10Expected; } manager.SignalState(ControlTypeWebServer, signalID, signalState, false); client.ReplyHtmlWithHeaderAndParagraph(signalState ? Languages::TextSignalStateIsClear : Languages::TextSignalStateIsStop, manager.GetSignalName(signalID)); } void WebClientSignal::HandleSignalList() { HtmlTag content; content.AddChildTag(HtmlTag("h1").AddContent(Languages::TextSignals)); HtmlTag table("table"); const map signalList = manager.SignalConfigByName(); map signalArgument; for (auto& signal : signalList) { const AccessoryConfig& signalConfig = signal.second; HtmlTag row("tr"); row.AddChildTag(HtmlTag("td").AddContent(signal.first)); row.AddChildTag(HtmlTag("td").AddContent(client.ProtocolName(signalConfig.GetProtocol()))); row.AddChildTag(HtmlTag("td").AddContent(to_string(signalConfig.GetAddress()))); const SignalID signalId = signalConfig.GetObjectIdentifier().GetObjectID(); const string& signalIdString = to_string(signalId); signalArgument["signal"] = signalIdString; if (signalId == SwitchNone) { signalArgument["control"] = to_string(signalConfig.GetControlId()); signalArgument["matchkey"] = signalConfig.GetMatchKey(); row.AddChildTag(HtmlTag("td").AddChildTag(HtmlTagButtonPopupWide(Languages::TextImport, "signaledit_list_" + signalIdString, signalArgument))); } else { row.AddChildTag(HtmlTag("td").AddChildTag(HtmlTagButtonPopupWide(Languages::TextEdit, "signaledit_list_" + signalIdString, signalArgument))); row.AddChildTag(HtmlTag("td").AddChildTag(HtmlTagButtonPopupWide(Languages::TextDelete, "signalaskdelete_" + signalIdString, signalArgument))); if (signalConfig.IsInUse()) { row.AddChildTag(HtmlTag("td").AddChildTag(HtmlTagButtonCommandWide(Languages::TextRelease, "signalrelease_" + signalIdString, signalArgument, "hideElement('b_signalrelease_" + signalIdString + "');"))); } } table.AddChildTag(row); } content.AddChildTag(HtmlTag("div").AddClass("popup_content").AddChildTag(table)); content.AddChildTag(HtmlTagButtonCancel()); content.AddChildTag(HtmlTagButtonPopupWide(Languages::TextNew, "signaledit_0")); client.ReplyHtmlWithHeader(content); } void WebClientSignal::HandleSignalAskDelete(const map& arguments) { SignalID signalID = Utils::Utils::GetIntegerMapEntry(arguments, "signal", SignalNone); if (signalID == SignalNone) { client.ReplyHtmlWithHeaderAndParagraph(Languages::TextSignalDoesNotExist); return; } const DataModel::Signal* signal = manager.GetSignal(signalID); if (signal == nullptr) { client.ReplyHtmlWithHeaderAndParagraph(Languages::TextSignalDoesNotExist); return; } HtmlTag content; const string& signalName = signal->GetName(); content.AddContent(HtmlTag("h1").AddContent(Languages::TextDeleteSignal)); content.AddContent(HtmlTag("p").AddContent(Languages::TextAreYouSureToDelete, signalName)); content.AddContent(HtmlTag("form").AddId("editform") .AddContent(HtmlTagInputHidden("cmd", "signaldelete")) .AddContent(HtmlTagInputHidden("signal", to_string(signalID)) )); content.AddContent(HtmlTagButtonCancel()); content.AddContent(HtmlTagButtonOK()); client.ReplyHtmlWithHeader(content); } void WebClientSignal::HandleSignalDelete(const map& arguments) { SignalID signalID = Utils::Utils::GetIntegerMapEntry(arguments, "signal", SignalNone); const DataModel::Signal* signal = manager.GetSignal(signalID); if (signal == nullptr) { client.ReplyResponse(WebClient::ResponseError, Languages::TextSignalDoesNotExist); return; } string name = signal->GetName(); string result; if (!manager.SignalDelete(signalID, result)) { client.ReplyResponse(WebClient::ResponseError, result); return; } client.ReplyResponse(WebClient::ResponseInfo, Languages::TextSignalDeleted, name); } void WebClientSignal::HandleSignalGet(const map& arguments) { SignalID signalID = Utils::Utils::GetIntegerMapEntry(arguments, "signal"); const DataModel::Signal* signal = manager.GetSignal(signalID); if (signal == nullptr) { client.ReplyHtmlWithHeader(HtmlTag()); return; } client.ReplyHtmlWithHeader(HtmlTagSignal(manager, signal)); } void WebClientSignal::HandleSignalRelease(const map& arguments) { const SignalID signalID = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "signal")); const bool ret = manager.SignalRelease(signalID); client.ReplyHtmlWithHeaderAndParagraph(ret ? "Signal released" : "Signal not released"); } void WebClientSignal::HandleSignalStates(const map& arguments) { const string name = Utils::Utils::GetStringMapEntry(arguments, "name"); const SignalID signalId = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "signal")); client.ReplyHtmlWithHeader(client.HtmlTagRelationSignalState(name, signalId)); } }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/WebClientSignal.h000066400000000000000000000042111500456250600220000ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include "Logger/Logger.h" #include "Manager.h" namespace Server { namespace Web { class WebClient; class WebClientSignal { public: WebClientSignal() = delete; WebClientSignal(const WebClientSignal&) = delete; WebClientSignal& operator=(const WebClientSignal&) = delete; inline WebClientSignal(Manager& manager, WebClient& client, Logger::Logger* logger) : manager(manager), client(client), logger(logger) { } void HandleSignalEdit(const std::map& arguments); void HandleSignalSave(const std::map& arguments); void HandleSignalList(); void HandleSignalAskDelete(const std::map& arguments); void HandleSignalDelete(const std::map& arguments); void HandleSignalGet(const std::map& arguments); void HandleSignalSetLoco(const std::map& arguments); void HandleSignalRelease(const std::map& arguments); void HandleSignalState(const std::map& arguments); void HandleSignalStates(const std::map& arguments); void HandleSignalAddresses(const std::map& arguments); private: Manager& manager; WebClient& client; Logger::Logger* logger; }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/WebClientStatic.cpp000066400000000000000000000752141500456250600223600ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include "DataModel/DataModel.h" #include "DataModel/LocoConfig.h" #include "Hardware/HardwareHandler.h" #include "Utils/Integer.h" #include "Server/Web/HtmlTagButton.h" #include "Server/Web/HtmlTagInputCheckboxWithLabel.h" #include "Server/Web/HtmlTagInputHidden.h" #include "Server/Web/HtmlTagInputIntegerWithLabel.h" #include "Server/Web/HtmlTagInputTextWithLabel.h" #include "Server/Web/WebClientStatic.h" using namespace DataModel; using LayoutRotation = DataModel::LayoutItem::LayoutRotation; using std::map; using std::string; using std::to_string; using std::vector; namespace Server { namespace Web { HtmlTag WebClientStatic::HtmlTagControlArgument(const unsigned char argNr, const ArgumentType type, const string& value) { Languages::TextSelector argumentName; string argumentNumber = "arg" + to_string(argNr); switch (type) { case ArgumentTypeIpAddress: argumentName = Languages::TextIPAddress; break; case ArgumentTypeSerialPort: { argumentName = Languages::TextSerialPort; #ifdef __CYGWIN__ vector comPorts; bool ret = Utils::Utils::GetComPorts(comPorts); if (ret == false || comPorts.size() == 0) { break; } map comPortOptions; for (auto comPort : comPorts) { comPortOptions["/dev/ttyS" + to_string(comPort)] = "COM" + to_string(comPort + 1); } return HtmlTagSelectWithLabel(argumentNumber, argumentName, comPortOptions, value); #else break; #endif } case ArgumentTypeS88Modules: { argumentName = Languages::TextNrOfS88Modules; const int valueInteger = Utils::Integer::StringToInteger(value, 0, 62); return HtmlTagInputIntegerWithLabel(argumentNumber, argumentName, valueInteger, 0, 62); } case ArgumentTypeMainSecundary: { argumentName = Languages::TextDeviceType; map mainSecondaryOptions; mainSecondaryOptions["1"] = Languages::Languages::GetText(Languages::Languages::TextSecondaryDevice); mainSecondaryOptions["0"] = Languages::Languages::GetText(Languages::Languages::TextMainDevice); return HtmlTagSelectWithLabel(argumentNumber, argumentName, mainSecondaryOptions, value); } default: return HtmlTag(); } return HtmlTagInputTextWithLabel(argumentNumber, argumentName, value); } HtmlTag WebClientStatic::HtmlTagControlArguments(const HardwareType hardwareType, const string& arg1, const string& arg2, const string& arg3, const string& arg4, const string& arg5) { HtmlTag div; std::map argumentTypes; std::string hint; Hardware::HardwareHandler::ArgumentTypesOfHardwareTypeAndHint(hardwareType, argumentTypes, hint); if (argumentTypes.count(1) == 1) { div.AddChildTag(HtmlTagControlArgument(1, argumentTypes.at(1), arg1)); } if (argumentTypes.count(2) == 1) { div.AddChildTag(HtmlTagControlArgument(2, argumentTypes.at(2), arg2)); } if (argumentTypes.count(3) == 1) { div.AddChildTag(HtmlTagControlArgument(3, argumentTypes.at(3), arg3)); } if (argumentTypes.count(4) == 1) { div.AddChildTag(HtmlTagControlArgument(4, argumentTypes.at(4), arg4)); } if (argumentTypes.count(5) == 1) { div.AddChildTag(HtmlTagControlArgument(5, argumentTypes.at(5), arg5)); } if (hint.size() > 0) { div.AddChildTag(HtmlTag("div").AddContent(Languages::GetText(Languages::TextHint)).AddContent(HtmlTag("br")).AddContent(hint)); } return div; } const std::map WebClientStatic::ListHardwareNames() { std::map hardwareList; // Keys should not contain more then 30 characters hardwareList["CAN-Digital-Bahn CC-Schnitte"] = HardwareTypeCcSchnitte; hardwareList["Digikeijs DR5000"] = HardwareTypeDR5000; hardwareList["DCC-EX Serial"] = HardwareTypeDccPpExSerial; hardwareList["DCC-EX TCP"] = HardwareTypeDccPpExTcp; hardwareList["ESU Ecos (beta)"] = HardwareTypeEcos; hardwareList["Fleischmann TwinCenter"] = HardwareTypeTwinCenter; hardwareList["KM1 System Control 7"] = HardwareTypeSystemControl7; hardwareList["LDT HSI-88 RS-232"] = HardwareTypeHsi88; hardwareList["LokStoreDigital LoDi-Rektor (beta)"] = HardwareTypeRektor; hardwareList["Märklin Central Station 1 (beta)"] = HardwareTypeCS1; hardwareList["Märklin Central Station 2/3 TCP"] = HardwareTypeCS2Tcp; hardwareList["Märklin Central Station 2/3 UDP"] = HardwareTypeCS2Udp; hardwareList["Märklin Interface 6050/6051"] = HardwareTypeM6051; hardwareList["OpenDCC Z1"] = HardwareTypeOpenDcc; hardwareList["Roco Z21"] = HardwareTypeZ21; hardwareList["Tams MasterControl"] = HardwareTypeMasterControl; hardwareList["Tams MasterControl 2"] = HardwareTypeMasterControl2; hardwareList["Tams RedBox"] = HardwareTypeRedBox; hardwareList["Uhlenbrock Adapter 63120"] = HardwareTypeLocoNetAdapter63120; hardwareList["Uhlenbrock Adapter 63820"] = HardwareTypeLocoNetAdapter63820; hardwareList["Uhlenbrock Intellibox"] = HardwareTypeIntellibox; hardwareList["Uhlenbrock Intellibox II"] = HardwareTypeIntellibox2; hardwareList["Virtual Command Station"] = HardwareTypeVirtual; return hardwareList; } HtmlTag WebClientStatic::HtmlTagProtocol(const map& protocolMap, const Protocol selectedProtocol) { size_t mapSize = protocolMap.size(); switch (mapSize) { case 0: return HtmlTagInputHidden("protocol", std::to_string(ProtocolNone)); case 1: return HtmlTagInputHidden("protocol", std::to_string(protocolMap.begin()->second)); default: HtmlTag content; content.AddChildTag(HtmlTagLabel(Languages::TextProtocol, "protocol")); content.AddChildTag(HtmlTagSelect("protocol", protocolMap, selectedProtocol)); return content; } } HtmlTag WebClientStatic::HtmlTagAccessoryAddress(const AccessoryType type, const Address address, const AddressPort port) { HtmlTag content; content.AddChildTag(HtmlTagInputIntegerWithLabel("address", Languages::TextAddress, address, 1, 2044)); switch (type & DataModel::AccessoryTypeConnectionMask) { case DataModel::AccessoryTypeOnPush: case DataModel::AccessoryTypeOnOff: { map portMap; portMap[AddressPortRed] = Languages::TextRed; portMap[AddressPortGreen] = Languages::TextGreen; content.AddChildTag(HtmlTagSelect("port", portMap, port).AddClass("select_port")); break; } case DataModel::AccessoryTypeOnOn: default: content.AddChildTag(HtmlTagInputHidden("port", std::to_string(port))); break; } return content; } HtmlTag WebClientStatic::HtmlTagDuration(const DataModel::AccessoryPulseDuration duration, const Languages::TextSelector label) { std::map durationOptions; durationOptions["0000"] = "0"; durationOptions["0100"] = "100"; durationOptions["0250"] = "250"; durationOptions["0500"] = "500"; durationOptions["1000"] = "1000"; durationOptions["1500"] = "1500"; durationOptions["2000"] = "2000"; durationOptions["3000"] = "3000"; durationOptions["4000"] = "4000"; durationOptions["5000"] = "5000"; return HtmlTagSelectWithLabel("duration", label, durationOptions, Utils::Utils::ToStringWithLeadingZeros(duration, 4)); } HtmlTag WebClientStatic::HtmlTagSlaveEntry(const string& prefix, const string& priority, const ObjectID objectId, const map& options) { HtmlTag content("div"); content.AddId(prefix + "_priority_" + priority); HtmlTagButton deleteButton(Languages::TextDelete, prefix + "_delete_" + priority); deleteButton.AddAttribute("onclick", "deleteElement('" + prefix + "_priority_" + priority + "');return false;"); deleteButton.AddClass("wide_button"); content.AddChildTag(deleteButton); HtmlTag contentObject("div"); contentObject.AddId(prefix + "_object_" + priority); contentObject.AddClass("inline-block"); contentObject.AddChildTag(HtmlTagSelect(prefix + "_id_" + priority, options, objectId).AddClass("select_slave_id")); content.AddChildTag(contentObject); return content; } HtmlTag WebClientStatic::HtmlTagRotation(const LayoutRotation rotation) { HtmlTag content; std::map rotationOptions; rotationOptions[DataModel::LayoutItem::Rotation0] = Languages::TextNoRotation; rotationOptions[DataModel::LayoutItem::Rotation90] = Languages::Text90DegClockwise; rotationOptions[DataModel::LayoutItem::Rotation180] = Languages::Text180Deg; rotationOptions[DataModel::LayoutItem::Rotation270] = Languages::Text90DegAntiClockwise; content.AddChildTag(HtmlTagSelectWithLabel("rotation", Languages::TextRotation, Languages::TextHintPositionRotate, rotationOptions, rotation)); return content; } HtmlTag WebClientStatic::HtmlTagSelectExecuteAccessory(const bool executeAccessory) { map options; options[0] = Languages::TextWhenWrongPosition; options[1] = Languages::TextAlways; return HtmlTagSelectWithLabel("executeaccessory", Languages::TextExecuteAccessory, options, static_cast(executeAccessory)); } HtmlTag WebClientStatic::HtmlTagSelectSelectRouteApproach(const DataModel::SelectRouteApproach selectRouteApproach, const bool addDefault) { map options; if (addDefault) { options[DataModel::SelectRouteSystemDefault] = Languages::TextSystemDefault; } options[DataModel::SelectRouteDoNotCare] = Languages::TextDoNotCare; options[DataModel::SelectRouteRandom] = Languages::TextRandom; options[DataModel::SelectRouteMinTrackLength] = Languages::TextMinTrackLength; options[DataModel::SelectRouteLongestUnused] = Languages::TextLongestUnused; return HtmlTagSelectWithLabel("selectrouteapproach", Languages::TextSelectRouteBy, options, selectRouteApproach); } HtmlTag WebClientStatic::HtmlTagNrOfTracksToReserve(const DataModel::Loco::NrOfTracksToReserve nrOfTracksToReserve) { map options; options[DataModel::Loco::ReserveOne] = "1"; options[DataModel::Loco::ReserveTwo] = "2"; return HtmlTagSelectWithLabel("nroftrackstoreserve", Languages::TextNrOfTracksToReserve, options, nrOfTracksToReserve); } HtmlTag WebClientStatic::HtmlTagLogLevel() { map options; options[Logger::Logger::LevelOff] = Languages::TextOff; options[Logger::Logger::LevelError] = Languages::TextError; options[Logger::Logger::LevelWarning] = Languages::TextWarning; options[Logger::Logger::LevelInfo] = Languages::TextInfo; options[Logger::Logger::LevelDebug] = Languages::TextDebug; return HtmlTagSelectWithLabel("loglevel", Languages::TextLogLevel, options, Logger::Logger::GetLogLevel()); } HtmlTag WebClientStatic::HtmlTagLanguage() { map options; options[Languages::EN] = Languages::TextEnglish; options[Languages::DE] = Languages::TextGerman; options[Languages::ES] = Languages::TextSpanish; return HtmlTagSelectWithLabel("language", Languages::TextLanguage, options, Languages::GetDefaultLanguage()); } HtmlTag WebClientStatic::HtmlTagStartupLocos(const StartupInitLocos startupInitLocos) { map options; options[StartupInitLocosNone] = Languages::TextStartupInitLocosNone; options[StartupInitLocosSpeed] = Languages::TextStartupInitLocosSpeed; options[StartupInitLocosAll] = Languages::TextStartupInitLocosAll; return HtmlTagSelectWithLabel("startupinitlocos", Languages::TextStartupInitLocos, options, startupInitLocos); } HtmlTag WebClientStatic::HtmlTagControl(const std::map& controls, ControlID& controlId, const string& objectType, const ObjectID objectID) { if (controls.size() == 0) { return HtmlTagInputTextWithLabel("control", Languages::TextControl, Languages::GetText(Languages::TextConfigureControlFirst)); } bool controlIdValid = false; if (controlId != ControlIdNone) { for (auto& control : controls) { if (control.first != controlId) { continue; } controlIdValid = true; break; } } if (!controlIdValid) { controlId = controls.begin()->first; } if (controls.size() == 1) { return HtmlTagInputHidden("control", to_string(controlId)); } std::map controlOptions; for (auto& control : controls) { controlOptions[to_string(control.first)] = control.second; } return HtmlTagSelectWithLabel("control", Languages::TextControl, controlOptions, to_string(controlId)).AddAttribute("onchange", "loadProtocol('" + objectType + "', " + to_string(objectID) + ")"); } HtmlTag WebClientStatic::HtmlTagControl(const string& name, const std::map& controls) { ControlID controlIdFirst = controls.begin()->first; if (controls.size() == 1) { return HtmlTagInputHidden("s_" + name, to_string(controlIdFirst)); } return HtmlTagSelectWithLabel(name, Languages::TextControl, controls, controlIdFirst).AddAttribute("onchange", "loadProgramModeSelector();"); } vector WebClientStatic::InterpretSlaveData(const string& prefix, const map& arguments) { vector ids; const unsigned int count = Utils::Utils::GetIntegerMapEntry(arguments, prefix + "counter", 0); for (unsigned int index = 1; index <= count; ++index) { const string indexAsString = to_string(index); const ObjectID id = Utils::Utils::GetIntegerMapEntry(arguments, prefix + "_id_" + indexAsString, ObjectNone); if (id == ObjectNone) { continue; } ids.push_back(id); } std::sort(ids.begin(), ids.end()); ids.erase(std::unique(ids.begin(), ids.end()), ids.end()); return ids; } HtmlTag WebClientStatic::HtmlTagTabMenuItem(const std::string& tabName, const Languages::TextSelector buttonValue, const bool selected, const bool hidden) { HtmlTag button("button"); button.AddClass("tab_button"); button.AddId("tab_button_" + tabName); button.AddAttribute("onclick", "ShowTab('" + tabName + "');"); button.AddContent(buttonValue); if (selected) { button.AddClass("tab_button_selected"); } if (hidden) { button.AddClass("hidden"); } return button; } HtmlTag WebClientStatic::HtmlTagSelectPropulsion(const Propulsion propulsion) { map propulsions; propulsions[PropulsionUnknown] = Languages::TextPropulsionUnknown; propulsions[PropulsionSteam] = Languages::TextPropulsionSteam; propulsions[PropulsionDiesel] = Languages::TextPropulsionDiesel; propulsions[PropulsionGas] = Languages::TextPropulsionGas; propulsions[PropulsionElectric] = Languages::TextPropulsionElectric; propulsions[PropulsionHydrogen] = Languages::TextPropulsionHydrogen; propulsions[PropulsionAccu] = Languages::TextPropulsionAccu; propulsions[PropulsionOther] = Languages::TextPropulsionOther; return HtmlTagSelectWithLabel("propulsion", Languages::TextPropulsion, propulsions, propulsion); } HtmlTag WebClientStatic::HtmlTagSelectTrainType(const TrainType trainType) { map trainTypes; trainTypes[TrainTypeUnknown] = Languages::TextTrainTypeUnknown; trainTypes[TrainTypeInternationalHighSpeed] = Languages::TextTrainTypeInternationalHighSpeed; trainTypes[TrainTypeNationalHighSpeed] = Languages::TextTrainTypeNationalHighSpeed; trainTypes[TrainTypeInternationalLongDistance] = Languages::TextTrainTypeInternationalLongDistance; trainTypes[TrainTypeNationalLongDistance] = Languages::TextTrainTypeNationalLongDistance; trainTypes[TrainTypeInternationalNight] = Languages::TextTrainTypeInternationalNight; trainTypes[TrainTypeNationalNight] = Languages::TextTrainTypeNationalNight; trainTypes[TrainTypeLongDistanceFastLocal] = Languages::TextTrainTypeLongDistanceFastLocal; trainTypes[TrainTypeFastLocal] = Languages::TextTrainTypeFastLocal; trainTypes[TrainTypeLocal] = Languages::TextTrainTypeLocal; trainTypes[TrainTypeSuburban] = Languages::TextTrainTypeSuburban; trainTypes[TrainTypeUnderground] = Languages::TextTrainTypeUnderground; trainTypes[TrainTypeHistoric] = Languages::TextTrainTypeHistoric; trainTypes[TrainTypeExtra] = Languages::TextTrainTypeExtra; trainTypes[TrainTypePassengerWithCargo] = Languages::TextTrainTypePassengerWithCargo; trainTypes[TrainTypeCargoLongDistance] = Languages::TextTrainTypeCargoLongDistance; trainTypes[TrainTypeCargoLocal] = Languages::TextTrainTypeCargoLocal; trainTypes[TrainTypeCargoBlock] = Languages::TextTrainTypeCargoBlock; trainTypes[TrainTypeCargoTractor] = Languages::TextTrainTypeCargoTractor; trainTypes[TrainTypeCargoExpress] = Languages::TextTrainTypeCargoExpress; trainTypes[TrainTypeCargoWithPassenger] = Languages::TextTrainTypeCargoWithPassenger; trainTypes[TrainTypeRescue] = Languages::TextTrainTypeRescue; trainTypes[TrainTypeConstruction] = Languages::TextTrainTypeConstruction; trainTypes[TrainTypeEmpty] = Languages::TextTrainTypeEmpty; trainTypes[TrainTypeLoco] = Languages::TextTrainTypeLoco; trainTypes[TrainTypeCleaning] = Languages::TextTrainTypeCleaning; trainTypes[TrainTypeOther] = Languages::TextTrainTypeOther; return HtmlTagSelectWithLabel("type", Languages::TextTrainType, trainTypes, trainType); } HtmlTag WebClientStatic::HtmlTagTabFunctions(const LocoFunctionEntry* locoFunctions) { HtmlTag functionsContent("div"); functionsContent.AddId("tab_functions"); functionsContent.AddClass("tab_content"); functionsContent.AddClass("hidden"); map functionTypes; functionTypes[DataModel::LocoFunctionTypeNone] = Languages::TextLocoFunctionTypeNone; functionTypes[DataModel::LocoFunctionTypePermanent] = Languages::TextLocoFunctionTypePermanent; functionTypes[DataModel::LocoFunctionTypeMoment] = Languages::TextLocoFunctionTypeMoment; // functionTypes[DataModel::LocoFunctionTypeFlashing] = Languages::TextLocoFunctionTypeFlashing; // functionTypes[DataModel::LocoFunctionTypeTimer] = Languages::TextLocoFunctionTypeTimer; map functionIcons; functionIcons[DataModel::LocoFunctionIconDefault] = Languages::TextLocoFunctionIconDefault; functionIcons[DataModel::LocoFunctionIconShuntingMode] = Languages::TextLocoFunctionIconShuntingMode; functionIcons[DataModel::LocoFunctionIconInertia] = Languages::TextLocoFunctionIconInertia; functionIcons[DataModel::LocoFunctionIconLight] = Languages::TextLocoFunctionIconLight; functionIcons[DataModel::LocoFunctionIconHeadlightLowBeamForward] = Languages::TextLocoFunctionIconHeadlightLowBeamForward; functionIcons[DataModel::LocoFunctionIconHeadlightLowBeamReverse] = Languages::TextLocoFunctionIconHeadlightLowBeamReverse; functionIcons[DataModel::LocoFunctionIconHeadlightHighBeamForward] = Languages::TextLocoFunctionIconHeadlightHighBeamForward; functionIcons[DataModel::LocoFunctionIconHeadlightHighBeamReverse] = Languages::TextLocoFunctionIconHeadlightHighBeamReverse; functionIcons[DataModel::LocoFunctionIconBacklightForward] = Languages::TextLocoFunctionIconBacklightForward; functionIcons[DataModel::LocoFunctionIconBacklightReverse] = Languages::TextLocoFunctionIconBacklightReverse; functionIcons[DataModel::LocoFunctionIconShuntingLight] = Languages::TextLocoFunctionIconShuntingLight; functionIcons[DataModel::LocoFunctionIconBlinkingLight] = Languages::TextLocoFunctionIconBlinkingLight; functionIcons[DataModel::LocoFunctionIconInteriorLight1] = Languages::TextLocoFunctionIconInteriorLight1; functionIcons[DataModel::LocoFunctionIconInteriorLight2] = Languages::TextLocoFunctionIconInteriorLight2; functionIcons[DataModel::LocoFunctionIconTableLight1] = Languages::TextLocoFunctionIconTableLight1; functionIcons[DataModel::LocoFunctionIconTableLight2] = Languages::TextLocoFunctionIconTableLight2; functionIcons[DataModel::LocoFunctionIconTableLight3] = Languages::TextLocoFunctionIconTableLight3; functionIcons[DataModel::LocoFunctionIconCabLight1] = Languages::TextLocoFunctionIconCabLight1; functionIcons[DataModel::LocoFunctionIconCabLight2] = Languages::TextLocoFunctionIconCabLight2; functionIcons[DataModel::LocoFunctionIconCabLight12] = Languages::TextLocoFunctionIconCabLight12; functionIcons[DataModel::LocoFunctionIconDriversDeskLight] = Languages::TextLocoFunctionIconDriversDeskLight; functionIcons[DataModel::LocoFunctionIconTrainDestinationIndicator] = Languages::TextLocoFunctionIconTrainDestinationIndicator; functionIcons[DataModel::LocoFunctionIconLocomotiveNumberIndicator] = Languages::TextLocoFunctionIconLocomotiveNumberIndicator; functionIcons[DataModel::LocoFunctionIconEngineLight] = Languages::TextLocoFunctionIconEngineLight; functionIcons[DataModel::LocoFunctionIconFireBox] = Languages::TextLocoFunctionIconFireBox; functionIcons[DataModel::LocoFunctionIconStairsLight] = Languages::TextLocoFunctionIconStairsLight; functionIcons[DataModel::LocoFunctionIconSmokeGenerator] = Languages::TextLocoFunctionIconSmokeGenerator; functionIcons[DataModel::LocoFunctionIconTelex1] = Languages::TextLocoFunctionIconTelex1; functionIcons[DataModel::LocoFunctionIconTelex2] = Languages::TextLocoFunctionIconTelex2; functionIcons[DataModel::LocoFunctionIconTelex12] = Languages::TextLocoFunctionIconTelex12; functionIcons[DataModel::LocoFunctionIconPanto1] = Languages::TextLocoFunctionIconPanto1; functionIcons[DataModel::LocoFunctionIconPanto2] = Languages::TextLocoFunctionIconPanto2; functionIcons[DataModel::LocoFunctionIconPanto12] = Languages::TextLocoFunctionIconPanto12; functionIcons[DataModel::LocoFunctionIconUp] = Languages::TextLocoFunctionIconUp; functionIcons[DataModel::LocoFunctionIconDown] = Languages::TextLocoFunctionIconDown; functionIcons[DataModel::LocoFunctionIconUpDown1] = Languages::TextLocoFunctionIconUpDown1; functionIcons[DataModel::LocoFunctionIconUpDown2] = Languages::TextLocoFunctionIconUpDown2; functionIcons[DataModel::LocoFunctionIconLeft] = Languages::TextLocoFunctionIconLeft; functionIcons[DataModel::LocoFunctionIconRight] = Languages::TextLocoFunctionIconRight; functionIcons[DataModel::LocoFunctionIconLeftRight] = Languages::TextLocoFunctionIconLeftRight; functionIcons[DataModel::LocoFunctionIconTurnLeft] = Languages::TextLocoFunctionIconTurnLeft; functionIcons[DataModel::LocoFunctionIconTurnRight] = Languages::TextLocoFunctionIconTurnRight; functionIcons[DataModel::LocoFunctionIconTurn] = Languages::TextLocoFunctionIconTurn; functionIcons[DataModel::LocoFunctionIconCrane] = Languages::TextLocoFunctionIconCrane; functionIcons[DataModel::LocoFunctionIconMagnet] = Languages::TextLocoFunctionIconMagnet; functionIcons[DataModel::LocoFunctionIconCraneHook] = Languages::TextLocoFunctionIconCraneHook; functionIcons[DataModel::LocoFunctionIconFan] = Languages::TextLocoFunctionIconFan; functionIcons[DataModel::LocoFunctionIconBreak] = Languages::TextLocoFunctionIconBreak; functionIcons[DataModel::LocoFunctionIconNoSound] = Languages::TextLocoFunctionIconNoSound; functionIcons[DataModel::LocoFunctionIconSoundGeneral] = Languages::TextLocoFunctionIconSoundGeneral; functionIcons[DataModel::LocoFunctionIconRunning1] = Languages::TextLocoFunctionIconRunning1; functionIcons[DataModel::LocoFunctionIconRunning2] = Languages::TextLocoFunctionIconRunning2; functionIcons[DataModel::LocoFunctionIconEngine1] = Languages::TextLocoFunctionIconEngine1; functionIcons[DataModel::LocoFunctionIconEngine2] = Languages::TextLocoFunctionIconEngine2; functionIcons[DataModel::LocoFunctionIconBreak1] = Languages::TextLocoFunctionIconBreak1; functionIcons[DataModel::LocoFunctionIconBreak2] = Languages::TextLocoFunctionIconBreak2; functionIcons[DataModel::LocoFunctionIconCurve] = Languages::TextLocoFunctionIconCurve; functionIcons[DataModel::LocoFunctionIconHorn1] = Languages::TextLocoFunctionIconHorn1; functionIcons[DataModel::LocoFunctionIconHorn2] = Languages::TextLocoFunctionIconHorn2; functionIcons[DataModel::LocoFunctionIconWhistle1] = Languages::TextLocoFunctionIconWhistle1; functionIcons[DataModel::LocoFunctionIconWhistle2] = Languages::TextLocoFunctionIconWhistle2; functionIcons[DataModel::LocoFunctionIconBell] = Languages::TextLocoFunctionIconBell; functionIcons[DataModel::LocoFunctionIconStationAnnouncement1] = Languages::TextLocoFunctionIconStationAnnouncement1; functionIcons[DataModel::LocoFunctionIconStationAnnouncement2] = Languages::TextLocoFunctionIconStationAnnouncement2; functionIcons[DataModel::LocoFunctionIconStationAnnouncement3] = Languages::TextLocoFunctionIconStationAnnouncement3; functionIcons[DataModel::LocoFunctionIconSpeak] = Languages::TextLocoFunctionIconSpeak; functionIcons[DataModel::LocoFunctionIconRadio] = Languages::TextLocoFunctionIconRadio; functionIcons[DataModel::LocoFunctionIconMusic1] = Languages::TextLocoFunctionIconMusic1; functionIcons[DataModel::LocoFunctionIconMusic2] = Languages::TextLocoFunctionIconMusic2; functionIcons[DataModel::LocoFunctionIconOpenDoor] = Languages::TextLocoFunctionIconOpenDoor; functionIcons[DataModel::LocoFunctionIconCloseDoor] = Languages::TextLocoFunctionIconCloseDoor; functionIcons[DataModel::LocoFunctionIconFan1] = Languages::TextLocoFunctionIconFan1; functionIcons[DataModel::LocoFunctionIconFan2] = Languages::TextLocoFunctionIconFan2; functionIcons[DataModel::LocoFunctionIconFan3] = Languages::TextLocoFunctionIconFan3; functionIcons[DataModel::LocoFunctionIconShovelCoal] = Languages::TextLocoFunctionIconShovelCoal; functionIcons[DataModel::LocoFunctionIconCompressedAir] = Languages::TextLocoFunctionIconCompressedAir; functionIcons[DataModel::LocoFunctionIconReliefValve] = Languages::TextLocoFunctionIconReliefValve; functionIcons[DataModel::LocoFunctionIconSteamBlowOut] = Languages::TextLocoFunctionIconSteamBlowOut; functionIcons[DataModel::LocoFunctionIconSteamBlow] = Languages::TextLocoFunctionIconSteamBlow; functionIcons[DataModel::LocoFunctionIconDrainValve] = Languages::TextLocoFunctionIconDrainValve; functionIcons[DataModel::LocoFunctionIconShakingRust] = Languages::TextLocoFunctionIconShakingRust; functionIcons[DataModel::LocoFunctionIconAirPump] = Languages::TextLocoFunctionIconAirPump; functionIcons[DataModel::LocoFunctionIconWaterPump] = Languages::TextLocoFunctionIconWaterPump; functionIcons[DataModel::LocoFunctionIconBufferPush] = Languages::TextLocoFunctionIconBufferPush; functionIcons[DataModel::LocoFunctionIconGenerator] = Languages::TextLocoFunctionIconGenerator; functionIcons[DataModel::LocoFunctionIconGearBox] = Languages::TextLocoFunctionIconGearBox; functionIcons[DataModel::LocoFunctionIconGearUp] = Languages::TextLocoFunctionIconGearUp; functionIcons[DataModel::LocoFunctionIconGearDown] = Languages::TextLocoFunctionIconGearDown; functionIcons[DataModel::LocoFunctionIconFillWater] = Languages::TextLocoFunctionIconFillWater; functionIcons[DataModel::LocoFunctionIconFillDiesel] = Languages::TextLocoFunctionIconFillDiesel; functionIcons[DataModel::LocoFunctionIconFillGas] = Languages::TextLocoFunctionIconFillGas; functionIcons[DataModel::LocoFunctionIconSand] = Languages::TextLocoFunctionIconSand; functionIcons[DataModel::LocoFunctionIconRailJoint] = Languages::TextLocoFunctionIconRailJoint; functionIcons[DataModel::LocoFunctionIconCoupler] = Languages::TextLocoFunctionIconCoupler; functionIcons[DataModel::LocoFunctionIconPanto] = Languages::TextLocoFunctionIconPanto; functionIcons[DataModel::LocoFunctionIconMainSwitch] = Languages::TextLocoFunctionIconMainSwitch; functionIcons[DataModel::LocoFunctionIconSoundLouder] = Languages::TextLocoFunctionIconSoundLouder; functionIcons[DataModel::LocoFunctionIconSoundLower] = Languages::TextLocoFunctionIconSoundLower; functionIcons[DataModel::LocoFunctionIconNoBreak] = Languages::TextLocoFunctionIconNoBreak; for (unsigned int nr = 0; nr < NumberOfLocoFunctions; ++nr) { HtmlTag fDiv("div"); fDiv.AddClass("function_line"); string nrString = to_string(nr); string fNrString = "f" + nrString; fDiv.AddChildTag(HtmlTagLabel("F" + nrString, fNrString + "_type")); const DataModel::LocoFunctionType functionType = locoFunctions[nr].type; DataModel::LocoFunctionIcon icon = locoFunctions[nr].icon; DataModel::LocoFunctionTimer timer = locoFunctions[nr].timer; fDiv.AddChildTag(HtmlTagSelect(fNrString + "_type", functionTypes, functionType).AddAttribute("onchange", "onChangeLocoFunctionType(" + nrString + ");return false;")); HtmlTagSelect selectIcon(fNrString + "_icon", functionIcons, icon); HtmlTagInputInteger inputTimer(fNrString + "_timer", timer, 1, 255); if (functionType == LocoFunctionTypeNone) { selectIcon.AddClass("hidden"); } if (functionType != LocoFunctionTypeTimer) { inputTimer.AddClass("hidden"); } inputTimer.AddClass("function_line_integer"); fDiv.AddChildTag(selectIcon); fDiv.AddChildTag(inputTimer); functionsContent.AddChildTag(fDiv); } return functionsContent; } HtmlTag WebClientStatic::HtmlTagTabAutomode(const bool pushpull, const Speed maxSpeed, const Speed travelSpeed, const Speed reducedSpeed, const Speed creepingSpeed) { HtmlTag automodeContent("div"); automodeContent.AddId("tab_automode"); automodeContent.AddClass("tab_content"); automodeContent.AddClass("hidden"); automodeContent.AddChildTag(HtmlTagInputCheckboxWithLabel("pushpull", Languages::TextPushPullTrain, "pushpull", pushpull)); automodeContent.AddChildTag(HtmlTagInputIntegerWithLabel("maxspeed", Languages::TextMaxSpeed, maxSpeed, 0, MaxSpeed)); automodeContent.AddChildTag(HtmlTagInputIntegerWithLabel("travelspeed", Languages::TextTravelSpeed, travelSpeed, 0, MaxSpeed)); automodeContent.AddChildTag(HtmlTagInputIntegerWithLabel("reducedspeed", Languages::TextReducedSpeed, reducedSpeed, 0, MaxSpeed)); automodeContent.AddChildTag(HtmlTagInputIntegerWithLabel("creepingspeed", Languages::TextCreepingSpeed, creepingSpeed, 0, MaxSpeed)); return automodeContent; } vector WebClientStatic::ConvertSlaveIDVectorToRelation(Manager& manager, const MultipleUnitID multipleUnitID, const vector& slaveIDs) { vector slaves; for (auto const & slaveID : slaveIDs) { slaves.push_back(new Relation(&manager, ObjectIdentifier(ObjectTypeMultipleUnit, multipleUnitID), ObjectIdentifier(ObjectTypeLoco, slaveID), Relation::RelationTypeMultipleUnitLoco)); } return slaves; } }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/WebClientStatic.h000066400000000000000000000121161500456250600220150ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once //#include #include #include #include #include "Server/Web/HtmlTag.h" #include "Server/Web/HtmlTagInputHidden.h" #include "Server/Web/HtmlTagSelectWithLabel.h" namespace Server { namespace Web { class WebClientStatic { public: WebClientStatic() = delete; WebClientStatic(const WebClientStatic&) = delete; WebClientStatic& operator=(const WebClientStatic&) = delete; static HtmlTag HtmlTagSelectExecuteAccessory(const bool executeAccessory = true); static HtmlTag HtmlTagSelectSelectRouteApproach(const DataModel::SelectRouteApproach selectRouteApproach, const bool addDefault = true); static HtmlTag HtmlTagControlArgument(const unsigned char argNr, const ArgumentType type, const std::string& value); template static HtmlTag HtmlTagMatchKey(const std::map& matchKeyMap, const std::string& selectedMatchKey) { if (matchKeyMap.size() == 0) { return HtmlTagInputHidden("matchkey", ""); } std::map options; for (auto& matchKey : matchKeyMap) { const std::string& name = matchKey.second.GetName(); const std::string& key = matchKey.second.GetMatchKey(); options[key] = name + (name != key ? (" (" + key + ")") : ""); } return HtmlTagSelectWithLabel("matchkey", Languages::TextNameInControl, options, selectedMatchKey); } static HtmlTag HtmlTagProtocol(const std::map& protocolMap, const Protocol selectedProtocol); static HtmlTag HtmlTagAccessoryAddress(const DataModel::AccessoryType type, const Address address, const AddressPort port); static inline HtmlTag HtmlTagDuration(const DataModel::AccessoryPulseDuration duration) { return HtmlTagDuration(duration, Languages::TextDuration); } static HtmlTag HtmlTagDuration(const DataModel::AccessoryPulseDuration duration, const Languages::TextSelector label); static HtmlTag HtmlTagRotation(const DataModel::LayoutItem::LayoutRotation rotation); static HtmlTag HtmlTagNrOfTracksToReserve(const DataModel::Loco::NrOfTracksToReserve nrOfTracksToReserve); static HtmlTag HtmlTagLogLevel(); static HtmlTag HtmlTagLanguage(); static HtmlTag HtmlTagStartupLocos(const StartupInitLocos startupInitLocos); static HtmlTag HtmlTagControlArguments(const HardwareType hardwareType, const std::string& arg1 = "", const std::string& arg2 = "", const std::string& arg3 = "", const std::string& arg4 = "", const std::string& arg5 = ""); static HtmlTag HtmlTagControl(const std::map& controls, ControlID& controlId, const std::string& objectType, const ObjectID objectID); static HtmlTag HtmlTagControl(const std::string& name, const std::map& controls); static HtmlTag HtmlTagSlaveEntry(const std::string& prefix, const std::string& priority, const ObjectID objectId, const std::map& options); static std::map GetLocoSlaveOptions(const LocoID locoID = LocoNone); static const std::map ListHardwareNames(); static std::vector InterpretSlaveData(const std::string& prefix, const std::map& arguments); static HtmlTag HtmlTagTabMenuItem(const std::string& tabName, const Languages::TextSelector buttonValue, const bool selected = false, const bool hidden = false); static HtmlTag HtmlTagSelectPropulsion(const Propulsion propulsion); static HtmlTag HtmlTagSelectTrainType(const TrainType trainType); static HtmlTag HtmlTagTabFunctions(const DataModel::LocoFunctionEntry* locoFunctions); static HtmlTag HtmlTagTabAutomode(const bool pushpull, const Speed maxSpeed, const Speed travelSpeed, const Speed reducedSpeed, const Speed creepingSpeed); static inline DataModel::ObjectIdentifier LocoIdToObjectIdentifier(const LocoID locoID) { const MultipleUnitID multipleUnitID = locoID & (~MultipleUnitIdPrefix); if (locoID == multipleUnitID) { return DataModel::ObjectIdentifier(ObjectTypeLoco, locoID); } return DataModel::ObjectIdentifier(ObjectTypeMultipleUnit, multipleUnitID); } static std::vector ConvertSlaveIDVectorToRelation(Manager& manager, const MultipleUnitID multipleUnitID, const std::vector& slaveIDs); }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/WebClientText.cpp000066400000000000000000000172351500456250600220540ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include "DataModel/Text.h" #include "Utils/Utils.h" #include "Server/Web/HtmlTag.h" #include "Server/Web/HtmlTagButtonCancel.h" #include "Server/Web/HtmlTagButtonCommandWide.h" #include "Server/Web/HtmlTagButtonOK.h" #include "Server/Web/HtmlTagButtonPopupWide.h" #include "Server/Web/HtmlTagInputHidden.h" #include "Server/Web/HtmlTagInputIntegerWithLabel.h" #include "Server/Web/HtmlTagInputTextWithLabel.h" #include "Server/Web/HtmlTagText.h" #include "Server/Web/WebClient.h" #include "Server/Web/WebClientText.h" using namespace DataModel; using LayoutPosition = DataModel::LayoutItem::LayoutPosition; using LayoutItemSize = DataModel::LayoutItem::LayoutItemSize; using LayoutRotation = DataModel::LayoutItem::LayoutRotation; using std::map; using std::string; using std::to_string; using std::vector; namespace Server { namespace Web { void WebClientText::HandleTextEdit(const map& arguments) { HtmlTag content; TextID textID = Utils::Utils::GetIntegerMapEntry(arguments, "text", TextNone); string name = Languages::GetText(Languages::TextNew); LayoutPosition posx = Utils::Utils::GetIntegerMapEntry(arguments, "posx", 0); LayoutPosition posy = Utils::Utils::GetIntegerMapEntry(arguments, "posy", 0); LayoutPosition posz = Utils::Utils::GetIntegerMapEntry(arguments, "posz", LayerUndeletable); LayoutItemSize width = Utils::Utils::GetIntegerMapEntry(arguments, "width", 1); LayoutRotation rotation = Utils::Utils::GetIntegerMapEntry(arguments, "rotation", DataModel::LayoutItem::Rotation0); if (textID > TextNone) { const DataModel::Text* text = manager.GetText(textID); if (text != nullptr) { name = text->GetName(); posx = text->GetPosX(); posy = text->GetPosY(); posz = text->GetPosZ(); width = text->GetWidth(); rotation = text->GetRotation(); } } content.AddChildTag(HtmlTag("h1").AddContent(name).AddId("popup_title")); HtmlTag tabMenu("div"); tabMenu.AddChildTag(WebClientStatic::HtmlTagTabMenuItem("main", Languages::TextBasic, true)); tabMenu.AddChildTag(WebClientStatic::HtmlTagTabMenuItem("position", Languages::TextPosition)); content.AddChildTag(tabMenu); HtmlTag formContent; formContent.AddChildTag(HtmlTagInputHidden("cmd", "textsave")); formContent.AddChildTag(HtmlTagInputHidden("text", to_string(textID))); HtmlTag mainContent("div"); mainContent.AddId("tab_main"); mainContent.AddClass("tab_content"); mainContent.AddChildTag(HtmlTagInputTextWithLabel("name", Languages::TextName, name).AddAttribute("onkeyup", "updateName();")); mainContent.AddChildTag(HtmlTagInputIntegerWithLabel("width", Languages::TextWidth, width, DataModel::Text::MinWidth, DataModel::Text::MaxWidth)); formContent.AddChildTag(mainContent); formContent.AddChildTag(client.HtmlTagTabPosition(posx, posy, posz, rotation)); content.AddChildTag(HtmlTag("div").AddClass("popup_content").AddChildTag(HtmlTag("form").AddId("editform").AddChildTag(formContent))); content.AddChildTag(HtmlTagButtonCancel()); content.AddChildTag(HtmlTagButtonOK()); client.ReplyHtmlWithHeader(content); } void WebClientText::HandleTextSave(const map& arguments) { TextID textID = Utils::Utils::GetIntegerMapEntry(arguments, "text", TextNone); string name = Utils::Utils::GetStringMapEntry(arguments, "name"); LayoutPosition posX = Utils::Utils::GetIntegerMapEntry(arguments, "posx", 0); LayoutPosition posY = Utils::Utils::GetIntegerMapEntry(arguments, "posy", 0); LayoutPosition posZ = Utils::Utils::GetIntegerMapEntry(arguments, "posz", 0); LayoutItemSize width = Utils::Utils::GetIntegerMapEntry(arguments, "width", 1); LayoutRotation rotation = Utils::Utils::GetIntegerMapEntry(arguments, "rotation", DataModel::LayoutItem::Rotation0); string result; if (!manager.TextSave(textID, name, posX, posY, posZ, width, rotation, result)) { client.ReplyResponse(WebClient::ResponseError, result); return; } client.ReplyResponse(WebClient::ResponseInfo, Languages::TextTextSaved, name); } void WebClientText::HandleTextList() { HtmlTag content; content.AddChildTag(HtmlTag("h1").AddContent(Languages::TextTexts)); HtmlTag table("table"); const map textList = manager.TextListByName(); map textArgument; for (auto& text : textList) { HtmlTag row("tr"); row.AddChildTag(HtmlTag("td").AddContent(text.first)); const string& textIdString = to_string(text.second->GetID()); textArgument["text"] = textIdString; row.AddChildTag(HtmlTag("td").AddChildTag(HtmlTagButtonPopupWide(Languages::TextEdit, "textedit_list_" + textIdString, textArgument))); row.AddChildTag(HtmlTag("td").AddChildTag(HtmlTagButtonPopupWide(Languages::TextDelete, "textaskdelete_" + textIdString, textArgument))); table.AddChildTag(row); } content.AddChildTag(HtmlTag("div").AddClass("popup_content").AddChildTag(table)); content.AddChildTag(HtmlTagButtonCancel()); content.AddChildTag(HtmlTagButtonPopupWide(Languages::TextNew, "textedit_0")); client.ReplyHtmlWithHeader(content); } void WebClientText::HandleTextAskDelete(const map& arguments) { TextID textID = Utils::Utils::GetIntegerMapEntry(arguments, "text", TextNone); if (textID == TextNone) { client.ReplyHtmlWithHeaderAndParagraph(Languages::TextTextDoesNotExist); return; } const DataModel::Text* text = manager.GetText(textID); if (text == nullptr) { client.ReplyHtmlWithHeaderAndParagraph(Languages::TextTextDoesNotExist); return; } HtmlTag content; const string& textName = text->GetName(); content.AddContent(HtmlTag("h1").AddContent(Languages::TextDeleteText)); content.AddContent(HtmlTag("p").AddContent(Languages::TextAreYouSureToDelete, textName)); content.AddContent(HtmlTag("form").AddId("editform") .AddContent(HtmlTagInputHidden("cmd", "textdelete")) .AddContent(HtmlTagInputHidden("text", to_string(textID)) )); content.AddContent(HtmlTagButtonCancel()); content.AddContent(HtmlTagButtonOK()); client.ReplyHtmlWithHeader(content); } void WebClientText::HandleTextDelete(const map& arguments) { TextID textID = Utils::Utils::GetIntegerMapEntry(arguments, "text", TextNone); const DataModel::Text* text = manager.GetText(textID); if (text == nullptr) { client.ReplyResponse(WebClient::ResponseError, Languages::TextTextDoesNotExist); return; } string name = text->GetName(); string result; if (!manager.TextDelete(textID, result)) { client.ReplyResponse(WebClient::ResponseError, result); return; } client.ReplyResponse(WebClient::ResponseInfo, Languages::TextTextDeleted, name); } void WebClientText::HandleTextGet(const map& arguments) { TextID textID = Utils::Utils::GetIntegerMapEntry(arguments, "text"); const DataModel::Text* text = manager.GetText(textID); if (text == nullptr) { client.ReplyHtmlWithHeader(HtmlTag()); return; } client.ReplyHtmlWithHeader(HtmlTagText(text)); } }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/WebClientText.h000066400000000000000000000032161500456250600215130ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include "Logger/Logger.h" #include "Manager.h" namespace Server { namespace Web { class WebClient; class WebClientText { public: WebClientText() = delete; WebClientText(const WebClientText&) = delete; WebClientText& operator=(const WebClientText&) = delete; inline WebClientText(Manager& manager, WebClient& client) : manager(manager), client(client) { } void HandleTextEdit(const std::map& arguments); void HandleTextSave(const std::map& arguments); void HandleTextList(); void HandleTextAskDelete(const std::map& arguments); void HandleTextDelete(const std::map& arguments); void HandleTextGet(const std::map& arguments); private: Manager& manager; WebClient& client; }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/WebClientTrack.cpp000066400000000000000000000521051500456250600221670ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include "DataModel/ObjectIdentifier.h" #include "DataModel/Track.h" #include "Utils/Utils.h" #include "Server/Web/HtmlTag.h" #include "Server/Web/HtmlTagButtonCancel.h" #include "Server/Web/HtmlTagButtonCommandWide.h" #include "Server/Web/HtmlTagButtonOK.h" #include "Server/Web/HtmlTagButtonPopupWide.h" #include "Server/Web/HtmlTagInputCheckboxWithLabel.h" #include "Server/Web/HtmlTagInputIntegerWithLabel.h" #include "Server/Web/HtmlTagInputHidden.h" #include "Server/Web/HtmlTagInputTextWithLabel.h" #include "Server/Web/HtmlTagSelect.h" #include "Server/Web/HtmlTagSelectWithLabel.h" #include "Server/Web/HtmlTagTrack.h" #include "Server/Web/WebClient.h" #include "Server/Web/WebClientCluster.h" #include "Server/Web/WebClientStatic.h" #include "Server/Web/WebClientTrack.h" using namespace DataModel; using LayoutPosition = DataModel::LayoutItem::LayoutPosition; using LayoutItemSize = DataModel::LayoutItem::LayoutItemSize; using LayoutRotation = DataModel::LayoutItem::LayoutRotation; using std::map; using std::string; using std::to_string; using std::vector; namespace Server { namespace Web { map WebClientTrack::GetFeedbackOptions(const TrackID trackId) const { map feedbackOptions; map allFeedbacks = manager.FeedbackListByName(); for (auto& feedback : allFeedbacks) { Track* trackOfFeedback = feedback.second->GetTrack(); if (trackOfFeedback && (trackOfFeedback->GetID() != trackId)) { continue; } feedbackOptions[feedback.first] = feedback.second->GetID(); } return feedbackOptions; } map WebClientTrack::GetSignalOptions(const TrackID trackId) const { map signalOptions; map allSignals = manager.SignalListByName(); for (auto& signal : allSignals) { Track* trackOfSignal = signal.second->GetTrack(); if (trackOfSignal && (trackOfSignal->GetID() != trackId)) { continue; } signalOptions[signal.first] = signal.second->GetID(); } return signalOptions; } HtmlTag WebClientTrack::HtmlTagSelectTrack(const std::string& name, const Languages::TextSelector label, const Languages::TextSelector hint, const TrackID trackID, const TrackID excludeTrackID, const string& onchange) const { HtmlTag tag; map tracks = manager.TrackListIdByName(excludeTrackID); tracks["-"] = TrackNone; HtmlTagSelectWithLabel selectTrack(name, label, hint, tracks, trackID); if (onchange.size() > 0) { selectTrack.AddAttribute("onchange", onchange); } tag.AddChildTag(selectTrack); return tag; } void WebClientTrack::HandleTrackEdit(const map& arguments) { HtmlTag content; TrackID trackID = Utils::Utils::GetIntegerMapEntry(arguments, "track", TrackNone); string name = Languages::GetText(Languages::TextNew); bool showName = true; string displayName; LayoutPosition posx = Utils::Utils::GetIntegerMapEntry(arguments, "posx", 0); LayoutPosition posy = Utils::Utils::GetIntegerMapEntry(arguments, "posy", 0); LayoutPosition posz = Utils::Utils::GetIntegerMapEntry(arguments, "posz", 0); LayoutItemSize height = Utils::Utils::GetIntegerMapEntry(arguments, "length", DataModel::LayoutItem::Height1); LayoutRotation rotation = Utils::Utils::GetIntegerMapEntry(arguments, "rotation", DataModel::LayoutItem::Rotation0); DataModel::TrackType type = DataModel::TrackTypeStraight; TrackID main = TrackNone; vector feedbacks; vector signals; Cluster* cluster = nullptr; DataModel::SelectRouteApproach selectRouteApproach = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "selectrouteapproach", DataModel::SelectRouteSystemDefault)); bool allowLocoTurn = Utils::Utils::GetBoolMapEntry(arguments, "allowlocoturn", false); bool releaseWhenFree = Utils::Utils::GetBoolMapEntry(arguments, "releasewhenfree", false); if (trackID > TrackNone) { const DataModel::Track* track = manager.GetTrack(trackID); if (track != nullptr) { name = track->GetName(); showName = track->GetShowName(); displayName = track->GetDisplayName(); posx = track->GetPosX(); posy = track->GetPosY(); posz = track->GetPosZ(); height = track->GetHeight(); rotation = track->GetRotation(); type = track->GetTrackType(); main = track->GetOwnMainID(); feedbacks = track->GetFeedbacks(); signals = track->GetSignals(); cluster = track->GetCluster(); selectRouteApproach = track->GetSelectRouteApproach(); allowLocoTurn = track->GetAllowLocoTurn(); releaseWhenFree = track->GetReleaseWhenFree(); } } switch (type) { case DataModel::TrackTypeTurn: case DataModel::TrackTypeTunnelEnd: height = DataModel::LayoutItem::Height1; break; case DataModel::TrackTypeCrossingLeft: case DataModel::TrackTypeCrossingRight: case DataModel::TrackTypeCrossingSymetric: height = DataModel::LayoutItem::Height2; break; default: break; } content.AddChildTag(HtmlTag("h1").AddContent(name).AddId("popup_title")); HtmlTag tabMenu("div"); tabMenu.AddChildTag(WebClientStatic::HtmlTagTabMenuItem("main", Languages::TextBasic, true)); tabMenu.AddChildTag(WebClientStatic::HtmlTagTabMenuItem("position", Languages::TextPosition)); if (main != TrackNone) { tabMenu.AddChildTag(WebClientStatic::HtmlTagTabMenuItem("feedbacks", Languages::TextFeedbacks).AddClass("hidden")); tabMenu.AddChildTag(WebClientStatic::HtmlTagTabMenuItem("signals", Languages::TextSignals).AddClass("hidden")); tabMenu.AddChildTag(WebClientStatic::HtmlTagTabMenuItem("automode", Languages::TextAutomode).AddClass("hidden")); } else { tabMenu.AddChildTag(WebClientStatic::HtmlTagTabMenuItem("feedbacks", Languages::TextFeedbacks)); tabMenu.AddChildTag(WebClientStatic::HtmlTagTabMenuItem("signals", Languages::TextSignals)); tabMenu.AddChildTag(WebClientStatic::HtmlTagTabMenuItem("automode", Languages::TextAutomode)); } content.AddChildTag(tabMenu); HtmlTag formContent("form"); formContent.AddId("editform"); formContent.AddChildTag(HtmlTagInputHidden("cmd", "tracksave")); formContent.AddChildTag(HtmlTagInputHidden("track", to_string(trackID))); std::map typeOptions; typeOptions[DataModel::TrackTypeStraight] = Languages::TextStraight; typeOptions[DataModel::TrackTypeTurn] = Languages::TextTurn; typeOptions[DataModel::TrackTypeEnd] = Languages::TextBufferStop; typeOptions[DataModel::TrackTypeBridge] = Languages::TextBridge; typeOptions[DataModel::TrackTypeTunnel] = Languages::TextTunnelTwoSides; typeOptions[DataModel::TrackTypeTunnelEnd] = Languages::TextTunnelOneSide; typeOptions[DataModel::TrackTypeLink] = Languages::TextLink; typeOptions[DataModel::TrackTypeCrossingLeft] = Languages::TextCrossingLeft; typeOptions[DataModel::TrackTypeCrossingRight] = Languages::TextCrossingRight; typeOptions[DataModel::TrackTypeCrossingSymetric] = Languages::TextCrossingSymetric; HtmlTag mainContent("div"); mainContent.AddId("tab_main"); mainContent.AddClass("tab_content"); mainContent.AddChildTag(HtmlTagSelectWithLabel("tracktype", Languages::TextType, typeOptions, type).AddAttribute("onchange", "onChangeTrackTypeMainTrack();return false;")); mainContent.AddChildTag(HtmlTagSelectTrack("main", Languages::TextMainTrack, Languages::TextMainTrackHint, main, trackID, "onChangeTrackTypeMainTrack();return false;")); HtmlTag i_name("div"); i_name.AddId("i_name"); i_name.AddChildTag(HtmlTagInputTextWithLabel("name", Languages::TextName, name).AddAttribute("onkeyup", "updateName();")); if (main != TrackNone) { i_name.AddClass("hidden"); } mainContent.AddChildTag(i_name); HtmlTag i_showName("div"); i_showName.AddId("i_showname"); i_showName.AddChildTag(HtmlTagInputCheckboxWithLabel("showname", Languages::TextShowName, "true", showName).AddAttribute("onchange", "onChangeTrackTypeMainTrack();return false;")); if ((type != DataModel::TrackTypeStraight) && (main != TrackNone)) { i_showName.AddClass("hidden"); } mainContent.AddChildTag(i_showName); HtmlTag i_displayName("div"); i_displayName.AddId("i_displayname"); i_displayName.AddChildTag(HtmlTagInputTextWithLabel("displayname", Languages::TextDisplayName, displayName)); if ((main != TrackNone) || (type != DataModel::TrackTypeStraight) || (!showName)) { i_displayName.AddClass("hidden"); } mainContent.AddChildTag(i_displayName); HtmlTag i_length("div"); i_length.AddId("i_length"); i_length.AddChildTag(HtmlTagInputIntegerWithLabel("length", Languages::TextLength, height, DataModel::Track::MinLength, DataModel::Track::MaxLength)); switch (type) { case DataModel::TrackTypeTurn: case DataModel::TrackTypeTunnelEnd: case DataModel::TrackTypeCrossingLeft: case DataModel::TrackTypeCrossingRight: case DataModel::TrackTypeCrossingSymetric: i_length.AddClass("hidden"); break; default: break; } mainContent.AddChildTag(i_length); formContent.AddChildTag(mainContent); formContent.AddChildTag(client.HtmlTagTabPosition(posx, posy, posz, rotation)); formContent.AddChildTag(client.HtmlTagSelectSlave("feedback", feedbacks, GetFeedbackOptions(trackID))); formContent.AddChildTag(client.HtmlTagSelectSlave("signal", signals, GetSignalOptions(trackID))); formContent.AddChildTag(HtmlTagTabTrackAutomode(selectRouteApproach, allowLocoTurn, releaseWhenFree, cluster)); content.AddChildTag(HtmlTag("div").AddClass("popup_content").AddChildTag(formContent)); content.AddChildTag(HtmlTagButtonCancel()); content.AddChildTag(HtmlTagButtonOK()); client.ReplyHtmlWithHeader(content); } void WebClientTrack::HandleTrackSave(const map& arguments) { const TrackID trackId = Utils::Utils::GetIntegerMapEntry(arguments, "track", TrackNone); const string name = Utils::Utils::GetStringMapEntry(arguments, "name"); const bool showName = Utils::Utils::GetBoolMapEntry(arguments, "showname", true); const string displayName = Utils::Utils::GetStringMapEntry(arguments, "displayname"); const LayoutPosition posX = Utils::Utils::GetIntegerMapEntry(arguments, "posx", 0); const LayoutPosition posY = Utils::Utils::GetIntegerMapEntry(arguments, "posy", 0); const LayoutPosition posZ = Utils::Utils::GetIntegerMapEntry(arguments, "posz", 0); LayoutItemSize height; const LayoutRotation rotation = Utils::Utils::GetIntegerMapEntry(arguments, "rotation", DataModel::LayoutItem::Rotation0); const DataModel::TrackType type = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "tracktype", DataModel::TrackTypeStraight)); const TrackID main = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "main", TrackNone)); switch (type) { case DataModel::TrackTypeTurn: case DataModel::TrackTypeTunnelEnd: height = DataModel::LayoutItem::Height1; break; case DataModel::TrackTypeCrossingLeft: case DataModel::TrackTypeCrossingRight: case DataModel::TrackTypeCrossingSymetric: height = DataModel::LayoutItem::Height2; break; default: height = Utils::Utils::GetIntegerMapEntry(arguments, "length", 1); break; } vector feedbacks; { vector feedbackIDs = WebClientStatic::InterpretSlaveData("feedback", arguments); for (auto feedbackId : feedbackIDs) { feedbacks.push_back(new Relation(&manager, ObjectIdentifier(ObjectTypeTrack, trackId), ObjectIdentifier(ObjectTypeFeedback, feedbackId), Relation::RelationTypeTrackFeedback)); } } vector signals; { vector signalIds = WebClientStatic::InterpretSlaveData("signal", arguments); for (auto signalId : signalIds) { signals.push_back(new Relation(&manager, ObjectIdentifier(ObjectTypeTrack, trackId), ObjectIdentifier(ObjectTypeSignal, signalId), Relation::RelationTypeTrackSignal)); } } const DataModel::SelectRouteApproach selectRouteApproach = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "selectrouteapproach", DataModel::SelectRouteSystemDefault)); const bool allowLocoTurn = Utils::Utils::GetBoolMapEntry(arguments, "allowlocoturn", false); const bool releaseWhenFree = Utils::Utils::GetBoolMapEntry(arguments, "releasewhenfree", false); string result; if (!manager.TrackSave(trackId, name, showName, displayName, posX, posY, posZ, height, rotation, type, main, feedbacks, signals, selectRouteApproach, allowLocoTurn, releaseWhenFree, result)) { client.ReplyResponse(WebClient::ResponseError, result); return; } client.ReplyResponse(WebClient::ResponseInfo, Languages::TextTrackSaved, name); } void WebClientTrack::HandleTrackAskDelete(const map& arguments) { TrackID trackID = Utils::Utils::GetIntegerMapEntry(arguments, "track", TrackNone); if (trackID == TrackNone) { client.ReplyHtmlWithHeaderAndParagraph(Languages::TextTrackDoesNotExist); return; } const DataModel::Track* track = manager.GetTrack(trackID); if (track == nullptr) { client.ReplyHtmlWithHeaderAndParagraph(Languages::TextTrackDoesNotExist); return; } HtmlTag content; const string& trackName = track->GetName(); content.AddContent(HtmlTag("h1").AddContent(Languages::TextDeleteTrack)); content.AddContent(HtmlTag("p").AddContent(Languages::TextAreYouSureToDelete, trackName)); content.AddContent(HtmlTag("form").AddId("editform") .AddContent(HtmlTagInputHidden("cmd", "trackdelete")) .AddContent(HtmlTagInputHidden("track", to_string(trackID)) )); content.AddContent(HtmlTagButtonCancel()); content.AddContent(HtmlTagButtonOK()); client.ReplyHtmlWithHeader(content); } void WebClientTrack::HandleTrackList() { HtmlTag content; content.AddChildTag(HtmlTag("h1").AddContent(Languages::TextTracks)); HtmlTag table("table"); const map trackList = manager.TrackListByName(); map trackArgument; for (auto& track : trackList) { HtmlTag row("tr"); row.AddChildTag(HtmlTag("td").AddContent(track.first)); const string& trackIdString = to_string(track.second->GetID()); trackArgument["track"] = trackIdString; row.AddChildTag(HtmlTag("td").AddChildTag(HtmlTagButtonPopupWide(Languages::TextEdit, "trackedit_list_" + trackIdString, trackArgument))); row.AddChildTag(HtmlTag("td").AddChildTag(HtmlTagButtonPopupWide(Languages::TextDelete, "trackaskdelete_" + trackIdString, trackArgument))); if (track.second->IsInUse()) { row.AddChildTag(HtmlTag("td").AddChildTag(HtmlTagButtonCommandWide(Languages::TextRelease, "trackrelease_" + trackIdString, trackArgument, "hideElement('b_trackrelease_" + trackIdString + "');"))); } table.AddChildTag(row); } content.AddChildTag(HtmlTag("div").AddClass("popup_content").AddChildTag(table)); content.AddChildTag(HtmlTagButtonCancel()); content.AddChildTag(HtmlTagButtonPopupWide(Languages::TextNew, "trackedit_0")); client.ReplyHtmlWithHeader(content); } void WebClientTrack::HandleTrackDelete(const map& arguments) { TrackID trackID = Utils::Utils::GetIntegerMapEntry(arguments, "track", TrackNone); const DataModel::Track* track = manager.GetTrack(trackID); if (track == nullptr) { client.ReplyResponse(WebClient::ResponseError, Languages::TextTrackDoesNotExist); return; } string name = track->GetName(); string result; if (!manager.TrackDelete(trackID, result)) { client.ReplyResponse(WebClient::ResponseError, result); return; } client.ReplyResponse(WebClient::ResponseInfo, Languages::TextTrackDeleted, name); } void WebClientTrack::HandleTrackGet(const map& arguments) { TrackID trackID = Utils::Utils::GetIntegerMapEntry(arguments, "track"); const DataModel::Track* track = manager.GetTrack(trackID); if (track == nullptr) { client.ReplyHtmlWithHeader(HtmlTag()); return; } client.ReplyHtmlWithHeader(HtmlTagTrack(manager, track)); } void WebClientTrack::HandleTrackSetLoco(const map& arguments) { HtmlTag content; const TrackID trackID = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "track", TrackNone)); Track* track = manager.GetTrack(trackID); if (track == nullptr) { client.ReplyResponse(WebClient::ResponseError, Languages::TextTrackDoesNotExist); return; } if (track->IsInUse()) { client.ReplyHtmlWithHeaderAndParagraph(Languages::TextTrackIsUsedByLoco, track->GetName(), manager.GetLocoBaseName(track->GetLocoBase())); return; } const LocoID locoID = Utils::Utils::GetIntegerMapEntry(arguments, "loco", LocoNone); const ObjectIdentifier locoBaseIdentifier(WebClientStatic::LocoIdToObjectIdentifier(locoID)); if (locoBaseIdentifier.IsSet()) { const bool ret = manager.LocoBaseIntoTrack(logger, locoBaseIdentifier, trackID); const string trackName = track->GetName(); ret ? client.ReplyResponse(WebClient::ResponseInfo, Languages::TextLocoIsOnTrack, manager.GetLocoBaseName(locoBaseIdentifier), trackName) : client.ReplyResponse(WebClient::ResponseError, Languages::TextUnableToAddLocoToTrack, manager.GetLocoBaseName(locoBaseIdentifier), trackName); return; } map locos = manager.LocoBaseListFree(); content.AddChildTag(HtmlTag("h1").AddContent(Languages::TextSelectLocoForTrack, track->GetName())); content.AddChildTag(HtmlTagInputHidden("cmd", "tracksetloco")); content.AddChildTag(HtmlTagInputHidden("track", to_string(trackID))); content.AddChildTag(HtmlTagSelectWithLabel("loco", Languages::TextLoco, locos)); content.AddChildTag(HtmlTag("br")); content.AddChildTag(HtmlTagButtonCancel()); content.AddChildTag(HtmlTagButtonOK()); client.ReplyHtmlWithHeader(HtmlTag("form").AddId("editform").AddChildTag(content)); } void WebClientTrack::HandleTrackRelease(const map& arguments) { const TrackID trackID = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "track", TrackNone)); bool ret = manager.TrackRelease(trackID); client.ReplyHtmlWithHeaderAndParagraph(ret ? "Track released" : "Track not released"); } void WebClientTrack::HandleTrackStartLoco(const map& arguments) { const TrackID trackID = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "track", TrackNone)); bool ret = manager.TrackStartLocoBase(trackID); client.ReplyHtmlWithHeaderAndParagraph(ret ? "Loco started" : "Loco not started"); } void WebClientTrack::HandleTrackStopLoco(const map& arguments) { const TrackID trackID = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "track", TrackNone)); bool ret = manager.TrackStopLocoBase(trackID); client.ReplyHtmlWithHeaderAndParagraph(ret ? "Loco stopped" : "Loco not stopped"); } void WebClientTrack::HandleTrackBlock(const map& arguments) { bool blocked = Utils::Utils::GetBoolMapEntry(arguments, "blocked"); const TrackID trackID = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "track", TrackNone)); manager.TrackBlock(trackID, blocked); client.ReplyHtmlWithHeaderAndParagraph(blocked ? "Block received" : "Unblock received"); } void WebClientTrack::HandleTrackOrientation(const map& arguments) { Orientation orientation = (Utils::Utils::GetBoolMapEntry(arguments, "orientation") ? OrientationRight : OrientationLeft); const TrackID trackID = static_cast(Utils::Utils::GetIntegerMapEntry(arguments, "track", TrackNone)); manager.TrackSetLocoOrientation(trackID, orientation); client.ReplyHtmlWithHeaderAndParagraph("Loco orientation of track set"); } HtmlTag WebClientTrack::HtmlTagTabTrackAutomode(DataModel::SelectRouteApproach selectRouteApproach, const bool allowLocoTurn, const bool releaseWhenFree, const Cluster* cluster) { HtmlTag automodeContent("div"); automodeContent.AddId("tab_automode"); automodeContent.AddClass("tab_content"); automodeContent.AddClass("hidden"); automodeContent.AddChildTag(WebClientStatic::HtmlTagSelectSelectRouteApproach(selectRouteApproach)); automodeContent.AddChildTag(HtmlTagInputCheckboxWithLabel("allowlocoturn", Languages::TextAllowLocoTurn, "false", allowLocoTurn)); automodeContent.AddChildTag(HtmlTagInputCheckboxWithLabel("releasewhenfree", Languages::TextReleaseWhenFree, "true", releaseWhenFree)); if (cluster != nullptr) { automodeContent.AddChildTag(HtmlTagInputTextWithLabel("cluster", Languages::TextCluster, cluster->GetName(), HtmlTagInput::StyleDisabled)); } return automodeContent; } }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/WebClientTrack.h000066400000000000000000000053221500456250600216330ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include "Logger/Logger.h" #include "Manager.h" namespace Server { namespace Web { class WebClient; class WebClientTrack { public: WebClientTrack() = delete; inline WebClientTrack(Manager& manager, WebClient& client, Logger::Logger* logger) : manager(manager), client(client), logger(logger) { } void HandleTrackEdit(const std::map& arguments); void HandleTrackSave(const std::map& arguments); void HandleTrackList(); void HandleTrackAskDelete(const std::map& arguments); void HandleTrackDelete(const std::map& arguments); void HandleTrackGet(const std::map& arguments); void HandleTrackSetLoco(const std::map& arguments); void HandleTrackRelease(const std::map& arguments); void HandleTrackStartLoco(const std::map& arguments); void HandleTrackStopLoco(const std::map& arguments); void HandleTrackBlock(const std::map& arguments); void HandleTrackOrientation(const std::map& arguments); std::map GetFeedbackOptions(const TrackID trackId = TrackNone) const; std::map GetSignalOptions(const TrackID trackId = TrackNone) const; private: HtmlTag HtmlTagSelectTrack(const std::string& name, const Languages::TextSelector label, const Languages::TextSelector hint, const TrackID trackID, const TrackID excludeTrackID, const std::string& onchange = "") const; static HtmlTag HtmlTagTabTrackAutomode(DataModel::SelectRouteApproach selectRouteApproach, const bool allowLocoTurn, const bool releaseWhenFree, const DataModel::Cluster* cluster); Manager& manager; WebClient& client; Logger::Logger* logger; }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/WebServer.cpp000066400000000000000000000510121500456250600212260ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include #include //memset #include #include #include #include #include #include #include #include "ControlInterface.h" #include "DataTypes.h" #include "DataModel/LocoBase.h" #include "DataModel/LocoFunctions.h" #include "DataModel/ObjectIdentifier.h" #include "Languages.h" #include "RailControl.h" #include "Utils/Network.h" #include "Version.h" #include "Server/Web/WebClient.h" #include "Server/Web/WebServer.h" using std::map; using std::thread; using std::string; using std::stringstream; using std::to_string; using std::vector; using DataModel::LocoBase; using DataModel::LocoFunctionNr; using DataModel::LocoFunctionState; using DataModel::Route; using DataModel::Track; namespace Server { namespace Web { WebServer::WebServer(Manager& manager, const std::string& webserveraddress, const unsigned short port) : ControlInterface(ControlTypeWebServer), Network::TcpServer(webserveraddress, port, "WebServer"), logger(Logger::Logger::GetLogger("WebServer")), lastClientID(0), manager(manager), updateID(1), updateAvailable(false) { AddUpdate(Languages::TextRailControlStarted); LogBrowserInfo(webserveraddress, port); updateAvailable = Utils::Network::HostResolves(GetVersionInfoGitHash() + ".hash.railcontrol.org"); } WebServer::~WebServer() { // delete all client memory while (clients.size()) { WebClient* client = clients.back(); clients.pop_back(); delete client; } logger->Info(Languages::TextWebServerStopped); } void WebServer::Start() { StartTcpServer(); logger->Info(Languages::TextWebServerStarted); } void WebServer::Stop() { AddUpdate(Languages::TextShutdownRailControl); TerminateTcpServer(); // stopping all clients for (auto client : clients) { client->Stop(); } } void WebServer::LogBrowserInfo(const std::string& webserveraddress, const unsigned short port) { static const string Http("\n http://"); static const string Port(to_string(port)); string localhostInfo(Http); localhostInfo += "localhost:" + Port + "/"; string ipv4Info; string ipv6Info; struct ifaddrs* ifAddrStruct = nullptr; struct ifaddrs* ifa = nullptr; getifaddrs(&ifAddrStruct); if (webserveraddress.compare("localhost") != 0) { for (ifa = ifAddrStruct; ifa != nullptr; ifa = ifa->ifa_next) { if (!ifa->ifa_addr) { continue; } if (ifa->ifa_addr->sa_family == AF_INET) { // is a valid IP4 Address void* tmpAddrPtr = &((struct sockaddr_in*) ifa->ifa_addr)->sin_addr; char addressBuffer[INET_ADDRSTRLEN]; inet_ntop(AF_INET, tmpAddrPtr, addressBuffer, INET_ADDRSTRLEN); string address(addressBuffer); if (address.compare("0.0.0.0") == 0) { continue; } if (address.substr(0, 8).compare("169.254.") == 0) { continue; } ipv4Info += Http + addressBuffer + ":" + Port + "/"; } else if (ifa->ifa_addr->sa_family == AF_INET6) { // is a valid IP6 Address void* tmpAddrPtr = &((struct sockaddr_in6*) ifa->ifa_addr)->sin6_addr; char addressBuffer[INET6_ADDRSTRLEN]; inet_ntop(AF_INET6, tmpAddrPtr, addressBuffer, INET6_ADDRSTRLEN); ipv6Info += Http + "[" + addressBuffer + "]:" + Port + "/"; } } } if (ifAddrStruct != NULL) { freeifaddrs(ifAddrStruct); } logger->Info(Languages::TextBrowserInfo, localhostInfo, ipv4Info, ipv6Info); } void WebServer::Work(Network::TcpConnection* connection) { clients.push_back(new WebClient(++lastClientID, connection, *this, manager)); // clean up unused clients for (auto iterator = clients.begin(); iterator != clients.end();) { WebClient* client = *iterator; if (client->IsTerminated()) { iterator = clients.erase(iterator); delete client; } else { ++iterator; } } } void WebServer::Booster(__attribute__((unused)) const ControlType controlType, const BoosterState status) { if (status) { AddUpdate("booster;on=true", Languages::TextTurningBoosterOn); } else { AddUpdate("booster;on=false", Languages::TextTurningBoosterOff); } } void WebServer::LocoBaseSpeed(__attribute__((unused)) const ControlType controlType, const LocoBase* loco, const Speed speed) { const LocoID locoId = loco->GetLocoIdWithPrefix(); string command = "locospeed;loco=" + to_string(locoId) + ";speed=" + to_string(speed); AddUpdate(command, Languages::TextLocoSpeedIs, loco->GetName(), speed); } void WebServer::LocoBaseOrientation(__attribute__((unused)) const ControlType controlType, const LocoBase* loco, const Orientation orientation) { const LocoID locoId = loco->GetLocoIdWithPrefix(); string command = "locoorientation;loco=" + to_string(locoId) + ";orientation=" + (orientation ? "true" : "false"); AddUpdate(command, orientation ? Languages::TextLocoDirectionOfTravelIsRight : Languages::TextLocoDirectionOfTravelIsLeft, loco->GetName()); } void WebServer::LocoBaseFunction(__attribute__((unused)) const ControlType controlType, const LocoBase* loco, const LocoFunctionNr function, const LocoFunctionState state) { const LocoID locoId = loco->GetLocoIdWithPrefix(); string command = "locofunction;loco=" + to_string(locoId) + ";function=" + to_string(function) + ";on=" + (state ? "true" : "false"); AddUpdate(command, state ? Languages::TextLocoFunctionIsOn : Languages::TextLocoFunctionIsOff, loco->GetName(), function); } void WebServer::AccessoryState(__attribute__((unused)) const ControlType controlType, const DataModel::Accessory* accessory) { stringstream command; const DataModel::AccessoryState state = accessory->GetAccessoryState(); command << "accessory;accessory=" << accessory->GetID() << ";state=" << (state == DataModel::AccessoryStateOn ? "green" : "red"); AddUpdate(command.str(), state ? Languages::TextAccessoryStateIsGreen : Languages::TextAccessoryStateIsRed, accessory->GetName()); } void WebServer::AccessorySettings(const AccessoryID accessoryID, const std::string& name, __attribute__((unused)) const std::string& matchkey) { stringstream command; command << "accessorysettings;accessory=" << accessoryID; AddUpdate(command.str(), Languages::TextAccessoryUpdated, name); } void WebServer::AccessoryDelete(const AccessoryID accessoryID, const std::string& name, __attribute__((unused)) const std::string& matchkey) { stringstream command; command << "accessorydelete;accessory=" << accessoryID; AddUpdate(command.str(), Languages::TextAccessoryDeleted, name); } void WebServer::FeedbackState(const std::string& name, const FeedbackID feedbackID, const DataModel::Feedback::FeedbackState state) { stringstream command; command << "feedback;feedback=" << feedbackID << ";state=" << (state ? "on" : "off"); AddUpdate(command.str(), state ? Languages::TextFeedbackStateIsOn : Languages::TextFeedbackStateIsOff, name); } void WebServer::FeedbackSettings(const FeedbackID feedbackID, const std::string& name) { stringstream command; command << "feedbacksettings;feedback=" << feedbackID; AddUpdate(command.str(), Languages::TextFeedbackUpdated, name); } void WebServer::FeedbackDelete(const FeedbackID feedbackID, const std::string& name) { stringstream command; command << "feedbackdelete;feedback=" << feedbackID; AddUpdate(command.str(), Languages::TextFeedbackDeleted, name); } void WebServer::RouteSettings(const RouteID routeID, const std::string& name) { stringstream command; command << "routesettings;route=" << routeID; AddUpdate(command.str(), Languages::TextRouteUpdated, name); } void WebServer::RouteDelete(const RouteID routeID, const std::string& name) { stringstream command; command << "routedelete;route=" << routeID; AddUpdate(command.str(), Languages::TextRouteDeleted, name); } void WebServer::SwitchState(__attribute__((unused)) const ControlType controlType, const DataModel::Switch* mySwitch) { stringstream command; const DataModel::AccessoryState state = mySwitch->GetAccessoryState(); command << "switch;switch=" << mySwitch->GetID() << ";state="; Languages::TextSelector text; switch (state) { case DataModel::AccessoryState::SwitchStateTurnout: command << "turnout"; text = Languages::TextSwitchStateIsTurnout; break; case DataModel::AccessoryState::SwitchStateThird: command << "third"; text = Languages::TextSwitchStateIsThird; break; case DataModel::AccessoryState::SwitchStateStraight: default: command << "straight"; text = Languages::TextSwitchStateIsStraight; break; } AddUpdate(command.str(), text, mySwitch->GetName()); } void WebServer::SwitchSettings(const SwitchID switchID, const std::string& name, __attribute__((unused)) const std::string& matchKey) { stringstream command; command << "switchsettings;switch=" << switchID; AddUpdate(command.str(), Languages::TextSwitchUpdated, name); } void WebServer::SwitchDelete(const SwitchID switchID, const std::string& name, __attribute__((unused)) const std::string& matchkey) { stringstream command; command << "switchdelete;switch=" << switchID; AddUpdate(command.str(), Languages::TextSwitchDeleted, name); } void WebServer::TrackState(const DataModel::Track* track) { const LocoBase* locoBase = manager.GetLocoBase(track->GetMainLocoBaseDelayed()); const bool reserved = locoBase != nullptr; const string& trackName = track->GetMainName(); const string& locoName = reserved ? locoBase->GetName() : ""; const bool occupied = track->GetMainStateDelayed() == DataModel::Feedback::FeedbackStateOccupied; const bool blocked = track->GetMainBlocked(); const Orientation orientation = track->GetMainLocoOrientation(); const string occupiedText = (occupied ? "true" : "false"); const string blockedText = (blocked ? "true" : "false"); const string reservedText = (reserved ? "true" : "false"); const string orientationText = (orientation ? "true" : "false"); string command = "trackstate;track=" + to_string(track->GetID()) + ";occupied=" + occupiedText + ";reserved=" + reservedText + ";blocked=" + blockedText + ";orientation=" + orientationText + ";loconame=" + locoName; if (blocked) { if (reserved) { AddUpdate(command, Languages::TextTrackStatusIsBlockedAndReserved, trackName, locoName); } else if (occupied) { AddUpdate(command, Languages::TextTrackStatusIsBlockedAndOccupied, trackName); } else { AddUpdate(command, Languages::TextTrackStatusIsBlocked, trackName); } } else { if (reserved) { AddUpdate(command, Languages::TextTrackStatusIsReserved, trackName, locoName);; } else if (occupied) { AddUpdate(command, Languages::TextTrackStatusIsOccupied, trackName); } else { AddUpdate(command, Languages::TextTrackStatusIsFree, trackName); } } } void WebServer::TrackSettings(const TrackID trackID, const std::string& name) { stringstream command; command << "tracksettings;track=" << trackID; AddUpdate(command.str(), Languages::TextTrackUpdated, name); } void WebServer::TrackDelete(const TrackID trackID, const std::string& name) { stringstream command; command << "trackdelete;track=" << trackID; AddUpdate(command.str(), Languages::TextTrackDeleted, name); } void WebServer::SignalState(__attribute__((unused)) const ControlType controlType, const DataModel::Signal* signal) { const DataModel::AccessoryState state = signal->GetAccessoryState(); string stateText; Languages::TextSelector text; switch(state) { case DataModel::SignalStateStop: default: stateText = "stop"; text = Languages::TextSignalStateIsStop; break; case DataModel::SignalStateClear: stateText = "clear"; text = Languages::TextSignalStateIsClear; break; case DataModel::SignalStateAspect2: stateText = "aspect2"; text = Languages::TextSignalStateIsAspect2; break; case DataModel::SignalStateAspect3: stateText = "aspect3"; text = Languages::TextSignalStateIsAspect3; break; case DataModel::SignalStateAspect4: stateText = "aspect4"; text = Languages::TextSignalStateIsAspect4; break; case DataModel::SignalStateAspect5: stateText = "aspect5"; text = Languages::TextSignalStateIsAspect5; break; case DataModel::SignalStateAspect6: stateText = "aspect6"; text = Languages::TextSignalStateIsAspect6; break; case DataModel::SignalStateAspect7: stateText = "aspect7"; text = Languages::TextSignalStateIsAspect7; break; case DataModel::SignalStateAspect8: stateText = "aspect8"; text = Languages::TextSignalStateIsAspect8; break; case DataModel::SignalStateAspect9: stateText = "aspect9"; text = Languages::TextSignalStateIsAspect9; break; case DataModel::SignalStateAspect10: stateText = "aspect10"; text = Languages::TextSignalStateIsAspect10; break; case DataModel::SignalStateDark: stateText = "dark"; text = Languages::TextSignalStateIsDark; break; case DataModel::SignalStateStopExpected: stateText = "stopexpected"; text = Languages::TextSignalStateIsStopExpected; break; case DataModel::SignalStateClearExpected: stateText = "clearexpected"; text = Languages::TextSignalStateIsClearExpected; break; case DataModel::SignalStateAspect2Expected: stateText = "aspect2expected"; text = Languages::TextSignalStateIsAspect2Expected; break; case DataModel::SignalStateAspect3Expected: stateText = "aspect3expected"; text = Languages::TextSignalStateIsAspect3Expected; break; case DataModel::SignalStateAspect4Expected: stateText = "aspect4expected"; text = Languages::TextSignalStateIsAspect4Expected; break; case DataModel::SignalStateAspect5Expected: stateText = "aspect5expected"; text = Languages::TextSignalStateIsAspect5Expected; break; case DataModel::SignalStateAspect6Expected: stateText = "aspect6expected"; text = Languages::TextSignalStateIsAspect6Expected; break; case DataModel::SignalStateAspect7Expected: stateText = "aspect7expected"; text = Languages::TextSignalStateIsAspect7Expected; break; case DataModel::SignalStateAspect8Expected: stateText = "aspect8expected"; text = Languages::TextSignalStateIsAspect8Expected; break; case DataModel::SignalStateAspect9Expected: stateText = "aspect9expected"; text = Languages::TextSignalStateIsAspect9Expected; break; case DataModel::SignalStateAspect10Expected: stateText = "aspect10expected"; text = Languages::TextSignalStateIsAspect10Expected; break; } const string signalIdText(to_string(signal->GetID())); const string command = "signal;signal=" + signalIdText + ";state=" + stateText; AddUpdate(command, text, signal->GetName()); } void WebServer::SignalSettings(const SignalID signalID, const std::string& name, __attribute__((unused)) const std::string& matchKey) { stringstream command; command << "signalsettings;signal=" << signalID; AddUpdate(command.str(), Languages::TextSignalUpdated, name); } void WebServer::SignalDelete(const SignalID signalID, const std::string& name, __attribute__((unused)) const std::string& matchkey) { stringstream command; command << "signaldelete;signal=" << signalID; AddUpdate(command.str(), Languages::TextSignalDeleted, name); } void WebServer::ClusterSettings(const ClusterID clusterID, const std::string& name) { stringstream command; command << "clustersettings;cluster=" << clusterID; AddUpdate(command.str(), Languages::TextClusterUpdated, name); } void WebServer::ClusterDelete(const ClusterID clusterID, const std::string& name) { stringstream command; command << "clusterdelete;cluster=" << clusterID; AddUpdate(command.str(), Languages::TextClusterDeleted, name); } void WebServer::TextSettings(const TextID textID, const std::string& name) { string command = "textsettings;text=" + to_string(textID); AddUpdate(command, Languages::TextTextUpdated, name); } void WebServer::TextDelete(const TextID textID, const std::string& name) { string command = "textdelete;text=" + to_string(textID); AddUpdate(command, Languages::TextTextDeleted, name); } void WebServer::LocoBaseRelease(const DataModel::LocoBase* loco) { string command("locorelease;loco="); command += loco->GetObjectIdentifier(); AddUpdate(command, Languages::TextLocoIsReleased, loco->GetName()); } void WebServer::RouteRelease(const RouteID routeID) { stringstream command; command << "routeRelease;route=" << routeID; AddUpdate(command.str(), Languages::TextRouteIsReleased, manager.GetRouteName(routeID)); } void WebServer::LocoBaseDestinationReached(const LocoBase* loco, const Route* route, const Track* track) { string command("locoDestinationReached;loco="); command += loco->GetObjectIdentifier(); command += ";route="; command += to_string(route->GetID()); command += ";track="; command += to_string(track->GetID()); AddUpdate(command, Languages::TextLocoHasReachedDestination, loco->GetName(), track->GetName(), route->GetName()); } void WebServer::LocoBaseStart(const DataModel::LocoBase* loco) { string command("locoStart;loco="); command += loco->GetObjectIdentifier(); AddUpdate(command, Languages::TextLocoIsInAutoMode, loco->GetName()); } void WebServer::LocoBaseStop(const DataModel::LocoBase* loco) { string command("locoStop;loco="); command += loco->GetObjectIdentifier(); AddUpdate(command, Languages::TextLocoIsInManualMode, loco->GetName()); } void WebServer::LocoSettings(const LocoID locoID, const std::string& name, __attribute__((unused)) const std::string& matchKey) { stringstream command; command << "locosettings;loco=" << locoID; AddUpdate(command.str(), Languages::TextLocoUpdated, name); } void WebServer::LocoDelete(const LocoID locoID, const std::string& name, __attribute__((unused)) const std::string& matchkey) { stringstream command; command << "locodelete;loco=" << locoID; AddUpdate(command.str(), Languages::TextLocoDeleted, name); } void WebServer::MultipleUnitSettings(const MultipleUnitID multipleUnitID, const std::string& name, __attribute__((unused)) const std::string& matchKey) { stringstream command; command << "multipleunitsettings;loco=" << multipleUnitID; AddUpdate(command.str(), Languages::TextLocoUpdated, name); } void WebServer::MultipleUnitDelete(const MultipleUnitID multipleUnitID, const std::string& name, __attribute__((unused)) const std::string& matchkey) { stringstream command; command << "multipleunitdelete;loco=" << multipleUnitID; AddUpdate(command.str(), Languages::TextLocoDeleted, name); } void WebServer::LayerSettings(const LayerID layerID, const std::string& name) { stringstream command; command << "layersettings;layer=" << layerID; AddUpdate(command.str(), Languages::TextLayerUpdated, name); } void WebServer::LayerDelete(const LayerID layerID, const std::string& name) { stringstream command; command << "layerdelete;layer=" << layerID; AddUpdate(command.str(), Languages::TextLayerDeleted, name); } void WebServer::ProgramValue(const CvNumber cv, const CvValue value) { stringstream command; command << "dcccvvalue;cv=" << static_cast(cv) << ";value=" << static_cast(value); AddUpdate(command.str(), Languages::TextProgramReadValue , static_cast(cv), static_cast(value)); } void WebServer::AddUpdate(const string& data) { std::lock_guard lock(updateMutex); updates[updateID] = data; ++updateID; updates.erase(updateID - MaxUpdates); } bool WebServer::NextUpdate(unsigned int& updateIDClient, string& s) { std::lock_guard lock(updateMutex); if (updateIDClient + MaxUpdates <= updateID) { updateIDClient = updateID - MaxUpdates + 1; } if (updates.count(updateIDClient) == 1) { s = updates.at(updateIDClient); return true; } return false; } }} // namespace Server::Web railcontrol-24+dfsg1/Server/Web/WebServer.h000066400000000000000000000146771500456250600207130ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include #include #include "ControlInterface.h" #include "DataModel/AccessoryBase.h" #include "Logger/Logger.h" #include "Manager.h" #include "Network/TcpServer.h" namespace Server { namespace Web { class WebClient; class WebServer : public ControlInterface, private Network::TcpServer { public: WebServer() = delete; WebServer(const WebServer&) = delete; WebServer& operator=(const WebServer&) = delete; WebServer(Manager& manager, const std::string& webserveraddress, const unsigned short port); ~WebServer(); void Start() override; void Stop() override; void Work(Network::TcpConnection* connection) override; bool NextUpdate(unsigned int& updateIDClient, std::string& s); inline const std::string& GetName() const override { static const std::string WebserverName("WebServer"); return WebserverName; } void AccessoryDelete(const AccessoryID accessoryID, const std::string& name, const std::string& matchKey) override; void AccessorySettings(const AccessoryID accessoryID, const std::string& name, const std::string& matchkey) override; void AccessoryState(const ControlType controlType, const DataModel::Accessory* accessory) override; void Booster(const ControlType controlType, const BoosterState status) override; void FeedbackDelete(const FeedbackID feedbackID, const std::string& name) override; void FeedbackSettings(const FeedbackID feedbackID, const std::string& name) override; void FeedbackState(const std::string& name, const FeedbackID feedbackID, const DataModel::Feedback::FeedbackState state) override; void LayerDelete(const LayerID layerID, const std::string& name) override; void LayerSettings(const LayerID layerID, const std::string& name) override; void LocoBaseDestinationReached(const DataModel::LocoBase* loco, const DataModel::Route* route, const DataModel::Track* track) override; void LocoBaseOrientation(const ControlType controlType, const DataModel::LocoBase* loco, const Orientation direction) override; void LocoBaseFunction(const ControlType controlType, const DataModel::LocoBase* loco, const DataModel::LocoFunctionNr function, const DataModel::LocoFunctionState on) override; void LocoBaseRelease(const DataModel::LocoBase* loco) override; void LocoBaseSpeed(const ControlType controlType, const DataModel::LocoBase* loco, const Speed speed) override; void LocoBaseStart(const DataModel::LocoBase* loco) override; void LocoBaseStop(const DataModel::LocoBase* loco) override; void LocoSettings(const LocoID locoID, const std::string& name, const std::string& matchKey) override; void LocoDelete(const LocoID locoID, const std::string& name, const std::string& matchKey) override; void MultipleUnitSettings(const MultipleUnitID multipleUnitID, const std::string& name, const std::string& matchKey) override; void MultipleUnitDelete(const MultipleUnitID multipleUnitID, const std::string& name, const std::string& matchkey) override; void RouteDelete(const RouteID routeID, const std::string& name) override; void RouteRelease(const RouteID routeID) override; void RouteSettings(const RouteID routeID, const std::string& name) override; void SwitchDelete(const SwitchID switchID, const std::string& name, const std::string& matchKey) override; void SwitchSettings(const SwitchID switchID, const std::string& name, const std::string& matchKey) override; void SwitchState(const ControlType controlType, const DataModel::Switch* mySwitch) override; void TrackDelete(const TrackID trackID, const std::string& name) override; void TrackSettings(const TrackID trackID, const std::string& name) override; void TrackState(const DataModel::Track* track) override; void SignalDelete(const SignalID signalID, const std::string& name, const std::string& matchKey) override; void SignalSettings(const SignalID signalID, const std::string& name, const std::string& matchKey) override; void SignalState(const ControlType controlType, const DataModel::Signal* signal) override; void ClusterDelete(const ClusterID clusterID, const std::string& name) override; void ClusterSettings(const ClusterID clusterID, const std::string& name) override; void TextDelete(const ClusterID clusterID, const std::string& name) override; void TextSettings(const ClusterID clusterID, const std::string& name) override; void ProgramValue(const CvNumber cv, const CvValue value) override; inline bool UpdateAvailable() { return updateAvailable; } inline void AddUpdate(const std::string& command, const Languages::TextSelector status) { AddUpdate(command, Languages::GetText(status)); } private: template inline void AddUpdate(const std::string& command, const Languages::TextSelector text, Args... args) { AddUpdate(command, Logger::Logger::Format(Languages::GetText(text), args...)); } inline void AddUpdate(const std::string& command, const std::string& status) { AddUpdate("data: command=" + command + ";status=" + status + "\r\n\r\n"); } inline void AddUpdate(const Languages::TextSelector status) { AddUpdate(std::string("data: status=") + Languages::GetText(status) + "\r\n\r\n"); } void AddUpdate(const std::string& data); void LogBrowserInfo(const std::string& webserveraddress, const unsigned short port); Logger::Logger* logger; unsigned int lastClientID; std::vector clients; Manager& manager; std::map updates; std::mutex updateMutex; unsigned int updateID; bool updateAvailable; static const unsigned int MaxUpdates = 10; }; }} // namespace Server::Web railcontrol-24+dfsg1/Server/Z21/000077500000000000000000000000001500456250600164565ustar00rootroot00000000000000railcontrol-24+dfsg1/Server/Z21/Z21Client.cpp000066400000000000000000000247111500456250600207020ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include //memset #include "DataModel/ObjectIdentifier.h" #include "Hardware/Protocols/Z21.h" #include "Manager.h" #include "Server/Z21/Z21Client.h" using DataModel::ObjectIdentifier; namespace Z21Enums = Hardware::Protocols::Z21Enums; namespace Server { namespace Z21 { Z21Client::Z21Client(const unsigned int id, Manager& manager, const int serverSocket, const struct sockaddr_storage* clientAddress) : Network::UdpClient(serverSocket, clientAddress, 60), logger(Logger::Logger::GetLogger("Z21Client # " + std::to_string(id))), manager(manager), broadCastFlags(Z21Enums::BroadCastFlagNone) { logger->Debug(Languages::TextUdpConnectionEstablished, AddressAsString()); SendSystemStatusChanged(); } Z21Client::~Z21Client() { logger->Debug(Languages::TextUdpConnectionClosed, AddressAsString()); } void Z21Client::Work(const unsigned char* buffer, const size_t dataLength) { logger->HexIn(buffer, dataLength); size_t dataRead = 0; while (dataRead < dataLength) { size_t ret = ParseData(buffer + dataRead, dataLength - dataRead); if (ret == 0) { break; } dataRead += ret; } } size_t Z21Client::ParseData(const unsigned char* buffer, const size_t bufferLength) { unsigned short dataLength = Utils::Integer::DataLittleEndianToShort(buffer); if (dataLength < 4 || dataLength > bufferLength) { return 1472; // 1472 = Max UDP data size } const uint16_t header = Utils::Integer::DataLittleEndianToShort(buffer + 2); switch (header) { case Z21Enums::HeaderSerialNumber: SendSerialNumber(); break; case Z21Enums::HeaderGetCode: SendCode(); break; case Z21Enums::HeaderGetHardwareInfo: SendHardwareInfo(); break; case Z21Enums::HeaderLogOff: Terminate(); break; case Z21Enums::HeaderSeeXHeader: ParseXHeader(buffer); break; case Z21Enums::HeaderSetBroadcastFlags: { broadCastFlags = static_cast(Utils::Integer::DataLittleEndianToInt(buffer + 4)); break; } case Z21Enums::HeaderGetBroadcastFlags: SendBroadcastFlags(); break; case Z21Enums::HeaderGetLocoMode: { const uint16_t address = *(reinterpret_cast(buffer + 4)); SendLocoMode(address); break; } case Z21Enums::HeaderSetLocoMode: // ignore loco mode, we always use DCC break; case Z21Enums::HeaderGetTurnoutMode: { const uint16_t address = *(reinterpret_cast(buffer + 4)); SendTurnoutMode(address); break; } case Z21Enums::HeaderSetTurnoutMode: // ignore turnout mode, we always use DCC break; case Z21Enums::HeaderGetSystemState: SendSystemStatusChanged(); break; default: SendUnknownCommand(); break; } return dataLength; } void Z21Client::ParseXHeader(const unsigned char* buffer) { Z21Enums::XHeader xHeader = static_cast(buffer[4]); switch (xHeader) { case Z21Enums::XHeaderSeeDB0_1: ParseDB0(buffer); return; case Z21Enums::XHeaderSetStop: logger->Debug(Languages::TextBoosterIsTurnedOff); manager.Booster(ControlTypeZ21Server, BoosterStateStop); SendBcStopped(); return; case Z21Enums::XHeaderGetLocoInfo: { const Address address = ParseLocoAddress(buffer + 6); logger->Debug("Address: {0}", address); const DataModel::LocoBase* const locoBase = manager.GetLocoBase(manager.GetIdentifierOfServerLocoAddress(address)); if (nullptr == locoBase) { return; } SendLocoInfo(locoBase); return; } case Z21Enums::XHeaderGetTurnoutInfo: { const Address address = ParseLocoAddress(buffer + 5); logger->Debug("Address: {0}", address); const DataModel::AccessoryBase* const accessoryBase = manager.GetAccessoryBase(manager.GetIdentifierOfServerAccessoryAddress(address)); if (nullptr == accessoryBase) { return; } SendTurnoutInfo(accessoryBase); return; } case Z21Enums::XHeaderLocoDrive: switch(buffer[5]) { case Z21Enums::DB0SetLocoDrive14: case Z21Enums::DB0SetLocoDrive28: case Z21Enums::DB0SetLocoDrive128: case Z21Enums::DB0LocoFunction: ParseLocoDrive(buffer); return; default: return; } case Z21Enums::XHeaderSetLocoBinaryState: // we do not care return; case Z21Enums::XHeaderSetTurnout: { const Address address = ParseAccessoryAddress(buffer + 5); const ObjectIdentifier accessoryBaseIdentifier(manager.GetIdentifierOfServerAccessoryAddress(address)); const bool activate = (buffer[7] & 0x08) == 0x08; if (!activate) { return; } const DataModel::AccessoryState state = static_cast(buffer[7] & 0x01); logger->Debug(Languages::Languages::TextReceivedAccessoryCommand, Utils::Utils::ProtocolToString(ProtocolDCC), address, state); manager.AccessoryBaseState(ControlTypeZ21Server, accessoryBaseIdentifier, state); return; } case Z21Enums::XHeaderGetFirmwareVersion: SendFirmwareVersion(); return; default: SendUnknownCommand(); return; } } void Z21Client::ParseDB0(const unsigned char* buffer) { switch (buffer[5]) { case Z21Enums::DB0Version: SendVersion(); return; case Z21Enums::DB0Status: SendStatusChanged(); return; case Z21Enums::DB0SetPowerOff: logger->Debug(Languages::TextBoosterIsTurnedOff); manager.Booster(ControlTypeZ21Server, BoosterStateStop); return; case Z21Enums::DB0SetPowerOn: logger->Debug(Languages::TextBoosterIsTurnedOn); manager.Booster(ControlTypeZ21Server, BoosterStateGo); return; default: SendUnknownCommand(); return; } } void Z21Client::ParseLocoDrive(const unsigned char* buffer) { const ObjectIdentifier locoBaseIdentifier(manager.GetIdentifierOfServerLocoAddress(ParseLocoAddress(buffer + 6))); Speed speed; switch(buffer[5]) { case Z21Enums::DB0SetLocoDrive14: speed = Hardware::Protocols::Z21::DecodeSpeed14(buffer[8] & 0x7F); break; case Z21Enums::DB0SetLocoDrive28: speed = Hardware::Protocols::Z21::DecodeSpeed28(buffer[8] & 0x7F); break; case Z21Enums::DB0SetLocoDrive128: speed = Hardware::Protocols::Z21::DecodeSpeed128(buffer[8] & 0x7F); break; case Z21Enums::DB0LocoFunction: { const DataModel::LocoFunctionNr nr = buffer[8] & 0x3F; const DataModel::LocoFunctionState on = static_cast((buffer[8] >> 6) & 0x01); manager.LocoBaseFunctionState(ControlTypeZ21Server, locoBaseIdentifier, nr, on); return; } default: speed = 0; break; } manager.LocoBaseSpeed(ControlTypeZ21Server, locoBaseIdentifier, speed); manager.LocoBaseOrientation(ControlTypeZ21Server, locoBaseIdentifier, static_cast(buffer[8] >> 7)); } void Z21Client::SendLocoInfo(const DataModel::LocoBase* const locoBase) { unsigned char sendBuffer[15] = { 0x0F, 0x00, 0x40, 0x00, 0xEF, 0x00, 0x00, 0x04 }; const Address address = locoBase->GetServerAddress(); if (address == AddressNone) { return; } Utils::Integer::ShortToDataBigEndian(address, sendBuffer + 5); sendBuffer[8] = (locoBase->GetOrientation() << 7) | Hardware::Protocols::Z21::EncodeSpeed128(locoBase->GetSpeed()); sendBuffer[9] = ((locoBase->GetFunctionState(0) & 0x01) << 4) | ((locoBase->GetFunctionState(4) & 0x01) << 3) | ((locoBase->GetFunctionState(3) & 0x01) << 2) | ((locoBase->GetFunctionState(2) & 0x01) << 1) | (locoBase->GetFunctionState(1) & 0x01); sendBuffer[10] = ((locoBase->GetFunctionState(12) & 0x01) << 7) | ((locoBase->GetFunctionState(11) & 0x01) << 6) | ((locoBase->GetFunctionState(10) & 0x01) << 5) | ((locoBase->GetFunctionState(9) & 0x01) << 4) | ((locoBase->GetFunctionState(8) & 0x01) << 3) | ((locoBase->GetFunctionState(7) & 0x01) << 2) | ((locoBase->GetFunctionState(6) & 0x01) << 1) | (locoBase->GetFunctionState(5) & 0x01); sendBuffer[11] = ((locoBase->GetFunctionState(20) & 0x01) << 7) | ((locoBase->GetFunctionState(19) & 0x01) << 6) | ((locoBase->GetFunctionState(18) & 0x01) << 5) | ((locoBase->GetFunctionState(17) & 0x01) << 4) | ((locoBase->GetFunctionState(16) & 0x01) << 3) | ((locoBase->GetFunctionState(15) & 0x01) << 2) | ((locoBase->GetFunctionState(14) & 0x01) << 1) | (locoBase->GetFunctionState(12) & 0x01); sendBuffer[12] = ((locoBase->GetFunctionState(28) & 0x01) << 7) | ((locoBase->GetFunctionState(27) & 0x01) << 6) | ((locoBase->GetFunctionState(26) & 0x01) << 5) | ((locoBase->GetFunctionState(25) & 0x01) << 4) | ((locoBase->GetFunctionState(24) & 0x01) << 3) | ((locoBase->GetFunctionState(23) & 0x01) << 2) | ((locoBase->GetFunctionState(22) & 0x01) << 1) | (locoBase->GetFunctionState(21) & 0x01); sendBuffer[13] = ((locoBase->GetFunctionState(31) & 0x01) << 2) | ((locoBase->GetFunctionState(30) & 0x01) << 1) | (locoBase->GetFunctionState(29) & 0x01); sendBuffer[sizeof(sendBuffer) - 1] = Utils::Utils::CalcXORCheckSum(sendBuffer, sizeof(sendBuffer) - 1); logger->Debug(Languages::Languages::TextSendingLocoInfo, address); Send(sendBuffer, sizeof(sendBuffer)); } void Z21Client::SendTurnoutInfo(const DataModel::AccessoryBase* const accessoryBase) { unsigned char sendBuffer[9] = { 0x09, 0x00, 0x40, 0x00, 0x43 }; const Address address = accessoryBase->GetServerAddress(); if (address == AddressNone) { return; } Utils::Integer::ShortToDataBigEndian(address - 1, sendBuffer + 5); // - 1 because Z21 address is 0-based sendBuffer[7] = 1 << (accessoryBase->GetAccessoryState() & 0x01); sendBuffer[sizeof(sendBuffer) - 1] = Utils::Utils::CalcXORCheckSum(sendBuffer, sizeof(sendBuffer) - 1); logger->Debug(Languages::Languages::TextSendingTurnoutInfo, address); Send(sendBuffer, sizeof(sendBuffer)); } }} // namespace Server::Z21 railcontrol-24+dfsg1/Server/Z21/Z21Client.h000066400000000000000000000127311500456250600203460ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "Hardware/Protocols/Z21DataTypes.h" #include "Network/UdpClient.h" #include "Utils/Integer.h" namespace Z21Enums = Hardware::Protocols::Z21Enums; namespace Server { namespace Z21 { class Z21Server; class Z21Client : protected Network::UdpClient { public: Z21Client() = delete; Z21Client(const Z21Client&) = delete; Z21Client& operator=(const Z21Client&) = delete; Z21Client(const unsigned int id, Manager& manager, const int serverSocket, const struct sockaddr_storage* clientAddress); ~Z21Client(); void Work(const unsigned char* buffer, const size_t size) override; size_t ParseData(const unsigned char* buffer, const size_t bufferLength); void ParseXHeader(const unsigned char* buffer); void ParseDB0(const unsigned char* buffer); void ParseLocoDrive(const unsigned char* buffer); inline uint16_t ParseLocoAddress(const unsigned char* buffer) { return ((buffer[0] & 0x3F) << 8) + buffer[1]; } inline uint16_t ParseAccessoryAddress(const unsigned char* buffer) { return ((buffer[0] << 8) + buffer[1]) + 1; // + 1 because Z21 protocol is 0 based } inline void SendPowerOff() { const unsigned char sendBuffer[7] = { 0x07, 0x00, 0x40, 0x00, 0x61, 0x00, 0x61 }; Send(sendBuffer, sizeof(sendBuffer)); } inline void SendPowerOn() { const unsigned char sendBuffer[7] = { 0x07, 0x00, 0x40, 0x00, 0x61, 0x01, 0x60 }; Send(sendBuffer, sizeof(sendBuffer)); } void SendLocoInfo(const DataModel::LocoBase* const locoBase); void SendTurnoutInfo(const DataModel::AccessoryBase* const accessoryBase); private: inline void SendSerialNumber() { const unsigned char sendBuffer[8] = { 0x08, 0x00, 0x10, 0x00, 0x12, 0x34, 0x56, 0x78 }; Send(sendBuffer, sizeof(sendBuffer)); } inline void SendCode() { const unsigned char sendBuffer[5] = { 0x05, 0x00, 0x18, 0x00, 0x00 }; Send(sendBuffer, sizeof(sendBuffer)); } inline void SendHardwareInfo() { const unsigned char sendBuffer[12] = { 0x0C, 0x00, 0x1A, 0x00, 0x01, 0x02, 0x00, 0x00, 0x42, 0x01, 0x00, 0x00 }; Send(sendBuffer, sizeof(sendBuffer)); } inline void SendSystemStatusChanged() { const BoosterState booster = manager.Booster(); const unsigned char centralState = (!booster) << 1; const unsigned char sendBuffer[20] = { 0x14, 0x00, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x20, 0x4E, 0x50, 0x46, centralState, 0x00, 0x00, 0x71 }; Send(sendBuffer, sizeof(sendBuffer)); } inline void SendBcStopped() { const unsigned char sendBuffer[7] = { 0x07, 0x00, 0x40, 0x00, 0x81, 0x00, 0x81 }; Send(sendBuffer, sizeof(sendBuffer)); } inline void SendFirmwareVersion() { const unsigned char sendBuffer[9] = { 0x09, 0x00, 0x40, 0x00, 0xF3, 0x0A, 0x01, 0x43, 0xBB }; Send(sendBuffer, sizeof(sendBuffer)); } inline void SendUnknownCommand() { const unsigned char sendBuffer[7] = { 0x07, 0x00, 0x40, 0x00, 0x61, 0x82, 0xE3 }; Send(sendBuffer, sizeof(sendBuffer)); } inline void SendStatusChanged() { const BoosterState booster = manager.Booster(); const unsigned char centralState = (!booster) << 1; unsigned char sendBuffer[8] = { 0x08, 0x00, 0x40, 0x00, 0x62, 0x22, centralState }; sendBuffer[sizeof(sendBuffer) - 1] = Utils::Utils::CalcXORCheckSum(sendBuffer + 4, sizeof(sendBuffer) - 5); Send(sendBuffer, sizeof(sendBuffer)); } inline void SendVersion() { unsigned char sendBuffer[9] = { 0x09, 0x00, 0x40, 0x00, 0x63, 0x21, 0x40, 0x12 }; sendBuffer[sizeof(sendBuffer) - 1] = Utils::Utils::CalcXORCheckSum(sendBuffer + 4, sizeof(sendBuffer) - 5); Send(sendBuffer, sizeof(sendBuffer)); } inline void SendBroadcastFlags() { unsigned char sendBuffer[8] = { 0x08, 0x00, 0x51, 0x00 }; Utils::Integer::IntToDataLittleEndian(broadCastFlags, sendBuffer + 4); Send(sendBuffer, sizeof(sendBuffer)); } inline void SendLocoMode(const uint16_t address) { unsigned char sendBuffer[7] = { 0x07, 0x00, 0x60, 0x00 }; Utils::Integer::ShortToDataBigEndian(address, sendBuffer + 4); sendBuffer[6] = 0x00; // we always use DCC Send(sendBuffer, sizeof(sendBuffer)); } inline void SendTurnoutMode(const uint16_t address) { unsigned char sendBuffer[7] = { 0x07, 0x00, 0x70, 0x00 }; Utils::Integer::ShortToDataBigEndian(address, sendBuffer + 4); sendBuffer[6] = 0x00; // we always use DCC Send(sendBuffer, sizeof(sendBuffer)); } inline void Send(const unsigned char* buffer, const size_t size) { logger->HexOut(buffer, size); UdpClient::Send(buffer, size); } Logger::Logger* logger; Manager& manager; Z21Enums::BroadCastFlags broadCastFlags; }; }} // namespace Server::Z21 railcontrol-24+dfsg1/Server/Z21/Z21Server.cpp000066400000000000000000000076501500456250600207350ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include "DataModel/LocoBase.h" #include "DataModel/LocoFunctions.h" #include "Manager.h" #include "Server/Z21/Z21Client.h" #include "Server/Z21/Z21Server.h" #include "Utils/Network.h" using DataModel::Loco; using DataModel::LocoBase; using DataModel::LocoFunctionNr; using DataModel::LocoFunctionState; namespace Network { class UdpClient; } namespace Server { namespace Z21 { Z21Server::Z21Server(Manager& manager, const unsigned short port) : ControlInterface(ControlTypeZ21Server), Network::UdpServer("0.0.0.0", port, "Z21Server"), logger(Logger::Logger::GetLogger("Z21Server")), manager(manager), lastClientID(0) { } Z21Server::~Z21Server() { logger->Info(Languages::TextZ21ServerStopped); } void Z21Server::Start() { StartUdpServer(); logger->Info(Languages::TextZ21ServerStarted); } void Z21Server::Stop() { TerminateUdpServer(); } Network::UdpClient* Z21Server::UdpClientFactory(const int serverSocket, const struct sockaddr_storage* clientAddress) { return reinterpret_cast(new Z21Client(++lastClientID, manager, serverSocket, clientAddress)); } void Z21Server::Booster(__attribute__((unused)) const ControlType controlType, const BoosterState status) { if (status == BoosterStateGo) { for (auto client : clients) { reinterpret_cast(client)->SendPowerOn(); } } else { for (auto client : clients) { reinterpret_cast(client)->SendPowerOff(); } } } void Z21Server::LocoBaseSpeed(__attribute__((unused)) const ControlType controlType, const LocoBase* locoBase, __attribute__((unused)) const Speed speed) { if (nullptr == locoBase) { return; } for (auto client : clients) { reinterpret_cast(client)->SendLocoInfo(locoBase); } } void Z21Server::LocoBaseOrientation(__attribute__((unused)) const ControlType controlType, const LocoBase* locoBase, __attribute__((unused)) const Orientation orientation) { if (nullptr == locoBase) { return; } for (auto client : clients) { reinterpret_cast(client)->SendLocoInfo(locoBase); } } void Z21Server::LocoBaseFunction(__attribute__((unused)) const ControlType controlType, const LocoBase* locoBase, __attribute__((unused)) const LocoFunctionNr function, __attribute__((unused)) const LocoFunctionState state) { if (!locoBase) { return; } for (auto client : clients) { reinterpret_cast(client)->SendLocoInfo(locoBase); } } void Z21Server::AccessoryState(__attribute__((unused)) const ControlType controlType, const DataModel::Accessory* accessory) { AccessoryBaseState(accessory); } void Z21Server::SwitchState(__attribute__((unused)) const ControlType controlType, const DataModel::Switch* mySwitch) { AccessoryBaseState(mySwitch); } void Z21Server::SignalState(__attribute__((unused)) const ControlType controlType, const DataModel::Signal* signal) { AccessoryBaseState(signal); } void Z21Server::AccessoryBaseState(const DataModel::AccessoryBase* accessoryBase) { if (!accessoryBase) { return; } for (auto client : clients) { reinterpret_cast(client)->SendTurnoutInfo(accessoryBase); } } }} // namespace Server::Z21 railcontrol-24+dfsg1/Server/Z21/Z21Server.h000066400000000000000000000056431500456250600204020ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include #include #include "ControlInterface.h" #include "Logger/Logger.h" #include "Manager.h" #include "Network/UdpServer.h" namespace Server { namespace Z21 { class Z21Client; class Z21Server : public ControlInterface, private Network::UdpServer { public: static const unsigned short Z21Port = 21105; Z21Server() = delete; Z21Server(const Z21Server&) = delete; Z21Server& operator=(const Z21Server&) = delete; Z21Server(Manager& manager, const unsigned short port); ~Z21Server(); void Start() override; void Stop() override; bool NextUpdate(unsigned int& updateIDClient, std::string& s); inline const std::string& GetName() const override { static const std::string Z21Name("Z21Server"); return Z21Name; } void Booster(const ControlType controlType, const BoosterState status) override; void LocoBaseOrientation(const ControlType controlType, const DataModel::LocoBase* loco, const Orientation direction) override; void LocoBaseFunction(const ControlType controlType, const DataModel::LocoBase* loco, const DataModel::LocoFunctionNr function, const DataModel::LocoFunctionState on) override; void LocoBaseSpeed(const ControlType controlType, const DataModel::LocoBase* loco, const Speed speed) override; void AccessoryState(const ControlType controlType, const DataModel::Accessory* accessory) override; void SwitchState(const ControlType controlType, const DataModel::Switch* mySwitch) override; void SignalState(const ControlType controlType, const DataModel::Signal* signal) override; protected: Network::UdpClient* UdpClientFactory(const int serverSocket, const struct sockaddr_storage* clientAddress) override; private: Logger::Logger* logger; Manager& manager; unsigned int lastClientID; ssize_t ParseData(const unsigned char* buffer, const ssize_t bufferLength, const struct sockaddr_storage* clientAddress); void ParseXHeader(const unsigned char* buffer); void ParseDB0(const unsigned char* buffer); void AccessoryBaseState(const DataModel::AccessoryBase* accessoryBase); }; }} // namespace Server::Z21 railcontrol-24+dfsg1/Storage/000077500000000000000000000000001500456250600162405ustar00rootroot00000000000000railcontrol-24+dfsg1/Storage/Sqlite.cpp000066400000000000000000000323451500456250600202140ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include #include #include "Languages.h" #include "Storage/Sqlite.h" #include "Utils/Integer.h" using DataModel::Accessory; using DataModel::Track; using DataModel::Feedback; using DataModel::Loco; using DataModel::Switch; using Hardware::HardwareParams; using std::map; using std::string; using std::vector; using std::to_string; namespace Storage { SQLite::SQLite(const StorageParams* params) : filename(params->filename), logger(Logger::Logger::GetLogger("SQLite")), keepBackups(params->keepBackups) { Utils::Utils::RemoveOldBackupFiles (logger, filename, keepBackups); logger->Info(Languages::TextOpeningSQLite, filename); int rc = sqlite3_open(filename.c_str(), &db); if (rc) { logger->Error(Languages::TextUnableToOpenSQLite, sqlite3_errmsg(db)); sqlite3_close(db); db = nullptr; return; } // check if needed tables exist map tablenames; const char* query = "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;"; bool ret = Execute(query, CallbackListTables, &tablenames); if (ret == false) { return; } // create hardware table if needed if (tablenames["hardware"] == false) { bool ret = CreateTableHardware(); if (ret == false) { return; } } // create objects table if needed if (tablenames["objects"] == false) { bool ret = CreateTableObjects(); if (ret == false) { return; } } // create relations table if needed string tableNameRelations = "relations"; if (tablenames[tableNameRelations] == true) { ret = CheckTableRelations(); } else { ret = CreateTableRelations(tableNameRelations); } if (ret == false) { return; } // create settings table if needed if (tablenames["settings"] == false) { bool ret = CreateTableSettings(); if (ret == false) { return; } } } SQLite::~SQLite() { if (db == nullptr) { return; } logger->Info(Languages::TextClosingSQLite); sqlite3_close(db); db = nullptr; if (keepBackups == 0) { return; } Utils::Utils::CopyFile(logger, filename, filename + "." + std::to_string(std::time(nullptr))); } int SQLite::CallbackListTables(void* v, int argc, char **argv, __attribute__((unused)) char **colName) { map* tablenames = static_cast*>(v); if (argc != 1) { return 0; } (*tablenames)[argv[0]] = true; return 0; } bool SQLite::DropTable(const string table) { logger->Info(Languages::TextDroppingTable, table); string query = "DROP TABLE " + table + ";"; return Execute(query); } bool SQLite::CreateTableHardware() { logger->Info(Languages::TextCreatingTable, "hardware"); const string query = "CREATE TABLE hardware (" "controlid UNSIGNED TINYINT," " hardwaretype UNSIGNED TINYINT," " name VARCHAR(50)," " arg1 VARCHAR(255)," " arg2 VARCHAR(255)," " arg3 VARCHAR(255)," " arg4 VARCHAR(255)," " arg5 VARCHAR(255)," "PRIMARY KEY (controlid));"; return Execute(query); } bool SQLite::CreateTableObjects() { logger->Info(Languages::TextCreatingTable, "objects"); const char* query = "CREATE TABLE objects (" "objecttype UNSIGNED TINYINT, " "objectid UNSIGNED SHORTINT, " "name VARCHAR(50), " "object SHORTTEXT," "PRIMARY KEY (objecttype, objectid));"; return Execute(query); } bool SQLite::CreateTableRelations(const string& name) { logger->Info(Languages::TextCreatingTable, name); const string query = "CREATE TABLE " + name + " (" "type UNSIGNED TINYINT, " "objectid1 UNSIGNED SHORTINT, " "objecttype2 UNSIGNED TINYINT, " "objectid2 UNSIGNED SHORTINT, " "priority UNSIGNED TINYINT, " "relation SHORTTEXT," "PRIMARY KEY (type, objectid1, objecttype2, objectid2, priority));"; return Execute(query); } bool SQLite::CreateTableSettings() { logger->Info(Languages::TextCreatingTable, "settings"); const char* query = "CREATE TABLE settings (" "key TINYTEXT, " "value SHORTTEXT," "PRIMARY KEY (key));"; return Execute(query); } struct TableInfo { public: int cid; string name; string type; bool notNull; bool defaultNull; string defaultValue; int primaryKey; }; bool SQLite::CheckTableRelations() { vector tableInfos; string query = "PRAGMA table_info('relations');"; bool ret = Execute(query, CallbackTableInfo, &tableInfos); if (ret == false) { return false; } string name = "relations"; if (tableInfos.size() != 6) { return DropTable(name) && CreateTableRelations(name); } if (tableInfos[0].name.compare("type") != 0) { return UpdateTableRelations1(); } // FIXME: simple check, extend with check if each object really exists query = "DELETE FROM relations WHERE objectid1 like 0;"; ret = Execute(query); if (ret == false) { return false; } logger->Info(Languages::TextIsUpToDate, name); return true; } bool SQLite::UpdateTableRelations1() { const string tableName = "relations"; const string tempTableName = "relations_temp"; bool ret = CreateTableRelations(tempTableName); if (ret == false) { return false; } logger->Info(Languages::TextCopyingFromTo, tableName, tempTableName); const string query = "INSERT INTO " + tempTableName + " SELECT objecttype1 * 8, objectid1, objecttype2, objectid2, priority, relation FROM " + tableName + ";"; ret = Execute(query); if (ret == false) { return false; } ret = DropTable(tableName); if (ret == false) { return false; } logger->Info(Languages::TextRenamingFromTo, tempTableName, tableName); return RenameTable(tempTableName, tableName); } bool SQLite::RenameTable(const string& oldName, const string& newName) { const string query = "ALTER TABLE " + oldName + " RENAME TO " + newName + ";"; return Execute(query); } int SQLite::CallbackTableInfo(void* v, int argc, char **argv, __attribute__((unused)) char **colName) { vector* tableInfos = static_cast*>(v); if (argc != 6) { return 0; } TableInfo tableInfo; tableInfo.cid = Utils::Integer::StringToInteger(argv[0]); tableInfo.name = argv[1]; tableInfo.type = argv[2]; tableInfo.notNull = Utils::Integer::StringToInteger(argv[3]) > 0; tableInfo.defaultNull = argv[4] == nullptr; tableInfo.defaultValue = argv[4] != nullptr ? argv[4] : ""; tableInfo.primaryKey = Utils::Integer::StringToInteger(argv[5]); tableInfos->push_back(tableInfo); return 0; } void SQLite::SaveHardwareParams(const Hardware::HardwareParams& hardwareParams) { string query = "INSERT OR REPLACE INTO hardware VALUES (" + to_string(hardwareParams.GetControlID()) + ", " + to_string(hardwareParams.GetHardwareType()) + ", '" + EscapeString(hardwareParams.GetName()) + "', '" + EscapeString(hardwareParams.GetArg1()) + "', '" + EscapeString(hardwareParams.GetArg2()) + "', '" + EscapeString(hardwareParams.GetArg3()) + "', '" + EscapeString(hardwareParams.GetArg4()) + "', '" + EscapeString(hardwareParams.GetArg5()) + "');"; Execute(query); } void SQLite::AllHardwareParams(std::map& hardwareParams) { const char* query = "SELECT controlid, hardwaretype, name, arg1, arg2, arg3, arg4, arg5 FROM hardware ORDER BY controlid;"; Execute(query, CallbackAllHardwareParams, &hardwareParams); } // callback read hardwareparams int SQLite::CallbackAllHardwareParams(void* v, int argc, char **argv, __attribute__((unused)) char **colName) { map* hardwareParams = static_cast*>(v); if (argc != 8) { return 0; } ControlID controlID = Utils::Integer::StringToInteger(argv[0]); HardwareParams* params = new HardwareParams(controlID, static_cast(Utils::Integer::StringToInteger(argv[1])), argv[2], argv[3], argv[4], argv[5], argv[6], argv[7]); (*hardwareParams)[controlID] = params; return 0; } // delete control void SQLite::DeleteHardwareParams(const ControlID controlID) { string query = "DELETE FROM hardware WHERE controlid = " + to_string(controlID) + ";"; Execute(query); } // save DataModelobject void SQLite::SaveObject(const ObjectType objectType, const ObjectID objectID, const std::string& name, const std::string& object) { string query = "INSERT OR REPLACE INTO objects (objecttype, objectid, name, object) VALUES (" + to_string(objectType) + ", " + to_string(objectID) + ", '" + EscapeString(name) + "', '" + EscapeString(object) + "');"; Execute(query); } // delete DataModelobject void SQLite::DeleteObject(const ObjectType objectType, const ObjectID objectID) { string query = "DELETE FROM objects WHERE objecttype = " + to_string(objectType) + " AND objectid = " + to_string(objectID) + ";"; Execute(query); } // read DataModelobjects void SQLite::ObjectsOfType(const ObjectType objectType, vector& objects) { string query = "SELECT object FROM objects WHERE objecttype = " + to_string(objectType) + " ORDER BY objectid;"; Execute(query, CallbackStringVector, &objects); } // save DataModelrelation void SQLite::SaveRelation(const DataModel::Relation::RelationType type, const ObjectID objectID1, const ObjectType objectType2, const ObjectID objectID2, const Priority priority, const std::string& relation) { string query = "INSERT OR REPLACE INTO relations (type, objectid1, objecttype2, objectid2, priority, relation) VALUES (" + to_string(type) + ", " + to_string(objectID1) + ", " + to_string(objectType2) + ", " + to_string(objectID2) + ", " + to_string(priority) + ", '" + EscapeString(relation) + "');"; Execute(query); } // delete DataModelrelaton void SQLite::DeleteRelationsFrom(const DataModel::Relation::RelationType type, const ObjectID objectID) { string query = "DELETE FROM relations WHERE type = " + to_string(type) + " AND objectid1 = " + to_string(objectID) + ";"; Execute(query); } // delete DataModelrelaton void SQLite::DeleteRelationsTo(const ObjectType objectType, const ObjectID objectID) { string query = "DELETE FROM relations WHERE objecttype2 = " + to_string(objectType) + " AND objectid2 = " + to_string(objectID) + ";"; Execute(query); } // read DataModelrelations void SQLite::RelationsFrom(const DataModel::Relation::RelationType type, const ObjectID objectID, vector& relations) { string query = "SELECT relation FROM relations WHERE type = " + to_string(type) + " AND objectid1 = " + to_string(objectID) + " ORDER BY priority ASC;"; Execute(query, CallbackStringVector, &relations); } // read DataModelrelations void SQLite::RelationsTo(const ObjectType objectType, const ObjectID objectID, vector& relations) { string query = "SELECT relation FROM relations WHERE objecttype2 = " + to_string(objectType) + " AND objectid2 = " + to_string(objectID) + ";"; Execute(query, CallbackStringVector, &relations); } void SQLite::SaveSetting(const string& key, const string& value) { Execute("INSERT OR REPLACE INTO settings (key, value) values ('" + EscapeString(key) + "', '" + EscapeString(value) + "');"); } string SQLite::GetSetting(const string& key) { vector values; string query = "SELECT value FROM settings WHERE key = '" + EscapeString(key) + "';"; bool ret = Execute(query, CallbackStringVector, &values); if (ret == false || values.size() == 0) { return ""; } return values[0]; } // callback read all DataModel objects/relations int SQLite::CallbackStringVector(void* v, int argc, char **argv, __attribute__((unused)) char **colName) { vector* objects = static_cast*>(v); if (argc != 1) { return 0; } objects->push_back(argv[0]); return 0; } void SQLite::StartTransaction() { Execute("BEGIN TRANSACTION;"); } void SQLite::CommitTransaction() { Execute("COMMIT;"); } bool SQLite::Execute(const char* query, sqlite3_callback callback = nullptr, void* result = nullptr) { if (db == nullptr) { return false; } char* dbError = nullptr; int rc = sqlite3_exec(db, query, callback, result, &dbError); if (rc == SQLITE_OK) { int affected = sqlite3_changes(db); if (affected) { logger->Debug(Languages::TextQueryAffected, query, affected); } else { logger->Debug(Languages::TextQuery, query); } return true; } logger->Error(Languages::TextSQLiteErrorQuery, dbError, query); sqlite3_free(dbError); return false; } string SQLite::EscapeString(const string& input) { string output; for (size_t i = 0; i < input.size(); ++i) { if (input[i] == '\'') { output += "'"; } output += input[i]; } return output; } } // namespace Storage railcontrol-24+dfsg1/Storage/Sqlite.h000066400000000000000000000071151500456250600176560ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include "DataModel/DataModel.h" #include "Logger/Logger.h" #include "Storage/sqlite/sqlite3.h" #include "Storage/StorageInterface.h" #include "Storage/StorageParams.h" namespace Storage { class SQLite : public StorageInterface { public: SQLite() = delete; SQLite(const StorageParams* params); ~SQLite(); void SaveHardwareParams(const Hardware::HardwareParams& params) override; void AllHardwareParams(std::map& hardwareParams) override; void DeleteHardwareParams(const ControlID controlID) override; void SaveObject(const ObjectType objectType, const ObjectID objectID, const std::string& name, const std::string& object) override; void DeleteObject(const ObjectType objectType, const ObjectID objectID) override; void ObjectsOfType(const ObjectType objectType, std::vector& objects) override; void SaveRelation(const DataModel::Relation::RelationType type, const ObjectID objectID1, const ObjectType objectType2, const ObjectID objectID2, const Priority priority, const std::string& relation) override; void DeleteRelationsFrom(const DataModel::Relation::RelationType type, const ObjectID objectID) override; void DeleteRelationsTo(const ObjectType objectType, const ObjectID objectID) override; void RelationsFrom(const DataModel::Relation::RelationType type, const ObjectID objectID, std::vector& relations) override; void RelationsTo(const ObjectType objectType, const ObjectID objectID, std::vector& relations) override; void SaveSetting(const std::string& key, const std::string& value) override; std::string GetSetting(const std::string& key) override; void StartTransaction() override; void CommitTransaction() override; private: sqlite3 *db; const std::string filename; Logger::Logger* logger; unsigned int keepBackups; inline bool Execute(const std::string& query, sqlite3_callback callback = nullptr, void* result = nullptr) { return Execute(query.c_str(), callback, result); } bool Execute(const char* query, sqlite3_callback callback, void* result); bool DropTable(const std::string table); bool CreateTableHardware(); bool CreateTableObjects(); bool CreateTableRelations(const std::string& name); bool CreateTableSettings(); bool CheckTableRelations(); bool UpdateTableRelations1(); bool RenameTable(const std::string& oldName, const std::string& newName); static int CallbackTableInfo(void *v, int argc, char **argv, char **colName); static int CallbackListTables(void *v, int argc, char **argv, char **colName); static int CallbackAllHardwareParams(void *v, int argc, char **argv, char **colName); static int CallbackStringVector(void* v, int argc, char **argv, char **colName); static std::string EscapeString(const std::string& input); }; } // namespace Storage railcontrol-24+dfsg1/Storage/StorageHandler.cpp000066400000000000000000000247311500456250600216550ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include #include "Logger/Logger.h" #include "Storage/Sqlite.h" #include "Storage/StorageHandler.h" #include "Utils/Utils.h" using DataModel::Accessory; using DataModel::Cluster; using DataModel::Feedback; using DataModel::Layer; using DataModel::Loco; using DataModel::MultipleUnit; using DataModel::Relation; using DataModel::Route; using DataModel::Signal; using DataModel::Switch; using DataModel::Text; using DataModel::Track; using std::map; using std::string; using std::vector; namespace Storage { void StorageHandler::AllLocos(map& locos) { vector serializedObjects; sqlite.ObjectsOfType(ObjectTypeLoco, serializedObjects); for (auto& serializedObject : serializedObjects) { Loco* loco = new Loco(manager, serializedObject); locos[loco->GetID()] = loco; } } void StorageHandler::DeleteLoco(const LocoID locoID) { sqlite.DeleteRelationsFrom(DataModel::Relation::RelationTypeLocoSlave, locoID); sqlite.DeleteRelationsTo(ObjectTypeLoco, locoID); sqlite.DeleteObject(ObjectTypeLoco, locoID); } void StorageHandler::AllMultipleUnits(map& multipleUnits) { vector serializedObjects; sqlite.ObjectsOfType(ObjectTypeMultipleUnit, serializedObjects); for (auto& serializedObject : serializedObjects) { MultipleUnit* multipleUnit = new MultipleUnit(manager, serializedObject); const MultipleUnitID multipleUnitID = multipleUnit->GetID(); multipleUnit->AssignSlaves(RelationsFrom(DataModel::Relation::RelationTypeMultipleUnitLoco, multipleUnitID)); multipleUnits[multipleUnitID] = multipleUnit; } } void StorageHandler::DeleteMultipleUnit(const MultipleUnitID multipleUnitID) { sqlite.DeleteRelationsFrom(DataModel::Relation::RelationTypeMultipleUnitLoco, multipleUnitID); sqlite.DeleteRelationsTo(ObjectTypeMultipleUnit, multipleUnitID); sqlite.DeleteObject(ObjectTypeMultipleUnit, multipleUnitID); } void StorageHandler::AllAccessories(std::map& accessories) { vector serializedObjects; sqlite.ObjectsOfType(ObjectTypeAccessory, serializedObjects); for (auto& serializedObject : serializedObjects) { Accessory* accessory = new Accessory(serializedObject); if (accessory == nullptr) { continue; } accessories[accessory->GetID()] = accessory; } } void StorageHandler::AllFeedbacks(std::map& feedbacks) { vector serializedObjects; sqlite.ObjectsOfType(ObjectTypeFeedback, serializedObjects); for (auto& serializedObject : serializedObjects) { Feedback* feedback = new Feedback(manager, serializedObject); if (feedback == nullptr) { continue; } feedbacks[feedback->GetID()] = feedback; } } void StorageHandler::AllTracks(std::map& tracks) { vector serializedObjects; sqlite.ObjectsOfType(ObjectTypeTrack, serializedObjects); for (auto& serializedObject : serializedObjects) { Track* track = new Track(manager, serializedObject); if (track == nullptr) { continue; } const TrackID trackId = track->GetID(); // FIXME: remove later: 2024-03-22 feedback vector has been replaced by relation // feedback vector was stored in track data // if no feedbacks are restored from track data override with data from relation const std::vector& feedbacksFromOldRelation = track->GetFeedbacks(); if (feedbacksFromOldRelation.size() == 0) { track->AssignFeedbacks(RelationsFrom(DataModel::Relation::RelationTypeTrackFeedback, trackId)); } track->AssignSignals(RelationsFrom(DataModel::Relation::RelationTypeTrackSignal, trackId)); tracks[trackId] = track; } } void StorageHandler::DeleteTrack(const TrackID trackID) { sqlite.DeleteRelationsTo(ObjectTypeTrack, trackID); sqlite.DeleteObject(ObjectTypeTrack, trackID); } void StorageHandler::AllSwitches(std::map& switches) { vector serializedObjects; sqlite.ObjectsOfType(ObjectTypeSwitch, serializedObjects); for (auto& serializedObject : serializedObjects) { Switch* mySwitch = new Switch(serializedObject); if (mySwitch == nullptr) { continue; } switches[mySwitch->GetID()] = mySwitch; } } void StorageHandler::Save(const DataModel::Route& route) { const string serialized = route.Serialize(); const RouteID routeID = route.GetID(); sqlite.SaveObject(ObjectTypeRoute, routeID, route.GetName(), serialized); sqlite.DeleteRelationsFrom(DataModel::Relation::RelationTypeRouteAtLock, routeID); SaveRelations(route.GetRelationsAtLock()); sqlite.DeleteRelationsFrom(DataModel::Relation::RelationTypeRouteAtUnlock, routeID); SaveRelations(route.GetRelationsAtUnlock()); } void StorageHandler::Save(const DataModel::Loco& loco) { const string serialized = loco.Serialize(); const LocoID locoID = loco.GetID(); sqlite.SaveObject(ObjectTypeLoco, locoID, loco.GetName(), serialized); sqlite.DeleteRelationsFrom(DataModel::Relation::RelationTypeLocoSlave, locoID); } void StorageHandler::Save(const DataModel::MultipleUnit& multipleUnit) { const string serialized = multipleUnit.Serialize(); const MultipleUnitID multipleUnitID = multipleUnit.GetID(); sqlite.SaveObject(ObjectTypeMultipleUnit, multipleUnitID, multipleUnit.GetName(), serialized); sqlite.DeleteRelationsFrom(DataModel::Relation::RelationTypeMultipleUnitLoco, multipleUnitID); SaveRelations(multipleUnit.GetSlaves()); } void StorageHandler::Save(const DataModel::Cluster& cluster) { const string serialized = cluster.Serialize(); const ClusterID clusterID = cluster.GetID(); sqlite.SaveObject(ObjectTypeCluster, clusterID, cluster.GetName(), serialized); sqlite.DeleteRelationsFrom(DataModel::Relation::RelationTypeClusterTrack, clusterID); SaveRelations(cluster.GetTracks()); } void StorageHandler::Save(const DataModel::Track& track) { const string serialized = track.Serialize(); const TrackID trackId = track.GetID(); sqlite.SaveObject(ObjectTypeTrack, trackId, track.GetName(), serialized); sqlite.DeleteRelationsFrom(DataModel::Relation::RelationTypeTrackFeedback, trackId); SaveRelations(track.GetFeedbacks()); sqlite.DeleteRelationsFrom(DataModel::Relation::RelationTypeTrackSignal, trackId); SaveRelations(track.GetSignals()); } void StorageHandler::AllRoutes(std::map& routes) { vector serializedObjects; sqlite.ObjectsOfType(ObjectTypeRoute, serializedObjects); for (auto& serializedObject : serializedObjects) { Route* route = new Route(manager, serializedObject); if (route == nullptr) { continue; } const RouteID routeID = route->GetID(); route->AssignRelationsAtLock(RelationsFrom(Relation::RelationTypeRouteAtLock, routeID)); route->AssignRelationsAtUnlock(RelationsFrom(Relation::RelationTypeRouteAtUnlock, routeID)); routes[routeID] = route; } } void StorageHandler::DeleteRoute(const RouteID routeID) { sqlite.DeleteRelationsFrom(DataModel::Relation::RelationTypeRouteAtLock, routeID); sqlite.DeleteRelationsFrom(DataModel::Relation::RelationTypeRouteAtUnlock, routeID); sqlite.DeleteObject(ObjectTypeRoute, routeID); } void StorageHandler::AllLayers(std::map& layers) { vector serializedObjects; sqlite.ObjectsOfType(ObjectTypeLayer, serializedObjects); for (auto& serializedObject : serializedObjects) { Layer* layer = new Layer(serializedObject); if (layer == nullptr) { continue; } layers[layer->GetID()] = layer; } } void StorageHandler::AllSignals(std::map& signals) { vector serializedObjects; sqlite.ObjectsOfType(ObjectTypeSignal, serializedObjects); for (auto& serializedObject : serializedObjects) { Signal* signal = new Signal(manager, serializedObject); if (signal == nullptr) { continue; } signals[signal->GetID()] = signal; } } void StorageHandler::DeleteSignal(const SignalID signalID) { sqlite.DeleteRelationsTo(ObjectTypeSignal, signalID); sqlite.DeleteObject(ObjectTypeSignal, signalID); } void StorageHandler::AllClusters(std::map& clusters) { vector serializedObjects; sqlite.ObjectsOfType(ObjectTypeCluster, serializedObjects); for (auto& serializedObject : serializedObjects) { Cluster* cluster = new Cluster(serializedObject); if (cluster == nullptr) { continue; } const ClusterID clusterId = cluster->GetID(); cluster->AssignTracks(RelationsFrom(DataModel::Relation::RelationTypeClusterTrack, clusterId)); clusters[clusterId] = cluster; } } void StorageHandler::AllTexts(std::map& texts) { vector serializedObjects; sqlite.ObjectsOfType(ObjectTypeText, serializedObjects); for (auto& serializedObject : serializedObjects) { Text* text = new Text(serializedObject); if (text == nullptr) { continue; } texts[text->GetID()] = text; } } void StorageHandler::SaveRelations(const vector relations) { for (auto relation : relations) { string serializedRelation = relation->Serialize(); sqlite.SaveRelation(relation->GetType(), relation->ObjectID1(), relation->ObjectType2(), relation->ObjectID2(), relation->GetPriority(), serializedRelation); } } vector StorageHandler::RelationsFrom(const DataModel::Relation::RelationType type, const ObjectID objectID) { vector relationStrings; sqlite.RelationsFrom(type, objectID, relationStrings); vector output; for (auto& relationString : relationStrings) { Relation* relation = new Relation(manager, relationString); if (relation == nullptr) { continue; } output.push_back(relation); } return output; } } // namespace Storage railcontrol-24+dfsg1/Storage/StorageHandler.h000066400000000000000000000107751500456250600213250ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include "DataModel/DataModel.h" #include "DataTypes.h" #include "Hardware/HardwareParams.h" #include "Storage/Sqlite.h" #include "Storage/StorageInterface.h" #include "Storage/TransactionGuard.h" #include "Storage/StorageParams.h" namespace Storage { class StorageHandler { public: inline StorageHandler(Manager* manager, const StorageParams* params) : manager(manager), sqlite(params) { } inline ~StorageHandler() { } inline void AllHardwareParams(std::map& hardwareParams) { sqlite.AllHardwareParams(hardwareParams); } inline void DeleteHardwareParams(const ControlID controlID) { sqlite.DeleteHardwareParams(controlID); } void AllLocos(std::map& locos); void DeleteLoco(LocoID locoID); void AllMultipleUnits(std::map& multipleUnits); void DeleteMultipleUnit(MultipleUnitID multipleUnitID); void AllAccessories(std::map& accessories); inline void DeleteAccessory(AccessoryID accessoryID) { sqlite.DeleteObject(ObjectTypeAccessory, accessoryID); } void AllFeedbacks(std::map& feedbacks); inline void DeleteFeedback(FeedbackID feedbackID) { sqlite.DeleteObject(ObjectTypeFeedback, feedbackID); } void AllTracks(std::map& tracks); void DeleteTrack(TrackID trackID); void AllSwitches(std::map& switches); inline void DeleteSwitch(SwitchID switchID) { sqlite.DeleteObject(ObjectTypeSwitch, switchID); } void AllRoutes(std::map& routes); void DeleteRoute(RouteID routeID); void AllLayers(std::map& layers); inline void DeleteLayer(LayerID layerID) { sqlite.DeleteObject(ObjectTypeLayer, layerID); } void AllSignals(std::map& signals); void DeleteSignal(SignalID signalID); void AllClusters(std::map& clusters); inline void DeleteCluster(ClusterID clusterID) { sqlite.DeleteObject(ObjectTypeCluster, clusterID); } void AllTexts(std::map& texts); inline void DeleteText(TextID textID) { sqlite.DeleteObject(ObjectTypeText, textID); } inline void Save(const Hardware::HardwareParams& hardwareParams) { sqlite.SaveHardwareParams(hardwareParams); } void Save(const DataModel::Route& route); void Save(const DataModel::Loco& loco); void Save(const DataModel::MultipleUnit& multipleUnit); void Save(const DataModel::Cluster& cluster); void Save(const DataModel::Track& track); template void Save(const T& t) { const std::string serialized = t.Serialize(); sqlite.SaveObject(t.GetObjectType(), t.GetID(), t.GetName(), serialized); } template static void Save(StorageHandler* storageHandler, const T* t) { storageHandler->Save(*t); } inline void SaveSetting(const std::string& key, const std::string& value) { sqlite.SaveSetting(key, value); } inline std::string GetSetting(const std::string& key) { return sqlite.GetSetting(key); } inline void StartTransaction() { transactionMutex.lock(); sqlite.StartTransaction(); } inline void CommitTransaction() { sqlite.CommitTransaction(); transactionMutex.unlock(); } private: void SaveRelations(const std::vector relations); std::vector RelationsFrom(const DataModel::Relation::RelationType type, const ObjectID objectID); Manager* manager; Storage::SQLite sqlite; std::mutex transactionMutex; }; } // namespace Storage railcontrol-24+dfsg1/Storage/StorageInterface.h000066400000000000000000000057121500456250600216430ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include "DataTypes.h" #include "Hardware/HardwareParams.h" namespace Storage { class StorageInterface { public: // non virtual default constructor is needed to prevent polymorphism StorageInterface() {}; // pure virtual destructor prevents polymorphism in derived class virtual ~StorageInterface() {}; // save control virtual void SaveHardwareParams(const Hardware::HardwareParams& hardwareParams) = 0; // read controls virtual void AllHardwareParams(std::map& hardwareParams) = 0; // delete control virtual void DeleteHardwareParams(const ControlID controlID) = 0; // save datamodelobject virtual void SaveObject(const ObjectType objectType, const ObjectID objectID, const std::string& name, const std::string& object) = 0; // delete datamodelobject virtual void DeleteObject(const ObjectType objectType, const ObjectID objectID) = 0; // read datamodelobject virtual void ObjectsOfType(const ObjectType objectType, std::vector& objects) = 0; // save datamodelrelation virtual void SaveRelation(const DataModel::Relation::RelationType type, const ObjectID objectID1, const ObjectType objectType2, const ObjectID objectID2, const Priority priority, const std::string& relation) = 0; // delete datamodelrelation virtual void DeleteRelationsFrom(const DataModel::Relation::RelationType type, const ObjectID objectID) = 0; // delete datamodelrelation virtual void DeleteRelationsTo(const ObjectType objectType, const ObjectID objectID) = 0; // read datamodelrelation virtual void RelationsFrom(const DataModel::Relation::RelationType type, const ObjectID objectID, std::vector& relations) = 0; // read datamodelrelation virtual void RelationsTo(const ObjectType objectType, const ObjectID objectID, std::vector& relations) = 0; // save setting virtual void SaveSetting(const std::string& key, const std::string& value) = 0; // read setting virtual std::string GetSetting(const std::string& key) = 0; // start transaction virtual void StartTransaction() {} // commit transaction virtual void CommitTransaction() {} }; } // namespace railcontrol-24+dfsg1/Storage/StorageParams.h000066400000000000000000000016301500456250600211610ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include namespace Storage { struct StorageParams { std::string module; std::string filename; unsigned int keepBackups; }; } // namespace Storage railcontrol-24+dfsg1/Storage/TransactionGuard.cpp000066400000000000000000000021011500456250600222060ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include "Storage/StorageHandler.h" #include "Storage/TransactionGuard.h" namespace Storage { TransactionGuard::TransactionGuard(StorageHandler* db) : db(db) { if (!db) { return; } db->StartTransaction(); } TransactionGuard::~TransactionGuard() { if (!db) { return; } db->CommitTransaction(); } } // namespace Storage railcontrol-24+dfsg1/Storage/TransactionGuard.h000066400000000000000000000021311500456250600216560ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once namespace Storage { class StorageHandler; class TransactionGuard { public: TransactionGuard() = delete; TransactionGuard(TransactionGuard&) = delete; TransactionGuard& operator=(TransactionGuard&) = delete; TransactionGuard(StorageHandler* db); ~TransactionGuard(); private: Storage::StorageHandler* db; }; } // namespace Storage railcontrol-24+dfsg1/TODO000066400000000000000000000015731500456250600153320ustar00rootroot00000000000000- Counter Objekt (Max Anzahl Züge in eine Richtung) - Link auf Ebenen verknüpfen mit Klick - Wartezeit bei Erreichen Fahrstrassen-Ende - Fahrstrassen mit unverschlossenen Objekten - Bedingungen für Fahrstrassen - Masseträgheit von Zügen simulieren - Bahnübergänge - Automatisches Umfahren des Zuges an der Endstation - Trotz freier Fahrt anhalten an Bahnsteigen - Lokbilder - Fahren nach Fahrplan - Formulare als POST-Request senden um maximale URL-Länge zu umgehen - Anbinden von SPROG DCC - Anbinden von Zimo-Zentralen - Anbinden von BidiB-Zentralen - Anbinden von uCon-Zentralen - Anbinden von USBtin-Märklin-CAN - Rückmelder für Weichenlage - S88 Link auf separaten Ebenen - Ausleuchtung von Gleisen aufgrund von Nachbargleis - Autokonversion der Protokolle bei Zentralenwechsel - CS2-Server - Verzögerung bei Feedback an / aus konfigurierbar - SocketCAN (für Märklin CAN) railcontrol-24+dfsg1/Utils/000077500000000000000000000000001500456250600157345ustar00rootroot00000000000000railcontrol-24+dfsg1/Utils/Integer.cpp000066400000000000000000000102241500456250600200340ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include #include "Utils/Integer.h" namespace Utils { int Integer::StringToInteger(const std::string& value, const int defaultValue) { size_t valueSize = value.length(); if (valueSize == 0) { return defaultValue; } char* end; const char* start = value.c_str(); long longValue = std::strtol(start, &end, 10); if (errno == ERANGE || start == end) { return defaultValue; } if (longValue > INT_MAX || longValue < INT_MIN) { return defaultValue; } return static_cast(longValue); } int Integer::StringToInteger(const std::string& value, const int min, const int max) { int intValue = StringToInteger(value, min); if (intValue < min) { return min; } if (intValue > max) { return max; } return intValue; } long Integer::HexToInteger(const std::string& value, const long defaultValue) { size_t valueSize = value.length(); if (valueSize == 0) { return defaultValue; } char* end; const char* start = value.c_str(); long longValue = std::strtol(start, &end, 16); if (errno == ERANGE || start == end) { return defaultValue; } return longValue; } signed char Integer::HexToChar(signed char c) { if (c >= 'a') { c -= 'a' - 10; } else if (c >= 'A') { c -= 'A' - 10; } else if (c >= '0') { c -= '0'; } return c > 15 ? 0 : c; } void Integer::IntToDataBigEndian(const uint32_t i, unsigned char* buffer) { buffer[0] = (i >> 24); buffer[1] = ((i >> 16) & 0xFF); buffer[2] = ((i >> 8) & 0xFF); buffer[3] = (i & 0xFF); } uint32_t Integer::DataBigEndianToInt(const unsigned char* buffer) { uint32_t i = buffer[0]; i <<= 8; i |= buffer[1]; i <<= 8; i |= buffer[2]; i <<= 8; i |= buffer[3]; return i; } void Integer::ShortToDataBigEndian(const uint16_t i, unsigned char* buffer) { buffer[0] = (i >> 8); buffer[1] = (i & 0xFF); } uint16_t Integer::DataBigEndianToShort(const unsigned char* buffer) { uint16_t i = buffer[0]; i <<= 8; i |= buffer[1]; return i; } void Integer::IntToDataLittleEndian(const uint32_t i, unsigned char* buffer) { buffer[0] = (i & 0xFF); buffer[1] = ((i >> 8) & 0xFF); buffer[2] = ((i >> 16) & 0xFF); buffer[3] = (i >> 24); } uint32_t Integer::DataLittleEndianToInt(const unsigned char* buffer) { return buffer[0] + (buffer[1] << 8) + (buffer[2] << 16) + (buffer[3] << 24); } void Integer::ShortToDataLittleEndian(const uint16_t i, unsigned char* buffer) { buffer[0] = (i & 0xFF); buffer[1] = (i >> 8); } uint16_t Integer::DataLittleEndianToShort(const unsigned char* buffer) { return buffer[0] + (buffer[1] << 8); } std::string Integer::IntegerToBCD(const unsigned int input) { unsigned char zero = (input >> 4); zero &= 0x0000000F; zero += '0'; unsigned char one = input; one &= 0x0000000F; one += '0'; std::string output; output.append(1, zero); output.append(1, one); return output; } std::string Integer::IntegerToHex(const unsigned int input, const unsigned int size) { if (input == 0) { return "0"; } std::string output; unsigned int decimal = input; unsigned int internalSize = 0; while (decimal) { std::stringstream part; part << std::setfill('0') << std::setw(1) << std::hex << (decimal & 0xF); output = part.str() + output; decimal >>= 4; ++internalSize; } while (internalSize < size) { ++internalSize; output = "0" + output; } return output; } } railcontrol-24+dfsg1/Utils/Integer.h000066400000000000000000000040041500456250600175000ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include namespace Utils { class Integer { public: static inline int StringToInteger(const std::string& value) { return StringToInteger(value, 0, INT_MAX); } static int StringToInteger(const std::string& value, const int defaultValue); static int StringToInteger(const std::string& value, const int min, const int max); static long HexToInteger(const std::string& value, const long defaultValue = 0); static signed char HexToChar(signed char c); static void IntToDataBigEndian(const uint32_t i, unsigned char* buffer); static uint32_t DataBigEndianToInt(const unsigned char* buffer); static void ShortToDataBigEndian(const uint16_t i, unsigned char* buffer); static uint16_t DataBigEndianToShort(const unsigned char* buffer); static void IntToDataLittleEndian(const uint32_t i, unsigned char* buffer); static uint32_t DataLittleEndianToInt(const unsigned char* buffer); static void ShortToDataLittleEndian(const uint16_t i, unsigned char* buffer); static uint16_t DataLittleEndianToShort(const unsigned char* buffer); static std::string IntegerToBCD(const unsigned int input); static std::string IntegerToHex(const unsigned int input, const unsigned int size = 1); }; // class Integer } // namespace Utils railcontrol-24+dfsg1/Utils/Network.cpp000066400000000000000000000070571500456250600201020ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include #include #include "Utils/Network.h" using std::string; namespace Utils { string Network::AddressToString(const struct sockaddr_storage *sockaddr) { char buffer[40] = { 0 }; unsigned short port = 0; switch(sockaddr->ss_family) { case AF_INET: { const struct sockaddr_in* sa4 = reinterpret_cast(sockaddr); inet_ntop(AF_INET, &(sa4->sin_addr), buffer, sizeof(buffer)); port = ntohs(sa4->sin_port); break; } case AF_INET6: { const struct sockaddr_in6* sa6 = reinterpret_cast(sockaddr); inet_ntop(AF_INET6, &(sa6->sin6_addr), buffer, sizeof(buffer)); port = ntohs(sa6->sin6_port); break; } default: break; } return string("[") + string(buffer) + string("]:") + std::to_string(port); } bool Network::CompareAddresses(const struct sockaddr_storage *address1, const struct sockaddr_storage *address2) { const unsigned short family = (reinterpret_cast(address1))->sa_family; if (family != (reinterpret_cast(address2))->sa_family) { return false; } switch (family) { case AF_INET: { const struct sockaddr_in* inet1 = reinterpret_cast(address1); const struct sockaddr_in* inet2 = reinterpret_cast(address2); if (inet1->sin_port != inet2->sin_port) { return false; } return (0 == memcmp(&(inet1->sin_addr), &(inet2->sin_addr), sizeof(inet1->sin_addr))); } case AF_INET6: { const struct sockaddr_in6* inet1 = reinterpret_cast(address1); const struct sockaddr_in6* inet2 = reinterpret_cast(address2); if (inet1->sin6_port != inet2->sin6_port) { return false; } return (0 == memcmp(&(inet1->sin6_addr), &(inet2->sin6_addr), sizeof(inet1->sin6_addr))); } default: return false; } } bool Network::HostResolves(const string& host) { struct addrinfo hints; memset(&hints, 0, sizeof(hints)); hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags |= AI_CANONNAME; struct addrinfo* res; struct addrinfo* result; int errcode = getaddrinfo(host.c_str(), NULL, &hints, &result); if (errcode != 0) { return false; } res = result; while (res) { char addrstr[100]; inet_ntop(res->ai_family, res->ai_addr->sa_data, addrstr, sizeof(addrstr)); switch (res->ai_family) { case AF_INET: case AF_INET6: freeaddrinfo(result); return true; default: res = res->ai_next; break; } } freeaddrinfo(result); return false; } } // namespace Utils railcontrol-24+dfsg1/Utils/Network.h000066400000000000000000000023221500456250600175350ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include namespace Utils { class Network { public: Network() = delete; Network(const Network&) = delete; Network& operator=(const Network&) = delete; static std::string AddressToString(const struct sockaddr_storage *sockaddr); static bool CompareAddresses(const struct sockaddr_storage *a1, const struct sockaddr_storage *a); static bool HostResolves(const std::string& host); }; } // namespace Utils railcontrol-24+dfsg1/Utils/ThreadSafeQueue.h000066400000000000000000000035231500456250600211230ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include namespace Utils { template class ThreadSafeQueue { public: inline ThreadSafeQueue() : list(), mutex(), run(true) { } inline ~ThreadSafeQueue() { Terminate(); } inline void EnqueueBack(T t) { std::unique_lock lock(mutex); list.push_back(t); cv.notify_all(); } inline void EnqueueFront(T t) { std::unique_lock lock(mutex); list.push_front(t); cv.notify_all(); } T Dequeue() { std::unique_lock lock(mutex); while (list.empty()) { if (run == false) { return T(); } cv.wait_for(lock, std::chrono::seconds(1)); } T val = list.front(); list.pop_front(); return val; } inline bool IsEmpty() { std::unique_lock lock(mutex); return list.empty(); } inline void Terminate() { run = false; cv.notify_all(); } private: std::list list; mutable std::mutex mutex; std::condition_variable cv; volatile bool run; }; } railcontrol-24+dfsg1/Utils/Utils.cpp000066400000000000000000000202671500456250600175470ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include #include #include // va_* in xlog #include // printf #include // exit(0); #include // memset #include #include #include // cout #include #include #include // gettimeofday #include #include "Languages.h" #include "Logger/Logger.h" #include "Network/Select.h" #include "Utils/Integer.h" #include "Utils/Utils.h" using std::cout; using std::deque; using std::endl; using std::string; using std::to_string; using std::vector; namespace Utils { void Utils::ReplaceString(std::string& str, const std::string& from, const std::string& to) { size_t lastOccurrence = 0; while (true) { size_t startPos = str.find(from, lastOccurrence); if (startPos == string::npos) { return; } str.replace(startPos, from.length(), to); lastOccurrence = startPos + to.length(); } } void Utils::SplitString(const string& input, const string& delimiter, deque& list) { size_t delimiterLength = delimiter.length(); string workingString(input); while (true) { size_t pos = workingString.find(delimiter); list.push_back(workingString.substr(0, pos)); if (pos == string::npos) { return; } workingString = string(workingString.substr(pos + delimiterLength, string::npos)); } } void Utils::SplitString(const string& input, const string& delimiter, string& first, string& second) { size_t delimiterLength = delimiter.length(); size_t pos = input.find(delimiter); first = input.substr(0, pos); second = input.substr(pos + delimiterLength); } string Utils::StringBeforeDelimiter(const std::string& input, const std::string& delimiter) { string ret; string unused; SplitString(input, delimiter, ret, unused); return ret; } string Utils::UrlDecode(const string& value) { string output = value; size_t startSearch = 0; while (true) { size_t pos = output.find('%', startSearch); if (pos == string::npos || pos + 3 > output.length()) { break; } const unsigned char highNibble = Integer::HexToChar(output[pos + 1]); const unsigned char lowNibble = Integer::HexToChar(output[pos + 2]); const unsigned char c = (highNibble << 4) + lowNibble; output.replace(pos, 3, 1, c); startSearch = pos + 1; } return output; } string Utils::UrlEncode(const string& value) { string output = value; ReplaceString(output, "%", "%25"); ReplaceString(output, "&", "%26"); ReplaceString(output, "=", "%3d"); return output; } string Utils::HtmlEncode(const string& value) { string modifiedValue = value; ReplaceString(modifiedValue, "&", "&"); ReplaceString(modifiedValue, "\"", """); ReplaceString(modifiedValue, "'", "'"); ReplaceString(modifiedValue, "<", "<"); ReplaceString(modifiedValue, ">", ">"); return modifiedValue; } const std::string& Utils::GetStringMapEntry(const std::map& map, const std::string& key, const std::string& defaultValue) { if (map.count(key) == 0) { return defaultValue; } return map.at(key); } int Utils::GetIntegerMapEntry(const std::map& map, const std::string& key, const int defaultValue) { if (map.count(key) == 0) { return defaultValue; } return Integer::StringToInteger(map.at(key), defaultValue); } bool Utils::GetBoolMapEntry(const std::map& map, const std::string& key, const bool defaultValue) { if (map.count(key) == 0) { return defaultValue; } string value = map.at(key); return (value.compare("true") == 0 || value.compare("on") == 0 || value.compare("1") == 0 || key.compare(value) == 0); } string Utils::ToStringWithLeadingZeros(const unsigned int number, const unsigned char chars) { string out = to_string(number); while (out.length() < chars) { out.insert(0, "0"); } return out; } bool Utils::StringToBool(const std::string& value, const bool defaultValue) { if (value.size() == 0) { return defaultValue; } int intValue = Integer::StringToInteger(value); return intValue != 0; } string Utils::StringToLower(const string& input) { string output = input; for(auto& c : output) { c = tolower(c); } return output; } void Utils::CopyFile(Logger::Logger* logger, const std::string& from, const std::string& to) { logger->Info(Languages::TextCopyingFromTo, from, to); std::ifstream source(from, std::ios::binary); std::ofstream destination(to, std::ios::binary); destination << source.rdbuf(); source.close(); destination.close(); } void Utils::RenameFile(Logger::Logger* logger, const std::string& from, const std::string& to) { if (logger != nullptr) { logger->Info(Languages::TextRenamingFromTo, from, to); } std::rename(from.c_str(), to.c_str()); } void Utils::RemoveOldBackupFiles (Logger::Logger *logger, const std::string &filename, unsigned int keepBackups) { DIR *dir = opendir("."); if (dir == nullptr) { return; } struct dirent *ent; std::vector < string > fileNames; const string filenameSearch = filename + "."; const size_t filenameSearchLength = filenameSearch.length() + 10; while (true) { ent = readdir(dir); if (ent == nullptr) { break; } string fileName = ent->d_name; if (fileName.length() != filenameSearchLength || fileName.find(filenameSearch) == string::npos) { continue; } fileNames.push_back(ent->d_name); } closedir(dir); std::sort(fileNames.begin(), fileNames.end()); size_t numberOfFiles = fileNames.size(); if (numberOfFiles == 0 || numberOfFiles < keepBackups) { return; } unsigned int removeBackups = fileNames.size() - keepBackups; ++removeBackups; // at shutdown we create another backupfile for (auto &fileName : fileNames) { if (removeBackups == 0) { return; } --removeBackups; logger->Info(Languages::TextRemoveBackupFile, fileName); remove(fileName.c_str()); } } void Utils::SetMinThreadPriority() { sched_param param; int policy; pthread_t self = pthread_self(); pthread_getschedparam(self, &policy, ¶m); param.sched_priority = sched_get_priority_min(policy); pthread_setschedparam(self, policy, ¶m); } std::string Utils::TimestampToDate(const time_t timestamp) { struct tm *tm = localtime(×tamp); char date[20]; strftime(date, sizeof(date), "%Y-%m-%d %H:%M:%S", tm); return date; } #ifdef __CYGWIN__ bool Utils::GetFilesInDir(vector& filesFound, const string& path, const string& prefix) { bool ret = false; size_t prefixLength = prefix.length(); DIR* dirp = opendir(path.c_str()); struct dirent* dp; while ((dp = readdir(dirp)) != nullptr) { string file(dp->d_name); string prefixRead = file.substr(0, prefixLength); if (prefix.compare(prefixRead) != 0) { continue; } filesFound.push_back(file); ret = true; } closedir(dirp); return ret; } bool Utils::GetComPorts(std::vector& comPorts) { vector filesFound; bool ret = GetFilesInDir(filesFound, "/dev/", "ttyS"); if (ret == false) { return false; } for (auto& file : filesFound) { string comPortString = file.substr(4); unsigned char comPort = Integer::StringToInteger(comPortString); comPorts.push_back(comPort); } return true; } #endif uint8_t Utils::CalcXORCheckSum(const uint8_t* const buffer, size_t length) { uint8_t ret = 0; for (size_t i = 0; i < length; ++i) { ret ^= buffer[i]; } return ret; } } railcontrol-24+dfsg1/Utils/Utils.h000066400000000000000000000105031500456250600172040ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #pragma once #include #include #include #include #include #include #include #include #include "DataTypes.h" namespace Logger { class Logger; } namespace Utils { class Utils { public: static void ReplaceString(std::string& str, const std::string& from, const std::string& to); static void SplitString(const std::string& str, const std::string& delimiter, std::deque& list); static void SplitString(const std::string& input, const std::string& delimiter, std::string& first, std::string& second); static std::string StringBeforeDelimiter(const std::string& input, const std::string& delimiter); static std::string UrlDecode(const std::string& value); static std::string UrlEncode(const std::string& value); static std::string HtmlEncode(const std::string& value); static inline bool IsMapEntrySet(const std::map& map, const std::string& key) { return map.count(key) != 0; } static const std::string& GetStringMapEntry(const std::map& map, const std::string& key, const std::string& defaultValue = ""); static int GetIntegerMapEntry(const std::map& map, const std::string& key, const int defaultValue = 0); static bool GetBoolMapEntry(const std::map& map, const std::string& key, const bool defaultValue = false); static std::string ToStringWithLeadingZeros(const unsigned int number, const unsigned char chars); static bool StringToBool(const std::string& value, const bool defaultValue = false); static std::string StringToLower(const std::string& input); static inline bool FileExists(const std::string& name) { struct stat buffer; return (stat(name.c_str(), &buffer) == 0); } static void CopyFile(Logger::Logger* logger, const std::string& from, const std::string& to); static void RenameFile(Logger::Logger* logger, const std::string& from, const std::string& to); static void RemoveOldBackupFiles (Logger::Logger *logger, const std::string &filename, unsigned int keepBackups); static inline void SetThreadName(const std::string& name) { SetThreadName(name.c_str()); } static inline void SetThreadName(__attribute__((unused)) const char* name) { #if __GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 12) pthread_setname_np(pthread_self(), name); #endif } static void SetMinThreadPriority(); static std::string TimestampToDate(const time_t timestamp); static inline void SleepForSeconds(unsigned int seconds) { std::this_thread::sleep_for(std::chrono::seconds(seconds)); } static inline void SleepForMilliseconds(unsigned int milliSeconds) { std::this_thread::sleep_for(std::chrono::milliseconds(milliSeconds)); } #ifdef __CYGWIN__ private: static bool GetFilesInDir(std::vector& filesFound, const std::string& path, const std::string& prefix); public: static bool GetComPorts(std::vector& comPorts); #endif static inline void Copy8Bytes(const void* const from, void* const to) { *(reinterpret_cast(to)) = *(reinterpret_cast(from)); } static inline void Copy4Bytes(const void* const from, void* const to) { *(reinterpret_cast(to)) = *(reinterpret_cast(from)); } static uint8_t CalcXORCheckSum(const uint8_t* const buffer, size_t length); static inline std::string ProtocolToString(const Protocol protocol) { return ProtocolSymbols[protocol]; } }; // class Utils } // namespace Utils railcontrol-24+dfsg1/Version.cpp.in000066400000000000000000000023271500456250600173760ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include "Version.h" const std::string& GetVersionInfoGitHash() { static const std::string gitHash = "@GIT_HASH@"; return gitHash; } time_t GetVersionInfoGitTimestamp() { return @GIT_TIMESTAMP@; } unsigned int GetVersionInfoGitDirty() { return @GIT_DIRTY@; } time_t GetVersionInfoCompileTimestamp() { return @COMPILE_TIMESTAMP@; } const std::string& GetVersionInfoRailControlVersion() { static const std::string railControlVersion = "@RAILCONTROL_VERSION@"; return railControlVersion; } railcontrol-24+dfsg1/Version.h000066400000000000000000000017161500456250600164370ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include const std::string& GetVersionInfoGitHash(); time_t GetVersionInfoGitTimestamp(); unsigned int GetVersionInfoGitDirty(); time_t GetVersionInfoCompileTimestamp(); const std::string& GetVersionInfoRailControlVersion(); railcontrol-24+dfsg1/amalgamation.bash000066400000000000000000000006131500456250600201250ustar00rootroot00000000000000#!/bin/bash EXEC=./make_amalgamation.bash CPP=amalgamation.cpp rm -f $CPP echo "#!/bin/bash echo Preparing \$1 if [ \"x\" = \"x\$1\" ] ; then exit fi cat \$1 >> $CPP " > $EXEC chmod u+x $EXEC for dir in . DataModel Hardware Hardware/Protocols Logger Network Storage Utils Server/CS2 Server/Web Server/Z21 ; do find $dir -maxdepth 1 -type f -name "*.cpp" -exec $EXEC {} \; done rm $EXEC railcontrol-24+dfsg1/html/000077500000000000000000000000001500456250600156005ustar00rootroot00000000000000railcontrol-24+dfsg1/html/clock.svg000066400000000000000000000070751500456250600174250ustar00rootroot00000000000000 railcontrol-24+dfsg1/html/favicon.ico000066400000000000000000000046721500456250600177320ustar00rootroot00000000000000  PNG  IHDR szz kIDATXˏ}?Տ.wEYDdh[,8p.r%6ɀ'H`8#İ%JeKDDH->=~U@C&U]}SտP)uKR$IhhRi#g" CutH)t:A>K$IrC)%>x INT'&_`qq!}jJ8> q$}odP12C9LNM8o""u45\~'b>MPP(2 C\0 §t,>Hg}{|O~q1IzJ)t]X,Cx8e$AIũϟBӴc< )%n@[}B>U:c_[B;8h_JIDQDqI^{U6 ni}RʃAh4X^h4bjuH$R&_G;یun* =w DZ:Kϭˌa T@Ŭn1hnmZ <}DCǿ_ 4yƶmǶIg274]X}N?4GVAq2I\箯$$,,;K>Φ)ߨm;ǞJn-ONcH^efq29ylB@Amm IckfNbPS"Fjɤ( gN8$Bbsl^dD6k!AD@=^u.eOVO[W1$? @JhH!O]mR#hhyB`J􃀽FFT䀿uxm?fN47ۇ2EC ; yѓ!O/r!riG4}ߣѝm+#fgY^%CJ*Gfi^^Itl.1:54)SO=LSp%B][$}.8~]|c8Dn}ǃaDa3Vs?Wq\ %5j[OE!g6m[ eޞF18VlmRDaQR):N! Pwa{ 0Pe-" K10ѠxDA#qnHih"nEH l}-fq8)ھVC u0u$AX6iX FVg?zl \Nv4KKDAH[]%M)]LSre{l&jG!+133CC*;$fjھW'xѦ8<&\y=,1nB(ep1Lѕy+f# ˑJgHm#ׯ!'g)dt]&"d? |{DQșjski$TզIXdc5t@ *1 'ymKY^<p4]_QmQ_? X_YڥeY:mcgMNVj}x3O%c8NvT+RB4q]Qg\))Jdm@AG ~Qz8 "DeIENDB`railcontrol-24+dfsg1/html/javascript.js000066400000000000000000001453271500456250600203200ustar00rootroot00000000000000function selectValue(key, value, elementId) { var dropdown = document.getElementById('d_' + elementId); if (!dropdown) { return; } dropdown.classList.remove('show'); var dropdownElements = dropdown.childNodes; for (var i = 0; i < dropdownElements.length; ++i) { var element = dropdownElements[i]; element.classList.remove('selected_option'); var attributes = element.attributes; if (attributes.key.value == key) { element.classList.add('selected_option'); } } var textElement = document.getElementById('skip_' + elementId); if (!textElement) { return; } textElement.value = value; var keyElement = document.getElementById(elementId); if (!keyElement) { return; } keyElement.value = key; keyElement.onchange(); } function selectMultipleValue(changedKey, elementId, none, several) { var dropdown = document.getElementById('d_' + elementId); if (!dropdown) { return; } var keyElement = document.getElementById(elementId); if (!keyElement) { return; } var valueElement = document.getElementById('skip_' + elementId); if (!valueElement) { return; } var dropdownElements = dropdown.childNodes; var key = keyElement.value; var value = valueElement.value; for (var i = 0; i < dropdownElements.length; ++i) { var element = dropdownElements[i]; var attributes = element.attributes; if (attributes.key.value != changedKey) { continue; } if (element.classList.contains('selected_option')) { key &= ~changedKey; } else { key |= changedKey; } } var optionsCount = 0; for (var i = 0; i < dropdownElements.length; ++i) { var element = dropdownElements[i]; var attributes = element.attributes; if ((key & attributes.key.value) == attributes.key.value) { element.classList.add('selected_option'); value = element.textContent; ++optionsCount; } else { element.classList.remove('selected_option'); } } var textElement = document.getElementById('skip_' + elementId); if (!textElement) { return; } switch(optionsCount) { case 0: textElement.value = none; break; case 1: textElement.value = value; break; default: textElement.value = several; break; } keyElement.value = key; keyElement.onchange(); } window.addEventListener('click', function(event) { for (const select of document.querySelectorAll('.dropdown')) { if (!select.contains(event.target)) { select.classList.remove('show'); } } }, true); function toggleClass(elementId, name) { var element = document.getElementById(elementId); if (!element) { return; } element.classList.toggle(name); } function modifierKeyPressed(event) { return (event.shiftKey || event.ctrlKey || event.altKey); } function onClickWithoutMenu(event, identifier) { if (!modifierKeyPressed(event)) { return false; } rotateObject(identifier); return false; } function onClickWithMenu(event, identifier) { if (!modifierKeyPressed(event)) { return showOnClickMenu(event, identifier); } rotateObject(identifier); return false; } function rotateObject(identifier) { var url = "/?cmd=rotate&" + parseObjectIdentifier(identifier); fireRequestAndForget(url); } function drag(event) { if (!modifierKeyPressed(event)) { event.dataTransfer.setData("Text", ""); return; } event.dataTransfer.setData("Text", event.target.id); } function allowDrop(event) { event.preventDefault(); } function drop(event) { if (!modifierKeyPressed(event)) { return; } event.preventDefault(); var data = event.dataTransfer.getData("Text"); var x = Math.round(event.target.scrollLeft + event.offsetX, 0); var y = Math.round(event.target.scrollTop + event.offsetY, 0); x = Math.floor(x / 36); y = Math.floor(y / 36); var url = "/?cmd=newposition&" + parseObjectIdentifier(data) + "&x=" + x + "&y=" + y; fireRequestAndForget(url); } function parseObjectIdentifier(identifier) { var dataArray = identifier.split('_'); var type; switch(dataArray[0]) { case "t": type = "track"; break; case "sw": type = "switch"; break; case "si": type = "signal"; break; case "f": type = "feedback"; break; case "a": type = "accessory"; break; case "r": type = "route"; break; case "tx": type = "text"; break; default: type = "object"; break; } return type + "=" + dataArray[1]; } function showMenuConfig() { var menuConfig = document.getElementById('menu_config'); if (!menuConfig) { return; } if (menuConfig.classList.contains('menu_config')) { menuConfig.classList.replace('menu_config', 'menu_config_visible'); } else { menuConfig.classList.replace('menu_config_visible', 'menu_config'); } } function activateFullScreen() { var element = document.documentElement; if (element.requestFullscreen) { element.requestFullscreen(); } } function closeFullScreen() { if (document.exitFullscreen) { document.exitFullscreen(); } } function fullScreen() { if (document.fullscreen) { closeFullScreen(); } else { activateFullScreen(); } } function onChangeLocoFunctionType(nr) { var locoFunctionType = document.getElementById('s_f' + nr + '_type'); if (!locoFunctionType) { return false; } var locoFunctionIcon = document.getElementById('s_f' + nr + '_icon_container'); if (!locoFunctionIcon) { return false; } var locoFunctionTimer = document.getElementById('d_f' + nr + '_timer'); if (!locoFunctionTimer) { return false; } var type = locoFunctionType.value; if (type == 0) // LocoFunctionTypeNone { locoFunctionIcon.classList.add('hidden'); } else { locoFunctionIcon.classList.remove('hidden'); } if (type == 4) // LocoFunctionTypeTimer { locoFunctionTimer.classList.remove('hidden'); } else { locoFunctionTimer.classList.add('hidden'); } return false; } function onClickAddresses(signal) { var addressElement = document.getElementById('address'); if (!addressElement) { return false; } var address = addressElement.value; var typeElement = document.getElementById('s_signaltype'); if (!typeElement) { return false; } var type = typeElement.value; var url = '?cmd=signaladdresses&address=' + address + '&type=' + type + '&signal=' + signal; requestUpdateItem('addresses', url); } function onClickProgramRead(cv) { var controlElement = document.getElementById('s_controlraw'); if (!controlElement) { return false; } var control = controlElement.value; var modeElement = document.getElementById('s_moderaw'); if (!modeElement) { return false; } var mode = modeElement.value; var addressElement = document.getElementById('addressraw'); if (!addressElement) { return false; } var address = addressElement.value; var indexElement = document.getElementById('indexraw'); if (!indexElement) { return false; } var index = indexElement.value; var cvElement = document.getElementById('cvraw'); if (!cvElement) { return false; } var cv = parseInt(cvElement.value) + (parseInt(index) * 1024); var valueElement = document.getElementById('valueraw'); if (!valueElement) { return false; } valueElement.value = '-'; var url = '?cmd=programread&control=' + control + '&mode=' + mode + '&address=' + address + '&cv=' + cv; fireRequestAndForget(url); return false; } function onClickProgramWrite() { var controlElement = document.getElementById('s_controlraw'); if (!controlElement) { return false; } var control = controlElement.value; var modeElement = document.getElementById('s_moderaw'); if (!modeElement) { return false; } var mode = modeElement.value; var addressElement = document.getElementById('addressraw'); if (!addressElement) { return false; } var address = addressElement.value; var indexElement = document.getElementById('indexraw'); if (!indexElement) { return false; } var index = indexElement.value; var cvElement = document.getElementById('cvraw'); if (!cvElement) { return false; } var cv = parseInt(cvElement.value) + (parseInt(index) * 1024); var valueElement = document.getElementById('valueraw'); if (!valueElement) { return false; } var value = valueElement.value; var url = '?cmd=programwrite&control=' + control + '&mode=' + mode + '&address=' + address + '&cv=' + cv + '&value=' + value; fireRequestAndForget(url); return false; } function loadProgramModeSelector() { var selectControl = document.getElementById('s_controlraw'); if (!selectControl) { return; } var control = selectControl.value; var modeElement = document.getElementById('s_moderaw'); if (!modeElement) { return false; } var mode = modeElement.value; var elementName = 'program_mode_selector'; var url = '/?cmd=programmodeselector'; url += '&control=' + control; url += '&mode=' + mode; requestUpdateItem(elementName, url); } function onChangeProgramModeSelector() { var selectControl = document.getElementById('s_controlraw'); if (!selectControl) { return; } var control = selectControl.value; var modeElement = document.getElementById('s_moderaw'); if (!modeElement) { return false; } var mode = modeElement.value; var elementName = 'cv_fields'; var url = '/?cmd=getcvfields'; url += '&control=' + control; url += '&mode=' + mode; requestUpdateItem(elementName, url); } function updateName() { var nameField = document.getElementById('name'); if (!nameField) { return false; } var title = document.getElementById('popup_title'); if (!title) { return false; } if (nameField.value.length > 0) { title.innerHTML = nameField.value; } else { title.innerHTML = 'NN'; } return true; } function getArgumentsOfHardwareType() { var hardwareType = document.getElementById('s_hardwaretype'); if (!hardwareType) { return false; } var url = '?cmd=controlarguments&hardwaretype=' + hardwareType.value; requestUpdateItem('controlarguments', url); return false; } function updateFeedbacksOfTrack() { var track = document.getElementById('s_totrack'); if (!track) { return false; } var url = '?cmd=feedbacksoftrack&track=' + track.value; requestUpdateItem('feedbacks', url); return false; } function addSlave(prefix) { var counter = document.getElementById(prefix + 'counter'); if (!counter) { return false; } var div = document.getElementById(prefix + 's'); if (!div) { return false; } counter.value++; var url = '/?cmd=slaveadd&priority=' + counter.value + '&prefix=' + prefix; requestAddItem(prefix + '_new_' + counter.value, url); return false; } function addRelation(type) { var relationCounter = document.getElementById('relationcounter' + type); if (!relationCounter) { return false; } var relationDiv = document.getElementById('relation' + type); if (!relationDiv) { return false; } var url = '/?cmd=relationadd&priority=' + relationCounter.value + '&type=' + type; requestAddItem('new_' + type + '_priority_' + relationCounter.value, url); relationCounter.value++; return false; } function addFeedback() { var feedbackCounter = document.getElementById('feedbackcounter'); if (!feedbackCounter) { return false; } var identifier = ''; var track = document.getElementById('track'); if (track) { identifier = '&track=' + track.value; } else { var signal = document.getElementById('signal'); if (signal) { identifier = '&signal=' + signal.value; } } if (identifier.length == 0) { return false; } feedbackCounter.value++; var url = '/?cmd=feedbackadd&counter=' + feedbackCounter.value + identifier; requestUpdateItem('div_feedback_' + feedbackCounter.value, url); return false; } function execFunctionByName(name) { if (typeof window[name] !== 'function') { return; } window[name](); } function checkIntegerValue(name, min, max) { if (min > max) { return; } var input = document.getElementById(name); if (!input) { return; } if (isNaN(input.value) || (input.value < min)) { input.value = min; } else if (input.value > max) { input.value = max; } execFunctionByName('update_' + name); } function incrementIntegerValue(name, max) { var input = document.getElementById(name); if (!input) { return; } var value = parseInt(input.value) + 1; if (value > max) { return; } input.value = value; execFunctionByName('update_' + name); } function decrementIntegerValue(name, min) { var input = document.getElementById(name); if (!input) { return; } var value = parseInt(input.value) - 1; if (value < min) { return; } input.value = value; execFunctionByName('update_' + name); } function update_valueraw() { var element = document.getElementById('valueraw'); if (!element) { return; } for (var bit = 0; bit < 8; ++bit) { updateCvBit(bit, (element.value >> bit) & 0x01); } } function updateCvBit(bit, value) { var name = 'valueraw' + bit; var element = document.getElementById(name); if (!element) { return; } element.checked = value ? true : false; } function updateCvValue() { var value = 0; for (var bit = 0; bit < 8; ++bit) { var element = document.getElementById('valueraw' + bit); if (!element) { return; } value |= (element.checked ? (1 << bit) : 0); } var element = document.getElementById('valueraw'); if (!element) { return; } element.value = value; } function setToggleButton(elementName, on) { var elements = document.getElementsByName(elementName); for (var i = 0; i < elements.length; ++i) { if (on == 'true') { elements[i].classList.remove('button_off'); elements[i].classList.add('button_on'); } else { elements[i].classList.remove('button_on'); elements[i].classList.add('button_off'); } } } function deleteElement(elementName) { var element = document.getElementById(elementName); if (element) { element.parentNode.removeChild(element); } } function onClickAccessory(accessoryID) { var identifier = 'a_' + accessoryID; if (modifierKeyPressed(event)) { rotateObject(identifier); return; } var element = document.getElementById(identifier); var url = '/?cmd=accessorystate'; url += '&state=' + (element.classList.contains('accessory_off') ? 'on' : 'off'); url += '&accessory=' + accessoryID; fireRequestAndForget(url); return false; } function onPointerDownAccessory(accessoryID) { var identifier = 'a_' + accessoryID; if (modifierKeyPressed(event)) { return; } if (event.button != 0) { return; } var element = document.getElementById(identifier); var url = '/?cmd=accessorystate'; url += '&state=on'; url += '&accessory=' + accessoryID; fireRequestAndForget(url); return false; } function onPointerUpAccessory(accessoryID) { var identifier = 'a_' + accessoryID; if (modifierKeyPressed(event)) { rotateObject(identifier); return; } if (event.button != 0) { return; } var element = document.getElementById(identifier); var url = '/?cmd=accessorystate'; url += '&state=off'; url += '&accessory=' + accessoryID; fireRequestAndForget(url); return false; } function onClickRoute(routeID) { var element = document.getElementById('r_' + routeID); var url = '/?cmd=routeexecute'; url += '&route=' + routeID; fireRequestAndForget(url); return false; } function onClickSwitch(event, switchID) { var identifier = 'sw_' + switchID; if (modifierKeyPressed(event)) { rotateObject(identifier); return; } var element = document.getElementById(identifier); var url = '/?cmd=switchstate&state=' if (element.classList.contains('switch_straight')) { url += 'turnout' } else { url += 'straight' } url += '&switch=' + switchID; fireRequestAndForget(url); return false; } function onClickFeedback(feedbackID) { var identifier = 'f_' + feedbackID; if (modifierKeyPressed(event)) { rotateObject(identifier); return; } var element = document.getElementById(identifier); var url = '/?cmd=feedbackstate'; url += '&state=' + (element.classList.contains('feedback_free') ? 'occupied' : 'free'); url += '&feedback=' + feedbackID; fireRequestAndForget(url); return false; } function onClickSignal(signalID) { var identifier = 'si_' + signalID; if (modifierKeyPressed(event)) { rotateObject(identifier); return; } var element = document.getElementById(identifier); var url = '/?cmd=signalstate'; url += '&state=' + (element.classList.contains('signal_clear') ? 'stop' : 'clear'); url += '&signal=' + signalID; fireRequestAndForget(url); return false; } function showMenu(elementName) { var element = document.getElementById(elementName); if (!element) { return; } var mouseX = event.clientX; var mouseY = event.clientY; var windowX = window.innerWidth; var windowY = window.innerHeight; if (windowX > (mouseX * 2)) { element.style.left = mouseX + "px"; element.style.right = "auto"; } else { element.style.left = "auto"; element.style.right = (windowX - mouseX) + "px"; } if (windowY > (mouseY * 2)) { element.style.top = mouseY + "px"; element.style.bottom = "auto"; } else { element.style.top = "auto"; element.style.bottom = (windowY - mouseY) + "px"; } element.style.display = 'block'; } function showOnClickMenu(event, ID) { if (modifierKeyPressed(event)) { return true; } event.stopPropagation(); hideAllContextMenus(); showMenu(ID + '_onclick'); return false; } function showContextMenu(event, ID) { if (modifierKeyPressed(event)) { return true; } event.stopPropagation(); hideAllContextMenus(); showMenu(ID + '_context'); return false; } function onChangeCheckboxShowHide(checkboxId, divId, tabId) { var checkbox = document.getElementById(checkboxId); if (!checkbox) { return; } var div = document.getElementById(divId); if (!div) { return; } div.hidden = !checkbox.checked; var tab = document.getElementById(tabId); if (!tab) { return; } if (checkbox.checked) { tab.classList.remove("hidden"); } else { tab.classList.add("hidden"); } } function onChangeTrackTypeMainTrack() { let feedbacks = document.getElementById('tab_button_feedbacks'); if (!feedbacks) { return; } let signals = document.getElementById('tab_button_signals'); if (!signals) { return; } let automode = document.getElementById('tab_button_automode'); if (!automode) { return; } let trackType = document.getElementById('s_tracktype'); if (!trackType) { return; } let main = document.getElementById('s_main'); if (!main) { return; } let name = document.getElementById('i_name'); if (!name) { return; } let showname = document.getElementById('i_showname'); if (!showname) { return; } let shownamecb = document.getElementById('showname'); if (!shownamecb) { return; } let displayname = document.getElementById('i_displayname'); if (!displayname) { return; } let length = document.getElementById('i_length'); if (!length) { return; } if (main.value != 0) { feedbacks.classList.add('hidden'); signals.classList.add('hidden'); automode.classList.add('hidden'); name.classList.add('hidden'); } else { feedbacks.classList.remove('hidden'); signals.classList.remove('hidden'); automode.classList.remove('hidden'); name.classList.remove('hidden'); } let trackTypeValue = trackType.value; if ((trackTypeValue != 0) && (main.value != 0)) { showname.classList.add('hidden'); } else { showname.classList.remove('hidden'); } if ((main.value != 0) || (trackTypeValue != 0) || (shownamecb.checked == false)) { displayname.classList.add('hidden'); } else { displayname.classList.remove('hidden'); } if (trackTypeValue == 1 || trackTypeValue == 5 || trackTypeValue == 7 || trackTypeValue == 8 || trackTypeValue == 9) { length.classList.add('hidden'); } else { length.classList.remove('hidden'); } } function updateLayoutItem(elementName, data) { var parentElement = document.getElementById('layout'); if (parentElement) { deleteElement(elementName); var elementOnClickName = elementName + '_onclick'; deleteElement(elementOnClickName); var elementContextName = elementName + '_context'; deleteElement(elementContextName); parentElement.innerHTML += data; var i; var tags = document.getElementsByClassName('layout_item'); for (i = 0; i < tags.length; i++) { var tag = tags[i]; var clone = tag.cloneNode(true); tag.parentNode.replaceChild(clone, tag); } } } function requestUpdateLayoutItem(elementName, url) { var xmlHttp = new XMLHttpRequest(); xmlHttp.onreadystatechange = function() { if (xmlHttp.readyState == 4 && xmlHttp.status == 200) { updateLayoutItem(elementName, xmlHttp.responseText); } } xmlHttp.open('GET', url, true); xmlHttp.send(null); } function addItem(elementName, data) { var element = document.getElementById(elementName); if (!element) { return; } element.innerHTML += data; } function requestAddItem(elementName, url) { var xmlHttp = new XMLHttpRequest(); xmlHttp.onreadystatechange = function() { if (xmlHttp.readyState == 4 && xmlHttp.status == 200) { addItem(elementName, xmlHttp.responseText); } } xmlHttp.open('GET', url, true); xmlHttp.send(null); } function updateItem(elementName, data) { var element = document.getElementById(elementName); if (!element) { return; } element.innerHTML = data; } function requestUpdateItem(elementName, url, followUpFunction) { updateItem(elementName, "Loading..."); var xmlHttp = new XMLHttpRequest(); xmlHttp.onreadystatechange = function() { if (xmlHttp.readyState != 4 || xmlHttp.status != 200) { return; } updateItem(elementName, xmlHttp.responseText); if (followUpFunction === undefined) { return; } followUpFunction(); } xmlHttp.open('GET', url, true); xmlHttp.send(null); } function updateTitle() { var locoName = document.getElementById("loconame"); if (!locoName) { return; } updateItem("title", locoName.innerHTML + " - RailControl"); } function updateText(textID) { elementName = 'tx_' + textID; var url = '/?cmd=textget'; url += '&text=' + textID; requestUpdateLayoutItem(elementName, url); } function updateTrack(trackID) { elementName = 't_' + trackID; var url = '/?cmd=trackget'; url += '&track=' + trackID; requestUpdateLayoutItem(elementName, url); } function updateTrackState(argumentMap) { var trackID = argumentMap.get('track'); var signalID = argumentMap.get('signal'); if (trackID > 0) { elementName = 't_' + trackID; } else if (signalID > 0) { elementName = 'si_' + signalID; } var element = document.getElementById(elementName); if (element) { var reserved = false; var occupied = false; var blocked = false; var error = false; var orientation = true; if (argumentMap.has('occupied')) { occupied = argumentMap.get('occupied') == 'true'; } if (argumentMap.has('reserved')) { reserved = argumentMap.get('reserved') == 'true'; } if (argumentMap.has('blocked')) { blocked = argumentMap.get('blocked') == 'true'; } element.classList.remove('track_free'); element.classList.remove('track_reserved'); element.classList.remove('track_reserved_occupied'); element.classList.remove('track_occupied'); element.classList.remove('track_error'); element.classList.remove('track_blocked'); if (reserved && occupied) { element.classList.add('track_reserved_occupied'); } else if (reserved) { element.classList.add('track_reserved'); } else if (occupied) { element.classList.add('track_occupied'); } else if (blocked) { element.classList.add('track_blocked'); } else { element.classList.add('track_free'); } if (error) { element.classList.add('track_error'); } if (argumentMap.has('orientation')) { orientation = argumentMap.get('orientation') == 'true'; } if (reserved) { element.classList.remove('loco_unknown'); element.classList.add('loco_known'); } else { element.classList.remove('loco_known'); element.classList.add('loco_unknown'); } } var onClickElement = document.getElementById(elementName + '_onclick'); if (onClickElement) { if (reserved) { onClickElement.classList.remove('loco_unknown'); onClickElement.classList.add('loco_known'); } else { onClickElement.classList.remove('loco_known'); onClickElement.classList.add('loco_unknown'); } if (blocked) { onClickElement.classList.remove('track_unblocked'); onClickElement.classList.add('track_blocked'); } else { onClickElement.classList.remove('track_blocked'); onClickElement.classList.add('track_unblocked'); } if (orientation) { onClickElement.classList.remove('orientation_left'); onClickElement.classList.add('orientation_right'); } else { onClickElement.classList.remove('orientation_right'); onClickElement.classList.add('orientation_left'); } } var locoElement = document.getElementById(elementName + '_text_loconame'); if (locoElement) { var orientationArrow = orientation ? '→ ' : '← '; var locoName = argumentMap.has('loconame') ? argumentMap.get('loconame') : ''; locoElement.innerHTML = orientationArrow + locoName; } } function updateSwitchState(argumentMap) { if (!argumentMap.has('switch') || !argumentMap.has('state')) { return; } var elementName = 'sw_' + argumentMap.get('switch'); var element = document.getElementById(elementName); if (!element) { return; } var state = argumentMap.get('state'); updateSwitchStateDiv(element, state); elementName += '_onclick'; element = document.getElementById(elementName); if (!element) { return; } updateSwitchStateDiv(element, state); } function updateSwitchStateDiv(element, state) { element.classList.remove('switch_turnout'); element.classList.remove('switch_straight'); element.classList.remove('switch_third'); switch (state) { case 'turnout': element.classList.add('switch_turnout'); break; case 'straight': element.classList.add('switch_straight'); break; case 'third': element.classList.add('switch_third'); break; } } function updateSignalState(argumentMap) { if (!argumentMap.has('signal') || !argumentMap.has('state')) { return; } var elementName = 'si_' + argumentMap.get('signal'); var element = document.getElementById(elementName); if (!element) { return; } var state = argumentMap.get('state'); updateSignalStateDiv(element, state); elementName += '_onclick'; element = document.getElementById(elementName); if (!element) { return; } updateSignalStateDiv(element, state); } function updateSignalStateDiv(element, state) { element.classList.remove('signal_stop'); element.classList.remove('signal_clear'); element.classList.remove('signal_aspect2'); element.classList.remove('signal_aspect3'); element.classList.remove('signal_aspect4'); element.classList.remove('signal_aspect5'); element.classList.remove('signal_aspect6'); element.classList.remove('signal_aspect7'); element.classList.remove('signal_aspect8'); element.classList.remove('signal_aspect9'); element.classList.remove('signal_aspect10'); element.classList.remove('signal_dark'); element.classList.remove('signal_stopexpected'); element.classList.remove('signal_clearexpected'); element.classList.remove('signal_aspect2expected'); element.classList.remove('signal_aspect3expected'); element.classList.remove('signal_aspect4expected'); element.classList.remove('signal_aspect5expected'); element.classList.remove('signal_aspect6expected'); element.classList.remove('signal_aspect7expected'); element.classList.remove('signal_aspect8expected'); element.classList.remove('signal_aspect9expected'); element.classList.remove('signal_aspect10expected'); switch (state) { case 'stop': element.classList.add('signal_stop'); break; case 'clear': element.classList.add('signal_clear'); break; case 'aspect2': element.classList.add('signal_aspect2'); break; case 'aspect3': element.classList.add('signal_aspect3'); break; case 'aspect4': element.classList.add('signal_aspect4'); break; case 'aspect5': element.classList.add('signal_aspect5'); break; case 'aspect6': element.classList.add('signal_aspect6'); break; case 'aspect7': element.classList.add('signal_aspect7'); break; case 'aspect8': element.classList.add('signal_aspect8'); break; case 'aspect9': element.classList.add('signal_aspect9'); break; case 'aspect10': element.classList.add('signal_aspect10'); break; case 'dark': element.classList.add('signal_dark'); break; case 'stopexpected': element.classList.add('signal_stopexpected'); break; case 'clearexpected': element.classList.add('signal_clearexpected'); break; case 'aspect2expected': element.classList.add('signal_aspect2expected'); break; case 'aspect3expected': element.classList.add('signal_aspect3expected'); break; case 'aspect4expected': element.classList.add('signal_aspect4expected'); break; case 'aspect5expected': element.classList.add('signal_aspect5expected'); break; case 'aspect6expected': element.classList.add('signal_aspect6expected'); break; case 'aspect7expected': element.classList.add('signal_aspect7expected'); break; case 'aspect8expected': element.classList.add('signal_aspect8expected'); break; case 'aspect9expected': element.classList.add('signal_aspect9expected'); break; case 'aspect10expected': element.classList.add('signal_aspect10expected'); break; } } function updateSignal(signalID) { elementName = 'si_' + signalID; var url = '/?cmd=signalget'; url += '&signal=' + signalID; requestUpdateLayoutItem(elementName, url); } function dataUpdate(event) { var status = document.getElementById('status'); var arguments = event.data.split(';'); var argumentMap = new Map(); arguments.forEach(function(argument) { var parts = argument.split('='); if (parts[0] == 'status') { var statusData = status.innerHTML + parts[1] + '
'; status.innerHTML = statusData.substring(statusData.length - 2500); status.scrollTop = status.scrollHeight - status.clientHeight; } argumentMap.set(parts[0], parts[1]); }); var elementName = ""; var command = argumentMap.get('command'); if (command == 'warning') { var state = argumentMap.get('status'); addInfoBox('w', state); } else if (command == 'booster') { elementName = 'skip_booster'; var on = argumentMap.get('on'); setToggleButton(elementName, on); } else if (command == 'locospeed') { var speed = argumentMap.get('speed'); elementName = 'locospeed_' + argumentMap.get('loco'); var elements = document.getElementsByName(elementName); for (var i = 0; i < elements.length; ++i) { elements[i].value = speed; } } else if (command == 'locofunction') { elementName = 'skip_locofunction_' + argumentMap.get('loco') + '_' + argumentMap.get('function'); var on = argumentMap.get('on'); setToggleButton(elementName, on); } else if (command == 'locoorientation') { elementName = 'skip_locoorientation_' + argumentMap.get('loco'); var on = argumentMap.get('orientation'); setToggleButton(elementName, on); } else if (command == 'accessory') { elementName = 'a_' + argumentMap.get('accessory'); var element = document.getElementById(elementName); if (element && argumentMap.has('state')) { var state = argumentMap.get('state'); if (state == 'green') { element.classList.remove('accessory_off'); element.classList.add('accessory_on'); } else { element.classList.remove('accessory_on'); element.classList.add('accessory_off'); } } } else if (command == 'accessorysettings') { var accessoryID = argumentMap.get('accessory'); elementName = 'a_' + accessoryID; var url = '/?cmd=accessoryget'; url += '&accessory=' + accessoryID; requestUpdateLayoutItem(elementName, url); } else if (command == 'accessorydelete') { elementName = 'a_' + argumentMap.get('accessory'); var element = document.getElementById(elementName); if (element) { element.parentNode.removeChild(element); } } else if (command == 'switch') { updateSwitchState(argumentMap); } else if (command == 'switchsettings') { var switchID = argumentMap.get('switch'); elementName = 'sw_' + switchID; var url = '/?cmd=switchget'; url += '&switch=' + switchID; requestUpdateLayoutItem(elementName, url); } else if (command == 'switchdelete') { elementName = 'sw_' + argumentMap.get('switch'); deleteElement(elementName); deleteElement(elementName + '_onclick'); deleteElement(elementName + '_context'); } else if (command == 'signal') { updateSignalState(argumentMap); } else if (command == 'signalsettings') { updateSignal(argumentMap.get('signal')); } else if (command == 'signaldelete') { elementName = 'si_' + argumentMap.get('signal'); deleteElement(elementName); deleteElement(elementName + '_onclick'); deleteElement(elementName + '_context'); } else if (command == 'routesettings') { var routeID = argumentMap.get('route'); elementName = 'r_' + routeID; var url = '/?cmd=routeget'; url += '&route=' + routeID; requestUpdateLayoutItem(elementName, url); } else if (command == 'routedelete') { elementName = 'r_' + argumentMap.get('route'); deleteElement(elementName); deleteElement(elementName + '_context'); } else if (command == 'textsettings') { updateText(argumentMap.get('text')); } else if (command == 'textdelete') { elementName = 'tx_' + argumentMap.get('text'); deleteElement(elementName); deleteElement(elementName + '_context'); } else if (command == 'trackstate') { updateTrackState(argumentMap); } else if (command == 'tracksettings') { updateTrack(argumentMap.get('track')); } else if (command == 'trackdelete') { elementName = 't_' + argumentMap.get('track'); deleteElement(elementName); deleteElement(elementName + '_onclick'); deleteElement(elementName + '_context'); } else if (command == 'feedback') { elementName = 'f_' + argumentMap.get('feedback'); var element = document.getElementById(elementName); if (element && argumentMap.has('state')) { var state = argumentMap.get('state'); if (state == 'on') { element.classList.remove('feedback_free'); element.classList.add('feedback_occupied'); } else { element.classList.remove('feedback_occupied'); element.classList.add('feedback_free'); } } } else if (command == 'feedbacksettings') { var feedbackID = argumentMap.get('feedback'); var layerID = document.getElementById('s_layer').value; elementName = 'f_' + feedbackID; var url = '/?cmd=feedbackget'; url += '&feedback=' + feedbackID; url += '&layer=' + layerID; requestUpdateLayoutItem(elementName, url); } else if (command == 'feedbackdelete') { elementName = 'f_' + argumentMap.get('feedback'); deleteElement(elementName); deleteElement(elementName + '_context'); } else if ((command == 'locosettings') || (command == 'locodelete') || (command == 'multipleunitsettings') || (command == 'multipleunitdelete')) { loadLocoSelectors(); } else if ((command == 'layersettings') || (command == 'layerdelete')) { loadLayerSelector(); } else if (command == 'dcccvvalue') { var cv = argumentMap.get('cv'); var cvElementName = 'cvraw'; var cvElement = document.getElementById(cvElementName); if (cvElement) { cvElement.value = cv; } var value = argumentMap.get('value'); var valueElementName = 'valueraw'; var valueElement = document.getElementById(valueElementName); if (valueElement) { valueElement.value = value; update_valueraw(); } } } function dataUpdateError() { var body = document.getElementById("body"); body.innerHTML = '

RailControl shut down

Please start RailControl server again.


RailControl wurde heruntergefahren

Bitte starte den RailControl server wieder.


RailControl se ha apagado

Por favor reinicie el servidor RailControl otra vez.

'; checkAvailability(); } function loadPopup(url) { url += '&posx=' + window.layoutPosX; url += '&posy=' + window.layoutPosY; url += '&posz=' + document.getElementById('s_layer').value; var xmlHttp = new XMLHttpRequest(); xmlHttp.onreadystatechange = function() { if (xmlHttp.readyState !== 4 || xmlHttp.status !== 200) { return; } var popup = document.getElementById('popup'); popup.innerHTML = xmlHttp.responseText; popup.style.display = 'block'; } xmlHttp.open('GET', url, true); xmlHttp.send(null); } function loadLocoSelector(selector) { var locoValue = document.getElementById('s_loco_' + selector).value; var url = '/?cmd=locoselector&selector=' + selector + '&loco=' + locoValue; var xmlHttp = new XMLHttpRequest(); xmlHttp.onload = function() { if (xmlHttp.status !== 200) { return; } var element = document.getElementById('loco_selector_' + selector); element.innerHTML = xmlHttp.responseText; loadLoco(selector); } xmlHttp.open('GET', url, true); xmlHttp.send(null); } function loadLocoSelectors() { for (var selector = 1; selector <= MaxNumberOfLocoControls; ++selector) { loadLocoSelector(selector); } } function loadLayerSelector() { var layerValue = document.getElementById('s_layer').value; var url = '/?cmd=layerselector&layer=' + layerValue; var xmlHttp = new XMLHttpRequest(); xmlHttp.onload = function() { if (xmlHttp.status !== 200) { return; } var element = document.getElementById('layer_selector'); element.innerHTML = xmlHttp.responseText; loadLayout(); } xmlHttp.open('GET', url, true); xmlHttp.send(null); } function loadProtocol(type, ID) { var selectControl = document.getElementById('s_control'); if (!selectControl) { return; } var controlID = selectControl.value; var elementName = 'select_protocol'; var selectProtocol = document.getElementById(elementName); if (!selectProtocol) { return; } var url = '/?cmd=protocol'; url += '&control=' + controlID; url += '&' + type + '=' + ID; requestUpdateItem(elementName, url); } function loadAccessoryAddress() { var selectAccessoryType = document.getElementById('s_connectiontype'); if (!selectAccessoryType) { return; } var type = selectAccessoryType.value; var elementName = 'select_address'; var selectAddress = document.getElementById(elementName); if (!selectAddress) { return; } var intAddress = document.getElementById('address'); if (!intAddress) { return; } var address = intAddress.value; var selectPort = document.getElementById('s_port'); if (!selectPort) { selectPort = document.getElementById('port'); if (!selectPort) { return; } } var port = selectPort.value; var url = '/?cmd=accessoryaddress'; url += '&type=' + type; url += '&address=' + address; url += '&port=' + port; requestUpdateItem(elementName, url); } function loadRelationObject(atlock, priority, objectType = 0, id = 0, state = 0) { var elementName = 'relation_' + atlock + '_' + priority; var object = document.getElementById(elementName); if (!object) { return; } if (objectType == 0) { var typeSelector = document.getElementById('s_' + elementName + '_type'); if (!typeSelector) { return; } objectType = typeSelector.value; } var url = '/?cmd=relationobject'; url += '&objecttype=' + objectType; url += '&priority=' + priority; url += '&atlock=' + atlock; if (objectType != 0) { url += '&id=' + id; url += '&state=' + state; } requestUpdateItem(elementName + "_object", url); } function loadRelationObjectStates(type, name) { var elementName = name + '_state'; var object = document.getElementById(elementName); if (!object) { return; } var object = document.getElementById('s_' + name + '_id'); if (!object) { return; } var objectId = object.value; var url = '/?cmd=' + type + 'states'; url += '&' + type + '=' + objectId; url += '&name=' + name; requestUpdateItem(elementName, url); } function loadLayoutContext(event) { if (modifierKeyPressed(event)) { return true; } event.preventDefault(); hideAllContextMenus(); showMenu('layout_context'); var x = Math.round(event.target.scrollLeft + event.offsetX, 0); var y = Math.round(event.target.scrollTop + event.offsetY, 0); window.layoutPosX = Math.floor(x / 36); window.layoutPosY = Math.floor(y / 36); return true; } function isInLayout(position) { layoutPosition = document.querySelector('.layout').getBoundingClientRect(); return (position.pageX >= layoutPosition.left && position.pageX <= layoutPosition.right && position.pageY >= layoutPosition.top && position.pageY <= layoutPosition.bottom); } function hideAllContextMenus() { var menus = document.getElementsByClassName('contextmenu'); for (var i = 0; i < menus.length; ++i) { menus[i].style.display = 'none'; } } function hideElement(name) { var element = document.getElementById(name); if (!element) { return; } element.style.display = 'none'; } function loadLoco(selector) { var loco = document.getElementById('s_loco_' + selector); if (!loco) { return; } requestUpdateItem('loco_' + selector, '/?cmd=loco&loco=' + loco.value, updateTitle); } function loadLayout() { var layer = document.getElementById('s_layer'); if (layer) { requestUpdateItem('layout', '/?cmd=layout&layer=' + layer.value); var context = document.getElementById('layout_context'); if (layer.value > 0) { context.classList.remove('feedback_layer'); } else { context.classList.add('feedback_layer'); } } } function sendTimestamp() { var url = '/?cmd=timestamp×tamp='; var timestamp = Math.round(Date.now() / 1000); url += timestamp; fireRequestAndForget(url); } function startUp() { let body = document.getElementById('body'); if (body) { body.addEventListener('click', function(event) { if (event.button == 2) { return false; } hideAllContextMenus(); return true; }, true); body.addEventListener('keydown', function(event) { let popup = document.getElementById('popup'); if (popup) { let popupstyle = window.getComputedStyle(popup, null).display; if (popupstyle != 'none') { return true; } } if (event.key === ' ') { let booster_button = document.getElementById('skip_booster'); if (!booster_button) { return true; } let on = !booster_button.classList.contains('button_on'); let url = '/?cmd=booster&on=' + (on ? '1' : '0') + ''; fireRequestAndForget(url); return false; } else if ((event.keyCode == 38) || (event.key === '8')) // arrow up { locoSpeedChange(+8); } else if ((event.keyCode == 40) || (event.key === '2')) // arrow down { locoSpeedChange(-8); } else if ((event.keyCode == 33) || (event.key === '9')) // arrow page up { locoSpeedChange(+80); } else if ((event.keyCode == 34) || (event.key === '3')) // arrow page down { locoSpeedChange(-80); } else if ((event.keyCode == 45) || (event.key === '0')) // 0 { locoSpeedChange(-1024); } else if ((event.keyCode == 37) || (event.key === '4')) // arrow left { locoOrientationChange(0); } else if ((event.keyCode == 39) || (event.key === '6')) // arrow right { locoOrientationChange(1); } }, true); } updateLocoControls(); loadLoco(1); loadLoco(2); loadLoco(3); loadLoco(4); loadLoco(5); loadLayout(); sendTimestamp(); } function ShowTab(tabName) { var tabs = document.getElementsByClassName('tab_content'); if (!tabs) { return; } for (var i = 0; i < tabs.length; ++i) { var tab = tabs[i]; tab.classList.add('hidden'); } var tab = document.getElementById('tab_' + tabName); if (!tab) { return; } tab.classList.remove('hidden'); var tabButtons = document.getElementsByClassName('tab_button'); if (!tabButtons) { return; } for (var i = 0; i < tabButtons.length; ++i) { var tabButton = tabButtons[i]; tabButton.classList.remove('tab_button_selected'); } var tabButton = document.getElementById('tab_button_' + tabName); if (!tabButton) { return; } tabButton.classList.add('tab_button_selected'); } function hidePopup() { var popup = document.getElementById('popup'); popup.style.display = 'none'; } var infoID = 0; function addInfoBox(type, info) { var id = 'res_' + ++infoID; var className; switch (type) { case 'i': // info hidePopup(); className = 'infoboxinfo'; setTimeout(function() { deleteElement(id); }, 2000); break; case 'w': // warning hidePopup(); className = 'infoboxwarning'; break; case 'e': // error className = 'infoboxerror'; break; default: className = 'infoboxunknown'; } var preparedInfo = '
' + info + '
'; var infobox = document.getElementById('infobox'); infobox.innerHTML += preparedInfo; } function submitEditForm() { var url = '/?'; var form = document.getElementById('editform'); var i = 0; while (true) { var formElement = form[i]; if (formElement == undefined) { break; } if (formElement.name.substr(0, 5) == "skip_") { ++i; continue; } if (i > 0) { url += '&'; } url += formElement.name; url += '='; if (formElement.type == 'checkbox') { url += formElement.checked; } else { url += encodeURIComponent(formElement.value); } ++i; } var xmlHttp = new XMLHttpRequest(); xmlHttp.onreadystatechange = function() { if (xmlHttp.readyState !== 4 || xmlHttp.status !== 200) { return; } var response = xmlHttp.responseText; addInfoBox(response[21], response.substring(22, response.length - 7)); } xmlHttp.open('GET', url, true); xmlHttp.send(null); return false; } var updateSliderAllowed = true; function locoSpeedSliderOnInput(locoId) { if (!updateSliderAllowed) { return false; } setTimeout(function() { updateSliderAllowed = true; }, 200); locoSpeedSliderOnChange(locoId); updateSliderAllowed = false; return false; } function locoSpeedSliderOnChange(locoId) { var slider = document.getElementById('locospeed_' + locoId); if (!slider) { return false; } var url = '/?cmd=locospeed&loco='; url += locoId; url += '&speed='; url += slider.value; fireRequestAndForget(url); updateSliderAllowed = true; return false; } function locoSpeedChange(delta) { let loco = document.getElementById('loco'); if (!loco) { return false; } let locoId = loco.value; let slider = document.getElementById('locospeed_' + locoId); if (!slider) { return false; } let oldValue = +slider.value; let newValue = 0 + oldValue; newValue += delta; if (newValue > 1023) { newValue = 1023; } if (newValue < 0) { newValue = 0; } let url = '/?cmd=locospeed&loco='; url += locoId; url += '&speed='; url += newValue; fireRequestAndForget(url); return false; } function locoOrientationChange(orientation) { let loco = document.getElementById('loco'); if (!loco) { return false; } let locoId = loco.value; let url = '/?cmd=locoorientation&loco='; url += locoId; url += '&on='; url += orientation; fireRequestAndForget(url); return false; } function fireRequestAndForget(url) { var xmlHttp = new XMLHttpRequest(); xmlHttp.open('GET', url, true); xmlHttp.send(null); } function checkAvailability() { var xmlHttp = new XMLHttpRequest(); xmlHttp.onerror = function() { setTimeout(function() { checkAvailability(); }, 3000); } xmlHttp.onload = function() { location.reload(); } xmlHttp.open('GET', '/', true); xmlHttp.send(null); } function updateLocoControls() { var layout = document.getElementById('layout'); if (!layout) { return; } var layerSelector = document.getElementById('layer_selector'); if (!layerSelector) { return; } maxNumberOfLocoControlsInScreen = Math.floor(document.documentElement.clientWidth / 252); if (numberOfLocoControls > maxNumberOfLocoControlsInScreen) { numberOfLocoControls = maxNumberOfLocoControlsInScreen; } var left = numberOfLocoControls * 252; left += "px"; layout.style.left = left; layerSelector.style.left = left; for (var control = 0; control <= MaxNumberOfLocoControls; ++control) { var element = document.getElementById('loco_container_' + control); if (!element) { continue; } if (control > numberOfLocoControls) { element.classList.add('hidden'); } else { element.classList.remove('hidden'); } } var reduce = document.getElementById("reduce_locos"); if (reduce) { if (numberOfLocoControls == 0) { reduce.classList.add("hidden"); } else { reduce.classList.remove("hidden"); if (numberOfLocoControls >= maxNumberOfLocoControlsInScreen || numberOfLocoControls >= MaxNumberOfLocoControls) { reduce.style.left = (numberOfLocoControls * 252 - 30) + "px"; reduce.style.width = "30px"; } else { reduce.style.left = (numberOfLocoControls * 252 - 15) + "px"; reduce.style.width = "15px"; } } } var extend = document.getElementById("extend_locos"); if (extend) { if (numberOfLocoControls >= maxNumberOfLocoControlsInScreen || numberOfLocoControls >= MaxNumberOfLocoControls) { extend.classList.add("hidden"); } else { extend.classList.remove("hidden"); extend.style.left = (numberOfLocoControls * 252) + "px"; if (numberOfLocoControls > 0) { extend.style.width = "15px"; } else { extend.style.width = "30px"; } } } } function reduceLocos() { if (numberOfLocoControls == 0) { return; } --numberOfLocoControls; updateLocoControls(); } function extendLocos() { if (numberOfLocoControls >= maxNumberOfLocoControlsInScreen) { return; } ++numberOfLocoControls; updateLocoControls(); } function swapRelations(atlock, ownPriority, up) { var ownElementName = 's_relation_' + atlock + '_' + ownPriority + '_'; var ownElementType = document.getElementById(ownElementName + 'type'); var ownElementId = document.getElementById(ownElementName + 'id'); var ownElementState = document.getElementById(ownElementName + 'state'); if (!ownElementType) { alert("Own element does not exist: " + ownElementName); return; } var ownType = ownElementType.value; var ownId = 0; if (ownElementId) { ownId = ownElementId.value; } var ownState = 0; if (ownElementState) { ownState = ownElementState.value; } var otherPriority = ownPriority; var otherElementName; var otherElementType = false; while (!otherElementType) { if (up == 'up') { --otherPriority; if (otherPriority == 0) { return; } } else { ++otherPriority; if (otherPriority == 255) { return; } } otherElementName = 's_relation_' + atlock + '_' + otherPriority + '_'; otherElementType = document.getElementById(otherElementName + 'type'); } var otherElementId = document.getElementById(otherElementName + 'id'); var otherElementState = document.getElementById(otherElementName + 'state'); var otherType = otherElementType.value; var otherId = 0; if (otherElementId) { otherId = otherElementId.value; } var otherState = 0; if (otherElementState) { otherState = otherElementState.value; } loadRelationObject(atlock, ownPriority, otherType, otherId, otherState); loadRelationObject(atlock, otherPriority, ownType, ownId, ownState); } function enableNoSleep() { document.removeEventListener('click', enableNoSleep, false); noSleep.enable(); } window.layoutPosX = 0; window.layoutPosY = 0; var numberOfLocoControls = 1; var maxNumberOfLocoControlsInScreen = Math.floor(document.documentElement.clientWidth / 252); const MaxNumberOfLocoControls = 5; var noSleep = new NoSleep(); document.addEventListener('click', enableNoSleep, false); var updater = new EventSource('/?cmd=updater'); updater.addEventListener('message', dataUpdate); updater.addEventListener('error', dataUpdateError); window.addEventListener('resize', updateLocoControls); railcontrol-24+dfsg1/html/layout_background.png000066400000000000000000000033221500456250600220220ustar00rootroot00000000000000PNG  IHDR$$zTXtRaw profile type exifxڵXm8 S,ɟ16z7o C&L`q;n!A,|H9_|AvQ3;C/_S+=xHm 7j; ʼbuo7iq;t ,+-"PVgQ;c@ސۻ #6oKq P|XwDr.c<Ƽ]-رk`8r].h qZZAˮqknBk\X ܹy7@2KB/ eMRA%6qE;Tm2*;^-~3v Sa8c\cW4EpE.`rvomBhθ+tYaЯOSPBYGvI$1 *۳1AA:@WB Y5[ Ŧ DШ I>#jC1)PFC1EKr5i)Rʩ5r)\r-R90XRɥZ*6Ua_13ɤ4L!|oŖZn.];D=K3ӌL19qNs\bm#8ȣ>P7dQҮf) tL3(&x2bދ)g" iCM1Hg0xKtM~SL+#HպڢN x9mT&{2͹=j3^-1UYE]y ch4V~/ޣwhv7y ;EB.a \[xKMA0w4!js:湘G겷d 9 1( 4>vΐShnH~ErwA9SNyG^RNGg(>QN3GG9̯oSN?rkSNߟW7(רߧ}rzd:gM,P3}f 9;)e~f[O :tBZja%,ŬRVϢ/.f#~=zB'>z㢰,9EGG!?µ-ugm1߷ZHWȉiCCPICC profilex}=H@_jE*v("bATQP VhIC(X:8* nnN.RBX JLcf*31" FdfDu_bN4ueywWkoimrI MbKGDC pHYs  tIME#,b railcontrol-24+dfsg1/test/startrailcontrol.sh000077500000000000000000000001521500456250600215560ustar00rootroot00000000000000#/bin/bash echo Starting railcontrol cd .. ./railcontrol test/testconfig.conf 2> /dev/null > /dev/null & railcontrol-24+dfsg1/test/stoprailcontrol.sh000077500000000000000000000003101500456250600214020ustar00rootroot00000000000000#/bin/bash echo s | ncat localhost 2222 while [ `ps auxwf|grep ./railcontrol|wc -l` -gt 1 ] ; do echo Waiting on railcontrol exit sleep 1 done rm /tmp/railcontrol.sqlite echo Railcontrol stopped railcontrol-24+dfsg1/test/testconfig.conf000066400000000000000000000004321500456250600206260ustar00rootroot00000000000000# This is the config file of RailControl # Lines starting with # are comments # before and after the = has to be placed a space dbengine = sqlite dbfilename = /tmp/railcontrol.sqlite dbusername = none dbpassword = none dbhost = none webserverport = 8080 consoleport = 2222 railcontrol-24+dfsg1/test/testcontrols.php000077500000000000000000000036071500456250600211000ustar00rootroot00000000000000#!/usr/bin/php railcontrol-24+dfsg1/test/testlocos.php000077500000000000000000000010201500456250600203370ustar00rootroot00000000000000#!/usr/bin/php railcontrol-24+dfsg1/tools/000077500000000000000000000000001500456250600157745ustar00rootroot00000000000000railcontrol-24+dfsg1/tools/Cc-Schnitte-Sniffer.cpp000066400000000000000000000035431500456250600222030ustar00rootroot00000000000000/* RailControl - Model Railway Control Software Copyright (c) 2017-2025 by Teddy / Dominik Mahrer - www.railcontrol.org RailControl 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 3, or (at your option) any later version. RailControl 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 RailControl; see the file LICENCE. If not see . */ #include //printf #include //exit(0); #include //memset #include #include #include #include //close; #include #include "Logger/Logger.h" #include "Network/Serial.h" static volatile unsigned int runSniffer; void stopSignal(int signo) { runSniffer = false; } int main (int argc, char* argv[]) { signal(SIGINT, stopSignal); signal(SIGTERM, stopSignal); runSniffer = true; Logger::Logger* logger = Logger::Logger::GetLogger("CAN bus sniffer"); logger->AddConsoleLogger(); logger->SetLogLevel(Logger::Logger::LevelDebug); logger->Debug("Starting CAN bus sniffer"); Network::Serial serial(logger, "/dev/ttyUSB0", B500000, 8, 'N', 1); do { unsigned char buffer[13]; int ret = serial.ReceiveExact(buffer, sizeof(buffer)); if (ret == 0) { // do nothing } else if (ret != sizeof(buffer)) { logger->Debug("Received != 13 bytes"); } else { logger->HexIn(buffer, sizeof(buffer)); } std::this_thread::sleep_for(std::chrono::milliseconds(100)); } while (runSniffer); logger->Debug("Terminating CAN bus sniffer"); return 0; } railcontrol-24+dfsg1/tools/Makefile000066400000000000000000000012671500456250600174420ustar00rootroot00000000000000 #CPPFLAGS=-g -O2 -Wall #CPPFLAGS=-g -O0 -Wall -std=c++11 CXXFLAGS=-I. -I.. -g -O0 -Wall -std=c++11 LDFLAGS=-g LIBS=-lpthread -ldl TOOLS= \ Cc-Schnitte-Sniffer OBJ= \ Cc-Schnitte-Sniffer.o \ ../Network/Serial.o \ ../Network/TcpConnection.o \ ../Network/TcpServer.o \ ../Logger/Logger.o \ ../Logger/LoggerServer.o \ ../Languages.o \ ../Utils/Utils.o all: $(TOOLS) %.o: %.cpp $(CXX) $(CXXFLAGS) -c -o $@ $< Cc-Schnitte-Sniffer: $(OBJ) $(CXX) $(LDFLAGS) -o Cc-Schnitte-Sniffer Cc-Schnitte-Sniffer.o ../Logger/Logger.o ../Logger/LoggerServer.o ../Network/Serial.o ../Network/TcpServer.o ../Network/TcpConnection.o ../Languages.o ../Utils/Utils.o $(LIBS) clean: rm -f $(TESTS) *.o