z21: rewrite -> new kernel/iohandler model

Dieser Commit ist enthalten in:
Reinder Feenstra 2022-01-02 18:33:04 +01:00
Ursprung 5c891c2428
Commit 14eb29e95b
15 geänderte Dateien mit 1158 neuen und 4 gelöschten Zeilen

Datei anzeigen

@ -31,5 +31,6 @@ std::shared_ptr<Interface> Interfaces::create(const std::shared_ptr<World>& worl
IF_CLASSID_CREATE(LocoNetInterface)
IF_CLASSID_CREATE(WlanMausInterface)
IF_CLASSID_CREATE(XpressNetInterface)
IF_CLASSID_CREATE(Z21Interface)
return std::shared_ptr<Interface>();
}

Datei anzeigen

@ -31,6 +31,7 @@
#include "loconetinterface.hpp"
#include "wlanmausinterface.hpp"
#include "xpressnetinterface.hpp"
#include "z21interface.hpp"
struct Interfaces
{
@ -41,7 +42,8 @@ struct Interfaces
ECoSInterface::classId,
LocoNetInterface::classId,
WlanMausInterface::classId,
XpressNetInterface::classId
XpressNetInterface::classId,
Z21Interface::classId
);
static std::shared_ptr<Interface> create(const std::shared_ptr<World>& world, std::string_view classId, std::string_view id = std::string_view{});

Datei anzeigen

