XpressNet: fix power state logic

Dieser Commit ist enthalten in:
Filippo Gentile 2024-09-17 14:08:03 +02:00
Ursprung 395a62281d
Commit 71ccb413b0
3 geänderte Dateien mit 159 neuen und 114 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

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

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);
}
/**