2 * Copyright (c) 2010-2024 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.insteon.internal.device;
15 import org.eclipse.jdt.annotation.NonNullByDefault;
16 import org.openhab.binding.insteon.internal.utils.Utils;
17 import org.slf4j.Logger;
18 import org.slf4j.LoggerFactory;
21 * Ideally, Insteon ALL LINK messages are received in this order, and
22 * only a single one of each:
24 * BCAST (a broadcast message from the device to all group members)
25 * CLEAN (a cleanup point-to-point message to ensure more reliable transmission)
26 * SUCCESS (a broadcast report of success or failure of cleanup, with cmd1=0x06)
28 * But often, the BCAST, CLEAN and SUCCESS messages are retransmitted multiple times,
29 * or (less frequently) messages are lost. The present state machine was developed
30 * to remove duplicates, yet make sure that a single lost message does not cause
31 * the binding to miss an update.
37 * SUCCESS / / \ \ [BCAST]
38 * / /['CLEAN'] 'SUCCESS'\ \
41 * "CLEAN" EXPECT_SUCCESS <-------------- EXPECT_CLEAN "BCAST"
46 * How to read this diagram:
48 * Regular, expected, non-duplicate messages do not have any quotes around them,
49 * and lead to the obvious state transitions.
51 * The actions in [square brackets] are transitions that cause a state
52 * update to be published when they occur.
54 * The presence of double quotes indicates a duplicate that does not lead
55 * to any state transitions, i.e. it is simply ignored.
57 * Single quotes indicate a message that is the result of a single dropped
58 * message, and leads to a state transition, in some cases even to a state
59 * update to be published.
61 * For instance at the top of the diagram, if a "SUCCESS" message is received
62 * when in state EXPECT_BCAST, it is considered a duplicate (it has "").
64 * When in state EXPECT_SUCCESS though, receiving a ['BCAST'] is most likely because
65 * the SUCCESS message was missed, and therefore it is considered the result
66 * of a single lost message (has '' around it). The state changes to EXPECT_CLEAN,
67 * and the message should lead to publishing of a state update (it has [] around it).
69 * @author Bernd Pfrommer - Initial contribution
70 * @author Rob Nielsen - Port to openHAB 2 insteon binding
73 public class GroupMessageStateMachine {
74 private final Logger logger = LoggerFactory.getLogger(GroupMessageStateMachine.class);
77 * The different kinds of Insteon ALL Link (Group) messages that can be received.
78 * Here is a typical sequence:
80 * IN:Cmd:0x50|fromAddress:20.AC.99|toAddress:00.00.01|messageFlags:0xCB=ALL_LINK_BROADCAST:3:2|command1:0x13|
83 * IN:Cmd:0x50|fromAddress:20.AC.99|toAddress:23.9B.65|messageFlags:0x41=ALL_LINK_CLEANUP:1:0|command1:0x13|command2
86 * IN:Cmd:0x50|fromAddress:20.AC.99|toAddress:13.03.01|messageFlags:0xCB=ALL_LINK_BROADCAST:3:2|command1:0x06|
96 * The state of the machine (i.e. what message we are expecting next).
97 * The usual state should be EXPECT_BCAST
105 private State state = State.EXPECT_BCAST;
106 private long lastUpdated = 0;
107 private boolean publish = false;
108 private byte lastCmd1 = 0;
111 * Advance the state machine and determine if update is genuine (no duplicate)
113 * @param a the group message (action) that was received
114 * @param address the address of the device that this state machine belongs to
115 * @param group the group that this state machine belongs to
116 * @param cmd1 cmd1 from the message received
117 * @return true if the group message is not a duplicate
119 public boolean action(GroupMessage a, InsteonAddress address, int group, byte cmd1) {
121 long currentTime = System.currentTimeMillis();
127 break; // missed() move state machine and pub!
130 break; // missed(BCAST)
134 } // missed(BCAST,CLEAN) or dup SUCCESS
139 if (lastCmd1 == cmd1) {
140 if (currentTime > lastUpdated + 30000) {
141 if (logger.isDebugEnabled()) {
143 "{} group {} cmd1 {} is not a dup BCAST, received last message over 30000 ms ago",
144 address, group, Utils.getHexByte(cmd1));
151 if (logger.isDebugEnabled()) {
152 logger.debug("{} group {} cmd1 {} is not a dup BCAST, last cmd1 {}", address, group,
153 Utils.getHexByte(cmd1), Utils.getHexByte(lastCmd1));
157 break; // missed(CLEAN, SUCCESS) or dup BCAST
160 break; // missed() move state machine, no pub
170 break; // missed(SUCCESS)
173 break; // missed(SUCCESS,BCAST) or dup CLEAN
177 } // missed(), move state machine, no pub
180 State oldState = state;
183 state = State.EXPECT_CLEAN;
186 state = State.EXPECT_SUCCESS;
189 state = State.EXPECT_BCAST;
194 lastUpdated = currentTime;
195 logger.debug("{} group {} state: {} --{}--> {}, publish: {}", address, group, oldState, a, state, publish);
199 public long getLastUpdated() {
203 public boolean getPublish() {