@ -0,0 +1,260 @@
/**
* server/src/hardware/interface/z21interface.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2021 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "z21interface.hpp"
#include "../decoder/decoderlisttablemodel.hpp"
#include "../protocol/z21/messages.hpp"
#include "../protocol/z21/iohandler/udpclientiohandler.hpp"
#include "../../core/attributes.hpp"
#include "../../log/log.hpp"
#include "../../log/logmessageexception.hpp"
#include "../../utils/category.hpp"
#include "../../utils/displayname.hpp"
#include "../../utils/inrange.hpp"
#include "../../world/world.hpp"
Z21Interface::Z21Interface(const std::weak_ptr<World>& world, std::string_view _id)
: Interface(world, _id)
, hostname{this, "hostname", "192.168.1.203", PropertyFlags::ReadWrite | PropertyFlags::Store}
, port{this, "port", 21105, PropertyFlags::ReadWrite | PropertyFlags::Store}
, z21{this, "z21", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject}
, decoders{this, "decoders", nullptr, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::SubObject}
, hardwareType{this, "hardware_type", "", PropertyFlags::ReadOnly | PropertyFlags::NoStore}
, serialNumber{this, "serial_number", "", PropertyFlags::ReadOnly | PropertyFlags::NoStore}
, firmwareVersion{this, "firmware_version", "", PropertyFlags::ReadOnly | PropertyFlags::NoStore}
{
z21.setValueInternal(std::make_shared<Z21::ClientSettings>(*this, z21.name()));
decoders.setValueInternal(std::make_shared<DecoderList>(*this, decoders.name()));
Attributes::addDisplayName(hostname, DisplayName::IP::hostname);
Attributes::addEnabled(hostname, !online);
m_interfaceItems.insertBefore(hostname, notes);
Attributes::addDisplayName(port, DisplayName::IP::port);
Attributes::addEnabled(port, !online);
m_interfaceItems.insertBefore(port, notes);
Attributes::addDisplayName(z21, DisplayName::Hardware::z21);
m_interfaceItems.insertBefore(z21, notes);
Attributes::addDisplayName(decoders, DisplayName::Hardware::decoders);
m_interfaceItems.insertBefore(decoders, notes);
Attributes::addCategory(hardwareType, Category::info);
m_interfaceItems.insertBefore(hardwareType, notes);
Attributes::addCategory(serialNumber, Category::info);
m_interfaceItems.insertBefore(serialNumber, notes);
Attributes::addCategory(firmwareVersion, Category::info);
m_interfaceItems.insertBefore(firmwareVersion, notes);
}
bool Z21Interface::addDecoder(Decoder& decoder)
{
const bool success = DecoderController::addDecoder(decoder);
if(success)
decoders->addObject(decoder.shared_ptr<Decoder>());
return success;
}
bool Z21Interface::removeDecoder(Decoder& decoder)
{
const bool success = DecoderController::removeDecoder(decoder);
if(success)
decoders->removeObject(decoder.shared_ptr<Decoder>());
return success;
}
void Z21Interface::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber)
{
if(m_kernel)
m_kernel->decoderChanged(decoder, changes, functionNumber);
}
bool Z21Interface::setOnline(bool& value)
{
if(!m_kernel && value)
{
try
{
m_kernel = Z21::ClientKernel::create<Z21::UDPClientIOHandler>(z21->config(), hostname.value(), port.value());
status.setValueInternal(InterfaceStatus::Initializing);
m_kernel->setLogId(id.value());
m_kernel->setOnStarted(
[this]()
{
status.setValueInternal(InterfaceStatus::Online);
});
m_kernel->setOnSerialNumberChanged(
[this](uint32_t newValue)
{
serialNumber.setValueInternal(std::to_string(newValue));
});
m_kernel->setOnHardwareInfoChanged(
[this](Z21::HardwareType type, uint8_t versionMajor, uint8_t versionMinor)
{
hardwareType.setValueInternal(std::string(Z21::toString(type)));
Log::log(*this, LogMessage::I2002_HARDWARE_TYPE_X, hardwareType.value());
if(versionMajor != 0 || versionMinor != 0)
{
firmwareVersion.setValueInternal(std::to_string(versionMajor).append(".").append(std::to_string(versionMinor)));
Log::log(*this, LogMessage::I2003_FIRMWARE_VERSION_X, firmwareVersion.value());
}
else
firmwareVersion.setValueInternal("");
});
m_kernel->setOnTrackPowerOnChanged(
[this](bool powerOn)
{
if(auto w = m_world.lock())
{
if(powerOn == contains(w->state.value(), WorldState::PowerOn))
return;
if(powerOn)
w->powerOn();
else
w->powerOff();
}
});
m_kernel->setOnEmergencyStop(
[this]()
{
if(auto w = m_world.lock(); w && contains(w->state.value(), WorldState::Run))
w->stop();
});
m_kernel->setDecoderController(this);
m_kernel->start();
m_z21PropertyChanged = z21->propertyChanged.connect(
[this](BaseProperty&)
{
m_kernel->setConfig(z21->config());
});
if(auto w = m_world.lock())
{
if(contains(w->state.value(), WorldState::PowerOn))
m_kernel->trackPowerOn();
else
m_kernel->trackPowerOff();
if(!contains(w->state.value(), WorldState::Run))
m_kernel->emergencyStop();
}
Attributes::setEnabled({hostname, port}, false);
}
catch(const LogMessageException& e)
{
status.setValueInternal(InterfaceStatus::Offline);
Log::log(*this, e.message(), e.args());
return false;
}
}
else if(m_kernel && !value)
{
Attributes::setEnabled({hostname, port}, true);
m_z21PropertyChanged.disconnect();
m_kernel->stop();
m_kernel.reset();
status.setValueInternal(InterfaceStatus::Offline);
hardwareType.setValueInternal("");
serialNumber.setValueInternal("");
firmwareVersion.setValueInternal("");
}
return true;
}
void Z21Interface::addToWorld()
{
Interface::addToWorld();
if(auto world = m_world.lock())
{
world->decoderControllers->add(std::dynamic_pointer_cast<DecoderController>(shared_from_this()));
}
}
void Z21Interface::destroying()
{
for(const auto& decoder : *decoders)
{
assert(decoder->interface.value() == std::dynamic_pointer_cast<DecoderController>(shared_from_this()));
decoder->interface = nullptr;
}
if(auto world = m_world.lock())
{
world->decoderControllers->remove(std::dynamic_pointer_cast<DecoderController>(shared_from_this()));
}
Interface::destroying();
}
void Z21Interface::worldEvent(WorldState state, WorldEvent event)
{
Interface::worldEvent(state, event);
if(m_kernel)
{
switch(event)
{
case WorldEvent::PowerOff:
m_kernel->trackPowerOff();
break;
case WorldEvent::PowerOn:
m_kernel->trackPowerOn();
if(!contains(state, WorldState::Run))
m_kernel->emergencyStop();
break;
case WorldEvent::Stop:
m_kernel->emergencyStop();
break;
case WorldEvent::Run:
if(contains(state, WorldState::PowerOn))
m_kernel->trackPowerOn();
break;
default:
break;
}
}
}
void Z21Interface::idChanged(const std::string& newId)
{
if(m_kernel)
m_kernel->setLogId(newId);
}

Datei anzeigen

@ -0,0 +1,76 @@
/**
* server/src/hardware/interface/z21interface.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2021 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef TRAINTASTIC_SERVER_HARDWARE_INTERFACE_Z21INTERFACE_HPP
#define TRAINTASTIC_SERVER_HARDWARE_INTERFACE_Z21INTERFACE_HPP
#include "interface.hpp"
#include "../protocol/z21/clientkernel.hpp"
#include "../protocol/z21/clientsettings.hpp"
#include "../decoder/decodercontroller.hpp"
#include "../decoder/decoderlist.hpp"
#include "../../core/objectproperty.hpp"
/**
* @brief Z21 hardware interface
*/
class Z21Interface final
: public Interface
, public DecoderController
{
CLASS_ID("interface.z21")
DEFAULT_ID("z21")
CREATE(Z21Interface)
private:
std::unique_ptr<Z21::ClientKernel> m_kernel;
boost::signals2::connection m_z21PropertyChanged;
void addToWorld() final;
void destroying() final;
void worldEvent(WorldState state, WorldEvent event) final;
void idChanged(const std::string& newId) final;
void updateVisible();
protected:
bool setOnline(bool& value) final;
public:
Property<std::string> hostname;
Property<uint16_t> port;
ObjectProperty<Z21::ClientSettings> z21;
ObjectProperty<DecoderList> decoders;
Property<std::string> hardwareType;
Property<std::string> serialNumber;
Property<std::string> firmwareVersion;
Z21Interface(const std::weak_ptr<World>& world, std::string_view _id);
// DecoderController:
[[nodiscard]] bool addDecoder(Decoder& decoder) final;
[[nodiscard]] bool removeDecoder(Decoder& decoder) final;
void decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber) final;
};
#endif

