2016-06-15 23:48:41 +02:00
|
|
|
#include <framework/devicehandlers/AssemblyBase.h>
|
|
|
|
|
|
|
|
AssemblyBase::AssemblyBase(object_id_t objectId, object_id_t parentId,
|
|
|
|
uint16_t commandQueueDepth) :
|
2019-12-20 23:09:35 +01:00
|
|
|
SubsystemBase(objectId, parentId, MODE_OFF, commandQueueDepth),
|
|
|
|
internalState(STATE_NONE), recoveryState(RECOVERY_IDLE),
|
|
|
|
recoveringDevice(childrenMap.end()), targetMode(MODE_OFF),
|
|
|
|
targetSubmode(SUBMODE_NONE) {
|
2016-06-15 23:48:41 +02:00
|
|
|
recoveryOffTimer.setTimeout(POWER_OFF_TIME_MS);
|
|
|
|
}
|
|
|
|
|
|
|
|
AssemblyBase::~AssemblyBase() {
|
|
|
|
}
|
|
|
|
|
|
|
|
ReturnValue_t AssemblyBase::handleCommandMessage(CommandMessage* message) {
|
|
|
|
return handleHealthReply(message);
|
|
|
|
}
|
|
|
|
|
|
|
|
void AssemblyBase::performChildOperation() {
|
|
|
|
if (isInTransition()) {
|
|
|
|
handleChildrenTransition();
|
|
|
|
} else {
|
|
|
|
handleChildrenChanged();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AssemblyBase::startTransition(Mode_t mode, Submode_t submode) {
|
|
|
|
doStartTransition(mode, submode);
|
|
|
|
if (modeHelper.isForced()) {
|
|
|
|
triggerEvent(FORCING_MODE, mode, submode);
|
|
|
|
} else {
|
|
|
|
triggerEvent(CHANGING_MODE, mode, submode);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AssemblyBase::doStartTransition(Mode_t mode, Submode_t submode) {
|
|
|
|
targetMode = mode;
|
|
|
|
targetSubmode = submode;
|
|
|
|
internalState = STATE_SINGLE_STEP;
|
|
|
|
ReturnValue_t result = commandChildren(mode, submode);
|
|
|
|
if (result == NEED_SECOND_STEP) {
|
|
|
|
internalState = STATE_NEED_SECOND_STEP;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool AssemblyBase::isInTransition() {
|
|
|
|
return (internalState != STATE_NONE) || (recoveryState != RECOVERY_IDLE);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool AssemblyBase::handleChildrenChanged() {
|
|
|
|
if (childrenChangedMode) {
|
|
|
|
ReturnValue_t result = checkChildrenState();
|
|
|
|
if (result != RETURN_OK) {
|
|
|
|
handleChildrenLostMode(result);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
return handleChildrenChangedHealth();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AssemblyBase::handleChildrenLostMode(ReturnValue_t result) {
|
|
|
|
triggerEvent(CANT_KEEP_MODE, mode, submode);
|
|
|
|
startTransition(MODE_OFF, SUBMODE_NONE);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool AssemblyBase::handleChildrenChangedHealth() {
|
|
|
|
auto iter = childrenMap.begin();
|
|
|
|
for (; iter != childrenMap.end(); iter++) {
|
|
|
|
if (iter->second.healthChanged) {
|
|
|
|
iter->second.healthChanged = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (iter == childrenMap.end()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
HealthState healthState = healthHelper.healthTable->getHealth(iter->first);
|
|
|
|
if (healthState == HasHealthIF::NEEDS_RECOVERY) {
|
|
|
|
triggerEvent(TRYING_RECOVERY);
|
|
|
|
recoveryState = RECOVERY_STARTED;
|
|
|
|
recoveringDevice = iter;
|
|
|
|
doStartTransition(targetMode, targetSubmode);
|
|
|
|
} else {
|
|
|
|
triggerEvent(CHILD_CHANGED_HEALTH);
|
|
|
|
doStartTransition(mode, submode);
|
|
|
|
}
|
|
|
|
if (modeHelper.isForced()) {
|
|
|
|
triggerEvent(FORCING_MODE, targetMode, targetSubmode);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AssemblyBase::handleChildrenTransition() {
|
|
|
|
if (commandsOutstanding <= 0) {
|
|
|
|
switch (internalState) {
|
|
|
|
case STATE_NEED_SECOND_STEP:
|
|
|
|
internalState = STATE_SECOND_STEP;
|
|
|
|
commandChildren(targetMode, targetSubmode);
|
|
|
|
return;
|
|
|
|
case STATE_OVERWRITE_HEALTH: {
|
|
|
|
internalState = STATE_SINGLE_STEP;
|
|
|
|
ReturnValue_t result = commandChildren(mode, submode);
|
|
|
|
if (result == NEED_SECOND_STEP) {
|
|
|
|
internalState = STATE_NEED_SECOND_STEP;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
case STATE_NONE:
|
|
|
|
//Valid state, used in recovery.
|
|
|
|
case STATE_SINGLE_STEP:
|
|
|
|
case STATE_SECOND_STEP:
|
|
|
|
if (checkAndHandleRecovery()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
ReturnValue_t result = checkChildrenState();
|
|
|
|
if (result == RETURN_OK) {
|
|
|
|
handleModeReached();
|
|
|
|
} else {
|
|
|
|
handleModeTransitionFailed(result);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AssemblyBase::handleModeReached() {
|
|
|
|
internalState = STATE_NONE;
|
|
|
|
setMode(targetMode, targetSubmode);
|
|
|
|
}
|
|
|
|
|
|
|
|
void AssemblyBase::handleModeTransitionFailed(ReturnValue_t result) {
|
|
|
|
//always accept transition to OFF, there is nothing we can do except sending an info event
|
|
|
|
//In theory this should never happen, but we would risk an infinite loop otherwise
|
|
|
|
if (targetMode == MODE_OFF) {
|
|
|
|
triggerEvent(CHILD_PROBLEMS, result);
|
|
|
|
internalState = STATE_NONE;
|
|
|
|
setMode(targetMode, targetSubmode);
|
|
|
|
} else {
|
|
|
|
if (handleChildrenChangedHealth()) {
|
|
|
|
//If any health change is pending, handle that first.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
triggerEvent(MODE_TRANSITION_FAILED, result);
|
|
|
|
startTransition(MODE_OFF, SUBMODE_NONE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AssemblyBase::sendHealthCommand(MessageQueueId_t sendTo,
|
|
|
|
HealthState health) {
|
|
|
|
CommandMessage command;
|
|
|
|
HealthMessage::setHealthMessage(&command, HealthMessage::HEALTH_SET,
|
|
|
|
health);
|
2018-07-12 16:29:32 +02:00
|
|
|
if (commandQueue->sendMessage(sendTo, &command) == RETURN_OK) {
|
2016-06-15 23:48:41 +02:00
|
|
|
commandsOutstanding++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ReturnValue_t AssemblyBase::checkChildrenState() {
|
|
|
|
if (targetMode == MODE_OFF) {
|
|
|
|
return checkChildrenStateOff();
|
|
|
|
} else {
|
|
|
|
return checkChildrenStateOn(targetMode, targetSubmode);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ReturnValue_t AssemblyBase::checkChildrenStateOff() {
|
|
|
|
for (std::map<object_id_t, ChildInfo>::iterator iter = childrenMap.begin();
|
|
|
|
iter != childrenMap.end(); iter++) {
|
|
|
|
if (checkChildOff(iter->first) != RETURN_OK) {
|
|
|
|
return NOT_ENOUGH_CHILDREN_IN_CORRECT_STATE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return RETURN_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
ReturnValue_t AssemblyBase::checkChildOff(uint32_t objectId) {
|
|
|
|
ChildInfo childInfo = childrenMap.find(objectId)->second;
|
|
|
|
if (healthHelper.healthTable->isCommandable(objectId)) {
|
|
|
|
if (childInfo.submode != SUBMODE_NONE) {
|
|
|
|
return RETURN_FAILED;
|
|
|
|
} else {
|
|
|
|
if ((childInfo.mode != MODE_OFF)
|
|
|
|
&& (childInfo.mode != DeviceHandlerIF::MODE_ERROR_ON)) {
|
|
|
|
return RETURN_FAILED;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return RETURN_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
ReturnValue_t AssemblyBase::checkModeCommand(Mode_t mode, Submode_t submode,
|
|
|
|
uint32_t* msToReachTheMode) {
|
|
|
|
|
|
|
|
//always accept transition to OFF
|
|
|
|
if (mode == MODE_OFF) {
|
|
|
|
if (submode != SUBMODE_NONE) {
|
|
|
|
return INVALID_SUBMODE;
|
|
|
|
}
|
|
|
|
return RETURN_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((mode != MODE_ON) && (mode != DeviceHandlerIF::MODE_NORMAL)) {
|
|
|
|
return INVALID_MODE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (internalState != STATE_NONE) {
|
|
|
|
return IN_TRANSITION;
|
|
|
|
}
|
|
|
|
|
|
|
|
return isModeCombinationValid(mode, submode);
|
|
|
|
}
|
|
|
|
|
|
|
|
ReturnValue_t AssemblyBase::handleHealthReply(CommandMessage* message) {
|
|
|
|
if (message->getCommand() == HealthMessage::HEALTH_INFO) {
|
|
|
|
HealthState health = HealthMessage::getHealth(message);
|
|
|
|
if (health != EXTERNAL_CONTROL) {
|
|
|
|
updateChildChangedHealth(message->getSender(), true);
|
|
|
|
}
|
|
|
|
return RETURN_OK;
|
|
|
|
}
|
|
|
|
if (message->getCommand() == HealthMessage::REPLY_HEALTH_SET
|
|
|
|
|| (message->getCommand() == CommandMessage::REPLY_REJECTED
|
|
|
|
&& message->getParameter2() == HealthMessage::HEALTH_SET)) {
|
|
|
|
if (isInTransition()) {
|
|
|
|
commandsOutstanding--;
|
|
|
|
}
|
|
|
|
return RETURN_OK;
|
|
|
|
}
|
|
|
|
return RETURN_FAILED;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool AssemblyBase::checkAndHandleRecovery() {
|
|
|
|
switch (recoveryState) {
|
|
|
|
case RECOVERY_STARTED:
|
|
|
|
recoveryState = RECOVERY_WAIT;
|
|
|
|
recoveryOffTimer.resetTimer();
|
|
|
|
return true;
|
|
|
|
case RECOVERY_WAIT:
|
|
|
|
if (recoveryOffTimer.isBusy()) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
triggerEvent(RECOVERY_STEP, 0);
|
|
|
|
sendHealthCommand(recoveringDevice->second.commandQueue, HEALTHY);
|
|
|
|
internalState = STATE_NONE;
|
|
|
|
recoveryState = RECOVERY_ONGOING;
|
|
|
|
//Don't check state!
|
|
|
|
return true;
|
|
|
|
case RECOVERY_ONGOING:
|
|
|
|
triggerEvent(RECOVERY_STEP, 1);
|
|
|
|
recoveryState = RECOVERY_ONGOING_2;
|
|
|
|
recoveringDevice->second.healthChanged = false;
|
|
|
|
//Device should be healthy again, so restart a transition.
|
|
|
|
//Might be including second step, but that's already handled.
|
|
|
|
doStartTransition(targetMode, targetSubmode);
|
|
|
|
return true;
|
|
|
|
case RECOVERY_ONGOING_2:
|
|
|
|
triggerEvent(RECOVERY_DONE);
|
|
|
|
//Now we're through, but not sure if it was successful.
|
|
|
|
recoveryState = RECOVERY_IDLE;
|
|
|
|
return false;
|
|
|
|
case RECOVERY_IDLE:
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AssemblyBase::overwriteDeviceHealth(object_id_t objectId,
|
|
|
|
HasHealthIF::HealthState oldHealth) {
|
|
|
|
triggerEvent(OVERWRITING_HEALTH, objectId, oldHealth);
|
|
|
|
internalState = STATE_OVERWRITE_HEALTH;
|
|
|
|
modeHelper.setForced(true);
|
|
|
|
sendHealthCommand(childrenMap[objectId].commandQueue, EXTERNAL_CONTROL);
|
|
|
|
}
|