diff --git a/README.md b/README.md index ed0f199..e98c5b1 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,6 @@ Learn more about buttplug.io at https://buttplug.io - ## Platform Support Buttplug Java should run on any 1.8 or later JRE (including Android). @@ -19,6 +18,7 @@ library, so there's no need to add a separate dependency); see the 2 dependency import examples below. For Gradle: + ```groovy dependencies { implementation 'io.github.blackspherefollower:buttplug4j.connectors.jetty.websocket.client:3.1.180' @@ -26,6 +26,7 @@ dependencies { ``` For Maven: + ```xml @@ -38,11 +39,10 @@ For Maven: For an all-in-one example project, please see https://github.com/blackspherefollower/buttplug4j-example -In general, you'll want to create a `ButtplugClientWSClient` object in a scope where it will not get garbage collected +In general, you'll want to create a `ButtplugClientWSClient` object in a scope where it will not get garbage collected before you're done, add callbacks for error and event handling, then connect it to the address Intiface is listening on. Then you can scan for devices, iterate over them and send commands. - ### Snapshots Snapshot libraries from the buttplug4j repo are available via Maven from the Central Portal Snapshots @@ -51,6 +51,7 @@ repository: https://central.sonatype.com/repository/maven-snapshots Releases will be available from Maven Central. For Gradle: + ```groovy repositories { maven { @@ -59,11 +60,12 @@ repositories { } } dependencies { - implementation 'io.github.blackspherefollower:buttplug4j.connectors.jetty.websocket.client:3.1.+' + implementation 'io.github.blackspherefollower:buttplug4j.connectors.jetty.websocket.client:4.0.+' } ``` For Maven: + ```xml @@ -79,7 +81,7 @@ For Maven: io.github.blackspherefollower buttplug4j.connectors.jetty.websocket.client - [3.1-SNAPSHOT,) + [4.0-SNAPSHOT,) ``` diff --git a/build.gradle b/build.gradle index b0d3603..8f5ec0b 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ plugins { } group = 'io.github.blackspherefollower' -version = "3.1." + ("${System.env.GITHUB_RUN_NUMBER ?: 1}") + ("${System.env.GITHUB_REF_TYPE}" == "tag" ? "" : "-SNAPSHOT") +version = "4.0." + ("${System.env.GITHUB_RUN_NUMBER ?: 1}") + ("${System.env.GITHUB_REF_TYPE}" == "tag" ? "" : "-SNAPSHOT") nexusPublishing { repositories { diff --git a/buttplug4j.connectors.javax.websocket.client/build.gradle b/buttplug4j.connectors.javax.websocket.client/build.gradle index 7abe579..cc3ff7d 100644 --- a/buttplug4j.connectors.javax.websocket.client/build.gradle +++ b/buttplug4j.connectors.javax.websocket.client/build.gradle @@ -14,10 +14,12 @@ dependencies { api project(':buttplug4j.connectors.javax.websocket.common') api 'org.eclipse.jetty.websocket:websocket-javax-client:10.0.26' testImplementation 'org.junit.jupiter:junit-jupiter:5.13.4' + testImplementation project(':buttplug4j.utils.test') testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.13.4' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.13.4' } + java { toolchain { languageVersion = JavaLanguageVersion.of(11) @@ -25,6 +27,7 @@ java { withJavadocJar() withSourcesJar() } + checkstyle { configFile = file("${rootDir}/checkstyle.xml") showViolations = false diff --git a/buttplug4j.connectors.javax.websocket.client/src/main/java/io/github/blackspherefollower/buttplug4j/connectors/javax/websocket/client/ButtplugClientWSClient.java b/buttplug4j.connectors.javax.websocket.client/src/main/java/io/github/blackspherefollower/buttplug4j/connectors/javax/websocket/client/ButtplugClientWSClient.java index 3077172..f5d4ff8 100644 --- a/buttplug4j.connectors.javax.websocket.client/src/main/java/io/github/blackspherefollower/buttplug4j/connectors/javax/websocket/client/ButtplugClientWSClient.java +++ b/buttplug4j.connectors.javax.websocket.client/src/main/java/io/github/blackspherefollower/buttplug4j/connectors/javax/websocket/client/ButtplugClientWSClient.java @@ -42,7 +42,7 @@ public void connect(final URI url) throws IllegalStateException, DeploymentExcep // Restore and echo down the line setOnConnected(stashCallback); - if(stashCallback != null ) + if (stashCallback != null) stashCallback.onConnected(this); } diff --git a/buttplug4j.connectors.javax.websocket.client/src/test/java/io/github/blackspherefollower/buttplug4j/connectors/javax/websocket/client/ButtplugWSClientMockTest.java b/buttplug4j.connectors.javax.websocket.client/src/test/java/io/github/blackspherefollower/buttplug4j/connectors/javax/websocket/client/ButtplugWSClientMockTest.java index fd2fbda..fb1e0b6 100644 --- a/buttplug4j.connectors.javax.websocket.client/src/test/java/io/github/blackspherefollower/buttplug4j/connectors/javax/websocket/client/ButtplugWSClientMockTest.java +++ b/buttplug4j.connectors.javax.websocket.client/src/test/java/io/github/blackspherefollower/buttplug4j/connectors/javax/websocket/client/ButtplugWSClientMockTest.java @@ -1,53 +1,87 @@ package io.github.blackspherefollower.buttplug4j.connectors.javax.websocket.client; import io.github.blackspherefollower.buttplug4j.client.ButtplugClientDevice; +import io.github.blackspherefollower.buttplug4j.client.ButtplugClientDeviceFeature; +import io.github.blackspherefollower.buttplug4j.client.ButtplugDeviceFeatureException; +import io.github.blackspherefollower.buttplug4j.protocol.ButtplugMessage; +import io.github.blackspherefollower.buttplug4j.protocol.messages.InputReading; +import io.github.blackspherefollower.buttplug4j.utils.test.WSDMClient; +import io.github.blackspherefollower.buttplug4j.utils.test.IntifaceEngineWrapper; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import java.net.URI; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; public class ButtplugWSClientMockTest { - @Disabled @Test public void TestConnect() throws Exception { - ButtplugClientWSClient client = new ButtplugClientWSClient("Java Test"); - client.connect(new URI("ws://localhost:12345/buttplug")); - client.startScanning(); - - Thread.sleep(5000); - client.requestDeviceList(); - for (ButtplugClientDevice dev : client.getDevices()) { - if (dev.getScalarVibrateCount() > 0) { - dev.sendScalarVibrateCmd(0.5).get(); - } - } + try(IntifaceEngineWrapper wrapper = new IntifaceEngineWrapper() ) { + Thread.sleep(500); + WSDMClient wsdev = new WSDMClient(new URI("ws://localhost:" + wrapper.dport), "LVS-Fake", "A9816725B"); + Thread.sleep(500); + + ButtplugClientWSClient client = new ButtplugClientWSClient("Java Test"); + client.connect(new URI("ws://localhost:" + wrapper.cport + "/buttplug")); + client.startScanning(); - Thread.sleep(1000); - assertTrue(client.stopAllDevices()); + Thread.sleep(500); + client.requestDeviceList(); - Thread.sleep(60000); - for (ButtplugClientDevice dev : client.getDevices()) { - if (dev.getScalarVibrateCount() > 0) { - dev.sendScalarVibrateCmd(0.5).get(); + assertEquals(1, client.getDevices().size()); + for (ButtplugClientDevice dev : client.getDevices()) { + for (ButtplugClientDeviceFeature feat : dev.getDeviceFeatures().values()) { + if (feat.HasVibrate()) { + feat.VibrateFloat(0.5F).get(); + } + } } - } + assertEquals(wsdev.messages.poll(), "Vibrate:10;"); - Thread.sleep(1000); - assertTrue(client.stopAllDevices()); + Thread.sleep(500); + assertTrue(client.stopAllDevices()); + assertEquals(wsdev.messages.poll(), "Vibrate:0;"); - Thread.sleep(60000); - for (ButtplugClientDevice dev : client.getDevices()) { - if (dev.getScalarVibrateCount() > 0) { - dev.sendScalarVibrateCmd(0.5).get(); - } + client.disconnect(); } + } - Thread.sleep(1000); - assertTrue(client.stopAllDevices()); + @Test + @Disabled("See https://github.com/buttplugio/buttplug/issues/801") + public void TestBattery() throws Exception { + try(IntifaceEngineWrapper wrapper = new IntifaceEngineWrapper() ) { + Thread.sleep(500); + WSDMClient wsdev = new WSDMClient(new URI("ws://localhost:" + wrapper.dport), "LVS-Fake", "A9816725B"); + Thread.sleep(500); + + ButtplugClientWSClient client = new ButtplugClientWSClient("Java Test"); + client.connect(new URI("ws://localhost:" + wrapper.cport + "/buttplug")); + client.startScanning(); - client.disconnect(); + Thread.sleep(500); + client.requestDeviceList(); + for (ButtplugClientDevice dev : client.getDevices()) { + for (ButtplugClientDeviceFeature feat : dev.getDeviceFeatures().values()) { + if (feat.HasBattery()) { + ButtplugMessage res = feat.ReadBattery().get(); + if (res instanceof InputReading && ((InputReading) res).getData() instanceof InputReading.BatteryData) { + InputReading.BatteryData reading = (InputReading.BatteryData) ((InputReading) res).getData(); + int battery = reading.getValue(); + System.out.println("Battery is " + battery); + assertTrue(battery >= 0); + assertTrue(battery <= 100); + } + } else { + assertThrows(ButtplugDeviceFeatureException.class, () -> { + feat.ReadBattery().get(); + }); + } + } + } + + client.disconnect(); + } } } \ No newline at end of file diff --git a/buttplug4j.connectors.javax.websocket.common/build.gradle b/buttplug4j.connectors.javax.websocket.common/build.gradle index 445ad4a..7b5786c 100644 --- a/buttplug4j.connectors.javax.websocket.common/build.gradle +++ b/buttplug4j.connectors.javax.websocket.common/build.gradle @@ -22,6 +22,7 @@ java { withJavadocJar() withSourcesJar() } + checkstyle { configFile = file("${rootDir}/checkstyle.xml") showViolations = false diff --git a/buttplug4j.connectors.javax.websocket.common/src/main/java/io/github/blackspherefollower/buttplug4j/connectors/javax/websocket/common/ButtplugClientWSEndpoint.java b/buttplug4j.connectors.javax.websocket.common/src/main/java/io/github/blackspherefollower/buttplug4j/connectors/javax/websocket/common/ButtplugClientWSEndpoint.java index abdf341..785441e 100644 --- a/buttplug4j.connectors.javax.websocket.common/src/main/java/io/github/blackspherefollower/buttplug4j/connectors/javax/websocket/common/ButtplugClientWSEndpoint.java +++ b/buttplug4j.connectors.javax.websocket.common/src/main/java/io/github/blackspherefollower/buttplug4j/connectors/javax/websocket/common/ButtplugClientWSEndpoint.java @@ -93,7 +93,7 @@ protected final CompletableFuture sendMessage(final ButtplugMes if (session == null) { Error err = new Error("Bad WS state!", Error.ErrorClass.ERROR_UNKNOWN, ButtplugConsts.SYSTEM_MSG_ID); - if( getErrorReceived() != null) { + if (getErrorReceived() != null) { getErrorReceived().errorReceived(err); } return CompletableFuture.completedFuture(err); @@ -103,7 +103,7 @@ protected final CompletableFuture sendMessage(final ButtplugMes session.getAsyncRemote().sendText(getParser().formatJson(msg)).get(); } catch (Exception e) { Error err = new Error(e, msg.getId()); - if( getErrorReceived() != null) { + if (getErrorReceived() != null) { getErrorReceived().errorReceived(err); } return CompletableFuture.completedFuture(err); diff --git a/buttplug4j.connectors.javax.websocket.server/build.gradle b/buttplug4j.connectors.javax.websocket.server/build.gradle index 018e639..5a07e6b 100644 --- a/buttplug4j.connectors.javax.websocket.server/build.gradle +++ b/buttplug4j.connectors.javax.websocket.server/build.gradle @@ -14,10 +14,12 @@ dependencies { api project(':buttplug4j.connectors.javax.websocket.common') testImplementation 'org.eclipse.jetty.websocket:websocket-javax-server:10.0.26' testImplementation 'org.junit.jupiter:junit-jupiter:5.13.4' + testImplementation project(':buttplug4j.utils.test') testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.13.4' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.13.4' } + java { toolchain { languageVersion = JavaLanguageVersion.of(11) @@ -25,6 +27,7 @@ java { withJavadocJar() withSourcesJar() } + checkstyle { configFile = file("${rootDir}/checkstyle.xml") showViolations = false diff --git a/buttplug4j.connectors.javax.websocket.server/src/test/java/io/github/blackspherefollower/buttplug4j/connectors/javax/websocket/server/ButtplugClientWSServerMockTest.java b/buttplug4j.connectors.javax.websocket.server/src/test/java/io/github/blackspherefollower/buttplug4j/connectors/javax/websocket/server/ButtplugClientWSServerMockTest.java index c572413..5ecc96b 100644 --- a/buttplug4j.connectors.javax.websocket.server/src/test/java/io/github/blackspherefollower/buttplug4j/connectors/javax/websocket/server/ButtplugClientWSServerMockTest.java +++ b/buttplug4j.connectors.javax.websocket.server/src/test/java/io/github/blackspherefollower/buttplug4j/connectors/javax/websocket/server/ButtplugClientWSServerMockTest.java @@ -2,8 +2,11 @@ import io.github.blackspherefollower.buttplug4j.client.ButtplugClient; import io.github.blackspherefollower.buttplug4j.client.ButtplugClientDevice; +import io.github.blackspherefollower.buttplug4j.client.ButtplugClientDeviceFeature; import io.github.blackspherefollower.buttplug4j.client.IConnectedEvent; import io.github.blackspherefollower.buttplug4j.connectors.javax.websocket.common.ButtplugClientWSEndpoint; +import io.github.blackspherefollower.buttplug4j.utils.test.IntifaceEngineWrapper; +import io.github.blackspherefollower.buttplug4j.utils.test.WSDMClient; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.servlet.ServletContextHandler; @@ -15,29 +18,42 @@ import javax.websocket.server.ServerEndpointConfig; import java.io.IOException; import java.net.URI; -import java.util.concurrent.ExecutionException; +import java.util.concurrent.*; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; public class ButtplugClientWSServerMockTest { - @Disabled @Test public void TestConnect() throws Exception { - ButtplugClientWSServerExample server = new ButtplugClientWSServerExample(54321); - server.join(); + int lport = (int) (Math.random() * 63000) + 1025; + CompletableFuture testDone = new CompletableFuture<>(); + ButtplugClientWSServerExample server = new ButtplugClientWSServerExample(lport, testDone); + Thread.sleep(500); + + try (IntifaceEngineWrapper wrapper = new IntifaceEngineWrapper(lport) ) { + Thread.sleep(500); + WSDMClient wsdev = new WSDMClient(new URI("ws://localhost:" + wrapper.dport), "LVS-Fake", "A9816725B"); + testDone.get(10, TimeUnit.SECONDS); + server.join(); + assertEquals(wsdev.messages.poll(), "Vibrate:10;"); + assertEquals(wsdev.messages.poll(), "Vibrate:0;"); + } + } - class ButtplugClientWSServerExample { + static class ButtplugClientWSServerExample { private final Server server; private final ServerConnector connector; - public ButtplugClientWSServerExample(int port) throws Exception { + public ButtplugClientWSServerExample(int port, CompletableFuture testDone) throws Exception { server = new Server(); connector = new ServerConnector(server); connector.setPort(port); server.addConnector(connector); + CompletableFuture firstDevice = new CompletableFuture<>(); // Setup the basic application "context" for this application at "/" // This is also known as the handler tree (in jetty speak) @@ -57,6 +73,7 @@ public ButtplugClientWSServerExample(int port) throws Exception { public T getEndpointInstance(Class endpointClass) { if (endpointClass == ButtplugClientWSEndpoint.class) { ButtplugClientWSServer client = new ButtplugClientWSServer("Java WS Server Buttplug Client"); + client.setDeviceAdded(dev -> firstDevice.complete(true)); client.setOnConnected(new IConnectedEvent() { @Override public void onConnected(ButtplugClient client) { @@ -71,50 +88,22 @@ public void onConnected(ButtplugClient client) { client.startScanning(); - Thread.sleep(5000); - client.requestDeviceList(); - for ( - ButtplugClientDevice dev : client.getDevices()) { - if (dev.getScalarVibrateCount() > 0) { - dev.sendScalarVibrateCmd(0.5).get(); - } - } - - Thread.sleep(1000); - - assertTrue(client.stopAllDevices()); - - Thread.sleep(60000); - for ( - ButtplugClientDevice dev : client.getDevices()) { - if (dev.getScalarVibrateCount() > 0) { - dev.sendScalarVibrateCmd(0.5).get(); + firstDevice.get(5, TimeUnit.SECONDS); + for (ButtplugClientDevice dev : client.getDevices()) { + for(ButtplugClientDeviceFeature feat : dev.getDeviceFeatures().values()) { + if( feat.HasVibrate() ) { + feat.VibrateFloat(0.5F).get(); + } } } - Thread.sleep(1000); - - assertTrue(client.stopAllDevices()); - - Thread.sleep(60000); - for ( - ButtplugClientDevice dev : client.getDevices()) { - if (dev.getScalarVibrateCount() > 0) { - dev.sendScalarVibrateCmd(0.5).get(); - } - } - - Thread.sleep(1000); + Thread.sleep(100); assertTrue(client.stopAllDevices()); client.disconnect(); - } catch (ExecutionException e) { - throw new RuntimeException(e); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } catch (IOException e) { - throw new RuntimeException(e); + server.stop(); + testDone.complete(true); } catch (Exception e) { throw new RuntimeException(e); } diff --git a/buttplug4j.connectors.jetty.websocket.client/build.gradle b/buttplug4j.connectors.jetty.websocket.client/build.gradle index d7bff7a..61af4e9 100644 --- a/buttplug4j.connectors.jetty.websocket.client/build.gradle +++ b/buttplug4j.connectors.jetty.websocket.client/build.gradle @@ -15,17 +15,23 @@ dependencies { api 'org.eclipse.jetty.websocket:websocket-client:9.4.58.v20250814' api 'org.eclipse.jetty.websocket:websocket-api:9.4.58.v20250814' testImplementation 'org.junit.jupiter:junit-jupiter:5.13.4' + testImplementation project(':buttplug4j.utils.test') testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.13.4' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.13.4' } java { toolchain { - languageVersion = JavaLanguageVersion.of(8) + languageVersion = JavaLanguageVersion.of(11) } withJavadocJar() withSourcesJar() } + +tasks.compileJava { + options.release.set(8) +} + checkstyle { configFile = file("${rootDir}/checkstyle.xml") showViolations = false diff --git a/buttplug4j.connectors.jetty.websocket.client/src/main/java/io/github/blackspherefollower/buttplug4j/connectors/jetty/websocket/client/ButtplugClientWSClient.java b/buttplug4j.connectors.jetty.websocket.client/src/main/java/io/github/blackspherefollower/buttplug4j/connectors/jetty/websocket/client/ButtplugClientWSClient.java index 1200910..447e353 100644 --- a/buttplug4j.connectors.jetty.websocket.client/src/main/java/io/github/blackspherefollower/buttplug4j/connectors/jetty/websocket/client/ButtplugClientWSClient.java +++ b/buttplug4j.connectors.jetty.websocket.client/src/main/java/io/github/blackspherefollower/buttplug4j/connectors/jetty/websocket/client/ButtplugClientWSClient.java @@ -51,7 +51,7 @@ public void connect(final URI url) throws Exception { // Restore and echo down the line setOnConnected(stashCallback); - if(stashCallback != null ) + if (stashCallback != null) stashCallback.onConnected(this); } @@ -125,8 +125,8 @@ protected CompletableFuture sendMessage(final ButtplugMessage m CompletableFuture promise = scheduleWait(msg.getId(), new CompletableFuture<>()); if (session == null) { Error err = new Error("Bad WS state!", - Error.ErrorClass.ERROR_UNKNOWN, ButtplugConsts.SYSTEM_MSG_ID); - if( getErrorReceived() != null) { + Error.ErrorClass.ERROR_UNKNOWN, ButtplugConsts.SYSTEM_MSG_ID); + if (getErrorReceived() != null) { getErrorReceived().errorReceived(err); } return CompletableFuture.completedFuture(err); @@ -136,7 +136,7 @@ protected CompletableFuture sendMessage(final ButtplugMessage m session.getRemote().sendStringByFuture(getParser().formatJson(msg)).get(); } catch (Exception e) { Error err = new Error(e, msg.getId()); - if( getErrorReceived() != null) { + if (getErrorReceived() != null) { getErrorReceived().errorReceived(err); } return CompletableFuture.completedFuture(err); diff --git a/buttplug4j.connectors.jetty.websocket.client/src/test/java/io/github/blackspherefollower/buttplug4j/connectors/jetty/websocket/client/ButtplugClientWSJettyClientTest.java b/buttplug4j.connectors.jetty.websocket.client/src/test/java/io/github/blackspherefollower/buttplug4j/connectors/jetty/websocket/client/ButtplugClientWSJettyClientTest.java index 8f3e7f4..1bf6c6a 100644 --- a/buttplug4j.connectors.jetty.websocket.client/src/test/java/io/github/blackspherefollower/buttplug4j/connectors/jetty/websocket/client/ButtplugClientWSJettyClientTest.java +++ b/buttplug4j.connectors.jetty.websocket.client/src/test/java/io/github/blackspherefollower/buttplug4j/connectors/jetty/websocket/client/ButtplugClientWSJettyClientTest.java @@ -1,81 +1,93 @@ package io.github.blackspherefollower.buttplug4j.connectors.jetty.websocket.client; import io.github.blackspherefollower.buttplug4j.client.ButtplugClientDevice; -import io.github.blackspherefollower.buttplug4j.client.ButtplugDeviceException; +import io.github.blackspherefollower.buttplug4j.client.ButtplugClientDeviceFeature; +import io.github.blackspherefollower.buttplug4j.client.ButtplugDeviceFeatureException; +import io.github.blackspherefollower.buttplug4j.utils.test.WSDMClient; +import io.github.blackspherefollower.buttplug4j.utils.test.IntifaceEngineWrapper; +import io.github.blackspherefollower.buttplug4j.protocol.ButtplugMessage; +import io.github.blackspherefollower.buttplug4j.protocol.messages.InputReading; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import java.io.File; +import java.io.IOException; import java.net.URI; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; public class ButtplugClientWSJettyClientTest { - @Disabled + @Test public void TestConnect() throws Exception { - ButtplugClientWSClient client = new ButtplugClientWSClient("Java Test"); - client.connect(new URI("ws://localhost:12345/buttplug")); - client.startScanning(); - - Thread.sleep(5000); - client.requestDeviceList(); - for (ButtplugClientDevice dev : client.getDevices()) { - if (dev.getScalarVibrateCount() > 0) { - dev.sendScalarVibrateCmd(0.5).get(); + try(IntifaceEngineWrapper wrapper = new IntifaceEngineWrapper() ) { + Thread.sleep(500); + WSDMClient wsdev = new WSDMClient(new URI("ws://localhost:" + wrapper.dport), "LVS-Fake", "A9816725B"); + Thread.sleep(500); + + ButtplugClientWSClient client = new ButtplugClientWSClient("Java Test"); + client.connect(new URI("ws://localhost:" + wrapper.cport + "/buttplug")); + client.startScanning(); + + Thread.sleep(500); + client.requestDeviceList(); + + assertEquals(1, client.getDevices().size()); + for (ButtplugClientDevice dev : client.getDevices()) { + for (ButtplugClientDeviceFeature feat : dev.getDeviceFeatures().values()) { + if (feat.HasVibrate()) { + feat.VibrateFloat(0.5F).get(); + } + } } - } - - Thread.sleep(1000); - assertTrue(client.stopAllDevices()); - Thread.sleep(60000); - for (ButtplugClientDevice dev : client.getDevices()) { - if (dev.getScalarVibrateCount() > 0) { - dev.sendScalarVibrateCmd(0.5).get(); - } - } + Thread.sleep(500); + assertEquals("Vibrate:10;", wsdev.messages.poll()); - Thread.sleep(1000); - assertTrue(client.stopAllDevices()); + assertTrue(client.stopAllDevices()); + Thread.sleep(500); + assertEquals("Vibrate:0;", wsdev.messages.poll()); - Thread.sleep(60000); - for (ButtplugClientDevice dev : client.getDevices()) { - if (dev.getScalarVibrateCount() > 0) { - dev.sendScalarVibrateCmd(0.5).get(); - } + client.disconnect(); } - - Thread.sleep(1000); - assertTrue(client.stopAllDevices()); - - client.disconnect(); } - @Disabled @Test + @Disabled("See https://github.com/buttplugio/buttplug/issues/801") public void TestBattery() throws Exception { - ButtplugClientWSClient client = new ButtplugClientWSClient("Java Test"); - client.connect(new URI("ws://localhost:12345/buttplug")); - client.startScanning(); - - Thread.sleep(2000); - client.requestDeviceList(); - for (ButtplugClientDevice dev : client.getDevices()) { - if (dev.hasBatterySensor()) { - long battery = dev.readBatteryLevel(); - System.out.println("Battery is " + battery); - assertTrue(battery >= 0); - assertTrue(battery <= 100); - } else { - assertThrows(ButtplugDeviceException.class, () -> { - long battery = dev.readBatteryLevel(); - }); + try(IntifaceEngineWrapper wrapper = new IntifaceEngineWrapper() ) { + Thread.sleep(500); + WSDMClient wsdev = new WSDMClient(new URI("ws://localhost:" + wrapper.dport), "LVS-Fake", "A9816725B"); + Thread.sleep(500); + + ButtplugClientWSClient client = new ButtplugClientWSClient("Java Test"); + client.connect(new URI("ws://localhost:" + wrapper.cport + "/buttplug")); + client.startScanning(); + + Thread.sleep(500); + client.requestDeviceList(); + for (ButtplugClientDevice dev : client.getDevices()) { + for (ButtplugClientDeviceFeature feat : dev.getDeviceFeatures().values()) { + if (feat.HasBattery()) { + ButtplugMessage res = feat.ReadBattery().get(); + if (res instanceof InputReading && ((InputReading) res).getData() instanceof InputReading.BatteryData) { + InputReading.BatteryData reading = (InputReading.BatteryData) ((InputReading) res).getData(); + int battery = reading.getValue(); + System.out.println("Battery is " + battery); + assertTrue(battery >= 0); + assertTrue(battery <= 100); + } + } else { + assertThrows(ButtplugDeviceFeatureException.class, () -> { + feat.ReadBattery().get(); + }); + } + } } - } - client.disconnect(); + client.disconnect(); + } } } \ No newline at end of file diff --git a/buttplug4j.utils.mdns/build.gradle b/buttplug4j.utils.mdns/build.gradle index b21f992..f3116dd 100644 --- a/buttplug4j.utils.mdns/build.gradle +++ b/buttplug4j.utils.mdns/build.gradle @@ -18,13 +18,19 @@ dependencies { testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.13.4' } + java { toolchain { - languageVersion = JavaLanguageVersion.of(8) + languageVersion = JavaLanguageVersion.of(11) } withJavadocJar() withSourcesJar() } + +tasks.compileJava { + options.release.set(8) +} + checkstyle { configFile = file("${rootDir}/checkstyle.xml") showViolations = false diff --git a/buttplug4j.utils.mdns/src/main/java/io/github/blackspherefollower/buttplug4j/utils/mdns/ButtplugDiscover.java b/buttplug4j.utils.mdns/src/main/java/io/github/blackspherefollower/buttplug4j/utils/mdns/ButtplugDiscover.java index a1df373..0dbbec5 100644 --- a/buttplug4j.utils.mdns/src/main/java/io/github/blackspherefollower/buttplug4j/utils/mdns/ButtplugDiscover.java +++ b/buttplug4j.utils.mdns/src/main/java/io/github/blackspherefollower/buttplug4j/utils/mdns/ButtplugDiscover.java @@ -12,18 +12,23 @@ public final class ButtplugDiscover implements ServiceListener { private final ConcurrentHashMap> servers = new ConcurrentHashMap<>(); + private DiscovereyEventHandler discovereyEventHandler = null; - public interface DiscovereyEventHandler { - public void FoundButtplug(String name, Set addresses); - public void LostButtplug(String name); + public ButtplugDiscover(DiscovereyEventHandler discovereyEventHandler) { + this.discovereyEventHandler = discovereyEventHandler; + JmmDNS jmmdns = JmmDNS.Factory.getInstance(); + jmmdns.addServiceListener("_intiface_engine._tcp.local.", this); + } + + public ButtplugDiscover() { + JmmDNS jmmdns = JmmDNS.Factory.getInstance(); + jmmdns.addServiceListener("_intiface_engine._tcp.local.", this); } public void setDiscovereyEventHandler(DiscovereyEventHandler discovereyEventHandler) { this.discovereyEventHandler = discovereyEventHandler; } - private DiscovereyEventHandler discovereyEventHandler = null; - @Override public void serviceAdded(ServiceEvent event) { } @@ -31,7 +36,7 @@ public void serviceAdded(ServiceEvent event) { @Override public void serviceRemoved(ServiceEvent event) { servers.remove(event.getInfo().getName()); - if( discovereyEventHandler != null ) { + if (discovereyEventHandler != null) { discovereyEventHandler.LostButtplug(event.getInfo().getName()); } } @@ -42,34 +47,31 @@ public void serviceResolved(ServiceEvent event) { ConcurrentSkipListSet set = new ConcurrentSkipListSet<>(); for (Inet4Address addr : event.getInfo().getInet4Addresses()) { try { - set.add(new URI("ws://" + addr.getHostAddress()+ ":" + event.getInfo().getPort())); + set.add(new URI("ws://" + addr.getHostAddress() + ":" + event.getInfo().getPort())); } catch (URISyntaxException e) { // Log weirdness } - }; + } ConcurrentSkipListSet set2 = servers.putIfAbsent(event.getName(), set); - if( set2 != null ) { - for( URI uri : set) { + if (set2 != null) { + for (URI uri : set) { set2.add(uri); } } - if( discovereyEventHandler != null ) { + if (discovereyEventHandler != null) { discovereyEventHandler.FoundButtplug(event.getName(), set); } } - public ButtplugDiscover(DiscovereyEventHandler discovereyEventHandler) { - this.discovereyEventHandler = discovereyEventHandler; - JmmDNS jmmdns = JmmDNS.Factory.getInstance(); - jmmdns.addServiceListener("_intiface_engine._tcp.local.", this); + public ConcurrentHashMap> GetServers() { + return servers; } - public ButtplugDiscover() { - JmmDNS jmmdns = JmmDNS.Factory.getInstance(); - jmmdns.addServiceListener("_intiface_engine._tcp.local.", this); - } + public interface DiscovereyEventHandler { + void FoundButtplug(String name, Set addresses); - public ConcurrentHashMap> GetServers() { return servers; } + void LostButtplug(String name); + } } diff --git a/buttplug4j.utils.test/build.gradle b/buttplug4j.utils.test/build.gradle new file mode 100644 index 0000000..b98272d --- /dev/null +++ b/buttplug4j.utils.test/build.gradle @@ -0,0 +1,47 @@ +plugins { + id 'java-library' + id 'jacoco' + id 'maven-publish' + id 'checkstyle' + id 'signing' +} + +repositories { + mavenCentral() +} + +dependencies { + api project(":buttplug4j") + api 'org.junit.jupiter:junit-jupiter:5.13.4' +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(11) + } + withJavadocJar() + withSourcesJar() +} +checkstyle { + configFile = file("${rootDir}/checkstyle.xml") + showViolations = false + ignoreFailures = true +} +test { + finalizedBy jacocoTestReport + finalizedBy check +} +jacocoTestReport { + dependsOn test +} +tasks.named('test') { + useJUnitPlatform() +} + +jacocoTestReport { + reports { + xml.required = true + csv.required = true + } +} + diff --git a/buttplug4j.utils.test/src/main/java/io/github/blackspherefollower/buttplug4j/utils/test/IntifaceEngineWrapper.java b/buttplug4j.utils.test/src/main/java/io/github/blackspherefollower/buttplug4j/utils/test/IntifaceEngineWrapper.java new file mode 100644 index 0000000..3172aa2 --- /dev/null +++ b/buttplug4j.utils.test/src/main/java/io/github/blackspherefollower/buttplug4j/utils/test/IntifaceEngineWrapper.java @@ -0,0 +1,76 @@ +package io.github.blackspherefollower.buttplug4j.utils.test; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; + +public class IntifaceEngineWrapper implements Closeable { + + public final int cport; + public final int dport; + private Process proc = null; + private Path userConfig = null; + + public IntifaceEngineWrapper() throws Exception { + cport = (int) (Math.random() * 63000) + 1025; + dport = (int) (Math.random() * 63000) + 1025; + + setup(true); + } + public IntifaceEngineWrapper(int lport) throws Exception { + cport = lport; + dport = (int) (Math.random() * 63000) + 1025; + + setup(false); + } + + public void setup(boolean listen) throws Exception { + try{ + Runtime.getRuntime().exec("intiface-engine --version").waitFor(); + } catch (IOException e) { + org.junit.jupiter.api.Assumptions.abort( "intiface-engine not found, skipping tests"); + } + + Path userConfig = Files.createTempFile("user-config_", ".json"); + try ( + InputStream in = new BufferedInputStream( + getClass().getResourceAsStream("/user-config.json")); + OutputStream out = new BufferedOutputStream( + Files.newOutputStream(userConfig.toFile().toPath()))) { + + byte[] buffer = new byte[1024]; + int lengthRead; + while ((lengthRead = in.read(buffer)) > 0) { + out.write(buffer, 0, lengthRead); + out.flush(); + } + } + + ProcessBuilder pb = + new ProcessBuilder("intiface-engine", listen ? "--websocket-port" : "--websocket-client-address", listen ? String.valueOf(cport) : ("ws://127.0.0.1:" + String.valueOf(cport) + "/bob"), "--use-device-websocket-server", "--user-device-config-file", userConfig.toAbsolutePath().toString(), "--device-websocket-server-port", String.valueOf(dport), "--log", "TRACE"); + + Path log = Files.createTempFile("intiface_", ".log"); + pb.redirectErrorStream(true); + pb.redirectOutput(ProcessBuilder.Redirect.appendTo(log.toFile())); + proc = pb.start(); + + Thread.sleep(500); + if( !proc.isAlive()) { + close(); + org.junit.jupiter.api.Assumptions.abort( "intiface-engine not started, skipping tests"); + } + } + + @Override + public void close() { + if(proc != null) { + proc.destroyForcibly(); + } + if(userConfig != null) { + try { + Files.deleteIfExists(userConfig); + } catch (IOException ignore) { + } + } + } +} \ No newline at end of file diff --git a/buttplug4j.utils.test/src/main/java/io/github/blackspherefollower/buttplug4j/utils/test/WSDMClient.java b/buttplug4j.utils.test/src/main/java/io/github/blackspherefollower/buttplug4j/utils/test/WSDMClient.java new file mode 100644 index 0000000..8169d77 --- /dev/null +++ b/buttplug4j.utils.test/src/main/java/io/github/blackspherefollower/buttplug4j/utils/test/WSDMClient.java @@ -0,0 +1,138 @@ +package io.github.blackspherefollower.buttplug4j.utils.test; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.WebSocket; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.*; + +public class WSDMClient { + + private WebSocket client; + private final WSDHeader header; + public ConcurrentLinkedQueue messages = new ConcurrentLinkedQueue<>(); + private final CompletableFuture connected = new CompletableFuture<>(); + + public int battery = 100; + + static class WSDHeader { + @JsonProperty("identifier") + public String identifier; + @JsonProperty("address") + public String address; + @JsonProperty("version") + public int version = 0; + } + + public WSDMClient(final URI url, final String identifier, final String address) throws Exception { + header = new WSDHeader(); + header.identifier = identifier; + header.address = address; + + HttpClient + .newHttpClient() + .newWebSocketBuilder() + .buildAsync(url, new WebSocketClient(this)) + .join(); + connected.get(10, TimeUnit.SECONDS); + + } + + private static class WebSocketClient implements WebSocket.Listener { + WSDMClient wsdmclient; + public WebSocketClient(WSDMClient wsdmclient) {this.wsdmclient = wsdmclient;} + + @Override + public void onOpen(WebSocket webSocket) { + System.out.println("onOpen using subprotocol " + webSocket.getSubprotocol()); + wsdmclient.onConnect(webSocket); + WebSocket.Listener.super.onOpen(webSocket); + } + + @Override + public CompletionStage onText(WebSocket webSocket, CharSequence data, boolean last) { + System.out.println("onText received " + data); + wsdmclient.onMessage(data.toString()); + return WebSocket.Listener.super.onText(webSocket, data, last); + } + + @Override + public void onError(WebSocket webSocket, Throwable error) { + System.out.println("Bad day! " + webSocket.toString()); + error.printStackTrace(); + WebSocket.Listener.super.onError(webSocket, error); + } + + @Override + public CompletionStage onClose(WebSocket webSocket, int statusCode, String reason) { + System.out.println("onClose received " + statusCode + " " + reason); + wsdmclient.onClose(statusCode, reason); + return WebSocket.Listener.super.onClose(webSocket, statusCode, reason); + } + + @Override + public CompletionStage onBinary(WebSocket webSocket, ByteBuffer message, boolean last) { + System.out.println("onBinary received " + message); + wsdmclient.onMessage(StandardCharsets.UTF_8.decode(message).toString()); + return WebSocket.Listener.super.onBinary(webSocket, message, last); + } + } + + + public void onClose(int statusCode, String reason) { + this.client = null; + } + + public void onConnect(WebSocket client) { + this.client = client; + // Don't block the WS thread + new Thread(() -> { + try { + client.sendText(new ObjectMapper().writeValueAsString(header), true).get(1, TimeUnit.SECONDS); + } catch (JsonProcessingException | ExecutionException | InterruptedException |TimeoutException e) { + System.out.println("Failed to send header: " + e.getMessage()); + } + }).start(); + } + + public void onMessage(final String message) { + System.out.println("Got message: " + message); + if(message.startsWith("DeviceType;")) { + new Thread(() -> { + try { + sendMessage("Z:10:" + header.address + ";"); + if(!connected.isDone()) { + connected.complete(true); + } + } catch (InterruptedException | ExecutionException | TimeoutException e) { + throw new RuntimeException(e); + } + + }).start(); + return; + } + if(message.startsWith("Battery;")) { + new Thread(() -> { + try { + sendMessage(battery + ";"); + connected.complete(true); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + throw new RuntimeException(e); + } + + }).start(); + return; + } + messages.add(message); + } + + protected void sendMessage(final String msg) throws ExecutionException, InterruptedException, TimeoutException { + client.sendBinary(ByteBuffer.wrap(msg.getBytes(StandardCharsets.UTF_8)), true).get(1, TimeUnit.SECONDS); + } + } diff --git a/buttplug4j.utils.test/src/main/resources/user-config.json b/buttplug4j.utils.test/src/main/resources/user-config.json new file mode 100644 index 0000000..79abe72 --- /dev/null +++ b/buttplug4j.utils.test/src/main/resources/user-config.json @@ -0,0 +1,21 @@ +{ + "version": { + "major": 4, + "minor": 0 + }, + "user_configs": { + "protocols": { + "lovense": { + "communication": [ + { + "websocket": { + "name": "LVS-Fake" + } + } + ], + "configurations": [] + } + }, + "devices": [] + } +} \ No newline at end of file diff --git a/buttplug4j/build.gradle b/buttplug4j/build.gradle index e29468e..ba9ea31 100644 --- a/buttplug4j/build.gradle +++ b/buttplug4j/build.gradle @@ -14,17 +14,24 @@ dependencies { api 'com.fasterxml.jackson.core:jackson-annotations:2.19.2' api 'com.fasterxml.jackson.core:jackson-databind:2.19.2' testImplementation 'org.junit.jupiter:junit-jupiter:5.13.4' + testImplementation 'dev.harrel:json-schema:1.8.2' + testImplementation 'org.mockito:mockito-core:5.20.0' testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.13.4' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.13.4' } java { toolchain { - languageVersion = JavaLanguageVersion.of(8) + languageVersion = JavaLanguageVersion.of(11) } withJavadocJar() withSourcesJar() } + +tasks.compileJava { + options.release.set(8) +} + checkstyle { configFile = file("${rootDir}/checkstyle.xml") showViolations = false diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/client/ButtplugClient.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/client/ButtplugClient.java index 689687e..0a949d8 100644 --- a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/client/ButtplugClient.java +++ b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/client/ButtplugClient.java @@ -4,20 +4,16 @@ import io.github.blackspherefollower.buttplug4j.protocol.ButtplugDeviceMessage; import io.github.blackspherefollower.buttplug4j.protocol.ButtplugJsonMessageParser; import io.github.blackspherefollower.buttplug4j.protocol.ButtplugMessage; -import io.github.blackspherefollower.buttplug4j.protocol.messages.Error; import io.github.blackspherefollower.buttplug4j.protocol.messages.*; -import io.github.blackspherefollower.buttplug4j.protocol.messages.Parts.DeviceMessageInfo; +import io.github.blackspherefollower.buttplug4j.protocol.messages.Error; import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Timer; -import java.util.TimerTask; +import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; -import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicInteger; /** * ButtplugClient is the abstract class containing the bulk of the logic for communicating with the Buttplug.io sever @@ -31,14 +27,15 @@ public abstract class ButtplugClient { static final int MAX_DISCONNECT_MESSAGE_TRYS = 3; private final ButtplugJsonMessageParser parser; private final String clientName; - private final ConcurrentHashMap> waitingMsgs = new ConcurrentHashMap<>(); - private final ConcurrentHashMap devices = new ConcurrentHashMap<>(); - private final AtomicLong msgId = new AtomicLong(1); + private final ConcurrentHashMap> waitingMsgs = new ConcurrentHashMap<>(); + private final ConcurrentHashMap devices = new ConcurrentHashMap<>(); + private final AtomicInteger msgId = new AtomicInteger(1); private final Object sendLock = new Object(); private ConnectionState connectionState = ConnectionState.DISCONNECTED; private Timer pingTimer; - private IDeviceEvent deviceAdded; - private IDeviceEvent deviceRemoved; + private IDeviceAddedEvent deviceAdded; + private IDeviceChangedEvent deviceChanged; + private IDeviceRemovedEvent deviceRemoved; private IScanningEvent scanningFinished; private IErrorEvent errorReceived; private ISensorReadingEvent sensorReadingReceived; @@ -65,12 +62,71 @@ public final boolean isConnected() { return connectionState == ConnectionState.CONNECTED; } - public final long getNextMsgId() { + public final int getNextMsgId() { return msgId.getAndIncrement(); } public final void onMessage(final List msgs) { for (ButtplugMessage msg : msgs) { + if (msg instanceof DeviceList) { + ArrayList curDevs = new ArrayList(); + for (Map.Entry dev : devices.entrySet()) { + curDevs.add(dev.getKey()); + } + + HashMap newDevices = ((DeviceList) msg).getDevices(); + ArrayList newDevs = new ArrayList(newDevices.keySet()); + + curDevs.sort(Integer::compare); + newDevs.sort(Integer::compare); + + int curIdx = 0; + int newIdx = 0; + while (curIdx < curDevs.size() && newIdx < newDevs.size()) { + int compare = curDevs.get(curIdx) - newDevs.get(newIdx); + + if (compare > 0) { + devices.put(newDevs.get(newIdx), new ButtplugClientDevice(this, newDevices.get(newDevs.get(newIdx)))); + if (getDeviceAdded() != null) { + getDeviceAdded().deviceAdded(devices.get(newDevs.get(newIdx))); + } + newIdx++; + } else if (compare < 0) { + devices.remove(curDevs.get(curIdx)); + if (getDeviceRemoved() != null) { + getDeviceRemoved().deviceRemoved(curDevs.get(curIdx)); + } + curIdx++; + } else { + // Same index, diff to see if updated + ButtplugClientDevice newDev = new ButtplugClientDevice(this, newDevices.get(newDevs.get(newIdx))); + if (!newDev.equals(devices.get(curDevs.get(curIdx)))) { + devices.put(newDevs.get(newIdx), newDev); + if (getDeviceChanged() != null) { + getDeviceChanged().deviceChanged(devices.get(newDevs.get(newIdx))); + } + } + newIdx++; + curIdx++; + } + } + + while (curIdx < curDevs.size()) { + devices.remove(curDevs.get(curIdx)); + if (getDeviceRemoved() != null) { + getDeviceRemoved().deviceRemoved(curDevs.get(curIdx)); + } + curIdx++; + } + while (newIdx < newDevs.size()) { + devices.put(newDevs.get(newIdx), new ButtplugClientDevice(this, newDevices.get(newDevs.get(newIdx)))); + if (getDeviceAdded() != null) { + getDeviceAdded().deviceAdded(devices.get(newDevs.get(newIdx))); + } + newIdx++; + } + } + if (msg.getId() > 0) { CompletableFuture val = waitingMsgs.remove(msg.getId()); if (val != null) { @@ -79,19 +135,7 @@ public final void onMessage(final List msgs) { } } - if (msg instanceof DeviceAdded) { - ButtplugClientDevice device = new ButtplugClientDevice(this, (DeviceAdded) msg); - devices.put(((DeviceAdded) msg).getDeviceIndex(), device); - if (getDeviceAdded() != null) { - getDeviceAdded().deviceAdded(device); - } - } else if (msg instanceof DeviceRemoved) { - if (devices.remove(((DeviceRemoved) msg).getDeviceIndex()) != null) { - if (getDeviceRemoved() != null) { - getDeviceRemoved().deviceRemoved(((DeviceRemoved) msg).getDeviceIndex()); - } - } - } else if (msg instanceof ScanningFinished) { + if (msg instanceof ScanningFinished) { if (getScanningFinished() != null) { getScanningFinished().scanningFinished(); } @@ -99,9 +143,9 @@ public final void onMessage(final List msgs) { if (getErrorReceived() != null) { getErrorReceived().errorReceived((Error) msg); } - } else if (msg instanceof SensorReading) { + } else if (msg instanceof InputReading) { if (getSensorReadingReceived() != null) { - getSensorReadingReceived().sensorReadingReceived((SensorReading) msg); + getSensorReadingReceived().sensorReadingReceived((InputReading) msg); } } } @@ -170,30 +214,14 @@ private void onPingTimer() throws ButtplugClientException, ExecutionException, I } public final void requestDeviceList() throws ButtplugClientException, ExecutionException, InterruptedException { - ButtplugMessage res = sendMessage(new RequestDeviceList(msgId.incrementAndGet())).get(); - if (!(res instanceof DeviceList) || ((DeviceList) res).getDevices() == null) { - if (res instanceof Error) { - throw new ButtplugClientException(((Error) res).getErrorMessage()); - } - return; - } - - for (DeviceMessageInfo d : ((DeviceList) res).getDevices()) { - if (!devices.containsKey(d.getDeviceIndex())) { - ButtplugClientDevice device = new ButtplugClientDevice(this, d); - if (devices.put(d.getDeviceIndex(), device) == null) { - if (getDeviceAdded() != null) { - getDeviceAdded().deviceAdded(device); - } - } - } + Object res = sendMessage(new RequestDeviceList(msgId.incrementAndGet())).get(); + if (res instanceof Error) { + throw new ButtplugClientException(((Error) res).getErrorMessage()); } } public final List getDevices() { - List devicesCopy = new ArrayList<>(); - devicesCopy.addAll(this.devices.values()); - return devicesCopy; + return new ArrayList<>(this.devices.values()); } public final boolean startScanning() throws ExecutionException, InterruptedException, IOException { @@ -224,13 +252,6 @@ public final CompletableFuture sendDeviceMessage( final ButtplugClientDevice device, final ButtplugDeviceMessage deviceMsg) { ButtplugClientDevice dev = devices.get(device.getDeviceIndex()); if (dev != null) { - if (!dev.getDeviceMessages().containsKey(deviceMsg.getClass().getSimpleName())) { - return CompletableFuture.completedFuture(new Error( - "Device does not accept message type: " - + deviceMsg.getClass().getSimpleName(), - Error.ErrorClass.ERROR_DEVICE, ButtplugConsts.SYSTEM_MSG_ID)); - } - deviceMsg.setDeviceIndex(device.getDeviceIndex()); deviceMsg.setId(msgId.incrementAndGet()); return sendMessage(deviceMsg); @@ -247,19 +268,27 @@ protected final boolean waitForOk(final Future msg) protected abstract CompletableFuture sendMessage(ButtplugMessage msg); - public final IDeviceEvent getDeviceAdded() { + public final IDeviceAddedEvent getDeviceAdded() { return deviceAdded; } - public final void setDeviceAdded(final IDeviceEvent deviceAddedHandler) { + public final void setDeviceAdded(final IDeviceAddedEvent deviceAddedHandler) { this.deviceAdded = deviceAddedHandler; } - public final IDeviceEvent getDeviceRemoved() { + public final IDeviceChangedEvent getDeviceChanged() { + return deviceChanged; + } + + public final void setDeviceChanged(final IDeviceChangedEvent deviceChangedHandler) { + this.deviceChanged = deviceChangedHandler; + } + + public final IDeviceRemovedEvent getDeviceRemoved() { return deviceRemoved; } - public final void setDeviceRemoved(final IDeviceEvent deviceRemovedHandler) { + public final void setDeviceRemoved(final IDeviceRemovedEvent deviceRemovedHandler) { this.deviceRemoved = deviceRemovedHandler; } @@ -306,8 +335,8 @@ public final void disconnect() { cleanup(); int max = MAX_DISCONNECT_MESSAGE_TRYS; - while (max-- > 0 && waitingMsgs.size() != 0) { - for (long waitMmsgId : waitingMsgs.keySet()) { + while (max-- > 0 && !waitingMsgs.isEmpty()) { + for (int waitMmsgId : waitingMsgs.keySet()) { CompletableFuture val = waitingMsgs.remove(waitMmsgId); if (val != null) { val.complete(new Error("Connection closed!", @@ -319,7 +348,7 @@ public final void disconnect() { msgId.set(1); } - protected final CompletableFuture scheduleWait(final long id, + protected final CompletableFuture scheduleWait(final int id, final CompletableFuture promise) { waitingMsgs.put(id, promise); return promise; diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/client/ButtplugClientDevice.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/client/ButtplugClientDevice.java index 1635cc3..8a86e5e 100644 --- a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/client/ButtplugClientDevice.java +++ b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/client/ButtplugClientDevice.java @@ -2,534 +2,100 @@ import io.github.blackspherefollower.buttplug4j.protocol.ButtplugMessage; import io.github.blackspherefollower.buttplug4j.protocol.messages.*; -import io.github.blackspherefollower.buttplug4j.protocol.messages.Parts.*; -import io.github.blackspherefollower.buttplug4j.util.Pair; -import java.util.ArrayList; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.ExecutionException; +import java.util.Objects; import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; public class ButtplugClientDevice { - private final long deviceIndex; - - private final String name; - - private final String displayName; - - private final Integer messageTimingGap; - - private final Map deviceMessages; private final ButtplugClient client; + private final int deviceIndex; + private final String deviceName; + private final String deviceDisplayName; + private final HashMap deviceFeatures = new HashMap<>(); + private Integer deviceMessageTimingGap = null; - public ButtplugClientDevice(final ButtplugClient bpClient, final DeviceMessageInfo deviceMessageInfo) { - this.client = bpClient; - this.deviceIndex = deviceMessageInfo.getDeviceIndex(); - this.name = deviceMessageInfo.getDeviceName(); - this.displayName = deviceMessageInfo.getDeviceDisplayName() != null - && !deviceMessageInfo.getDeviceDisplayName().isEmpty() - ? deviceMessageInfo.getDeviceDisplayName() : deviceMessageInfo.getDeviceName(); - this.messageTimingGap = deviceMessageInfo.getDeviceMessageTimingGap(); - this.deviceMessages = new HashMap<>(); - for (DeviceMessage deviceMessage : deviceMessageInfo.getDeviceMessages()) { - this.deviceMessages.put(deviceMessage.getMessage(), deviceMessage.getAttributes()); - } - } - - public ButtplugClientDevice(final ButtplugClient bpClient, final DeviceAdded deviceAdded) { - this.client = bpClient; - this.deviceIndex = deviceAdded.getDeviceIndex(); - this.name = deviceAdded.getDeviceName(); - this.displayName = deviceAdded.getDeviceDisplayName() != null - && !deviceAdded.getDeviceDisplayName().isEmpty() - ? deviceAdded.getDeviceDisplayName() : deviceAdded.getDeviceName(); - this.messageTimingGap = deviceAdded.getDeviceMessageTimingGap(); - this.deviceMessages = new HashMap<>(); - for (DeviceMessage deviceMessage : deviceAdded.getDeviceMessages()) { - this.deviceMessages.put(deviceMessage.getMessage(), deviceMessage.getAttributes()); - } - } - - public ButtplugClientDevice(final ButtplugClient bpClient, final DeviceRemoved deviceRemoved) { + public ButtplugClientDevice(final ButtplugClient bpClient, final Device device) { this.client = bpClient; - this.deviceIndex = deviceRemoved.getDeviceIndex(); - this.name = ""; - this.displayName = ""; - this.messageTimingGap = null; - this.deviceMessages = new HashMap<>(); - } - - public final Future sendStopDeviceCmd() { - return client.sendDeviceMessage(this, new StopDeviceCmd(getDeviceIndex(), - client.getNextMsgId())); - } - - public final long getScalarCount(final String actuatorType) { - MessageAttributes attrs = getDeviceMessages().get("ScalarCmd"); - if (!(attrs instanceof GenericMessageAttributes)) { - return 0; - } - GenericMessageAttributes gattrs = (GenericMessageAttributes) attrs; - return gattrs.getFeatures().stream().filter(genericFeatureAttributes -> - genericFeatureAttributes.getActuatorType().contentEquals(actuatorType)).count(); - } - - public final Future sendScalarCmd(final String actuatorType, final double scalar) - throws ButtplugDeviceException { - - MessageAttributes attrs = getDeviceMessages().get("ScalarCmd"); - if (!(attrs instanceof GenericMessageAttributes)) { - throw new ButtplugDeviceException("Device doesn't support ScalarCmd!"); - } - - long count = 0; - - ArrayList> values = new ArrayList<>(); - GenericMessageAttributes gattrs = (GenericMessageAttributes) attrs; - for (GenericFeatureAttributes attr : gattrs.getFeatures()) { - if (attr.getActuatorType().contentEquals(actuatorType)) { - values.add(new Pair(scalar, actuatorType)); - count++; - } else { - values.add(null); - } - } - if (count == 0) { - throw new ButtplugDeviceException("Device doesn't have any ScalarCmd features that support " - + actuatorType + "!"); - } - - return client.sendDeviceMessage(this, new ScalarCmd(getDeviceIndex(), - values.toArray(new Pair[]{}), client.getNextMsgId())); - } - - public final Future sendScalarCmd(final String actuatorType, final long index, final double scalar) - throws ButtplugDeviceException { - - MessageAttributes attrs = getDeviceMessages().get("ScalarCmd"); - if (!(attrs instanceof GenericMessageAttributes)) { - throw new ButtplugDeviceException("Device doesn't support ScalarCmd!"); - } - - long count = 0; - - ArrayList> values = new ArrayList<>(); - GenericMessageAttributes gattrs = (GenericMessageAttributes) attrs; - for (GenericFeatureAttributes attr : gattrs.getFeatures()) { - if (attr.getActuatorType().contentEquals(actuatorType) && count++ == index) { - values.add(new Pair(scalar, actuatorType)); - } else { - values.add(null); + this.deviceIndex = device.getDeviceIndex(); + this.deviceName = device.getDeviceName(); + this.deviceDisplayName = device.getDeviceDisplayName() != null + && !device.getDeviceDisplayName().isEmpty() + ? device.getDeviceDisplayName() : device.getDeviceName(); + this.deviceMessageTimingGap = device.getDeviceMessageTimingGap(); + if (device.getDeviceFeatures() != null) { + for (Map.Entry feature : device.getDeviceFeatures().entrySet()) { + this.deviceFeatures.put(feature.getKey(), new ButtplugClientDeviceFeature(this, feature.getValue())); } } - if (count == 0) { - throw new ButtplugDeviceException("Device doesn't have any ScalarCmd features that support " - + actuatorType + " at index " + index + "!"); - } - - return client.sendDeviceMessage(this, new ScalarCmd(getDeviceIndex(), - values.toArray(new Pair[]{}), client.getNextMsgId())); } - public final Future sendScalarCmd(final String actuatorType, final Double[] scalars) - throws ButtplugDeviceException { - - MessageAttributes attrs = getDeviceMessages().get("ScalarCmd"); - if (!(attrs instanceof GenericMessageAttributes)) { - throw new ButtplugDeviceException("Device doesn't support ScalarCmd!"); - } - - int count = 0; - int index = 0; - HashMap indexMap = new HashMap<>(); - - ArrayList> values = new ArrayList<>(); - GenericMessageAttributes gattrs = (GenericMessageAttributes) attrs; - for (GenericFeatureAttributes attr : gattrs.getFeatures()) { - if (attr.getActuatorType().contentEquals(actuatorType)) { - indexMap.put(count++, index); - } - values.add(null); - index++; - } - if (count == 0) { - throw new ButtplugDeviceException("Device doesn't have any ScalarCmd features that support " - + actuatorType + "!"); - } - - for (index = 0; index < scalars.length; index++) { - if (scalars[index] == null) { - continue; - } - if (index >= count) { - throw new ButtplugDeviceException("Device doesn't have any ScalarCmd features that support " - + actuatorType + " at index " + index + "!"); - } - values.set(indexMap.get(index), new Pair<>(scalars[index], actuatorType)); - } - - - return client.sendDeviceMessage(this, new ScalarCmd(getDeviceIndex(), - values.toArray(new Pair[]{}), client.getNextMsgId())); - } - - public final Future sendScalarCmd(final Pair[] scalars) - throws ButtplugDeviceException { - - MessageAttributes attrs = getDeviceMessages().get("ScalarCmd"); - if (!(attrs instanceof GenericMessageAttributes)) { - throw new ButtplugDeviceException("Device doesn't support ScalarCmd!"); - } - - GenericMessageAttributes gattrs = (GenericMessageAttributes) attrs; - for (int index = 0; index < scalars.length; index++) { - if (index >= gattrs.getFeatures().size()) { - throw new ButtplugDeviceException("Device doesn't have any ScalarCmd features at index " + index + "!"); - } - if (!scalars[index].getRight().contentEquals(gattrs.getFeatures().get(index).getActuatorType())) { - throw new ButtplugDeviceException("Device doesn't have a ScalarCmd feature of type " - + scalars[index].getRight() + " at index " + index + " (found " - + gattrs.getFeatures().get(index).getActuatorType() + " instead)!"); - } - } - - return client.sendDeviceMessage(this, new ScalarCmd(getDeviceIndex(), - scalars, client.getNextMsgId())); - } - - public final Future sendScalarVibrateCmd(final double scalar) - throws ButtplugDeviceException { - return sendScalarCmd("Vibrate", scalar); - } - - public final long getScalarVibrateCount() { - return getScalarCount("Vibrate"); - } - - public final Future sendScalarRotateCmd(final double scalar) - throws ButtplugDeviceException { - return sendScalarCmd("Rotate", scalar); - } - - public final long getScalarRotateCount() { - return getScalarCount("Rotate"); + public final Future sendStopDeviceCmd() { + return client.sendMessage(new StopDeviceCmd(client.getNextMsgId(), getDeviceIndex())); } - public final Future sendScalarOscillateCmd(final double scalar) - throws ButtplugDeviceException { - return sendScalarCmd("Oscillate", scalar); + public final int getDeviceIndex() { + return deviceIndex; } - public final long getScalarOscillateCount() { - return getScalarCount("Oscillate"); + public final String getName() { + return deviceName; } - public final Future sendRotateCmd(final double speed, final boolean clockwise) - throws ButtplugDeviceException { - - MessageAttributes attrs = getDeviceMessages().get("RotateCmd"); - if (!(attrs instanceof GenericMessageAttributes)) { - throw new ButtplugDeviceException("Device doesn't support RotateCmd!"); - } - - long count = 0; - - ArrayList values = new ArrayList<>(); - GenericMessageAttributes gattrs = (GenericMessageAttributes) attrs; - for (GenericFeatureAttributes attr : gattrs.getFeatures()) { - values.add(new RotateCmd.RotateSubCmd(count++, speed, clockwise)); - } - if (count == 0) { - throw new ButtplugDeviceException("Device doesn't have any Rotate features!"); - } - - return client.sendDeviceMessage(this, new RotateCmd(getDeviceIndex(), - values.toArray(new RotateCmd.RotateSubCmd[]{}), client.getNextMsgId())); + public final String getDisplayName() { + return deviceDisplayName; } - public final Future sendRotateCmd(final long index, final double speed, - final boolean clockwise) - throws ButtplugDeviceException { - - MessageAttributes attrs = getDeviceMessages().get("RotateCmd"); - if (!(attrs instanceof GenericMessageAttributes)) { - throw new ButtplugDeviceException("Device doesn't support RotateCmd!"); - } - - long count = 0; - - ArrayList values = new ArrayList<>(); - GenericMessageAttributes gattrs = (GenericMessageAttributes) attrs; - if (index < 0 || index >= gattrs.getFeatures().size()) { - throw new ButtplugDeviceException("Device doesn't have a RotateCmd feature at index " + index + "!"); - } - - for (GenericFeatureAttributes attr : gattrs.getFeatures()) { - if (count == index) { - values.add(new RotateCmd.RotateSubCmd(count++, speed, clockwise)); - } else { - values.add(null); - count++; - } - } - - return client.sendDeviceMessage(this, new RotateCmd(index, - values.toArray(new RotateCmd.RotateSubCmd[]{}), client.getNextMsgId())); + public final Integer getMessageTimingGap() { + return deviceMessageTimingGap; } - public final Future sendRotateCmd(final Pair[] vectors) - throws ButtplugDeviceException { - - MessageAttributes attrs = getDeviceMessages().get("Rotate"); - if (!(attrs instanceof GenericMessageAttributes)) { - throw new ButtplugDeviceException("Device doesn't support RotateCmd!"); - } - - ArrayList values = new ArrayList<>(); - GenericMessageAttributes gattrs = (GenericMessageAttributes) attrs; - for (GenericFeatureAttributes attr : gattrs.getFeatures()) { - values.add(null); - } - - for (int index = 0; index < vectors.length; index++) { - if (index >= gattrs.getFeatures().size()) { - throw new ButtplugDeviceException("Device doesn't have a RotateCmd feature at index " + index + "!"); - } - values.set(index, new RotateCmd.RotateSubCmd(index, vectors[index].getLeft(), vectors[index].getRight())); - } - - return client.sendDeviceMessage(this, new RotateCmd(getDeviceIndex(), - values.toArray(new RotateCmd.RotateSubCmd[]{}), client.getNextMsgId())); + public final Map getDeviceFeatures() { + return deviceFeatures; } - public final Future sendLinearCmd(final double position, final long duration) - throws ButtplugDeviceException { - - MessageAttributes attrs = getDeviceMessages().get("LinearCmd"); - if (!(attrs instanceof GenericMessageAttributes)) { - throw new ButtplugDeviceException("Device doesn't support LinearCmd!"); - } - - long count = 0; - - ArrayList values = new ArrayList<>(); - GenericMessageAttributes gattrs = (GenericMessageAttributes) attrs; - for (GenericFeatureAttributes attr : gattrs.getFeatures()) { - values.add(new LinearCmd.LinearSubCmd(count++, position, duration)); - } - if (count == 0) { - throw new ButtplugDeviceException("Device doesn't have any LinearCmd features!"); - } - - return client.sendDeviceMessage(this, new LinearCmd(getDeviceIndex(), - values.toArray(new LinearCmd.LinearSubCmd[]{}), client.getNextMsgId())); + public Future sendOutputCommand(int featureIndex, OutputCmd.IOutputCommand outputCommand) { + OutputCmd cmd = new OutputCmd(client.getNextMsgId(), deviceIndex, featureIndex); + cmd.setCommand(outputCommand); + return client.sendMessage(cmd); } - public final Future sendLinearCmd(final long index, final double position, - final long duration) - throws ButtplugDeviceException { - - MessageAttributes attrs = getDeviceMessages().get("LinearCmd"); - if (!(attrs instanceof GenericMessageAttributes)) { - throw new ButtplugDeviceException("Device doesn't support LinearCmd!"); - } - - long count = 0; - - ArrayList values = new ArrayList<>(); - GenericMessageAttributes gattrs = (GenericMessageAttributes) attrs; - if (index < 0 || index >= gattrs.getFeatures().size()) { - throw new ButtplugDeviceException("Device doesn't have a LinearCmd feature at index " + index + "!"); - } - - for (GenericFeatureAttributes attr : gattrs.getFeatures()) { - if (count == index) { - values.add(new LinearCmd.LinearSubCmd(count++, position, duration)); - } else { - values.add(null); - count++; - } - } - - return client.sendDeviceMessage(this, new LinearCmd(getDeviceIndex(), - values.toArray(new LinearCmd.LinearSubCmd[]{}), client.getNextMsgId())); + public Future sendInputCommand(int featureIndex, final String inputType, final InputCommandType inputCommand) { + InputCmd cmd = new InputCmd(client.getNextMsgId(), deviceIndex, featureIndex, inputType, inputCommand); + return client.sendMessage(cmd); } - public final Future sendLinearCmd(final Pair[] vectors) - throws ButtplugDeviceException { - - MessageAttributes attrs = getDeviceMessages().get("LinearCmd"); - if (!(attrs instanceof GenericMessageAttributes)) { - throw new ButtplugDeviceException("Device doesn't support LinearCmd!"); + @Override + public boolean equals(Object o) { + if (this == o) { + return true; } - - ArrayList values = new ArrayList<>(); - GenericMessageAttributes gattrs = (GenericMessageAttributes) attrs; - for (GenericFeatureAttributes attr : gattrs.getFeatures()) { - values.add(null); + if (o == null || getClass() != o.getClass()) { + return false; } - - for (int index = 0; index < vectors.length; index++) { - if (index >= gattrs.getFeatures().size()) { - throw new ButtplugDeviceException("Device doesn't have a LinearCmd feature at index " + index + "!"); - } - values.set(index, new LinearCmd.LinearSubCmd(index, vectors[index].getLeft(), vectors[index].getRight())); + ButtplugClientDevice that = (ButtplugClientDevice) o; + if (deviceIndex != that.deviceIndex || + !deviceName.equals(that.deviceName) || + !deviceDisplayName.equals(that.deviceDisplayName) || + !Objects.equals(deviceMessageTimingGap, that.deviceMessageTimingGap) || + (deviceFeatures == null) != (that.deviceFeatures == null)) { + return false; } - - return client.sendDeviceMessage(this, new LinearCmd(getDeviceIndex(), - values.toArray(new LinearCmd.LinearSubCmd[]{}), client.getNextMsgId())); - } - - /** - * Sends a command to read a sensor value from the device. - *

