]> git.basschouten.com Git - openhab-addons.git/blob
fceb86a53aff6006f83fd5a817def78dd83cb31a
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.insteon.internal.transport.message;
14
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;
19
20 /**
21  * Ideally, Insteon ALL LINK messages are received in this order, and
22  * only a single one of each:
23  *
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)
27  *
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.
32  *
33  * @formatter:off
34  *                          "SUCCESS"
35  *                         EXPECT_BCAST
36  *                    ^ /                ^ \
37  *           SUCCESS / /                  \ \ [BCAST]
38  *                  / /['CLEAN']  'SUCCESS'\ \
39  *                 / /                      \ \
40  *                / V         CLEAN          \ V
41  * "CLEAN" EXPECT_SUCCESS <-------------- EXPECT_CLEAN "BCAST"
42  *                         -------------->
43  *                            ['BCAST']
44  * @formatter:on
45  *
46  * How to read this diagram:
47  *
48  * Regular, expected, non-duplicate messages do not have any quotes around them,
49  * and lead to the obvious state transitions.
50  *
51  * The types in [square brackets] are transitions that cause a state
52  * update to be published when they occur.
53  *
54  * The presence of double quotes indicates a duplicate that does not lead
55  * to any state transitions, i.e. it is simply ignored.
56  *
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.
60  *
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 "").
63  *
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).
68  *
69  * @author Bernd Pfrommer - Initial contribution
70  * @author Rob Nielsen - Port to openHAB 2 insteon binding
71  * @author Jeremy Setton - Rewrite insteon binding
72  */
73 @NonNullByDefault
74 public class GroupMessageStateMachine {
75     private static final int GROUP_STATE_TIMEOUT = 10000; // in milliseconds
76
77     /**
78      * The different kinds of Insteon ALL Link (Group) messages that can be received.
79      * Here is a typical sequence:
80      * BCAST:
81      * IN:Cmd:0x50|fromAddress:20.AC.99|toAddress:00.00.01|messageFlags:0xCB=ALL_LINK_BROADCAST:3:2|command1:0x13|
82      * command2:0x00|
83      * CLEAN:
84      * IN:Cmd:0x50|fromAddress:20.AC.99|toAddress:23.9B.65|messageFlags:0x41=ALL_LINK_CLEANUP:1:0|command1:0x13|command2
85      * :0x01|
86      * SUCCESS:
87      * IN:Cmd:0x50|fromAddress:20.AC.99|toAddress:13.03.01|messageFlags:0xCB=ALL_LINK_BROADCAST:3:2|command1:0x06|
88      * command2:0x00|
89      */
90     public static enum GroupMessageType {
91         BCAST,
92         CLEAN,
93         SUCCESS
94     }
95
96     /**
97      * The state of the machine (i.e. what message we are expecting next).
98      * The usual state should be EXPECT_BCAST
99      */
100     private static enum State {
101         EXPECT_BCAST,
102         EXPECT_CLEAN,
103         EXPECT_SUCCESS
104     }
105
106     private final Logger logger = LoggerFactory.getLogger(GroupMessageStateMachine.class);
107
108     private State state = State.EXPECT_BCAST;
109     private boolean duplicate = false;
110     private byte lastCmd1 = 0;
111     private long lastTimestamp = 0;
112
113     public boolean isDuplicate() {
114         return duplicate;
115     }
116
117     public byte getLastCommand() {
118         return lastCmd1;
119     }
120
121     public long getLastTimestamp() {
122         return lastTimestamp;
123     }
124
125     /**
126      * Updates the state machine and determine if not duplicate
127      *
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
134      */
135     public boolean update(InsteonAddress address, int group, byte cmd1, long timestamp, GroupMessageType type) {
136         boolean isNewGroupMsg = cmd1 != lastCmd1 || timestamp > lastTimestamp + GROUP_STATE_TIMEOUT;
137
138         switch (state) {
139             case EXPECT_BCAST:
140                 switch (type) {
141                     case BCAST:
142                         duplicate = false;
143                         break;
144                     case CLEAN:
145                     case SUCCESS:
146                         duplicate = !isNewGroupMsg;
147                         break;
148                 }
149                 break;
150             case EXPECT_CLEAN:
151                 switch (type) {
152                     case BCAST:
153                         duplicate = !isNewGroupMsg;
154                         break;
155                     case CLEAN:
156                     case SUCCESS:
157                         duplicate = true;
158                         break;
159                 }
160                 break;
161             case EXPECT_SUCCESS:
162                 switch (type) {
163                     case BCAST:
164                         duplicate = false;
165                         break;
166                     case CLEAN:
167                     case SUCCESS:
168                         duplicate = true;
169                         break;
170                 }
171                 break;
172         }
173
174         switch (type) {
175             case BCAST:
176                 state = State.EXPECT_CLEAN;
177                 break;
178             case CLEAN:
179                 state = State.EXPECT_SUCCESS;
180                 break;
181             case SUCCESS:
182                 state = State.EXPECT_BCAST;
183                 break;
184         }
185
186         lastCmd1 = cmd1;
187         lastTimestamp = timestamp;
188
189         logger.debug("{} group:{} type:{} state:{} duplicate:{}", address, group, type, state, duplicate);
190
191         return duplicate;
192     }
193 }