Skip to content

Commit 61bb4c2

Browse files
mjagdisolemr
authored andcommitted
[tuya] Improve handling of battery devices (openhab#20760)
* [tuya] Improve handling of battery devices Signed-off-by: Mike Jagdis <mjagdis@eris-associates.co.uk> Signed-off-by: olemr <olemr@olemr.com>
1 parent b0761a5 commit 61bb4c2

7 files changed

Lines changed: 297 additions & 124 deletions

File tree

bundles/org.openhab.binding.tuya/src/main/java/org/openhab/binding/tuya/internal/TuyaBindingConstants.java

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ public class TuyaBindingConstants {
5252
public static final List<String> DIMMER_CHANNEL_CODES = List.of("bright_value", "bright_value_1", "bright_value_2",
5353
"temp_value");
5454

55+
// The maximum length of time a connection to the device is maintained. After this we close
56+
// and reconnect in an attempt to limit possible connection related, device-side memory leaks.
57+
public static final int TCP_CONNECTION_MAX_LIFETIME = 86400; // Seconds
58+
5559
// The heartbeat interval specifies the maximum amount of time that can pass without us
5660
// sending anything to a device. Once the heartbeat interval is reached we send a heartbeat
5761
// message to let the device know we are still present. Devices that do not receive traffic
@@ -62,13 +66,13 @@ public class TuyaBindingConstants {
6266
// The amount of time a device has to respond to a message. If we don't see anything from
6367
// the device for this long after sending a message we consider the connection dead, close
6468
// it, and start trying to reconnect.
65-
public static final int TCP_CONNECTION_MESSAGE_RESPONSE = 1; // Seconds
69+
public static final int TCP_CONNECTION_MESSAGE_RESPONSE = 200; // Milliseconds
6670

6771
// How long to wait for a TCP session to connect before closing it and starting again. We do
6872
// not rely on TCP's own retry strategy because that varies between implementations so we
6973
// cannot know when the retry interval has become so great that it exceeds the amount of
7074
// time a battery device may be awake.
71-
public static final int TCP_CONNECT_TIMEOUT = 1000; // Milliseconds
75+
public static final int TCP_CONNECT_TIMEOUT = 500; // Milliseconds
7276

7377
// How long to wait before attempting another connection after the previous closed or failed.
7478
// Note that if the previous attempt failed because the device was not reachable the interval
@@ -78,5 +82,19 @@ public class TuyaBindingConstants {
7882
// of TCP_CONNECT_TIMEOUT and TCP_CONNECT_RETRY_INTERVAL must therefore be small enough that at
7983
// least one, preferably two or three, connection attempts will be made during the time the
8084
// device is awake.
81-
public static final int TCP_CONNECT_RETRY_INTERVAL = 1000; // Milliseconds
85+
public static final int TCP_CONNECT_RETRY_INTERVAL = 50; // Milliseconds
86+
87+
// How long to wait before sending the initial query after connecting.
88+
// We need to delay the initial query because some battery devices seem to ignore requests that
89+
// come too soon and sometimes they claim DP_QUERY isn't supported when it really is. Perhaps the
90+
// TCP stack is initialized before the API?
91+
public static final int TCP_CONNECT_INITIAL_DELAY = 750; // Milliseconds
92+
93+
// How long to wait before attempting another connection after the first "Connection refused".
94+
// When battery devices wake up their TCP can come online before the API server is started
95+
// and even before the API backend is plugged in to the API server. Hammering battery devices
96+
// (which tend to be especially slow) with connection attempts will just waste their CPU cycles
97+
// and can mean they don't even come online until a second event has overwritten the first
98+
// (especially a problem for contact sensors).
99+
public static final int TCP_CONNECT_INITIAL_INTERVAL = 1000; // Milliseconds
82100
}

bundles/org.openhab.binding.tuya/src/main/java/org/openhab/binding/tuya/internal/handler/TuyaDeviceHandler.java

Lines changed: 27 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -147,17 +147,6 @@ public TuyaDeviceHandler(Thing thing, Gson gson,
147147

148148
@Override
149149
public void processDeviceStatus(Map<Integer, Object> deviceStatus) {
150-
// Older devices may need to use the control method to request device status.
151-
if (deviceStatus.isEmpty()) {
152-
TuyaDevice tuyaDevice = this.tuyaDevice;
153-
if (tuyaDevice != null) {
154-
logger.debug("'{}' switching to control instead of query", thing.getUID());
155-
tuyaDevice.setQueryUsesControl();
156-
tuyaDevice.requestStatus(List.of());
157-
}
158-
return;
159-
}
160-
161150
// Changes to function DPs might lead to changes in status DPs. For instance, if a
162151
// power switch is turned on the measured current and power can be expected to change
163152
// within a few seconds.
@@ -178,6 +167,8 @@ public void processDeviceStatus(Map<Integer, Object> deviceStatus) {
178167
}
179168

180169
if (needRefresh && configuration.pollingInterval > 0) {
170+
// We cannot assume that DPs are current or that the device will automatically
171+
// re-measure on a function change.
181172
TuyaDevice tuyaDevice = this.tuyaDevice;
182173
if (tuyaDevice != null) {
183174
ScheduledFuture<?> pollingJob = this.pollingJob;
@@ -186,7 +177,7 @@ public void processDeviceStatus(Map<Integer, Object> deviceStatus) {
186177
}
187178

188179
pollBurst = 3;
189-
this.pollingJob = scheduler.scheduleWithFixedDelay(this::burstPoller, 1, 1, TimeUnit.SECONDS);
180+
this.pollingJob = scheduler.scheduleWithFixedDelay(this::burstPoller, 0, 1, TimeUnit.SECONDS);
190181
}
191182
} else if (!missingStatus) {
192183
// If we have updates for everything we can stand down the burst polling.
@@ -200,19 +191,19 @@ private void burstPoller() {
200191
TuyaDevice tuyaDevice = this.tuyaDevice;
201192
if (tuyaDevice != null) {
202193
if (pollBurst > 0) {
203-
tuyaDevice.refreshStatus(List.of());
194+
tuyaDevice.refreshStatus();
204195
pollBurst = pollBurst - 1;
205196
} else {
206197
ScheduledFuture<?> pollingJob = this.pollingJob;
207198
if (pollingJob != null) {
208199
this.pollingJob = null;
209-
pollingJob.cancel(true);
200+
pollingJob.cancel(false);
210201
}
211202

212203
int pollingInterval = configuration.pollingInterval;
213204
if (pollingInterval > 0) {
214205
this.pollingJob = scheduler.scheduleWithFixedDelay(() -> {
215-
tuyaDevice.refreshStatus(List.of());
206+
tuyaDevice.refreshStatus();
216207
}, pollingInterval, pollingInterval, TimeUnit.SECONDS);
217208
}
218209
}
@@ -324,7 +315,7 @@ private void processChannelStatus(Integer dp, Object value) {
324315
}
325316

326317
@Override
327-
public void connectionStatus(boolean status) {
318+
public void connectionStatus(boolean status, int initialDelay) {
328319
if (status) {
329320
logger.debug("{}: connected", thing.getUID().getId());
330321

@@ -335,17 +326,25 @@ public void connectionStatus(boolean status) {
335326

336327
TuyaDevice tuyaDevice = this.tuyaDevice;
337328
if (tuyaDevice != null) {
338-
// When we first connect the device state is unknown so we want to know everything
339-
// it is willing to tell us.
340-
tuyaDevice.requestStatus(List.of());
341-
342329
if (pollingJob == null) {
343-
int pollingInterval = configuration.pollingInterval;
344-
if (pollingInterval > 0) {
345-
pollingJob = scheduler.scheduleWithFixedDelay(() -> {
346-
tuyaDevice.refreshStatus(List.of());
347-
}, pollingInterval, pollingInterval, TimeUnit.SECONDS);
348-
}
330+
// When we first connect the device state is unknown so we want to query for everything.
331+
// Some devices seem to initialize their stacks in the wrong order and
332+
// requests that come too soon can be either ignored completely or responded
333+
// to with a, "not supported". The lower level protocol handler suggests an
334+
// initial delay where it might be advisable.
335+
this.pollingJob = scheduler.schedule(() -> {
336+
tuyaDevice.requestStatus();
337+
338+
// After that we poll for the measurable DPs.
339+
int pollingInterval = configuration.pollingInterval;
340+
if (pollingInterval > 0) {
341+
// The first refresh request is immediate because we do not know how old
342+
// the current status might be.
343+
pollingJob = scheduler.scheduleWithFixedDelay(() -> {
344+
tuyaDevice.refreshStatus();
345+
}, 0, pollingInterval, TimeUnit.SECONDS);
346+
}
347+
}, initialDelay, TimeUnit.MILLISECONDS);
349348
} else {
350349
logger.debug("{}: polling job already exists?!?", thing.getUID().getId());
351350
}
@@ -586,7 +585,7 @@ public void initialize() {
586585

587586
this.tuyaDevice = new TuyaDevice(gson, this, eventLoopGroup, configuration.deviceId,
588587
configuration.localKey.getBytes(StandardCharsets.UTF_8), configuration.ip, configuration.port,
589-
configuration.protocol);
588+
configuration.protocol, schemaDps.values().stream().map(e -> e.id).toList());
590589
} else {
591590
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, "@text/offline.wait-for-ip");
592591
}
@@ -618,7 +617,7 @@ public void deviceInfoChanged(DeviceInfo deviceInfo) {
618617

619618
this.tuyaDevice = new TuyaDevice(gson, this, eventLoopGroup, configuration.deviceId,
620619
configuration.localKey.getBytes(StandardCharsets.UTF_8), configuration.ip, configuration.port,
621-
configuration.protocol);
620+
configuration.protocol, schemaDps.values().stream().map(e -> e.id).toList());
622621
} catch (IllegalArgumentException e) {
623622
logger.warn("{}", e.getMessage());
624623
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());

bundles/org.openhab.binding.tuya/src/main/java/org/openhab/binding/tuya/internal/local/CommandType.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,7 @@ public enum CommandType {
5858
LAN_REMOVE_GW(249),
5959
LAN_CHECK_GW_UPDATE(250),
6060
LAN_GW_UPDATE(251),
61-
LAN_SET_GW_CHANNEL(252),
62-
DP_QUERY_NOT_SUPPORTED(-1); // this is an internal value
61+
LAN_SET_GW_CHANNEL(252);
6362

6463
private final int code;
6564

bundles/org.openhab.binding.tuya/src/main/java/org/openhab/binding/tuya/internal/local/DeviceStatusListener.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,5 @@
2525
public interface DeviceStatusListener {
2626
void processDeviceStatus(Map<Integer, Object> deviceStatus);
2727

28-
void connectionStatus(boolean status);
28+
void connectionStatus(boolean status, int initialDelay);
2929
}

0 commit comments

Comments
 (0)