Datei anzeigen

@ -0,0 +1,345 @@
/**
* server/src/hardware/protocol/z21/clientkernel.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 "clientkernel.hpp"
#include "messages.hpp"
#include "../xpressnet/messages.hpp"
#include "../../decoder/decoder.hpp"
#include "../../decoder/decoderchangeflags.hpp"
#include "../../input/inputcontroller.hpp"
#include "../../../core/eventloop.hpp"
#include "../../../log/log.hpp"
namespace Z21 {
ClientKernel::ClientKernel(const ClientConfig& config)
: Kernel()
, m_keepAliveTimer(m_ioContext)
, m_config{config}
{
}
void ClientKernel::setConfig(const ClientConfig& config)
{
m_ioContext.post(
[this, newConfig=config]()
{
m_config = newConfig;
});
}
void ClientKernel::receive(const Message& message)
{
if(m_config.debugLogRXTX)
EventLoop::call(
[this, msg=toString(message)]()
{
Log::log(m_logId, LogMessage::D2002_RX_X, msg);
});
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 0x61:
if(message == LanXBCTrackPowerOff())
{
if(m_trackPowerOn != TriState::False)
{
m_trackPowerOn = TriState::False;
if(m_onTrackPowerOnChanged)
EventLoop::call(
[this]()
{
m_onTrackPowerOnChanged(false);
});
}
}
else if(message == LanXBCTrackPowerOn())
{
if(m_trackPowerOn != TriState::True)
{
m_trackPowerOn = TriState::True;
if(m_onTrackPowerOnChanged)
EventLoop::call(
[this]()
{
m_onTrackPowerOnChanged(true);
});
}
}
break;
case 0x81:
if(message == LanXBCStopped())
{
if(m_emergencyStop != TriState::True)
{
m_emergencyStop = TriState::True;
if(m_onEmergencyStop)
EventLoop::call(
[this]()
{
m_onEmergencyStop();
});
}
}
break;
}
break;
}
case LAN_GET_SERIAL_NUMBER:
if(message.dataLen() == sizeof(LanGetSerialNumberReply))
{
const auto& reply = static_cast<const LanGetSerialNumberReply&>(message);
if(m_serialNumber != reply.serialNumber())
{
m_serialNumber = reply.serialNumber();
if(m_onSerialNumberChanged)
{
EventLoop::call(
[this, serialNumber=m_serialNumber]()
{
m_onSerialNumberChanged(serialNumber);
});
}
}
}
break;
case LAN_GET_HWINFO:
if(message.dataLen() == sizeof(LanGetHardwareInfoReply))
{
const auto& reply = static_cast<const LanGetHardwareInfoReply&>(message);
if(m_hardwareType != reply.hardwareType() ||
m_firmwareVersionMajor != reply.firmwareVersionMajor() ||
m_firmwareVersionMinor != reply.firmwareVersionMinor())
{
m_hardwareType = reply.hardwareType();
m_firmwareVersionMajor = reply.firmwareVersionMajor();
m_firmwareVersionMinor = reply.firmwareVersionMinor();
if(m_onHardwareInfoChanged)
{
EventLoop::call(
[this, hardwareType=m_hardwareType, firmwareVersionMajor=m_firmwareVersionMajor, firmwareVersionMinor=m_firmwareVersionMinor]()
{
m_onHardwareInfoChanged(hardwareType, firmwareVersionMajor, firmwareVersionMinor);
});
}
}
}
break;
case LAN_GET_CODE:
case LAN_LOGOFF:
case LAN_SET_BROADCASTFLAGS:
case LAN_GET_BROADCASTFLAGS:
case LAN_GET_LOCO_MODE:
case LAN_SET_LOCO_MODE:
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_SYSTEMSTATE_GETDATA:
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 ClientKernel::trackPowerOn()
{
m_ioContext.post(
[this]()
{
if(m_trackPowerOn != TriState::True || m_emergencyStop != TriState::False)
send(LanXSetTrackPowerOn());
});
}
void ClientKernel::trackPowerOff()
{
m_ioContext.post(
[this]()
{
if(m_trackPowerOn != TriState::False)
send(LanXSetTrackPowerOff());
});
}
void ClientKernel::emergencyStop()
{
m_ioContext.post(
[this]()
{
if(m_emergencyStop != TriState::True)
send(LanXSetStop());
});
}
void ClientKernel::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber)
{
if(has(changes, DecoderChangeFlags::EmergencyStop | DecoderChangeFlags::Direction | DecoderChangeFlags::Throttle | DecoderChangeFlags::SpeedSteps))
{
LanXSetLocoDrive cmd;
cmd.setAddress(decoder.address, decoder.longAddress);
switch(decoder.speedSteps)
{
case 14:
{
const uint8_t speedStep = Decoder::throttleToSpeedStep(decoder.throttle, 14);
cmd.db0 = 0x10;
if(decoder.emergencyStop)
cmd.speedAndDirection = 0x01;
else if(speedStep > 0)
cmd.speedAndDirection = speedStep + 1;
break;
}
case 28:
{
uint8_t speedStep = Decoder::throttleToSpeedStep(decoder.throttle, 28);
cmd.db0 = 0x12;
if(decoder.emergencyStop)
cmd.speedAndDirection = 0x01;
else if(speedStep > 0)
{
speedStep++;
cmd.speedAndDirection = ((speedStep & 0x01) << 4) | (speedStep >> 1);
}
break;
}
case 126:
case 128:
default:
{
const uint8_t speedStep = Decoder::throttleToSpeedStep(decoder.throttle, 126);
cmd.db0 = 0x13;
if(decoder.emergencyStop)
cmd.speedAndDirection = 0x01;
else if(speedStep > 0)
cmd.speedAndDirection = speedStep + 1;
break;
}
}
if(decoder.direction.value() == Direction::Forward)
cmd.speedAndDirection |= 0x80;
cmd.checksum = XpressNet::calcChecksum(*reinterpret_cast<const XpressNet::Message*>(&cmd.xheader));
postSend(cmd);
}
else if(has(changes, DecoderChangeFlags::FunctionValue))
{
if(functionNumber <= LanXSetLocoFunction::functionNumberMax)
if(const auto& f = decoder.getFunction(functionNumber))
postSend(LanXSetLocoFunction(
decoder.address, decoder.longAddress,
static_cast<uint8_t>(functionNumber),
f->value ? LanXSetLocoFunction::SwitchType::On : LanXSetLocoFunction::SwitchType::Off));
}
}
void ClientKernel::onStart()
{
// reset all state values
m_serialNumber = 0;
m_hardwareType = HWT_UNKNOWN;
m_firmwareVersionMajor = 0;
m_firmwareVersionMinor = 0;
m_trackPowerOn = TriState::Undefined;
m_emergencyStop = TriState::Undefined;
send(LanGetSerialNumber());
send(LanGetHardwareInfo());
send(LanSetBroadcastFlags(
BroadcastFlags::PowerLocoTurnoutChanges |
BroadcastFlags::SystemStatusChanges |
BroadcastFlags::AllLocoChanges)); // seems not to work with DR5000
send(LanGetBroadcastFlags());
send(LanSystemStateGetData());
startKeepAliveTimer();
}
void ClientKernel::onStop()
{
send(LanLogoff());
}
void ClientKernel::send(const Message& message)
{
if(m_ioHandler->send(message))
{
if(m_config.debugLogRXTX)
EventLoop::call(
[this, msg=toString(message)]()
{
Log::log(m_logId, LogMessage::D2001_TX_X, msg);
});
}
else
{} // log message and go to error state
}
void ClientKernel::startKeepAliveTimer()
{
assert(m_config.keepAliveInterval > 0);
m_keepAliveTimer.expires_after(boost::asio::chrono::seconds(m_config.keepAliveInterval));
m_keepAliveTimer.async_wait(std::bind(&ClientKernel::keepAliveTimerExpired, this, std::placeholders::_1));
}
void ClientKernel::keepAliveTimerExpired(const boost::system::error_code& ec)
{
if(ec)
return;
send(LanSystemStateGetData());
startKeepAliveTimer();
}
}

Datei anzeigen

@ -0,0 +1,178 @@
/**
* server/src/hardware/protocol/z21/clientkernel.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_CLIENTKERNEL_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_Z21_CLIENTKERNEL_HPP
#include "kernel.hpp"
#include <boost/asio/steady_timer.hpp>
#include "../../../enum/tristate.hpp"
namespace Z21 {
class ClientKernel final : public Kernel
{
private:
boost::asio::steady_timer m_keepAliveTimer;
uint32_t m_serialNumber;
std::function<void(uint32_t)> m_onSerialNumberChanged;
HardwareType m_hardwareType;
uint8_t m_firmwareVersionMajor;
uint8_t m_firmwareVersionMinor;
std::function<void(HardwareType, uint8_t, uint8_t)> m_onHardwareInfoChanged;
TriState m_trackPowerOn;
TriState m_emergencyStop;
std::function<void(bool)> m_onTrackPowerOnChanged;
std::function<void()> m_onEmergencyStop;
DecoderController* m_decoderController = nullptr;
ClientConfig m_config;
ClientKernel(const ClientConfig& config);
void onStart() final;
void onStop() final;
template<class T>
void postSend(const T& message)
{
m_ioContext.post(
[this, message]()
{
send(message);
});
}
void send(const Message& message);
void startKeepAliveTimer();
void keepAliveTimerExpired(const boost::system::error_code& ec);
public:
/**
* @brief Create kernel and IO handler
* @param[in] config Z21 client configuration
* @param[in] args IO handler arguments
* @return The kernel instance
*/
template<class IOHandlerType, class... Args>
static std::unique_ptr<ClientKernel> create(const ClientConfig& config, Args... args)
{
static_assert(std::is_base_of_v<IOHandler, IOHandlerType>);
std::unique_ptr<ClientKernel> kernel{new ClientKernel(config)};
kernel->setIOHandler(std::make_unique<IOHandlerType>(*kernel, std::forward<Args>(args)...));
return kernel;
}
/**
* @brief Set Z21 client configuration
* @param[in] config The Z21 client configuration
*/
void setConfig(const ClientConfig& config);
/**
* @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
* @note This function must run in the kernel's IO context
*/
void receive(const Message& message);
/**
* @brief ...
* @param[in] callback ...
* @note This function may not be called when the kernel is running.
*/
inline void setOnSerialNumberChanged(std::function<void(uint32_t)> callback)
{
assert(!m_started);
m_onSerialNumberChanged = std::move(callback);
}
/**
* @brief ...
* @param[in] callback ...
* @note This function may not be called when the kernel is running.
*/
inline void setOnHardwareInfoChanged(std::function<void(HardwareType, uint8_t, uint8_t)> callback)
{
assert(!m_started);
m_onHardwareInfoChanged = std::move(callback);
}
/**
* @brief ...
* @param[in] callback ...
* @note This function may not be called when the kernel is running.
*/
inline void setOnTrackPowerOnChanged(std::function<void(bool)> callback)
{
assert(!m_started);
m_onTrackPowerOnChanged = std::move(callback);
}
/**
*/
void trackPowerOn();
/**
*/
void trackPowerOff();
/**
*/
void emergencyStop();
/**
* @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 the decoder controller
* @param[in] decoderController The decoder controller
* @note This function may not be called when the kernel is running.
*/
inline void setDecoderController(DecoderController* decoderController)
{
assert(!m_started);
m_decoderController = decoderController;
}
/**
*/
void decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber);
};
}
#endif

