/**
* Copyright (c) 2010-2016 by the respective copyright holders.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.openhab.binding.insteonplm.internal.device;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Ideally, Insteon ALL LINK messages are received in this order, and
* only a single one of each:
*
* BCAST (a broadcast message from the device to all group members)
* CLEAN (a cleanup point-to-point message to ensure more reliable transmission)
* SUCCESS (a broadcast report of success or failure of cleanup, with cmd1=0x06)
*
* But often, the BCAST, CLEAN and SUCCESS messages are retransmitted multiple times,
* or (less frequently) messages are lost. The present state machine was developed
* to remove duplicates, yet make sure that a single lost message does not cause
* the binding to miss an update.
*
*
* "SUCCESS"
* EXPECT_BCAST
* ^ / ^ \
* SUCCESS / / \ \ [BCAST]
* / /['CLEAN'] 'SUCCESS'\ \
* / / \ \
* / V CLEAN \ V
* "CLEAN" EXPECT_SUCCESS <-------------- EXPECT_CLEAN "BCAST"
* -------------->
* ['BCAST']
*
* How to read this diagram:
*
* Regular, expected, non-duplicate messages do not have any quotes around them,
* and lead to the obvious state transitions.
*
* The actions in [square brackets] are transitions that cause a state
* update to be published when they occur.
*
* The presence of double quotes indicates a duplicate that does not lead
* to any state transitions, i.e. it is simply ignored.
*
* Single quotes indicate a message that is the result of a single dropped
* message, and leads to a state transition, in some cases even to a state
* update to be published.
*
* For instance at the top of the diagram, if a "SUCCESS" message is received
* when in state EXPECT_BCAST, it is considered a duplicate (it has "").
*
* When in state EXPECT_SUCCESS though, receiving a ['BCAST'] is most likely because
* the SUCCESS message was missed, and therefore it is considered the result
* of a single lost message (has '' around it). The state changes to EXPECT_CLEAN,
* and the message should lead to publishing of a state update (it has [] around it).
*
* @author Bernd Pfrommer
* @since 1.7.0
*/
public class GroupMessageStateMachine {
private static final Logger logger = LoggerFactory.getLogger(GroupMessageStateMachine.class);
/**
* The different kinds of Insteon ALL Link (Group) messages that can be received.
* Here is a typical sequence:
* BCAST:
* IN:Cmd:0x50|fromAddress:20.AC.99|toAddress:00.00.01|messageFlags:0xCB=ALL_LINK_BROADCAST:3:2|command1:0x13|
* command2:0x00|
* CLEAN:
* IN:Cmd:0x50|fromAddress:20.AC.99|toAddress:23.9B.65|messageFlags:0x41=ALL_LINK_CLEANUP:1:0|command1:0x13|command2
* :0x01|
* SUCCESS:
* IN:Cmd:0x50|fromAddress:20.AC.99|toAddress:13.03.01|messageFlags:0xCB=ALL_LINK_BROADCAST:3:2|command1:0x06|
* command2:0x00|
*/
enum GroupMessage {
BCAST,
CLEAN,
SUCCESS;
};
/**
* The state of the machine (i.e. what message we are expecting next).
* The usual state should be EXPECT_BCAST
*/
enum State {
EXPECT_BCAST,
EXPECT_CLEAN,
EXPECT_SUCCESS
};
State m_state = State.EXPECT_BCAST;
int m_lastHops = 0;
/**
* Advance the state machine and determine if update is genuine (no duplicate)
*
* @param a the group message (action) that was received
* @param hops number of hops that was given on the message. Currently not used.
* @return true if the group message is not a duplicate
*/
public boolean action(GroupMessage a, int hops) {
boolean publish = false;
switch (m_state) {
case EXPECT_BCAST:
switch (a) {
case BCAST:
publish = true;
break; // missed() move state machine and pub!
case CLEAN:
publish = true;
break; // missed(BCAST)
case SUCCESS:
publish = false;
break;
} // missed(BCAST,CLEAN) or dup SUCCESS
break;
case EXPECT_CLEAN:
switch (a) {
case BCAST:
publish = false;
break; // missed(CLEAN, SUCCESS) or dup BCAST
case CLEAN:
publish = false;
break; // missed() move state machine, no pub
case SUCCESS:
publish = false;
break;
} // missed(CLEAN)
break;
case EXPECT_SUCCESS:
switch (a) {
case BCAST:
publish = true;
break; // missed(SUCCESS)
case CLEAN:
publish = false;
break; // missed(SUCCESS,BCAST) or dup CLEAN
case SUCCESS:
publish = false;
break;
} // missed(), move state machine, no pub
break;
}
State oldState = m_state;
switch (a) {
case BCAST:
m_state = State.EXPECT_CLEAN;
break;
case CLEAN:
m_state = State.EXPECT_SUCCESS;
break;
case SUCCESS:
m_state = State.EXPECT_BCAST;
break;
}
logger.trace("group state: {} --{}--> {}, publish: {}", oldState, a, m_state, publish);
return (publish);
}
}