traintastic/server/src/hardware/interface/xpressnetinterface.cpp
2025-03-18 11:49:22 +01:00

458 Zeilen
16 KiB
C++

/**
* server/src/hardware/interface/xpressnetinterface.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "xpressnetinterface.hpp"
#include "../decoder/list/decoderlist.hpp"
#include "../input/input.hpp"
#include "../input/list/inputlist.hpp"
#include "../output/list/outputlist.hpp"
#include "../protocol/xpressnet/kernel.hpp"
#include "../protocol/xpressnet/settings.hpp"
#include "../protocol/xpressnet/messages.hpp"
#include "../protocol/xpressnet/iohandler/serialiohandler.hpp"
#include "../protocol/xpressnet/iohandler/simulationiohandler.hpp"
#include "../protocol/xpressnet/iohandler/liusbiohandler.hpp"
#include "../protocol/xpressnet/iohandler/rosofts88xpressnetliiohandler.hpp"
#include "../protocol/xpressnet/iohandler/tcpiohandler.hpp"
#include "../../core/attributes.hpp"
#include "../../core/eventloop.hpp"
#include "../../core/method.tpp"
#include "../../core/objectproperty.tpp"
#include "../../log/log.hpp"
#include "../../log/logmessageexception.hpp"
#include "../../utils/displayname.hpp"
#include "../../utils/inrange.hpp"
#include "../../utils/makearray.hpp"
#include "../../world/world.hpp"
constexpr auto decoderListColumns = DecoderListColumn::Id | DecoderListColumn::Name | DecoderListColumn::Address;
constexpr auto inputListColumns = InputListColumn::Id | InputListColumn::Name | InputListColumn::Address;
constexpr auto outputListColumns = OutputListColumn::Address;
CREATE_IMPL(XpressNetInterface)
XpressNetInterface::XpressNetInterface(World& world, std::string_view _id)
: Interface(world, _id)
, DecoderController(*this, decoderListColumns)
, InputController(static_cast<IdObject&>(*this))
, OutputController(static_cast<IdObject&>(*this))
, type{this, "type", XpressNetInterfaceType::Serial, PropertyFlags::ReadWrite | PropertyFlags::Store,
[this](XpressNetInterfaceType /*value*/)
{
updateVisible();
}}
, serialInterfaceType{this, "interface", XpressNetSerialInterfaceType::LenzLI100, PropertyFlags::ReadWrite | PropertyFlags::Store,
[this](XpressNetSerialInterfaceType value)
{
switch(value)
{
case XpressNetSerialInterfaceType::LenzLI100:
case XpressNetSerialInterfaceType::RoSoftS88XPressNetLI:
baudrate.setValueInternal(9600);
flowControl.setValueInternal(SerialFlowControl::Hardware);
break;
case XpressNetSerialInterfaceType::LenzLI100F:
case XpressNetSerialInterfaceType::LenzLI101F:
baudrate.setValueInternal(19200);
flowControl.setValueInternal(SerialFlowControl::Hardware);
break;
case XpressNetSerialInterfaceType::LenzLIUSB:
baudrate.setValueInternal(57600);
flowControl.setValueInternal(SerialFlowControl::None);
break;
case XpressNetSerialInterfaceType::DigikeijsDR5000:
baudrate.setValueInternal(115200);
flowControl.setValueInternal(SerialFlowControl::None);
break;
}
updateVisible();
}}
, device{this, "device", "", PropertyFlags::ReadWrite | PropertyFlags::Store}
, baudrate{this, "baudrate", 19200, PropertyFlags::ReadWrite | PropertyFlags::Store}
, flowControl{this, "flow_control", SerialFlowControl::None, PropertyFlags::ReadWrite | PropertyFlags::Store}
, hostname{this, "hostname", "", PropertyFlags::ReadWrite | PropertyFlags::Store}
, port{this, "port", 5550, PropertyFlags::ReadWrite | PropertyFlags::Store}
, s88StartAddress{this, "s88_start_address", XpressNet::RoSoftS88XpressNetLI::S88StartAddress::startAddressDefault, PropertyFlags::ReadWrite | PropertyFlags::Store}
, s88ModuleCount{this, "s88_module_count", XpressNet::RoSoftS88XpressNetLI::S88ModuleCount::moduleCountDefault, PropertyFlags::ReadWrite | PropertyFlags::Store}
, xpressnet{this, "xpressnet", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject}
{
name = "XpressNet";
xpressnet.setValueInternal(std::make_shared<XpressNet::Settings>(*this, xpressnet.name()));
Attributes::addDisplayName(type, DisplayName::Interface::type);
Attributes::addEnabled(type, !online);
Attributes::addValues(type, xpressNetInterfaceTypeValues);
m_interfaceItems.insertBefore(type, notes);
Attributes::addValues(serialInterfaceType, XpressNetSerialInterfaceTypeValues);
Attributes::addEnabled(serialInterfaceType, !online);
Attributes::addVisible(serialInterfaceType, false);
m_interfaceItems.insertBefore(serialInterfaceType, notes);
Attributes::addEnabled(device, !online);
Attributes::addVisible(device, false);
m_interfaceItems.insertBefore(device, notes);
Attributes::addDisplayName(baudrate, DisplayName::Serial::baudrate);
Attributes::addEnabled(baudrate, !online);
Attributes::addVisible(baudrate, false);
m_interfaceItems.insertBefore(baudrate, notes);
Attributes::addDisplayName(flowControl, DisplayName::Serial::flowControl);
Attributes::addEnabled(flowControl, !online);
Attributes::addValues(flowControl, SerialFlowControlValues);
Attributes::addVisible(flowControl, false);
m_interfaceItems.insertBefore(flowControl, notes);
Attributes::addDisplayName(hostname, DisplayName::IP::hostname);
Attributes::addEnabled(hostname, !online);
Attributes::addVisible(hostname, false);
m_interfaceItems.insertBefore(hostname, notes);
Attributes::addDisplayName(port, DisplayName::IP::port);
Attributes::addEnabled(port, !online);
Attributes::addVisible(port, false);
m_interfaceItems.insertBefore(port, notes);
Attributes::addMinMax(s88StartAddress, XpressNet::RoSoftS88XpressNetLI::S88StartAddress::startAddressMin, XpressNet::RoSoftS88XpressNetLI::S88StartAddress::startAddressMax);
Attributes::addEnabled(s88StartAddress, !online);
Attributes::addVisible(s88StartAddress, false);
m_interfaceItems.insertBefore(s88StartAddress, notes);
Attributes::addMinMax(s88ModuleCount, XpressNet::RoSoftS88XpressNetLI::S88ModuleCount::moduleCountMin, XpressNet::RoSoftS88XpressNetLI::S88ModuleCount::moduleCountMax);
Attributes::addEnabled(s88ModuleCount, !online);
Attributes::addVisible(s88ModuleCount, false);
m_interfaceItems.insertBefore(s88ModuleCount, notes);
Attributes::addDisplayName(xpressnet, DisplayName::Hardware::xpressnet);
m_interfaceItems.insertBefore(xpressnet, notes);
m_interfaceItems.insertBefore(decoders, notes);
m_interfaceItems.insertBefore(inputs, notes);
m_interfaceItems.insertBefore(outputs, notes);
updateVisible();
}
std::span<const DecoderProtocol> XpressNetInterface::decoderProtocols() const
{
static constexpr std::array<DecoderProtocol, 2> protocols{DecoderProtocol::DCCShort, DecoderProtocol::DCCLong};
return std::span<const DecoderProtocol>{protocols.data(), protocols.size()};
}
std::pair<uint16_t, uint16_t> XpressNetInterface::decoderAddressMinMax(DecoderProtocol protocol) const
{
switch(protocol)
{
case DecoderProtocol::DCCShort:
return {XpressNet::shortAddressMin, XpressNet::shortAddressMax};
case DecoderProtocol::DCCLong:
return {XpressNet::longAddressMin, XpressNet::longAddressMax};
default: /*[[unlikely]]*/
return DecoderController::decoderAddressMinMax(protocol);
}
}
std::span<const uint8_t> XpressNetInterface::decoderSpeedSteps(DecoderProtocol protocol) const
{
static constexpr std::array<uint8_t, 4> dccSpeedSteps{{14, 27, 28, 128}}; // XpressNet also support 27 steps
switch(protocol)
{
case DecoderProtocol::DCCShort:
case DecoderProtocol::DCCLong:
return dccSpeedSteps;
default: /*[[unlikely]]*/
return DecoderController::decoderSpeedSteps(protocol);
}
}
void XpressNetInterface::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber)
{
if(m_kernel)
m_kernel->decoderChanged(decoder, changes, functionNumber);
}
std::pair<uint32_t, uint32_t> XpressNetInterface::inputAddressMinMax(uint32_t /*channel*/) const
{
return {XpressNet::Kernel::inputAddressMin, XpressNet::Kernel::inputAddressMax};
}
void XpressNetInterface::inputSimulateChange(uint32_t channel, uint32_t address, SimulateInputAction action)
{
if(m_kernel && inRange(address, inputAddressMinMax(channel)))
m_kernel->simulateInputChange(address, action);
}
std::span<const OutputChannel> XpressNetInterface::outputChannels() const
{
static const auto values = makeArray(OutputChannel::Accessory);
return values;
}
std::pair<uint32_t, uint32_t> XpressNetInterface::outputAddressMinMax(OutputChannel /*channel*/) const
{
return {XpressNet::Kernel::accessoryOutputAddressMin, XpressNet::Kernel::accessoryOutputAddressMax};
}
bool XpressNetInterface::setOutputValue(OutputChannel channel, uint32_t address, OutputValue value)
{
assert(isOutputChannel(channel));
return
m_kernel &&
inRange(address, outputAddressMinMax(channel)) &&
m_kernel->setOutput(static_cast<uint16_t>(address), std::get<OutputPairValue>(value));
}
bool XpressNetInterface::setOnline(bool& value, bool simulation)
{
if(!m_kernel && value)
{
try
{
if(simulation)
{
m_kernel = XpressNet::Kernel::create<XpressNet::SimulationIOHandler>(id.value(), xpressnet->config());
}
else
{
switch(type)
{
case XpressNetInterfaceType::Serial:
switch(serialInterfaceType)
{
case XpressNetSerialInterfaceType::LenzLI100:
case XpressNetSerialInterfaceType::LenzLI100F:
case XpressNetSerialInterfaceType::LenzLI101F:
m_kernel = XpressNet::Kernel::create<XpressNet::SerialIOHandler>(id.value(), xpressnet->config(), device.value(), baudrate.value(), flowControl.value());
break;
case XpressNetSerialInterfaceType::RoSoftS88XPressNetLI:
m_kernel = XpressNet::Kernel::create<XpressNet::RoSoftS88XPressNetLIIOHandler>(id.value(), xpressnet->config(), device.value(), baudrate.value(), flowControl.value(), s88StartAddress.value(), s88ModuleCount.value());
break;
case XpressNetSerialInterfaceType::LenzLIUSB:
case XpressNetSerialInterfaceType::DigikeijsDR5000:
m_kernel = XpressNet::Kernel::create<XpressNet::LIUSBIOHandler>(id.value(), xpressnet->config(), device.value(), baudrate.value(), flowControl.value());
break;
}
break;
case XpressNetInterfaceType::Network:
m_kernel = XpressNet::Kernel::create<XpressNet::TCPIOHandler>(id.value(), xpressnet->config(), hostname.value(), port.value());
break;
}
}
if(!m_kernel)
{
assert(false);
return false;
}
setState(InterfaceState::Initializing);
m_kernel->setOnStarted(
[this]()
{
setState(InterfaceState::Online);
});
m_kernel->setOnError(
[this]()
{
setState(InterfaceState::Error);
online = false; // communication no longer possible
});
m_kernel->setOnTrackPowerChanged(
[this](bool powerOn, bool isStopped)
{
if(powerOn)
{
/* NOTE:
* Setting stop and powerOn together is not an atomic operation,
* so it would trigger 2 state changes with in the middle state.
* Fortunately this does not happen because at least one of the state is already set.
* Because if we are in Run state we go to PowerOn,
* and if we are on PowerOff then we go to PowerOn.
*/
// First of all, stop if we have to, otherwhise we might inappropiately run trains
if(isStopped && contains(m_world.state.value(), WorldState::Run))
{
m_world.stop();
}
else if(!contains(m_world.state.value(), WorldState::Run) && !isStopped)
{
m_world.run(); // Run trains yay!
}
// EmergencyStop in XpressNet also means power is still on
if(!contains(m_world.state.value(), WorldState::PowerOn) && isStopped)
{
m_world.powerOn(); // Just power on but keep stopped
}
}
else
{
// Power off regardless of stop state
if(contains(m_world.state.value(), WorldState::PowerOn))
m_world.powerOff();
}
});
m_kernel->setDecoderController(this);
m_kernel->setInputController(this);
m_kernel->setOutputController(this);
m_kernel->start();
m_xpressnetPropertyChanged = xpressnet->propertyChanged.connect(
[this](BaseProperty& /*property*/)
{
m_kernel->setConfig(xpressnet->config());
});
// Avoid to set multiple power states in rapid succession
if(!contains(m_world.state.value(), WorldState::PowerOn))
m_kernel->stopOperations(); // Stop by powering off
else if(!contains(m_world.state.value(), WorldState::Run))
m_kernel->stopAllLocomotives(); // Emergency stop with power on
else
m_kernel->resumeOperations(); // Run trains
Attributes::setEnabled({type, serialInterfaceType, device, baudrate, flowControl, hostname, port, s88StartAddress, s88ModuleCount}, false);
}
catch(const LogMessageException& e)
{
setState(InterfaceState::Offline);
Log::log(*this, e.message(), e.args());
return false;
}
}
else if(m_kernel && !value)
{
Attributes::setEnabled({type, serialInterfaceType, device, baudrate, flowControl, hostname, port, s88StartAddress, s88ModuleCount}, true);
m_xpressnetPropertyChanged.disconnect();
m_kernel->stop();
EventLoop::deleteLater(m_kernel.release());
setState(InterfaceState::Offline);
}
return true;
}
void XpressNetInterface::addToWorld()
{
Interface::addToWorld();
DecoderController::addToWorld();
InputController::addToWorld(inputListColumns);
OutputController::addToWorld(outputListColumns);
}
void XpressNetInterface::loaded()
{
Interface::loaded();
updateVisible();
}
void XpressNetInterface::destroying()
{
OutputController::destroying();
InputController::destroying();
DecoderController::destroying();
Interface::destroying();
}
void XpressNetInterface::worldEvent(WorldState state, WorldEvent event)
{
Interface::worldEvent(state, event);
if(m_kernel)
{
switch(event)
{
case WorldEvent::PowerOff:
{
m_kernel->stopOperations();
break;
}
case WorldEvent::PowerOn:
{
if(contains(state, WorldState::Run))
m_kernel->resumeOperations();
else
m_kernel->stopAllLocomotives(); // In XpressNet E-Stop means power on but not running
break;
}
case WorldEvent::Stop:
{
if(contains(state, WorldState::PowerOn))
{
// In XpressNet E-Stop means power is on but trains are not running
m_kernel->stopAllLocomotives();
}
else
{
// This Stops everything by removing power
m_kernel->stopOperations();
}
break;
}
case WorldEvent::Run:
{
if(contains(state, WorldState::PowerOn))
m_kernel->resumeOperations();
break;
}
default:
break;
}
}
}
void XpressNetInterface::updateVisible()
{
const bool isSerial = (type == XpressNetInterfaceType::Serial);
Attributes::setVisible(serialInterfaceType, isSerial);
Attributes::setVisible(device, isSerial);
Attributes::setVisible(baudrate, isSerial);
Attributes::setVisible(flowControl, isSerial);
const bool isNetwork = (type == XpressNetInterfaceType::Network);
Attributes::setVisible(hostname, isNetwork);
Attributes::setVisible(port, isNetwork);
const bool isRoSoftS88XPressNetLI = isSerial && (serialInterfaceType == XpressNetSerialInterfaceType::RoSoftS88XPressNetLI);
Attributes::setVisible(s88StartAddress, isRoSoftS88XPressNetLI);
Attributes::setVisible(s88ModuleCount, isRoSoftS88XPressNetLI);
}