Skip to content

Commit 0fea91e

Browse files
committed
Implement WebSocket keep alive by sending WS PING once per minute for
alwaysOn devices. The PONG will restart the watchdog. WS connection idle time has been reduced to 5 min, which means about 4 retries if the devices does not respond once per minute. Signed-off-by: Markus Michels <markus7017@gmail.com>
1 parent 091e48f commit 0fea91e

File tree

7 files changed

+74
-13
lines changed

7 files changed

+74
-13
lines changed

bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiInterface.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public interface ShellyApiInterface extends ShellyDiscoveryInterface {
3737

3838
void setConfig(String thingName, ShellyThingConfiguration config);
3939

40-
ShellySettingsStatus getStatus() throws ShellyApiException;
40+
ShellySettingsStatus getStatus(boolean ping) throws ShellyApiException;
4141

4242
void setLedStatus(String ledName, boolean value) throws ShellyApiException;
4343

bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1HttpApi.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ public boolean isInitialized() {
167167
* @throws ShellyApiException
168168
*/
169169
@Override
170-
public ShellySettingsStatus getStatus() throws ShellyApiException {
170+
public ShellySettingsStatus getStatus(boolean ping) throws ShellyApiException {
171171
String json = "";
172172
try {
173173
json = httpRequest(SHELLY_URL_STATUS);

bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiRpc.java

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ public ShellyDeviceProfile getDeviceProfile(ThingTypeUID thingTypeUID, @Nullable
334334
}
335335

336336
if (!profile.initialized && profile.alwaysOn) {
337-
getStatus(); // make sure profile.status is initialized (e.g,. relay/meter status)
337+
getStatus(false); // make sure profile.status is initialized (e.g,. relay/meter status)
338338
asyncApiRequest(SHELLYRPC_METHOD_GETSTATUS); // request periodic status updates from device
339339
}
340340
profile.initialized = true;
@@ -747,6 +747,13 @@ public void onMessage(String message) {
747747
incProtErrors();
748748
}
749749

750+
@Override
751+
public void onPong() {
752+
if (thing != null) {
753+
thing.restartWatchdog();
754+
}
755+
}
756+
750757
@Override
751758
public void onClose(int statusCode, String description) {
752759
try {
@@ -790,7 +797,7 @@ private void thingOffline(String reason) {
790797
}
791798

792799
@Override
793-
public ShellySettingsStatus getStatus() throws ShellyApiException {
800+
public ShellySettingsStatus getStatus(boolean ping) throws ShellyApiException {
794801
ShellyDeviceProfile profile = getProfile();
795802
ShellySettingsStatus status = profile.status;
796803
Shelly2DeviceStatusResult ds = apiRequest(SHELLYRPC_METHOD_GETSTATUS, null, Shelly2DeviceStatusResult.class);
@@ -853,9 +860,23 @@ public ShellySettingsStatus getStatus() throws ShellyApiException {
853860
}
854861
}
855862

863+
if (ping) {
864+
sendPing();
865+
}
866+
856867
return status;
857868
}
858869

870+
private void sendPing() {
871+
Shelly2RpcSocket rpcSocket;
872+
synchronized (this) {
873+
rpcSocket = this.rpcSocket;
874+
}
875+
if (rpcSocket != null) {
876+
rpcSocket.ping();
877+
}
878+
}
879+
859880
@Override
860881
public void setSleepTime(int value) throws ShellyApiException {
861882
}
@@ -864,7 +885,7 @@ public void setSleepTime(int value) throws ShellyApiException {
864885
public ShellyStatusRelay getRelayStatus(int relayIndex) throws ShellyApiException {
865886
if (getProfile().status.wifiSta.ssid == null) {
866887
// Update status when not yet initialized
867-
getStatus();
888+
getStatus(false);
868889
}
869890
return relayStatus;
870891
}

bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2RpcSocket.java

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.io.IOException;
2222
import java.net.URI;
2323
import java.net.URISyntaxException;
24+
import java.nio.ByteBuffer;
2425
import java.util.ArrayList;
2526
import java.util.List;
2627

@@ -35,8 +36,10 @@
3536
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
3637
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
3738
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
39+
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketFrame;
3840
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
3941
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
42+
import org.eclipse.jetty.websocket.api.extensions.Frame;
4043
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
4144
import org.eclipse.jetty.websocket.client.WebSocketClient;
4245
import org.openhab.binding.shelly.internal.api.ShellyApiException;
@@ -56,7 +59,7 @@
5659
* @author Markus Michels - Initial contribution
5760
*/
5861
@NonNullByDefault
59-
@WebSocket(maxIdleTime = Integer.MAX_VALUE)
62+
@WebSocket(maxIdleTime = 5 * 60 * 1000)
6063
public class Shelly2RpcSocket implements WriteCallback {
6164
private final Logger logger = LoggerFactory.getLogger(Shelly2RpcSocket.class);
6265
private final Gson gson = new Gson();
@@ -377,10 +380,6 @@ public synchronized boolean isConnected() {
377380
return session != null && session.isOpen();
378381
}
379382

380-
public boolean isInbound() {
381-
return inbound;
382-
}
383-
384383
/**
385384
* WebSocket closed, notify thing handler (close initiated by the binding)
386385
*
@@ -436,6 +435,45 @@ public void onError(Throwable cause) {
436435
}
437436
}
438437

438+
public void ping() {
439+
Session session;
440+
synchronized (this) {
441+
session = this.session;
442+
}
443+
if (session != null && session.isOpen()) {
444+
RemoteEndpoint remote = session.getRemote();
445+
String ipAddress = remote.getInetSocketAddress().getHostString();
446+
if (logger.isDebugEnabled()) {
447+
logger.debug("{}: Sending WebSocket PING to {}", thingName, ipAddress);
448+
}
449+
try {
450+
remote.sendPing(ByteBuffer.allocate(0));
451+
} catch (IOException e) {
452+
logger.debug("{}: Faied to send WebSocket PING to {}", thingName, ipAddress, e);
453+
}
454+
}
455+
}
456+
457+
@OnWebSocketFrame
458+
public void onFrame(Session session, Frame frame) {
459+
switch (frame.getOpCode()) {
460+
case 0xA: // PONG opcode
461+
case 0x9: // PING opcode (optional)
462+
// Jetty auto-responds with PONG by default
463+
if (logger.isTraceEnabled()) {
464+
logger.trace("{}: WebSocket PONG received", thingName);
465+
}
466+
Shelly2RpctInterface websocketHandler;
467+
synchronized (this) {
468+
websocketHandler = this.websocketHandler;
469+
}
470+
if (websocketHandler != null) {
471+
websocketHandler.onPong();
472+
}
473+
break;
474+
}
475+
}
476+
439477
/**
440478
* Clears session and drops queued messages.
441479
* Must only be called when session has been/is being closed one way or another.

bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2RpctInterface.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ public interface Shelly2RpctInterface {
3232

3333
void onNotifyEvent(String json) throws ShellyApiException;
3434

35+
void onPong();
36+
3537
void onClose(int statusCode, String reason);
3638

3739
void onError(Throwable cause);

bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/ShellyBluApi.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ public ShellyDeviceProfile getDeviceProfile(ThingTypeUID thingTypeUID, @Nullable
138138
}
139139

140140
@Override
141-
public ShellySettingsStatus getStatus() throws ShellyApiException {
141+
public ShellySettingsStatus getStatus(boolean ping) throws ShellyApiException {
142142
if (!connected) {
143143
throw new ShellyApiException("Thing is not yet initialized -> status not available");
144144
}

bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyBaseHandler.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ thingName, getThing().getLabel(), thingType, config.deviceAddress.toUpperCase(),
368368
tmpPrf.updatePeriod = UPDATE_SETTINGS_INTERVAL_SECONDS + 10;
369369
}
370370

371-
tmpPrf.status = api.getStatus(); // update thing properties
371+
tmpPrf.status = api.getStatus(false); // update thing properties
372372
tmpPrf.updateFromStatus(tmpPrf.status);
373373
addStateOptions(tmpPrf);
374374

@@ -559,7 +559,7 @@ protected void refreshStatus() {
559559
logger.debug("{}: Status update triggered thing initialization", thingName);
560560
initializeThing(); // may fire an exception if initialization failed
561561
}
562-
ShellySettingsStatus status = api.getStatus();
562+
ShellySettingsStatus status = api.getStatus(profile.alwaysOn);
563563
boolean restarted = checkRestarted(status);
564564
profile = getProfile(refreshSettings || restarted);
565565
profile.status = status;

0 commit comments

Comments
 (0)