From 35869a7fd879ac4e6893a48af187a3d429067d58 Mon Sep 17 00:00:00 2001 From: Reinder Feenstra Date: Sun, 2 Jan 2022 00:37:26 +0100 Subject: [PATCH] wlanmaus: rewrite -> new kernel/iohandler model --- server/CMakeLists.txt | 6 +- server/src/hardware/decoder/decoder.cpp | 3 +- server/src/hardware/decoder/decoder.hpp | 4 +- server/src/hardware/decoder/decoderlist.cpp | 29 +- server/src/hardware/decoder/decoderlist.hpp | 5 +- server/src/hardware/interface/interfaces.cpp | 3 +- server/src/hardware/interface/interfaces.hpp | 4 +- .../hardware/interface/wlanmausinterface.cpp | 137 ++++++ .../hardware/interface/wlanmausinterface.hpp | 56 +++ server/src/hardware/protocol/z21/config.hpp | 53 +++ .../protocol/z21/iohandler/iohandler.hpp | 63 +++ .../protocol/z21/iohandler/udpiohandler.cpp | 77 +++ .../protocol/z21/iohandler/udpiohandler.hpp | 58 +++ .../z21/iohandler/udpserveriohandler.cpp | 87 ++++ .../z21/iohandler/udpserveriohandler.hpp | 52 +++ server/src/hardware/protocol/z21/kernel.cpp | 102 ++++ server/src/hardware/protocol/z21/kernel.hpp | 118 +++++ server/src/hardware/protocol/z21/messages.cpp | 117 ++++- server/src/hardware/protocol/z21/messages.hpp | 124 ++++- .../hardware/protocol/z21/serverkernel.cpp | 439 ++++++++++++++++++ .../hardware/protocol/z21/serverkernel.hpp | 169 +++++++ .../hardware/protocol/z21/serversettings.cpp | 51 ++ .../hardware/protocol/z21/serversettings.hpp | 46 ++ server/src/hardware/protocol/z21/settings.cpp | 42 ++ server/src/hardware/protocol/z21/settings.hpp | 45 ++ server/src/hardware/protocol/z21/utils.hpp | 7 +- server/src/utils/displayname.hpp | 3 +- 27 files changed, 1876 insertions(+), 24 deletions(-) create mode 100644 server/src/hardware/interface/wlanmausinterface.cpp create mode 100644 server/src/hardware/interface/wlanmausinterface.hpp create mode 100644 server/src/hardware/protocol/z21/config.hpp create mode 100644 server/src/hardware/protocol/z21/iohandler/iohandler.hpp create mode 100644 server/src/hardware/protocol/z21/iohandler/udpiohandler.cpp create mode 100644 server/src/hardware/protocol/z21/iohandler/udpiohandler.hpp create mode 100644 server/src/hardware/protocol/z21/iohandler/udpserveriohandler.cpp create mode 100644 server/src/hardware/protocol/z21/iohandler/udpserveriohandler.hpp create mode 100644 server/src/hardware/protocol/z21/kernel.cpp create mode 100644 server/src/hardware/protocol/z21/kernel.hpp create mode 100644 server/src/hardware/protocol/z21/serverkernel.cpp create mode 100644 server/src/hardware/protocol/z21/serverkernel.hpp create mode 100644 server/src/hardware/protocol/z21/serversettings.cpp create mode 100644 server/src/hardware/protocol/z21/serversettings.hpp create mode 100644 server/src/hardware/protocol/z21/settings.cpp create mode 100644 server/src/hardware/protocol/z21/settings.hpp diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index 01c520fc..2d826df4 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -115,8 +115,10 @@ file(GLOB SOURCES "src/hardware/protocol/xpressnet/*.cpp" "src/hardware/protocol/xpressnet/iohandler/*.hpp" "src/hardware/protocol/xpressnet/iohandler/*.cpp" -# "src/hardware/protocol/z21/*.hpp" -# "src/hardware/protocol/z21/*.cpp" + "src/hardware/protocol/z21/*.hpp" + "src/hardware/protocol/z21/*.cpp" + "src/hardware/protocol/z21/iohandler/*.hpp" + "src/hardware/protocol/z21/iohandler/*.cpp" "src/log/*.hpp" "src/log/*.cpp" "src/train/*.hpp" diff --git a/server/src/hardware/decoder/decoder.cpp b/server/src/hardware/decoder/decoder.cpp index 78ef50c5..14a5d540 100644 --- a/server/src/hardware/decoder/decoder.cpp +++ b/server/src/hardware/decoder/decoder.cpp @@ -3,7 +3,7 @@ * * This file is part of the traintastic source code. * - * Copyright (C) 2019-2021 Reinder Feenstra + * Copyright (C) 2019-2022 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -289,4 +289,5 @@ void Decoder::changed(DecoderChangeFlags changes, uint32_t functionNumber) { if(interface) interface->decoderChanged(*this, changes, functionNumber); + decoderChanged(*this, changes, functionNumber); } diff --git a/server/src/hardware/decoder/decoder.hpp b/server/src/hardware/decoder/decoder.hpp index 6506937d..df97f0d7 100644 --- a/server/src/hardware/decoder/decoder.hpp +++ b/server/src/hardware/decoder/decoder.hpp @@ -3,7 +3,7 @@ * * This file is part of the traintastic source code. * - * Copyright (C) 2019-2021 Reinder Feenstra + * Copyright (C) 2019-2022 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -90,6 +90,8 @@ class Decoder : public IdObject ObjectProperty functions; Property notes; + boost::signals2::signal decoderChanged; + Decoder(const std::weak_ptr& world, std::string_view _id); void addToWorld() final; diff --git a/server/src/hardware/decoder/decoderlist.cpp b/server/src/hardware/decoder/decoderlist.cpp index 48547672..fa764e73 100644 --- a/server/src/hardware/decoder/decoderlist.cpp +++ b/server/src/hardware/decoder/decoderlist.cpp @@ -3,7 +3,7 @@ * * This file is part of the traintastic source code. * - * Copyright (C) 2019-2020 Reinder Feenstra + * Copyright (C) 2019-2022 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -69,6 +69,33 @@ TableModelPtr DecoderList::getModel() return std::make_shared(*this); } +std::shared_ptr DecoderList::getDecoder(uint16_t address) const +{ + auto it = std::find_if(begin(), end(), + [address](const auto& decoder) + { + return decoder->address.value() == address; + }); + if(it != end()) + return *it; + return {}; +} + +std::shared_ptr DecoderList::getDecoder(DecoderProtocol protocol, uint16_t address, bool longAddress) const +{ + auto it = std::find_if(begin(), end(), + [protocol, address, longAddress](const auto& decoder) + { + return + decoder->protocol.value() == protocol && + decoder->address.value() == address && + (protocol != DecoderProtocol::DCC || decoder->longAddress == longAddress); + }); + if(it != end()) + return *it; + return {}; +} + void DecoderList::worldEvent(WorldState state, WorldEvent event) { ObjectList::worldEvent(state, event); diff --git a/server/src/hardware/decoder/decoderlist.hpp b/server/src/hardware/decoder/decoderlist.hpp index 570bcf71..910669c9 100644 --- a/server/src/hardware/decoder/decoderlist.hpp +++ b/server/src/hardware/decoder/decoderlist.hpp @@ -3,7 +3,7 @@ * * This file is part of the traintastic source code. * - * Copyright (C) 2019-2020 Reinder Feenstra + * Copyright (C) 2019-2022 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -42,6 +42,9 @@ class DecoderList : public ObjectList DecoderList(Object& _parent, const std::string& parentPropertyName); TableModelPtr getModel() final; + + std::shared_ptr getDecoder(uint16_t address) const; + std::shared_ptr getDecoder(DecoderProtocol protocol, uint16_t address, bool longAddress = false) const; }; #endif diff --git a/server/src/hardware/interface/interfaces.cpp b/server/src/hardware/interface/interfaces.cpp index 6d84f7ac..9149a3c5 100644 --- a/server/src/hardware/interface/interfaces.cpp +++ b/server/src/hardware/interface/interfaces.cpp @@ -3,7 +3,7 @@ * * This file is part of the traintastic source code. * - * Copyright (C) 2021 Reinder Feenstra + * Copyright (C) 2021-2022 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -29,6 +29,7 @@ std::shared_ptr Interfaces::create(const std::shared_ptr& worl IF_CLASSID_CREATE(DCCPlusPlusInterface) IF_CLASSID_CREATE(ECoSInterface) IF_CLASSID_CREATE(LocoNetInterface) + IF_CLASSID_CREATE(WlanMausInterface) IF_CLASSID_CREATE(XpressNetInterface) return std::shared_ptr(); } diff --git a/server/src/hardware/interface/interfaces.hpp b/server/src/hardware/interface/interfaces.hpp index e9700725..60663de6 100644 --- a/server/src/hardware/interface/interfaces.hpp +++ b/server/src/hardware/interface/interfaces.hpp @@ -3,7 +3,7 @@ * * This file is part of the traintastic source code. * - * Copyright (C) 2021 Reinder Feenstra + * Copyright (C) 2021-2022 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -29,6 +29,7 @@ #include "dccplusplusinterface.hpp" #include "ecosinterface.hpp" #include "loconetinterface.hpp" +#include "wlanmausinterface.hpp" #include "xpressnetinterface.hpp" struct Interfaces @@ -39,6 +40,7 @@ struct Interfaces DCCPlusPlusInterface::classId, ECoSInterface::classId, LocoNetInterface::classId, + WlanMausInterface::classId, XpressNetInterface::classId ); diff --git a/server/src/hardware/interface/wlanmausinterface.cpp b/server/src/hardware/interface/wlanmausinterface.cpp new file mode 100644 index 00000000..ba70d285 --- /dev/null +++ b/server/src/hardware/interface/wlanmausinterface.cpp @@ -0,0 +1,137 @@ +/** + * server/src/hardware/interface/wlanmausinterface.cpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2019-2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "wlanmausinterface.hpp" +#include "../protocol/z21/iohandler/udpserveriohandler.hpp" +#include "../../core/attributes.hpp" +#include "../../log/log.hpp" +#include "../../log/logmessageexception.hpp" +#include "../../utils/displayname.hpp" +#include "../../world/world.hpp" + +WlanMausInterface::WlanMausInterface(const std::weak_ptr& world, std::string_view _id) + : Interface(world, _id) + , z21{this, "z21", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject} +{ + z21.setValueInternal(std::make_shared(*this, z21.name())); + + Attributes::addDisplayName(z21, DisplayName::Hardware::z21); + m_interfaceItems.insertBefore(z21, notes); +} + +void WlanMausInterface::worldEvent(WorldState state, WorldEvent event) +{ + Interface::worldEvent(state, event); + + if(m_kernel) + { + switch(event) + { + case WorldEvent::PowerOff: + case WorldEvent::PowerOn: + case WorldEvent::Stop: + case WorldEvent::Run: + m_kernel->setState(contains(state, WorldState::PowerOn), !contains(state, WorldState::Run)); + break; + + default: + break; + } + } +} + +void WlanMausInterface::idChanged(const std::string& newId) +{ + if(m_kernel) + m_kernel->setLogId(newId); +} + +bool WlanMausInterface::setOnline(bool& value) +{ + if(!m_kernel && value) + { + try + { + auto world = m_world.lock(); + assert(world); + m_kernel = Z21::ServerKernel::create(z21->config(), world->decoders.value()); + + status.setValueInternal(InterfaceStatus::Initializing); + + m_kernel->setLogId(id.value()); + m_kernel->setOnStarted( + [this]() + { + status.setValueInternal(InterfaceStatus::Online); + }); + + m_kernel->setOnTrackPowerOff( + [this]() + { + if(auto w = m_world.lock(); w && contains(w->state.value(), WorldState::PowerOn)) + w->powerOff(); + }); + m_kernel->setOnTrackPowerOn( + [this]() + { + if(auto w = m_world.lock()) + { + if(!contains(w->state.value(), WorldState::PowerOn)) + w->powerOn(); + if(!contains(w->state.value(), WorldState::Run)) + w->run(); + } + }); + m_kernel->setOnEmergencyStop( + [this]() + { + if(auto w = m_world.lock(); w && contains(w->state.value(), WorldState::Run)) + w->stop(); + }); + + m_kernel->start(); + + m_z21PropertyChanged = z21->propertyChanged.connect( + [this](BaseProperty&) + { + m_kernel->setConfig(z21->config()); + }); + + if(auto w = m_world.lock()) + m_kernel->setState(contains(w->state.value(), WorldState::PowerOn), !contains(w->state.value(), WorldState::Run)); + } + catch(const LogMessageException& e) + { + status.setValueInternal(InterfaceStatus::Offline); + Log::log(*this, e.message(), e.args()); + return false; + } + } + else if(m_kernel && !value) + { + m_kernel->stop(); + m_kernel.reset(); + + status.setValueInternal(InterfaceStatus::Offline); + } + return true; +} diff --git a/server/src/hardware/interface/wlanmausinterface.hpp b/server/src/hardware/interface/wlanmausinterface.hpp new file mode 100644 index 00000000..08c2b217 --- /dev/null +++ b/server/src/hardware/interface/wlanmausinterface.hpp @@ -0,0 +1,56 @@ +/** + * server/src/hardware/interface/wlanmausinterface.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2019-2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef TRAINTASTIC_SERVER_HARDWARE_INTERFACE_WLANMAUSINTERFACE_HPP +#define TRAINTASTIC_SERVER_HARDWARE_INTERFACE_WLANMAUSINTERFACE_HPP + +#include "interface.hpp" +#include "../protocol/z21/serverkernel.hpp" +#include "../protocol/z21/serversettings.hpp" +#include "../../core/objectproperty.hpp" + +/** + * @brief WLANmaus/Z21 app hardware interface + */ +class WlanMausInterface : public Interface +{ + CLASS_ID("interface.wlanmaus") + DEFAULT_ID("wlanmaus") + CREATE(WlanMausInterface) + + private: + std::unique_ptr m_kernel; + boost::signals2::connection m_z21PropertyChanged; + + protected: + void worldEvent(WorldState state, WorldEvent event) final; + void idChanged(const std::string& newId) final; + bool setOnline(bool& value) final; + + public: + ObjectProperty z21; + + WlanMausInterface(const std::weak_ptr& world, std::string_view _id); +}; + +#endif + diff --git a/server/src/hardware/protocol/z21/config.hpp b/server/src/hardware/protocol/z21/config.hpp new file mode 100644 index 00000000..11274434 --- /dev/null +++ b/server/src/hardware/protocol/z21/config.hpp @@ -0,0 +1,53 @@ +/** + * server/src/hardware/protocol/z21/config.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021-2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_Z21_CONFIG_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_Z21_CONFIG_HPP + +#include "messages.hpp" + +namespace Z21 { + +struct Config +{ + bool debugLogRXTX; +}; + +struct ServerConfig : Config +{ + static constexpr CommandStationId commandStationId = CommandStationId::Z21; + static constexpr uint8_t firmwareVersionMajor = 1; + static constexpr uint8_t firmwareVersionMinor = 30; + static constexpr HardwareType hardwareType = HWT_Z21_START; + static constexpr uint16_t inactiveClientPurgeTime = 60; ///< sec + static constexpr uint32_t serialNumber = 123456789; + static constexpr size_t subscriptionMax = 16; + static constexpr uint8_t xBusVersion = 30; + + bool allowEmergencyStop; + bool allowTrackPowerOff; + bool allowTrackPowerOnReleaseEmergencyStop; +}; + +} + +#endif diff --git a/server/src/hardware/protocol/z21/iohandler/iohandler.hpp b/server/src/hardware/protocol/z21/iohandler/iohandler.hpp new file mode 100644 index 00000000..eccf1617 --- /dev/null +++ b/server/src/hardware/protocol/z21/iohandler/iohandler.hpp @@ -0,0 +1,63 @@ +/** + * server/src/hardware/protocol/z21/iohandler/iohandler.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021-2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_Z21_IOHANDLER_IOHANDLER_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_Z21_IOHANDLER_IOHANDLER_HPP + +#include +#include + +namespace Z21 { + +class Kernel; +struct Message; + +class IOHandler +{ + protected: + Kernel& m_kernel; + + IOHandler(Kernel& kernel) + : m_kernel{kernel} + { + } + + public: + using ClientId = size_t; + + IOHandler(const IOHandler&) = delete; + IOHandler& operator =(const IOHandler&) = delete; + + virtual ~IOHandler() = default; + + virtual void start() = 0; + virtual void stop() = 0; + + virtual bool send(const Message& /*message*/) { return false; } + virtual bool sendTo(const Message& /*message*/, ClientId /*id*/) { return false; } + + virtual void purgeClient(ClientId /*id*/) {} +}; + +} + +#endif diff --git a/server/src/hardware/protocol/z21/iohandler/udpiohandler.cpp b/server/src/hardware/protocol/z21/iohandler/udpiohandler.cpp new file mode 100644 index 00000000..49434959 --- /dev/null +++ b/server/src/hardware/protocol/z21/iohandler/udpiohandler.cpp @@ -0,0 +1,77 @@ +/** + * server/src/hardware/protocol/z21/iohandler/udpiohandler.cpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021-2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "udpiohandler.hpp" +#include "../kernel.hpp" +#include "../messages.hpp" +#include "../../../../core/eventloop.hpp" +#include "../../../../log/log.hpp" +#include "../../../../log/logmessageexception.hpp" + +namespace Z21 { + +UDPIOHandler::UDPIOHandler(Kernel& kernel) + : IOHandler(kernel) + , m_socket{m_kernel.ioContext()} +{ +} + +void UDPIOHandler::start() +{ + receive(); +} + +void UDPIOHandler::stop() +{ +} + +void UDPIOHandler::receive() +{ + m_socket.async_receive_from(boost::asio::buffer(m_receiveBuffer), m_receiveEndpoint, + [this](const boost::system::error_code& ec, std::size_t bytesReceived) + { + if(!ec) + { + const std::byte* pos = m_receiveBuffer.data(); + while(bytesReceived >= sizeof(Message)) + { + const Message& message = *reinterpret_cast(pos); + if(message.dataLen() < sizeof(Message)) + break; + receive(message, m_receiveEndpoint); + pos += message.dataLen(); + bytesReceived -= message.dataLen(); + } + receive(); + } + else + { + EventLoop::call( + [this, ec]() + { + Log::log(m_kernel.logId(), LogMessage::E2009_SOCKET_RECEIVE_FAILED_X, ec); + }); + } + }); +} + +} diff --git a/server/src/hardware/protocol/z21/iohandler/udpiohandler.hpp b/server/src/hardware/protocol/z21/iohandler/udpiohandler.hpp new file mode 100644 index 00000000..0d5e9f5c --- /dev/null +++ b/server/src/hardware/protocol/z21/iohandler/udpiohandler.hpp @@ -0,0 +1,58 @@ +/** + * server/src/hardware/protocol/z21/iohandler/udpiohandler.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021-2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_Z21_IOHANDLER_UDPIOHANDLER_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_Z21_IOHANDLER_UDPIOHANDLER_HPP + +#include "iohandler.hpp" +#include + +namespace Z21 { + +class UDPIOHandler : public IOHandler +{ + public: + static constexpr size_t payloadSizeMax = 1500 - 20 - 8; ///< Ethernet MTU - IPv4 header - UDP header + + private: + boost::asio::ip::udp::endpoint m_receiveEndpoint; + std::array m_receiveBuffer; + + void receive(); + + protected: + boost::asio::ip::udp::socket m_socket; + + virtual void receive(const Message& message, const boost::asio::ip::udp::endpoint& remoteEndpoint) = 0; + + public: + static constexpr uint16_t defaultPort = 21105; + + UDPIOHandler(Kernel& kernel); + + void start() override; + void stop() override; +}; + +} + +#endif diff --git a/server/src/hardware/protocol/z21/iohandler/udpserveriohandler.cpp b/server/src/hardware/protocol/z21/iohandler/udpserveriohandler.cpp new file mode 100644 index 00000000..6cfa91bb --- /dev/null +++ b/server/src/hardware/protocol/z21/iohandler/udpserveriohandler.cpp @@ -0,0 +1,87 @@ +/** + * server/src/hardware/protocol/z21/iohandler/udpclientiohandler.cpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021-2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "udpserveriohandler.hpp" +//#include +#include "../serverkernel.hpp" +#include "../messages.hpp" +#include "../../../../log/logmessageexception.hpp" + +namespace Z21 { + +UDPServerIOHandler::UDPServerIOHandler(ServerKernel& kernel) + : UDPIOHandler(kernel) +{ + boost::system::error_code ec; + + if(m_socket.open(boost::asio::ip::udp::v4(), ec)) + throw LogMessageException(LogMessage::E2004_SOCKET_OPEN_FAILED_X, ec); + + if(m_socket.bind(boost::asio::ip::udp::endpoint(boost::asio::ip::address_v4::any(), defaultPort), ec)) + { + m_socket.close(); + throw LogMessageException(LogMessage::E2006_SOCKET_BIND_FAILED_X, ec); + } +} + +bool UDPServerIOHandler::sendTo(const Message& message, ClientId id) +{ + //! \todo use async_send_to() + if(auto it = m_clients.find(id); it != m_clients.end()) + { + m_socket.send_to(boost::asio::buffer(&message, message.dataLen()), it->second); + return true; + } + + return false; +} + +void UDPServerIOHandler::purgeClient(ClientId id) +{ + m_clients.erase(id); +} + +void UDPServerIOHandler::receive(const Message& message, const boost::asio::ip::udp::endpoint& remoteEndpoint) +{ + ClientId clientId; + + if(auto it = std::find_if(m_clients.begin(), m_clients.end(), [&remoteEndpoint](auto n) { return n.second == remoteEndpoint; }); it != m_clients.end()) + { + clientId = it->first; + } + else // new client + { + do + { + m_lastClientId++; + } + while(m_clients.find(m_lastClientId) != m_clients.end()); + + m_clients.emplace(m_lastClientId, remoteEndpoint); + + clientId = m_lastClientId; + } + + static_cast(m_kernel).receiveFrom(message, clientId); +} + +} diff --git a/server/src/hardware/protocol/z21/iohandler/udpserveriohandler.hpp b/server/src/hardware/protocol/z21/iohandler/udpserveriohandler.hpp new file mode 100644 index 00000000..324afbea --- /dev/null +++ b/server/src/hardware/protocol/z21/iohandler/udpserveriohandler.hpp @@ -0,0 +1,52 @@ +/** + * server/src/hardware/protocol/z21/iohandler/udpserveriohandler.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021-2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_Z21_IOHANDLER_UDPSERVERIOHANDLER_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_Z21_IOHANDLER_UDPSERVERIOHANDLER_HPP + +#include "udpiohandler.hpp" +#include + +namespace Z21 { + +class ServerKernel; + +class UDPServerIOHandler final : public UDPIOHandler +{ + private: + ClientId m_lastClientId = 0; + std::unordered_map m_clients; + + protected: + void receive(const Message& message, const boost::asio::ip::udp::endpoint& remoteEndpoint) final; + + public: + UDPServerIOHandler(ServerKernel& kernel); + + bool sendTo(const Message& message, ClientId id) final; + + void purgeClient(ClientId id) final; +}; + +} + +#endif diff --git a/server/src/hardware/protocol/z21/kernel.cpp b/server/src/hardware/protocol/z21/kernel.cpp new file mode 100644 index 00000000..1b93201b --- /dev/null +++ b/server/src/hardware/protocol/z21/kernel.cpp @@ -0,0 +1,102 @@ +/** + * server/src/hardware/protocol/z21/kernel.cpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2019-2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "kernel.hpp" +#include "messages.hpp" +#include "../xpressnet/messages.hpp" +#include "../../decoder/decoder.hpp" +#include "../../decoder/decoderchangeflags.hpp" +#include "../../input/inputcontroller.hpp" +#include "../../../utils/setthreadname.hpp" +#include "../../../core/eventloop.hpp" +#include "../../../log/log.hpp" + +namespace Z21 { + +Kernel::Kernel() + : m_ioContext{1} +#ifndef NDEBUG + , m_started{false} +#endif +{ +} + +void Kernel::start() +{ + assert(m_ioHandler); + assert(!m_started); + + m_thread = std::thread( + [this]() + { + setThreadName("z21"); + auto work = std::make_shared(m_ioContext); + m_ioContext.run(); + }); + + m_ioContext.post( + [this]() + { + m_ioHandler->start(); + + onStart(); + + if(m_onStarted) + EventLoop::call( + [this]() + { + m_onStarted(); + }); + }); + +#ifndef NDEBUG + m_started = true; +#endif +} + +void Kernel::stop() +{ + m_ioContext.post( + [this]() + { + onStop(); + + m_ioHandler->stop(); + }); + + m_ioContext.stop(); + + m_thread.join(); + +#ifndef NDEBUG + m_started = false; +#endif +} + +void Kernel::setIOHandler(std::unique_ptr handler) +{ + assert(handler); + assert(!m_ioHandler); + m_ioHandler = std::move(handler); +} + +} diff --git a/server/src/hardware/protocol/z21/kernel.hpp b/server/src/hardware/protocol/z21/kernel.hpp new file mode 100644 index 00000000..dd7fec70 --- /dev/null +++ b/server/src/hardware/protocol/z21/kernel.hpp @@ -0,0 +1,118 @@ +/** + * server/src/hardware/protocol/z21/kernel.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2019-2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_Z21_KERNEL_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_Z21_KERNEL_HPP + +#include +#include +#include + +#include +#include "config.hpp" +#include "iohandler/iohandler.hpp" + +class Decoder; +enum class DecoderChangeFlags; +class DecoderController; + +namespace Z21 { + +struct Message; +enum HardwareType : uint32_t; + +class Kernel +{ + private: + std::thread m_thread; + std::function m_onStarted; + + protected: + boost::asio::io_context m_ioContext; + std::unique_ptr m_ioHandler; + std::string m_logId; +#ifndef NDEBUG + bool m_started; +#endif + + Kernel(); + virtual ~Kernel() = default; + + void setIOHandler(std::unique_ptr handler); + + virtual void onStart() {} + virtual void onStop() {} + + public: + Kernel(const Kernel&) = delete; + Kernel& operator =(const Kernel&) = delete; + + /** + * @brief IO context for Z21 kernel and IO handler + * + * @return The IO context + */ + boost::asio::io_context& ioContext() { return m_ioContext; } + + /** + * @brief Get object id used for log messages + * @return The object id + */ + inline const std::string& logId() + { + return m_logId; + } + + /** + * @brief Set object id used for log messages + * @param[in] value The object id + */ + inline void setLogId(std::string value) + { + m_logId = std::move(value); + } + + /** + * @brief ... + * @param[in] callback ... + * @note This function may not be called when the kernel is running. + */ + inline void setOnStarted(std::function callback) + { + assert(!m_started); + m_onStarted = std::move(callback); + } + + /** + * @brief Start the kernel and IO handler + */ + virtual void start(); + + /** + * @brief Stop the kernel and IO handler + */ + virtual void stop(); +}; + +} + +#endif diff --git a/server/src/hardware/protocol/z21/messages.cpp b/server/src/hardware/protocol/z21/messages.cpp index 92f4813f..5b56775f 100644 --- a/server/src/hardware/protocol/z21/messages.cpp +++ b/server/src/hardware/protocol/z21/messages.cpp @@ -3,7 +3,7 @@ * * This file is part of the traintastic source code. * - * Copyright (C) 2019-2020 Reinder Feenstra + * Copyright (C) 2019-2022 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -31,6 +31,7 @@ static std::string_view toString(Header header) switch(header) { case LAN_GET_SERIAL_NUMBER: return "LAN_GET_SERIAL_NUMBER"; + case LAN_GET_CODE: return "LAN_GET_CODE"; case LAN_GET_HWINFO: return "LAN_GET_HWINFO"; case LAN_LOGOFF: return "LAN_LOGOFF"; case LAN_X: return "LAN_X"; @@ -38,10 +39,21 @@ static std::string_view toString(Header header) case LAN_GET_BROADCASTFLAGS: return "LAN_GET_BROADCASTFLAGS"; case LAN_GET_LOCO_MODE: return "LAN_GET_LOCO_MODE"; case LAN_SET_LOCO_MODE: return "LAN_SET_LOCO_MODE"; + case LAN_GET_TURNOUTMODE: return "LAN_GET_TURNOUTMODE"; + case LAN_SET_TURNOUTMODE: return "LAN_SET_TURNOUTMODE"; + case LAN_RMBUS_DATACHANGED: return "LAN_RMBUS_DATACHANGED"; + case LAN_RMBUS_GETDATA: return "LAN_RMBUS_GETDATA"; + case LAN_RMBUS_PROGRAMMODULE: return "LAN_RMBUS_PROGRAMMODULE"; case LAN_SYSTEMSTATE_DATACHANGED: return "LAN_SYSTEMSTATE_DATACHANGED"; case LAN_SYSTEMSTATE_GETDATA: return "LAN_SYSTEMSTATE_GETDATA"; + case LAN_RAILCOM_DATACHANGED: return "LAN_RAILCOM_DATACHANGED"; + case LAN_RAILCOM_GETDATA: return "LAN_RAILCOM_GETDATA"; case LAN_LOCONET_Z21_RX: return "LAN_LOCONET_Z21_RX"; case LAN_LOCONET_Z21_TX: return "LAN_LOCONET_Z21_TX"; + case LAN_LOCONET_FROM_LAN: return "LAN_LOCONET_FROM_LAN"; + case LAN_LOCONET_DISPATCH_ADDR: return "LAN_LOCONET_DISPATCH_ADDR"; + case LAN_LOCONET_DETECTOR: return "LAN_LOCONET_DETECTOR"; + case LAN_CAN_DETECTOR: return "LAN_CAN_DETECTOR"; } return {}; } @@ -84,28 +96,119 @@ std::string toString(const Message& message, bool raw) raw = true; break; + case 0xE3: + if(const auto& getLocoInfo = static_cast(message); getLocoInfo.db0 == 0xF0) + { + s = "LAN_X_GET_LOCO_INFO"; + s.append(" address=").append(std::to_string(getLocoInfo.address())); + if(getLocoInfo.isLongAddress()) + s.append(" (long)"); + } + else + raw = true; + break; + + case 0xE4: + if(const auto& setLocoDrive = static_cast(message); + setLocoDrive.db0 >= 0x10 && setLocoDrive.db0 <= 0x13) + { + s = "LAN_X_SET_LOCO_DRIVE"; + s.append(" address=").append(std::to_string(setLocoDrive.address())); + if(setLocoDrive.isLongAddress()) + s.append("/long"); + s.append(" direction=").append(setLocoDrive.direction() == Direction::Forward ? "fwd" : "rev"); + s.append(" speed="); + if(setLocoDrive.isEmergencyStop()) + s.append("estop"); + else + s.append(std::to_string(setLocoDrive.speedStep())).append("/").append(std::to_string(setLocoDrive.speedSteps())); + } + else if(const auto& setLocoFunction = static_cast(message); + setLocoFunction.db0 == 0xF8) + { + s = "LAN_X_SET_LOCO_FUNCTION"; + s.append(" address=").append(std::to_string(setLocoFunction.address())); + if(setLocoFunction.isLongAddress()) + s.append("/long"); + s.append(" function=").append(std::to_string(setLocoFunction.functionIndex())); + s.append(" state=").append(toString(setLocoFunction.switchType())); + } + else + raw = true; + break; + + case 0xEF: + { + const auto& locoInfo = static_cast(message); + s = "LAN_X_LOCO_INFO"; + s.append(" address=").append(std::to_string(locoInfo.address())); + if(locoInfo.isLongAddress()) + s.append("/long"); + s.append(" direction=").append(locoInfo.direction() == Direction::Forward ? "fwd" : "rev"); + s.append(" speed="); + if(locoInfo.isEmergencyStop()) + s.append("estop"); + else + s.append(std::to_string(locoInfo.speedStep())).append("/").append(std::to_string(locoInfo.speedSteps())); + for(uint8_t i = 0; i <= LanXLocoInfo::functionIndexMax; i++) + s.append(" f").append(std::to_string(i)).append("=").append(locoInfo.getFunction(i) ? "1" : "0"); + s.append(" busy=").append(locoInfo.isBusy() ? "1" : "0"); + break; + } + case 0xF1: + if(message == LanXGetFirmwareVersion()) + s = "LAN_X_GET_FIRMWARE_VERSION"; + else + raw = true; + break; + + case 0xF3: + if(message.dataLen() == sizeof(LanXGetFirmwareVersionReply)) + { + const auto& getFirmwareVersion = static_cast(message); + s = "LAN_X_GET_FIRMWARE_VERSION"; + s.append(" version=").append(std::to_string(getFirmwareVersion.versionMajor())).append(".").append(std::to_string(getFirmwareVersion.versionMinor())); + } + else + raw = true; + break; + default: raw = true; break; } break; - case Z21::LAN_GET_BROADCASTFLAGS: - if(message.dataLen() == sizeof(Z21::LanGetBroadcastFlags)) + case LAN_GET_BROADCASTFLAGS: + if(message == LanGetBroadcastFlags()) s = "LAN_GET_BROADCASTFLAGS"; - //else if(message.dataLen() == sizeof(Z21::LanGetBroadcastFlagsReply)) - // s = "LAN_GET_BROADCASTFLAGS flags=0x" + toHex(static_cast(message).broadcastFlags())); else raw = true; break; - case Z21::LAN_SET_BROADCASTFLAGS: + case LAN_SET_BROADCASTFLAGS: if(message.dataLen() == sizeof(Z21::LanSetBroadcastFlags)) - s = "LAN_SET_BROADCASTFLAGS flags=0x" + toHex(static_cast(message).broadcastFlags()); + { + s = "LAN_SET_BROADCASTFLAGS"; + s.append(" flags=0x").append(toHex(static_cast>(static_cast(message).broadcastFlags()))); + } else raw = true; break; + case LAN_SYSTEMSTATE_DATACHANGED: + { + const auto& systemState = static_cast(message); + s.append(" mainCurrent=").append(std::to_string(systemState.mainCurrent)); + s.append(" progCurrent=").append(std::to_string(systemState.progCurrent)); + s.append(" filteredMainCurrent=").append(std::to_string(systemState.filteredMainCurrent)); + s.append(" temperature=").append(std::to_string(systemState.temperature)); + s.append(" supplyVoltage=").append(std::to_string(systemState.supplyVoltage)); + s.append(" vccVoltage=").append(std::to_string(systemState.vccVoltage)); + s.append(" centralState=0x").append(toHex(systemState.centralState)); + s.append(" centralStateEx=0x").append(toHex(systemState.centralStateEx)); + break; + } default: raw = true; break; diff --git a/server/src/hardware/protocol/z21/messages.hpp b/server/src/hardware/protocol/z21/messages.hpp index cd58dc89..e33d0b71 100644 --- a/server/src/hardware/protocol/z21/messages.hpp +++ b/server/src/hardware/protocol/z21/messages.hpp @@ -3,7 +3,7 @@ * * This file is part of the traintastic source code. * - * Copyright (C) 2019-2020 Reinder Feenstra + * Copyright (C) 2019-2022 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -67,7 +67,7 @@ enum Header : uint16_t LAN_CAN_DETECTOR = 0xC4, }; -enum BroadcastFlags : uint32_t +enum class BroadcastFlags : uint32_t { None = 0, @@ -157,6 +157,7 @@ static constexpr uint8_t LAN_X_LOCO_INFO = 0xEF; enum HardwareType : uint32_t { + HWT_UNKNOWN = 0, HWT_Z21_OLD = 0x00000200, //!< „black Z21” (hardware variant from 2012) HWT_Z21_NEW = 0x00000201, //!< „black Z21”(hardware variant from 2013) HWT_SMARTRAIL = 0x00000202, //!< SmartRail (from 2012) @@ -164,6 +165,37 @@ enum HardwareType : uint32_t HWT_Z21_START = 0x00000204, //!< „z21 start” starter set variant (from 2016) }; +constexpr std::string_view toString(HardwareType value) +{ + switch(value) + { + case HWT_Z21_OLD: + return "Black Z21 (hardware variant from 2012)"; + + case HWT_Z21_NEW: + return "Black Z21 (hardware variant from 2013)"; + + case HWT_SMARTRAIL: + return "SmartRail (from 2012)"; + + case HWT_Z21_SMALL: + return "White Z21 (starter set variant from 2013)"; + + case HWT_Z21_START : + return "Z21 start (starter set variant from 2016)"; + + case HWT_UNKNOWN: + break; + } + + return {}; +} + +enum class CommandStationId : uint8_t +{ + Z21 = 0x12, +}; + #define Z21_CENTRALSTATE_EMERGENCYSTOP 0x01 //!< The emergency stop is switched on #define Z21_CENTRALSTATE_TRACKVOLTAGEOFF 0x02 //!< The track voltage is switched off #define Z21_CENTRALSTATE_SHORTCIRCUIT 0x04 //!< Short circuit @@ -260,6 +292,19 @@ struct LanLogoff : Message static_assert(sizeof(LanLogoff) == 4); // LAN_X_GET_VERSION +struct LanXGetVersion : LanX +{ + uint8_t db0 = 0x21; + uint8_t checksum = 0x00; + + LanXGetVersion() : + LanX(sizeof(LanXGetVersion), 0x21) + { + } +} ATTRIBUTE_PACKED; +static_assert(sizeof(LanXGetVersion) == 7); + +// LAN_X_GET_FIRMWARE_VERSION struct LanXGetFirmwareVersion : LanX { uint8_t db0 = 0x0A; @@ -378,9 +423,6 @@ static_assert(sizeof(LanXGetLocoInfo) == 9); // LAN_X_SET_LOCO_DRIVE struct LanXSetLocoDrive : LanX { - //static constexpr uint8_t directionFlag = 0x80; - - //uint8_t xheader = 0xe4; uint8_t db0; uint8_t addressHigh; uint8_t addressLow; @@ -402,6 +444,12 @@ struct LanXSetLocoDrive : LanX return (addressHigh & 0xC0) == 0xC0; } + inline void setAddress(uint16_t address, bool longAddress) + { + addressHigh = longAddress ? (0xC0 | (address >> 8)) : 0x00; + addressLow = longAddress ? address & 0xFF : address & 0x7F; + } + inline uint8_t speedSteps() const { switch(db0 & 0x0F) @@ -477,6 +525,12 @@ struct LanXSetLocoFunction : LanX return (addressHigh & 0xC0) == 0xC0; } + inline void setAddress(uint16_t address, bool longAddress) + { + addressHigh = longAddress ? (0xC0 | (address >> 8)) : 0x00; + addressLow = longAddress ? address & 0xFF : address & 0x7F; + } + inline SwitchType switchType() const { return static_cast(db3 >> 6); @@ -633,6 +687,40 @@ struct LanGetSerialNumberReply : Message } ATTRIBUTE_PACKED; static_assert(sizeof(LanGetSerialNumberReply) == 8); +// Reply to LAN_X_GET_VERSION +struct LanXGetVersionReply : LanX +{ + uint8_t db0 = 0x21; + uint8_t xBusVersionBCD; + CommandStationId commandStationId; + uint8_t checksum; + + LanXGetVersionReply() + : LanX(sizeof(LanXGetVersionReply), 0x63) + { + } + + LanXGetVersionReply(uint8_t _xBusVersion, CommandStationId _commandStationId) + : LanXGetVersionReply() + { + setXBusVersion(_xBusVersion); + commandStationId = _commandStationId; + calcChecksum(); + } + + inline uint8_t xBusVersion() const + { + return Utils::fromBCD(xBusVersionBCD); + } + + inline void setXBusVersion(uint8_t value) + { + assert(value < 100); + xBusVersionBCD = Utils::toBCD(value); + } +}; +static_assert(sizeof(LanXGetVersionReply) == 9); + // Reply to LAN_GET_CODE struct LanXGetFirmwareVersionReply : LanX { @@ -788,6 +876,7 @@ struct LanXLocoInfo : LanX static constexpr uint8_t directionFlag = 0x80; static constexpr uint8_t speedStepMask = 0x7F; static constexpr uint8_t flagF0 = 0x10; + static constexpr uint8_t functionIndexMax = 28; uint8_t addressHigh = 0; uint8_t addressLow = 0; @@ -889,7 +978,7 @@ struct LanXLocoInfo : LanX Z21::Utils::setSpeedStep(speedAndDirection, speedSteps(), value); } - bool getFunction(uint8_t index) + bool getFunction(uint8_t index) const { if(index == 0) return db4 & flagF0; @@ -1029,6 +1118,22 @@ static_assert(sizeof(LanSystemStateDataChanged) == 20); PRAGMA_PACK_POP +constexpr std::string_view toString(LanXSetLocoFunction::SwitchType value) +{ + switch(value) + { + case LanXSetLocoFunction::SwitchType::Off: + return "off"; + case LanXSetLocoFunction::SwitchType::On: + return "on"; + case LanXSetLocoFunction::SwitchType::Toggle: + return "toggle"; + case LanXSetLocoFunction::SwitchType::Invalid: + return "invalid"; + } + return {}; +} + } inline bool operator ==(const Z21::Message& lhs, const Z21::Message& rhs) @@ -1043,4 +1148,11 @@ constexpr Z21::BroadcastFlags operator |(Z21::BroadcastFlags lhs, Z21::Broadcast static_cast>(rhs)); } +constexpr Z21::BroadcastFlags operator &(Z21::BroadcastFlags lhs, Z21::BroadcastFlags rhs) +{ + return static_cast( + static_cast>(lhs) & + static_cast>(rhs)); +} + #endif diff --git a/server/src/hardware/protocol/z21/serverkernel.cpp b/server/src/hardware/protocol/z21/serverkernel.cpp new file mode 100644 index 00000000..ec0254e9 --- /dev/null +++ b/server/src/hardware/protocol/z21/serverkernel.cpp @@ -0,0 +1,439 @@ +/** + * server/src/hardware/protocol/z21/serverkernel.cpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2019-2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "serverkernel.hpp" +#include "messages.hpp" +#include "../xpressnet/messages.hpp" +#include "../../decoder/decoderlist.hpp" +#include "../../../core/eventloop.hpp" +#include "../../../log/log.hpp" + +namespace Z21 { + +ServerKernel::ServerKernel(const ServerConfig& config, std::shared_ptr decoderList) + : Kernel() + , m_inactiveClientPurgeTimer{m_ioContext} + , m_config{config} + , m_decoderList{std::move(decoderList)} +{ +} + +void ServerKernel::setConfig(const ServerConfig& config) +{ + m_ioContext.post( + [this, newConfig=config]() + { + m_config = newConfig; + }); +} + +void ServerKernel::setState(bool trackPowerOn, bool emergencyStop) +{ + m_ioContext.post( + [this, trackPowerOn, emergencyStop]() + { + const auto trackPowerOnTri = toTriState(trackPowerOn); + const auto emergencyStopTri = toTriState(emergencyStop); + + const bool trackPowerOnChanged = m_trackPowerOn != trackPowerOnTri; + const bool emergencyStopChanged = m_emergencyStop != emergencyStopTri; + + m_trackPowerOn = trackPowerOnTri; + m_emergencyStop = emergencyStopTri; + + if(emergencyStopChanged && m_emergencyStop == TriState::True) + sendTo(LanXBCStopped(), BroadcastFlags::PowerLocoTurnoutChanges); + + if(trackPowerOnChanged && m_trackPowerOn == TriState::False) + sendTo(LanXBCTrackPowerOff(), BroadcastFlags::PowerLocoTurnoutChanges); + + if((trackPowerOnChanged || emergencyStopChanged) && m_trackPowerOn == TriState::True && m_emergencyStop == TriState::False) + sendTo(LanXBCTrackPowerOn(), BroadcastFlags::PowerLocoTurnoutChanges); + + if(trackPowerOnChanged || emergencyStopChanged) + sendTo(getLanSystemStateDataChanged(), BroadcastFlags::PowerLocoTurnoutChanges); + }); +} + +void ServerKernel::receiveFrom(const Message& message, IOHandler::ClientId clientId) +{ + if(m_config.debugLogRXTX) + EventLoop::call( + [this, clientId, msg=toString(message)]() + { + Log::log(m_logId, LogMessage::D2005_X_RX_X, clientId, msg); + }); + + m_clients[clientId].lastSeen = std::chrono::steady_clock::now(); + + switch(message.header()) + { + case LAN_X: + { + const auto& lanX = static_cast(message); + + if(!XpressNet::isChecksumValid(*reinterpret_cast(&lanX.xheader))) + break; + + switch(lanX.xheader) + { + case 0x21: + if(message == LanXGetVersion()) + { + sendTo(LanXGetVersionReply(ServerConfig::xBusVersion, ServerConfig::commandStationId), clientId); + } + else if(message == LanXGetStatus()) + { + LanXStatusChanged response; + if(m_emergencyStop != TriState::False) + response.db1 |= Z21_CENTRALSTATE_EMERGENCYSTOP; + if(m_trackPowerOn != TriState::True) + response.db1 |= Z21_CENTRALSTATE_TRACKVOLTAGEOFF; + response.calcChecksum(); + sendTo(response, clientId); + } + else if(message == LanXSetTrackPowerOn()) + { + if(m_config.allowTrackPowerOnReleaseEmergencyStop && (m_trackPowerOn != TriState::True || m_emergencyStop != TriState::False) && m_onTrackPowerOn) + { + EventLoop::call( + [this]() + { + m_onTrackPowerOn(); + }); + } + } + else if(message == LanXSetTrackPowerOff()) + { + if(m_config.allowTrackPowerOff && m_trackPowerOn != TriState::False && m_onTrackPowerOff) + { + EventLoop::call( + [this]() + { + m_onTrackPowerOff(); + }); + } + } + break; + + case 0x80: + if(message == LanXSetStop()) + { + if(m_config.allowEmergencyStop && m_emergencyStop != TriState::True && m_onEmergencyStop) + { + EventLoop::call( + [this]() + { + m_onEmergencyStop(); + }); + } + } + break; + + case 0xE3: + if(const auto& getLocoInfo = static_cast(message); + getLocoInfo.db0 == 0xF0) + { + subscribe(clientId, getLocoInfo.address(), getLocoInfo.isLongAddress()); + + EventLoop::call( + [this, getLocoInfo, clientId]() + { + if(auto decoder = getDecoder(getLocoInfo.address(), getLocoInfo.isLongAddress())) + postSendTo(LanXLocoInfo(*decoder), clientId); + }); + } + break; + + case 0xE4: + if(const auto& setLocoDrive = static_cast(message); + setLocoDrive.db0 >= 0x10 && setLocoDrive.db0 <= 0x13) + { + subscribe(clientId, setLocoDrive.address(), setLocoDrive.isLongAddress()); + + EventLoop::call( + [this, setLocoDrive]() + { + if(auto decoder = getDecoder(setLocoDrive.address(), setLocoDrive.isLongAddress())) + { + decoder->direction = setLocoDrive.direction(); + decoder->emergencyStop = setLocoDrive.isEmergencyStop(); + decoder->throttle = Decoder::speedStepToThrottle(setLocoDrive.speedStep(), setLocoDrive.speedSteps()); + } + //else + // Log::log(*this, LogMessage::I2001_UNKNOWN_LOCO_ADDRESS_X, setLocoDrive.address()); + }); + } + else if(const auto& setLocoFunction = static_cast(message); + setLocoFunction.db0 == 0xF8 && + setLocoFunction.switchType() != LanXSetLocoFunction::SwitchType::Invalid) + { + subscribe(clientId, setLocoFunction.address(), setLocoFunction.isLongAddress()); + + EventLoop::call( + [this, setLocoFunction]() + { + if(auto decoder = getDecoder(setLocoFunction.address(), setLocoFunction.isLongAddress())) + { + if(auto function = decoder->getFunction(setLocoFunction.functionIndex())) + { + switch(setLocoFunction.switchType()) + { + case LanXSetLocoFunction::SwitchType::Off: + function->value = false; + break; + + case LanXSetLocoFunction::SwitchType::On: + function->value = true; + break; + + case LanXSetLocoFunction::SwitchType::Toggle: + function->value = !function->value; + break; + + case LanXSetLocoFunction::SwitchType::Invalid: + assert(false); + break; + } + } + } + }); + } + break; + + case 0xF1: + if(message == LanXGetFirmwareVersion()) + sendTo(LanXGetFirmwareVersionReply(ServerConfig::firmwareVersionMajor, ServerConfig::firmwareVersionMinor), clientId); + break; + } + break; + } + case LAN_GET_LOCO_MODE: + if(message.dataLen() == sizeof(LanGetLocoMode)) + sendTo(LanGetLocoModeReply(static_cast(message).address(), LocoMode::DCC), clientId); + break; + + case LAN_SET_LOCO_MODE: + // ignore, we always report DCC + break; + + case LAN_GET_SERIAL_NUMBER: + if(message.dataLen() == sizeof(LanGetSerialNumber)) + sendTo(LanGetSerialNumberReply(ServerConfig::serialNumber), clientId); + break; + + case LAN_GET_HWINFO: + if(message.dataLen() == sizeof(LanGetHardwareInfo)) + sendTo(LanGetHardwareInfoReply(ServerConfig::hardwareType, ServerConfig::firmwareVersionMajor, ServerConfig::firmwareVersionMinor), clientId); + break; + + case LAN_GET_BROADCASTFLAGS: + if(message == LanGetBroadcastFlags()) + sendTo(LanSetBroadcastFlags(m_clients[clientId].broadcastFlags), clientId); + break; + + case LAN_SET_BROADCASTFLAGS: + if(message.dataLen() == sizeof(LanSetBroadcastFlags)) + m_clients[clientId].broadcastFlags = static_cast(message).broadcastFlags(); + break; + + case LAN_SYSTEMSTATE_GETDATA: + if(message == LanSystemStateGetData()) + sendTo(getLanSystemStateDataChanged(), clientId); + break; + + case LAN_LOGOFF: + if(message == LanLogoff()) + m_clients.erase(clientId); + break; + + case LAN_GET_CODE: + case LAN_GET_TURNOUTMODE: + case LAN_SET_TURNOUTMODE: + case LAN_RMBUS_DATACHANGED: + case LAN_RMBUS_GETDATA: + case LAN_RMBUS_PROGRAMMODULE: + case LAN_SYSTEMSTATE_DATACHANGED: + case LAN_RAILCOM_DATACHANGED: + case LAN_RAILCOM_GETDATA: + case LAN_LOCONET_Z21_RX: + case LAN_LOCONET_Z21_TX: + case LAN_LOCONET_FROM_LAN: + case LAN_LOCONET_DISPATCH_ADDR: + case LAN_LOCONET_DETECTOR: + case LAN_CAN_DETECTOR: + break; // not (yet) supported + } +} + +void ServerKernel::onStart() +{ + startInactiveClientPurgeTimer(); +} + +void ServerKernel::onStop() +{ + m_inactiveClientPurgeTimer.cancel(); +} + +void ServerKernel::sendTo(const Message& message, IOHandler::ClientId clientId) +{ + if(m_ioHandler->sendTo(message, clientId)) + { + if(m_config.debugLogRXTX) + EventLoop::call( + [this, clientId, msg=toString(message)]() + { + Log::log(m_logId, LogMessage::D2004_X_TX_X, clientId, msg); + }); + } + else + {} // log message and go to error state +} + +void ServerKernel::sendTo(const Message& message, BroadcastFlags broadcastFlags) +{ + for(const auto& client : m_clients) + if((client.second.broadcastFlags & broadcastFlags) != BroadcastFlags::None) + sendTo(message, client.first); +} + +LanSystemStateDataChanged ServerKernel::getLanSystemStateDataChanged() const +{ + LanSystemStateDataChanged message; + + if(m_emergencyStop != TriState::False) + message.centralState |= Z21_CENTRALSTATE_EMERGENCYSTOP; + if(m_trackPowerOn != TriState::True) + message.centralState |= Z21_CENTRALSTATE_TRACKVOLTAGEOFF; + + return message; +} + +std::shared_ptr ServerKernel::getDecoder(uint16_t address, bool longAddress) const +{ + auto decoder = m_decoderList->getDecoder(DecoderProtocol::DCC, address, longAddress); + if(!decoder) + decoder = m_decoderList->getDecoder(DecoderProtocol::Auto, address); + if(!decoder) + decoder = m_decoderList->getDecoder(address); + return decoder; +} + +void ServerKernel::removeClient(IOHandler::ClientId clientId) +{ + auto& subscriptions = m_clients[clientId].subscriptions; + while(!subscriptions.empty()) + unsubscribe(clientId, *subscriptions.begin()); + m_clients.erase(clientId); + m_ioHandler->purgeClient(clientId); +} + +void ServerKernel::subscribe(IOHandler::ClientId clientId, uint16_t address, bool longAddress) +{ + auto& subscriptions = m_clients[clientId].subscriptions; + const std::pair key{address, longAddress}; + if(std::find(subscriptions.begin(), subscriptions.end(), key) != subscriptions.end()) + return; + subscriptions.emplace_back(address, longAddress); + if(subscriptions.size() > ServerConfig::subscriptionMax) + unsubscribe(clientId, *subscriptions.begin()); + + EventLoop::call( + [this, key]() + { + if(auto it = m_decoderSubscriptions.find(key); it == m_decoderSubscriptions.end()) + { + if(auto decoder = getDecoder(key.first, key.second)) + m_decoderSubscriptions.emplace(key, DecoderSubscription{decoder->decoderChanged.connect(std::bind(&ServerKernel::decoderChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)), 1}); + } + else + { + it->second.count++; + } + }); +} + +void ServerKernel::unsubscribe(IOHandler::ClientId clientId, std::pair key) +{ + { + auto& subscriptions = m_clients[clientId].subscriptions; + auto it = std::find(subscriptions.begin(), subscriptions.end(), key); + if(it != subscriptions.end()) + subscriptions.erase(it); + } + + EventLoop::call( + [this, key]() + { + if(auto it = m_decoderSubscriptions.find(key); it != m_decoderSubscriptions.end()) + { + assert(it->second.count > 0); + if(--it->second.count == 0) + m_decoderSubscriptions.erase(it); + } + }); +} + +void ServerKernel::decoderChanged(const Decoder& decoder, DecoderChangeFlags /*changes*/, uint32_t /*functionNumber*/) +{ + const std::pair key(decoder.address, decoder.longAddress); + const LanXLocoInfo message(decoder); + + EventLoop::call( + [this, key, message]() + { + for(auto it : m_clients) + if((it.second.broadcastFlags & BroadcastFlags::PowerLocoTurnoutChanges) == BroadcastFlags::PowerLocoTurnoutChanges) + { + auto& subscriptions = it.second.subscriptions; + if(std::find(subscriptions.begin(), subscriptions.end(), key) != subscriptions.end()) + sendTo(message, it.first); + } + }); +} + +void ServerKernel::startInactiveClientPurgeTimer() +{ + assert(m_config.inactiveClientPurgeTime > 0); + m_inactiveClientPurgeTimer.expires_after(boost::asio::chrono::seconds(std::max(1, m_config.inactiveClientPurgeTime / 4))); + m_inactiveClientPurgeTimer.async_wait(std::bind(&ServerKernel::inactiveClientPurgeTimerExpired, this, std::placeholders::_1)); +} + +void ServerKernel::inactiveClientPurgeTimerExpired(const boost::system::error_code& ec) +{ + if(ec) + return; + + std::vector clientsToRemove; + const auto purgeTime = std::chrono::steady_clock::now() - std::chrono::seconds(ServerConfig::inactiveClientPurgeTime); + for(const auto& it : m_clients) + if(it.second.lastSeen < purgeTime) + clientsToRemove.emplace_back(it.first); + + for(const auto& clientId : clientsToRemove) + removeClient(clientId); + + startInactiveClientPurgeTimer(); +} + +} diff --git a/server/src/hardware/protocol/z21/serverkernel.hpp b/server/src/hardware/protocol/z21/serverkernel.hpp new file mode 100644 index 00000000..2aead98f --- /dev/null +++ b/server/src/hardware/protocol/z21/serverkernel.hpp @@ -0,0 +1,169 @@ +/** + * server/src/hardware/protocol/z21/serverkernel.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2019-2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_Z21_SERVERKERNEL_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_Z21_SERVERKERNEL_HPP + +#include "kernel.hpp" +#include +#include +#include +#include +#include +#include "messages.hpp" + +class DecoderList; + +namespace Z21 { + +class ServerKernel final : public Kernel +{ + private: + struct Client + { + std::chrono::time_point lastSeen; + BroadcastFlags broadcastFlags = BroadcastFlags::None; + std::list> subscriptions; + }; + + struct DecoderSubscription + { + boost::signals2::connection connection; + size_t count; //!< number of clients subscribed to the decoder + }; + + boost::asio::steady_timer m_inactiveClientPurgeTimer; + ServerConfig m_config; + std::shared_ptr m_decoderList; + std::unordered_map m_clients; + std::map, DecoderSubscription> m_decoderSubscriptions; + TriState m_trackPowerOn = TriState::Undefined; + std::function m_onTrackPowerOff; + std::function m_onTrackPowerOn; + TriState m_emergencyStop = TriState::Undefined; + std::function m_onEmergencyStop; + + ServerKernel(const ServerConfig& config, std::shared_ptr decoderList); + + void onStart() final; + void onStop() final; + + template + void postSendTo(const T& message, IOHandler::ClientId clientId) + { + m_ioContext.post( + [this, message, clientId]() + { + sendTo(message, clientId); + }); + } + + void sendTo(const Message& message, IOHandler::ClientId clientId); + void sendTo(const Message& message, BroadcastFlags broadcastFlags); + + LanSystemStateDataChanged getLanSystemStateDataChanged() const; + + std::shared_ptr getDecoder(uint16_t address, bool longAddress) const; + + void removeClient(IOHandler::ClientId clientId); + void subscribe(IOHandler::ClientId clientId, uint16_t address, bool longAddress); + void unsubscribe(IOHandler::ClientId clientId, std::pair key); + void decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber); + + void startInactiveClientPurgeTimer(); + void inactiveClientPurgeTimerExpired(const boost::system::error_code& ec); + + public: + /** + * @brief Create kernel and IO handler + * @param[in] config Z21 server configuration + * @param[in] args IO handler arguments + * @return The kernel instance + */ + template + static std::unique_ptr create(const ServerConfig& config, std::shared_ptr decoderList, Args... args) + { + static_assert(std::is_base_of_v); + std::unique_ptr kernel{new ServerKernel(config, std::move(decoderList))}; + kernel->setIOHandler(std::make_unique(*kernel, std::forward(args)...)); + return kernel; + } + + /** + * @brief ... + * @param[in] callback ... + * @note This function may not be called when the kernel is running. + */ + inline void setOnTrackPowerOff(std::function callback) + { + assert(!m_started); + m_onTrackPowerOff = std::move(callback); + } + + /** + * @brief ... + * @param[in] callback ... + * @note This function may not be called when the kernel is running. + */ + inline void setOnTrackPowerOn(std::function callback) + { + assert(!m_started); + m_onTrackPowerOn = std::move(callback); + } + + /** + * @brief ... + * @param[in] callback ... + * @note This function may not be called when the kernel is running. + */ + inline void setOnEmergencyStop(std::function callback) + { + assert(!m_started); + m_onEmergencyStop = std::move(callback); + } + + /** + * @brief Set Z21 server configuration + * @param[in] config The Z21 server configuration + */ + void setConfig(const ServerConfig& config); + + /** + * @brief Set Z21 state + * @param[in] trackPowerOn \c true if track power is on, \c false if track power is off + * @param[in] emergencyStop \c true if emergency stop is active, \c false if emergency stop isn't active + */ + void setState(bool trackPowerOn, bool emergencyStop); + + /** + * @brief Incoming message handler + * This method must be called by the IO handler whenever a Z21 message is received. + * @param[in] message The received Z21 message + * @param[in] cliendId The client who sent the message + * @note This function must run in the kernel's IO context + */ + void receiveFrom(const Message& message, IOHandler::ClientId clientId); +}; + +} + +#endif diff --git a/server/src/hardware/protocol/z21/serversettings.cpp b/server/src/hardware/protocol/z21/serversettings.cpp new file mode 100644 index 00000000..af6052a3 --- /dev/null +++ b/server/src/hardware/protocol/z21/serversettings.cpp @@ -0,0 +1,51 @@ +/** + * server/src/hardware/protocol/z21/serversettings.cpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021-2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "serversettings.hpp" + +namespace Z21 { + +ServerSettings::ServerSettings(Object& _parent, const std::string& parentPropertyName) + : Settings(_parent, parentPropertyName) + , allowEmergencyStop{this, "allow_emergency_stop", true, PropertyFlags::ReadWrite | PropertyFlags::Store} + , allowTrackPowerOff{this, "allow_track_power_off", true, PropertyFlags::ReadWrite | PropertyFlags::Store} + , allowTrackPowerOnReleaseEmergencyStop{this, "allow_track_power_on_release_emergency_stop", false, PropertyFlags::ReadWrite | PropertyFlags::Store} +{ + m_interfaceItems.insertBefore(allowEmergencyStop, debugLogRXTX); + m_interfaceItems.insertBefore(allowTrackPowerOff, debugLogRXTX); + m_interfaceItems.insertBefore(allowTrackPowerOnReleaseEmergencyStop, debugLogRXTX); +} + +ServerConfig ServerSettings::config() const +{ + ServerConfig config; + + getConfig(config); + + config.allowEmergencyStop = allowEmergencyStop; + config.allowTrackPowerOff = allowTrackPowerOff; + config.allowTrackPowerOnReleaseEmergencyStop = allowTrackPowerOnReleaseEmergencyStop; + + return config; +} + +} diff --git a/server/src/hardware/protocol/z21/serversettings.hpp b/server/src/hardware/protocol/z21/serversettings.hpp new file mode 100644 index 00000000..c5d90677 --- /dev/null +++ b/server/src/hardware/protocol/z21/serversettings.hpp @@ -0,0 +1,46 @@ +/** + * server/src/hardware/protocol/z21/serversettings.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2021-2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_Z21_SERVERSETTINGS_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_Z21_SERVERSETTINGS_HPP + +#include "settings.hpp" + +namespace Z21 { + +class ServerSettings final : public Settings +{ + CLASS_ID("z21_settings.server") + + public: + Property allowEmergencyStop; + Property allowTrackPowerOff; + Property allowTrackPowerOnReleaseEmergencyStop; + + ServerSettings(Object& _parent, const std::string& parentPropertyName); + + ServerConfig config() const; +}; + +} + +#endif diff --git a/server/src/hardware/protocol/z21/settings.cpp b/server/src/hardware/protocol/z21/settings.cpp new file mode 100644 index 00000000..acdcb878 --- /dev/null +++ b/server/src/hardware/protocol/z21/settings.cpp @@ -0,0 +1,42 @@ +/** + * server/src/hardware/protocol/z21/settings.cpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2019-2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "settings.hpp" +#include "../../../core/attributes.hpp" +#include "../../../utils/displayname.hpp" + +namespace Z21 { + +Settings::Settings(Object& _parent, const std::string& parentPropertyName) + : SubObject(_parent, parentPropertyName) + , debugLogRXTX{this, "debug_log_rx_tx", false, PropertyFlags::ReadWrite | PropertyFlags::Store} +{ + Attributes::addDisplayName(debugLogRXTX, DisplayName::Hardware::debugLogRXTX); + m_interfaceItems.add(debugLogRXTX); +} + +void Settings::getConfig(Config& config) const +{ + config.debugLogRXTX = debugLogRXTX; +} + +} diff --git a/server/src/hardware/protocol/z21/settings.hpp b/server/src/hardware/protocol/z21/settings.hpp new file mode 100644 index 00000000..f9e8070f --- /dev/null +++ b/server/src/hardware/protocol/z21/settings.hpp @@ -0,0 +1,45 @@ +/** + * server/src/hardware/protocol/z21/settings.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2019-2022 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_Z21_SETTINGS_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_Z21_SETTINGS_HPP + +#include "../../../core/subobject.hpp" +#include "../../../core/property.hpp" +#include "config.hpp" + +namespace Z21 { + +class Settings : public SubObject +{ + protected: + Settings(Object& _parent, const std::string& parentPropertyName); + + void getConfig(Config& config) const; + + public: + Property debugLogRXTX; +}; + +} + +#endif diff --git a/server/src/hardware/protocol/z21/utils.hpp b/server/src/hardware/protocol/z21/utils.hpp index d033cc16..7ef253a6 100644 --- a/server/src/hardware/protocol/z21/utils.hpp +++ b/server/src/hardware/protocol/z21/utils.hpp @@ -3,7 +3,7 @@ * * This file is part of the traintastic source code. * - * Copyright (C) 2019-2020 Reinder Feenstra + * Copyright (C) 2019-2022 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -63,6 +63,9 @@ constexpr void setEmergencyStop(uint8_t& db) constexpr uint8_t getSpeedStep(uint8_t db, uint8_t speedSteps) { + if(isEmergencyStop(db, speedSteps)) + return 0; + switch(speedSteps) { case 126: @@ -70,7 +73,7 @@ constexpr uint8_t getSpeedStep(uint8_t db, uint8_t speedSteps) break; case 28: - db = ((db & 0x0F) << 1) | ((db & 0x10) >> 4); + db = ((db & 0x0F) << 1) | ((db & 0x10) >> 4); //! @todo check break; case 14: diff --git a/server/src/utils/displayname.hpp b/server/src/utils/displayname.hpp index b65daed7..deea70ba 100644 --- a/server/src/utils/displayname.hpp +++ b/server/src/utils/displayname.hpp @@ -3,7 +3,7 @@ * * This file is part of the traintastic source code. * - * Copyright (C) 2021 Reinder Feenstra + * Copyright (C) 2021-2022 Reinder Feenstra * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -58,6 +58,7 @@ namespace DisplayName constexpr std::string_view outputs = "hardware:outputs"; constexpr std::string_view speedSteps = "hardware:speed_steps"; constexpr std::string_view xpressnet = "hardware:xpressnet"; + constexpr std::string_view z21 = "hardware:z21"; } namespace Interface {