Skip to content

Commit 22c9f57

Browse files
wip: Adding intiface-engine based tests
1 parent 82d6230 commit 22c9f57

13 files changed

Lines changed: 251 additions & 196 deletions

File tree

Original file line numberDiff line numberDiff line change
@@ -1,80 +1,92 @@
11
package io.github.blackspherefollower.buttplug4j.connectors.jetty.websocket.client;
22

33
import io.github.blackspherefollower.buttplug4j.client.ButtplugClientDevice;
4-
import io.github.blackspherefollower.buttplug4j.client.ButtplugDeviceException;
4+
import io.github.blackspherefollower.buttplug4j.client.ButtplugClientDeviceFeature;
5+
import io.github.blackspherefollower.buttplug4j.client.ButtplugDeviceFeatureException;
6+
import io.github.blackspherefollower.buttplug4j.utils.test.WSDMClient;
7+
import io.github.blackspherefollower.buttplug4j.utils.test.IntifaceEngineWrapper;
8+
import io.github.blackspherefollower.buttplug4j.protocol.ButtplugMessage;
9+
import io.github.blackspherefollower.buttplug4j.protocol.messages.InputReading;
10+
511
import org.junit.jupiter.api.Disabled;
612
import org.junit.jupiter.api.Test;
713

14+
import java.io.File;
15+
import java.io.IOException;
816
import java.net.URI;
917

10-
import static org.junit.jupiter.api.Assertions.assertThrows;
11-
import static org.junit.jupiter.api.Assertions.assertTrue;
18+
import static org.junit.jupiter.api.Assertions.*;
1219

