Merge pull request #169 from gfgit/work/gfgit/xpressnet_wip
XpressNet WIP
Dieser Commit ist enthalten in:
Commit
7c6a655ea9
@ -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;
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -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]()
|
||||
{
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
Laden…
x
In neuem Issue referenzieren
Einen Benutzer sperren