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.transport.message;
15 import org.eclipse.jdt.annotation.NonNullByDefault;
16 import org.openhab.binding.insteon.internal.device.InsteonAddress;
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 types 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
71 * @author Jeremy Setton - Rewrite insteon binding
74 public class GroupMessageStateMachine {
75 private static final int GROUP_STATE_TIMEOUT = 10000; // in milliseconds
78 * The different kinds of Insteon ALL Link (Group) messages that can be received.
79 * Here is a typical sequence:
81 * IN:Cmd:0x50|fromAddress:20.AC.99|toAddress:00.00.01|messageFlags:0xCB=ALL_LINK_BROADCAST:3:2|command1:0x13|
84 * IN:Cmd:0x50|fromAddress:20.AC.99|toAddress:23.9B.65|messageFlags:0x41=ALL_LINK_CLEANUP:1:0|command1:0x13|command2
87 * IN:Cmd:0x50|fromAddress:20.AC.99|toAddress:13.03.01|messageFlags:0xCB=ALL_LINK_BROADCAST:3:2|command1:0x06|
90 public static enum GroupMessageType {
97 * The state of the machine (i.e. what message we are expecting next).
98 * The usual state should be EXPECT_BCAST
100 private static enum State {
106 private final Logger logger = LoggerFactory.getLogger(GroupMessageStateMachine.class);
108 private State state = State.EXPECT_BCAST;
109 private boolean duplicate = false;
110 private byte lastCmd1 = 0;
111 private long lastTimestamp = 0;
113 public boolean isDuplicate() {
117 public byte getLastCommand() {
121 public long getLastTimestamp() {
122 return lastTimestamp;
126 * Updates the state machine and determine if not duplicate
128 * @param address the address of the device that this state machine belongs to
129 * @param group the group that this state machine belongs to
130 * @param cmd1 cmd1 from the message received
131 * @param timestamp timestamp from the message received
132 * @param type the group message type that was received
133 * @return true if the group message is duplicate
135 public boolean update(InsteonAddress address, int group, byte cmd1, long timestamp, GroupMessageType type) {
136 boolean isNewGroupMsg = cmd1 != lastCmd1 || timestamp > lastTimestamp + GROUP_STATE_TIMEOUT;
146 duplicate = !isNewGroupMsg;
153 duplicate = !isNewGroupMsg;
176 state = State.EXPECT_CLEAN;
179 state = State.EXPECT_SUCCESS;
182 state = State.EXPECT_BCAST;
187 lastTimestamp = timestamp;
189 logger.debug("{} group:{} type:{} state:{} duplicate:{}", address, group, type, state, duplicate);