Merge pull request #169 from gfgit/work/gfgit/xpressnet_wip

XpressNet WIP
Dieser Commit ist enthalten in:
Reinder Feenstra 2025-04-01 22:29:02 +02:00 committet von GitHub
Commit 7c6a655ea9
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: B5690EEEBB952194
6 geänderte Dateien mit 341 neuen und 177 gelöschten Zeilen

Datei anzeigen

@ -290,27 +290,41 @@ bool XpressNetInterface::setOnline(bool& value, bool simulation)
setState(InterfaceState::Error);
online = false; // communication no longer possible
});
m_kernel->setOnNormalOperationResumed(
[this]()
m_kernel->setOnTrackPowerChanged(
[this](bool powerOn, bool isStopped)
{
if(!contains(m_world.state.value(), WorldState::PowerOn))
m_world.powerOn();
if(!contains(m_world.state.value(), WorldState::Run))
m_world.run();
});
m_kernel->setOnTrackPowerOff(
[this]()
{
if(contains(m_world.state.value(), WorldState::PowerOn))
m_world.powerOff();
if(contains(m_world.state.value(), WorldState::Run))
m_world.stop();
});
m_kernel->setOnEmergencyStop(
[this]()
{
if(contains(m_world.state.value(), WorldState::Run))
m_world.stop();
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);
@ -324,12 +338,13 @@ bool XpressNetInterface::setOnline(bool& value, bool simulation)
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();
m_kernel->stopOperations(); // Stop by powering off
else if(!contains(m_world.state.value(), WorldState::Run))
m_kernel->stopAllLocomotives();
m_kernel->stopAllLocomotives(); // Emergency stop with power on
else
m_kernel->resumeOperations();
m_kernel->resumeOperations(); // Run trains
Attributes::setEnabled({type, serialInterfaceType, device, baudrate, flowControl, hostname, port, s88StartAddress, s88ModuleCount}, false);
}
@ -386,24 +401,38 @@ void XpressNetInterface::worldEvent(WorldState state, WorldEvent event)
switch(event)
{
case WorldEvent::PowerOff:
{
m_kernel->stopOperations();
break;
}
case WorldEvent::PowerOn:
m_kernel->resumeOperations();
if(!contains(state, WorldState::Run))
m_kernel->stopAllLocomotives();
{
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:
m_kernel->stopAllLocomotives();
{
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;
}

Datei anzeigen

@ -215,35 +215,35 @@ bool Z21Interface::setOnline(bool& value, bool simulation)
{
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.
*/
/* 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!
}
// 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 Z21 also means power is still on
if(!contains(m_world.state.value(), WorldState::PowerOn) && isStopped)
{
m_world.powerOn(); // Just power on but keep stopped
}
// EmergencyStop in Z21 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();
// Power off regardless of stop state
if(contains(m_world.state.value(), WorldState::PowerOn))
m_world.powerOff();
}
});

Datei anzeigen

@ -181,84 +181,99 @@ void Kernel::receive(const Message& message)
break;
}
case 0x60:
if(message == NormalOperationResumed())
{
if(message == TrackPowerOff())
{
if(m_trackPowerOn != TriState::True || m_emergencyStop != TriState::False)
{
m_trackPowerOn = TriState::True;
m_emergencyStop = TriState::False;
if(m_onNormalOperationResumed)
EventLoop::call(
[this]()
{
m_onNormalOperationResumed();
});
}
EventLoop::call(
[this]()
{
if(m_trackPowerOn != TriState::False)
{
m_trackPowerOn = TriState::False;
m_emergencyStop = TriState::False;
if(m_onTrackPowerChanged)
m_onTrackPowerChanged(false, false);
}
});
}
else if(message == TrackPowerOff())
else if(message == NormalOperationResumed())
{
if(m_trackPowerOn != TriState::False)
{
m_trackPowerOn = TriState::False;
if(m_onTrackPowerOff)
EventLoop::call(
[this]()
{
m_onTrackPowerOff();
});
EventLoop::call(
[this]()
{
if(m_trackPowerOn != TriState::True || m_emergencyStop != TriState::False)
{
m_trackPowerOn = TriState::True;
m_emergencyStop = TriState::False;
if(m_onTrackPowerChanged)
m_onTrackPowerChanged(true, false);
}
});
}
}
break;
}
case 0x80:
{
if(message == EmergencyStop())
{
if(m_emergencyStop != TriState::True)
{
m_emergencyStop = TriState::True;
EventLoop::call(
[this]()
{
if(m_emergencyStop != TriState::True)
{
m_emergencyStop = TriState::True;
m_trackPowerOn = TriState::True;
if(m_onEmergencyStop)
EventLoop::call(
[this]()
{
m_onEmergencyStop();
});
}
if(m_onTrackPowerChanged)
m_onTrackPowerChanged(true, true);
}
});
}
break;
}
}
}
void Kernel::resumeOperations()
{
m_ioContext.post(
[this]()
{
if(m_trackPowerOn != TriState::True || m_emergencyStop != TriState::False)
assert(isEventLoopThread());
if(m_trackPowerOn != TriState::True || m_emergencyStop != TriState::False)
{
m_ioContext.post(
[this]()
{
send(ResumeOperationsRequest());
});
});
}
}
void Kernel::stopOperations()
{
m_ioContext.post(
[this]()
{
if(m_trackPowerOn != TriState::False)
assert(isEventLoopThread());
if(m_trackPowerOn != TriState::False || m_emergencyStop != TriState::False)
{
m_ioContext.post(
[this]()
{
send(StopOperationsRequest());
});
});
}
}
void Kernel::stopAllLocomotives()
{
m_ioContext.post(
[this]()
{
if(m_emergencyStop != TriState::True)
assert(isEventLoopThread());
if(m_trackPowerOn != TriState::True || m_emergencyStop != TriState::True)
{
m_ioContext.post(
[this]()
{
send(StopAllLocomotivesRequest());
});
});
}
}
void Kernel::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber)
@ -388,7 +403,7 @@ bool Kernel::setOutput(uint16_t address, OutputPairValue value)
{
assert(isEventLoopThread());
assert(address >= accessoryOutputAddressMin && address <= accessoryOutputAddressMax);
assert(value == OutputPairValue::First || value == OutputPairValue::First);
assert(value == OutputPairValue::First || value == OutputPairValue::Second);
m_ioContext.post(
[this, address, value]()
{

Datei anzeigen

@ -54,11 +54,37 @@ class Kernel : public ::KernelBase
std::unique_ptr<IOHandler> m_ioHandler;
const bool m_simulation;
TriState m_trackPowerOn;
TriState m_emergencyStop;
std::function<void()> m_onNormalOperationResumed;
std::function<void()> m_onTrackPowerOff;
std::function<void()> m_onEmergencyStop;
/*!
* \brief m_trackPowerOn caches command station track power state.
*
* \note It must be accessed only from event loop thread or from
* XpressNet::Kernel::start().
*
* \sa EventLoop
*/
TriState m_trackPowerOn = TriState::Undefined;
/*!
* \brief m_emergencyStop caches command station emergency stop state.
*
* \note It must be accessed only from event loop thread or from
* XpressNet::Kernel::start().
*
* \sa EventLoop
*/
TriState m_emergencyStop = TriState::Undefined;
/*!
* \brief m_onTrackPowerChanged callback is called when XpressNet power state changes.
*
* \note It is always called from event loop thread
* \note First argument is powerOn, second argument is isStopped
* In XpressNet EmergencyStop is really PowerOn + EmergencyStop and
* PowerOn implicitly means Run so we cannot call \sa trackPowerOn() if world must be stopped
*
* \sa EventLoop
*/
std::function<void(bool, bool)> m_onTrackPowerChanged;
DecoderController* m_decoderController;
@ -135,38 +161,13 @@ class Kernel : public ::KernelBase
/**
* @brief ...
*
* @param[in] callback ...
* @note This function may not be called when the kernel is running.
*/
inline void setOnNormalOperationResumed(std::function<void()> callback)
inline void setOnTrackPowerChanged(std::function<void(bool, bool)> callback)
{
assert(!m_started);
m_onNormalOperationResumed = std::move(callback);
}
/**
* @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 setOnEmergencyStop(std::function<void()> callback)
{
assert(!m_started);
m_onEmergencyStop = std::move(callback);
m_onTrackPowerChanged = std::move(callback);
}
/**

Datei anzeigen

@ -44,13 +44,61 @@ bool isChecksumValid(const Message& msg, const int dataSize)
return calcChecksum(msg, dataSize) == *(reinterpret_cast<const uint8_t*>(&msg) + dataSize + 1);
}
std::string toString(const Message& message)
std::string toString(const Message& message, bool raw)
{
std::string s;
std::string s = toHex(message.identification());
// Human readable:
switch(message.header)
{
case 0x21:
{
if(message == ResumeOperationsRequest())
{
s = "RESUME_OPERATIONS_REQUEST";
}
else if(message == StopOperationsRequest())
{
s = "STOP_OPERATIONS_REQUEST";
}
else
raw = true;
break;
}
case 0x61:
{
if(message == NormalOperationResumed())
{
s = "NORMAL_OPERATIONS_RESUMED";
}
else if(message == TrackPowerOff())
{
s = "TRACK_POWER_OFF";
}
else
raw = true;
break;
}
case 0x80:
{
if(message == StopAllLocomotivesRequest())
{
s = "STOP_ALL_LOCO_REQUEST";
}
else
raw = true;
break;
}
case 0x81:
{
if(message == EmergencyStop())
{
s = "EMERGENCY_STOP";
}
else
raw = true;
break;
}
case 0x52:
{
const auto& req = static_cast<const AccessoryDecoderOperationRequest&>(message);
@ -60,13 +108,21 @@ std::string toString(const Message& message)
s.append(req.activate() ? " activate" : " deactivate");
break;
}
default:
{
raw = true;
break;
}
// FIXME: add all messages
}
// Raw data:
s.append(" [");
s.append(toHex(reinterpret_cast<const uint8_t*>(&message), message.size(), true));
s.append("]");
if(raw)
{
// Raw data:
s.append(" [");
s.append(toHex(reinterpret_cast<const uint8_t*>(&message), message.size(), true));
s.append("]");
}
return s;
}

Datei anzeigen

@ -27,7 +27,10 @@
#include <cassert>
#include <cstring>
#include <string>
#include "../../../enum/direction.hpp"
#include <traintastic/enum/direction.hpp>
#include "../../../utils/packed.hpp"
#include "../../../utils/endian.hpp"
#include "../../../utils/byte.hpp"
namespace XpressNet {
@ -37,6 +40,7 @@ constexpr uint16_t longAddressMin = 100;
constexpr uint16_t longAddressMax = 9999;
constexpr uint8_t idFeedbackBroadcast = 0x40;
constexpr uint8_t idLocomotiveBusy = 0xE4;
struct Message;
@ -48,8 +52,10 @@ void updateChecksum(Message& msg);
inline bool isChecksumValid(const Message& msg);
bool isChecksumValid(const Message& msg, const int dataSize);
std::string toString(const Message& message);
std::string toString(const Message& message, bool raw = true);
// Chapters are based on:
// Lenz Dokumentation XpressNet Version 4.0 02/2022
struct Message
{
uint8_t header;
@ -77,9 +83,10 @@ struct Message
{
return 2 + dataSize();
}
};
} ATTRIBUTE_PACKED;
static_assert(sizeof(Message) == 1);
// 2.4.1
struct NormalOperationResumed : Message
{
uint8_t db1 = 0x01;
@ -89,9 +96,10 @@ struct NormalOperationResumed : Message
{
header = 0x61;
}
};
} ATTRIBUTE_PACKED;
static_assert(sizeof(NormalOperationResumed) == 3);
// 2.4.2
struct TrackPowerOff : Message
{
uint8_t db1 = 0x00;
@ -101,9 +109,10 @@ struct TrackPowerOff : Message
{
header = 0x61;
}
};
} ATTRIBUTE_PACKED;
static_assert(sizeof(TrackPowerOff) == 3);
// 2.4.3
struct EmergencyStop : Message
{
uint8_t db1 = 0x00;
@ -113,9 +122,35 @@ struct EmergencyStop : Message
{
header = 0x81;
}
};
} ATTRIBUTE_PACKED;
static_assert(sizeof(EmergencyStop) == 3);
// 2.13
struct CommandStationBusy : Message
{
uint8_t db1 = 0x81;
uint8_t checksum = 0xE0;
CommandStationBusy()
{
header = 0x61;
}
} ATTRIBUTE_PACKED;
static_assert(sizeof(CommandStationBusy) == 3);
// 2.14
struct CommandUnknown : Message
{
uint8_t db1 = 0x82;
uint8_t checksum = 0xE3;
CommandUnknown()
{
header = 0x61;
}
} ATTRIBUTE_PACKED;
static_assert(sizeof(CommandUnknown) == 3);
struct FeedbackBroadcast : Message
{
struct Pair
@ -180,7 +215,7 @@ struct FeedbackBroadcast : Message
else
data &= ~static_cast<uint8_t>(1 << index);
}
};
} ATTRIBUTE_PACKED;
static_assert(sizeof(Pair) == 2);
constexpr uint8_t pairCount() const
@ -205,8 +240,9 @@ struct FeedbackBroadcast : Message
assert(index < pairCount());
return *(reinterpret_cast<Pair*>(&header + sizeof(header)) + index);
}
};
} ATTRIBUTE_PACKED;
// 3.2
struct ResumeOperationsRequest : Message
{
uint8_t db1 = 0x81;
@ -216,9 +252,10 @@ struct ResumeOperationsRequest : Message
{
header = 0x21;
}
};
} ATTRIBUTE_PACKED;
static_assert(sizeof(ResumeOperationsRequest) == 3);
// 3.3
struct StopOperationsRequest : Message
{
uint8_t db1 = 0x80;
@ -228,9 +265,10 @@ struct StopOperationsRequest : Message
{
header = 0x21;
}
};
} ATTRIBUTE_PACKED;
static_assert(sizeof(StopOperationsRequest) == 3);
// 3.4
struct StopAllLocomotivesRequest : Message
{
uint8_t checksum = 0x80;
@ -239,9 +277,10 @@ struct StopAllLocomotivesRequest : Message
{
header = 0x80;
}
};
} ATTRIBUTE_PACKED;
static_assert(sizeof(StopAllLocomotivesRequest) == 2);
// 3.7 Emergency stop locomotive (from Central version 3.0)
struct EmergencyStopLocomotive : Message
{
uint8_t addressHigh;
@ -264,7 +303,7 @@ struct EmergencyStopLocomotive : Message
addressLow = address & 0x7f;
}
}
};
} ATTRIBUTE_PACKED;
static_assert(sizeof(EmergencyStopLocomotive) == 4);
struct LocomotiveInstruction : Message
@ -279,17 +318,27 @@ struct LocomotiveInstruction : Message
if(address >= longAddressMin)
{
assert(address >= longAddressMin && address <= longAddressMax);
addressHigh = 0xc0 | address >> 8;
addressLow = address & 0xff;
addressHigh = 0xC0 | address >> 8;
addressLow = address & 0xFF;
}
else
{
assert(address >= shortAddressMin && address <= shortAddressMax);
addressHigh = 0x00;
addressLow = address & 0x7f;
addressLow = address & 0x7F;
}
}
};
inline uint16_t address() const
{
return (static_cast<uint16_t>(addressHigh & 0x3F) << 8) | addressLow;
}
inline bool isLongAddress() const
{
return (addressHigh & 0xC0) == 0xC0;
}
} ATTRIBUTE_PACKED;
static_assert(sizeof(LocomotiveInstruction) == 4);
struct SpeedAndDirectionInstruction : LocomotiveInstruction
@ -305,7 +354,7 @@ struct SpeedAndDirectionInstruction : LocomotiveInstruction
if(direction == Direction::Forward)
speedAndDirection |= 0x80;
}
};
} ATTRIBUTE_PACKED;
struct SpeedAndDirectionInstruction14 : SpeedAndDirectionInstruction
{
@ -320,7 +369,7 @@ struct SpeedAndDirectionInstruction14 : SpeedAndDirectionInstruction
speedAndDirection |= 0x10;
checksum = calcChecksum(*this);
}
};
} ATTRIBUTE_PACKED;
struct SpeedAndDirectionInstruction27 : SpeedAndDirectionInstruction
{
@ -333,7 +382,7 @@ struct SpeedAndDirectionInstruction27 : SpeedAndDirectionInstruction
speedAndDirection |= (((speedStep + 1) & 0x01) << 4) | ((speedStep + 1) >> 1);
checksum = calcChecksum(*this);
}
};
} ATTRIBUTE_PACKED;
struct SpeedAndDirectionInstruction28 : SpeedAndDirectionInstruction
{
@ -346,7 +395,7 @@ struct SpeedAndDirectionInstruction28 : SpeedAndDirectionInstruction
speedAndDirection |= (((speedStep + 1) & 0x01) << 4) | ((speedStep + 1) >> 1);
checksum = calcChecksum(*this);
}
};
} ATTRIBUTE_PACKED;
struct SpeedAndDirectionInstruction128 : SpeedAndDirectionInstruction
{
@ -359,7 +408,7 @@ struct SpeedAndDirectionInstruction128 : SpeedAndDirectionInstruction
speedAndDirection |= speedStep + 1;
checksum = calcChecksum(*this);
}
};
} ATTRIBUTE_PACKED;
struct FunctionInstructionGroup : LocomotiveInstruction
{
@ -372,7 +421,7 @@ struct FunctionInstructionGroup : LocomotiveInstruction
assert(group >= 1 && group <= 5);
identification = (group == 5) ? 0x28 : (0x1F + group);
}
};
} ATTRIBUTE_PACKED;
struct FunctionInstructionGroup1 : FunctionInstructionGroup
{
@ -392,7 +441,7 @@ struct FunctionInstructionGroup1 : FunctionInstructionGroup
checksum = calcChecksum(*this);
}
};
} ATTRIBUTE_PACKED;
struct FunctionInstructionGroup2 : FunctionInstructionGroup
{
@ -410,7 +459,7 @@ struct FunctionInstructionGroup2 : FunctionInstructionGroup
checksum = calcChecksum(*this);
}
};
} ATTRIBUTE_PACKED;
struct FunctionInstructionGroup3 : FunctionInstructionGroup
{
@ -428,7 +477,7 @@ struct FunctionInstructionGroup3 : FunctionInstructionGroup
checksum = calcChecksum(*this);
}
};
} ATTRIBUTE_PACKED;
struct FunctionInstructionGroup4 : FunctionInstructionGroup
{
@ -454,7 +503,7 @@ struct FunctionInstructionGroup4 : FunctionInstructionGroup
checksum = calcChecksum(*this);
}
};
} ATTRIBUTE_PACKED;
struct FunctionInstructionGroup5 : FunctionInstructionGroup
{
@ -480,7 +529,7 @@ struct FunctionInstructionGroup5 : FunctionInstructionGroup
checksum = calcChecksum(*this);
}
};
} ATTRIBUTE_PACKED;
/*
struct setFunctionStateGroup : LocomotiveInstruction
@ -495,9 +544,23 @@ struct setFunctionStateGroup : LocomotiveInstruction
identification = 0x23 + group;
addressLowHigh(address, addressLow, addressHigh);
}
} __attribute__((packed));
} ATTRIBUTE_PACKED;
*/
// 2.19.7 Locomotive is Occupied (from Central version 3.0)
struct LocomotiveBusy : LocomotiveInstruction
{
uint8_t checksum;
LocomotiveBusy(uint16_t address)
: LocomotiveInstruction(address)
{
identification = idLocomotiveBusy;
checksum = calcChecksum(*this);
}
} ATTRIBUTE_PACKED;
static_assert(sizeof(LocomotiveBusy) == 5);
struct AccessoryDecoderOperationRequest : Message
{
static constexpr uint8_t db2Port = 0x01;
@ -539,7 +602,7 @@ struct AccessoryDecoderOperationRequest : Message
{
return db2 & db2Activate;
}
};
} ATTRIBUTE_PACKED;
static_assert(sizeof(AccessoryDecoderOperationRequest) == 4);
namespace RocoMultiMAUS
@ -573,7 +636,7 @@ namespace RocoMultiMAUS
checksum = calcChecksum(*this);
}
};
} ATTRIBUTE_PACKED;
}
namespace RoSoftS88XpressNetLI
@ -597,7 +660,7 @@ namespace RoSoftS88XpressNetLI
assert((startAddress >= startAddressMin && startAddress <= startAddressMax) || startAddress == startAddressGet);
checksum = calcChecksum(*this);
}
};
} ATTRIBUTE_PACKED;
struct S88ModuleCount : Message
{
@ -618,7 +681,7 @@ namespace RoSoftS88XpressNetLI
assert((moduleCount >= moduleCountMin && moduleCount <= moduleCountMax) || moduleCount == moduleCountGet);
checksum = calcChecksum(*this);
}
};
} ATTRIBUTE_PACKED;
}
inline uint8_t calcChecksum(const Message& msg)