2 * Copyright (c) 2010-2023 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.echonetlite.internal;
15 import static org.openhab.binding.echonetlite.internal.EchonetLiteBindingConstants.DEFAULT_RETRY_TIMEOUT_MS;
17 import java.nio.ByteBuffer;
18 import java.util.HashMap;
19 import java.util.HashSet;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.openhab.core.types.State;
24 import org.slf4j.Logger;
25 import org.slf4j.LoggerFactory;
28 * @author Michael Barker - Initial contribution
31 public abstract class EchonetObject {
33 private final Logger logger = LoggerFactory.getLogger(EchonetObject.class);
35 protected final InstanceKey instanceKey;
36 protected final HashSet<Epc> pendingGets = new HashSet<>();
38 protected InflightRequest inflightGetRequest = new InflightRequest(DEFAULT_RETRY_TIMEOUT_MS, "GET");
39 protected InflightRequest inflightSetRequest = new InflightRequest(DEFAULT_RETRY_TIMEOUT_MS, "SET");
41 protected long pollIntervalMs;
43 public EchonetObject(final InstanceKey instanceKey, final Epc initialProperty) {
44 this.instanceKey = instanceKey;
45 pendingGets.add(initialProperty);
48 public InstanceKey instanceKey() {
52 public void applyProperty(InstanceKey sourceInstanceKey, Esv esv, final int epcCode, final int pdc,
53 final ByteBuffer edt) {
56 public boolean buildPollMessage(final EchonetMessageBuilder messageBuilder, final ShortSupplier tidSupplier,
57 long nowMs, InstanceKey managementControllerKey) {
58 if (pendingGets.isEmpty()) {
62 if (hasInflight(nowMs, this.inflightGetRequest)) {
66 final short tid = tidSupplier.getAsShort();
67 messageBuilder.start(tid, managementControllerKey, instanceKey(), Esv.Get);
69 for (Epc pendingProperty : pendingGets) {
70 messageBuilder.appendEpcRequest(pendingProperty.code());
73 this.inflightGetRequest.requestSent(tid, nowMs);
78 protected boolean hasInflight(long nowMs, InflightRequest inflightRequest) {
79 if (inflightRequest.isInflight()) {
80 return !inflightRequest.hasTimedOut(nowMs);
85 protected void setTimeouts(long pollIntervalMs, long retryTimeoutMs) {
86 this.pollIntervalMs = pollIntervalMs;
87 this.inflightGetRequest = new InflightRequest(retryTimeoutMs, inflightGetRequest);
88 this.inflightSetRequest = new InflightRequest(retryTimeoutMs, inflightSetRequest);
91 public boolean buildUpdateMessage(final EchonetMessageBuilder messageBuilder, final ShortSupplier tid,
92 final long nowMs, InstanceKey managementControllerKey) {
96 public void refreshAll(long nowMs) {
100 public String toString() {
101 return "ItemBase{" + "instanceKey=" + instanceKey + ", pendingProperties=" + pendingGets + '}';
104 public void update(String channelId, State state) {
107 public void removed() {
110 public void refresh(String channelId) {
113 public void applyHeader(Esv esv, short tid, long nowMs) {
114 if ((esv == Esv.Get_Res || esv == Esv.Get_SNA)) {
115 final long sentTimestampMs = this.inflightGetRequest.timestampMs;
116 if (this.inflightGetRequest.responseReceived(tid)) {
117 logger.debug("{} response time: {}ms", esv, nowMs - sentTimestampMs);
119 logger.warn("Unexpected {} response: {}", esv, tid);
120 this.inflightGetRequest.checkOldResponse(tid, nowMs);
122 } else if ((esv == Esv.Set_Res || esv == Esv.SetC_SNA)) {
123 final long sentTimestampMs = this.inflightSetRequest.timestampMs;
124 if (this.inflightSetRequest.responseReceived(tid)) {
125 logger.debug("{} response time: {}ms", esv, nowMs - sentTimestampMs);
127 logger.warn("Unexpected {} response: {}", esv, tid);
128 this.inflightSetRequest.checkOldResponse(tid, nowMs);
133 public void checkTimeouts() {
136 protected static class InflightRequest {
137 private static final long NULL_TIMESTAMP = -1;
139 private final Logger logger = LoggerFactory.getLogger(InflightRequest.class);
140 private final long timeoutMs;
141 private final String name;
142 private final Map<Short, Long> oldRequests = new HashMap<>();
145 private long timestampMs = NULL_TIMESTAMP;
146 @SuppressWarnings("unused")
147 private int timeoutCount = 0;
149 InflightRequest(long timeoutMs, InflightRequest existing) {
150 this(timeoutMs, existing.name);
151 this.tid = existing.tid;
152 this.timestampMs = existing.timestampMs;
155 InflightRequest(long timeoutMs, String name) {
156 this.timeoutMs = timeoutMs;
160 void requestSent(short tid, long timestampMs) {
162 this.timestampMs = timestampMs;
165 boolean responseReceived(short tid) {
166 timestampMs = NULL_TIMESTAMP;
169 return this.tid == tid;
172 boolean hasTimedOut(long nowMs) {
173 final boolean timedOut = timestampMs + timeoutMs <= nowMs;
175 logger.debug("Timed out {}, tid={}, timestampMs={} + timeoutMs={} <= nowMs={}", name, tid, timestampMs,
179 if (NULL_TIMESTAMP != tid) {
180 oldRequests.put(tid, timestampMs);
186 public boolean isInflight() {
187 return NULL_TIMESTAMP != timestampMs;
190 public void checkOldResponse(short tid, long nowMs) {
191 final Long oldResponseTimestampMs = oldRequests.remove(tid);
192 if (null != oldResponseTimestampMs) {
193 logger.debug("Timed out request, tid={}, actually took={}", tid, nowMs - oldResponseTimestampMs);
197 public int timeoutCount() {