wlanmaus: rewrite -> new kernel/iohandler model

Dieser Commit ist enthalten in:
Reinder Feenstra 2022-01-02 00:37:26 +01:00
Ursprung be63db9378
Commit 35869a7fd8
27 geänderte Dateien mit 1876 neuen und 24 gelöschten Zeilen

Datei anzeigen

@ -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"

Datei anzeigen

@ -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);
}

Datei anzeigen

@ -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<DecoderFunctions> functions;
Property<std::string> notes;
boost::signals2::signal<void (Decoder&, DecoderChangeFlags, uint32_t)> decoderChanged;
Decoder(const std::weak_ptr<World>& world, std::string_view _id);
void addToWorld() final;

Datei anzeigen

@ -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<DecoderListTableModel>(*this);
}
std::shared_ptr<Decoder> 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<Decoder> 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<Decoder>::worldEvent(state, event);

Datei anzeigen

@ -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<Decoder>
DecoderList(Object& _parent, const std::string& parentPropertyName);
TableModelPtr getModel() final;
std::shared_ptr<Decoder> getDecoder(uint16_t address) const;
std::shared_ptr<Decoder> getDecoder(DecoderProtocol protocol, uint16_t address, bool longAddress = false) const;
};
#endif

Datei anzeigen

@ -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<Interface> Interfaces::create(const std::shared_ptr<World>& 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<Interface>();
}

Datei anzeigen

@ -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
);

Datei anzeigen

@ -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>& world, std::string_view _id)
: Interface(world, _id)
, z21{this, "z21", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject}
{
z21.setValueInternal(std::make_shared<Z21::ServerSettings>(*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::UDPServerIOHandler>(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;
}

Datei anzeigen

@ -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<Z21::ServerKernel> 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::ServerSettings> z21;
WlanMausInterface(const std::weak_ptr<World>& world, std::string_view _id);
};
#endif

Datei anzeigen

@ -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

Datei anzeigen

@ -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 <cstddef>
#include <limits>
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

Datei anzeigen

@ -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<const Message*>(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);
});
}
});
}
}

Datei anzeigen

@ -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 <boost/asio/ip/udp.hpp>
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<std::byte, payloadSizeMax> 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

Datei anzeigen

@ -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 <boost/asio/buffer.hpp>
#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<ServerKernel&>(m_kernel).receiveFrom(message, clientId);
}
}

Datei anzeigen

@ -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 <unordered_map>
namespace Z21 {
class ServerKernel;
class UDPServerIOHandler final : public UDPIOHandler
{
private:
ClientId m_lastClientId = 0;
std::unordered_map<ClientId, boost::asio::ip::udp::endpoint> 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

Datei anzeigen

@ -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<boost::asio::io_context::work>(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<IOHandler> handler)
{
assert(handler);
assert(!m_ioHandler);
m_ioHandler = std::move(handler);
}
}

Datei anzeigen

@ -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 <array>
#include <thread>
#include <boost/asio/io_context.hpp>
#include <traintastic/enum/tristate.hpp>
#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<void()> m_onStarted;
protected:
boost::asio::io_context m_ioContext;
std::unique_ptr<IOHandler> m_ioHandler;
std::string m_logId;
#ifndef NDEBUG
bool m_started;
#endif
Kernel();
virtual ~Kernel() = default;
void setIOHandler(std::unique_ptr<IOHandler> 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<void()> 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

Datei anzeigen

@ -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<const LanXGetLocoInfo&>(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<const LanXSetLocoDrive&>(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<const LanXSetLocoFunction&>(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<const LanXLocoInfo&>(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<const LanXGetFirmwareVersionReply&>(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<const LanGetBroadcastFlagsReply&>(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<const LanSetBroadcastFlags&>(message).broadcastFlags());
{
s = "LAN_SET_BROADCASTFLAGS";
s.append(" flags=0x").append(toHex(static_cast<std::underlying_type_t<BroadcastFlags>>(static_cast<const LanSetBroadcastFlags&>(message).broadcastFlags())));
}
else
raw = true;
break;
case LAN_SYSTEMSTATE_DATACHANGED:
{
const auto& systemState = static_cast<const LanSystemStateDataChanged&>(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;

Datei anzeigen

@ -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<SwitchType>(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<std::underlying_type_t<Z21::BroadcastFlags>>(rhs));
}
constexpr Z21::BroadcastFlags operator &(Z21::BroadcastFlags lhs, Z21::BroadcastFlags rhs)
{
return static_cast<Z21::BroadcastFlags>(
static_cast<std::underlying_type_t<Z21::BroadcastFlags>>(lhs) &
static_cast<std::underlying_type_t<Z21::BroadcastFlags>>(rhs));
}
#endif

Datei anzeigen

@ -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> 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<const LanX&>(message);
if(!XpressNet::isChecksumValid(*reinterpret_cast<const XpressNet::Message*>(&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<const LanXGetLocoInfo&>(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<const LanXSetLocoDrive&>(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<const LanXSetLocoFunction&>(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<const LanGetLocoMode&>(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<const LanSetBroadcastFlags&>(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<Decoder> 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<uint16_t, bool> 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<uint16_t, bool> 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<uint16_t, bool> 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<IOHandler::ClientId> 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();
}
}

Datei anzeigen

@ -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 <list>
#include <map>
#include <unordered_map>
#include <boost/asio/steady_timer.hpp>
#include <boost/signals2/signal.hpp>
#include "messages.hpp"
class DecoderList;
namespace Z21 {
class ServerKernel final : public Kernel
{
private:
struct Client
{
std::chrono::time_point<std::chrono::steady_clock> lastSeen;
BroadcastFlags broadcastFlags = BroadcastFlags::None;
std::list<std::pair<uint16_t, bool>> 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<DecoderList> m_decoderList;
std::unordered_map<IOHandler::ClientId, Client> m_clients;
std::map<std::pair<uint16_t, bool>, DecoderSubscription> m_decoderSubscriptions;
TriState m_trackPowerOn = TriState::Undefined;
std::function<void()> m_onTrackPowerOff;
std::function<void()> m_onTrackPowerOn;
TriState m_emergencyStop = TriState::Undefined;
std::function<void()> m_onEmergencyStop;
ServerKernel(const ServerConfig& config, std::shared_ptr<DecoderList> decoderList);
void onStart() final;
void onStop() final;
template<class T>
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<Decoder> 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<uint16_t, bool> 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<class IOHandlerType, class... Args>
static std::unique_ptr<ServerKernel> create(const ServerConfig& config, std::shared_ptr<DecoderList> decoderList, Args... args)
{
static_assert(std::is_base_of_v<IOHandler, IOHandlerType>);
std::unique_ptr<ServerKernel> kernel{new ServerKernel(config, std::move(decoderList))};
kernel->setIOHandler(std::make_unique<IOHandlerType>(*kernel, std::forward<Args>(args)...));
return kernel;
}
/**
* @brief ...
* @param[in] callback ...
* @note This function may not be called when the kernel is running.
*/
inline void setOnTrackPowerOff(std::function<void()> 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<void()> 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<void()> 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

Datei anzeigen

@ -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;
}
}

Datei anzeigen

@ -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<bool> allowEmergencyStop;
Property<bool> allowTrackPowerOff;
Property<bool> allowTrackPowerOnReleaseEmergencyStop;
ServerSettings(Object& _parent, const std::string& parentPropertyName);
ServerConfig config() const;
};
}
#endif

Datei anzeigen

@ -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;
}
}

Datei anzeigen

@ -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<bool> debugLogRXTX;
};
}
#endif

Datei anzeigen

@ -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:

Datei anzeigen

@ -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
{