Datei anzeigen

@ -0,0 +1,41 @@
/**
* server/src/hardware/protocol/z21/clientsettings.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 "clientsettings.hpp"
namespace Z21 {
ClientSettings::ClientSettings(Object& _parent, const std::string& parentPropertyName)
: Settings(_parent, parentPropertyName)
{
}
ClientConfig ClientSettings::config() const
{
ClientConfig config;
getConfig(config);
return config;
}
}

Datei anzeigen

@ -0,0 +1,42 @@
/**
* server/src/hardware/protocol/z21/clientsettings.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_CLIENTSETTINGS_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_Z21_CLIENTSETTINGS_HPP
#include "settings.hpp"
namespace Z21 {
class ClientSettings final : public Settings
{
CLASS_ID("z21_settings.client")
public:
ClientSettings(Object& _parent, const std::string& parentPropertyName);
ClientConfig config() const;
};
}
#endif

Datei anzeigen

@ -32,6 +32,11 @@ struct Config
bool debugLogRXTX;
};
struct ClientConfig : Config
{
static constexpr uint16_t keepAliveInterval = 15; //!< sec
};
struct ServerConfig : Config
{
static constexpr CommandStationId commandStationId = CommandStationId::Z21;

Datei anzeigen

@ -0,0 +1,101 @@
/**
* 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 "udpclientiohandler.hpp"
#include "../clientkernel.hpp"
#include "../messages.hpp"
#include "../../../../core/eventloop.hpp"
#include "../../../../log/log.hpp"
#include "../../../../log/logmessageexception.hpp"
namespace Z21 {
UDPClientIOHandler::UDPClientIOHandler(ClientKernel& kernel, const std::string& hostname, uint16_t port)
: UDPIOHandler(kernel)
, m_sendBufferOffset{0}
{
boost::system::error_code ec;
m_remoteEndpoint.port(port);
m_remoteEndpoint.address(boost::asio::ip::make_address(hostname, ec));
if(ec)
throw LogMessageException(LogMessage::E2003_MAKE_ADDRESS_FAILED_X, 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(), 0), ec))
{
m_socket.close();
throw LogMessageException(LogMessage::E2006_SOCKET_BIND_FAILED_X, ec);
}
}
bool UDPClientIOHandler::send(const Message& message)
{
if(m_sendBufferOffset + message.dataLen() > m_sendBuffer.size())
return false;
const bool wasEmpty = m_sendBufferOffset == 0;
memcpy(m_sendBuffer.data() + m_sendBufferOffset, &message, message.dataLen());
m_sendBufferOffset += message.dataLen();
if(wasEmpty)
send();
return true;
}
void UDPClientIOHandler::receive(const Message& message, const boost::asio::ip::udp::endpoint& /*remoteEndpoint*/)
{
static_cast<ClientKernel&>(m_kernel).receive(message);
}
void UDPClientIOHandler::send()
{
m_socket.async_send_to(boost::asio::buffer(m_sendBuffer.data(), m_sendBufferOffset), m_remoteEndpoint,
[this](const boost::system::error_code& ec, std::size_t bytesTransferred)
{
if(!ec)
{
m_sendBufferOffset -= bytesTransferred;
if(m_sendBufferOffset > 0)
{
memmove(m_sendBuffer.data(), m_sendBuffer.data() + bytesTransferred, m_sendBufferOffset);
send();
}
}
else if(ec != boost::asio::error::operation_aborted)
{
EventLoop::call(
[this, ec]()
{
Log::log(m_kernel.logId(), LogMessage::E2011_SOCKET_SEND_FAILED_X, ec);
// TODO interface status -> error
});
}
});
}
}

