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) {
99 public String toString() {
100 return "ItemBase{" + "instanceKey=" + instanceKey + ", pendingProperties=" + pendingGets + '}';
103 public void update(String channelId, State state) {
106 public void removed() {
109 public void refresh(String channelId) {
112 public void applyHeader(Esv esv, short tid, long nowMs) {
113 if ((esv == Esv.Get_Res || esv == Esv.Get_SNA)) {
114 final long sentTimestampMs = this.inflightGetRequest.timestampMs;
115 if (this.inflightGetRequest.responseReceived(tid)) {
116 logger.debug("{} response time: {}ms", esv, nowMs - sentTimestampMs);
118 logger.warn("Unexpected {} response: {}", esv, tid);
119 this.inflightGetRequest.checkOldResponse(tid, nowMs);
121 } else if ((esv == Esv.Set_Res || esv == Esv.SetC_SNA)) {
122 final long sentTimestampMs = this.inflightSetRequest.timestampMs;
123 if (this.inflightSetRequest.responseReceived(tid)) {
124 logger.debug("{} response time: {}ms", esv, nowMs - sentTimestampMs);
126 logger.warn("Unexpected {} response: {}", esv, tid);
127 this.inflightSetRequest.checkOldResponse(tid, nowMs);
132 public void checkTimeouts() {
135 protected static class InflightRequest {
136 private static final long NULL_TIMESTAMP = -1;
138 private final Logger logger = LoggerFactory.getLogger(InflightRequest.class);
139 private final long timeoutMs;
140 private final String name;
141 private final Map<Short, Long> oldRequests = new HashMap<>();
144 private long timestampMs = NULL_TIMESTAMP;
145 @SuppressWarnings("unused")
146 private int timeoutCount = 0;
148 InflightRequest(long timeoutMs, InflightRequest existing) {
149 this(timeoutMs, existing.name);
150 this.tid = existing.tid;
151 this.timestampMs = existing.timestampMs;
154 InflightRequest(long timeoutMs, String name) {
155 this.timeoutMs = timeoutMs;
159 void requestSent(short tid, long timestampMs) {
161 this.timestampMs = timestampMs;
164 boolean responseReceived(short tid) {
165 timestampMs = NULL_TIMESTAMP;
168 return this.tid == tid;
171 boolean hasTimedOut(long nowMs) {
172 final boolean timedOut = timestampMs + timeoutMs <= nowMs;
174 logger.debug("Timed out {}, tid={}, timestampMs={} + timeoutMs={} <= nowMs={}", name, tid, timestampMs,
178 if (NULL_TIMESTAMP != tid) {
179 oldRequests.put(tid, timestampMs);
185 public boolean isInflight() {
186 return NULL_TIMESTAMP != timestampMs;
189 public void checkOldResponse(short tid, long nowMs) {
190 final Long oldResponseTimestampMs = oldRequests.remove(tid);
191 if (null != oldResponseTimestampMs) {
192 logger.debug("Timed out request, tid={}, actually took={}", tid, nowMs - oldResponseTimestampMs);
196 public int timeoutCount() {