1320
public class ButtplugClientWSJettyClientTest {
1421

15-
@Disabled
22+
1623
@Test
1724
public void TestConnect() throws Exception {
18-
ButtplugClientWSClient client = new ButtplugClientWSClient("Java Test");
19-
client.connect(new URI("ws://localhost:12345/buttplug"));
20-
client.startScanning();
21-
22-
Thread.sleep(5000);
23-
client.requestDeviceList();
24-
for (ButtplugClientDevice dev : client.getDevices()) {
25-
if (dev.getScalarVibrateCount() > 0) {
26-
dev.sendScalarVibrateCmd(0.5).get();
27-
}
28-
}
25+
try(IntifaceEngineWrapper wrapper = new IntifaceEngineWrapper() ) {
26+
Thread.sleep(500);
27+
WSDMClient wsdev = new WSDMClient(new URI("ws://localhost:" + wrapper.dport), "LVS-Fake", "A9816725B");
28+
Thread.sleep(500);
2929

30-
Thread.sleep(1000);
31-
assertTrue(client.stopAllDevices());
32-
33-
Thread.sleep(60000);
34-
for (ButtplugClientDevice dev : client.getDevices()) {
35-
if (dev.getScalarVibrateCount() > 0) {
36-
dev.sendScalarVibrateCmd(0.5).get();
37-
}
38-
}
30+
ButtplugClientWSClient client = new ButtplugClientWSClient("Java Test");
31+
client.connect(new URI("ws://localhost:" + wrapper.cport + "/buttplug"));
32+
client.startScanning();
3933

40-
Thread.sleep(1000);
41-
assertTrue(client.stopAllDevices());
34+
Thread.sleep(500);
35+
client.requestDeviceList();
4236

43-
Thread.sleep(60000);
44-
for (ButtplugClientDevice dev : client.getDevices()) {
45-
if (dev.getScalarVibrateCount() > 0) {
46-
dev.sendScalarVibrateCmd(0.5).get();
37+
assertEquals(1, client.getDevices().size());
38+
for (ButtplugClientDevice dev : client.getDevices()) {
39+
for (ButtplugClientDeviceFeature feat : dev.getDeviceFeatures().values()) {
40+
if (feat.HasVibrate()) {
41+
feat.VibrateFloat(0.5F).get();
42+
}
43+
}
4744
}
48-
}
45+
assertEquals(wsdev.messages.poll(), "DeviceType;");
46+
assertEquals(wsdev.messages.poll(), "Vibrate:10;");
4947

50-
Thread.sleep(1000);
51-
assertTrue(client.stopAllDevices());
48+
Thread.sleep(500);
49+
assertTrue(client.stopAllDevices());
50+
assertEquals(wsdev.messages.poll(), "Vibrate:0;");
5251

53-
client.disconnect();
52+
client.disconnect();
53+
}
5454
}
5555

56-
@Disabled
5756
@Test
57+
@Disabled("See https://github.com/buttplugio/buttplug/issues/801")
5858
public void TestBattery() throws Exception {
59-
ButtplugClientWSClient client = new ButtplugClientWSClient("Java Test");
60-
client.connect(new URI("ws://localhost:12345/buttplug"));
61-
client.startScanning();
62-
63-
Thread.sleep(2000);
64-
client.requestDeviceList();
65-
for (ButtplugClientDevice dev : client.getDevices()) {
66-
if (dev.hasBatterySensor()) {
67-
long battery = dev.readBatteryLevel();
68-
System.out.println("Battery is " + battery);
69-
assertTrue(battery >= 0);
70-
assertTrue(battery <= 100);
71-
} else {
72-
assertThrows(ButtplugDeviceException.class, () -> {
73-
long battery = dev.readBatteryLevel();
74-
});
59+
try(IntifaceEngineWrapper wrapper = new IntifaceEngineWrapper() ) {
60+
Thread.sleep(500);
61+
WSDMClient wsdev = new WSDMClient(new URI("ws://localhost:" + wrapper.dport), "LVS-Fake", "A9816725B");
62+
Thread.sleep(500);
63+
64+
ButtplugClientWSClient client = new ButtplugClientWSClient("Java Test");
65+
client.connect(new URI("ws://localhost:" + wrapper.cport + "/buttplug"));
66+
client.startScanning();
67+
68+
Thread.sleep(500);
69+
client.requestDeviceList();
70+
for (ButtplugClientDevice dev : client.getDevices()) {
71+
for (ButtplugClientDeviceFeature feat : dev.getDeviceFeatures().values()) {
72+
if (feat.HasBattery()) {
73+
ButtplugMessage res = feat.ReadBattery().get();
74+
if (res instanceof InputReading && ((InputReading) res).getData() instanceof InputReading.BatteryData) {
75+
InputReading.BatteryData reading = (InputReading.BatteryData) ((InputReading) res).getData();
76+
int battery = reading.getValue();
77+
System.out.println("Battery is " + battery);
78+
assertTrue(battery >= 0);
79+
assertTrue(battery <= 100);
80+
}
81+
} else {
82+
assertThrows(ButtplugDeviceFeatureException.class, () -> {
83+
feat.ReadBattery().get();
84+
});
85+
}
86+
}
7587
}
76-
}
7788

78-
client.disconnect();
89+
client.disconnect();
90+
}
7991
}
8092
}

buttplug4j.utils.test/build.gradle

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@ repositories {
1212

1313
dependencies {
1414
api project(":buttplug4j")
15+
api 'org.eclipse.jetty.websocket:websocket-client:9.4.58.v20250814'
16+
api 'org.eclipse.jetty.websocket:websocket-api:9.4.58.v20250814'
1517
api 'org.junit.jupiter:junit-jupiter:5.13.4'
1618
}
1719

1820
java {
1921
toolchain {
20-
languageVersion = JavaLanguageVersion.of(11)
22+
languageVersion = JavaLanguageVersion.of(8)
2123
}
2224
withJavadocJar()
2325
withSourcesJar()

buttplug4j.utils.test/src/main/java/io/github/blackspherefollower/buttplug4j/utils/test/WSDMClient.java

Lines changed: 60 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,36 @@
33
import com.fasterxml.jackson.annotation.JsonProperty;
44
import com.fasterxml.jackson.core.JsonProcessingException;
55
import com.fasterxml.jackson.databind.ObjectMapper;
6+
import io.github.blackspherefollower.buttplug4j.client.ButtplugClient;
7+
import io.github.blackspherefollower.buttplug4j.client.IConnectedEvent;
8+
import io.github.blackspherefollower.buttplug4j.protocol.ButtplugConsts;
9+
import io.github.blackspherefollower.buttplug4j.protocol.ButtplugMessage;
10+
import io.github.blackspherefollower.buttplug4j.protocol.ButtplugProtocolException;
11+
import io.github.blackspherefollower.buttplug4j.protocol.messages.Error;
12+
import org.eclipse.jetty.util.component.LifeCycle;
13+
import org.eclipse.jetty.websocket.api.Session;
14+
import org.eclipse.jetty.websocket.api.annotations.*;
15+
import org.eclipse.jetty.websocket.api.extensions.Frame;
16+
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
17+
import org.eclipse.jetty.websocket.client.WebSocketClient;
618

719
import java.net.URI;
8-
import java.net.http.HttpClient;
9-
import java.net.http.WebSocket;
1020
import java.nio.ByteBuffer;
11-
import java.nio.CharBuffer;
1221
import java.nio.charset.StandardCharsets;
1322
import java.util.concurrent.*;
1423

24+
@WebSocket(maxTextMessageSize = 64 * 1024)
1525
public class WSDMClient {
1626

17-
private WebSocket client;
18-
private final WSDHeader header;
27+
private WebSocketClient client;
28+
private Session session;
29+
private WSDHeader header;
1930
public ConcurrentLinkedQueue<String> messages = new ConcurrentLinkedQueue<>();
20-
private final CompletableFuture<Boolean> connected = new CompletableFuture<>();
31+
private CompletableFuture<Boolean> connected = new CompletableFuture<>();
2132

2233
public int battery = 100;
2334

24-
static class WSDHeader {
35+
class WSDHeader {
2536
@JsonProperty("identifier")
2637
public String identifier;
2738
@JsonProperty("address")
@@ -34,82 +45,64 @@ public WSDMClient(final URI url, final String identifier, final String address)
3445
header = new WSDHeader();
3546
header.identifier = identifier;
3647
header.address = address;
48+
client = new WebSocketClient();
3749

38-
HttpClient
39-
.newHttpClient()
40-
.newWebSocketBuilder()
41-
.buildAsync(url, new WebSocketClient(this))
42-
.join();
43-
connected.get(10, TimeUnit.SECONDS);
44-
50+
client.start();
51+
client.connect(this, url, new ClientUpgradeRequest()).get(2, TimeUnit.SECONDS);
52+
connected.get(5, TimeUnit.SECONDS);
4553
}
4654

47-
private static class WebSocketClient implements WebSocket.Listener {
48-
WSDMClient wsdmclient;
49-
public WebSocketClient(WSDMClient wsdmclient) {this.wsdmclient = wsdmclient;}
50-
51-
@Override
52-
public void onOpen(WebSocket webSocket) {
53-
System.out.println("onOpen using subprotocol " + webSocket.getSubprotocol());
54-
wsdmclient.onConnect(webSocket);
55-
WebSocket.Listener.super.onOpen(webSocket);
56-
}
57-
58-
@Override
59-
public CompletionStage<?> onText(WebSocket webSocket, CharSequence data, boolean last) {
60-
System.out.println("onText received " + data);
61-
wsdmclient.onMessage(data.toString());
62-
return WebSocket.Listener.super.onText(webSocket, data, last);
63-
}
55+
protected void cleanup() {
6456

65-
@Override
66-
public void onError(WebSocket webSocket, Throwable error) {
67-
System.out.println("Bad day! " + webSocket.toString());
68-
error.printStackTrace();
69-
WebSocket.Listener.super.onError(webSocket, error);
70-
}
57+
if (session != null) {
58+
session.close();
59+
}
7160

72-
@Override
73-
public CompletionStage<?> onClose(WebSocket webSocket, int statusCode, String reason) {
74-
System.out.println("onClose received " + statusCode + " " + reason);
75-
wsdmclient.onClose(statusCode, reason);
76-
return WebSocket.Listener.super.onClose(webSocket, statusCode, reason);
61+
try {
62+
LifeCycle.stop(client);
63+
} catch (RuntimeException ignored) {
64+
}
65+
client = null;
7766
}
7867

79-
@Override
80-
public CompletionStage<?> onBinary(WebSocket webSocket, ByteBuffer message, boolean last) {
81-
System.out.println("onBinary received " + message);
82-
wsdmclient.onMessage(StandardCharsets.UTF_8.decode(message).toString());
83-
return WebSocket.Listener.super.onBinary(webSocket, message, last);
84-
}
85-
}
86-
87-
68+
@OnWebSocketClose
8869
public void onClose(int statusCode, String reason) {
89-
this.client = null;
70+
this.session = null;
71+
cleanup();
9072
}
9173

92-
public void onConnect(WebSocket client) {
93-
this.client = client;
74+
@OnWebSocketConnect
75+
public void onConnect(Session session) {
76+
this.session = session;
77+
9478
// Don't block the WS thread
9579
new Thread(() -> {
9680
try {
97-
client.sendText(new ObjectMapper().writeValueAsString(header), true).get(1, TimeUnit.SECONDS);
81+
session.getRemote().sendStringByFuture(new ObjectMapper().writeValueAsString(header)).get(1, TimeUnit.SECONDS);
9882
} catch (JsonProcessingException | ExecutionException | InterruptedException |TimeoutException e) {
9983
System.out.println("Failed to send header: " + e.getMessage());
10084
}
10185
}).start();
10286
}
10387

104-
public void onMessage(final String message) {
88+
@OnWebSocketFrame
89+
public void onFrame(final Frame frame) {
90+
if( frame.getType().isData()) {
91+
System.out.println("Got frame: " + frame);
92+
byte[] data = new byte[frame.getPayloadLength()];
93+
frame.getPayload().get(data);
94+
onMessage(null, new String(data, StandardCharsets.UTF_8));
95+
}
96+
}
97+
98+
@OnWebSocketMessage
99+
public void onMessage(final Session sess, final String message) {
105100
System.out.println("Got message: " + message);
106101
if(message.startsWith("DeviceType;")) {
107102
new Thread(() -> {
108103
try {
109-
sendMessage("Z:10:" + header.address + ";");
110-
if(!connected.isDone()) {
111-
connected.complete(true);
112-
}
104+
session.getRemote().sendBytesByFuture(ByteBuffer.wrap(("Z:10:" + header.address + ";").getBytes(StandardCharsets.UTF_8))).get(1, TimeUnit.SECONDS);
105+
connected.complete(true);
113106
} catch (InterruptedException | ExecutionException | TimeoutException e) {
114107
throw new RuntimeException(e);
115108
}
@@ -120,7 +113,7 @@ public void onMessage(final String message) {
120113
if(message.startsWith("Battery;")) {
121114
new Thread(() -> {
122115
try {
123-
sendMessage(battery + ";");
116+
session.getRemote().sendBytesByFuture(ByteBuffer.wrap((battery + ";").getBytes(StandardCharsets.UTF_8))).get(1, TimeUnit.SECONDS);
124117
connected.complete(true);
125118
} catch (InterruptedException | ExecutionException | TimeoutException e) {
126119
throw new RuntimeException(e);
@@ -132,7 +125,12 @@ public void onMessage(final String message) {
132125
messages.add(message);
133126
}
134127

128+
@OnWebSocketError
129+
public void onWebSocketError(final Throwable cause) {
130+
System.out.println("Got error: " + cause.getMessage());
131+
}
132+
135133
protected void sendMessage(final String msg) throws ExecutionException, InterruptedException, TimeoutException {
136-
client.sendBinary(ByteBuffer.wrap(msg.getBytes(StandardCharsets.UTF_8)), true).get(1, TimeUnit.SECONDS);
134+
session.getRemote().sendStringByFuture(msg).get(1, TimeUnit.SECONDS);
137135
}
138136
}

buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/client/ButtplugClientDevice.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
package io.github.blackspherefollower.buttplug4j.client;
22

33
import io.github.blackspherefollower.buttplug4j.protocol.ButtplugMessage;
4-
import io.github.blackspherefollower.buttplug4j.protocol.messages.Device;
5-
import io.github.blackspherefollower.buttplug4j.protocol.messages.DeviceFeature;
6-
import io.github.blackspherefollower.buttplug4j.protocol.messages.OutputCmd;
7-
import io.github.blackspherefollower.buttplug4j.protocol.messages.StopDeviceCmd;
4+
import io.github.blackspherefollower.buttplug4j.protocol.messages.*;
85

96
import java.util.HashMap;
107
import java.util.Map;
@@ -66,6 +63,11 @@ public Future<ButtplugMessage> sendOutputCommand(int featureIndex, OutputCmd.IOu
6663
return client.sendMessage(cmd);
6764
}
6865

66+
public Future<ButtplugMessage> sendInputCommand(int featureIndex, final String inputType, final InputCommandType inputCommand) {
67+
InputCmd cmd = new InputCmd(client.getNextMsgId(), deviceIndex, featureIndex, inputType, inputCommand);
68+
return client.sendMessage(cmd);
69+
}
70+
6971
@Override
7072
public boolean equals(Object o) {
7173
if (this == o) {

0 commit comments

Comments
 (0)