Datei anzeigen

@ -0,0 +1,52 @@
/**
* server/src/hardware/protocol/z21/iohandler/udpclientiohandler.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_UDPCLIENTIOHANDLER_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_Z21_IOHANDLER_UDPCLIENTIOHANDLER_HPP
#include "udpiohandler.hpp"
namespace Z21 {
class ClientKernel;
class UDPClientIOHandler final : public UDPIOHandler
{
private:
boost::asio::ip::udp::endpoint m_remoteEndpoint;
std::array<std::byte, payloadSizeMax> m_sendBuffer;
size_t m_sendBufferOffset;
void send();
protected:
void receive(const Message& message, const boost::asio::ip::udp::endpoint& remoteEndpoint) final;
public:
UDPClientIOHandler(ClientKernel& kernel, const std::string& hostname, uint16_t port);
bool send(const Message& message) final;
};
}
#endif

Datei anzeigen

@ -77,8 +77,23 @@ std::string toString(const Message& message, bool raw)
switch(static_cast<const LanX&>(message).xheader)
{
case 0x21:
if(message == LanXGetStatus())
if(message == LanXGetVersion())
s = "LAN_X_GET_VERSION";
else if(message == LanXGetStatus())
s = "LAN_X_GET_STATUS";
else if(message == LanXSetTrackPowerOff())
s = "LAN_X_SET_TRACK_POWER_OFF";
else if(message == LanXSetTrackPowerOn())
s = "LAN_X_SET_TRACK_POWER_ON";
else
raw = true;
break;
case 0x61:
if(message == LanXBCTrackPowerOff())
s = "LAN_X_BC_TRACK_POWER_OFF";
else if(message == LanXBCTrackPowerOn())
s = "LAN_X_BC_TRACK_POWER_ON";
else
raw = true;
break;
@ -96,6 +111,13 @@ std::string toString(const Message& message, bool raw)
raw = true;
break;
case 0x80:
if(message == LanXSetStop())
s = "LAN_X_SET_STOP";
else
raw = true;
break;
case 0xE3:
if(const auto& getLocoInfo = static_cast<const LanXGetLocoInfo&>(message); getLocoInfo.db0 == 0xF0)
{

Datei anzeigen

@ -504,6 +504,11 @@ struct LanXSetLocoFunction : LanX
Invalid = 3,
};
static constexpr uint8_t functionNumberMax = 28;
static constexpr uint8_t functionNumberMask = 0x3F;
static constexpr uint8_t switchTypeMask = 0xC0;
static constexpr uint8_t switchTypeShift = 6;
uint8_t db0 = 0xf8;
uint8_t addressHigh;
uint8_t addressLow;
@ -515,6 +520,15 @@ struct LanXSetLocoFunction : LanX
{
}
LanXSetLocoFunction(uint16_t address, bool longAddress, uint8_t functionIndex, SwitchType value)
: LanXSetLocoFunction()
{
setAddress(address, longAddress);
setFunctionIndex(functionIndex);
setSwitchType(value);
calcChecksum();
}
inline uint16_t address() const
{
return (static_cast<uint16_t>(addressHigh & 0x3F) << 8) | addressLow;
@ -533,12 +547,23 @@ struct LanXSetLocoFunction : LanX
inline SwitchType switchType() const
{
return static_cast<SwitchType>(db3 >> 6);
return static_cast<SwitchType>(db3 >> switchTypeShift);
}
inline void setSwitchType(SwitchType value)
{
db3 = (db3 & functionNumberMask) | (static_cast<uint8_t>(value) << switchTypeShift);
}
inline uint8_t functionIndex() const
{
return db3 & 0x3F;
return db3 & functionNumberMask;
}
inline void setFunctionIndex(uint8_t value)
{
assert(value <= functionNumberMax);
db3 = (db3 & switchTypeMask) | (value & functionNumberMask);
}
} ATTRIBUTE_PACKED;
static_assert(sizeof(LanXSetLocoFunction) == 10);

Datei anzeigen

@ -70,6 +70,8 @@ enum class LogMessage : uint32_t
I1004_CONNECTION_LOST = LogMessageOffset::info + 1004,
I1005_BUILDING_WORLD_INDEX = LogMessageOffset::info + 1005,
I2001_UNKNOWN_LOCO_ADDRESS_X = LogMessageOffset::info + 2001,
I2002_HARDWARE_TYPE_X = LogMessageOffset::info + 2002,
I2003_FIRMWARE_VERSION_X = LogMessageOffset::info + 2003,
I9999_X = LogMessageOffset::info + 9999,
// Notice:

Datei anzeigen

@ -236,6 +236,8 @@ message:I1003=Client connected
message:I1004=Connection lost
message:I1005=Building world index
message:I2001=Unknown loco address: %1
message:I2002=Hardware type: %1
message:I2003=Firmware version: %1
message:I9999=%1
message:N1001=Received signal: %1
message:N1002=Created new world