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