]> git.basschouten.com Git - openhab-addons.git/blob
bbd8f598b89d024671bd1461dffbf3ef9c3919f6
[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.device;
14
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;
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 actions 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  */
72 @NonNullByDefault
73 public class GroupMessageStateMachine {
74     private final Logger logger = LoggerFactory.getLogger(GroupMessageStateMachine.class);
75
76     /**
77      * The different kinds of Insteon ALL Link (Group) messages that can be received.
78      * Here is a typical sequence:
79      * BCAST:
80      * IN:Cmd:0x50|fromAddress:20.AC.99|toAddress:00.00.01|messageFlags:0xCB=ALL_LINK_BROADCAST:3:2|command1:0x13|
81      * command2:0x00|
82      * CLEAN:
83      * IN:Cmd:0x50|fromAddress:20.AC.99|toAddress:23.9B.65|messageFlags:0x41=ALL_LINK_CLEANUP:1:0|command1:0x13|command2
84      * :0x01|
85      * SUCCESS:
86      * IN:Cmd:0x50|fromAddress:20.AC.99|toAddress:13.03.01|messageFlags:0xCB=ALL_LINK_BROADCAST:3:2|command1:0x06|
87      * command2:0x00|
88      */
89     enum GroupMessage {
90         BCAST,
91         CLEAN,
92         SUCCESS
93     }
94
95     /**
96      * The state of the machine (i.e. what message we are expecting next).
97      * The usual state should be EXPECT_BCAST
98      */
99     enum State {
100         EXPECT_BCAST,
101         EXPECT_CLEAN,
102         EXPECT_SUCCESS
103     }
104
105     private State state = State.EXPECT_BCAST;
106     private long lastUpdated = 0;
107     private boolean publish = false;
108     private byte lastCmd1 = 0;
109
110     /**
111      * Advance the state machine and determine if update is genuine (no duplicate)
112      *
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
118      */
119     public boolean action(GroupMessage a, InsteonAddress address, int group, byte cmd1) {
120         publish = false;
121         long currentTime = System.currentTimeMillis();
122         switch (state) {
123             case EXPECT_BCAST:
124                 switch (a) {
125                     case BCAST:
126                         publish = true;
127                         break; // missed() move state machine and pub!
128                     case CLEAN:
129                         publish = true;
130                         break; // missed(BCAST)
131                     case SUCCESS:
132                         publish = false;
133                         break;
134                 } // missed(BCAST,CLEAN) or dup SUCCESS
135                 break;
136             case EXPECT_CLEAN:
137                 switch (a) {
138                     case BCAST:
139                         if (lastCmd1 == cmd1) {
140                             if (currentTime > lastUpdated + 30000) {
141                                 if (logger.isDebugEnabled()) {
142                                     logger.debug(
143                                             "{} group {} cmd1 {} is not a dup BCAST, received last message over 30000 ms ago",
144                                             address, group, Utils.getHexByte(cmd1));
145                                 }
146                                 publish = true;
147                             } else {
148                                 publish = false;
149                             }
150                         } else {
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));
154                             }
155                             publish = true;
156                         }
157                         break; // missed(CLEAN, SUCCESS) or dup BCAST
158                     case CLEAN:
159                         publish = false;
160                         break; // missed() move state machine, no pub
161                     case SUCCESS:
162                         publish = false;
163                         break;
164                 } // missed(CLEAN)
165                 break;
166             case EXPECT_SUCCESS:
167                 switch (a) {
168                     case BCAST:
169                         publish = true;
170                         break; // missed(SUCCESS)
171                     case CLEAN:
172                         publish = false;
173                         break; // missed(SUCCESS,BCAST) or dup CLEAN
174                     case SUCCESS:
175                         publish = false;
176                         break;
177                 } // missed(), move state machine, no pub
178                 break;
179         }
180         State oldState = state;
181         switch (a) {
182             case BCAST:
183                 state = State.EXPECT_CLEAN;
184                 break;
185             case CLEAN:
186                 state = State.EXPECT_SUCCESS;
187                 break;
188             case SUCCESS:
189                 state = State.EXPECT_BCAST;
190                 break;
191         }
192
193         lastCmd1 = cmd1;
194         lastUpdated = currentTime;
195         logger.debug("{} group {} state: {} --{}--> {}, publish: {}", address, group, oldState, a, state, publish);
196         return (publish);
197     }
198
199     public long getLastUpdated() {
200         return lastUpdated;
201     }
202
203     public boolean getPublish() {
204         return publish;
205     }
206 }