]> git.basschouten.com Git - openhab-addons.git/blob
41dfd62be0851163bdfc3db51bb975aeb855a31e
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.echonetlite.internal;
14
15 import static org.openhab.binding.echonetlite.internal.HexUtil.hex;
16
17 import java.nio.ByteBuffer;
18 import java.util.HashMap;
19 import java.util.LinkedHashMap;
20 import java.util.Map;
21 import java.util.Set;
22
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.core.types.State;
26 import org.slf4j.Logger;
27 import org.slf4j.LoggerFactory;
28
29 /**
30  * @author Michael Barker - Initial contribution
31  */
32 @NonNullByDefault
33 public class EchonetDevice extends EchonetObject {
34
35     private final LinkedHashMap<Epc, State> pendingSets = new LinkedHashMap<>();
36     private final HashMap<Epc, State> stateFields = new HashMap<>();
37     private final HashMap<String, Epc> epcByChannelId = new HashMap<>();
38     private final Logger logger = LoggerFactory.getLogger(EchonetDevice.class);
39     @Nullable
40     private EchonetPropertyMap getPropertyMap;
41     private EchonetDeviceListener listener;
42     private boolean initialised = false;
43
44     private long lastPollMs = 0;
45
46     public EchonetDevice(final InstanceKey instanceKey, EchonetDeviceListener listener) {
47         super(instanceKey, Epc.Device.GET_PROPERTY_MAP);
48         this.listener = listener;
49     }
50
51     @Override
52     public void applyProperty(InstanceKey sourceInstanceKey, Esv esv, final int epcCode, final int pdc,
53             final ByteBuffer edt) {
54         final Epc epc = Epc.lookup(instanceKey().klass.groupCode(), instanceKey().klass.classCode(), epcCode);
55
56         if ((Esv.Get_Res == esv || Esv.Get_SNA == esv || Esv.INF == esv) && 0 < pdc) {
57             pendingGets.remove(epc);
58
59             int edtPosition = edt.position();
60
61             final StateDecode decoder = epc.decoder();
62             State state = null;
63             if (null != decoder) {
64                 state = decoder.decodeState(edt);
65                 if (null == stateFields.put(epc, state)) {
66                     epcByChannelId.put(epc.channelId(), epc);
67                 }
68
69                 final @Nullable State pendingState = lookupPendingSet(epc);
70                 if (null != pendingState && pendingState.equals(state)) {
71                     logger.debug("pendingSet - removing: {} {}", epc, state);
72                     pendingSets.remove(epc);
73                 } else if (null != pendingState) {
74                     logger.debug("pendingSet - state mismatch: {} {} {}", epc, pendingState, state);
75                 }
76
77                 if (initialised) {
78                     listener.onUpdated(epc.channelId(), state);
79                 }
80             } else if (Epc.Device.GET_PROPERTY_MAP == epc) {
81                 if (null == getPropertyMap) {
82                     final EchonetPropertyMap getPropertyMap = new EchonetPropertyMap(epc);
83                     getPropertyMap.update(edt);
84                     getPropertyMap.getProperties(instanceKey().klass.groupCode(), instanceKey().klass.classCode(),
85                             Set.of(Epc.Device.GET_PROPERTY_MAP), pendingGets);
86                     this.getPropertyMap = getPropertyMap;
87                 }
88             }
89
90             if (!initialised && null != getPropertyMap && pendingGets.isEmpty()) {
91                 initialised = true;
92                 listener.onInitialised(identifier(), instanceKey, channelIds());
93                 stateFields.forEach((e, s) -> listener.onUpdated(e.channelId(), s));
94             }
95
96             if (logger.isDebugEnabled()) {
97                 String value = null != state ? state.toString() : "";
98                 edt.position(edtPosition);
99                 logger.debug("Applying: {}({},{}) {} {} pending: {}", epc, hex(epc.code()), pdc, value, hex(edt),
100                         pendingGets.size());
101             }
102         } else if (esv == Esv.Set_Res) {
103             pendingSets.remove(epc);
104         }
105     }
106
107     public String identifier() {
108         final State identificationNumber = stateFields.get(Epc.Device.IDENTIFICATION_NUMBER);
109         if (null == identificationNumber) {
110             throw new IllegalStateException("Echonet devices must support identification number property");
111         }
112
113         return identificationNumber.toString();
114     }
115
116     @Override
117     public boolean buildUpdateMessage(final EchonetMessageBuilder messageBuilder, final ShortSupplier tidSupplier,
118             final long nowMs, InstanceKey managementControllerKey) {
119         if (pendingSets.isEmpty()) {
120             return false;
121         }
122
123         final InflightRequest inflightSetRequest = this.inflightSetRequest;
124
125         if (hasInflight(nowMs, inflightSetRequest)) {
126             return false;
127         }
128
129         final short tid = tidSupplier.getAsShort();
130         messageBuilder.start(tid, managementControllerKey, instanceKey, Esv.SetC);
131
132         pendingSets.forEach((k, v) -> {
133             final StateEncode encoder = k.encoder();
134             if (null != encoder) {
135                 final ByteBuffer buffer = messageBuilder.edtBuffer();
136                 encoder.encodeState(v, buffer);
137                 messageBuilder.appendEpcUpdate(k.code(), buffer.flip());
138             }
139         });
140
141         inflightSetRequest.requestSent(tid, nowMs);
142
143         return true;
144     }
145
146     @Override
147     public void update(String channelId, State state) {
148         final Epc epc = epcByChannelId.get(channelId);
149         if (null == epc) {
150             logger.warn("Unable to find epc for channelId: {}", channelId);
151             return;
152         }
153
154         pendingSets.put(epc, state);
155     }
156
157     @Override
158     public void removed() {
159         listener.onRemoved();
160     }
161
162     @Override
163     public void checkTimeouts() {
164         if (EchonetLiteBindingConstants.OFFLINE_TIMEOUT_COUNT <= inflightGetRequest.timeoutCount()) {
165             listener.onOffline();
166         }
167     }
168
169     @Override
170     public void refreshAll(long nowMs) {
171         final EchonetPropertyMap getPropertyMap = this.getPropertyMap;
172         if (lastPollMs + pollIntervalMs <= nowMs && null != getPropertyMap) {
173             getPropertyMap.getProperties(instanceKey().klass.groupCode(), instanceKey().klass.classCode(),
174                     Set.of(Epc.Device.GET_PROPERTY_MAP), pendingGets);
175             lastPollMs = nowMs;
176         }
177     }
178
179     @Override
180     public void refresh(String channelId) {
181         final Epc epc = epcByChannelId.get(channelId);
182         if (null == epc) {
183             return;
184         }
185
186         final State state = stateFields.get(epc);
187         if (null == state) {
188             return;
189         }
190
191         listener.onUpdated(channelId, state);
192     }
193
194     public void setListener(EchonetDeviceListener listener) {
195         this.listener = listener;
196         if (initialised) {
197             listener.onInitialised(identifier(), instanceKey(), channelIds());
198             stateFields.forEach((e, s) -> listener.onUpdated(e.channelId(), s));
199         }
200     }
201
202     private Map<String, String> channelIds() {
203         final HashMap<String, String> channelIdAndType = new HashMap<>();
204         for (Epc e : stateFields.keySet()) {
205             final StateDecode decoder = e.decoder();
206             if (null != decoder) {
207                 channelIdAndType.put(e.channelId(), decoder.itemType());
208             }
209         }
210         return channelIdAndType;
211     }
212
213     private @Nullable State lookupPendingSet(Epc epc) {
214         return pendingSets.get(epc);
215     }
216 }