- * This method constructs and sends a sensor read command to the device for the specified sensor type and index. - * It checks if the device supports the "SensorReadCmd" attribute before attempting to send the command. - * If the device does not support this attribute, or if the command cannot be sent, a {@link ButtplugDeviceException} - * is thrown. - *

- * - * @param sensorIndex The index of the sensor feature to read. This value specifies which sensor data - * to retrieve from the device. - * @param sensorType The type of sensor to read (e.g., "Battery"). This value indicates the specific - * type of sensor data requested. - * - * @return A {@link Future} representing the pending result of the sensor read command. - * Once completed, the {@link ButtplugMessage} returned by the Future will contain - * the sensor data if the command succeeds. - * - * @throws ButtplugDeviceException if the device does not support "SensorReadCmd" or if - * the sensor read command could not be created or sent. - */ - public final Future sendSensorReadCmd(final int sensorIndex, final String sensorType) - throws ButtplugDeviceException { - - MessageAttributes attrs = getDeviceMessages().get("SensorReadCmd"); - if (!(attrs instanceof SensorMessageAttributes)) { - throw new ButtplugDeviceException("Device doesn't support SensorReadCmd!"); + if (deviceFeatures == null) { + return true; } - - final SensorReadCmd cmd = new SensorReadCmd(this.deviceIndex, client.getNextMsgId()); - cmd.setSensorType(sensorType); - cmd.setSensorIndex(sensorIndex); - - return client.sendDeviceMessage(this, cmd); - } - - /** - * Checks if the device has a battery sensor feature. - *

- * This method verifies if the device's message attributes include a "Battery" sensor feature - * by looking for the presence of "SensorReadCmd" in the device's messages. If found, - * it then checks if one of the sensor features corresponds to "Battery". - *

- * - * @return {@code true} if the device has a "Battery" sensor feature, {@code false} otherwise. - */ - public final boolean hasBatterySensor() { - MessageAttributes attrs = getDeviceMessages().get("SensorReadCmd"); - if (!(attrs instanceof SensorMessageAttributes)) { + if (deviceFeatures.size() != that.deviceFeatures.size() || + !deviceFeatures.keySet().containsAll(that.deviceFeatures.keySet())) { return false; } - - SensorMessageAttributes sensorAttrs = (SensorMessageAttributes) attrs; - - boolean hasBatteryLevel = sensorAttrs.getFeatures().stream().anyMatch( - featureAttributes -> "Battery".equals(featureAttributes.getSensorType()) - ); - - return hasBatteryLevel; - } - - /** - * Reads the battery level of the device. - *

- * This method queries the device to retrieve its battery level as a percentage from 0 to 100. - * Before calling this method, use {@link #hasBatterySensor()} to verify that the device supports - * a "Battery" sensor feature. If the device lacks this feature or returns an unexpected message type, - * an exception is thrown. - *

- * - * @return The battery level of the device, in the range of 0 to 100. - * - * @throws ButtplugDeviceException if the device does not support "SensorReadCmd", - * does not have a "Battery" feature, or returns an invalid response. - * @throws InterruptedException if the thread is interrupted while waiting for a response from the device. - * @throws ExecutionException if an exception occurred during the execution of the sensor read command. - * @throws TimeoutException if the response from the device took more than 2 seconds - */ - public final long readBatteryLevel() throws ButtplugDeviceException, InterruptedException, ExecutionException, TimeoutException { - MessageAttributes attrs = getDeviceMessages().get("SensorReadCmd"); - if (!(attrs instanceof SensorMessageAttributes)) { - throw new ButtplugDeviceException("Device doesn't support SensorReadCmd!"); - } - - SensorMessageAttributes sensorAttrs = (SensorMessageAttributes) attrs; - int index = -1; - boolean found = false; - for (SensorFeatureAttributes featureAttributes : sensorAttrs.getFeatures()) { - index++; - if ("Battery".equals(featureAttributes.getSensorType())) { - found = true; - break; + for (Integer feat : deviceFeatures.keySet()) { + if (!deviceFeatures.get(feat).equals(that.deviceFeatures.get(feat))) { + return false; } } - - if (!found) { - throw new ButtplugDeviceException("Device doesn't have Battery feature!"); - } - - Future sensorReadFuture = sendSensorReadCmd(index, "Battery"); - ButtplugMessage message = sensorReadFuture.get(2, TimeUnit.SECONDS); - if (!(message instanceof SensorReading)) { - throw new ButtplugDeviceException("Invalid ButtplugMessage returned. Expecting SensorReading and got " + message.getClass()); - } - - SensorReading sensorReading = (SensorReading) message; - byte singleByte = sensorReading.getData()[0]; - long result = (singleByte & 0xFF); - return result; - } - - public final long getRotateCount() { - return getFeatureCount("RotateCmd"); - } - - public final long getLinearCount() { - return getFeatureCount("LinearCmd"); - } - - private long getFeatureCount(final String command) { - MessageAttributes attrs = getDeviceMessages().get(command); - if (!(attrs instanceof GenericMessageAttributes)) { - return 0; - } - GenericMessageAttributes gattrs = (GenericMessageAttributes) attrs; - return gattrs.getFeatures().size(); - } - - public final long getDeviceIndex() { - return deviceIndex; - } - - public final String getName() { - return name; - } - - public final String getDisplayName() { - return displayName; - } - - public final Integer getMessageTimingGap() { - return messageTimingGap; - } - - public final Map getDeviceMessages() { - return deviceMessages; + return true; } } diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/client/ButtplugClientDeviceFeature.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/client/ButtplugClientDeviceFeature.java new file mode 100644 index 0000000..119e414 --- /dev/null +++ b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/client/ButtplugClientDeviceFeature.java @@ -0,0 +1,258 @@ +package io.github.blackspherefollower.buttplug4j.client; + +import io.github.blackspherefollower.buttplug4j.protocol.ButtplugMessage; +import io.github.blackspherefollower.buttplug4j.protocol.messages.DeviceFeature; +import io.github.blackspherefollower.buttplug4j.protocol.messages.InputCmd; +import io.github.blackspherefollower.buttplug4j.protocol.messages.InputCommandType; +import io.github.blackspherefollower.buttplug4j.protocol.messages.OutputCmd; + +import java.util.HashMap; +import java.util.concurrent.Future; + +public class ButtplugClientDeviceFeature { + + private final ButtplugClientDevice device; + private final String description; + private final HashMap output; + private final HashMap input; + private final int featureIndex; + + public ButtplugClientDeviceFeature(final ButtplugClientDevice device, final DeviceFeature feature) { + this.device = device; + this.featureIndex = feature.getFeatureIndex(); + this.description = feature.getFeatureDescription(); + this.output = new HashMap<>(); + if (feature.getOutput() != null) { + feature.getOutput().forEach(outputDescriptor -> this.output.put(outputDescriptor.getClass().getSimpleName(), outputDescriptor)); + } + this.input = new HashMap<>(); + if (feature.getInput() != null) { + feature.getInput().forEach(inputDescriptor -> this.input.put(inputDescriptor.getClass().getSimpleName(), inputDescriptor)); + } + } + + private int GetStepFromFloat(final String type, final float value) throws ButtplugDeviceFeatureException { + if (value < 0.0f || value > 1.0f) { + throw new ButtplugDeviceFeatureException("Range error"); + } + DeviceFeature.OutputDescriptor desc = output.get(type); + if (desc == null) { + throw new ButtplugDeviceFeatureException(type); + } + if (desc instanceof DeviceFeature.SteppedOutputDescriptor) { + double steps = ((DeviceFeature.SteppedOutputDescriptor) desc).getValue()[1]; + steps *= value; + return (int) Math.floor(steps); + } else if (desc instanceof DeviceFeature.PositionWithDuration) { + double steps = ((DeviceFeature.PositionWithDuration) desc).getPosition()[1]; + steps *= value; + return (int) Math.floor(steps); + } else { + throw new ButtplugDeviceFeatureException(type); + } + } + + private void CheckStepRange(final String type, final float value) throws ButtplugDeviceFeatureException { + DeviceFeature.OutputDescriptor desc = output.get(type); + if (desc == null) { + throw new ButtplugDeviceFeatureException(type); + } + if (desc instanceof DeviceFeature.SteppedOutputDescriptor) { + int steps = ((DeviceFeature.SteppedOutputDescriptor) desc).getValue()[1]; + if (value > steps || value < 0) { + throw new ButtplugDeviceFeatureException("Range error"); + } + } else if (desc instanceof DeviceFeature.PositionWithDuration) { + int steps = ((DeviceFeature.PositionWithDuration) desc).getPosition()[1]; + if (value > steps || value < 0) { + throw new ButtplugDeviceFeatureException("Range error"); + } + } else { + throw new ButtplugDeviceFeatureException(type); + } + } + + public boolean HasVibrate() { + return output.get("Vibrate") != null; + } + + public Future Vibrate(final int vibrate) throws ButtplugDeviceFeatureException { + CheckStepRange("Vibrate", vibrate); + return device.sendOutputCommand(featureIndex, new OutputCmd.Vibrate(vibrate)); + } + + public Future VibrateFloat(final float vibrate) throws ButtplugDeviceFeatureException { + return device.sendOutputCommand(featureIndex, new OutputCmd.Vibrate(GetStepFromFloat("Vibrate", vibrate))); + } + + public boolean HasRotate() { + return output.get("Rotate") != null; + } + + public Future Rotate(final int rotate) throws ButtplugDeviceFeatureException { + CheckStepRange("Rotate", rotate); + return device.sendOutputCommand(featureIndex, new OutputCmd.Rotate(rotate)); + } + + public Future RotateFloat(final float rotate) throws ButtplugDeviceFeatureException { + return device.sendOutputCommand(featureIndex, new OutputCmd.Rotate(GetStepFromFloat("Rotate", rotate))); + } + + public boolean HasConstrict() { + return output.get("Constrict") != null; + } + + public Future Constrict(final int constrict) throws ButtplugDeviceFeatureException { + CheckStepRange("Constrict", constrict); + return device.sendOutputCommand(featureIndex, new OutputCmd.Constrict(constrict)); + } + + public Future ConstrictFloat(final float constrict) throws ButtplugDeviceFeatureException { + return device.sendOutputCommand(featureIndex, new OutputCmd.Constrict(GetStepFromFloat("Constrict", constrict))); + } + + public boolean HasSpray() { + return output.get("Spray") != null; + } + + public Future Spray(final int spray) throws ButtplugDeviceFeatureException { + CheckStepRange("Spray", spray); + return device.sendOutputCommand(featureIndex, new OutputCmd.Spray(spray)); + } + + public Future SprayFloat(final float spray) throws ButtplugDeviceFeatureException { + return device.sendOutputCommand(featureIndex, new OutputCmd.Spray(GetStepFromFloat("Spray", spray))); + } + + public boolean HasPosition() { + return output.get("Position") != null; + } + + public Future Position(final int position) throws ButtplugDeviceFeatureException { + CheckStepRange("Position", position); + return device.sendOutputCommand(featureIndex, new OutputCmd.Position(position)); + } + + public Future PositionFloat(final float position) throws ButtplugDeviceFeatureException { + return device.sendOutputCommand(featureIndex, new OutputCmd.Position(GetStepFromFloat("Position", position))); + } + + public boolean HasPositionWithDuration() { + return output.get("PositionWithDuration") != null; + } + + public Future PositionWithDuration(final int position, final int duration) throws ButtplugDeviceFeatureException { + CheckStepRange("PositionWithDuration", position); + return device.sendOutputCommand(featureIndex, new OutputCmd.PositionWithDuration(position, duration)); + } + + public Future PositionWithDurationFloat(final float position, final int duration) throws ButtplugDeviceFeatureException { + return device.sendOutputCommand(featureIndex, new OutputCmd.PositionWithDuration(GetStepFromFloat("PositionWithDuration", position), duration)); + } + + public boolean HasLed() { + return output.get("Led") != null; + } + + public Future Led(final int led) throws ButtplugDeviceFeatureException { + CheckStepRange("Led", led); + return device.sendOutputCommand(featureIndex, new OutputCmd.Led(led)); + } + + public Future LedFloat(final float led) throws ButtplugDeviceFeatureException { + return device.sendOutputCommand(featureIndex, new OutputCmd.Led(GetStepFromFloat("Led", led))); + } + + public boolean HasOscillate() { + return output.get("Oscillate") != null; + } + + public Future Oscillate(final int oscillate) throws ButtplugDeviceFeatureException { + CheckStepRange("Oscillate", oscillate); + return device.sendOutputCommand(featureIndex, new OutputCmd.Oscillate(oscillate)); + } + + public Future OscillateFloat(final float oscillate) throws ButtplugDeviceFeatureException { + return device.sendOutputCommand(featureIndex, new OutputCmd.Oscillate(GetStepFromFloat("Oscillate", oscillate))); + } + + public boolean HasTemperature() { + return output.get("Temperature") != null; + } + + public Future Temperature(final int temperature) throws ButtplugDeviceFeatureException { + CheckStepRange("Temperature", temperature); + return device.sendOutputCommand(featureIndex, new OutputCmd.Temperature(temperature)); + } + + public Future TemperatureFloat(final float temperature) throws ButtplugDeviceFeatureException { + return device.sendOutputCommand(featureIndex, new OutputCmd.Temperature(GetStepFromFloat("Temperature", temperature))); + } + + private void CheckInput(final String type) throws ButtplugDeviceFeatureException { + if (input.get(type) == null) { + throw new ButtplugDeviceFeatureException(type); + } + } + + public boolean HasBattery() { + return input.get("Battery") != null; + } + + public Future ReadBattery() throws ButtplugDeviceFeatureException { + CheckInput("Battery"); + return device.sendInputCommand(featureIndex, "Battery", InputCommandType.READ); + } + + public boolean HasRssi() { + return input.get("Rssi") != null; + } + + public Future ReadRssi() throws ButtplugDeviceFeatureException { + CheckInput("Rssi"); + return device.sendInputCommand(featureIndex, "Rssi", InputCommandType.READ); + } + + public String getDescription() { + return description; + } + + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ButtplugClientDeviceFeature that = (ButtplugClientDeviceFeature) o; + if (featureIndex != that.featureIndex || + !description.equals(that.description) || + (output == null) != (that.output == null) || + (input == null) != (that.input == null)) { + return false; + } + if (output != null) { + if (output.size() != that.output.size() || output.keySet().containsAll(that.output.keySet())) { + return false; + } + for (String type : output.keySet()) { + if (!output.get(type).equals(that.output.get(type))) { + return false; + } + } + } + if (input != null) { + if (input.size() != that.input.size() || input.keySet().containsAll(that.input.keySet())) { + return false; + } + for (String type : input.keySet()) { + if (!input.get(type).equals(that.input.get(type))) { + return false; + } + } + } + return true; + } +} diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/client/ButtplugDeviceFeatureException.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/client/ButtplugDeviceFeatureException.java new file mode 100644 index 0000000..336b315 --- /dev/null +++ b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/client/ButtplugDeviceFeatureException.java @@ -0,0 +1,10 @@ +package io.github.blackspherefollower.buttplug4j.client; + +import io.github.blackspherefollower.buttplug4j.ButtplugException; + +public class ButtplugDeviceFeatureException extends ButtplugException { + public ButtplugDeviceFeatureException(final String cmd) { + super(); + setMessage("Buttplug Device Feature does not support " + cmd); + } +} diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/client/IDeviceEvent.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/client/IDeviceAddedEvent.java similarity index 61% rename from buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/client/IDeviceEvent.java rename to buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/client/IDeviceAddedEvent.java index 2e77a45..a47ea7d 100644 --- a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/client/IDeviceEvent.java +++ b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/client/IDeviceAddedEvent.java @@ -1,7 +1,5 @@ package io.github.blackspherefollower.buttplug4j.client; -public interface IDeviceEvent { +public interface IDeviceAddedEvent { void deviceAdded(ButtplugClientDevice dev); - - void deviceRemoved(long index); } diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/client/IDeviceChangedEvent.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/client/IDeviceChangedEvent.java new file mode 100644 index 0000000..afc3f4f --- /dev/null +++ b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/client/IDeviceChangedEvent.java @@ -0,0 +1,5 @@ +package io.github.blackspherefollower.buttplug4j.client; + +public interface IDeviceChangedEvent { + void deviceChanged(ButtplugClientDevice dev); +} diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/client/IDeviceRemovedEvent.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/client/IDeviceRemovedEvent.java new file mode 100644 index 0000000..3b1ded3 --- /dev/null +++ b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/client/IDeviceRemovedEvent.java @@ -0,0 +1,5 @@ +package io.github.blackspherefollower.buttplug4j.client; + +public interface IDeviceRemovedEvent { + void deviceRemoved(int index); +} diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/client/ISensorReadingEvent.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/client/ISensorReadingEvent.java index b265722..628f650 100644 --- a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/client/ISensorReadingEvent.java +++ b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/client/ISensorReadingEvent.java @@ -1,7 +1,7 @@ package io.github.blackspherefollower.buttplug4j.client; -import io.github.blackspherefollower.buttplug4j.protocol.messages.SensorReading; +import io.github.blackspherefollower.buttplug4j.protocol.messages.InputReading; public interface ISensorReadingEvent { - void sensorReadingReceived(SensorReading msg); + void sensorReadingReceived(InputReading msg); } diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/ButtplugConsts.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/ButtplugConsts.java index b3b241b..a9b8663 100644 --- a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/ButtplugConsts.java +++ b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/ButtplugConsts.java @@ -1,9 +1,10 @@ package io.github.blackspherefollower.buttplug4j.protocol; public final class ButtplugConsts { - public static final long SYSTEM_MSG_ID = 0; - public static final long DEFAULT_MSG_ID = 1; - public static final long MESSAGE_VERSION = 3; + public static final int SYSTEM_MSG_ID = 0; + public static final int DEFAULT_MSG_ID = 1; + public static final int PROTOCOL_VERSION_MAJOR = 4; + public static final int PROTOCOL_VERSION_MINOR = 0; private ButtplugConsts() { } diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/ButtplugDeviceMessage.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/ButtplugDeviceMessage.java index 908662b..886ebca 100644 --- a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/ButtplugDeviceMessage.java +++ b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/ButtplugDeviceMessage.java @@ -7,7 +7,7 @@ public abstract class ButtplugDeviceMessage extends ButtplugMessage { @JsonProperty(value = "DeviceIndex", required = true) private long deviceIndex; - public ButtplugDeviceMessage(final long id, final long deviceIndex) { + public ButtplugDeviceMessage(final int id, final long deviceIndex) { super(id); this.setDeviceIndex(deviceIndex); } diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/ButtplugJsonMessageParser.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/ButtplugJsonMessageParser.java index d120ad1..36fe62f 100644 --- a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/ButtplugJsonMessageParser.java +++ b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/ButtplugJsonMessageParser.java @@ -3,9 +3,11 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeInfo.As; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.StreamReadFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper.DefaultTypeResolverBuilder; import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping; +import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder; import java.util.Arrays; @@ -16,7 +18,9 @@ public final class ButtplugJsonMessageParser { private final ObjectMapper mapper; public ButtplugJsonMessageParser() { - mapper = new ObjectMapper(); + mapper = JsonMapper.builder() + .enable(StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION) + .build(); TypeResolverBuilder typer = DefaultTypeResolverBuilder.construct(DefaultTyping.JAVA_LANG_OBJECT, this.mapper.getPolymorphicTypeValidator()); typer = typer.init(JsonTypeInfo.Id.NAME, null); diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/ButtplugMessage.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/ButtplugMessage.java index 445b9dd..80e25b5 100644 --- a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/ButtplugMessage.java +++ b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/ButtplugMessage.java @@ -5,27 +5,21 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeInfo.As; import com.fasterxml.jackson.annotation.JsonTypeInfo.Id; -import io.github.blackspherefollower.buttplug4j.protocol.messages.Error; import io.github.blackspherefollower.buttplug4j.protocol.messages.*; +import io.github.blackspherefollower.buttplug4j.protocol.messages.Error; @JsonTypeInfo(include = As.WRAPPER_OBJECT, use = Id.NAME) @JsonSubTypes({ - @JsonSubTypes.Type(value = DeviceAdded.class, name = "DeviceAdded"), @JsonSubTypes.Type(value = DeviceList.class, name = "DeviceList"), - @JsonSubTypes.Type(value = DeviceRemoved.class, name = "DeviceRemoved"), @JsonSubTypes.Type(value = Error.class, name = "Error"), - @JsonSubTypes.Type(value = LinearCmd.class, name = "LinearCmd"), @JsonSubTypes.Type(value = Ok.class, name = "Ok"), @JsonSubTypes.Type(value = Ping.class, name = "Ping"), @JsonSubTypes.Type(value = RequestDeviceList.class, name = "RequestDeviceList"), @JsonSubTypes.Type(value = RequestServerInfo.class, name = "RequestServerInfo"), - @JsonSubTypes.Type(value = RotateCmd.class, name = "RotateCmd"), - @JsonSubTypes.Type(value = ScalarCmd.class, name = "ScalarCmd"), + @JsonSubTypes.Type(value = OutputCmd.class, name = "OutputCmd"), + @JsonSubTypes.Type(value = InputCmd.class, name = "InputCmd"), @JsonSubTypes.Type(value = ScanningFinished.class, name = "ScanningFinished"), - @JsonSubTypes.Type(value = SensorReadCmd.class, name = "SensorReadCmd"), - @JsonSubTypes.Type(value = SensorReading.class, name = "SensorReading"), - @JsonSubTypes.Type(value = SensorSubscribeCmd.class, name = "SensorSubscribeCmd"), - @JsonSubTypes.Type(value = SensorUnsubscribeCmd.class, name = "SensorUnsubscribeCmd"), + @JsonSubTypes.Type(value = InputReading.class, name = "InputReading"), @JsonSubTypes.Type(value = ServerInfo.class, name = "ServerInfo"), @JsonSubTypes.Type(value = StartScanning.class, name = "StartScanning"), @JsonSubTypes.Type(value = StopAllDevices.class, name = "StopAllDevices"), @@ -35,17 +29,17 @@ public abstract class ButtplugMessage { @JsonProperty(value = "Id", required = true) - private long id; + private int id; - public ButtplugMessage(final long id) { + public ButtplugMessage(final int id) { this.setId(id); } - public final long getId() { + public final int getId() { return id; } - public final void setId(final long id) { + public final void setId(final int id) { this.id = id; } } diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Parts/DeviceMessageInfo.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Device.java similarity index 59% rename from buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Parts/DeviceMessageInfo.java rename to buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Device.java index e06d47c..7ba8ac4 100644 --- a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Parts/DeviceMessageInfo.java +++ b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Device.java @@ -1,16 +1,14 @@ -package io.github.blackspherefollower.buttplug4j.protocol.messages.Parts; +package io.github.blackspherefollower.buttplug4j.protocol.messages; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import java.util.ArrayList; +import java.util.HashMap; -public final class DeviceMessageInfo { +public class Device { @JsonProperty(value = "DeviceIndex", required = true) - private long deviceIndex; + private int deviceIndex; @JsonProperty(value = "DeviceName", required = true) private String deviceName; @@ -22,35 +20,34 @@ public final class DeviceMessageInfo { @JsonProperty(value = "DeviceDisplayName") @JsonInclude(JsonInclude.Include.NON_EMPTY) private String deviceDisplayName; - @JsonProperty(value = "DeviceMessages", required = true) - @JsonDeserialize(using = DeviceMessagesDeserializer.class) - @JsonSerialize(using = DeviceMessagesSerializer.class) - private ArrayList deviceMessages; - - public DeviceMessageInfo(final long deviceIndex, final String deviceName, - final ArrayList deviceMessages, final int deviceMessageTimingGap, - final String deviceDisplayName) { + + @JsonProperty(value = "DeviceFeatures", required = true) + private HashMap deviceFeatures; + + public Device(final int deviceIndex, final String deviceName, + final HashMap deviceFeatures, final int deviceMessageTimingGap, + final String deviceDisplayName) { this.deviceName = deviceName; this.deviceIndex = deviceIndex; - this.deviceMessages = deviceMessages; + this.deviceFeatures = deviceFeatures; this.deviceMessageTimingGap = deviceMessageTimingGap; this.deviceDisplayName = deviceDisplayName; } @SuppressWarnings("unused") - private DeviceMessageInfo() { + private Device() { this.deviceName = ""; this.deviceIndex = -1; - this.deviceMessages = new ArrayList<>(); + this.deviceFeatures = new HashMap<>(); this.deviceMessageTimingGap = null; this.deviceDisplayName = ""; } - public long getDeviceIndex() { + public int getDeviceIndex() { return deviceIndex; } - public void setDeviceIndex(final long deviceIndex) { + public void setDeviceIndex(final int deviceIndex) { this.deviceIndex = deviceIndex; } @@ -78,11 +75,11 @@ public void setDeviceDisplayName(final String deviceDisplayName) { this.deviceDisplayName = deviceDisplayName; } - public ArrayList getDeviceMessages() { - return deviceMessages; + public HashMap getDeviceFeatures() { + return deviceFeatures; } - public void setDeviceMessages(final ArrayList deviceMessages) { - this.deviceMessages = deviceMessages; + public void setDeviceFeatures(final HashMap deviceFeatures) { + this.deviceFeatures = deviceFeatures; } } diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/DeviceAdded.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/DeviceAdded.java deleted file mode 100644 index 4a13921..0000000 --- a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/DeviceAdded.java +++ /dev/null @@ -1,77 +0,0 @@ -package io.github.blackspherefollower.buttplug4j.protocol.messages; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import io.github.blackspherefollower.buttplug4j.protocol.ButtplugConsts; -import io.github.blackspherefollower.buttplug4j.protocol.ButtplugDeviceMessage; -import io.github.blackspherefollower.buttplug4j.protocol.messages.Parts.DeviceMessage; -import io.github.blackspherefollower.buttplug4j.protocol.messages.Parts.DeviceMessagesDeserializer; -import io.github.blackspherefollower.buttplug4j.protocol.messages.Parts.DeviceMessagesSerializer; - -import java.util.ArrayList; - -public final class DeviceAdded extends ButtplugDeviceMessage { - @JsonProperty(value = "DeviceName", required = true) - private String deviceName; - - @JsonProperty(value = "DeviceMessageTimingGap") - @JsonInclude(JsonInclude.Include.NON_DEFAULT) - private Integer deviceMessageTimingGap = null; - - @JsonProperty(value = "DeviceDisplayName") - @JsonInclude(JsonInclude.Include.NON_EMPTY) - private String deviceDisplayName; - - @JsonProperty(value = "DeviceMessages", required = true) - @JsonDeserialize(using = DeviceMessagesDeserializer.class) - @JsonSerialize(using = DeviceMessagesSerializer.class) - private ArrayList deviceMessages; - - public DeviceAdded(final long deviceIndex, final String deviceName, final ArrayList deviceMessages) { - super(ButtplugConsts.SYSTEM_MSG_ID, deviceIndex); - - this.setDeviceName(deviceName); - this.setDeviceMessages(deviceMessages); - } - - @SuppressWarnings("unused") - private DeviceAdded() { - super(ButtplugConsts.SYSTEM_MSG_ID, 0); - this.setDeviceName(""); - this.setDeviceMessages(new ArrayList<>()); - } - - public String getDeviceName() { - return deviceName; - } - - public void setDeviceName(final String deviceName) { - this.deviceName = deviceName; - } - - public Integer getDeviceMessageTimingGap() { - return deviceMessageTimingGap; - } - - public void setDeviceMessageTimingGap(final Integer deviceMessageTimingGap) { - this.deviceMessageTimingGap = deviceMessageTimingGap; - } - - public String getDeviceDisplayName() { - return deviceDisplayName; - } - - public void setDeviceDisplayName(final String deviceDisplayName) { - this.deviceDisplayName = deviceDisplayName; - } - - public ArrayList getDeviceMessages() { - return deviceMessages; - } - - public void setDeviceMessages(final ArrayList deviceMessages) { - this.deviceMessages = deviceMessages; - } -} diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/DeviceFeature.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/DeviceFeature.java new file mode 100644 index 0000000..7ee4176 --- /dev/null +++ b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/DeviceFeature.java @@ -0,0 +1,445 @@ +package io.github.blackspherefollower.buttplug4j.protocol.messages; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.TreeNode; +import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; + +public final class DeviceFeature { + + @JsonProperty(value = "FeatureIndex", required = true) + private int featureIndex; + @JsonProperty(value = "FeatureDescription", required = true) + private String featureDescription; + @JsonProperty(value = "Output", required = false) + @JsonDeserialize(using = OutputDescriptorSetDeserialiser.class) + @JsonSerialize(using = OutputDescriptorSetSerialiser.class, include = JsonSerialize.Inclusion.NON_NULL) + private ArrayList output; + @JsonProperty(value = "Input", required = false) + @JsonDeserialize(using = InputDescriptorSetDeserialiser.class) + @JsonSerialize(using = InputDescriptorSetSerialiser.class, include = JsonSerialize.Inclusion.NON_NULL) + private ArrayList input; + + public DeviceFeature() { + } + + public String getFeatureDescription() { + return featureDescription; + } + + public void setFeatureDescription(String featureDescription) { + this.featureDescription = featureDescription; + } + + public int getFeatureIndex() { + return featureIndex; + } + + public void setFeatureIndex(int featureIndex) { + this.featureIndex = featureIndex; + } + + public ArrayList getOutput() { + return output; + } + + public void setOutput(ArrayList output) { + this.output = output; + } + + public ArrayList getInput() { + return input; + } + + public void setInput(ArrayList input) { + this.input = input; + } + + @JsonTypeInfo(include = JsonTypeInfo.As.WRAPPER_OBJECT, use = JsonTypeInfo.Id.NAME) + @JsonSubTypes({ + @JsonSubTypes.Type(value = DeviceFeature.Vibrate.class, name = "Vibrate"), + @JsonSubTypes.Type(value = DeviceFeature.Rotate.class, name = "Rotate"), + @JsonSubTypes.Type(value = DeviceFeature.Spray.class, name = "Spray"), + @JsonSubTypes.Type(value = DeviceFeature.Oscillate.class, name = "Oscillate"), + @JsonSubTypes.Type(value = DeviceFeature.Position.class, name = "Position"), + @JsonSubTypes.Type(value = DeviceFeature.Temperature.class, name = "Temperature"), + @JsonSubTypes.Type(value = DeviceFeature.Constrict.class, name = "Constrict"), + @JsonSubTypes.Type(value = DeviceFeature.PositionWithDuration.class, name = "PositionWithDuration"), + @JsonSubTypes.Type(value = DeviceFeature.Led.class, name = "Led") + }) + public interface OutputDescriptor { + } + + public static class SteppedOutputDescriptor implements OutputDescriptor { + @JsonProperty(value = "Value", required = true) + private int[] value; + + public SteppedOutputDescriptor(int[] value) { + this.value = value; + } + + public int[] getValue() { + return value; + } + + public void setStepCount(int[] value) { + this.value = value; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SteppedOutputDescriptor that = (SteppedOutputDescriptor) o; + return java.util.Arrays.equals(value, that.value); + } + } + + public static class Vibrate extends SteppedOutputDescriptor { + public Vibrate(int[] value) { + super(value); + } + + public Vibrate() { + super(new int[]{0, 0}); + } + } + + public static class Rotate extends SteppedOutputDescriptor { + public Rotate(int[] value) { + super(value); + } + + public Rotate() { + super(new int[]{0, 0}); + } + } + + public static class Oscillate extends SteppedOutputDescriptor { + public Oscillate(int[] value) { + super(value); + } + + public Oscillate() { + super(new int[]{0, 0}); + } + } + + public static class Constrict extends SteppedOutputDescriptor { + public Constrict(int[] value) { + super(value); + } + + public Constrict() { + super(new int[]{0, 0}); + } + } + + public static class Spray extends SteppedOutputDescriptor { + public Spray(int[] value) { + super(value); + } + + public Spray() { + super(new int[]{0, 0}); + } + } + + public static class Temperature extends SteppedOutputDescriptor { + public Temperature(int[] value) { + super(value); + } + + public Temperature() { + super(new int[]{0, 0}); + } + } + + public static class Led extends SteppedOutputDescriptor { + public Led(int[] value) { + super(value); + } + + public Led() { + super(new int[]{0, 0}); + } + } + + public static class Position extends SteppedOutputDescriptor { + public Position(int[] value) { + super(value); + } + + public Position() { + super(new int[]{0, 0}); + } + } + + public static class PositionWithDuration implements OutputDescriptor { + @JsonProperty(value = "Position", required = true) + private int[] position; + @JsonProperty(value = "Duration", required = true) + private int[] duration; + + public PositionWithDuration(int[] position, int[] duration) { + this.position = position; + this.duration = duration; + } + + public PositionWithDuration() { + this.position = new int[]{0, 0}; + this.duration = new int[]{0, 0}; + } + + public int[] getPosition() { + return position; + } + + public void setPosition(int[] position) { + this.position = position; + } + + public int[] getDuration() { + return duration; + } + + public void setDuration(int[] duration) { + this.duration = duration; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + PositionWithDuration that = (PositionWithDuration) o; + return java.util.Arrays.equals(position, that.position) && java.util.Arrays.equals(duration, that.duration); + } + } + + @JsonTypeInfo(include = JsonTypeInfo.As.WRAPPER_OBJECT, use = JsonTypeInfo.Id.NAME) + @JsonSubTypes({ + @JsonSubTypes.Type(value = DeviceFeature.Battery.class, name = "Battery"), + @JsonSubTypes.Type(value = DeviceFeature.Rssi.class, name = "RSSI"), + @JsonSubTypes.Type(value = DeviceFeature.Button.class, name = "Button"), + @JsonSubTypes.Type(value = DeviceFeature.Pressure.class, name = "Pressure"), + @JsonSubTypes.Type(value = DeviceFeature.PositionInput.class, name = "Position") + }) + public static class InputDescriptor { + @JsonProperty(value = "InputCommands", required = true) + private ArrayList input; + + public InputDescriptor(ArrayList input) { + this.input = input; + } + + public ArrayList getInput() { + return input; + } + + public void setInput(ArrayList input) { + this.input = input; + } + } + + public static class RangedInputDescriptor extends InputDescriptor { + @JsonProperty(value = "ValueRange", required = true) + private int[][] valueRange; + + public RangedInputDescriptor(ArrayList input, int[][] valueRange) { + super(input); + this.valueRange = valueRange; + } + + public RangedInputDescriptor() { + super(new ArrayList<>()); + this.valueRange = new int[][]{{0, 0}, {0, 0}}; + } + + public int[][] getValueRange() { + return valueRange; + } + + public void setValueRange(int[][] valueRange) { + this.valueRange = valueRange; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + RangedInputDescriptor that = (RangedInputDescriptor) o; + if (valueRange.length != that.valueRange.length) { + return false; + } + for (int i = 0; i < valueRange.length; i++) { + if (!java.util.Arrays.equals(valueRange[i], that.valueRange[i])) { + return false; + } + } + return true; + } + } + + public static class Battery extends RangedInputDescriptor { + + public Battery(ArrayList input, int[][] valueRange) { + super(input, valueRange); + } + + public Battery() { + super(); + } + } + + public static class Rssi extends RangedInputDescriptor { + public Rssi(ArrayList input, int[][] valueRange) { + super(input, valueRange); + } + + public Rssi() { + super(); + } + } + + public static class Button extends RangedInputDescriptor { + public Button(ArrayList input, int[][] valueRange) { + super(input, valueRange); + } + + public Button() { + super(); + } + } + + public static class Pressure extends RangedInputDescriptor { + public Pressure(ArrayList input, int[][] valueRange) { + super(input, valueRange); + } + + public Pressure() { + super(); + } + } + + public static class PositionInput extends RangedInputDescriptor { + public PositionInput(ArrayList input, int[][] valueRange) { + super(input, valueRange); + } + + public PositionInput() { + super(); + } + } + + static class OutputDescriptorSetDeserialiser extends JsonDeserializer> { + + @Override + public ArrayList deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { + ObjectMapper mapper = ((ObjectMapper) jsonParser.getCodec()); + ArrayList ret = new ArrayList(); + try { + TreeNode tree = jsonParser.readValueAsTree(); + for (Iterator it = tree.fieldNames(); it.hasNext(); ) { + String f = it.next(); + ObjectNode node = mapper.createObjectNode(); + node.set(f, mapper.readTree(tree.get(f).traverse(mapper))); + ret.add(node.traverse(mapper).readValueAs(OutputDescriptor.class)); + } + } catch (Exception e) { + System.out.println("Unknown OutputDescriptor: " + jsonParser.readValueAsTree()); + // unknown type + } + return ret; + } + } + + static class OutputDescriptorSetSerialiser extends JsonSerializer> { + + @Override + public void serialize(ArrayList outputDescriptors, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + + ObjectMapper mapper = ((ObjectMapper) jsonGenerator.getCodec()); + ObjectNode node = null; + for (OutputDescriptor outputDescriptor : outputDescriptors) { + if (node == null) { + node = mapper.createObjectNode(); + } + + TreeNode n = mapper.readValue(mapper.writeValueAsString(outputDescriptor), JsonNode.class).traverse(mapper).readValueAsTree(); + for (Iterator it = n.fieldNames(); it.hasNext(); ) { + String f = it.next(); + ObjectNode on = mapper.createObjectNode(); + node.set(f, mapper.readTree(n.get(f).traverse(mapper))); + } + } + jsonGenerator.writeObject(node); + } + } + + static class InputDescriptorSetDeserialiser extends JsonDeserializer> { + + @Override + public ArrayList deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { + ObjectMapper mapper = ((ObjectMapper) jsonParser.getCodec()); + ArrayList ret = new ArrayList(); + try { + TreeNode tree = jsonParser.readValueAsTree(); + for (Iterator it = tree.fieldNames(); it.hasNext(); ) { + String f = it.next(); + ObjectNode node = mapper.createObjectNode(); + node.set(f, mapper.readTree(tree.get(f).traverse(mapper))); + ret.add(node.traverse(mapper).readValueAs(InputDescriptor.class)); + } + } catch (Exception e) { + System.out.println("Unknown InputDescriptor: " + jsonParser.readValueAsTree()); + // unknown type + } + return ret; + } + } + + static class InputDescriptorSetSerialiser extends JsonSerializer> { + + @Override + public void serialize(ArrayList inputDescriptors, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + + ObjectMapper mapper = ((ObjectMapper) jsonGenerator.getCodec()); + ObjectNode node = null; + for (InputDescriptor inputDescriptor : inputDescriptors) { + if (node == null) { + node = mapper.createObjectNode(); + } + + TreeNode n = mapper.readValue(mapper.writeValueAsString(inputDescriptor), JsonNode.class).traverse(mapper).readValueAsTree(); + for (Iterator it = n.fieldNames(); it.hasNext(); ) { + String f = it.next(); + ObjectNode on = mapper.createObjectNode(); + node.set(f, mapper.readTree(n.get(f).traverse(mapper))); + } + } + jsonGenerator.writeObject(node); + } + } +} diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/DeviceList.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/DeviceList.java index b691020..542ec94 100644 --- a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/DeviceList.java +++ b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/DeviceList.java @@ -3,31 +3,30 @@ import com.fasterxml.jackson.annotation.JsonProperty; import io.github.blackspherefollower.buttplug4j.protocol.ButtplugConsts; import io.github.blackspherefollower.buttplug4j.protocol.ButtplugMessage; -import io.github.blackspherefollower.buttplug4j.protocol.messages.Parts.DeviceMessageInfo; -import java.util.ArrayList; +import java.util.HashMap; public final class DeviceList extends ButtplugMessage { @JsonProperty(value = "Devices", required = true) - private ArrayList devices; + private HashMap devices; - public DeviceList(final ArrayList devices, final long id) { + public DeviceList(final HashMap devices, final int id) { super(id); - this.setDevices(devices); + this.devices = devices; } @SuppressWarnings("unused") private DeviceList() { super(ButtplugConsts.DEFAULT_MSG_ID); - this.setDevices(new ArrayList<>()); + this.setDevices(new HashMap<>()); } - public ArrayList getDevices() { + public HashMap getDevices() { return devices; } - public void setDevices(final ArrayList devices) { + public void setDevices(final HashMap devices) { this.devices = devices; } } diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/DeviceRemoved.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/DeviceRemoved.java deleted file mode 100644 index 6b5bd10..0000000 --- a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/DeviceRemoved.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.github.blackspherefollower.buttplug4j.protocol.messages; - -import io.github.blackspherefollower.buttplug4j.protocol.ButtplugConsts; -import io.github.blackspherefollower.buttplug4j.protocol.ButtplugDeviceMessage; - -public final class DeviceRemoved extends ButtplugDeviceMessage { - public DeviceRemoved(final long deviceMessage) { - super(ButtplugConsts.SYSTEM_MSG_ID, deviceMessage); - } - - @SuppressWarnings("unused") - private DeviceRemoved() { - super(ButtplugConsts.SYSTEM_MSG_ID, -1); - } -} diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Error.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Error.java index 081979b..5b24e06 100644 --- a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Error.java +++ b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Error.java @@ -16,7 +16,7 @@ public final class Error extends ButtplugMessage { @JsonIgnore private Throwable exception = null; - public Error(final String errorMessage, final ErrorClass errorCode, final long id) { + public Error(final String errorMessage, final ErrorClass errorCode, final int id) { super(id); this.setErrorMessage(errorMessage); this.setErrorCode(errorCode); @@ -35,7 +35,8 @@ public Error(Throwable e) { this.setErrorCode(ErrorClass.ERROR_UNKNOWN); this.exception = e; } - public Error(Throwable e, final long id) { + + public Error(Throwable e, final int id) { super(id); this.setErrorMessage(e.getMessage()); this.setErrorCode(ErrorClass.ERROR_UNKNOWN); diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/InputCmd.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/InputCmd.java new file mode 100644 index 0000000..b6ffba8 --- /dev/null +++ b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/InputCmd.java @@ -0,0 +1,55 @@ +package io.github.blackspherefollower.buttplug4j.protocol.messages; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.github.blackspherefollower.buttplug4j.protocol.ButtplugDeviceMessage; + +public class InputCmd extends ButtplugDeviceMessage { + + @JsonProperty(value = "FeatureIndex", required = true) + private int featureIndex; + + @JsonProperty(value = "InputType", required = true) + private String inputType; + + @JsonProperty(value = "InputCommand", required = true) + private InputCommandType inputCommand; + + public InputCmd(int id, final long deviceIndex, final int featureIndex, final String inputType, final InputCommandType inputCommand) { + super(id, deviceIndex); + this.featureIndex = featureIndex; + this.inputType = inputType; + this.inputCommand = inputCommand; + } + + public InputCmd() { + super(-1, -1); + this.featureIndex = -1; + this.inputType = "None"; + this.inputCommand = InputCommandType.READ; + } + + + public int getFeatureIndex() { + return featureIndex; + } + + public void setFeatureIndex(int featureIndex) { + this.featureIndex = featureIndex; + } + + public String getInputType() { + return inputType; + } + + public void setInputType(String inputType) { + this.inputType = inputType; + } + + public InputCommandType getInputCommand() { + return inputCommand; + } + + public void setInputCommand(InputCommandType inputCommand) { + this.inputCommand = inputCommand; + } +} diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/InputCommandType.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/InputCommandType.java new file mode 100644 index 0000000..e702749 --- /dev/null +++ b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/InputCommandType.java @@ -0,0 +1,20 @@ +package io.github.blackspherefollower.buttplug4j.protocol.messages; + +import com.fasterxml.jackson.annotation.JsonValue; + +public enum InputCommandType { + READ("Read"), + SUBSCRIBE("Subscribe"), + UNSUBSCRIBE("Unsubscribe"); + + private final String specName; + + InputCommandType(String specName) { + this.specName = specName; + } + + @JsonValue + public String getSpecName() { + return specName; + } +} diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/InputReading.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/InputReading.java new file mode 100644 index 0000000..ad0846e --- /dev/null +++ b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/InputReading.java @@ -0,0 +1,61 @@ +package io.github.blackspherefollower.buttplug4j.protocol.messages; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.github.blackspherefollower.buttplug4j.protocol.ButtplugDeviceMessage; + +public class InputReading extends ButtplugDeviceMessage { + + @JsonProperty(value = "FeatureIndex", required = true) + private int featureIndex; + @JsonProperty(value = "Data", required = true) + private InputData data; + + public InputReading(int id, long deviceIndex, int featureIndex) { + super(id, deviceIndex); + this.featureIndex = featureIndex; + } + + public InputData getData() { + return data; + } + + public void setData(InputData data) { + this.data = data; + } + + public int getFeatureIndex() { + return featureIndex; + } + + public void setFeatureIndex(int featureIndex) { + this.featureIndex = featureIndex; + } + + public interface InputData { + } + + static public class InputIntegerData { + @JsonProperty(value = "Data", required = true) + int value; + + public int getValue() { + return value; + } + + public void setValue(int value) { + this.value = value; + } + } + + static public class BatteryData extends InputIntegerData { + } + + static public class RssiData extends InputIntegerData { + } + + static public class ButtonData extends InputIntegerData { + } + + static public class PresureData extends InputIntegerData { + } +} diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/LinearCmd.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/LinearCmd.java deleted file mode 100644 index 4bc8140..0000000 --- a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/LinearCmd.java +++ /dev/null @@ -1,89 +0,0 @@ -package io.github.blackspherefollower.buttplug4j.protocol.messages; - -import com.fasterxml.jackson.annotation.JsonProperty; -import io.github.blackspherefollower.buttplug4j.protocol.ButtplugConsts; -import io.github.blackspherefollower.buttplug4j.protocol.ButtplugDeviceMessage; - -public final class LinearCmd extends ButtplugDeviceMessage { - - @JsonProperty(value = "Vectors", required = true) - private LinearSubCmd[] vectors; - - public LinearCmd(final long deviceIndex, final LinearSubCmd[] vectors, final long id) { - super(id, deviceIndex); - this.setVectors(vectors); - } - - @SuppressWarnings("unused") - private LinearCmd() { - super(ButtplugConsts.DEFAULT_MSG_ID, -1); - } - - public LinearSubCmd[] getVectors() { - return vectors; - } - - public void setVectors(final LinearSubCmd[] vectors) { - this.vectors = vectors; - } - - public static final class LinearSubCmd { - @JsonProperty(value = "Index", required = true) - private long index; - - @JsonProperty(value = "Position", required = true) - private double position; - - @JsonProperty(value = "Duration", required = true) - private long duration; - - public LinearSubCmd(final long index, final double position, final long duration) { - this.index = index; - this.duration = duration; - setPosition(position); - } - - public LinearSubCmd() { - this.index = -1; - this.duration = 0; - setPosition(0); - } - - public double getPosition() { - if (position > 1 || position < 0) { - return 0; - } - return position; - } - - public void setPosition(final double position) { - if (position > 1) { - throw new IllegalArgumentException( - "Linear position cannot be greater than 1!"); - } - - if (position < 0) { - throw new IllegalArgumentException( - "Linear position cannot be lower than 0!"); - } - - this.position = position; - } - - public long getDuration() { - return duration; - } - - public void setDuration(long duration) { - this.duration = duration; - } - - public long getIndex() { - return index; - } - - public void setIndex(long index) { - this.index = index; - } - } -} diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Ok.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Ok.java index 1b14231..015d237 100644 --- a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Ok.java +++ b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Ok.java @@ -5,7 +5,7 @@ public final class Ok extends ButtplugMessage { - public Ok(final long id) { + public Ok(final int id) { super(id); } diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/OutputCmd.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/OutputCmd.java new file mode 100644 index 0000000..10f1e00 --- /dev/null +++ b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/OutputCmd.java @@ -0,0 +1,187 @@ +package io.github.blackspherefollower.buttplug4j.protocol.messages; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import io.github.blackspherefollower.buttplug4j.protocol.ButtplugDeviceMessage; + +public class OutputCmd extends ButtplugDeviceMessage { + + + @JsonProperty(value = "FeatureIndex", required = true) + private long featureIndex; + @JsonProperty(value = "Command", required = true) + private IOutputCommand command; + + public OutputCmd(int id, final long deviceIndex, final long featureIndex) { + super(id, deviceIndex); + this.featureIndex = featureIndex; + } + + public OutputCmd() { + super(-1, -1); + this.featureIndex = -1; + } + + public final long getFeatureIndex() { + return featureIndex; + } + + public final void setFeatureIndex(final long deviceIndex) { + this.featureIndex = deviceIndex; + } + + public final IOutputCommand getCommand() { + return command; + } + + public final void setCommand(final IOutputCommand command) { + this.command = command; + } + + @JsonTypeInfo(include = JsonTypeInfo.As.WRAPPER_OBJECT, use = JsonTypeInfo.Id.NAME) + @JsonSubTypes({ + @JsonSubTypes.Type(value = Vibrate.class, name = "Vibrate"), + @JsonSubTypes.Type(value = Rotate.class, name = "Rotate"), + @JsonSubTypes.Type(value = Spray.class, name = "Spray"), + @JsonSubTypes.Type(value = Oscillate.class, name = "Oscillate"), + @JsonSubTypes.Type(value = Position.class, name = "Position"), + @JsonSubTypes.Type(value = Temperature.class, name = "Temperature"), + @JsonSubTypes.Type(value = Constrict.class, name = "Constrict"), + @JsonSubTypes.Type(value = PositionWithDuration.class, name = "PositionWithDuration"), + @JsonSubTypes.Type(value = Led.class, name = "Led") + }) + public interface IOutputCommand { + } + + public abstract static class ValueCommand implements IOutputCommand { + @JsonProperty(value = "Value", required = true) + private int value; + + protected ValueCommand(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public void setValue(int value) { + this.value = value; + } + } + + public static class Vibrate extends ValueCommand { + public Vibrate(int value) { + super(value); + } + + public Vibrate() { + super(0); + } + } + + public static class Rotate extends ValueCommand { + public Rotate(int value) { + super(value); + } + + public Rotate() { + super(0); + } + } + + public static class Oscillate extends ValueCommand { + public Oscillate(int value) { + super(value); + } + + public Oscillate() { + super(0); + } + } + + public static class Constrict extends ValueCommand { + public Constrict(int value) { + super(value); + } + + public Constrict() { + super(0); + } + } + + public static class Spray extends ValueCommand { + public Spray(int value) { + super(value); + } + + public Spray() { + super(0); + } + } + + public static class Temperature extends ValueCommand { + public Temperature(int value) { + super(value); + } + + public Temperature() { + super(0); + } + } + + public static class Led extends ValueCommand { + public Led(int value) { + super(value); + } + + public Led() { + super(0); + } + } + + public static class Position extends ValueCommand { + public Position(int value) { + super(value); + } + + public Position() { + super(0); + } + } + + public static class PositionWithDuration implements IOutputCommand { + @JsonProperty(value = "Position", required = true) + private int position; + @JsonProperty(value = "Duration", required = true) + private int duration; + + public PositionWithDuration(int position, int duration) { + this.position = position; + this.duration = duration; + } + + public PositionWithDuration() { + this.position = 0; + this.duration = 0; + } + + public int getPosition() { + return position; + } + + public void setPosition(int position) { + this.position = position; + } + + public int getDuration() { + return duration; + } + + public void setDuration(int duration) { + this.duration = duration; + } + } + +} diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Parts/DeviceMessage.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Parts/DeviceMessage.java deleted file mode 100644 index 8b80cbd..0000000 --- a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Parts/DeviceMessage.java +++ /dev/null @@ -1,22 +0,0 @@ -package io.github.blackspherefollower.buttplug4j.protocol.messages.Parts; - -public final class DeviceMessage { - private String message; - private MessageAttributes attributes; - - public String getMessage() { - return message; - } - - public void setMessage(final String message) { - this.message = message; - } - - public MessageAttributes getAttributes() { - return attributes; - } - - public void setAttributes(final MessageAttributes attributes) { - this.attributes = attributes; - } -} diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Parts/DeviceMessagesDeserializer.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Parts/DeviceMessagesDeserializer.java deleted file mode 100644 index f7cb3eb..0000000 --- a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Parts/DeviceMessagesDeserializer.java +++ /dev/null @@ -1,66 +0,0 @@ -package io.github.blackspherefollower.buttplug4j.protocol.messages.Parts; - -import com.fasterxml.jackson.core.JsonParseException; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.ObjectCodec; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.Map; - -public final class DeviceMessagesDeserializer extends JsonDeserializer { - @Override - public Object deserialize(final JsonParser p, final DeserializationContext ctxt) throws IOException { - ObjectCodec oc = p.getCodec(); - JsonNode node = oc.readTree(p); - - ArrayList ret = new ArrayList<>(); - for (Iterator> it = node.fields(); it.hasNext(); ) { - Map.Entry msg = it.next(); - DeviceMessage dmsg = new DeviceMessage(); - dmsg.setMessage(msg.getKey()); - - switch (dmsg.getMessage()) { - case "StopDeviceCmd": - dmsg.setAttributes(new NullMessageAttributes()); - break; - case "RawReadCmd": - case "RawWriteCmd": - case "RawSubscribeCmd": - dmsg.setAttributes(new ObjectMapper().treeToValue(msg.getValue(), - RawMessageAttributes.class)); - - break; - case "SensorReadCmd": - case "SensorSubscribeCmd": - SensorMessageAttributes sattrs = new SensorMessageAttributes(); - for (Iterator it2 = msg.getValue().elements(); it2.hasNext(); ) { - sattrs.getFeatures().add(new ObjectMapper().treeToValue(it2.next(), - SensorFeatureAttributes.class)); - } - dmsg.setAttributes(sattrs); - break; - case "ScalarCmd": - case "LinearCmd": - case "RotateCmd": - GenericMessageAttributes gattrs = new GenericMessageAttributes(); - for (Iterator it2 = msg.getValue().elements(); it2.hasNext(); ) { - gattrs.getFeatures().add(new ObjectMapper().treeToValue(it2.next(), - GenericFeatureAttributes.class)); - } - dmsg.setAttributes(gattrs); - break; - default: - throw new JsonParseException(p, "Unknown Buttplug Device Message type: " + dmsg.getMessage()); - } - - ret.add(dmsg); - } - return ret; - } -} diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Parts/DeviceMessagesSerializer.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Parts/DeviceMessagesSerializer.java deleted file mode 100644 index b2d83b8..0000000 --- a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Parts/DeviceMessagesSerializer.java +++ /dev/null @@ -1,27 +0,0 @@ -package io.github.blackspherefollower.buttplug4j.protocol.messages.Parts; - - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; - -import java.io.IOException; -import java.util.List; - -public final class DeviceMessagesSerializer extends JsonSerializer { - @Override - public void serialize(final Object value, final JsonGenerator gen, final SerializerProvider serializers) - throws IOException { - gen.writeStartObject(); - if (value instanceof List) { - List data = (List) value; - for (Object obj : data) { - if (obj instanceof DeviceMessage) { - DeviceMessage dmsg = (DeviceMessage) obj; - gen.writeObjectField(dmsg.getMessage(), dmsg.getAttributes()); - } - } - } - gen.writeEndObject(); - } -} diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Parts/EmptySerializer.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Parts/EmptySerializer.java deleted file mode 100644 index 0a113bd..0000000 --- a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Parts/EmptySerializer.java +++ /dev/null @@ -1,17 +0,0 @@ -package io.github.blackspherefollower.buttplug4j.protocol.messages.Parts; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; - -import java.io.IOException; - -public final class EmptySerializer extends JsonSerializer { - - @Override - public void serialize(final Object value, final JsonGenerator gen, final SerializerProvider serializers) - throws IOException { - gen.writeStartObject(); - gen.writeEndObject(); - } -} diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Parts/GenericFeatureAttributes.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Parts/GenericFeatureAttributes.java deleted file mode 100644 index 5cd6612..0000000 --- a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Parts/GenericFeatureAttributes.java +++ /dev/null @@ -1,44 +0,0 @@ -package io.github.blackspherefollower.buttplug4j.protocol.messages.Parts; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; - -public final class GenericFeatureAttributes { - @JsonProperty(value = "StepCount", required = true) - private long stepCount; - - @JsonProperty(value = "FeatureDescriptor") - @JsonInclude(JsonInclude.Include.NON_EMPTY) - private String featureDescriptor; - - @JsonProperty(value = "ActuatorType") - @JsonInclude(JsonInclude.Include.NON_EMPTY) - private String actuatorType; - - public GenericFeatureAttributes() { - } - - public long getStepCount() { - return stepCount; - } - - public void setStepCount(final long stepCount) { - this.stepCount = stepCount; - } - - public String getFeatureDescriptor() { - return featureDescriptor; - } - - public void setFeatureDescriptor(final String featureDescriptor) { - this.featureDescriptor = featureDescriptor; - } - - public String getActuatorType() { - return actuatorType; - } - - public void setActuatorType(final String actuatorType) { - this.actuatorType = actuatorType; - } -} diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Parts/GenericFeaturesSerializer.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Parts/GenericFeaturesSerializer.java deleted file mode 100644 index 73d2ced..0000000 --- a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Parts/GenericFeaturesSerializer.java +++ /dev/null @@ -1,20 +0,0 @@ -package io.github.blackspherefollower.buttplug4j.protocol.messages.Parts; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; - -import java.io.IOException; - -public final class GenericFeaturesSerializer extends JsonSerializer { - @Override - public void serialize(final Object value, final JsonGenerator gen, final SerializerProvider serializers) - throws IOException { - GenericMessageAttributes data = (GenericMessageAttributes) value; - gen.writeStartArray(); - for (GenericFeatureAttributes feat : data.getFeatures()) { - gen.writeObject(feat); - } - gen.writeEndArray(); - } -} diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Parts/GenericMessageAttributes.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Parts/GenericMessageAttributes.java deleted file mode 100644 index 2c1e27a..0000000 --- a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Parts/GenericMessageAttributes.java +++ /dev/null @@ -1,24 +0,0 @@ -package io.github.blackspherefollower.buttplug4j.protocol.messages.Parts; - -import com.fasterxml.jackson.databind.annotation.JsonSerialize; - -import java.util.ArrayList; - -@JsonSerialize(using = GenericFeaturesSerializer.class) -public final class GenericMessageAttributes extends MessageAttributes { - - private ArrayList features; - - public GenericMessageAttributes() { - setFeatures(new ArrayList<>()); - } - - public ArrayList getFeatures() { - return features; - } - - public void setFeatures(final ArrayList features) { - this.features = features; - } -} - diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Parts/MessageAttributes.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Parts/MessageAttributes.java deleted file mode 100644 index 671edab..0000000 --- a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Parts/MessageAttributes.java +++ /dev/null @@ -1,4 +0,0 @@ -package io.github.blackspherefollower.buttplug4j.protocol.messages.Parts; - -public abstract class MessageAttributes { -} diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Parts/NullMessageAttributes.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Parts/NullMessageAttributes.java deleted file mode 100644 index 1eb5740..0000000 --- a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Parts/NullMessageAttributes.java +++ /dev/null @@ -1,8 +0,0 @@ -package io.github.blackspherefollower.buttplug4j.protocol.messages.Parts; - -import com.fasterxml.jackson.databind.annotation.JsonSerialize; - -@JsonSerialize(using = EmptySerializer.class) -public final class NullMessageAttributes extends MessageAttributes { - -} diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Parts/RawMessageAttributes.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Parts/RawMessageAttributes.java deleted file mode 100644 index 2636e0b..0000000 --- a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Parts/RawMessageAttributes.java +++ /dev/null @@ -1,18 +0,0 @@ -package io.github.blackspherefollower.buttplug4j.protocol.messages.Parts; - -import com.fasterxml.jackson.annotation.JsonProperty; - -public final class RawMessageAttributes extends MessageAttributes { - - @JsonProperty(value = "Endpoints", required = true) - private String[] endpoints; - - public String[] getEndpoints() { - return endpoints; - } - - public void setEndpoints(final String[] endpoints) { - this.endpoints = endpoints; - } -} - diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Parts/SensorFeatureAttributes.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Parts/SensorFeatureAttributes.java deleted file mode 100644 index 86c9b18..0000000 --- a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Parts/SensorFeatureAttributes.java +++ /dev/null @@ -1,38 +0,0 @@ -package io.github.blackspherefollower.buttplug4j.protocol.messages.Parts; - -import com.fasterxml.jackson.annotation.JsonProperty; - -public final class SensorFeatureAttributes { - @JsonProperty(value = "SensorType", required = true) - private String sensorType; - - @JsonProperty(value = "FeatureDescriptor") - private String featureDescriptor; - - @JsonProperty(value = "SensorRange", required = true) - private int[][] sensorRange; - - public String getSensorType() { - return sensorType; - } - - public void setSensorType(final String sensorType) { - this.sensorType = sensorType; - } - - public String getFeatureDescriptor() { - return featureDescriptor; - } - - public void setFeatureDescriptor(final String featureDescriptor) { - this.featureDescriptor = featureDescriptor; - } - - public int[][] getSensorRange() { - return sensorRange; - } - - public void setSensorRange(final int[][] sensorRange) { - this.sensorRange = sensorRange; - } -} diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Parts/SensorFeaturesSerializer.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Parts/SensorFeaturesSerializer.java deleted file mode 100644 index e019c8c..0000000 --- a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Parts/SensorFeaturesSerializer.java +++ /dev/null @@ -1,20 +0,0 @@ -package io.github.blackspherefollower.buttplug4j.protocol.messages.Parts; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; - -import java.io.IOException; - -public final class SensorFeaturesSerializer extends JsonSerializer { - @Override - public void serialize(final Object value, final JsonGenerator gen, final SerializerProvider serializers) - throws IOException { - SensorMessageAttributes data = (SensorMessageAttributes) value; - gen.writeStartArray(); - for (SensorFeatureAttributes feat : data.getFeatures()) { - gen.writeObject(feat); - } - gen.writeEndArray(); - } -} diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Parts/SensorMessageAttributes.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Parts/SensorMessageAttributes.java deleted file mode 100644 index d9e42d1..0000000 --- a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Parts/SensorMessageAttributes.java +++ /dev/null @@ -1,23 +0,0 @@ -package io.github.blackspherefollower.buttplug4j.protocol.messages.Parts; - -import com.fasterxml.jackson.databind.annotation.JsonSerialize; - -import java.util.ArrayList; - -@JsonSerialize(using = SensorFeaturesSerializer.class) -public final class SensorMessageAttributes extends MessageAttributes { - - private ArrayList features; - - SensorMessageAttributes() { - setFeatures(new ArrayList<>()); - } - - public ArrayList getFeatures() { - return features; - } - - public void setFeatures(final ArrayList features) { - this.features = features; - } -} diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Parts/package-info.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Parts/package-info.java deleted file mode 100644 index c2cb28f..0000000 --- a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Parts/package-info.java +++ /dev/null @@ -1 +0,0 @@ -package io.github.blackspherefollower.buttplug4j.protocol.messages.Parts; diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Ping.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Ping.java index b452cec..ee5bbf0 100644 --- a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Ping.java +++ b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/Ping.java @@ -10,7 +10,7 @@ private Ping() { super(ButtplugConsts.DEFAULT_MSG_ID); } - public Ping(final long id) { + public Ping(final int id) { super(id); } } diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/RequestDeviceList.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/RequestDeviceList.java index f0e9c35..b49cc49 100644 --- a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/RequestDeviceList.java +++ b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/RequestDeviceList.java @@ -10,7 +10,7 @@ private RequestDeviceList() { super(ButtplugConsts.DEFAULT_MSG_ID); } - public RequestDeviceList(final long id) { + public RequestDeviceList(final int id) { super(id); } } diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/RequestServerInfo.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/RequestServerInfo.java index d93e883..f158f26 100644 --- a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/RequestServerInfo.java +++ b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/RequestServerInfo.java @@ -4,16 +4,20 @@ import io.github.blackspherefollower.buttplug4j.protocol.ButtplugConsts; import io.github.blackspherefollower.buttplug4j.protocol.ButtplugMessage; -import static io.github.blackspherefollower.buttplug4j.protocol.ButtplugConsts.MESSAGE_VERSION; +import static io.github.blackspherefollower.buttplug4j.protocol.ButtplugConsts.PROTOCOL_VERSION_MAJOR; +import static io.github.blackspherefollower.buttplug4j.protocol.ButtplugConsts.PROTOCOL_VERSION_MINOR; public final class RequestServerInfo extends ButtplugMessage { - @JsonProperty(value = "MessageVersion", required = true) - private final long messageVersion = MESSAGE_VERSION; + @JsonProperty(value = "ProtocolVersionMajor", required = true) + private final long protocolVersionMajor = PROTOCOL_VERSION_MAJOR; + + @JsonProperty(value = "ProtocolVersionMinor", required = true) + private final long protocolVersionMinor = PROTOCOL_VERSION_MINOR; @JsonProperty(value = "ClientName", required = true) private String clientName; - public RequestServerInfo(final String clientName, final long id) { + public RequestServerInfo(final String clientName, final int id) { super(id); this.setClientName(clientName); } @@ -24,8 +28,12 @@ private RequestServerInfo() { this.setClientName(""); } - public long getMessageVersion() { - return messageVersion; + public long getProtocolVersionMajor() { + return protocolVersionMajor; + } + + public long getProtocolVersionMinor() { + return protocolVersionMinor; } public String getClientName() { diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/RotateCmd.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/RotateCmd.java deleted file mode 100644 index 36dfeaf..0000000 --- a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/RotateCmd.java +++ /dev/null @@ -1,89 +0,0 @@ -package io.github.blackspherefollower.buttplug4j.protocol.messages; - -import com.fasterxml.jackson.annotation.JsonProperty; -import io.github.blackspherefollower.buttplug4j.protocol.ButtplugConsts; -import io.github.blackspherefollower.buttplug4j.protocol.ButtplugDeviceMessage; - -public final class RotateCmd extends ButtplugDeviceMessage { - - @JsonProperty(value = "Rotations", required = true) - private RotateSubCmd[] rotations; - - public RotateCmd(final long deviceIndex, final RotateSubCmd[] rotations, final long id) { - super(id, deviceIndex); - this.setRotations(rotations); - } - - @SuppressWarnings("unused") - private RotateCmd() { - super(ButtplugConsts.DEFAULT_MSG_ID, -1); - } - - public RotateSubCmd[] getRotations() { - return rotations; - } - - public void setRotations(final RotateSubCmd[] rotations) { - this.rotations = rotations; - } - - public static final class RotateSubCmd { - @JsonProperty(value = "Index", required = true) - private long index; - - @JsonProperty(value = "Speed", required = true) - private double speed; - - public boolean isClockwise() { - return clockwise; - } - - public void setClockwise(boolean clockwise) { - this.clockwise = clockwise; - } - - @JsonProperty(value = "Clockwise", required = true) - private boolean clockwise; - - public RotateSubCmd(final long index, final double speed, final boolean clockwise) { - this.index = index; - this.clockwise = clockwise; - setSpeed(speed); - } - - public RotateSubCmd() { - this.index = -1; - this.clockwise = true; - this.speed = 0; - } - - public double getSpeed() { - if (speed > 1 || speed < 0) { - return 0; - } - return speed; - } - - public void setSpeed(final double speed) { - if (speed > 1) { - throw new IllegalArgumentException( - "Rotation speed cannot be greater than 1!"); - } - - if (speed < 0) { - throw new IllegalArgumentException( - "Rotation speed cannot be lower than 0!"); - } - - this.speed = speed; - } - - public long getIndex() { - return index; - } - - public void setIndex(long index) { - this.index = index; - } - } -} diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/ScalarCmd.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/ScalarCmd.java deleted file mode 100644 index 550b236..0000000 --- a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/ScalarCmd.java +++ /dev/null @@ -1,100 +0,0 @@ -package io.github.blackspherefollower.buttplug4j.protocol.messages; - -import com.fasterxml.jackson.annotation.JsonProperty; -import io.github.blackspherefollower.buttplug4j.protocol.ButtplugConsts; -import io.github.blackspherefollower.buttplug4j.protocol.ButtplugDeviceMessage; -import io.github.blackspherefollower.buttplug4j.util.Pair; - -import java.util.ArrayList; - -public final class ScalarCmd extends ButtplugDeviceMessage { - - @JsonProperty(value = "Scalars", required = true) - private ScalarSubCmd[] scalars; - - public ScalarCmd(final long deviceIndex, final Pair[] scalars, final long id) { - super(id, deviceIndex); - long i = 0; - ArrayList scalarsubs = new ArrayList<>(); - for (Pair scalar : scalars) { - if (scalar != null) { - scalarsubs.add(new ScalarSubCmd(i, scalar.getLeft(), scalar.getRight())); - } - i++; - } - this.setScalars(scalarsubs.toArray(new ScalarSubCmd[]{})); - } - - @SuppressWarnings("unused") - private ScalarCmd() { - super(ButtplugConsts.DEFAULT_MSG_ID, -1); - } - - public ScalarSubCmd[] getScalars() { - return scalars; - } - - public void setScalars(final ScalarSubCmd[] scalars) { - this.scalars = scalars; - } - - public static final class ScalarSubCmd { - @JsonProperty(value = "Index", required = true) - private long index; - - @JsonProperty(value = "Scalar", required = true) - private double scalar; - - @JsonProperty(value = "ActuatorType", required = true) - private String actuatorType; - - public ScalarSubCmd(final long index, final double scalar, final String actuatorType) { - this.setIndex(index); - this.setActuatorType(actuatorType); - setScalar(scalar); - } - - public ScalarSubCmd() { - this.index = -1; - this.actuatorType = ""; - this.scalar = 0; - } - - public double getScalar() { - if (scalar > 1 || scalar < 0) { - return 0; - } - return scalar; - } - - public void setScalar(final double scalar) { - if (scalar > 1) { - throw new IllegalArgumentException( - "Scalar values cannot be greater than 1!"); - } - - if (scalar < 0) { - throw new IllegalArgumentException( - "Scalar values cannot be lower than 0!"); - } - - this.scalar = scalar; - } - - public long getIndex() { - return index; - } - - public void setIndex(final long index) { - this.index = index; - } - - public String getActuatorType() { - return actuatorType; - } - - public void setActuatorType(final String actuatorType) { - this.actuatorType = actuatorType; - } - } -} diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/SensorReadCmd.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/SensorReadCmd.java deleted file mode 100644 index b168d03..0000000 --- a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/SensorReadCmd.java +++ /dev/null @@ -1,38 +0,0 @@ -package io.github.blackspherefollower.buttplug4j.protocol.messages; - -import com.fasterxml.jackson.annotation.JsonProperty; -import io.github.blackspherefollower.buttplug4j.protocol.ButtplugConsts; -import io.github.blackspherefollower.buttplug4j.protocol.ButtplugDeviceMessage; - -public final class SensorReadCmd extends ButtplugDeviceMessage { - - @JsonProperty(value = "SensorIndex", required = true) - private int sensorIndex; - @JsonProperty(value = "SensorType", required = true) - private String sensorType; - - public SensorReadCmd(final long deviceIndex, final long id) { - super(id, deviceIndex); - } - - @SuppressWarnings("unused") - private SensorReadCmd() { - super(ButtplugConsts.DEFAULT_MSG_ID, -1); - } - - public int getSensorIndex() { - return sensorIndex; - } - - public void setSensorIndex(final int sensorIndex) { - this.sensorIndex = sensorIndex; - } - - public String getSensorType() { - return sensorType; - } - - public void setSensorType(final String sensorType) { - this.sensorType = sensorType; - } -} diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/SensorReading.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/SensorReading.java deleted file mode 100644 index 0afa901..0000000 --- a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/SensorReading.java +++ /dev/null @@ -1,50 +0,0 @@ -package io.github.blackspherefollower.buttplug4j.protocol.messages; - -import com.fasterxml.jackson.annotation.JsonProperty; -import io.github.blackspherefollower.buttplug4j.protocol.ButtplugConsts; -import io.github.blackspherefollower.buttplug4j.protocol.ButtplugDeviceMessage; - -public final class SensorReading extends ButtplugDeviceMessage { - - @JsonProperty(value = "SensorIndex", required = true) - private int sensorIndex; - - @JsonProperty(value = "SensorType", required = true) - private String sensorType; - - @JsonProperty(value = "Data", required = true) - private byte[] data; - - public SensorReading(final long deviceIndex, final long id) { - super(id, deviceIndex); - } - - @SuppressWarnings("unused") - private SensorReading() { - super(ButtplugConsts.DEFAULT_MSG_ID, -1); - } - - public int getSensorIndex() { - return sensorIndex; - } - - public void setSensorIndex(final int sensorIndex) { - this.sensorIndex = sensorIndex; - } - - public String getSensorType() { - return sensorType; - } - - public void setSensorType(final String sensorType) { - this.sensorType = sensorType; - } - - public byte[] getData() { - return data; - } - - public void setData(final byte[] data) { - this.data = data; - } -} diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/SensorSubscribeCmd.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/SensorSubscribeCmd.java deleted file mode 100644 index 0ad27c7..0000000 --- a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/SensorSubscribeCmd.java +++ /dev/null @@ -1,38 +0,0 @@ -package io.github.blackspherefollower.buttplug4j.protocol.messages; - -import com.fasterxml.jackson.annotation.JsonProperty; -import io.github.blackspherefollower.buttplug4j.protocol.ButtplugConsts; -import io.github.blackspherefollower.buttplug4j.protocol.ButtplugDeviceMessage; - -public final class SensorSubscribeCmd extends ButtplugDeviceMessage { - - @JsonProperty(value = "SensorIndex", required = true) - private int sensorIndex; - @JsonProperty(value = "SensorType", required = true) - private String sensorType; - - public SensorSubscribeCmd(final long deviceIndex, final long id) { - super(id, deviceIndex); - } - - @SuppressWarnings("unused") - private SensorSubscribeCmd() { - super(ButtplugConsts.DEFAULT_MSG_ID, -1); - } - - public int getSensorIndex() { - return sensorIndex; - } - - public void setSensorIndex(final int sensorIndex) { - this.sensorIndex = sensorIndex; - } - - public String getSensorType() { - return sensorType; - } - - public void setSensorType(final String sensorType) { - this.sensorType = sensorType; - } -} diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/SensorUnsubscribeCmd.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/SensorUnsubscribeCmd.java deleted file mode 100644 index 012baf6..0000000 --- a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/SensorUnsubscribeCmd.java +++ /dev/null @@ -1,38 +0,0 @@ -package io.github.blackspherefollower.buttplug4j.protocol.messages; - -import com.fasterxml.jackson.annotation.JsonProperty; -import io.github.blackspherefollower.buttplug4j.protocol.ButtplugConsts; -import io.github.blackspherefollower.buttplug4j.protocol.ButtplugDeviceMessage; - -public final class SensorUnsubscribeCmd extends ButtplugDeviceMessage { - - @JsonProperty(value = "SensorIndex", required = true) - private int sensorIndex; - @JsonProperty(value = "SensorType", required = true) - private String sensorType; - - public SensorUnsubscribeCmd(final long deviceIndex, final long id) { - super(id, deviceIndex); - } - - @SuppressWarnings("unused") - private SensorUnsubscribeCmd() { - super(ButtplugConsts.DEFAULT_MSG_ID, -1); - } - - public int getSensorIndex() { - return sensorIndex; - } - - public void setSensorIndex(final int sensorIndex) { - this.sensorIndex = sensorIndex; - } - - public String getSensorType() { - return sensorType; - } - - public void setSensorType(final String sensorType) { - this.sensorType = sensorType; - } -} diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/ServerInfo.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/ServerInfo.java index 8c2db68..6e6de43 100644 --- a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/ServerInfo.java +++ b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/ServerInfo.java @@ -6,8 +6,11 @@ public final class ServerInfo extends ButtplugMessage { - @JsonProperty(value = "MessageVersion", required = true) - private int messageVersion; + @JsonProperty(value = "ProtocolVersionMajor", required = true) + private int protocolVersionMajor; + + @JsonProperty(value = "ProtocolVersionMinor", required = true) + private int protocolVersionMinor; @JsonProperty(value = "MaxPingTime", required = true) private long maxPingTime; @@ -15,29 +18,39 @@ public final class ServerInfo extends ButtplugMessage { @JsonProperty(value = "ServerName", required = true) private String serverName; - public ServerInfo(final String serverName, final int messageVersion, final long maxPingTime, final long id) { + public ServerInfo(final String serverName, final int protocolVersionMajor, final int protocolVersionMinor, final long maxPingTime, final int id) { super(id); - this.setServerName(serverName); - this.setMessageVersion(messageVersion); - this.setMaxPingTime(maxPingTime); + this.serverName = serverName; + this.protocolVersionMajor = protocolVersionMajor; + this.protocolVersionMinor = protocolVersionMinor; + this.maxPingTime = maxPingTime; } @SuppressWarnings("unused") private ServerInfo() { super(ButtplugConsts.DEFAULT_MSG_ID); - this.setServerName(""); - this.setMessageVersion(1); - this.setMaxPingTime(0); + this.serverName = ""; + this.protocolVersionMajor = 4; + this.protocolVersionMinor = 0; + this.maxPingTime = 0; + } + + public int getProtocolVersionMajor() { + return protocolVersionMajor; + } + + public void setProtocolVersionMajor(final int protocolVersionMajor) { + this.protocolVersionMajor = protocolVersionMajor; } - public int getMessageVersion() { - return messageVersion; + public int getProtocolVersionMinor() { + return protocolVersionMinor; } - public void setMessageVersion(final int messageVersion) { - this.messageVersion = messageVersion; + public void setProtocolVersionMinor(final int protocolVersionMinor) { + this.protocolVersionMinor = protocolVersionMinor; } public long getMaxPingTime() { diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/StartScanning.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/StartScanning.java index b31c679..2898b20 100644 --- a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/StartScanning.java +++ b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/StartScanning.java @@ -10,7 +10,7 @@ private StartScanning() { super(ButtplugConsts.DEFAULT_MSG_ID); } - public StartScanning(final long id) { + public StartScanning(final int id) { super(id); } } diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/StopAllDevices.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/StopAllDevices.java index 1168448..edb6f65 100644 --- a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/StopAllDevices.java +++ b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/StopAllDevices.java @@ -10,7 +10,7 @@ private StopAllDevices() { super(ButtplugConsts.DEFAULT_MSG_ID); } - public StopAllDevices(final long id) { + public StopAllDevices(final int id) { super(id); } } diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/StopDeviceCmd.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/StopDeviceCmd.java index 8487d3b..b2ec26e 100644 --- a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/StopDeviceCmd.java +++ b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/StopDeviceCmd.java @@ -5,7 +5,7 @@ public final class StopDeviceCmd extends ButtplugDeviceMessage { - public StopDeviceCmd(final long deviceIndex, final long id) { + public StopDeviceCmd(final int id, final long deviceIndex) { super(id, deviceIndex); } diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/StopScanning.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/StopScanning.java index 6bcf30d..35dd4bd 100644 --- a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/StopScanning.java +++ b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/protocol/messages/StopScanning.java @@ -10,7 +10,7 @@ private StopScanning() { super(ButtplugConsts.DEFAULT_MSG_ID); } - public StopScanning(final long id) { + public StopScanning(final int id) { super(id); } } diff --git a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/util/Pair.java b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/util/Pair.java index 31355e8..c2e3b8d 100644 --- a/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/util/Pair.java +++ b/buttplug4j/src/main/java/io/github/blackspherefollower/buttplug4j/util/Pair.java @@ -27,4 +27,25 @@ public B setRight(final B value) { b = value; return b; } + + @Override + public String toString() { + return String.format("(%s, %s)", a, b); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + return ((Pair) o).a.equals(a) && ((Pair) o).b.equals(b); + } + + @Override + public int hashCode() { + return a.hashCode() ^ b.hashCode(); + } } diff --git a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/ButtplugExceptionTest.java b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/ButtplugExceptionTest.java new file mode 100644 index 0000000..fac4ec2 --- /dev/null +++ b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/ButtplugExceptionTest.java @@ -0,0 +1,17 @@ + +package io.github.blackspherefollower.buttplug4j; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +public class ButtplugExceptionTest { + + @Test + public void testExceptionWithMessage() { + String errorMessage = "Test error message"; + ButtplugException exception = new ButtplugException(); + exception.setMessage(errorMessage); + + assertEquals(errorMessage, exception.getMessage()); + } +} diff --git a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/client/ButtplugClientDeviceFeatureTest.java b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/client/ButtplugClientDeviceFeatureTest.java new file mode 100644 index 0000000..6625a2a --- /dev/null +++ b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/client/ButtplugClientDeviceFeatureTest.java @@ -0,0 +1,367 @@ + +package io.github.blackspherefollower.buttplug4j.client; + +import io.github.blackspherefollower.buttplug4j.protocol.ButtplugMessage; +import io.github.blackspherefollower.buttplug4j.protocol.messages.DeviceFeature; +import io.github.blackspherefollower.buttplug4j.protocol.messages.OutputCmd; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +import java.util.ArrayList; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +class ButtplugClientDeviceFeatureTest { + + private ButtplugClientDevice mockDevice; + private DeviceFeature testFeature; + private ButtplugClientDeviceFeature clientFeature; + + @BeforeEach + void setup() { + mockDevice = mock(ButtplugClientDevice.class); + + // Create a test feature with various output types + testFeature = new DeviceFeature(); + testFeature.setFeatureIndex(0); + testFeature.setFeatureDescription("Test Feature"); + + ArrayList outputs = new ArrayList<>(); + outputs.add(new DeviceFeature.Vibrate(new int[]{0, 100})); + outputs.add(new DeviceFeature.Rotate(new int[]{0, 50})); + outputs.add(new DeviceFeature.Oscillate(new int[]{0, 20})); + outputs.add(new DeviceFeature.Constrict(new int[]{0, 10})); + outputs.add(new DeviceFeature.Spray(new int[]{0, 5})); + outputs.add(new DeviceFeature.Position(new int[]{0, 25})); + outputs.add(new DeviceFeature.Led(new int[]{0, 255})); + outputs.add(new DeviceFeature.Temperature(new int[]{0, 30})); + testFeature.setOutput(outputs); + + ArrayList inputs = new ArrayList<>(); + testFeature.setInput(inputs); + + clientFeature = new ButtplugClientDeviceFeature(mockDevice, testFeature); + } + + @Test + void testConstructor() { + assertEquals("Test Feature", clientFeature.getDescription()); + } + + @Test + void testVibrateWithValidStep() throws Exception { + CompletableFuture future = CompletableFuture.completedFuture(mock(ButtplugMessage.class)); + when(mockDevice.sendOutputCommand(anyInt(), any(OutputCmd.IOutputCommand.class))).thenReturn(future); + + Future result = clientFeature.Vibrate(50); + + assertNotNull(result); + ArgumentCaptor captor = ArgumentCaptor.forClass(OutputCmd.IOutputCommand.class); + verify(mockDevice).sendOutputCommand(eq(0), captor.capture()); + assertInstanceOf(OutputCmd.Vibrate.class, captor.getValue()); + assertEquals(50, ((OutputCmd.Vibrate) captor.getValue()).getValue()); + } + + @Test + void testVibrateWithInvalidStepThrowsException() { + assertThrows(ButtplugDeviceFeatureException.class, () -> clientFeature.Vibrate(150)); + assertThrows(ButtplugDeviceFeatureException.class, () -> clientFeature.Vibrate(-1)); + } + + @Test + void testVibrateFloatWithValidValue() throws Exception { + CompletableFuture future = CompletableFuture.completedFuture(mock(ButtplugMessage.class)); + when(mockDevice.sendOutputCommand(anyInt(), any(OutputCmd.IOutputCommand.class))).thenReturn(future); + + Future result = clientFeature.VibrateFloat(0.5f); + + assertNotNull(result); + ArgumentCaptor captor = ArgumentCaptor.forClass(OutputCmd.IOutputCommand.class); + verify(mockDevice).sendOutputCommand(eq(0), captor.capture()); + assertInstanceOf(OutputCmd.Vibrate.class, captor.getValue()); + assertEquals(50, ((OutputCmd.Vibrate) captor.getValue()).getValue()); + } + + @Test + void testVibrateFloatWithBoundaryValues() throws Exception { + CompletableFuture future = CompletableFuture.completedFuture(mock(ButtplugMessage.class)); + when(mockDevice.sendOutputCommand(anyInt(), any(OutputCmd.IOutputCommand.class))).thenReturn(future); + + // Test minimum value + clientFeature.VibrateFloat(0.0f); + ArgumentCaptor captor = ArgumentCaptor.forClass(OutputCmd.IOutputCommand.class); + verify(mockDevice).sendOutputCommand(eq(0), captor.capture()); + assertEquals(0, ((OutputCmd.Vibrate) captor.getValue()).getValue()); + + // Test maximum value + reset(mockDevice); + when(mockDevice.sendOutputCommand(anyInt(), any(OutputCmd.IOutputCommand.class))).thenReturn(future); + clientFeature.VibrateFloat(1.0f); + verify(mockDevice).sendOutputCommand(eq(0), captor.capture()); + assertEquals(100, ((OutputCmd.Vibrate) captor.getValue()).getValue()); + } + + @Test + void testVibrateFloatWithInvalidValueThrowsException() { + assertThrows(ButtplugDeviceFeatureException.class, () -> clientFeature.VibrateFloat(1.5f)); + assertThrows(ButtplugDeviceFeatureException.class, () -> clientFeature.VibrateFloat(-0.1f)); + } + + @Test + void testRotateWithValidStep() throws Exception { + CompletableFuture future = CompletableFuture.completedFuture(mock(ButtplugMessage.class)); + when(mockDevice.sendOutputCommand(anyInt(), any(OutputCmd.IOutputCommand.class))).thenReturn(future); + + Future result = clientFeature.Rotate(25); + + assertNotNull(result); + ArgumentCaptor captor = ArgumentCaptor.forClass(OutputCmd.IOutputCommand.class); + verify(mockDevice).sendOutputCommand(eq(0), captor.capture()); + assertInstanceOf(OutputCmd.Rotate.class, captor.getValue()); + assertEquals(25, ((OutputCmd.Rotate) captor.getValue()).getValue()); + } + + @Test + void testRotateFloatWithValidValue() throws Exception { + CompletableFuture future = CompletableFuture.completedFuture(mock(ButtplugMessage.class)); + when(mockDevice.sendOutputCommand(anyInt(), any(OutputCmd.IOutputCommand.class))).thenReturn(future); + + Future result = clientFeature.RotateFloat(0.5f); + + assertNotNull(result); + ArgumentCaptor captor = ArgumentCaptor.forClass(OutputCmd.IOutputCommand.class); + verify(mockDevice).sendOutputCommand(eq(0), captor.capture()); + assertInstanceOf(OutputCmd.Rotate.class, captor.getValue()); + assertEquals(25, ((OutputCmd.Rotate) captor.getValue()).getValue()); + } + + @Test + void testConstrictWithValidStep() throws Exception { + CompletableFuture future = CompletableFuture.completedFuture(mock(ButtplugMessage.class)); + when(mockDevice.sendOutputCommand(anyInt(), any(OutputCmd.IOutputCommand.class))).thenReturn(future); + + Future result = clientFeature.Constrict(5); + + assertNotNull(result); + ArgumentCaptor captor = ArgumentCaptor.forClass(OutputCmd.IOutputCommand.class); + verify(mockDevice).sendOutputCommand(eq(0), captor.capture()); + assertInstanceOf(OutputCmd.Constrict.class, captor.getValue()); + assertEquals(5, ((OutputCmd.Constrict) captor.getValue()).getValue()); + } + + @Test + void testConstrictFloatWithValidValue() throws Exception { + CompletableFuture future = CompletableFuture.completedFuture(mock(ButtplugMessage.class)); + when(mockDevice.sendOutputCommand(anyInt(), any(OutputCmd.IOutputCommand.class))).thenReturn(future); + + Future result = clientFeature.ConstrictFloat(0.5f); + + assertNotNull(result); + ArgumentCaptor captor = ArgumentCaptor.forClass(OutputCmd.IOutputCommand.class); + verify(mockDevice).sendOutputCommand(eq(0), captor.capture()); + assertInstanceOf(OutputCmd.Constrict.class, captor.getValue()); + assertEquals(5, ((OutputCmd.Constrict) captor.getValue()).getValue()); + } + + @Test + void testSprayWithValidStep() throws Exception { + CompletableFuture future = CompletableFuture.completedFuture(mock(ButtplugMessage.class)); + when(mockDevice.sendOutputCommand(anyInt(), any(OutputCmd.IOutputCommand.class))).thenReturn(future); + + Future result = clientFeature.Spray(3); + + assertNotNull(result); + ArgumentCaptor captor = ArgumentCaptor.forClass(OutputCmd.IOutputCommand.class); + verify(mockDevice).sendOutputCommand(eq(0), captor.capture()); + assertInstanceOf(OutputCmd.Spray.class, captor.getValue()); + assertEquals(3, ((OutputCmd.Spray) captor.getValue()).getValue()); + } + + @Test + void testSprayFloatWithValidValue() throws Exception { + CompletableFuture future = CompletableFuture.completedFuture(mock(ButtplugMessage.class)); + when(mockDevice.sendOutputCommand(anyInt(), any(OutputCmd.IOutputCommand.class))).thenReturn(future); + + Future result = clientFeature.SprayFloat(0.6f); + + assertNotNull(result); + ArgumentCaptor captor = ArgumentCaptor.forClass(OutputCmd.IOutputCommand.class); + verify(mockDevice).sendOutputCommand(eq(0), captor.capture()); + assertInstanceOf(OutputCmd.Spray.class, captor.getValue()); + assertEquals(3, ((OutputCmd.Spray) captor.getValue()).getValue()); + } + + @Test + void testPositionWithValidStep() throws Exception { + CompletableFuture future = CompletableFuture.completedFuture(mock(ButtplugMessage.class)); + when(mockDevice.sendOutputCommand(anyInt(), any(OutputCmd.IOutputCommand.class))).thenReturn(future); + + Future result = clientFeature.Position(15); + + assertNotNull(result); + ArgumentCaptor captor = ArgumentCaptor.forClass(OutputCmd.IOutputCommand.class); + verify(mockDevice).sendOutputCommand(eq(0), captor.capture()); + assertInstanceOf(OutputCmd.Position.class, captor.getValue()); + assertEquals(15, ((OutputCmd.Position) captor.getValue()).getValue()); + } + + @Test + void testPositionFloatWithValidValue() throws Exception { + CompletableFuture future = CompletableFuture.completedFuture(mock(ButtplugMessage.class)); + when(mockDevice.sendOutputCommand(anyInt(), any(OutputCmd.IOutputCommand.class))).thenReturn(future); + + Future result = clientFeature.PositionFloat(0.8f); + + assertNotNull(result); + ArgumentCaptor captor = ArgumentCaptor.forClass(OutputCmd.IOutputCommand.class); + verify(mockDevice).sendOutputCommand(eq(0), captor.capture()); + assertInstanceOf(OutputCmd.Position.class, captor.getValue()); + assertEquals(20, ((OutputCmd.Position) captor.getValue()).getValue()); + } + + @Test + void testPositionWithDurationWithValidStep() throws Exception { + CompletableFuture future = CompletableFuture.completedFuture(mock(ButtplugMessage.class)); + when(mockDevice.sendOutputCommand(anyInt(), any(OutputCmd.IOutputCommand.class))).thenReturn(future); + + // Add PositionWithDuration to the feature + testFeature.getOutput().add(new DeviceFeature.PositionWithDuration(new int[]{0, 25}, new int[]{0, 1000})); + clientFeature = new ButtplugClientDeviceFeature(mockDevice, testFeature); + + Future result = clientFeature.PositionWithDuration(15, 500); + + assertNotNull(result); + ArgumentCaptor captor = ArgumentCaptor.forClass(OutputCmd.IOutputCommand.class); + verify(mockDevice).sendOutputCommand(eq(0), captor.capture()); + assertInstanceOf(OutputCmd.PositionWithDuration.class, captor.getValue()); + assertEquals(15, ((OutputCmd.PositionWithDuration) captor.getValue()).getPosition()); + assertEquals(500, ((OutputCmd.PositionWithDuration) captor.getValue()).getDuration()); + } + + @Test + void testPositionWithDurationFloatWithValidValue() throws Exception { + CompletableFuture future = CompletableFuture.completedFuture(mock(ButtplugMessage.class)); + when(mockDevice.sendOutputCommand(anyInt(), any(OutputCmd.IOutputCommand.class))).thenReturn(future); + + // Add PositionWithDuration to the feature + testFeature.getOutput().add(new DeviceFeature.PositionWithDuration(new int[]{0, 25}, new int[]{0, 1000})); + clientFeature = new ButtplugClientDeviceFeature(mockDevice, testFeature); + + Future result = clientFeature.PositionWithDurationFloat(0.8f, 500); + + assertNotNull(result); + ArgumentCaptor captor = ArgumentCaptor.forClass(OutputCmd.IOutputCommand.class); + verify(mockDevice).sendOutputCommand(eq(0), captor.capture()); + assertInstanceOf(OutputCmd.PositionWithDuration.class, captor.getValue()); + assertEquals(20, ((OutputCmd.PositionWithDuration) captor.getValue()).getPosition()); + assertEquals(500, ((OutputCmd.PositionWithDuration) captor.getValue()).getDuration()); + } + + @Test + void testLedWithValidStep() throws Exception { + CompletableFuture future = CompletableFuture.completedFuture(mock(ButtplugMessage.class)); + when(mockDevice.sendOutputCommand(anyInt(), any(OutputCmd.IOutputCommand.class))).thenReturn(future); + + Future result = clientFeature.Led(128); + + assertNotNull(result); + ArgumentCaptor captor = ArgumentCaptor.forClass(OutputCmd.IOutputCommand.class); + verify(mockDevice).sendOutputCommand(eq(0), captor.capture()); + assertInstanceOf(OutputCmd.Led.class, captor.getValue()); + assertEquals(128, ((OutputCmd.Led) captor.getValue()).getValue()); + } + + @Test + void testLedFloatWithValidValue() throws Exception { + CompletableFuture future = CompletableFuture.completedFuture(mock(ButtplugMessage.class)); + when(mockDevice.sendOutputCommand(anyInt(), any(OutputCmd.IOutputCommand.class))).thenReturn(future); + + Future result = clientFeature.LedFloat(0.5f); + + assertNotNull(result); + ArgumentCaptor captor = ArgumentCaptor.forClass(OutputCmd.IOutputCommand.class); + verify(mockDevice).sendOutputCommand(eq(0), captor.capture()); + assertInstanceOf(OutputCmd.Led.class, captor.getValue()); + assertEquals(127, ((OutputCmd.Led) captor.getValue()).getValue()); + } + + @Test + void testOscillateWithValidStep() throws Exception { + CompletableFuture future = CompletableFuture.completedFuture(mock(ButtplugMessage.class)); + when(mockDevice.sendOutputCommand(anyInt(), any(OutputCmd.IOutputCommand.class))).thenReturn(future); + + Future result = clientFeature.Oscillate(10); + + assertNotNull(result); + ArgumentCaptor captor = ArgumentCaptor.forClass(OutputCmd.IOutputCommand.class); + verify(mockDevice).sendOutputCommand(eq(0), captor.capture()); + assertInstanceOf(OutputCmd.Oscillate.class, captor.getValue()); + assertEquals(10, ((OutputCmd.Oscillate) captor.getValue()).getValue()); + } + + @Test + void testOscillateFloatWithValidValue() throws Exception { + CompletableFuture future = CompletableFuture.completedFuture(mock(ButtplugMessage.class)); + when(mockDevice.sendOutputCommand(anyInt(), any(OutputCmd.IOutputCommand.class))).thenReturn(future); + + Future result = clientFeature.OscillateFloat(0.5f); + + assertNotNull(result); + ArgumentCaptor captor = ArgumentCaptor.forClass(OutputCmd.IOutputCommand.class); + verify(mockDevice).sendOutputCommand(eq(0), captor.capture()); + assertInstanceOf(OutputCmd.Oscillate.class, captor.getValue()); + assertEquals(10, ((OutputCmd.Oscillate) captor.getValue()).getValue()); + } + + @Test + void testTemperatureWithValidStep() throws Exception { + CompletableFuture future = CompletableFuture.completedFuture(mock(ButtplugMessage.class)); + when(mockDevice.sendOutputCommand(anyInt(), any(OutputCmd.IOutputCommand.class))).thenReturn(future); + + Future result = clientFeature.Temperature(15); + + assertNotNull(result); + ArgumentCaptor captor = ArgumentCaptor.forClass(OutputCmd.IOutputCommand.class); + verify(mockDevice).sendOutputCommand(eq(0), captor.capture()); + assertInstanceOf(OutputCmd.Temperature.class, captor.getValue()); + assertEquals(15, ((OutputCmd.Temperature) captor.getValue()).getValue()); + } + + @Test + void testTemperatureFloatWithValidValue() throws Exception { + CompletableFuture future = CompletableFuture.completedFuture(mock(ButtplugMessage.class)); + when(mockDevice.sendOutputCommand(anyInt(), any(OutputCmd.IOutputCommand.class))).thenReturn(future); + + Future result = clientFeature.TemperatureFloat(0.5f); + + assertNotNull(result); + ArgumentCaptor captor = ArgumentCaptor.forClass(OutputCmd.IOutputCommand.class); + verify(mockDevice).sendOutputCommand(eq(0), captor.capture()); + assertInstanceOf(OutputCmd.Temperature.class, captor.getValue()); + assertEquals(15, ((OutputCmd.Temperature) captor.getValue()).getValue()); + } + + @Test + void testUnsupportedOutputTypeThrowsException() { + // Create a feature without Vibrate output + DeviceFeature limitedFeature = new DeviceFeature(); + limitedFeature.setFeatureIndex(0); + limitedFeature.setFeatureDescription("Limited Feature"); + limitedFeature.setOutput(new ArrayList<>()); + limitedFeature.setInput(new ArrayList<>()); + + ButtplugClientDeviceFeature limited = new ButtplugClientDeviceFeature(mockDevice, limitedFeature); + + assertThrows(ButtplugDeviceFeatureException.class, () -> limited.Vibrate(50)); + } + + @Test + void testGetDescription() { + assertEquals("Test Feature", clientFeature.getDescription()); + } +} diff --git a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/client/ButtplugClientDeviceTest.java b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/client/ButtplugClientDeviceTest.java new file mode 100644 index 0000000..46f0f52 --- /dev/null +++ b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/client/ButtplugClientDeviceTest.java @@ -0,0 +1,309 @@ + +package io.github.blackspherefollower.buttplug4j.client; + +import io.github.blackspherefollower.buttplug4j.protocol.ButtplugMessage; +import io.github.blackspherefollower.buttplug4j.protocol.messages.Device; +import io.github.blackspherefollower.buttplug4j.protocol.messages.DeviceFeature; +import io.github.blackspherefollower.buttplug4j.protocol.messages.InputCommandType; +import io.github.blackspherefollower.buttplug4j.protocol.messages.OutputCmd; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +class ButtplugClientDeviceTest { + + private ButtplugClient mockClient; + private Device testDevice; + private ButtplugClientDevice clientDevice; + + @BeforeEach + void setup() { + mockClient = mock(ButtplugClient.class); + + HashMap features = new HashMap<>(); + + // Feature 0: Vibrator + DeviceFeature vibratorFeature = new DeviceFeature(); + vibratorFeature.setFeatureIndex(0); + vibratorFeature.setFeatureDescription("Vibrator"); + ArrayList vibratorOutputs = new ArrayList<>(); + vibratorOutputs.add(new DeviceFeature.Vibrate(new int[]{0, 100})); + vibratorFeature.setOutput(vibratorOutputs); + features.put(0, vibratorFeature); + + // Feature 1: Battery sensor + DeviceFeature batteryFeature = new DeviceFeature(); + batteryFeature.setFeatureIndex(1); + batteryFeature.setFeatureDescription("Battery"); + ArrayList batteryInputs = new ArrayList<>(); + ArrayList batteryCommands = new ArrayList<>(); + batteryCommands.add(InputCommandType.READ); + batteryInputs.add(new DeviceFeature.Battery(batteryCommands, new int[][]{{0, 0}, {0, 100}})); + batteryFeature.setInput(batteryInputs); + features.put(1, batteryFeature); + + // Create a test device with various features + testDevice = new Device(5, "Test Device",features, 100, "Display Name"); + + testDevice.setDeviceFeatures(features); + + when(mockClient.getNextMsgId()).thenReturn(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + when(mockClient.sendMessage(any(ButtplugMessage.class))) + .thenReturn(CompletableFuture.completedFuture(mock(ButtplugMessage.class))); + + clientDevice = new ButtplugClientDevice(mockClient, testDevice); + } + + @Test + void testConstructorInitializesProperties() { + assertEquals(5, clientDevice.getDeviceIndex()); + assertEquals("Test Device", clientDevice.getName()); + assertEquals("Display Name", clientDevice.getDisplayName()); + assertEquals(Integer.valueOf(100), clientDevice.getMessageTimingGap()); + } + + @Test + void testConstructorWithNullDisplayName() { + testDevice.setDeviceDisplayName(null); + ButtplugClientDevice device = new ButtplugClientDevice(mockClient, testDevice); + + assertEquals("Test Device", device.getDisplayName()); + } + + @Test + void testConstructorWithEmptyDisplayName() { + testDevice.setDeviceDisplayName(""); + ButtplugClientDevice device = new ButtplugClientDevice(mockClient, testDevice); + + assertEquals("Test Device", device.getDisplayName()); + } + + @Test + void testGetDeviceFeatures() { + Map features = clientDevice.getDeviceFeatures(); + + assertNotNull(features); + assertEquals(2, features.size()); + assertTrue(features.containsKey(0)); + assertTrue(features.containsKey(1)); + assertEquals("Vibrator", features.get(0).getDescription()); + assertEquals("Battery", features.get(1).getDescription()); + } + + @Test + void testSendStopDeviceCmd() { + Future result = clientDevice.sendStopDeviceCmd(); + + assertNotNull(result); + verify(mockClient).getNextMsgId(); + verify(mockClient).sendMessage(any(ButtplugMessage.class)); + } + + @Test + void testSendOutputCommand() { + OutputCmd.Vibrate vibrateCommand = new OutputCmd.Vibrate(50); + + Future result = clientDevice.sendOutputCommand(0, vibrateCommand); + + assertNotNull(result); + verify(mockClient).getNextMsgId(); + verify(mockClient).sendMessage(any(OutputCmd.class)); + } + + @Test + void testSendOutputCommandWithDifferentFeatureIndex() { + OutputCmd.Vibrate vibrateCommand = new OutputCmd.Vibrate(75); + + Future result = clientDevice.sendOutputCommand(1, vibrateCommand); + + assertNotNull(result); + verify(mockClient).getNextMsgId(); + verify(mockClient).sendMessage(any(OutputCmd.class)); + } + + @Test + void testDeviceWithNoMessageTimingGap() { + testDevice.setDeviceMessageTimingGap(null); + ButtplugClientDevice device = new ButtplugClientDevice(mockClient, testDevice); + + assertNull(device.getMessageTimingGap()); + } + + @Test + void testDeviceWithZeroMessageTimingGap() { + testDevice.setDeviceMessageTimingGap(0); + ButtplugClientDevice device = new ButtplugClientDevice(mockClient, testDevice); + + assertEquals(Integer.valueOf(0), device.getMessageTimingGap()); + } + + @Test + void testDeviceWithNoFeatures() { + testDevice.setDeviceFeatures(new HashMap<>()); + ButtplugClientDevice device = new ButtplugClientDevice(mockClient, testDevice); + + Map features = device.getDeviceFeatures(); + assertNotNull(features); + assertTrue(features.isEmpty()); + } + + @Test + void testDeviceWithNullFeatures() { + testDevice.setDeviceFeatures(null); + ButtplugClientDevice device = new ButtplugClientDevice(mockClient, testDevice); + + Map features = device.getDeviceFeatures(); + assertNotNull(features); + assertTrue(features.isEmpty()); + } + + @Test + void testDeviceWithMultipleOutputFeatures() { + HashMap multiFeatures = new HashMap<>(); + + // Add multiple output features + for (int i = 0; i < 5; i++) { + DeviceFeature feature = new DeviceFeature(); + feature.setFeatureIndex(i); + feature.setFeatureDescription("Feature " + i); + ArrayList outputs = new ArrayList<>(); + outputs.add(new DeviceFeature.Vibrate(new int[]{0, 100})); + feature.setOutput(outputs); + multiFeatures.put(i, feature); + } + + testDevice.setDeviceFeatures(multiFeatures); + ButtplugClientDevice device = new ButtplugClientDevice(mockClient, testDevice); + + Map features = device.getDeviceFeatures(); + assertEquals(5, features.size()); + + for (int i = 0; i < 5; i++) { + assertTrue(features.containsKey(i)); + assertEquals("Feature " + i, features.get(i).getDescription()); + } + } + + @Test + void testDeviceWithMixedInputOutputFeatures() { + HashMap mixedFeatures = new HashMap<>(); + + // Output feature + DeviceFeature outputFeature = new DeviceFeature(); + outputFeature.setFeatureIndex(0); + outputFeature.setFeatureDescription("Output Feature"); + ArrayList outputs = new ArrayList<>(); + outputs.add(new DeviceFeature.Rotate(new int[]{0, 50})); + outputFeature.setOutput(outputs); + mixedFeatures.put(0, outputFeature); + + // Input feature + DeviceFeature inputFeature = new DeviceFeature(); + inputFeature.setFeatureIndex(1); + inputFeature.setFeatureDescription("Input Feature"); + ArrayList inputs = new ArrayList<>(); + ArrayList commands = new ArrayList<>(); + commands.add(InputCommandType.READ); + inputs.add(new DeviceFeature.Pressure(commands, new int[][]{{0, 0}, {0, 100}})); + inputFeature.setInput(inputs); + mixedFeatures.put(1, inputFeature); + + // Both input and output feature + DeviceFeature mixedFeature = new DeviceFeature(); + mixedFeature.setFeatureIndex(2); + mixedFeature.setFeatureDescription("Mixed Feature"); + mixedFeature.setOutput(outputs); + mixedFeature.setInput(inputs); + mixedFeatures.put(2, mixedFeature); + + testDevice.setDeviceFeatures(mixedFeatures); + ButtplugClientDevice device = new ButtplugClientDevice(mockClient, testDevice); + + Map features = device.getDeviceFeatures(); + assertEquals(3, features.size()); + assertEquals("Output Feature", features.get(0).getDescription()); + assertEquals("Input Feature", features.get(1).getDescription()); + assertEquals("Mixed Feature", features.get(2).getDescription()); + } + + @Test + void testGetDeviceIndex() { + assertEquals(5, clientDevice.getDeviceIndex()); + } + + @Test + void testGetName() { + assertEquals("Test Device", clientDevice.getName()); + } + + @Test + void testGetDisplayName() { + assertEquals("Display Name", clientDevice.getDisplayName()); + } + + @Test + void testGetMessageTimingGap() { + assertEquals(Integer.valueOf(100), clientDevice.getMessageTimingGap()); + } + + @Test + void testDeviceWithSpecialCharactersInName() { + String specialName = "Test!@#$%^&*()_+-=[]{}|;':\",./<>?"; + testDevice.setDeviceName(specialName); + testDevice.setDeviceDisplayName(specialName); + ButtplugClientDevice device = new ButtplugClientDevice(mockClient, testDevice); + + assertEquals(specialName, device.getName()); + assertEquals(specialName, device.getDisplayName()); + } + + @Test + void testDeviceWithNegativeIndex() { + testDevice.setDeviceIndex(-1); + ButtplugClientDevice device = new ButtplugClientDevice(mockClient, testDevice); + + assertEquals(-1, device.getDeviceIndex()); + } + + @Test + void testDeviceWithLargeIndex() { + testDevice.setDeviceIndex(Integer.MAX_VALUE); + ButtplugClientDevice device = new ButtplugClientDevice(mockClient, testDevice); + + assertEquals(Integer.MAX_VALUE, device.getDeviceIndex()); + } + + @Test + void testDeviceWithLargeMessageTimingGap() { + testDevice.setDeviceMessageTimingGap(Integer.MAX_VALUE); + ButtplugClientDevice device = new ButtplugClientDevice(mockClient, testDevice); + + assertEquals(Integer.valueOf(Integer.MAX_VALUE), device.getMessageTimingGap()); + } + + @Test + void testMultipleSendOutputCommandCalls() { + OutputCmd.Vibrate command1 = new OutputCmd.Vibrate(25); + OutputCmd.Vibrate command2 = new OutputCmd.Vibrate(50); + OutputCmd.Vibrate command3 = new OutputCmd.Vibrate(75); + + Future result1 = clientDevice.sendOutputCommand(0, command1); + Future result2 = clientDevice.sendOutputCommand(0, command2); + Future result3 = clientDevice.sendOutputCommand(0, command3); + + assertNotNull(result1); + assertNotNull(result2); + assertNotNull(result3); + verify(mockClient, times(3)).getNextMsgId(); + verify(mockClient, times(3)).sendMessage(any(OutputCmd.class)); + } +} diff --git a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/client/ButtplugClientExceptionTest.java b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/client/ButtplugClientExceptionTest.java new file mode 100644 index 0000000..6676d42 --- /dev/null +++ b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/client/ButtplugClientExceptionTest.java @@ -0,0 +1,22 @@ +package io.github.blackspherefollower.buttplug4j.client; + +import io.github.blackspherefollower.buttplug4j.ButtplugException; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +public class ButtplugClientExceptionTest { + + @Test + public void testExceptionWithMessage() { + String errorMessage = "Client error occurred"; + ButtplugClientException exception = new ButtplugClientException(errorMessage); + + assertEquals(errorMessage, exception.getMessage()); + } + + @Test + public void testExceptionInheritance() { + ButtplugClientException exception = new ButtplugClientException("test"); + assertInstanceOf(ButtplugException.class, exception); + } +} diff --git a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/client/ButtplugClientTest.java b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/client/ButtplugClientTest.java new file mode 100644 index 0000000..b43aed4 --- /dev/null +++ b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/client/ButtplugClientTest.java @@ -0,0 +1,487 @@ +package io.github.blackspherefollower.buttplug4j.client; + +import io.github.blackspherefollower.buttplug4j.protocol.ButtplugMessage; +import io.github.blackspherefollower.buttplug4j.protocol.messages.*; +import io.github.blackspherefollower.buttplug4j.protocol.messages.Error; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.jupiter.api.Assertions.*; + +class ButtplugClientTest { + + private TestButtplugClient client; + private AtomicReference addedDevice; + private AtomicReference updatedDevice; + private AtomicReference removedDevice; + private AtomicBoolean scanningFinishedCalled; + private AtomicReference errorReceived; + private AtomicReference sensorReadingReceived; + private AtomicBoolean connectedCalled; + + @BeforeEach + void setup() { + client = new TestButtplugClient("Test Client"); + addedDevice = new AtomicReference<>(); + updatedDevice = new AtomicReference<>(); + removedDevice = new AtomicReference<>(); + scanningFinishedCalled = new AtomicBoolean(false); + errorReceived = new AtomicReference<>(); + sensorReadingReceived = new AtomicReference<>(); + connectedCalled = new AtomicBoolean(false); + } + + @Test + void testConstructor() { + assertNotNull(client); + assertEquals(ButtplugClient.ConnectionState.DISCONNECTED, client.getConnectionState()); + assertFalse(client.isConnected()); + } + + @Test + void testGetNextMsgId() { + assertEquals(1, client.getNextMsgId()); + assertEquals(2, client.getNextMsgId()); + assertEquals(3, client.getNextMsgId()); + } + + @Test + void testConnectionStates() { + assertEquals(ButtplugClient.ConnectionState.DISCONNECTED, client.getConnectionState()); + assertFalse(client.isConnected()); + + client.setConnectionState(ButtplugClient.ConnectionState.CONNECTING); + assertEquals(ButtplugClient.ConnectionState.CONNECTING, client.getConnectionState()); + assertFalse(client.isConnected()); + + client.setConnectionState(ButtplugClient.ConnectionState.CONNECTED); + assertEquals(ButtplugClient.ConnectionState.CONNECTED, client.getConnectionState()); + assertTrue(client.isConnected()); + } + + @Test + void testSetAndGetDeviceAddedHandler() { + IDeviceAddedEvent handler = device -> addedDevice.set(device); + client.setDeviceAdded(handler); + assertEquals(handler, client.getDeviceAdded()); + } + + @Test + void testSetAndGetDeviceRemovedHandler() { + IDeviceRemovedEvent handler = device -> removedDevice.set(device); + client.setDeviceRemoved(handler); + assertEquals(handler, client.getDeviceRemoved()); + } + + @Test + void testSetAndGetScanningFinishedHandler() { + IScanningEvent handler = () -> scanningFinishedCalled.set(true); + client.setScanningFinished(handler); + assertEquals(handler, client.getScanningFinished()); + } + + @Test + void testSetAndGetErrorReceivedHandler() { + IErrorEvent handler = error -> errorReceived.set(error); + client.setErrorReceived(handler); + assertEquals(handler, client.getErrorReceived()); + } + + @Test + void testSetAndGetSensorReadingReceivedHandler() { + ISensorReadingEvent handler = reading -> sensorReadingReceived.set(reading); + client.setSensorReadingReceived(handler); + assertEquals(handler, client.getSensorReadingReceived()); + } + + @Test + void testSetAndGetOnConnectedHandler() { + IConnectedEvent handler = c -> connectedCalled.set(true); + client.setOnConnected(handler); + assertEquals(handler, client.getOnConnected()); + } + + @Test + void testOnMessageWithOk() { + AtomicInteger completedId = new AtomicInteger(-1); + CompletableFuture future = new CompletableFuture<>(); + future.thenAccept(msg -> { + if (msg instanceof Ok) { + completedId.set(msg.getId()); + } + }); + + client.scheduleWait(5, future); + + List messages = new ArrayList<>(); + messages.add(new Ok(5)); + client.onMessage(messages); + + assertTrue(future.isDone()); + assertEquals(5, completedId.get()); + } + + @Test + void testOnMessageWithError() { + client.setErrorReceived(error -> errorReceived.set(error)); + + List messages = new ArrayList<>(); + Error error = new Error("Test error", Error.ErrorClass.ERROR_DEVICE, 0); + messages.add(error); + client.onMessage(messages); + + assertNotNull(errorReceived.get()); + assertEquals("Test error", errorReceived.get().getErrorMessage()); + } + + @Test + void testOnMessageWithScanningFinished() { + client.setScanningFinished(() -> scanningFinishedCalled.set(true)); + + List messages = new ArrayList<>(); + messages.add(new ScanningFinished()); + client.onMessage(messages); + + assertTrue(scanningFinishedCalled.get()); + } + + @Test + void testOnMessageWithInputReading() { + client.setSensorReadingReceived(reading -> sensorReadingReceived.set(reading)); + + List messages = new ArrayList<>(); + InputReading reading = new InputReading(0, 1, 1); + messages.add(reading); + client.onMessage(messages); + + assertNotNull(sensorReadingReceived.get()); + } + + @Test + void testStartScanningAsync() { + CompletableFuture future = (CompletableFuture) client.startScanningAsync(); + + assertNotNull(future); + assertInstanceOf(StartScanning.class, client.lastSentMessage); + } + + @Test + void testStopScanningAsync() { + CompletableFuture future = (CompletableFuture) client.stopScanningAsync(); + + assertNotNull(future); + assertInstanceOf(StopScanning.class, client.lastSentMessage); + } + + @Test + void testStopAllDevicesAsync() { + CompletableFuture future = (CompletableFuture) client.stopAllDevicesAsync(); + + assertNotNull(future); + assertInstanceOf(StopAllDevices.class, client.lastSentMessage); + } + + @Test + void testStartScanning() throws ExecutionException, InterruptedException, IOException { + client.setNextResponse(new Ok(1)); + boolean result = client.startScanning(); + + assertTrue(result); + assertInstanceOf(StartScanning.class, client.lastSentMessage); + } + + @Test + void testStopScanning() throws ExecutionException, InterruptedException { + client.setNextResponse(new Ok(1)); + boolean result = client.stopScanning(); + + assertTrue(result); + assertInstanceOf(StopScanning.class, client.lastSentMessage); + } + + @Test + void testStopAllDevices() throws ExecutionException, InterruptedException, IOException { + client.setNextResponse(new Ok(1)); + boolean result = client.stopAllDevices(); + + assertTrue(result); + assertInstanceOf(StopAllDevices.class, client.lastSentMessage); + } + + @Test + void testGetDevicesReturnsEmptyListInitially() { + List devices = client.getDevices(); + + assertNotNull(devices); + assertTrue(devices.isEmpty()); + } + + @Test + void testRequestDeviceList() throws ButtplugClientException, ExecutionException, InterruptedException { + + // Create test device + Device device = new Device(0,"Test Device",new HashMap<>(),100, "Display Name" ); + HashMap devices = new HashMap<>(); + devices.put(0, device); + + DeviceList deviceList = new DeviceList(devices, 1); + client.setNextResponse(deviceList); + + client.setDeviceAdded(dev -> addedDevice.set(dev)); + client.setDeviceRemoved(dev -> removedDevice.set(dev)); + client.setDeviceChanged(dev -> updatedDevice.set(dev)); + client.requestDeviceList(); + ButtplugMessage msg = client.sentMessages.remove(0); + assertInstanceOf(RequestDeviceList.class, msg); + + // Since we don't exactly mock commands exactly, we need to throw the response back to the client + client.onMessage(Collections.singletonList(deviceList)); + + assertNotNull(addedDevice.get()); + assertNull(removedDevice.get()); + assertNull(updatedDevice.get()); + assertEquals("Test Device", addedDevice.get().getName()); + assertEquals(1, client.getDevices().size()); + addedDevice.set(null); + + Device device2 = new Device(1,"Test Device 2",new HashMap<>(),100, "Other" ); + devices.put(1, device2); + + client.onMessage(Collections.singletonList(deviceList)); + + assertNotNull(addedDevice.get()); + assertNull(removedDevice.get()); + assertEquals("Test Device 2", addedDevice.get().getName()); + assertEquals(2, client.getDevices().size()); + addedDevice.set(null); + + devices.remove(0); + device2.setDeviceName("Bob"); + client.onMessage(Collections.singletonList(deviceList)); + + assertNull(addedDevice.get()); + assertNotNull(removedDevice.get()); + assertEquals(0, removedDevice.get()); + assertNotNull(updatedDevice.get()); + assertEquals("Bob", updatedDevice.get().getName()); + assertEquals(1, client.getDevices().size()); + } + + @Test + void testRequestDeviceListWithError() { + client.setNextResponse(new Error("Test error", Error.ErrorClass.ERROR_DEVICE, 1)); + + assertThrows(ButtplugClientException.class, () -> client.requestDeviceList()); + } + + @Test + void testSendDeviceMessageWithValidDevice() throws Exception { + // Add a device first + Device device = new Device(5,"Test Device",new HashMap<>(),100, "Display Name" ); + + HashMap devices = new HashMap<>(); + devices.put(5, device); + + DeviceList deviceList = new DeviceList(devices, 1); + client.setNextResponse(deviceList); + client.requestDeviceList(); + + // Now send a message to the device + ButtplugClientDevice clientDevice = client.getDevices().get(0); + StopDeviceCmd stopCmd = new StopDeviceCmd(2, 5); + client.setNextResponse(new Ok(2)); + + CompletableFuture result = client.sendDeviceMessage(clientDevice, stopCmd); + + assertNotNull(result); + ButtplugMessage response = result.get(); + assertInstanceOf(Ok.class, response); + } + + @Test + void testSendDeviceMessageWithInvalidDevice() { + Device device = new Device(999,"Invalid Device",new HashMap<>(),100, "" ); + + + ButtplugClientDevice clientDevice = new ButtplugClientDevice(client, device); + StopDeviceCmd stopCmd = new StopDeviceCmd(1, 999); + + CompletableFuture result = client.sendDeviceMessage(clientDevice, stopCmd); + + assertTrue(result.isDone()); + assertInstanceOf(Error.class, result.getNow(null)); + Error error = (Error) result.getNow(null); + assertEquals("Device not available.", error.getErrorMessage()); + } + + @Test + void testDisconnect() { + client.setConnectionState(ButtplugClient.ConnectionState.CONNECTED); + + CompletableFuture future = new CompletableFuture<>(); + client.scheduleWait(10, future); + + client.disconnect(); + + assertTrue(future.isDone()); + assertInstanceOf(Error.class, future.getNow(null)); + Error error = (Error) future.getNow(null); + assertEquals("Connection closed!", error.getErrorMessage()); + assertTrue(client.cleanupCalled); + } + + @Test + void testDoHandshakeSuccess() { + ServerInfo serverInfo = new ServerInfo("Test Server", 4, 0, 0, 1); + client.setNextResponse(serverInfo); + client.setNextResponse(new DeviceList(new HashMap<>(), 2)); + client.setOnConnected(c -> connectedCalled.set(true)); + + client.doHandshake(); + + assertEquals(ButtplugClient.ConnectionState.CONNECTED, client.getConnectionState()); + assertTrue(connectedCalled.get()); + } + + @Test + void testDoHandshakeWithPing() throws InterruptedException { + ServerInfo serverInfo = new ServerInfo("Test Server", 4, 0, 500, 1); + client.setNextResponse(serverInfo); + client.setNextResponse(new DeviceList(new HashMap<>(), 2)); + client.setNextResponse(new Ok(3)); // For ping + + client.doHandshake(); + + assertEquals(ButtplugClient.ConnectionState.CONNECTED, client.getConnectionState()); + + // Wait a bit to see if ping timer fires + Thread.sleep(300); + + // Verify ping was sent + boolean foundPing = false; + for (ButtplugMessage msg : client.sentMessages) { + if (msg instanceof Ping) { + foundPing = true; + break; + } + } + assertTrue(foundPing); + } + + @Test + void testDoHandshakeWithError() { + client.setNextResponse(new Error("Handshake failed", Error.ErrorClass.ERROR_UNKNOWN, 1)); + client.setErrorReceived(error -> errorReceived.set(error)); + + client.doHandshake(); + + assertNotNull(errorReceived.get()); + } + + @Test + void testMultipleDevicesInDeviceList() throws Exception { + Device device1 = new Device(0,"Device 1",new HashMap<>(),100, "" ); + Device device2 = new Device(1,"Device 2",new HashMap<>(),100, "" ); + + HashMap devices = new HashMap<>(); + devices.put(0, device1); + devices.put(1, device2); + + DeviceList deviceList = new DeviceList(devices, 1); + client.setNextResponse(deviceList); + + AtomicInteger deviceCount = new AtomicInteger(0); + client.setDeviceAdded(dev -> deviceCount.incrementAndGet()); + client.requestDeviceList(); + + assertEquals(2, deviceCount.get()); + assertEquals(2, client.getDevices().size()); + } + + @Test + void testScheduleWait() { + CompletableFuture future = new CompletableFuture<>(); + CompletableFuture result = client.scheduleWait(42, future); + + assertSame(future, result); + assertFalse(result.isDone()); + + // Simulate receiving a message + List messages = new ArrayList<>(); + messages.add(new Ok(42)); + client.onMessage(messages); + + assertTrue(result.isDone()); + assertInstanceOf(Ok.class, result.getNow(null)); + } + + @Test + void testGetParser() { + assertNotNull(client.getParser()); + } + + @Test + void testWaitForOk() throws ExecutionException, InterruptedException { + CompletableFuture okFuture = CompletableFuture.completedFuture(new Ok(1)); + assertTrue(client.waitForOk(okFuture)); + + CompletableFuture errorFuture = CompletableFuture.completedFuture( + new Error("Error", Error.ErrorClass.ERROR_UNKNOWN, 1) + ); + assertFalse(client.waitForOk(errorFuture)); + } + + /** + * Concrete implementation of ButtplugClient for testing purposes + */ + private static class TestButtplugClient extends ButtplugClient { + private final List messageQueue = new ArrayList<>(); + private int queueIndex = 0; + ButtplugMessage lastSentMessage; + List sentMessages = new ArrayList<>(); + boolean cleanupCalled = false; + + public TestButtplugClient(String clientName) { + super(clientName); + } + + public void setNextResponse(ButtplugMessage message) { + messageQueue.add(message); + } + + @Override + protected CompletableFuture sendMessage(ButtplugMessage msg) { + lastSentMessage = msg; + sentMessages.add(msg); + + CompletableFuture future = new CompletableFuture<>(); + scheduleWait(msg.getId(), future); + + if (queueIndex < messageQueue.size()) { + ButtplugMessage response = messageQueue.get(queueIndex++); + response.setId(msg.getId()); + onMessage(Collections.singletonList(response)); + } else { + onMessage(Collections.singletonList(new Ok(msg.getId()))); + } + + return future; + } + + @Override + protected void cleanup() { + cleanupCalled = true; + } + } +} diff --git a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/client/ButtplugDeviceExceptionTest.java b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/client/ButtplugDeviceExceptionTest.java new file mode 100644 index 0000000..a56fb4a --- /dev/null +++ b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/client/ButtplugDeviceExceptionTest.java @@ -0,0 +1,22 @@ +package io.github.blackspherefollower.buttplug4j.client; + +import io.github.blackspherefollower.buttplug4j.ButtplugException; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +public class ButtplugDeviceExceptionTest { + + @Test + public void testExceptionWithMessage() { + String errorMessage = "Device not found"; + ButtplugDeviceException exception = new ButtplugDeviceException(errorMessage); + + assertEquals(errorMessage, exception.getMessage()); + } + + @Test + public void testExceptionInheritance() { + ButtplugDeviceException exception = new ButtplugDeviceException("test"); + assertInstanceOf(ButtplugException.class, exception); + } +} diff --git a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/client/ButtplugDeviceFeatureExceptionTest.java b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/client/ButtplugDeviceFeatureExceptionTest.java new file mode 100644 index 0000000..a86283d --- /dev/null +++ b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/client/ButtplugDeviceFeatureExceptionTest.java @@ -0,0 +1,21 @@ +package io.github.blackspherefollower.buttplug4j.client; + +import io.github.blackspherefollower.buttplug4j.ButtplugException; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +public class ButtplugDeviceFeatureExceptionTest { + + @Test + public void testExceptionWithMessage() { + ButtplugDeviceFeatureException exception = new ButtplugDeviceFeatureException("slap"); + + assertEquals("Buttplug Device Feature does not support slap", exception.getMessage()); + } + + @Test + public void testExceptionInheritance() { + ButtplugDeviceFeatureException exception = new ButtplugDeviceFeatureException("test"); + assertInstanceOf(ButtplugException.class, exception); + } +} diff --git a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/client/ButtplugProtocolExceptionTest.java b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/client/ButtplugProtocolExceptionTest.java new file mode 100644 index 0000000..b358461 --- /dev/null +++ b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/client/ButtplugProtocolExceptionTest.java @@ -0,0 +1,27 @@ +package io.github.blackspherefollower.buttplug4j.protocol; + +import com.fasterxml.jackson.core.JsonParseException; +import io.github.blackspherefollower.buttplug4j.ButtplugException; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +public class ButtplugProtocolExceptionTest { + + @Test + public void testExceptionWithMessage() { + String errorMessage = "Invalid protocol message"; + JsonParseException cause = new JsonParseException(errorMessage); + ButtplugProtocolException exception = new ButtplugProtocolException(cause); + + assertEquals("Buttplug JSON message exception", exception.getMessage()); + assertEquals(errorMessage, exception.getCause().getMessage()); + } + + @Test + public void testExceptionInheritance() { + String errorMessage = "Invalid protocol message"; + JsonParseException cause = new JsonParseException(errorMessage); + ButtplugProtocolException exception = new ButtplugProtocolException(cause); + assertInstanceOf(ButtplugException.class, exception); + } +} diff --git a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/client/DeviceFeatureTest.java b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/client/DeviceFeatureTest.java new file mode 100644 index 0000000..6393074 --- /dev/null +++ b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/client/DeviceFeatureTest.java @@ -0,0 +1,23 @@ +package io.github.blackspherefollower.buttplug4j.protocol.messages; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class DeviceFeatureTest { + + @Test + public void testDeviceFeatureCreation() { + DeviceFeature feature = new DeviceFeature(); + assertNotNull(feature); + } + + @Test + public void testDeviceFeatureWithProperties() { + DeviceFeature feature = new DeviceFeature(); + + // Test that the feature can be created and has expected behavior + // The exact implementation depends on the DeviceFeature class structure + assertNotNull(feature); + } +} diff --git a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/ButtplugMessageTest.java b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/ButtplugMessageTest.java new file mode 100644 index 0000000..aaf0b58 --- /dev/null +++ b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/ButtplugMessageTest.java @@ -0,0 +1,38 @@ +package io.github.blackspherefollower.buttplug4j.protocol; + +import io.github.blackspherefollower.buttplug4j.protocol.messages.Ok; +import io.github.blackspherefollower.buttplug4j.protocol.messages.Ping; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class ButtplugMessageTest { + + @Test + public void testMessageId() { + ButtplugMessage msg = new Ok(5); + assertEquals(5, msg.getId()); + } + + @Test + public void testMessageIdSetter() { + ButtplugMessage msg = new Ok(1); + msg.setId(10); + assertEquals(10, msg.getId()); + } + + @Test + public void testSystemMessageId() { + ButtplugMessage msg = new Ok(ButtplugConsts.SYSTEM_MSG_ID); + assertEquals(0, msg.getId()); + } + + @Test + public void testDifferentMessageTypes() { + ButtplugMessage okMsg = new Ok(1); + ButtplugMessage pingMsg = new Ping(2); + + assertNotEquals(okMsg.getClass(), pingMsg.getClass()); + assertNotEquals(okMsg.getId(), pingMsg.getId()); + } +} diff --git a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/DeviceAddedTest.java b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/DeviceAddedTest.java deleted file mode 100644 index d476587..0000000 --- a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/DeviceAddedTest.java +++ /dev/null @@ -1,37 +0,0 @@ -package io.github.blackspherefollower.buttplug4j.protocol.messages; - -import io.github.blackspherefollower.buttplug4j.protocol.ButtplugJsonMessageParser; -import io.github.blackspherefollower.buttplug4j.protocol.ButtplugMessage; -import io.github.blackspherefollower.buttplug4j.protocol.ButtplugProtocolException; -import org.junit.jupiter.api.Test; - -import java.io.IOException; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -public class DeviceAddedTest { - - @Test - public void test() throws IOException, ButtplugProtocolException { - String testStr = "[{\"DeviceAdded\":{\"Id\":3,\"DeviceIndex\":2,\"DeviceName\":\"foo\",\"DeviceMessages\":{\"ScalarCmd\":[{\"StepCount\":20,\"FeatureDescriptor\":\"Clitoral Stimulator\",\"ActuatorType\":\"Vibrate\"},{\"StepCount\":20,\"FeatureDescriptor\":\"Insertable Vibrator\",\"ActuatorType\":\"Vibrate\"}],\"StopDeviceCmd\":{}}}}]"; - - ButtplugJsonMessageParser parser = new ButtplugJsonMessageParser(); - List msgs = parser.parseJson(testStr); - - assertEquals(1, msgs.size()); - assertEquals(DeviceAdded.class, msgs.get(0).getClass()); - assertEquals(3, msgs.get(0).getId()); - assertEquals(2, ((DeviceAdded) msgs.get(0)).getDeviceIndex()); - assertEquals("foo", ((DeviceAdded) msgs.get(0)).getDeviceName()); - assertEquals("ScalarCmd", ((DeviceAdded) msgs.get(0)).getDeviceMessages().get(0).getMessage()); - assertEquals("StopDeviceCmd", ((DeviceAdded) msgs.get(0)).getDeviceMessages().get(1).getMessage()); - - String jsonOut = parser.formatJson(msgs); - assertEquals(testStr, jsonOut); - - jsonOut = parser.formatJson(msgs.get(0)); - assertEquals(testStr, jsonOut); - } - -} diff --git a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/DeviceListTest.java b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/DeviceListTest.java index 8d582d2..f42a29f 100644 --- a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/DeviceListTest.java +++ b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/DeviceListTest.java @@ -1,20 +1,42 @@ package io.github.blackspherefollower.buttplug4j.protocol.messages; +import dev.harrel.jsonschema.Validator; +import dev.harrel.jsonschema.ValidatorFactory; import io.github.blackspherefollower.buttplug4j.protocol.ButtplugJsonMessageParser; import io.github.blackspherefollower.buttplug4j.protocol.ButtplugMessage; import io.github.blackspherefollower.buttplug4j.protocol.ButtplugProtocolException; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import java.io.BufferedInputStream; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; +import java.util.HashMap; import java.util.List; +import java.util.stream.Collectors; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.*; public class DeviceListTest { + static String schema = null; + + @BeforeAll + public static void setup() throws IOException { + BufferedInputStream in = new BufferedInputStream(new URL(TestConstants.SCHEMA_URL).openStream()); + schema = new BufferedReader(new InputStreamReader(in)) + .lines().collect(Collectors.joining("\n")); + in.close(); + } + @Test public void test() throws IOException, ButtplugProtocolException { - String testStr = "[{\"DeviceList\":{\"Id\":5,\"Devices\":[{\"DeviceIndex\":2,\"DeviceName\":\"foo\",\"DeviceMessages\":{\"ScalarCmd\":[{\"StepCount\":20,\"FeatureDescriptor\":\"Clitoral Stimulator\",\"ActuatorType\":\"Vibrate\"},{\"StepCount\":20,\"FeatureDescriptor\":\"Insertable Vibrator\",\"ActuatorType\":\"Vibrate\"}],\"StopDeviceCmd\":{}}},{\"DeviceIndex\":4,\"DeviceName\":\"bar\",\"DeviceMessages\":{\"ScalarCmd\":[{\"StepCount\":20,\"FeatureDescriptor\":\"Clitoral Stimulator\",\"ActuatorType\":\"Vibrate\"},{\"StepCount\":20,\"FeatureDescriptor\":\"Insertable Vibrator\",\"ActuatorType\":\"Vibrate\"}],\"StopDeviceCmd\":{}}}]}}]"; + String testStr = "[{\"DeviceList\":{\"Id\":5,\"Devices\":{\"0\":{\"DeviceIndex\":0,\"DeviceName\":\"Test Vibrator\",\"DeviceMessageTimingGap\":100,\"DeviceFeatures\":{\"0\":{\"FeatureIndex\":0,\"FeatureDescription\":\"Clitoral Stimulator\",\"Output\":{\"Vibrate\":{\"Value\":[0,20]}}},\"1\":{\"FeatureIndex\":1,\"FeatureDescription\":\"Insertable Stimulator\",\"Output\":{\"Vibrate\":{\"Value\":[0,20]}}},\"2\":{\"FeatureIndex\":2,\"FeatureDescription\":\"Battery\",\"Input\":{\"Battery\":{\"InputCommands\":[\"Read\"],\"ValueRange\":[[0,0],[0,100]]}}}}},\"2\":{\"DeviceIndex\":2,\"DeviceName\":\"Test Stroker\",\"DeviceMessageTimingGap\":100,\"DeviceDisplayName\":\"User set name\",\"DeviceFeatures\":{\"0\":{\"FeatureIndex\":0,\"FeatureDescription\":\"Stroker\",\"Output\":{\"Oscillate\":{\"Value\":[0,20]},\"PositionWithDuration\":{\"Position\":[0,100],\"Duration\":[0,2000]}},\"Input\":{\"Position\":{\"InputCommands\":[\"Subscribe\",\"Read\"],\"ValueRange\":[[0,0],[0,100]]}}},\"1\":{\"FeatureIndex\":1,\"FeatureDescription\":\"Bluetooth Radio RSSI\",\"Input\":{\"RSSI\":{\"InputCommands\":[\"Read\"],\"ValueRange\":[[-10,0],[-100,0]]}}}}}}}}]"; + + Validator.Result result = new ValidatorFactory().validate(schema, testStr); + assertTrue(result.isValid(), result.getErrors().stream().map(error -> error.getError() + " - " + error.getInstanceLocation()).collect(Collectors.joining("\n"))); ButtplugJsonMessageParser parser = new ButtplugJsonMessageParser(); List msgs = parser.parseJson(testStr); @@ -24,14 +46,51 @@ public void test() throws IOException, ButtplugProtocolException { assertEquals(5, msgs.get(0).getId()); assertEquals(2, ((DeviceList) msgs.get(0)).getDevices().size()); - //DeviceMessageInfo[] devs = ((DeviceList) msgs.get(0)).devices; - //assertEquals(2, devs[0].deviceIndex); - //assertEquals("foo", devs[0].deviceName); - //assertArrayEquals(new String[]{"foo-cmd-1", "foo-cmd-2"}, devs[0].deviceMessages); + HashMap devs = ((DeviceList) msgs.get(0)).getDevices(); + assertNotNull(devs.get(0)); + assertEquals(0, devs.get(0).getDeviceIndex()); + assertEquals("Test Vibrator", devs.get(0).getDeviceName()); + assertEquals("", devs.get(0).getDeviceDisplayName()); + assertEquals(100, devs.get(0).getDeviceMessageTimingGap()); + HashMap dev0Features = devs.get(0).getDeviceFeatures(); + assertEquals(3, dev0Features.size()); + assertEquals(0, dev0Features.get(0).getFeatureIndex()); + assertEquals("Clitoral Stimulator", dev0Features.get(0).getFeatureDescription()); + assertEquals(1, dev0Features.get(0).getOutput().size()); + assertArrayEquals(new String[]{"Vibrate"}, dev0Features.get(0).getOutput().stream().map(outputDescriptor -> outputDescriptor.getClass().getSimpleName()).toArray()); + assertNull(dev0Features.get(0).getInput()); + assertEquals(1, dev0Features.get(1).getFeatureIndex()); + assertEquals("Insertable Stimulator", dev0Features.get(1).getFeatureDescription()); + assertEquals(1, dev0Features.get(1).getOutput().size()); + assertArrayEquals(new String[]{"Vibrate"}, dev0Features.get(1).getOutput().stream().map(outputDescriptor -> outputDescriptor.getClass().getSimpleName()).toArray()); + assertNull(dev0Features.get(1).getInput()); + assertEquals(2, dev0Features.get(2).getFeatureIndex()); + assertEquals("Battery", dev0Features.get(2).getFeatureDescription()); + assertNull(dev0Features.get(2).getOutput()); + assertEquals(1, dev0Features.get(2).getInput().size()); + assertArrayEquals(new String[]{"Battery"}, dev0Features.get(2).getInput().stream().map(inputDescriptor -> inputDescriptor.getClass().getSimpleName()).toArray()); + + assertNull(devs.get(1)); + + assertNotNull(devs.get(2)); + assertEquals(2, devs.get(2).getDeviceIndex()); + assertEquals("Test Stroker", devs.get(2).getDeviceName()); + assertEquals("User set name", devs.get(2).getDeviceDisplayName()); + assertEquals(100, devs.get(2).getDeviceMessageTimingGap()); + HashMap dev2Features = devs.get(2).getDeviceFeatures(); + assertEquals(2, dev2Features.size()); + assertEquals(0, dev2Features.get(0).getFeatureIndex()); + assertEquals("Stroker", dev2Features.get(0).getFeatureDescription()); + assertEquals(2, dev2Features.get(0).getOutput().size()); + assertArrayEquals(new String[]{"Oscillate","PositionWithDuration"}, dev2Features.get(0).getOutput().stream().map(outputDescriptor -> outputDescriptor.getClass().getSimpleName()).toArray()); + assertEquals(1, dev2Features.get(0).getInput().size()); + assertEquals(1, dev2Features.get(1).getFeatureIndex()); + assertArrayEquals(new String[]{"PositionInput"}, dev2Features.get(0).getInput().stream().map(inputDescriptor -> inputDescriptor.getClass().getSimpleName()).toArray()); + assertEquals("Bluetooth Radio RSSI", dev2Features.get(1).getFeatureDescription()); + assertNull(dev2Features.get(1).getOutput()); + assertEquals(1, dev2Features.get(1).getInput().size()); + assertArrayEquals(new String[]{"Rssi"}, dev2Features.get(1).getInput().stream().map(inputDescriptor -> inputDescriptor.getClass().getSimpleName()).toArray()); - //assertEquals(4, devs[1].deviceIndex); - //assertEquals("bar", devs[1].deviceName); - //assertArrayEquals(new String[]{"bar-cmd-1", "bar-cmd-2"}, devs[1].deviceMessages); String jsonOut = parser.formatJson(msgs); assertEquals(testStr, jsonOut); diff --git a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/DeviceRemovedTest.java b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/DeviceRemovedTest.java deleted file mode 100644 index a4e3f8d..0000000 --- a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/DeviceRemovedTest.java +++ /dev/null @@ -1,34 +0,0 @@ -package io.github.blackspherefollower.buttplug4j.protocol.messages; - -import io.github.blackspherefollower.buttplug4j.protocol.ButtplugJsonMessageParser; -import io.github.blackspherefollower.buttplug4j.protocol.ButtplugMessage; -import io.github.blackspherefollower.buttplug4j.protocol.ButtplugProtocolException; -import org.junit.jupiter.api.Test; - -import java.io.IOException; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -public class DeviceRemovedTest { - - @Test - public void test() throws IOException, ButtplugProtocolException { - String testStr = "[{\"DeviceRemoved\":{\"Id\":3,\"DeviceIndex\":2}}]"; - - ButtplugJsonMessageParser parser = new ButtplugJsonMessageParser(); - List msgs = parser.parseJson(testStr); - - assertEquals(1, msgs.size()); - assertEquals(DeviceRemoved.class, msgs.get(0).getClass()); - assertEquals(3, msgs.get(0).getId()); - assertEquals(2, ((DeviceRemoved) msgs.get(0)).getDeviceIndex()); - - String jsonOut = parser.formatJson(msgs); - assertEquals(testStr, jsonOut); - - jsonOut = parser.formatJson(msgs.get(0)); - assertEquals(testStr, jsonOut); - } - -} diff --git a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/ErrorTest.java b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/ErrorTest.java index 06bacaa..3c4d501 100644 --- a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/ErrorTest.java +++ b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/ErrorTest.java @@ -1,21 +1,43 @@ package io.github.blackspherefollower.buttplug4j.protocol.messages; +import dev.harrel.jsonschema.Validator; +import dev.harrel.jsonschema.ValidatorFactory; import io.github.blackspherefollower.buttplug4j.protocol.ButtplugJsonMessageParser; import io.github.blackspherefollower.buttplug4j.protocol.ButtplugMessage; import io.github.blackspherefollower.buttplug4j.protocol.ButtplugProtocolException; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import java.io.BufferedInputStream; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; import java.util.List; +import java.util.stream.Collectors; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; public class ErrorTest { + static String schema = null; + + @BeforeAll + public static void setup() throws IOException { + BufferedInputStream in = new BufferedInputStream(new URL(TestConstants.SCHEMA_URL).openStream()); + schema = new BufferedReader(new InputStreamReader(in)) + .lines().collect(Collectors.joining("\n")); + in.close(); + } + @Test public void test() throws IOException, ButtplugProtocolException { String testStr = "[{\"Error\":{\"Id\":7,\"ErrorCode\":4,\"ErrorMessage\":\"TestError\"}}]"; + Validator.Result result = new ValidatorFactory().validate(schema, testStr); + assertTrue(result.isValid(), result.getErrors().stream().map(dev.harrel.jsonschema.Error::getError).collect(Collectors.joining("\n"))); + ButtplugJsonMessageParser parser = new ButtplugJsonMessageParser(); List msgs = parser.parseJson(testStr); diff --git a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/InputCmdTest.java b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/InputCmdTest.java new file mode 100644 index 0000000..5375670 --- /dev/null +++ b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/InputCmdTest.java @@ -0,0 +1,104 @@ +package io.github.blackspherefollower.buttplug4j.protocol.messages; + +import dev.harrel.jsonschema.Error; +import dev.harrel.jsonschema.Validator; +import dev.harrel.jsonschema.ValidatorFactory; +import io.github.blackspherefollower.buttplug4j.protocol.ButtplugJsonMessageParser; +import io.github.blackspherefollower.buttplug4j.protocol.ButtplugMessage; +import io.github.blackspherefollower.buttplug4j.protocol.ButtplugProtocolException; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; +import java.util.List; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.*; + +public class InputCmdTest { + + static String schema = null; + + @BeforeAll + public static void setup() throws IOException { + BufferedInputStream in = new BufferedInputStream(new URL(TestConstants.SCHEMA_URL).openStream()); + schema = new BufferedReader(new InputStreamReader(in)) + .lines().collect(Collectors.joining("\n")); + in.close(); + } + + @Test + public void testInputCmdSubscribe() throws IOException, ButtplugProtocolException { + String testStr = "[{\"InputCmd\":{\"Id\":1,\"DeviceIndex\":0,\"FeatureIndex\":0,\"InputType\":\"Battery\",\"InputCommand\":\"Subscribe\"}}]"; + + Validator.Result result = new ValidatorFactory().validate(schema, testStr); + assertTrue(result.isValid(), result.getErrors().stream().map(Error::getError).collect(Collectors.joining("\n"))); + + ButtplugJsonMessageParser parser = new ButtplugJsonMessageParser(); + List msgs = parser.parseJson(testStr); + + assertEquals(1, msgs.size()); + assertEquals(InputCmd.class, msgs.get(0).getClass()); + assertEquals(1, msgs.get(0).getId()); + assertEquals(0, ((InputCmd) msgs.get(0)).getDeviceIndex()); + assertEquals(0, ((InputCmd) msgs.get(0)).getFeatureIndex()); + assertEquals("Battery", ((InputCmd) msgs.get(0)).getInputType()); + assertEquals(InputCommandType.SUBSCRIBE, ((InputCmd) msgs.get(0)).getInputCommand()); + + String jsonOut = parser.formatJson(msgs); + assertEquals(testStr, jsonOut); + + jsonOut = parser.formatJson(msgs.get(0)); + assertEquals(testStr, jsonOut); + } + + @Test + public void testInputCmdUnsubscribe() throws IOException, ButtplugProtocolException { + String testStr = "[{\"InputCmd\":{\"Id\":2,\"DeviceIndex\":1,\"FeatureIndex\":1,\"InputType\":\"RSSI\",\"InputCommand\":\"Unsubscribe\"}}]"; + + Validator.Result result = new ValidatorFactory().validate(schema, testStr); + assertTrue(result.isValid(), result.getErrors().stream().map(Error::getError).collect(Collectors.joining("\n"))); + + ButtplugJsonMessageParser parser = new ButtplugJsonMessageParser(); + List msgs = parser.parseJson(testStr); + + assertEquals(1, msgs.size()); + assertEquals(InputCmd.class, msgs.get(0).getClass()); + assertEquals(2, msgs.get(0).getId()); + assertEquals(1, ((InputCmd) msgs.get(0)).getDeviceIndex()); + assertEquals(1, ((InputCmd) msgs.get(0)).getFeatureIndex()); + assertEquals("RSSI", ((InputCmd) msgs.get(0)).getInputType()); + assertEquals(InputCommandType.UNSUBSCRIBE, ((InputCmd) msgs.get(0)).getInputCommand()); + + String jsonOut = parser.formatJson(msgs); + assertEquals(testStr, jsonOut); + + jsonOut = parser.formatJson(msgs.get(0)); + assertEquals(testStr, jsonOut); + } + + @Test + public void testInputCmdRead() throws IOException, ButtplugProtocolException { + String testStr = "[{\"InputCmd\":{\"Id\":3,\"DeviceIndex\":0,\"FeatureIndex\":0,\"InputType\":\"Button\",\"InputCommand\":\"Read\"}}]"; + + Validator.Result result = new ValidatorFactory().validate(schema, testStr); + assertTrue(result.isValid(), result.getErrors().stream().map(Error::getError).collect(Collectors.joining("\n"))); + + ButtplugJsonMessageParser parser = new ButtplugJsonMessageParser(); + List msgs = parser.parseJson(testStr); + + assertEquals(1, msgs.size()); + assertEquals(InputCmd.class, msgs.get(0).getClass()); + assertEquals(InputCommandType.READ, ((InputCmd) msgs.get(0)).getInputCommand()); + + String jsonOut = parser.formatJson(msgs); + assertEquals(testStr, jsonOut); + + jsonOut = parser.formatJson(msgs.get(0)); + assertEquals(testStr, jsonOut); + } +} diff --git a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/InputReadingTest.java b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/InputReadingTest.java new file mode 100644 index 0000000..960c73c --- /dev/null +++ b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/InputReadingTest.java @@ -0,0 +1,90 @@ +package io.github.blackspherefollower.buttplug4j.protocol.messages; + +import dev.harrel.jsonschema.Validator; +import dev.harrel.jsonschema.ValidatorFactory; +import io.github.blackspherefollower.buttplug4j.protocol.ButtplugDeviceMessage; +import io.github.blackspherefollower.buttplug4j.protocol.ButtplugJsonMessageParser; +import io.github.blackspherefollower.buttplug4j.protocol.ButtplugMessage; +import io.github.blackspherefollower.buttplug4j.protocol.ButtplugProtocolException; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; +import java.util.List; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.*; + +public class InputReadingTest { + + static String schema = null; + + @BeforeAll + public static void setup() throws IOException { + BufferedInputStream in = new BufferedInputStream(new URL(TestConstants.SCHEMA_URL).openStream()); + schema = new BufferedReader(new InputStreamReader(in)) + .lines().collect(Collectors.joining("\n")); + in.close(); + } + + @Test + public void testInputReadingCreation() { + InputReading reading = new InputReading(1, 0, 0); + + assertEquals(1, reading.getId()); + assertEquals(0, reading.getDeviceIndex()); + assertNotNull(reading); + } + + @Test + public void testInputReadingInheritance() { + InputReading reading = new InputReading(1, 0, 0); + + assertInstanceOf(ButtplugDeviceMessage.class, reading); + } + + @Test + public void testInputDataInterfaces() { + // Test that InputData interface classes exist + InputReading.BatteryData batteryData = new InputReading.BatteryData(); + InputReading.RssiData rssiData = new InputReading.RssiData(); + InputReading.ButtonData buttonData = new InputReading.ButtonData(); + InputReading.PresureData presureData = new InputReading.PresureData(); + + assertInstanceOf(InputReading.BatteryData.class, batteryData); + assertInstanceOf(InputReading.RssiData.class, rssiData); + assertInstanceOf(InputReading.ButtonData.class, buttonData); + assertInstanceOf(InputReading.PresureData.class, presureData); + } + + @Test + @Disabled("See https://github.com/buttplugio/buttplug/issues/801") + public void testBatteryReading() throws ButtplugProtocolException { + String testStr = "[{\"InputReading\":{\"Id\":1,\"DeviceIndex\":0,\"FeatureIndex\":1,\"Data\":{\"Battery\":{\"Data\":100}}}}]"; + + Validator.Result result = new ValidatorFactory().validate(schema, testStr); + assertTrue(result.isValid(), result.getErrors().stream().map(error -> error.getError() + " - " + error.getInstanceLocation()).collect(Collectors.joining("\n"))); + + ButtplugJsonMessageParser parser = new ButtplugJsonMessageParser(); + List msgs = parser.parseJson(testStr); + + assertEquals(1, msgs.size()); + assertEquals(InputReading.BatteryData.class, msgs.get(0).getClass()); + assertEquals(1, msgs.get(0).getId(), 1); + assertEquals(4, ((ServerInfo) msgs.get(0)).getProtocolVersionMajor()); + assertEquals(0, ((ServerInfo) msgs.get(0)).getProtocolVersionMinor()); + assertEquals(500, ((ServerInfo) msgs.get(0)).getMaxPingTime()); + assertEquals("Websocket Server", ((ServerInfo) msgs.get(0)).getServerName()); + + String jsonOut = parser.formatJson(msgs); + assertEquals(testStr, jsonOut); + + jsonOut = parser.formatJson(msgs.get(0)); + assertEquals(testStr, jsonOut); + } +} diff --git a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/LinearCmdTest.java b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/LinearCmdTest.java deleted file mode 100644 index baaafc2..0000000 --- a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/LinearCmdTest.java +++ /dev/null @@ -1,40 +0,0 @@ -package io.github.blackspherefollower.buttplug4j.protocol.messages; - -import io.github.blackspherefollower.buttplug4j.protocol.ButtplugJsonMessageParser; -import io.github.blackspherefollower.buttplug4j.protocol.ButtplugMessage; -import io.github.blackspherefollower.buttplug4j.protocol.ButtplugProtocolException; -import org.junit.jupiter.api.Test; - -import java.io.IOException; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.*; - -class LinearCmdTest { - - @Test - public void test() throws IOException, ButtplugProtocolException { - String testStr = "[{\"LinearCmd\":{\"Id\":1,\"DeviceIndex\":0,\"Vectors\":[{\"Index\":0,\"Position\":0.3,\"Duration\":500},{\"Index\":1,\"Position\":0.8,\"Duration\":1000}]}}]"; - - ButtplugJsonMessageParser parser = new ButtplugJsonMessageParser(); - List msgs = parser.parseJson(testStr); - - assertEquals(1, msgs.size()); - assertEquals(LinearCmd.class, msgs.get(0).getClass()); - assertEquals(1, msgs.get(0).getId()); - assertEquals(0, ((LinearCmd) msgs.get(0)).getDeviceIndex()); - assertEquals(2, ((LinearCmd) msgs.get(0)).getVectors().length); - assertEquals(0, ((LinearCmd) msgs.get(0)).getVectors()[0].getIndex()); - assertEquals(500, ((LinearCmd) msgs.get(0)).getVectors()[0].getDuration()); - assertEquals(0.3, ((LinearCmd) msgs.get(0)).getVectors()[0].getPosition()); - assertEquals(1, ((LinearCmd) msgs.get(0)).getVectors()[1].getIndex()); - assertEquals(1000, ((LinearCmd) msgs.get(0)).getVectors()[1].getDuration()); - assertEquals(0.8, ((LinearCmd) msgs.get(0)).getVectors()[1].getPosition()); - - String jsonOut = parser.formatJson(msgs); - assertEquals(testStr, jsonOut); - - jsonOut = parser.formatJson(msgs.get(0)); - assertEquals(testStr, jsonOut); - } -} \ No newline at end of file diff --git a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/OkTest.java b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/OkTest.java index 6c5478c..d5d3f5d 100644 --- a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/OkTest.java +++ b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/OkTest.java @@ -1,21 +1,44 @@ package io.github.blackspherefollower.buttplug4j.protocol.messages; +import dev.harrel.jsonschema.Error; +import dev.harrel.jsonschema.Validator; +import dev.harrel.jsonschema.ValidatorFactory; import io.github.blackspherefollower.buttplug4j.protocol.ButtplugJsonMessageParser; import io.github.blackspherefollower.buttplug4j.protocol.ButtplugMessage; import io.github.blackspherefollower.buttplug4j.protocol.ButtplugProtocolException; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import java.io.BufferedInputStream; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; import java.util.List; +import java.util.stream.Collectors; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; public class OkTest { + static String schema = null; + + @BeforeAll + public static void setup() throws IOException { + BufferedInputStream in = new BufferedInputStream(new URL(TestConstants.SCHEMA_URL).openStream()); + schema = new BufferedReader(new InputStreamReader(in)) + .lines().collect(Collectors.joining("\n")); + in.close(); + } + @Test public void test() throws IOException, ButtplugProtocolException { String testStr = "[{\"Ok\":{\"Id\":3}}]"; + Validator.Result result = new ValidatorFactory().validate(schema, testStr); + assertTrue(result.isValid(), result.getErrors().stream().map(Error::getError).collect(Collectors.joining("\n"))); + ButtplugJsonMessageParser parser = new ButtplugJsonMessageParser(); List msgs = parser.parseJson(testStr); diff --git a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/OutputCmdTest.java b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/OutputCmdTest.java new file mode 100644 index 0000000..33a9fcc --- /dev/null +++ b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/OutputCmdTest.java @@ -0,0 +1,284 @@ +package io.github.blackspherefollower.buttplug4j.protocol.messages; + +import dev.harrel.jsonschema.Error; +import dev.harrel.jsonschema.Validator; +import dev.harrel.jsonschema.ValidatorFactory; +import io.github.blackspherefollower.buttplug4j.protocol.ButtplugJsonMessageParser; +import io.github.blackspherefollower.buttplug4j.protocol.ButtplugMessage; +import io.github.blackspherefollower.buttplug4j.protocol.ButtplugProtocolException; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; +import java.util.List; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.*; + +class OutputCmdTest { + + static String schema = null; + + @BeforeAll + public static void setup() throws IOException { + BufferedInputStream in = new BufferedInputStream(new URL(TestConstants.SCHEMA_URL).openStream()); + schema = new BufferedReader(new InputStreamReader(in)) + .lines().collect(Collectors.joining("\n")); + in.close(); + } + + @Test + public void testVibrate() throws IOException, ButtplugProtocolException { + String testStr = "[{\"OutputCmd\":{\"Id\":1,\"DeviceIndex\":0,\"FeatureIndex\":0,\"Command\":{\"Vibrate\":{\"Value\":5}}}}]"; + + Validator.Result result = new ValidatorFactory().validate(schema, testStr); + assertTrue(result.isValid(), result.getErrors().stream().map(Error::getError).collect(Collectors.joining("\n"))); + + ButtplugJsonMessageParser parser = new ButtplugJsonMessageParser(); + List msgs = parser.parseJson(testStr); + + assertEquals(1, msgs.size()); + assertEquals(OutputCmd.class, msgs.get(0).getClass()); + assertEquals(1, msgs.get(0).getId()); + assertEquals(0, ((OutputCmd) msgs.get(0)).getDeviceIndex()); + assertEquals(0, ((OutputCmd) msgs.get(0)).getFeatureIndex()); + assertInstanceOf(OutputCmd.Vibrate.class, ((OutputCmd) msgs.get(0)).getCommand()); + assertEquals(5, ((OutputCmd.Vibrate)((OutputCmd) msgs.get(0)).getCommand()).getValue()); + + String jsonOut = parser.formatJson(msgs); + assertEquals(testStr, jsonOut); + + jsonOut = parser.formatJson(msgs.get(0)); + assertEquals(testStr, jsonOut); + } + + @Test + public void testRotate() throws IOException, ButtplugProtocolException { + String testStr = "[{\"OutputCmd\":{\"Id\":1,\"DeviceIndex\":0,\"FeatureIndex\":0,\"Command\":{\"Rotate\":{\"Value\":5}}}}]"; + + Validator.Result result = new ValidatorFactory().validate(schema, testStr); + assertTrue(result.isValid(), result.getErrors().stream().map(Error::getError).collect(Collectors.joining("\n"))); + + ButtplugJsonMessageParser parser = new ButtplugJsonMessageParser(); + List msgs = parser.parseJson(testStr); + + assertEquals(1, msgs.size()); + assertEquals(OutputCmd.class, msgs.get(0).getClass()); + assertEquals(1, msgs.get(0).getId()); + assertEquals(0, ((OutputCmd) msgs.get(0)).getDeviceIndex()); + assertEquals(0, ((OutputCmd) msgs.get(0)).getFeatureIndex()); + assertInstanceOf(OutputCmd.Rotate.class, ((OutputCmd) msgs.get(0)).getCommand()); + assertEquals(5, ((OutputCmd.Rotate)((OutputCmd) msgs.get(0)).getCommand()).getValue()); + + String jsonOut = parser.formatJson(msgs); + assertEquals(testStr, jsonOut); + + jsonOut = parser.formatJson(msgs.get(0)); + assertEquals(testStr, jsonOut); + } + + @Test + public void testRotateBackwards() throws IOException, ButtplugProtocolException { + String testStr = "[{\"OutputCmd\":{\"Id\":1,\"DeviceIndex\":0,\"FeatureIndex\":0,\"Command\":{\"Rotate\":{\"Value\":-5}}}}]"; + + Validator.Result result = new ValidatorFactory().validate(schema, testStr); + assertTrue(result.isValid(), result.getErrors().stream().map(Error::getError).collect(Collectors.joining("\n"))); + + ButtplugJsonMessageParser parser = new ButtplugJsonMessageParser(); + List msgs = parser.parseJson(testStr); + + assertEquals(1, msgs.size()); + assertEquals(OutputCmd.class, msgs.get(0).getClass()); + assertEquals(1, msgs.get(0).getId()); + assertEquals(0, ((OutputCmd) msgs.get(0)).getDeviceIndex()); + assertEquals(0, ((OutputCmd) msgs.get(0)).getFeatureIndex()); + assertInstanceOf(OutputCmd.Rotate.class, ((OutputCmd) msgs.get(0)).getCommand()); + assertEquals(-5, ((OutputCmd.Rotate)((OutputCmd) msgs.get(0)).getCommand()).getValue()); + + String jsonOut = parser.formatJson(msgs); + assertEquals(testStr, jsonOut); + + jsonOut = parser.formatJson(msgs.get(0)); + assertEquals(testStr, jsonOut); + } + + @Test + public void testPosition() throws IOException, ButtplugProtocolException { + String testStr = "[{\"OutputCmd\":{\"Id\":1,\"DeviceIndex\":0,\"FeatureIndex\":0,\"Command\":{\"Position\":{\"Value\":5}}}}]"; + + Validator.Result result = new ValidatorFactory().validate(schema, testStr); + assertTrue(result.isValid(), result.getErrors().stream().map(Error::getError).collect(Collectors.joining("\n"))); + + ButtplugJsonMessageParser parser = new ButtplugJsonMessageParser(); + List msgs = parser.parseJson(testStr); + + assertEquals(1, msgs.size()); + assertEquals(OutputCmd.class, msgs.get(0).getClass()); + assertEquals(1, msgs.get(0).getId()); + assertEquals(0, ((OutputCmd) msgs.get(0)).getDeviceIndex()); + assertEquals(0, ((OutputCmd) msgs.get(0)).getFeatureIndex()); + assertInstanceOf(OutputCmd.Position.class, ((OutputCmd) msgs.get(0)).getCommand()); + assertEquals(5, ((OutputCmd.Position)((OutputCmd) msgs.get(0)).getCommand()).getValue()); + + String jsonOut = parser.formatJson(msgs); + assertEquals(testStr, jsonOut); + + jsonOut = parser.formatJson(msgs.get(0)); + assertEquals(testStr, jsonOut); + } + + @Test + public void testSpray() throws IOException, ButtplugProtocolException { + String testStr = "[{\"OutputCmd\":{\"Id\":1,\"DeviceIndex\":0,\"FeatureIndex\":0,\"Command\":{\"Spray\":{\"Value\":5}}}}]"; + + Validator.Result result = new ValidatorFactory().validate(schema, testStr); + assertTrue(result.isValid(), result.getErrors().stream().map(Error::getError).collect(Collectors.joining("\n"))); + + ButtplugJsonMessageParser parser = new ButtplugJsonMessageParser(); + List msgs = parser.parseJson(testStr); + + assertEquals(1, msgs.size()); + assertEquals(OutputCmd.class, msgs.get(0).getClass()); + assertEquals(1, msgs.get(0).getId()); + assertEquals(0, ((OutputCmd) msgs.get(0)).getDeviceIndex()); + assertEquals(0, ((OutputCmd) msgs.get(0)).getFeatureIndex()); + assertInstanceOf(OutputCmd.Spray.class, ((OutputCmd) msgs.get(0)).getCommand()); + assertEquals(5, ((OutputCmd.Spray)((OutputCmd) msgs.get(0)).getCommand()).getValue()); + + String jsonOut = parser.formatJson(msgs); + assertEquals(testStr, jsonOut); + + jsonOut = parser.formatJson(msgs.get(0)); + assertEquals(testStr, jsonOut); + } + + @Test + public void testConstrict() throws IOException, ButtplugProtocolException { + String testStr = "[{\"OutputCmd\":{\"Id\":1,\"DeviceIndex\":0,\"FeatureIndex\":0,\"Command\":{\"Constrict\":{\"Value\":5}}}}]"; + + Validator.Result result = new ValidatorFactory().validate(schema, testStr); + assertTrue(result.isValid(), result.getErrors().stream().map(Error::getError).collect(Collectors.joining("\n"))); + + ButtplugJsonMessageParser parser = new ButtplugJsonMessageParser(); + List msgs = parser.parseJson(testStr); + + assertEquals(1, msgs.size()); + assertEquals(OutputCmd.class, msgs.get(0).getClass()); + assertEquals(1, msgs.get(0).getId()); + assertEquals(0, ((OutputCmd) msgs.get(0)).getDeviceIndex()); + assertEquals(0, ((OutputCmd) msgs.get(0)).getFeatureIndex()); + assertInstanceOf(OutputCmd.Constrict.class, ((OutputCmd) msgs.get(0)).getCommand()); + assertEquals(5, ((OutputCmd.Constrict)((OutputCmd) msgs.get(0)).getCommand()).getValue()); + + String jsonOut = parser.formatJson(msgs); + assertEquals(testStr, jsonOut); + + jsonOut = parser.formatJson(msgs.get(0)); + assertEquals(testStr, jsonOut); + } + + @Test + public void testOscillate() throws IOException, ButtplugProtocolException { + String testStr = "[{\"OutputCmd\":{\"Id\":1,\"DeviceIndex\":0,\"FeatureIndex\":0,\"Command\":{\"Oscillate\":{\"Value\":5}}}}]"; + + Validator.Result result = new ValidatorFactory().validate(schema, testStr); + assertTrue(result.isValid(), result.getErrors().stream().map(Error::getError).collect(Collectors.joining("\n"))); + + ButtplugJsonMessageParser parser = new ButtplugJsonMessageParser(); + List msgs = parser.parseJson(testStr); + + assertEquals(1, msgs.size()); + assertEquals(OutputCmd.class, msgs.get(0).getClass()); + assertEquals(1, msgs.get(0).getId()); + assertEquals(0, ((OutputCmd) msgs.get(0)).getDeviceIndex()); + assertEquals(0, ((OutputCmd) msgs.get(0)).getFeatureIndex()); + assertInstanceOf(OutputCmd.Oscillate.class, ((OutputCmd) msgs.get(0)).getCommand()); + assertEquals(5, ((OutputCmd.Oscillate)((OutputCmd) msgs.get(0)).getCommand()).getValue()); + + String jsonOut = parser.formatJson(msgs); + assertEquals(testStr, jsonOut); + + jsonOut = parser.formatJson(msgs.get(0)); + assertEquals(testStr, jsonOut); + } + + @Test + public void testTemperature() throws IOException, ButtplugProtocolException { + String testStr = "[{\"OutputCmd\":{\"Id\":1,\"DeviceIndex\":0,\"FeatureIndex\":0,\"Command\":{\"Temperature\":{\"Value\":5}}}}]"; + + Validator.Result result = new ValidatorFactory().validate(schema, testStr); + assertTrue(result.isValid(), result.getErrors().stream().map(Error::getError).collect(Collectors.joining("\n"))); + + ButtplugJsonMessageParser parser = new ButtplugJsonMessageParser(); + List msgs = parser.parseJson(testStr); + + assertEquals(1, msgs.size()); + assertEquals(OutputCmd.class, msgs.get(0).getClass()); + assertEquals(1, msgs.get(0).getId()); + assertEquals(0, ((OutputCmd) msgs.get(0)).getDeviceIndex()); + assertEquals(0, ((OutputCmd) msgs.get(0)).getFeatureIndex()); + assertInstanceOf(OutputCmd.Temperature.class, ((OutputCmd) msgs.get(0)).getCommand()); + assertEquals(5, ((OutputCmd.Temperature)((OutputCmd) msgs.get(0)).getCommand()).getValue()); + + String jsonOut = parser.formatJson(msgs); + assertEquals(testStr, jsonOut); + + jsonOut = parser.formatJson(msgs.get(0)); + assertEquals(testStr, jsonOut); + } + + @Test + public void testLed() throws IOException, ButtplugProtocolException { + String testStr = "[{\"OutputCmd\":{\"Id\":1,\"DeviceIndex\":0,\"FeatureIndex\":0,\"Command\":{\"Led\":{\"Value\":5}}}}]"; + + Validator.Result result = new ValidatorFactory().validate(schema, testStr); + assertTrue(result.isValid(), result.getErrors().stream().map(Error::getError).collect(Collectors.joining("\n"))); + + ButtplugJsonMessageParser parser = new ButtplugJsonMessageParser(); + List msgs = parser.parseJson(testStr); + + assertEquals(1, msgs.size()); + assertEquals(OutputCmd.class, msgs.get(0).getClass()); + assertEquals(1, msgs.get(0).getId()); + assertEquals(0, ((OutputCmd) msgs.get(0)).getDeviceIndex()); + assertEquals(0, ((OutputCmd) msgs.get(0)).getFeatureIndex()); + assertInstanceOf(OutputCmd.Led.class, ((OutputCmd) msgs.get(0)).getCommand()); + assertEquals(5, ((OutputCmd.Led)((OutputCmd) msgs.get(0)).getCommand()).getValue()); + + String jsonOut = parser.formatJson(msgs); + assertEquals(testStr, jsonOut); + + jsonOut = parser.formatJson(msgs.get(0)); + assertEquals(testStr, jsonOut); + } + + @Test + public void testPositionWithDuration() throws IOException, ButtplugProtocolException { + String testStr = "[{\"OutputCmd\":{\"Id\":1,\"DeviceIndex\":0,\"FeatureIndex\":0,\"Command\":{\"PositionWithDuration\":{\"Position\":5,\"Duration\":10}}}}]"; + + Validator.Result result = new ValidatorFactory().validate(schema, testStr); + assertTrue(result.isValid(), result.getErrors().stream().map(Error::getError).collect(Collectors.joining("\n"))); + + ButtplugJsonMessageParser parser = new ButtplugJsonMessageParser(); + List msgs = parser.parseJson(testStr); + + assertEquals(1, msgs.size()); + assertEquals(OutputCmd.class, msgs.get(0).getClass()); + assertEquals(1, msgs.get(0).getId()); + assertEquals(0, ((OutputCmd) msgs.get(0)).getDeviceIndex()); + assertEquals(0, ((OutputCmd) msgs.get(0)).getFeatureIndex()); + assertInstanceOf(OutputCmd.PositionWithDuration.class, ((OutputCmd) msgs.get(0)).getCommand()); + assertEquals(5, ((OutputCmd.PositionWithDuration)((OutputCmd) msgs.get(0)).getCommand()).getPosition()); + assertEquals(10, ((OutputCmd.PositionWithDuration)((OutputCmd) msgs.get(0)).getCommand()).getDuration()); + + String jsonOut = parser.formatJson(msgs); + assertEquals(testStr, jsonOut); + + jsonOut = parser.formatJson(msgs.get(0)); + assertEquals(testStr, jsonOut); + } +} \ No newline at end of file diff --git a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/PingTest.java b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/PingTest.java index 4fa71fa..76f3426 100644 --- a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/PingTest.java +++ b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/PingTest.java @@ -1,21 +1,44 @@ package io.github.blackspherefollower.buttplug4j.protocol.messages; +import dev.harrel.jsonschema.Error; +import dev.harrel.jsonschema.Validator; +import dev.harrel.jsonschema.ValidatorFactory; import io.github.blackspherefollower.buttplug4j.protocol.ButtplugJsonMessageParser; import io.github.blackspherefollower.buttplug4j.protocol.ButtplugMessage; import io.github.blackspherefollower.buttplug4j.protocol.ButtplugProtocolException; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import java.io.BufferedInputStream; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; import java.util.List; +import java.util.stream.Collectors; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; public class PingTest { + static String schema = null; + + @BeforeAll + public static void setup() throws IOException { + BufferedInputStream in = new BufferedInputStream(new URL(TestConstants.SCHEMA_URL).openStream()); + schema = new BufferedReader(new InputStreamReader(in)) + .lines().collect(Collectors.joining("\n")); + in.close(); + } + @Test public void test() throws IOException, ButtplugProtocolException { String testStr = "[{\"Ping\":{\"Id\":4}}]"; + Validator.Result result = new ValidatorFactory().validate(schema, testStr); + assertTrue(result.isValid(), result.getErrors().stream().map(Error::getError).collect(Collectors.joining("\n"))); + ButtplugJsonMessageParser parser = new ButtplugJsonMessageParser(); List msgs = parser.parseJson(testStr); diff --git a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/RequestDeviceListTest.java b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/RequestDeviceListTest.java index bd4b351..8da67c2 100644 --- a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/RequestDeviceListTest.java +++ b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/RequestDeviceListTest.java @@ -1,21 +1,44 @@ package io.github.blackspherefollower.buttplug4j.protocol.messages; +import dev.harrel.jsonschema.Error; +import dev.harrel.jsonschema.Validator; +import dev.harrel.jsonschema.ValidatorFactory; import io.github.blackspherefollower.buttplug4j.protocol.ButtplugJsonMessageParser; import io.github.blackspherefollower.buttplug4j.protocol.ButtplugMessage; import io.github.blackspherefollower.buttplug4j.protocol.ButtplugProtocolException; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import java.io.BufferedInputStream; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; import java.util.List; +import java.util.stream.Collectors; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; public class RequestDeviceListTest { + static String schema = null; + + @BeforeAll + public static void setup() throws IOException { + BufferedInputStream in = new BufferedInputStream(new URL(TestConstants.SCHEMA_URL).openStream()); + schema = new BufferedReader(new InputStreamReader(in)) + .lines().collect(Collectors.joining("\n")); + in.close(); + } + @Test public void test() throws IOException, ButtplugProtocolException { String testStr = "[{\"RequestDeviceList\":{\"Id\":7}}]"; + Validator.Result result = new ValidatorFactory().validate(schema, testStr); + assertTrue(result.isValid(), result.getErrors().stream().map(Error::getError).collect(Collectors.joining("\n"))); + ButtplugJsonMessageParser parser = new ButtplugJsonMessageParser(); List msgs = parser.parseJson(testStr); diff --git a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/RequestServerInfoTest.java b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/RequestServerInfoTest.java index 480727d..2d4af12 100644 --- a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/RequestServerInfoTest.java +++ b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/RequestServerInfoTest.java @@ -1,20 +1,43 @@ package io.github.blackspherefollower.buttplug4j.protocol.messages; +import dev.harrel.jsonschema.Error; +import dev.harrel.jsonschema.Validator; +import dev.harrel.jsonschema.ValidatorFactory; import io.github.blackspherefollower.buttplug4j.protocol.ButtplugJsonMessageParser; import io.github.blackspherefollower.buttplug4j.protocol.ButtplugMessage; import io.github.blackspherefollower.buttplug4j.protocol.ButtplugProtocolException; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import java.io.BufferedInputStream; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; import java.util.List; +import java.util.stream.Collectors; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; public class RequestServerInfoTest { + static String schema = null; + + @BeforeAll + public static void setup() throws IOException { + BufferedInputStream in = new BufferedInputStream(new URL(TestConstants.SCHEMA_URL).openStream()); + schema = new BufferedReader(new InputStreamReader(in)) + .lines().collect(Collectors.joining("\n")); + in.close(); + } + @Test public void test() throws IOException, ButtplugProtocolException { - String testStr = "[{\"RequestServerInfo\":{\"Id\":7,\"MessageVersion\":3,\"ClientName\":\"UnitTest\"}}]"; + String testStr = "[{\"RequestServerInfo\":{\"Id\":7,\"ProtocolVersionMajor\":4,\"ProtocolVersionMinor\":0,\"ClientName\":\"UnitTest\"}}]"; + + Validator.Result result = new ValidatorFactory().validate(schema, testStr); + assertTrue(result.isValid(), result.getErrors().stream().map(Error::getError).collect(Collectors.joining("\n"))); ButtplugJsonMessageParser parser = new ButtplugJsonMessageParser(); List msgs = parser.parseJson(testStr); diff --git a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/RotateCmdTest.java b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/RotateCmdTest.java deleted file mode 100644 index ef3a39e..0000000 --- a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/RotateCmdTest.java +++ /dev/null @@ -1,40 +0,0 @@ -package io.github.blackspherefollower.buttplug4j.protocol.messages; - -import io.github.blackspherefollower.buttplug4j.protocol.ButtplugJsonMessageParser; -import io.github.blackspherefollower.buttplug4j.protocol.ButtplugMessage; -import io.github.blackspherefollower.buttplug4j.protocol.ButtplugProtocolException; -import org.junit.jupiter.api.Test; - -import java.io.IOException; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.*; - -class RotateCmdTest { - - @Test - public void test() throws IOException, ButtplugProtocolException { - String testStr = "[{\"RotateCmd\":{\"Id\":6,\"DeviceIndex\":1,\"Rotations\":[{\"Index\":0,\"Speed\":0.5,\"Clockwise\":true},{\"Index\":1,\"Speed\":1.0,\"Clockwise\":false}]}}]"; - - ButtplugJsonMessageParser parser = new ButtplugJsonMessageParser(); - List msgs = parser.parseJson(testStr); - - assertEquals(1, msgs.size()); - assertEquals(RotateCmd.class, msgs.get(0).getClass()); - assertEquals(6, msgs.get(0).getId()); - assertEquals(1, ((RotateCmd) msgs.get(0)).getDeviceIndex()); - assertEquals(2, ((RotateCmd) msgs.get(0)).getRotations().length); - assertEquals(0, ((RotateCmd) msgs.get(0)).getRotations()[0].getIndex()); - assertEquals(0.5, ((RotateCmd) msgs.get(0)).getRotations()[0].getSpeed()); - assertEquals(true, ((RotateCmd) msgs.get(0)).getRotations()[0].isClockwise()); - assertEquals(1, ((RotateCmd) msgs.get(0)).getRotations()[1].getIndex()); - assertEquals(1.0, ((RotateCmd) msgs.get(0)).getRotations()[1].getSpeed()); - assertEquals(false, ((RotateCmd) msgs.get(0)).getRotations()[1].isClockwise()); - - String jsonOut = parser.formatJson(msgs); - assertEquals(testStr, jsonOut); - - jsonOut = parser.formatJson(msgs.get(0)); - assertEquals(testStr, jsonOut); - } -} \ No newline at end of file diff --git a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/ScalarCmdTest.java b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/ScalarCmdTest.java deleted file mode 100644 index d85c048..0000000 --- a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/ScalarCmdTest.java +++ /dev/null @@ -1,40 +0,0 @@ -package io.github.blackspherefollower.buttplug4j.protocol.messages; - -import io.github.blackspherefollower.buttplug4j.protocol.ButtplugJsonMessageParser; -import io.github.blackspherefollower.buttplug4j.protocol.ButtplugMessage; -import io.github.blackspherefollower.buttplug4j.protocol.ButtplugProtocolException; -import org.junit.jupiter.api.Test; - -import java.io.IOException; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.*; - -class ScalarCmdTest { - - @Test - public void test() throws IOException, ButtplugProtocolException { - String testStr = "[{\"ScalarCmd\":{\"Id\":1,\"DeviceIndex\":0,\"Scalars\":[{\"Index\":0,\"Scalar\":0.3,\"ActuatorType\":\"Vibrate\"},{\"Index\":1,\"Scalar\":0.8,\"ActuatorType\":\"Inflate\"}]}}]"; - - ButtplugJsonMessageParser parser = new ButtplugJsonMessageParser(); - List msgs = parser.parseJson(testStr); - - assertEquals(1, msgs.size()); - assertEquals(ScalarCmd.class, msgs.get(0).getClass()); - assertEquals(1, msgs.get(0).getId()); - assertEquals(0, ((ScalarCmd) msgs.get(0)).getDeviceIndex()); - assertEquals(2, ((ScalarCmd) msgs.get(0)).getScalars().length); - assertEquals(0, ((ScalarCmd) msgs.get(0)).getScalars()[0].getIndex()); - assertEquals("Vibrate", ((ScalarCmd) msgs.get(0)).getScalars()[0].getActuatorType()); - assertEquals(0.3, ((ScalarCmd) msgs.get(0)).getScalars()[0].getScalar()); - assertEquals(1, ((ScalarCmd) msgs.get(0)).getScalars()[1].getIndex()); - assertEquals("Inflate", ((ScalarCmd) msgs.get(0)).getScalars()[1].getActuatorType()); - assertEquals(0.8, ((ScalarCmd) msgs.get(0)).getScalars()[1].getScalar()); - - String jsonOut = parser.formatJson(msgs); - assertEquals(testStr, jsonOut); - - jsonOut = parser.formatJson(msgs.get(0)); - assertEquals(testStr, jsonOut); - } -} \ No newline at end of file diff --git a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/ScanningFinishedTest.java b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/ScanningFinishedTest.java index 73a486c..90f835e 100644 --- a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/ScanningFinishedTest.java +++ b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/ScanningFinishedTest.java @@ -1,27 +1,50 @@ package io.github.blackspherefollower.buttplug4j.protocol.messages; +import dev.harrel.jsonschema.Error; +import dev.harrel.jsonschema.Validator; +import dev.harrel.jsonschema.ValidatorFactory; import io.github.blackspherefollower.buttplug4j.protocol.ButtplugJsonMessageParser; import io.github.blackspherefollower.buttplug4j.protocol.ButtplugMessage; import io.github.blackspherefollower.buttplug4j.protocol.ButtplugProtocolException; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import java.io.BufferedInputStream; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; import java.util.List; +import java.util.stream.Collectors; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; public class ScanningFinishedTest { + static String schema = null; + + @BeforeAll + public static void setup() throws IOException { + BufferedInputStream in = new BufferedInputStream(new URL(TestConstants.SCHEMA_URL).openStream()); + schema = new BufferedReader(new InputStreamReader(in)) + .lines().collect(Collectors.joining("\n")); + in.close(); + } + @Test public void test() throws IOException, ButtplugProtocolException { - String testStr = "[{\"ScanningFinished\":{\"Id\":5}}]"; + String testStr = "[{\"ScanningFinished\":{\"Id\":0}}]"; + + Validator.Result result = new ValidatorFactory().validate(schema, testStr); + assertTrue(result.isValid(), result.getErrors().stream().map(Error::getError).collect(Collectors.joining("\n"))); ButtplugJsonMessageParser parser = new ButtplugJsonMessageParser(); List msgs = parser.parseJson(testStr); assertEquals(msgs.size(), 1); assertEquals(msgs.get(0).getClass(), ScanningFinished.class); - assertEquals(msgs.get(0).getId(), 5); + assertEquals(msgs.get(0).getId(), 0); String jsonOut = parser.formatJson(msgs); assertEquals(testStr, jsonOut); diff --git a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/ServerInfoTest.java b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/ServerInfoTest.java index d9d8e7e..e390ebf 100644 --- a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/ServerInfoTest.java +++ b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/ServerInfoTest.java @@ -1,20 +1,43 @@ package io.github.blackspherefollower.buttplug4j.protocol.messages; +import dev.harrel.jsonschema.Error; +import dev.harrel.jsonschema.Validator; +import dev.harrel.jsonschema.ValidatorFactory; import io.github.blackspherefollower.buttplug4j.protocol.ButtplugJsonMessageParser; import io.github.blackspherefollower.buttplug4j.protocol.ButtplugMessage; import io.github.blackspherefollower.buttplug4j.protocol.ButtplugProtocolException; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import java.io.BufferedInputStream; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; import java.util.List; +import java.util.stream.Collectors; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; public class ServerInfoTest { + static String schema = null; + + @BeforeAll + public static void setup() throws IOException { + BufferedInputStream in = new BufferedInputStream(new URL(TestConstants.SCHEMA_URL).openStream()); + schema = new BufferedReader(new InputStreamReader(in)) + .lines().collect(Collectors.joining("\n")); + in.close(); + } + @Test public void test() throws IOException, ButtplugProtocolException { - String testStr = "[{\"ServerInfo\":{\"Id\":1,\"MessageVersion\":3,\"MaxPingTime\":500,\"ServerName\":\"Websocket Server\"}}]"; + String testStr = "[{\"ServerInfo\":{\"Id\":1,\"ProtocolVersionMajor\":4,\"ProtocolVersionMinor\":0,\"MaxPingTime\":500,\"ServerName\":\"Websocket Server\"}}]"; + + Validator.Result result = new ValidatorFactory().validate(schema, testStr); + assertTrue(result.isValid(), result.getErrors().stream().map(Error::getError).collect(Collectors.joining("\n"))); ButtplugJsonMessageParser parser = new ButtplugJsonMessageParser(); List msgs = parser.parseJson(testStr); @@ -22,7 +45,8 @@ public void test() throws IOException, ButtplugProtocolException { assertEquals(1, msgs.size()); assertEquals(ServerInfo.class, msgs.get(0).getClass()); assertEquals(1, msgs.get(0).getId(), 1); - assertEquals(3, ((ServerInfo) msgs.get(0)).getMessageVersion()); + assertEquals(4, ((ServerInfo) msgs.get(0)).getProtocolVersionMajor()); + assertEquals(0, ((ServerInfo) msgs.get(0)).getProtocolVersionMinor()); assertEquals(500, ((ServerInfo) msgs.get(0)).getMaxPingTime()); assertEquals("Websocket Server", ((ServerInfo) msgs.get(0)).getServerName()); diff --git a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/StartScanningTest.java b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/StartScanningTest.java index 67be288..58c03d9 100644 --- a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/StartScanningTest.java +++ b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/StartScanningTest.java @@ -1,21 +1,44 @@ package io.github.blackspherefollower.buttplug4j.protocol.messages; +import dev.harrel.jsonschema.Error; +import dev.harrel.jsonschema.Validator; +import dev.harrel.jsonschema.ValidatorFactory; import io.github.blackspherefollower.buttplug4j.protocol.ButtplugJsonMessageParser; import io.github.blackspherefollower.buttplug4j.protocol.ButtplugMessage; import io.github.blackspherefollower.buttplug4j.protocol.ButtplugProtocolException; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import java.io.BufferedInputStream; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; import java.util.List; +import java.util.stream.Collectors; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; public class StartScanningTest { + static String schema = null; + + @BeforeAll + public static void setup() throws IOException { + BufferedInputStream in = new BufferedInputStream(new URL(TestConstants.SCHEMA_URL).openStream()); + schema = new BufferedReader(new InputStreamReader(in)) + .lines().collect(Collectors.joining("\n")); + in.close(); + } + @Test public void test() throws IOException, ButtplugProtocolException { String testStr = "[{\"StartScanning\":{\"Id\":6}}]"; + Validator.Result result = new ValidatorFactory().validate(schema, testStr); + assertTrue(result.isValid(), result.getErrors().stream().map(Error::getError).collect(Collectors.joining("\n"))); + ButtplugJsonMessageParser parser = new ButtplugJsonMessageParser(); List msgs = parser.parseJson(testStr); diff --git a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/StopAllDevicesTest.java b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/StopAllDevicesTest.java index 5bf1804..5cfd7f6 100644 --- a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/StopAllDevicesTest.java +++ b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/StopAllDevicesTest.java @@ -1,21 +1,44 @@ package io.github.blackspherefollower.buttplug4j.protocol.messages; +import dev.harrel.jsonschema.Error; +import dev.harrel.jsonschema.Validator; +import dev.harrel.jsonschema.ValidatorFactory; import io.github.blackspherefollower.buttplug4j.protocol.ButtplugJsonMessageParser; import io.github.blackspherefollower.buttplug4j.protocol.ButtplugMessage; import io.github.blackspherefollower.buttplug4j.protocol.ButtplugProtocolException; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import java.io.BufferedInputStream; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; import java.util.List; +import java.util.stream.Collectors; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; public class StopAllDevicesTest { + static String schema = null; + + @BeforeAll + public static void setup() throws IOException { + BufferedInputStream in = new BufferedInputStream(new URL(TestConstants.SCHEMA_URL).openStream()); + schema = new BufferedReader(new InputStreamReader(in)) + .lines().collect(Collectors.joining("\n")); + in.close(); + } + @Test public void test() throws IOException, ButtplugProtocolException { String testStr = "[{\"StopAllDevices\":{\"Id\":7}}]"; + Validator.Result result = new ValidatorFactory().validate(schema, testStr); + assertTrue(result.isValid(), result.getErrors().stream().map(Error::getError).collect(Collectors.joining("\n"))); + ButtplugJsonMessageParser parser = new ButtplugJsonMessageParser(); List msgs = parser.parseJson(testStr); diff --git a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/StopDeviceCmdTest.java b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/StopDeviceCmdTest.java index 8e9dd30..e195ec0 100644 --- a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/StopDeviceCmdTest.java +++ b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/StopDeviceCmdTest.java @@ -1,21 +1,44 @@ package io.github.blackspherefollower.buttplug4j.protocol.messages; +import dev.harrel.jsonschema.Error; +import dev.harrel.jsonschema.Validator; +import dev.harrel.jsonschema.ValidatorFactory; import io.github.blackspherefollower.buttplug4j.protocol.ButtplugJsonMessageParser; import io.github.blackspherefollower.buttplug4j.protocol.ButtplugMessage; import io.github.blackspherefollower.buttplug4j.protocol.ButtplugProtocolException; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import java.io.BufferedInputStream; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; import java.util.List; +import java.util.stream.Collectors; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; public class StopDeviceCmdTest { + static String schema = null; + + @BeforeAll + public static void setup() throws IOException { + BufferedInputStream in = new BufferedInputStream(new URL(TestConstants.SCHEMA_URL).openStream()); + schema = new BufferedReader(new InputStreamReader(in)) + .lines().collect(Collectors.joining("\n")); + in.close(); + } + @Test public void test() throws IOException, ButtplugProtocolException { String testStr = "[{\"StopDeviceCmd\":{\"Id\":7,\"DeviceIndex\":3}}]"; + Validator.Result result = new ValidatorFactory().validate(schema, testStr); + assertTrue(result.isValid(), result.getErrors().stream().map(Error::getError).collect(Collectors.joining("\n"))); + ButtplugJsonMessageParser parser = new ButtplugJsonMessageParser(); List msgs = parser.parseJson(testStr); diff --git a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/StopScanningTest.java b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/StopScanningTest.java index 4297ace..f54b67e 100644 --- a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/StopScanningTest.java +++ b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/StopScanningTest.java @@ -1,21 +1,44 @@ package io.github.blackspherefollower.buttplug4j.protocol.messages; +import dev.harrel.jsonschema.Error; +import dev.harrel.jsonschema.Validator; +import dev.harrel.jsonschema.ValidatorFactory; import io.github.blackspherefollower.buttplug4j.protocol.ButtplugJsonMessageParser; import io.github.blackspherefollower.buttplug4j.protocol.ButtplugMessage; import io.github.blackspherefollower.buttplug4j.protocol.ButtplugProtocolException; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import java.io.BufferedInputStream; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; import java.util.List; +import java.util.stream.Collectors; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; public class StopScanningTest { + static String schema = null; + + @BeforeAll + public static void setup() throws IOException { + BufferedInputStream in = new BufferedInputStream(new URL(TestConstants.SCHEMA_URL).openStream()); + schema = new BufferedReader(new InputStreamReader(in)) + .lines().collect(Collectors.joining("\n")); + in.close(); + } + @Test public void test() throws IOException, ButtplugProtocolException { String testStr = "[{\"StopScanning\":{\"Id\":7}}]"; + Validator.Result result = new ValidatorFactory().validate(schema, testStr); + assertTrue(result.isValid(), result.getErrors().stream().map(Error::getError).collect(Collectors.joining("\n"))); + ButtplugJsonMessageParser parser = new ButtplugJsonMessageParser(); List msgs = parser.parseJson(testStr); diff --git a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/TestConstants.java b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/TestConstants.java new file mode 100644 index 0000000..1cee321 --- /dev/null +++ b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/protocol/messages/TestConstants.java @@ -0,0 +1,5 @@ +package io.github.blackspherefollower.buttplug4j.protocol.messages; + +public class TestConstants { + static public final String SCHEMA_URL = "https://raw.githubusercontent.com/buttplugio/buttplug/refs/heads/dev/crates/buttplug_core/schema/buttplug-schema.json"; +} diff --git a/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/util/PairTest.java b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/util/PairTest.java new file mode 100644 index 0000000..9096083 --- /dev/null +++ b/buttplug4j/src/test/java/io/github/blackspherefollower/buttplug4j/util/PairTest.java @@ -0,0 +1,70 @@ +package io.github.blackspherefollower.buttplug4j.util; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +public class PairTest { + + @Test + public void testPairCreation() { + Pair pair = new Pair<>("test", 42); + + assertEquals("test", pair.getLeft()); + assertEquals(42, pair.getRight()); + } + + @Test + public void testPairWithNullValues() { + Pair pair = new Pair<>(null, null); + + assertNull(pair.getLeft()); + assertNull(pair.getRight()); + } + + @Test + public void testPairSetters() { + Pair pair = new Pair<>("initial", 1); + + pair.setLeft("updated"); + pair.setRight(2); + + assertEquals("updated", pair.getLeft()); + assertEquals(2, pair.getRight()); + } + + @Test + public void testPairEquality() { + Pair pair1 = new Pair<>("test", 42); + Pair pair2 = new Pair<>("test", 42); + Pair pair3 = new Pair<>("different", 42); + + assertEquals(pair1, pair2); + assertNotEquals(pair1, pair3); + } + + @Test + public void testPairHashCode() { + Pair pair1 = new Pair<>("test", 42); + Pair pair2 = new Pair<>("test", 42); + + assertEquals(pair1.hashCode(), pair2.hashCode()); + } + + @Test + public void testPairToString() { + Pair pair = new Pair<>("test", 42); + String str = pair.toString(); + + assertNotNull(str); + assertTrue(str.contains("test")); + assertTrue(str.contains("42")); + } + + @Test + public void testPairWithDifferentTypes() { + Pair pair = new Pair<>(3.14, true); + + assertEquals(3.14, pair.getLeft()); + assertEquals(true, pair.getRight()); + } +} diff --git a/settings.gradle b/settings.gradle index d8ce59b..df11ecc 100644 --- a/settings.gradle +++ b/settings.gradle @@ -3,4 +3,5 @@ include 'buttplug4j.connectors.javax.websocket.common' include 'buttplug4j.connectors.javax.websocket.client' include 'buttplug4j.connectors.javax.websocket.server' include 'buttplug4j.connectors.jetty.websocket.client' -include 'buttplug4j.utils.mdns' \ No newline at end of file +include 'buttplug4j.utils.mdns' +include 'buttplug4j.utils.test' \ No newline at end of file