Bläddra i källkod

Merge branch 'develop'

Klaas, Wilfried 4 år sedan
förälder
incheckning
4e6bc08036
100 ändrade filer med 7522 tillägg och 40 borttagningar
  1. 1 8
      .gitignore
  2. 41 0
      SPSEmulator-client/.classpath
  3. 36 0
      SPSEmulator-client/.project
  4. 156 0
      SPSEmulator-client/pom.xml
  5. 75 0
      SPSEmulator-client/src/main/java/de/mcs/tools/sps/emulator/client/SPSEmulatorClient.java
  6. 8 0
      SPSEmulator-client/src/main/java/de/mcs/tools/sps/emulator/client/package-info.java
  7. 98 0
      SPSEmulator-client/src/test/java/de/mcs/tools/sps/emulator/TestRESTEndpoints.java
  8. 37 0
      SPSEmulator-client/src/test/resources/test.tps
  9. 33 0
      SPSEmulator-gui/.classpath
  10. 36 0
      SPSEmulator-gui/.project
  11. 36 0
      SPSEmulator-gui/README.md
  12. 143 0
      SPSEmulator-gui/pom.xml
  13. BIN
      SPSEmulator-gui/spsemulatorgui.zip
  14. 86 0
      SPSEmulator-gui/src/main/java/de/mcs/tools/sps/backend/Category.java
  15. 206 0
      SPSEmulator-gui/src/main/java/de/mcs/tools/sps/backend/CategoryService.java
  16. 184 0
      SPSEmulator-gui/src/main/java/de/mcs/tools/sps/backend/Review.java
  17. 168 0
      SPSEmulator-gui/src/main/java/de/mcs/tools/sps/backend/ReviewService.java
  18. 111 0
      SPSEmulator-gui/src/main/java/de/mcs/tools/sps/backend/StaticData.java
  19. 73 0
      SPSEmulator-gui/src/main/java/de/mcs/tools/sps/ui/MainLayout.java
  20. 252 0
      SPSEmulator-gui/src/main/java/de/mcs/tools/sps/ui/common/AbstractEditorDialog.java
  21. 128 0
      SPSEmulator-gui/src/main/java/de/mcs/tools/sps/ui/common/ConfirmationDialog.java
  22. 29 0
      SPSEmulator-gui/src/main/java/de/mcs/tools/sps/ui/encoders/LocalDateToStringEncoder.java
  23. 41 0
      SPSEmulator-gui/src/main/java/de/mcs/tools/sps/ui/encoders/LongToStringEncoder.java
  24. 90 0
      SPSEmulator-gui/src/main/java/de/mcs/tools/sps/ui/views/assemblerview/AssemblerView.java
  25. 8 0
      SPSEmulator-gui/src/main/java/de/mcs/tools/sps/ui/views/assemblerview/package-info.java
  26. 106 0
      SPSEmulator-gui/src/main/java/de/mcs/tools/sps/ui/views/emulatorview/EmulatorView.java
  27. 168 0
      SPSEmulator-gui/src/main/java/de/mcs/tools/sps/ui/views/emulatorview/EmulatorView2.java
  28. 141 0
      SPSEmulator-gui/src/main/java/de/mcs/tools/sps/ui/views/reviewslist/ReviewEditorDialog.java
  29. 129 0
      SPSEmulator-gui/src/main/java/de/mcs/tools/sps/ui/views/reviewslist/ReviewsList.java
  30. 6 0
      SPSEmulator-gui/src/main/resources/banner.txt
  31. 270 0
      SPSEmulator-gui/src/main/webapp/frontend/src/views/emulatorview/emulator-view.html
  32. 280 0
      SPSEmulator-gui/src/main/webapp/frontend/src/views/reviewslist/reviews-list.html
  33. 206 0
      SPSEmulator-gui/src/main/webapp/frontend/styles/shared-styles.html
  34. BIN
      SPSEmulator-gui/src/main/webapp/icons/icon.png
  35. 1 0
      SPSEmulator-model/.classpath
  36. 36 0
      SPSEmulator-model/.project
  37. 0 0
      SPSEmulator-model/examples/Blink.tps
  38. 155 0
      SPSEmulator-model/pom.xml
  39. 86 0
      SPSEmulator-model/src/main/java/de/mcs/tools/sps/emulator/model/CommandModel.java
  40. 8 4
      SPSEmulator-model/src/main/java/de/mcs/tools/sps/emulator/model/Hardware.java
  41. 176 0
      SPSEmulator-model/src/main/java/de/mcs/tools/sps/emulator/model/InputModel.java
  42. 160 0
      SPSEmulator-model/src/main/java/de/mcs/tools/sps/emulator/model/OutputModel.java
  43. 158 0
      SPSEmulator-model/src/main/java/de/mcs/tools/sps/emulator/model/ProgramModel.java
  44. 174 0
      SPSEmulator-model/src/main/java/de/mcs/tools/sps/emulator/model/WebSessionModel.java
  45. 201 0
      SPSEmulator-model/src/main/java/de/mcs/tools/sps/emulator/model/WorkingModel.java
  46. 54 0
      SPSEmulator-model/src/main/java/de/mcs/utils/JacksonUtils.java
  47. 8 0
      SPSEmulator-model/src/main/java/de/mcs/utils/package-info.java
  48. 70 0
      SPSEmulator-model/src/test/java/de/mcs/tools/sps/emulator/model/testWebSessionModel.java
  49. 37 0
      SPSEmulator-model/src/test/resources/test.tps
  50. 40 0
      SPSEmulator-service/.classpath
  51. 0 0
      SPSEmulator-service/.project
  52. 0 0
      SPSEmulator-service/LICENSE
  53. 108 0
      SPSEmulator-service/Pop.hex
  54. 108 0
      SPSEmulator-service/Rock.hex
  55. 0 0
      SPSEmulator-service/SimpleServo.tps
  56. 108 0
      SPSEmulator-service/Soul.hex
  57. 34 0
      SPSEmulator-service/examples/Blink.hex
  58. 37 0
      SPSEmulator-service/examples/Blink.tps
  59. 263 0
      SPSEmulator-service/examples/Blink.txt
  60. 0 0
      SPSEmulator-service/examples/Blink2.tps
  61. 0 0
      SPSEmulator-service/examples/BlinkKomment.tps
  62. 0 0
      SPSEmulator-service/examples/Blink_LOOP.tps
  63. 0 0
      SPSEmulator-service/examples/includes/macro_blink.tps
  64. 1 0
      SPSEmulator-service/log/.gitignore
  65. BIN
      SPSEmulator-service/mcs.keystore
  66. 47 16
      SPSEmulator-service/pom.xml
  67. 216 0
      SPSEmulator-service/src/main/java/de/mcs/tools/midicontroller/ConvertJsonData2Hex.java
  68. 0 0
      SPSEmulator-service/src/main/java/de/mcs/tools/midicontroller/data/ButtonData.java
  69. 0 0
      SPSEmulator-service/src/main/java/de/mcs/tools/midicontroller/data/DataData.java
  70. 0 0
      SPSEmulator-service/src/main/java/de/mcs/tools/midicontroller/data/ProgramData.java
  71. 0 0
      SPSEmulator-service/src/main/java/de/mcs/tools/midicontroller/data/Programs.java
  72. 0 0
      SPSEmulator-service/src/main/java/de/mcs/tools/midicontroller/data/SequenceData.java
  73. 0 0
      SPSEmulator-service/src/main/java/de/mcs/tools/sps/HEXTextOutputter.java
  74. 0 0
      SPSEmulator-service/src/main/java/de/mcs/tools/sps/IntelHEXOutputter.java
  75. 0 0
      SPSEmulator-service/src/main/java/de/mcs/tools/sps/Macro.java
  76. 0 0
      SPSEmulator-service/src/main/java/de/mcs/tools/sps/Outputter.java
  77. 45 12
      SPSEmulator-service/src/main/java/de/mcs/tools/sps/SPSAssembler.java
  78. 0 0
      SPSEmulator-service/src/main/java/de/mcs/tools/sps/TPSTextOutputter.java
  79. 0 0
      SPSEmulator-service/src/main/java/de/mcs/tools/sps/annotations/SPSOutputter.java
  80. 0 0
      SPSEmulator-service/src/main/java/de/mcs/tools/sps/annotations/package-info.java
  81. 221 0
      SPSEmulator-service/src/main/java/de/mcs/tools/sps/emulator/AbstractEmulator.java
  82. 12 0
      SPSEmulator-service/src/main/java/de/mcs/tools/sps/emulator/BaseHealthCheck.java
  83. 42 0
      SPSEmulator-service/src/main/java/de/mcs/tools/sps/emulator/Emulator.java
  84. 47 0
      SPSEmulator-service/src/main/java/de/mcs/tools/sps/emulator/EmulatorFactory.java
  85. 45 0
      SPSEmulator-service/src/main/java/de/mcs/tools/sps/emulator/WebEmulatorApplication.java
  86. 55 0
      SPSEmulator-service/src/main/java/de/mcs/tools/sps/emulator/WebEmulatorConfiguration.java
  87. 0 0
      SPSEmulator-service/src/main/java/de/mcs/tools/sps/emulator/exceptions/WrongProgramSizeException.java
  88. 0 0
      SPSEmulator-service/src/main/java/de/mcs/tools/sps/emulator/exceptions/package-info.java
  89. 313 0
      SPSEmulator-service/src/main/java/de/mcs/tools/sps/emulator/holtek/HoltekEmulator.java
  90. 86 0
      SPSEmulator-service/src/main/java/de/mcs/tools/sps/emulator/model/CommandModel.java
  91. 30 0
      SPSEmulator-service/src/main/java/de/mcs/tools/sps/emulator/model/Hardware.java
  92. 176 0
      SPSEmulator-service/src/main/java/de/mcs/tools/sps/emulator/model/InputModel.java
  93. 160 0
      SPSEmulator-service/src/main/java/de/mcs/tools/sps/emulator/model/OutputModel.java
  94. 158 0
      SPSEmulator-service/src/main/java/de/mcs/tools/sps/emulator/model/ProgramModel.java
  95. 174 0
      SPSEmulator-service/src/main/java/de/mcs/tools/sps/emulator/model/WebSessionModel.java
  96. 201 0
      SPSEmulator-service/src/main/java/de/mcs/tools/sps/emulator/model/WorkingModel.java
  97. 0 0
      SPSEmulator-service/src/main/java/de/mcs/tools/sps/emulator/package-info.java
  98. 89 0
      SPSEmulator-service/src/main/java/de/mcs/tools/sps/emulator/resources/EmulatorResource.java
  99. 0 0
      SPSEmulator-service/src/main/java/de/mcs/tools/sps/exceptions/HardwareException.java
  100. 0 0
      SPSEmulator-service/src/main/java/de/mcs/tools/sps/exceptions/IllegalArgument.java

+ 1 - 8
.gitignore

@@ -1,11 +1,4 @@
-/target/
-
-SimpleServo\.hex
-
-SimpleServo\.txt
-
-*.hex
-
+target
 dependency-reduced-pom\.xml
 
 \.settings/

+ 41 - 0
SPSEmulator-client/.classpath

@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" output="target/classes" path="src/main/java">
+		<attributes>
+			<attribute name="optional" value="true"/>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
+		<attributes>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="src" output="target/test-classes" path="src/test/java">
+		<attributes>
+			<attribute name="test" value="true"/>
+			<attribute name="optional" value="true"/>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources">
+		<attributes>
+			<attribute name="test" value="true"/>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
+		<attributes>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
+		<attributes>
+			<attribute name="maven.pomderived" value="true"/>
+			<attribute name="org.eclipse.jst.component.nondependency" value=""/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/5"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/SPSEmulator-model"/>
+	<classpathentry kind="output" path="target/classes"/>
+</classpath>

+ 36 - 0
SPSEmulator-client/.project

@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>SPSEmulator-client</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.wst.common.project.facet.core.builder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.wst.validation.validationbuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.m2e.core.maven2Builder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jem.workbench.JavaEMFNature</nature>
+		<nature>org.eclipse.wst.common.modulecore.ModuleCoreNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+		<nature>org.eclipse.m2e.core.maven2Nature</nature>
+		<nature>org.eclipse.wst.common.project.facet.core.nature</nature>
+	</natures>
+</projectDescription>

+ 156 - 0
SPSEmulator-client/pom.xml

@@ -0,0 +1,156 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<groupId>de.mcs.tools.sps</groupId>
+	<artifactId>SPSEmulator-client</artifactId>
+	<version>0.0.1-SNAPSHOT</version>
+	<description>SPSEmulator-client</description>
+	<name>${project.groupId}:${project.artifactId}</name>
+	<url>http://www.wk-music.de</url>
+	<licenses>
+		<license>
+			<name>The Apache License, Version 2.0</name>
+			<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+		</license>
+	</licenses>
+
+	<developers>
+		<developer>
+			<name>Wilfried Klaas</name>
+			<email>w.klaas@gmx.de</email>
+			<organization>MCS</organization>
+			<organizationUrl>http://www.wk-music.de</organizationUrl>
+		</developer>
+	</developers>
+
+	<properties>
+		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+		<timestamp>${maven.build.timestamp}</timestamp>
+		<maven.build.timestamp.format>dd.mm.yyyy HH:mm</maven.build.timestamp.format>
+		<jackson.version>2.9.6</jackson.version>
+		<jersey.client.version>2.28</jersey.client.version>
+	</properties>
+	<build>
+		<plugins>
+			<plugin>
+				<artifactId>maven-compiler-plugin</artifactId>
+				<version>3.3</version>
+				<configuration>
+					<source>1.8</source>
+					<target>1.8</target>
+				</configuration>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-surefire-plugin</artifactId>
+				<version>2.19.1</version>
+				<dependencies>
+					<dependency>
+						<groupId>org.junit.platform</groupId>
+						<artifactId>junit-platform-surefire-provider</artifactId>
+						<version>1.1.0</version>
+					</dependency>
+					<dependency>
+						<groupId>org.junit.jupiter</groupId>
+						<artifactId>junit-jupiter-engine</artifactId>
+						<version>5.1.0</version>
+					</dependency>
+				</dependencies>
+				<configuration>
+					<skipTests>true</skipTests>
+				</configuration>
+			</plugin>
+<!-- 
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-jar-plugin</artifactId>
+				<version>2.6</version>
+				<configuration>
+					<archive>
+						<manifest>
+							<mainClass>de.mcs.tools.sps.SPSAssembler</mainClass>
+							<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
+							<addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
+						</manifest>
+						<manifestEntries>
+							<Build-Time>${maven.build.timestamp}</Build-Time>
+							<Application-Name>SPSTools</Application-Name>
+							<Application-Update-Id>72</Application-Update-Id>
+							<Application-Update-Url>http\://wkla.no-ip.biz/downloader/version.php?ID\=73</Application-Update-Url>
+							<Application-Url>http\://wkla.no-ip.biz/</Application-Url>
+							<Implementation-Vendor>MCS, Media Computer Software</Implementation-Vendor>
+						</manifestEntries>
+					</archive>
+				</configuration>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-shade-plugin</artifactId>
+				<version>2.4.1</version>
+				<executions>
+					<execution>
+						<phase>package</phase>
+						<goals>
+							<goal>shade</goal>
+						</goals>
+						<configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> 
+							<mainClass></mainClass> </transformer> </transformers> </configuration>
+					</execution>
+				</executions>
+			</plugin>
+ -->			<!-- <plugin> <groupId>com.akathist.maven.plugins.launch4j</groupId> <artifactId>launch4j-maven-plugin</artifactId> <version>1.7.24</version> 
+				<executions> <execution> <id>l4j-clui</id> <phase>package</phase> <goals> <goal>launch4j</goal> </goals> <configuration> 
+				<dontWrapJar>false</dontWrapJar> <headerType>console</headerType> <jar>${project.build.directory}/${project.artifactId}-${project.version}.jar</jar> 
+				<outfile>${project.build.directory}/MCSSPSTools.exe</outfile> <downloadUrl>http://java.com/download</downloadUrl> <classPath> 
+				<mainClass>com.howtodoinjava.ApplicationMain</mainClass> <preCp>anything</preCp> </classPath> <icon>src\main\resources\MSA.ico</icon> 
+				<jre> <path>/jre</path> <minVersion>1.8.0</minVersion> <jdkPreference>jreOnly</jdkPreference> </jre> <versionInfo> <fileVersion>1.0.0.0</fileVersion> 
+				<txtFileVersion>${project.version}</txtFileVersion> <fileDescription>${project.name}</fileDescription> <copyright>2018 MCS</copyright> 
+				<productVersion>1.0.0.0</productVersion> <txtProductVersion>1.0.0.0</txtProductVersion> <productName>${project.name}</productName> 
+				<companyName>MCS</companyName> <internalName>MCSSPSTools</internalName> <originalFilename>MCSSPSTools.exe</originalFilename> 
+				</versionInfo> </configuration> </execution> </executions> </plugin> -->
+		</plugins>
+	</build>
+	<dependencies>
+		<dependency>
+			<groupId>net.sourceforge.jmeasurement2</groupId>
+			<artifactId>MCSUtils</artifactId>
+			<version>1.0.152</version>
+		</dependency>
+		<dependency>
+			<groupId>org.junit.jupiter</groupId>
+			<artifactId>junit-jupiter-api</artifactId>
+			<version>5.1.0</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>net.sourceforge.jmeasurement2</groupId>
+			<artifactId>JMeasurement</artifactId>
+			<version>1.1.225</version>
+		</dependency>
+		<dependency>
+			<groupId>org.glassfish.jersey.core</groupId>
+			<artifactId>jersey-client</artifactId>
+			<version>${jersey.client.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.glassfish.jersey.media</groupId>
+			<artifactId>jersey-media-json-jackson</artifactId>
+			<version>${jersey.client.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.glassfish.jersey.inject</groupId>
+			<artifactId>jersey-hk2</artifactId>
+			<version>${jersey.client.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>de.mcs.tools.sps</groupId>
+			<artifactId>SPSEmulator-model</artifactId>
+			<version>0.0.1-SNAPSHOT</version>
+		</dependency>
+		<dependency>
+			<groupId>commons-io</groupId>
+			<artifactId>commons-io</artifactId>
+			<version>2.4</version>
+		</dependency>
+	</dependencies>
+</project>

+ 75 - 0
SPSEmulator-client/src/main/java/de/mcs/tools/sps/emulator/client/SPSEmulatorClient.java

@@ -0,0 +1,75 @@
+/**
+ * 
+ */
+package de.mcs.tools.sps.emulator.client;
+
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.Invocation.Builder;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.MediaType;
+
+import de.mcs.tools.sps.emulator.model.WebSessionModel;
+
+/**
+ * @author w.klaas
+ *
+ */
+public class SPSEmulatorClient {
+
+  private Builder invocationBuilder;
+
+  /**
+   * @throws NoSuchAlgorithmException
+   * @throws KeyManagementException
+   * 
+   */
+  public SPSEmulatorClient(String url) throws NoSuchAlgorithmException, KeyManagementException {
+    TrustManager[] noopTrustManager = new TrustManager[] { new X509TrustManager() {
+
+      @Override
+      public X509Certificate[] getAcceptedIssuers() {
+        return null;
+      }
+
+      @Override
+      public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {
+      }
+
+      @Override
+      public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {
+      }
+    } };
+
+    javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier(new javax.net.ssl.HostnameVerifier() {
+
+      public boolean verify(String hostname, javax.net.ssl.SSLSession sslSession) {
+        return true;
+      }
+    });
+
+    SSLContext sc = SSLContext.getInstance("ssl");
+    sc.init(null, noopTrustManager, null);
+
+    Client client = ClientBuilder.newBuilder().sslContext(sc).build();
+    WebTarget webTarget = client.target(url);
+    invocationBuilder = webTarget.request(MediaType.APPLICATION_JSON);
+  }
+
+  public WebSessionModel getNewWebsession() {
+    return invocationBuilder.get(WebSessionModel.class);
+  }
+
+  public WebSessionModel processWebSession(WebSessionModel webSessionModel) {
+    return invocationBuilder.post(Entity.entity(webSessionModel, MediaType.APPLICATION_JSON), WebSessionModel.class);
+  }
+
+}

+ 8 - 0
SPSEmulator-client/src/main/java/de/mcs/tools/sps/emulator/client/package-info.java

@@ -0,0 +1,8 @@
+/**
+ * 
+ */
+/**
+ * @author w.klaas
+ *
+ */
+package de.mcs.tools.sps.emulator.client;

+ 98 - 0
SPSEmulator-client/src/test/java/de/mcs/tools/sps/emulator/TestRESTEndpoints.java

@@ -0,0 +1,98 @@
+/**
+ * MCS Media Computer Software
+ * Copyright 2019 by Wilfried Klaas
+ * Project: SPSEmulator
+ * File: TestRESTEndpoints.java
+ * EMail: W.Klaas@gmx.de
+ * Created: 03.02.2019 wklaa_000
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+package de.mcs.tools.sps.emulator;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.io.IOUtils;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import de.mcs.tools.sps.emulator.client.SPSEmulatorClient;
+import de.mcs.tools.sps.emulator.model.CommandModel.COMMAND;
+import de.mcs.tools.sps.emulator.model.WebSessionModel;
+
+/**
+ * @author wklaa_000
+ *
+ */
+class TestRESTEndpoints {
+
+  private static SPSEmulatorClient client;
+
+  @BeforeAll
+  public static void init() throws KeyManagementException, NoSuchAlgorithmException {
+    client = new SPSEmulatorClient("https://127.0.0.1:8443/emulator");
+  }
+
+  /**
+   * @throws java.lang.Exception
+   */
+  @BeforeEach
+  void setUp() throws Exception {
+  }
+
+  @Test
+  void test() throws IOException {
+
+    WebSessionModel webSessionModel = client.getNewWebsession();
+    assertNotNull(webSessionModel);
+    assertFalse(webSessionModel.isError());
+    System.out.println(webSessionModel.toString());
+
+    try (InputStream inputStream = ClassLoader.getSystemResourceAsStream("test.tps")) {
+      List<String> readLines = IOUtils.readLines(inputStream, "UTF-8");
+      assertNotNull(readLines);
+      webSessionModel.getProgram().setSource(readLines.toArray(new String[0]));
+    }
+
+    Set<COMMAND> availableCommands = webSessionModel.getCommand().getAvailableCommands();
+    assertNotNull(availableCommands);
+    assertTrue(availableCommands.contains(COMMAND.COMPILE));
+    webSessionModel.getCommand().setActualCommand(COMMAND.COMPILE);
+
+    webSessionModel = client.processWebSession(webSessionModel);
+    assertNotNull(webSessionModel);
+    assertFalse(webSessionModel.isError());
+    assertTrue(webSessionModel.getProgram().getBin().length > 10);
+    System.out.println(webSessionModel.toString());
+
+    assertTrue(availableCommands.contains(COMMAND.START));
+    webSessionModel.getCommand().setActualCommand(COMMAND.START);
+    webSessionModel = client.processWebSession(webSessionModel);
+    assertNotNull(webSessionModel);
+    assertFalse(webSessionModel.isError());
+    assertFalse(webSessionModel.getProgram().isModified());
+    System.out.println(webSessionModel.toString());
+  }
+
+}

+ 37 - 0
SPSEmulator-client/src/test/resources/test.tps

@@ -0,0 +1,37 @@
+.macro blink
+PORT #0B0101
+WAIT 200ms
+PORT #0B1010
+WAIT 200ms
+.endmacro
+
+:loop
+.blink
+RJMP :loop
+/* 
+Kommentar über mehrere Zeilen
+*/
+
+.macro macro1 output time
+PORT output
+WAIT time
+PORT #0x00
+WAIT time
+.endmacro
+
+;.include macro_blink
+:loop1
+.macro1 #0x0f 200ms
+
+PORT #0x0F ;Zeilenkommentar
+WAIT 200ms
+PORT #0x00
+WAIT 200ms
+RJMP :loop1
+
+;DFSB 1
+PORT #0x0F ;Zeilenkommentar
+WAIT 200ms
+PORT #0x00
+WAIT 200ms
+RTR

+ 33 - 0
SPSEmulator-gui/.classpath

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" output="target/classes" path="src/main/java">
+		<attributes>
+			<attribute name="optional" value="true"/>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
+		<attributes>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="src" output="target/test-classes" path="src/test/java">
+		<attributes>
+			<attribute name="test" value="true"/>
+			<attribute name="optional" value="true"/>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
+		<attributes>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
+		<attributes>
+			<attribute name="maven.pomderived" value="true"/>
+			<attribute name="org.eclipse.jst.component.dependency" value="/WEB-INF/lib"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="output" path="target/classes"/>
+</classpath>

+ 36 - 0
SPSEmulator-gui/.project

@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>SPSEmulator-gui</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.wst.common.project.facet.core.builder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.wst.validation.validationbuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.m2e.core.maven2Builder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jem.workbench.JavaEMFNature</nature>
+		<nature>org.eclipse.wst.common.modulecore.ModuleCoreNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+		<nature>org.eclipse.m2e.core.maven2Nature</nature>
+		<nature>org.eclipse.wst.common.project.facet.core.nature</nature>
+	</natures>
+</projectDescription>

+ 36 - 0
SPSEmulator-gui/README.md

@@ -0,0 +1,36 @@
+[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/vaadin-flow/Lobby#?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
+
+# Beverage Buddy App Starter for Vaadin Flow
+:coffee::tea::sake::baby_bottle::beer::cocktail::tropical_drink::wine_glass:
+
+This is a Vaadin platform Java example application, used to demonstrate features of the Vaadin Flow framework.
+
+The easiest way of using it is via [https://vaadin.com/start](https://vaadin.com/start/v10-simple-ui) - you can choose the package naming you want.
+
+The Starter demonstrates the core Vaadin Flow concepts:
+* Building UIs in Java with Components based on [Vaadin components](https://vaadin.com/components/browse), such as `TextField`, `Button`, `ComboBox`, `DatePicker`, `VerticalLayout` and `Grid` (see `CategoriesList`)
+* [Creating forms with `Binder`](https://github.com/vaadin/free-starter-flow/blob/master/documentation/using-binder-in-review-editor-dialog.asciidoc) (`ReviewEditorDialog`)
+* Making reusable Components on server side with `Composite` (`AbstractEditorDialog`)
+* [Creating a Component based on a HTML Template](https://github.com/vaadin/free-starter-flow/blob/master/documentation/polymer-template-based-view.asciidoc) (`ReviewsList`) 
+  * This template can be opened and edited with [the Vaadin Designer](https://vaadin.com/designer)
+* [Creating Navigation with the Router API](https://github.com/vaadin/free-starter-flow/blob/master/documentation/using-annotation-based-router-api.asciidoc) (`MainLayout`, `ReviewsList`, `CategoriesList`)
+
+## Prerequisites
+
+The project can be imported into the IDE of your choice, with Java 8 installed, as a Maven project.
+
+## Running the Project
+
+1. Run using `mvn jetty:run`
+2. Wait for the application to start
+3. Open http://localhost:8080/ to view the application
+
+## Documentation
+
+Brief introduction to the application parts can be found from the `documentation` folder. For Vaadin documentation for Java users, see [Vaadin.com/docs](https://vaadin.com/docs/v10/flow/Overview.html).
+
+### Branching information
+* `master` the latest version of the starter, using the latest platform snapshot
+* `V10` the version for Vaadin 10
+* `V11` the version for Vaadin 11
+* `V12` the version for Vaadin 12

+ 143 - 0
SPSEmulator-gui/pom.xml

@@ -0,0 +1,143 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>de.mcs.tools.sps</groupId>
+  <artifactId>spsemulator-gui</artifactId>
+  <name>SPSEmulator-GUI</name>
+  <version>1.0-SNAPSHOT</version>
+  <packaging>war</packaging>
+
+  <properties>
+    <maven.compiler.source>1.8</maven.compiler.source>
+    <maven.compiler.target>1.8</maven.compiler.target>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+    <failOnMissingWebXml>false</failOnMissingWebXml>
+
+    <vaadin.version>12.0.5</vaadin.version>
+    <jetty.version>9.4.11.v20180605</jetty.version>
+  </properties>
+
+  <repositories>
+        <!-- Repository used by many Vaadin add-ons -->
+    <repository>
+      <id>Vaadin Directory</id>
+      <url>http://maven.vaadin.com/vaadin-addons</url>
+    </repository>
+        <!-- Repository needed for the prerelease versions of Vaadin -->
+    <repository>
+      <id>vaadin-prereleases</id>
+      <url>https://maven.vaadin.com/vaadin-prereleases</url>
+    </repository>
+  </repositories>
+
+  <pluginRepositories>
+        <!-- Repository needed for the prerelease versions of Vaadin -->
+    <pluginRepository>
+      <id>vaadin-prereleases</id>
+      <url>https://maven.vaadin.com/vaadin-prereleases</url>
+    </pluginRepository>
+  </pluginRepositories>
+
+  <dependencyManagement>
+    <dependencies>
+      <dependency>
+        <groupId>com.vaadin</groupId>
+        <artifactId>vaadin-bom</artifactId>
+        <type>pom</type>
+        <scope>import</scope>
+        <version>${vaadin.version}</version>
+      </dependency>
+    </dependencies>
+  </dependencyManagement>
+
+  <dependencies>
+    <dependency>
+      <groupId>com.vaadin</groupId>
+      <artifactId>vaadin-core</artifactId>
+    </dependency>
+
+        <!-- Added to provide logging output as Flow uses -->
+        <!-- the unbound SLF4J no-operation (NOP) logger implementation -->
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-simple</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>javax.servlet</groupId>
+      <artifactId>javax.servlet-api</artifactId>
+      <version>3.1.0</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>commons-beanutils</groupId>
+      <artifactId>commons-beanutils</artifactId>
+      <version>1.9.2</version>
+      <type>jar</type>
+    </dependency>
+    <dependency>
+    	<groupId>de.mcs.tools.sps</groupId>
+    	<artifactId>SPSEmulator-client</artifactId>
+    	<version>0.0.1-SNAPSHOT</version>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.eclipse.jetty</groupId>
+        <artifactId>jetty-maven-plugin</artifactId>
+        <version>${jetty.version}</version>
+      </plugin>
+    </plugins>
+  </build>
+
+  <profiles>
+    <profile>
+            <!-- Production mode can be activated with either property or profile -->
+      <id>production-mode</id>
+      <activation>
+        <property>
+          <name>vaadin.productionMode</name>
+        </property>
+      </activation>
+
+      <properties>
+        <vaadin.productionMode>true</vaadin.productionMode>
+      </properties>
+
+      <dependencies>
+        <dependency>
+          <groupId>com.vaadin</groupId>
+          <artifactId>flow-server-production-mode</artifactId>
+        </dependency>
+      </dependencies>
+
+      <build>
+        <plugins>
+          <plugin>
+            <groupId>com.vaadin</groupId>
+            <artifactId>vaadin-maven-plugin</artifactId>
+            <version>${vaadin.version}</version>
+            <executions>
+              <execution>
+                <goals>
+                  <goal>copy-production-files</goal>
+                  <goal>package-for-production</goal>
+                </goals>
+              </execution>
+            </executions>
+          </plugin>
+
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-war-plugin</artifactId>
+            <version>3.1.0</version>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
+  </profiles>
+</project>

BIN
SPSEmulator-gui/spsemulatorgui.zip


+ 86 - 0
SPSEmulator-gui/src/main/java/de/mcs/tools/sps/backend/Category.java

@@ -0,0 +1,86 @@
+package de.mcs.tools.sps.backend;
+
+import java.io.Serializable;
+import java.util.Objects;
+
+/**
+ * Represents a beverage category.
+ */
+public class Category implements Serializable {
+
+    private Long id = null;
+
+    private String name = "";
+
+    public Category() {
+    }
+
+    public Category(String name) {
+        this.name = name;
+    }
+
+    public Category(Category other) {
+        Objects.requireNonNull(other);
+        this.name = other.getName();
+        this.id = other.getId();
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    /**
+     * Gets the value of name
+     *
+     * @return the value of name
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Sets the value name
+     *
+     * @param name
+     *            new value of name
+     */
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    @Override
+    public String toString() {
+        // Must use getters instead of direct member access,
+        // to make it work with proxy objects generated by the view model
+        return "Category{" + getId() + ":" + getName() + '}';
+    }
+
+    @Override
+    public int hashCode() {
+        if (getId() == null) {
+            return super.hashCode();
+        }
+        return getId().hashCode();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!(obj instanceof Category)) {
+            return false;
+        }
+        Category other = (Category) obj;
+        if (getId() == null) {
+            if (other.getId() != null)
+                return false;
+        } else if (!getId().equals(other.getId()))
+            return false;
+        return true;
+    }
+}

+ 206 - 0
SPSEmulator-gui/src/main/java/de/mcs/tools/sps/backend/CategoryService.java

@@ -0,0 +1,206 @@
+package de.mcs.tools.sps.backend;
+
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Collectors;
+
+/**
+ * Simple backend service to store and retrieve {@link Category} instances.
+ */
+public class CategoryService {
+
+    /**
+     * Helper class to initialize the singleton Service in a thread-safe way and
+     * to keep the initialization ordering clear between the two services. See
+     * also: https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom
+     */
+    private static class SingletonHolder {
+        static final CategoryService INSTANCE = createDemoCategoryService();
+
+        /** This class is not meant to be instantiated. */
+        private SingletonHolder() {
+        }
+
+        private static CategoryService createDemoCategoryService() {
+            CategoryService categoryService = new CategoryService();
+            Set<String> categoryNames = new LinkedHashSet<>(
+                    StaticData.BEVERAGES.values());
+
+            categoryNames.forEach(name -> {
+                Category category = categoryService
+                        .doSaveCategory(new Category(name));
+                if (StaticData.UNDEFINED.equals(name)) {
+                    categoryService.undefinedCategoryId.set(category.getId());
+                }
+            });
+
+            return categoryService;
+        }
+    }
+
+    private Map<Long, Category> categories = new HashMap<>();
+    private AtomicLong nextId = new AtomicLong(0);
+    private AtomicLong undefinedCategoryId = new AtomicLong(-1);
+
+    /**
+     * Declared private to ensure uniqueness of this Singleton.
+     */
+    private CategoryService() {
+    }
+
+    /**
+     * Gets the unique instance of this Singleton.
+     *
+     * @return the unique instance of this Singleton
+     */
+    public static CategoryService getInstance() {
+        return SingletonHolder.INSTANCE;
+    }
+
+    /**
+     * Returns a dedicated undefined category.
+     *
+     * @return the undefined category
+     */
+    public Category getUndefinedCategory() {
+        return categories.get(undefinedCategoryId.get());
+    }
+
+    /**
+     * Fetches the categories whose name matches the given filter text.
+     *
+     * The matching is case insensitive. When passed an empty filter text, the
+     * method returns all categories. The returned list is ordered by name.
+     *
+     * @param filter
+     *            the filter text
+     * @return the list of matching categories
+     */
+    public List<Category> findCategories(String filter) {
+        String normalizedFilter = filter.toLowerCase();
+
+        // Make a copy of each matching item to keep entities and DTOs separated
+        return categories.values().stream()
+                .filter(c -> c
+                        .getName().toLowerCase().contains(normalizedFilter))
+                .map(Category::new)
+                .sorted((c1, c2) -> c1.getName()
+                        .compareToIgnoreCase(c2.getName()))
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * Searches for the exact category whose name matches the given filter text.
+     *
+     * The matching is case insensitive.
+     *
+     * @param name
+     *            the filter text
+     * @return an {@link Optional} containing the category if found, or
+     *         {@link Optional#empty()}
+     * @throws IllegalStateException
+     *             if the result is ambiguous
+     */
+    public Optional<Category> findCategoryByName(String name) {
+        List<Category> categoriesMatching = findCategories(name);
+
+        if (categoriesMatching.isEmpty()) {
+            return Optional.empty();
+        }
+        if (categoriesMatching.size() > 1) {
+            throw new IllegalStateException(
+                    "Category " + name + " is ambiguous");
+        }
+        return Optional.of(categoriesMatching.get(0));
+    }
+
+    /**
+     * Fetches the exact category whose name matches the given filter text.
+     *
+     * Behaves like {@link #findCategoryByName(String)}, except that returns a
+     * {@link Category} instead of an {@link Optional}. If the category can't be
+     * identified, an exception is thrown.
+     *
+     * @param name
+     *            the filter text
+     * @return the category, if found
+     * @throws IllegalStateException
+     *             if not exactly one category matches the given name
+     */
+    public Category findCategoryOrThrow(String name) {
+        return findCategoryByName(name)
+                .orElseThrow(() -> new IllegalStateException(
+                        "Category " + name + " does not exist"));
+    }
+
+    /**
+     * Searches for the exact category with the given id.
+     *
+     * @param id
+     *            the category id
+     * @return an {@link Optional} containing the category if found, or
+     *         {@link Optional#empty()}
+     */
+    public Optional<Category> findCategoryById(Long id) {
+        Category category = categories.get(id);
+        return Optional.ofNullable(category);
+    }
+
+    /**
+     * Deletes the given category from the category store.
+     *
+     * @param category
+     *            the category to delete
+     * @return true if the operation was successful, otherwise false
+     */
+    public boolean deleteCategory(Category category) {
+        if (category.getId() != null
+                && undefinedCategoryId.get() == category.getId().longValue()) {
+            throw new IllegalArgumentException(
+                    "Undefined category may not be removed");
+        }
+        return categories.remove(category.getId()) != null;
+    }
+
+    /**
+     * Persists the given category into the category store.
+     *
+     * If the category is already persistent, the saved category will get
+     * updated with the name of the given category object. If the category is
+     * new (i.e. its id is null), it will get a new unique id before being
+     * saved.
+     *
+     * @param dto
+     *            the category to save
+     */
+    public void saveCategory(Category dto) {
+        doSaveCategory(dto);
+    }
+
+    private Category doSaveCategory(Category dto) {
+        Category entity = categories.get(dto.getId());
+
+        if (entity == null) {
+            // Make a copy to keep entities and DTOs separated
+            entity = new Category(dto);
+            if (dto.getId() == null) {
+                entity.setId(nextId.incrementAndGet());
+            }
+            categories.put(entity.getId(), entity);
+        } else if (undefinedCategoryId.get() == dto.getId().longValue()
+                && !Objects.equals(entity.getName(), dto.getName())) {
+            throw new IllegalArgumentException(
+                    "Undefined category may not be renamed");
+        } else {
+            entity.setName(dto.getName());
+        }
+        return entity;
+    }
+
+}

+ 184 - 0
SPSEmulator-gui/src/main/java/de/mcs/tools/sps/backend/Review.java

@@ -0,0 +1,184 @@
+package de.mcs.tools.sps.backend;
+
+import java.io.Serializable;
+import java.time.LocalDate;
+
+/**
+ * Represents a beverage review.
+ */
+public class Review implements Serializable {
+
+    private Long id = null;
+    private int score;
+    private String name;
+    private LocalDate date;
+    private Category category;
+    private int count;
+
+    /**
+     * Default constructor.
+     */
+    public Review() {
+        reset();
+    }
+
+    /**
+     * Constructs a new instance with the given data.
+     *
+     * @param score
+     *            Review score
+     * @param name
+     *            Name of beverage reviewed
+     * @param date
+     *            Last review date
+     * @param category
+     *            Category of beverage
+     * @param count
+     *            Times tasted
+     */
+    public Review(int score, String name, LocalDate date, Category category,
+            int count) {
+        this.score = score;
+        this.name = name;
+        this.date = date;
+        this.category = new Category(category);
+        this.count = count;
+    }
+
+    /**
+     * Copy constructor.
+     *
+     * @param other
+     *            The instance to copy
+     */
+    public Review(Review other) {
+        this(other.getScore(), other.getName(), other.getDate(),
+                other.getCategory(), other.getCount());
+        this.id = other.getId();
+    }
+
+    /**
+     * Resets all fields to their default values.
+     */
+    public void reset() {
+        this.id = null;
+        this.score = 1;
+        this.name = "";
+        this.date = LocalDate.now();
+        this.category = null;
+        this.count = 1;
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    /**
+     * Gets the value of score
+     *
+     * @return the value of score
+     */
+    public int getScore() {
+        return score;
+    }
+
+    /**
+     * Sets the value of score
+     *
+     * @param score
+     *            new value of Score
+     */
+    public void setScore(int score) {
+        this.score = score;
+    }
+
+    /**
+     * Gets the value of name
+     *
+     * @return the value of name
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Sets the value of name
+     *
+     * @param name
+     *            new value of name
+     */
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    /**
+     * Gets the value of category
+     *
+     * @return the value of category
+     */
+    public Category getCategory() {
+        return category;
+    }
+
+    /**
+     * Sets the value of category
+     *
+     * @param category
+     *            new value of category
+     */
+    public void setCategory(Category category) {
+        this.category = category;
+    }
+
+    /**
+     * Gets the value of date
+     *
+     * @return the value of date
+     */
+    public LocalDate getDate() {
+        return date;
+    }
+
+    /**
+     * Sets the value of date
+     *
+     * @param date
+     *            new value of date
+     */
+    public void setDate(LocalDate date) {
+        this.date = date;
+    }
+
+    /**
+     * Gets the value of count
+     *
+     * @return the value of count
+     */
+    public int getCount() {
+        return count;
+    }
+
+    /**
+     * Sets the value of count
+     *
+     * @param count
+     *            new value of count
+     */
+    public void setCount(int count) {
+        this.count = count;
+    }
+
+    @Override
+    public String toString() {
+        // Must use getters instead of direct member access,
+        // to make it work with proxy objects generated by the view model
+        return "Review{" + "id=" + getId() + ", score=" + getScore() + ", name="
+                + getName() + ", category=" + getCategory() + ", date="
+                + getDate() + ", count=" + getCount() + '}';
+    }
+
+}

+ 168 - 0
SPSEmulator-gui/src/main/java/de/mcs/tools/sps/backend/ReviewService.java

@@ -0,0 +1,168 @@
+package de.mcs.tools.sps.backend;
+
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import de.mcs.tools.sps.ui.encoders.LocalDateToStringEncoder;
+
+/**
+ * Simple backend service to store and retrieve {@link Review} instances.
+ */
+public class ReviewService {
+
+    /**
+     * Helper class to initialize the singleton Service in a thread-safe way and
+     * to keep the initialization ordering clear between the two services. See
+     * also: https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom
+     */
+    private static class SingletonHolder {
+        static final ReviewService INSTANCE = createDemoReviewService();
+
+        /** This class is not meant to be instantiated. */
+        private SingletonHolder() {
+        }
+
+        private static ReviewService createDemoReviewService() {
+            final ReviewService reviewService = new ReviewService();
+            Random r = new Random();
+            int reviewCount = 20 + r.nextInt(30);
+            List<Map.Entry<String, String>> beverages = new ArrayList<>(
+                    StaticData.BEVERAGES.entrySet());
+
+            for (int i = 0; i < reviewCount; i++) {
+                Review review = new Review();
+                Map.Entry<String, String> beverage = beverages
+                        .get(r.nextInt(StaticData.BEVERAGES.size()));
+                Category category = CategoryService.getInstance()
+                        .findCategoryOrThrow(beverage.getValue());
+
+                review.setName(beverage.getKey());
+                LocalDate testDay = getRandomDate();
+                review.setDate(testDay);
+                review.setScore(1 + r.nextInt(5));
+                review.setCategory(category);
+                review.setCount(1 + r.nextInt(15));
+                reviewService.saveReview(review);
+            }
+
+            return reviewService;
+        }
+
+        private static LocalDate getRandomDate() {
+            long minDay = LocalDate.of(1930, 1, 1).toEpochDay();
+            long maxDay = LocalDate.now().toEpochDay();
+            long randomDay = ThreadLocalRandom.current().nextLong(minDay,
+                    maxDay);
+            return LocalDate.ofEpochDay(randomDay);
+        }
+    }
+
+    private Map<Long, Review> reviews = new HashMap<>();
+    private AtomicLong nextId = new AtomicLong(0);
+
+    /**
+     * Declared private to ensure uniqueness of this Singleton.
+     */
+    private ReviewService() {
+    }
+
+    /**
+     * Gets the unique instance of this Singleton.
+     *
+     * @return the unique instance of this Singleton
+     */
+    public static ReviewService getInstance() {
+        return SingletonHolder.INSTANCE;
+    }
+
+    /**
+     * Fetches the reviews matching the given filter text.
+     *
+     * The matching is case insensitive. When passed an empty filter text, the
+     * method returns all categories. The returned list is ordered by name.
+     *
+     * @param filter
+     *            the filter text
+     * @return the list of matching reviews
+     */
+    public List<Review> findReviews(String filter) {
+        String normalizedFilter = filter.toLowerCase();
+
+        return reviews.values().stream().filter(
+                review -> filterTextOf(review).contains(normalizedFilter))
+                .sorted((r1, r2) -> r2.getId().compareTo(r1.getId()))
+                .collect(Collectors.toList());
+    }
+
+    private String filterTextOf(Review review) {
+        LocalDateToStringEncoder dateConverter = new LocalDateToStringEncoder();
+        // Use a delimiter which can't be entered in the search box,
+        // to avoid false positives
+        String filterableText = Stream
+                .of(review.getName(),
+                        review.getCategory() == null ? StaticData.UNDEFINED
+                                : review.getCategory().getName(),
+                        String.valueOf(review.getScore()),
+                        String.valueOf(review.getCount()),
+                        dateConverter.encode(review.getDate()))
+                .collect(Collectors.joining("\t"));
+        return filterableText.toLowerCase();
+    }
+
+    /**
+     * Deletes the given review from the review store.
+     *
+     * @param review
+     *            the review to delete
+     * @return true if the operation was successful, otherwise false
+     */
+    public boolean deleteReview(Review review) {
+        return reviews.remove(review.getId()) != null;
+    }
+
+    /**
+     * Persists the given review into the review store.
+     *
+     * If the review is already persistent, the saved review will get updated
+     * with the field values of the given review object. If the review is new
+     * (i.e. its id is null), it will get a new unique id before being saved.
+     *
+     * @param dto
+     *            the review to save
+     */
+    public void saveReview(Review dto) {
+        Review entity = reviews.get(dto.getId());
+        Category category = dto.getCategory();
+
+        if (category != null) {
+            // The case when the category is new (not persisted yet, thus
+            // has null id) is not handled here, because it can't currently
+            // occur via the UI.
+            // Note that Category.UNDEFINED also gets mapped to null.
+            category = CategoryService.getInstance()
+                    .findCategoryById(category.getId()).orElse(null);
+        }
+        if (entity == null) {
+            // Make a copy to keep entities and DTOs separated
+            entity = new Review(dto);
+            if (dto.getId() == null) {
+                entity.setId(nextId.incrementAndGet());
+            }
+            reviews.put(entity.getId(), entity);
+        } else {
+            entity.setScore(dto.getScore());
+            entity.setName(dto.getName());
+            entity.setDate(dto.getDate());
+            entity.setCount(dto.getCount());
+        }
+        entity.setCategory(category);
+    }
+}

+ 111 - 0
SPSEmulator-gui/src/main/java/de/mcs/tools/sps/backend/StaticData.java

@@ -0,0 +1,111 @@
+package de.mcs.tools.sps.backend;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.stream.Stream;
+
+class StaticData {
+
+    private static final String MINERAL_WATER = "Mineral Water";
+    private static final String SOFT_DRINK = "Soft Drink";
+    private static final String COFFEE = "Coffee";
+    private static final String TEA = "Tea";
+    private static final String DAIRY = "Dairy";
+    private static final String CIDER = "Cider";
+    private static final String BEER = "Beer";
+    private static final String WINE = "Wine";
+    private static final String OTHER = "Other";
+
+    public static final String UNDEFINED = "Undefined";
+    
+    static final Map<String, String> BEVERAGES = new LinkedHashMap<>();
+
+    static {
+        Stream.of("Evian",
+                "Voss",
+                "Veen",
+                "San Pellegrino",
+                "Perrier")
+                .forEach(name -> BEVERAGES.put(name, MINERAL_WATER));
+
+        Stream.of("Coca-Cola",
+                "Fanta",
+                "Sprite")
+                .forEach(name -> BEVERAGES.put(name, SOFT_DRINK));
+
+        Stream.of("Maxwell Ready-to-Drink Coffee",
+                "Nescafé Gold",
+                "Starbucks East Timor Tatamailau")
+                .forEach(name -> BEVERAGES.put(name, COFFEE));
+
+        Stream.of("Prince Of Peace Organic White Tea",
+                "Pai Mu Tan White Peony Tea",
+                "Tazo Zen Green Tea",
+                "Dilmah Sencha Green Tea",
+                "Twinings Earl Grey",
+                "Twinings Lady Grey",
+                "Classic Indian Chai")
+                .forEach(name -> BEVERAGES.put(name, TEA));
+
+        Stream.of("Cow's Milk",
+                "Goat's Milk",
+                "Unicorn's Milk",
+                "Salt Lassi",
+                "Mango Lassi",
+                "Airag")
+                .forEach(name -> BEVERAGES.put(name, DAIRY));
+
+        Stream.of("Crowmoor Extra Dry Apple",
+                "Golden Cap Perry",
+                "Somersby Blueberry",
+                "Kopparbergs Naked Apple Cider",
+                "Kopparbergs Raspberry",
+                "Kingstone Press Wild Berry Flavoured Cider",
+                "Crumpton Oaks Apple",
+                "Frosty Jack's",
+                "Ciderboys Mad Bark",
+                "Angry Orchard Stone Dry",
+                "Walden Hollow",
+                "Fox Barrel Wit Pear")
+                .forEach(name -> BEVERAGES.put(name, CIDER));
+
+        Stream.of("Budweiser",
+                "Miller",
+                "Heineken",
+                "Holsten Pilsener",
+                "Krombacher",
+                "Weihenstephaner Hefeweissbier",
+                "Ayinger Kellerbier",
+                "Guinness Draught",
+                "Kilkenny Irish Cream Ale",
+                "Hoegaarden White",
+                "Barbar",
+                "Corsendonk Agnus Dei",
+                "Leffe Blonde",
+                "Chimay Tripel",
+                "Duvel",
+                "Pilsner Urquell",
+                "Kozel",
+                "Staropramen",
+                "Lapin Kulta IVA",
+                "Kukko Pils III",
+                "Finlandia Sahti")
+                .forEach(name -> BEVERAGES.put(name, BEER));
+
+        Stream.of("Jacob's Creek Classic Shiraz",
+                "Chateau d’Yquem Sauternes",
+                "Oremus Tokaji Aszú 5 Puttonyos")
+                .forEach(name -> BEVERAGES.put(name, WINE));
+
+        Stream.of("Pan Galactic Gargle Blaster",
+                "Mead",
+                "Soma")
+                .forEach(name -> BEVERAGES.put(name, OTHER));
+
+        BEVERAGES.put("", UNDEFINED);
+    }
+
+    /** This class is not meant to be instantiated. */
+    private StaticData() {
+    }
+}

+ 73 - 0
SPSEmulator-gui/src/main/java/de/mcs/tools/sps/ui/MainLayout.java

@@ -0,0 +1,73 @@
+/*
+ * Copyright 2000-2017 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package de.mcs.tools.sps.ui;
+
+import com.vaadin.flow.component.Text;
+import com.vaadin.flow.component.dependency.HtmlImport;
+import com.vaadin.flow.component.html.Div;
+import com.vaadin.flow.component.html.H2;
+import com.vaadin.flow.component.icon.Icon;
+import com.vaadin.flow.component.icon.VaadinIcon;
+import com.vaadin.flow.component.page.Viewport;
+import com.vaadin.flow.router.HighlightConditions;
+import com.vaadin.flow.router.RouterLayout;
+import com.vaadin.flow.router.RouterLink;
+import com.vaadin.flow.server.InitialPageSettings;
+import com.vaadin.flow.server.PWA;
+import com.vaadin.flow.server.PageConfigurator;
+
+import de.mcs.tools.sps.ui.views.assemblerview.AssemblerView;
+import de.mcs.tools.sps.ui.views.emulatorview.EmulatorView;
+
+/**
+ * The main layout contains the header with the navigation buttons, and the
+ * child views below that.
+ */
+@HtmlImport("frontend://styles/shared-styles.html")
+@PWA(name = "ArduinoSPS/TinySPS", shortName = "ArduinoSPS")
+@Viewport("width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes")
+public class MainLayout extends Div implements RouterLayout, PageConfigurator {
+
+  public MainLayout() {
+    H2 title = new H2("ArduinoSPS/TinySPS");
+    title.addClassName("main-layout__title");
+
+    RouterLink reviews = new RouterLink(null, AssemblerView.class);
+    reviews.add(new Icon(VaadinIcon.LINES), new Text("SPS Assembler"));
+    reviews.addClassName("main-layout__nav-item");
+    // Only show as active for the exact URL, but not for sub paths
+    reviews.setHighlightCondition(HighlightConditions.sameLocation());
+
+    RouterLink categories = new RouterLink(null, EmulatorView.class);
+    categories.add(new Icon(VaadinIcon.COG), new Text("SPS Emulator"));
+    categories.addClassName("main-layout__nav-item");
+
+    Div navigation = new Div(reviews, categories);
+    navigation.addClassName("main-layout__nav");
+
+    Div header = new Div(title, navigation);
+    header.addClassName("main-layout__header");
+    add(header);
+
+    addClassName("main-layout");
+  }
+
+  @Override
+  public void configurePage(InitialPageSettings settings) {
+    settings.addMetaTag("apple-mobile-web-app-capable", "yes");
+    settings.addMetaTag("apple-mobile-web-app-status-bar-style", "black");
+  }
+}

+ 252 - 0
SPSEmulator-gui/src/main/java/de/mcs/tools/sps/ui/common/AbstractEditorDialog.java

@@ -0,0 +1,252 @@
+/*
+ * Copyright 2000-2017 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package de.mcs.tools.sps.ui.common;
+
+import java.io.Serializable;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+
+import com.vaadin.flow.component.button.Button;
+import com.vaadin.flow.component.button.ButtonVariant;
+import com.vaadin.flow.component.dialog.Dialog;
+import com.vaadin.flow.component.formlayout.FormLayout;
+import com.vaadin.flow.component.html.Div;
+import com.vaadin.flow.component.html.H3;
+import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
+import com.vaadin.flow.data.binder.Binder;
+import com.vaadin.flow.data.binder.BinderValidationStatus;
+import com.vaadin.flow.shared.Registration;
+
+/**
+ * Abstract base class for dialogs adding, editing or deleting items.
+ *
+ * Subclasses are expected to
+ * <ul>
+ * <li>add, during construction, the needed UI components to
+ * {@link #getFormLayout()} and bind them using {@link #getBinder()}, as well
+ * as</li>
+ * <li>override {@link #confirmDelete()} to open the confirmation dialog with
+ * the desired message (by calling
+ * {@link #openConfirmationDialog(String, String, String)}.</li>
+ * </ul>
+ *
+ * @param <T>
+ *            the type of the item to be added, edited or deleted
+ */
+public abstract class AbstractEditorDialog<T extends Serializable>
+        extends Dialog {
+
+    /**
+     * The operations supported by this dialog. Delete is enabled when editing
+     * an already existing item.
+     */
+    public enum Operation {
+        ADD("New", "add", false), EDIT("Edit", "edit", true);
+
+        private final String nameInTitle;
+        private final String nameInText;
+        private final boolean deleteEnabled;
+
+        Operation(String nameInTitle, String nameInText,
+                boolean deleteEnabled) {
+            this.nameInTitle = nameInTitle;
+            this.nameInText = nameInText;
+            this.deleteEnabled = deleteEnabled;
+        }
+
+        public String getNameInTitle() {
+            return nameInTitle;
+        }
+
+        public String getNameInText() {
+            return nameInText;
+        }
+
+        public boolean isDeleteEnabled() {
+            return deleteEnabled;
+        }
+    }
+
+    private final H3 titleField = new H3();
+    private final Button saveButton = new Button("Save");
+    private final Button cancelButton = new Button("Cancel");
+    private final Button deleteButton = new Button("Delete");
+    private Registration registrationForSave;
+
+    private final FormLayout formLayout = new FormLayout();
+    private final HorizontalLayout buttonBar = new HorizontalLayout(saveButton,
+            cancelButton, deleteButton);
+
+    private Binder<T> binder = new Binder<>();
+    private T currentItem;
+
+    private final ConfirmationDialog<T> confirmationDialog = new ConfirmationDialog<>();
+
+    private final String itemType;
+    private final BiConsumer<T, Operation> itemSaver;
+    private final Consumer<T> itemDeleter;
+
+    /**
+     * Constructs a new instance.
+     *
+     * @param itemType
+     *            The readable name of the item type
+     * @param itemSaver
+     *            Callback to save the edited item
+     * @param itemDeleter
+     *            Callback to delete the edited item
+     */
+    protected AbstractEditorDialog(String itemType,
+            BiConsumer<T, Operation> itemSaver, Consumer<T> itemDeleter) {
+        this.itemType = itemType;
+        this.itemSaver = itemSaver;
+        this.itemDeleter = itemDeleter;
+
+        initTitle();
+        initFormLayout();
+        initButtonBar();
+        setCloseOnEsc(true);
+        setCloseOnOutsideClick(false);
+    }
+
+    private void initTitle() {
+        add(titleField);
+    }
+
+    private void initFormLayout() {
+        formLayout.setResponsiveSteps(new FormLayout.ResponsiveStep("0", 1),
+                new FormLayout.ResponsiveStep("25em", 2));
+        Div div = new Div(formLayout);
+        div.addClassName("has-padding");
+        add(div);
+    }
+
+    private void initButtonBar() {
+        saveButton.setAutofocus(true);
+        saveButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
+        cancelButton.addClickListener(e -> close());
+        deleteButton.addClickListener(e -> deleteClicked());
+        deleteButton.addThemeVariants(ButtonVariant.LUMO_ERROR);
+        buttonBar.setClassName("buttons");
+        buttonBar.setSpacing(true);
+        add(buttonBar);
+    }
+
+    /**
+     * Gets the form layout, where additional components can be added for
+     * displaying or editing the item's properties.
+     *
+     * @return the form layout
+     */
+    protected final FormLayout getFormLayout() {
+        return formLayout;
+    }
+
+    /**
+     * Gets the binder.
+     *
+     * @return the binder
+     */
+    protected final Binder<T> getBinder() {
+        return binder;
+    }
+
+    /**
+     * Gets the item currently being edited.
+     *
+     * @return the item currently being edited
+     */
+    protected final T getCurrentItem() {
+        return currentItem;
+    }
+
+    /**
+     * Opens the given item for editing in the dialog.
+     *
+     * @param item
+     *            The item to edit; it may be an existing or a newly created
+     *            instance
+     * @param operation
+     *            The operation being performed on the item
+     */
+    public final void open(T item, Operation operation) {
+        currentItem = item;
+        titleField.setText(operation.getNameInTitle() + " " + itemType);
+        if (registrationForSave != null) {
+            registrationForSave.remove();
+        }
+        registrationForSave = saveButton
+                .addClickListener(e -> saveClicked(operation));
+        binder.readBean(currentItem);
+
+        deleteButton.setEnabled(operation.isDeleteEnabled());
+        open();
+    }
+
+    private void saveClicked(Operation operation) {
+        boolean isValid = binder.writeBeanIfValid(currentItem);
+
+        if (isValid) {
+            itemSaver.accept(currentItem, operation);
+            close();
+        } else {
+            BinderValidationStatus<T> status = binder.validate();
+        }
+    }
+
+    private void deleteClicked() {
+        if (confirmationDialog.getElement().getParent() == null) {
+            getUI().ifPresent(ui -> ui.add(confirmationDialog));
+        }
+        confirmDelete();
+    }
+
+    protected abstract void confirmDelete();
+
+    /**
+     * Opens the confirmation dialog before deleting the current item.
+     *
+     * The dialog will display the given title and message(s), then call
+     * {@link #deleteConfirmed(Serializable)} if the Delete button is clicked.
+     *
+     * @param title
+     *            The title text
+     * @param message
+     *            Detail message (optional, may be empty)
+     * @param additionalMessage
+     *            Additional message (optional, may be empty)
+     */
+    protected final void openConfirmationDialog(String title, String message,
+            String additionalMessage) {
+        confirmationDialog.open(title, message, additionalMessage, "Delete",
+                true, getCurrentItem(), this::deleteConfirmed, null);
+    }
+
+    /**
+     * Removes the {@code item} from the backend and close the dialog.
+     *
+     * @param item
+     *            the item to delete
+     */
+    protected void doDelete(T item) {
+        itemDeleter.accept(item);
+        close();
+    }
+
+    private void deleteConfirmed(T item) {
+        doDelete(item);
+    }
+}

+ 128 - 0
SPSEmulator-gui/src/main/java/de/mcs/tools/sps/ui/common/ConfirmationDialog.java

@@ -0,0 +1,128 @@
+/*
+ * Copyright 2000-2017 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package de.mcs.tools.sps.ui.common;
+
+import java.io.Serializable;
+import java.util.function.Consumer;
+
+import com.vaadin.flow.component.button.Button;
+import com.vaadin.flow.component.button.ButtonVariant;
+import com.vaadin.flow.component.dialog.Dialog;
+import com.vaadin.flow.component.html.Div;
+import com.vaadin.flow.component.html.H3;
+import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
+import com.vaadin.flow.shared.Registration;
+
+/**
+ * A generic dialog for confirming or cancelling an action.
+ *
+ * @param <T>
+ *            The type of the action's subject
+ */
+class ConfirmationDialog<T extends Serializable> extends Dialog {
+
+    private final H3 titleField = new H3();
+    private final Div messageLabel = new Div();
+    private final Div extraMessageLabel = new Div();
+    private final Button confirmButton = new Button();
+    private final Button cancelButton = new Button("Cancel");
+    private Registration registrationForConfirm;
+    private Registration registrationForCancel;
+
+    private static final Runnable NO_OP = () -> {
+    };
+
+    /**
+     * Constructor.
+     */
+    public ConfirmationDialog() {
+        setCloseOnEsc(true);
+        setCloseOnOutsideClick(false);
+
+        confirmButton.addClickListener(e -> close());
+        confirmButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
+        confirmButton.setAutofocus(true);
+        cancelButton.addClickListener(e -> close());
+        cancelButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
+
+        HorizontalLayout buttonBar = new HorizontalLayout(confirmButton,
+                cancelButton);
+        buttonBar.setClassName("buttons confirm-buttons");
+
+        Div labels = new Div(messageLabel, extraMessageLabel);
+        labels.setClassName("confirm-text");
+
+        titleField.setClassName("confirm-title");
+
+        add(titleField, labels, buttonBar);
+    }
+
+    /**
+     * Opens the confirmation dialog.
+     *
+     * The dialog will display the given title and message(s), then call
+     * <code>confirmHandler</code> if the Confirm button is clicked, or
+     * <code>cancelHandler</code> if the Cancel button is clicked.
+     *
+     * @param title
+     *            The title text
+     * @param message
+     *            Detail message (optional, may be empty)
+     * @param additionalMessage
+     *            Additional message (optional, may be empty)
+     * @param actionName
+     *            The action name to be shown on the Confirm button
+     * @param isDisruptive
+     *            True if the action is disruptive, such as deleting an item
+     * @param item
+     *            The subject of the action
+     * @param confirmHandler
+     *            The confirmation handler function
+     * @param cancelHandler
+     *            The cancellation handler function
+     */
+    public void open(String title, String message, String additionalMessage,
+            String actionName, boolean isDisruptive, T item,
+            Consumer<T> confirmHandler, Runnable cancelHandler) {
+        titleField.setText(title);
+        messageLabel.setText(message);
+        extraMessageLabel.setText(additionalMessage);
+        confirmButton.setText(actionName);
+
+        Runnable cancelAction = cancelHandler == null ? NO_OP : cancelHandler;
+
+        if (registrationForConfirm != null) {
+            registrationForConfirm.remove();
+        }
+        registrationForConfirm = confirmButton
+                .addClickListener(e -> confirmHandler.accept(item));
+        if (registrationForCancel != null) {
+            registrationForCancel.remove();
+        }
+        registrationForCancel = cancelButton
+                .addClickListener(e -> cancelAction.run());
+        this.addOpenedChangeListener(e -> {
+            if (!e.isOpened()) {
+                cancelAction.run();
+            }
+        });
+        confirmButton.removeThemeVariants(ButtonVariant.LUMO_ERROR);
+        if (isDisruptive) {
+            confirmButton.addThemeVariants(ButtonVariant.LUMO_ERROR);
+        }
+        open();
+    }
+}

+ 29 - 0
SPSEmulator-gui/src/main/java/de/mcs/tools/sps/ui/encoders/LocalDateToStringEncoder.java

@@ -0,0 +1,29 @@
+package de.mcs.tools.sps.ui.encoders;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+
+import com.vaadin.flow.templatemodel.ModelEncoder;
+
+/**
+ * Converts between DateTime-objects and their String-representations
+ *
+ */
+
+public class LocalDateToStringEncoder
+        implements ModelEncoder<LocalDate, String> {
+
+    public static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter
+            .ofPattern("MM/dd/yyyy");
+
+    @Override
+    public LocalDate decode(String presentationValue) {
+        return LocalDate.parse(presentationValue, DATE_FORMAT);
+    }
+
+    @Override
+    public String encode(LocalDate modelValue) {
+        return modelValue == null ? null : modelValue.format(DATE_FORMAT);
+    }
+
+}

+ 41 - 0
SPSEmulator-gui/src/main/java/de/mcs/tools/sps/ui/encoders/LongToStringEncoder.java

@@ -0,0 +1,41 @@
+/*
+ * Copyright 2000-2017 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package de.mcs.tools.sps.ui.encoders;
+
+import com.vaadin.flow.templatemodel.ModelEncoder;
+
+/**
+ * @author Vaadin Ltd
+ *
+ */
+
+public class LongToStringEncoder implements ModelEncoder<Long, String> {
+
+    @Override
+    public String encode(Long modelValue) {
+        if (modelValue == null)
+            return null;
+        return modelValue.toString();
+    }
+
+    @Override
+    public Long decode(String presentationValue) {
+        if (presentationValue == null)
+            return null;
+        return Long.parseLong(presentationValue);
+    }
+
+}

+ 90 - 0
SPSEmulator-gui/src/main/java/de/mcs/tools/sps/ui/views/assemblerview/AssemblerView.java

@@ -0,0 +1,90 @@
+/**
+ * 
+ */
+package de.mcs.tools.sps.ui.views.assemblerview;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.vaadin.flow.component.button.Button;
+import com.vaadin.flow.component.combobox.ComboBox;
+import com.vaadin.flow.component.html.Label;
+import com.vaadin.flow.component.icon.Icon;
+import com.vaadin.flow.component.icon.VaadinIcon;
+import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
+import com.vaadin.flow.component.orderedlayout.VerticalLayout;
+import com.vaadin.flow.component.textfield.TextArea;
+import com.vaadin.flow.router.PageTitle;
+import com.vaadin.flow.router.Route;
+
+import de.mcs.tools.sps.emulator.model.Hardware;
+import de.mcs.tools.sps.ui.MainLayout;
+
+/**
+ * @author w.klaas
+ *
+ */
+@Route(value = "", layout = MainLayout.class)
+@PageTitle("SPS Assembler")
+public class AssemblerView extends VerticalLayout {
+
+  private final Label header = new Label("SPS Assembler");
+  private final TextArea source = new TextArea("source");
+  private final Label lbHardware = new Label("Hardware:");
+  private final ComboBox<String> cbHardware = new ComboBox<>();
+  private final Label lbOutputFormat = new Label("Output Format:");
+  private final ComboBox<String> cbOutputFormat = new ComboBox<>();
+  private final Button btCompile = new Button(new Icon(VaadinIcon.CODE));
+
+  /**
+   * 
+   */
+  public AssemblerView() {
+    initView();
+
+    initComboBoxes();
+
+    HorizontalLayout viewToolbar = new HorizontalLayout();
+    viewToolbar.addClassName("view-toolbar");
+    viewToolbar.add(lbHardware);
+    viewToolbar.add(cbHardware);
+    viewToolbar.add(lbOutputFormat);
+    viewToolbar.add(cbOutputFormat);
+    viewToolbar.add(btCompile);
+    viewToolbar.setVerticalComponentAlignment(Alignment.CENTER, lbHardware, lbOutputFormat);
+    add(viewToolbar);
+
+    btCompile.getElement().setAttribute("title", "compile");
+
+    VerticalLayout container = new VerticalLayout();
+    container.setPadding(false);
+    container.setClassName("view-container");
+    container.setAlignItems(Alignment.STRETCH);
+    source.setSizeFull();
+    container.add(source);
+    add(container);
+  }
+
+  private void initComboBoxes() {
+    List<String> items = new ArrayList<>();
+    for (Hardware hardwareItem : Hardware.values()) {
+      items.add(hardwareItem.name());
+    }
+    cbHardware.setItems(items);
+    cbHardware.setValue("HOLTEK");
+
+    items = new ArrayList<>();
+    items.add("IntelHEX");
+    items.add("TPSText");
+    items.add("HEXText");
+
+    cbOutputFormat.setItems(items);
+    cbOutputFormat.setValue("IntelHEX");
+  }
+
+  private void initView() {
+    addClassName("assembler-view");
+    setDefaultHorizontalComponentAlignment(Alignment.STRETCH);
+  }
+
+}

+ 8 - 0
SPSEmulator-gui/src/main/java/de/mcs/tools/sps/ui/views/assemblerview/package-info.java

@@ -0,0 +1,8 @@
+/**
+ * 
+ */
+/**
+ * @author w.klaas
+ *
+ */
+package de.mcs.tools.sps.ui.views.assemblerview;

+ 106 - 0
SPSEmulator-gui/src/main/java/de/mcs/tools/sps/ui/views/emulatorview/EmulatorView.java

@@ -0,0 +1,106 @@
+/*
+ * Copyright 2000-2017 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package de.mcs.tools.sps.ui.views.emulatorview;
+
+import java.util.List;
+
+import com.vaadin.flow.component.Tag;
+import com.vaadin.flow.component.button.Button;
+import com.vaadin.flow.component.checkbox.CheckboxGroup;
+import com.vaadin.flow.component.dependency.HtmlImport;
+import com.vaadin.flow.component.grid.Grid;
+import com.vaadin.flow.component.notification.Notification;
+import com.vaadin.flow.component.notification.Notification.Position;
+import com.vaadin.flow.component.polymertemplate.Id;
+import com.vaadin.flow.component.polymertemplate.PolymerTemplate;
+import com.vaadin.flow.component.textfield.TextField;
+import com.vaadin.flow.router.PageTitle;
+import com.vaadin.flow.router.Route;
+
+import de.mcs.tools.sps.backend.Category;
+import de.mcs.tools.sps.backend.CategoryService;
+import de.mcs.tools.sps.backend.Review;
+import de.mcs.tools.sps.backend.ReviewService;
+import de.mcs.tools.sps.ui.MainLayout;
+import de.mcs.tools.sps.ui.common.AbstractEditorDialog;
+import de.mcs.tools.sps.ui.views.reviewslist.ReviewsList.ReviewsModel;
+
+/**
+ * Displays the list of available categories, with a search filter as well as buttons to add a new category or edit
+ * existing ones.
+ */
+@Route(value = "emulator", layout = MainLayout.class)
+@PageTitle("SPS Emulator")
+@Tag("emulator-view")
+@HtmlImport("frontend://src/views/emulatorview/emulator-view.html")
+public class EmulatorView extends PolymerTemplate<ReviewsModel> {
+
+  @Id("btStart")
+  private Button btStart; // = new Button(new Icon(VaadinIcon.STEP_FORWARD));
+  @Id("btPause")
+  private Button btPause;// = new Button(new Icon(VaadinIcon.PAUSE));
+  @Id("btStop")
+  private Button btStop;// = new Button(new Icon(VaadinIcon.STOP));
+
+  private final Grid<Category> grid = new Grid<>();
+
+  private final CheckboxGroup<String> cbgInput = new CheckboxGroup<>();
+  private final CheckboxGroup<String> cbgBtn = new CheckboxGroup<>();
+  private final TextField tfAdc1 = new TextField("ADC 1");
+  private final TextField tfAdc2 = new TextField("ADC 2");
+  private final CheckboxGroup<String> cbgOutput = new CheckboxGroup<>();
+
+  public EmulatorView() {
+  }
+
+  private String getReviewCount(Category category) {
+    List<Review> reviewsInCategory = ReviewService.getInstance().findReviews(category.getName());
+    int sum = reviewsInCategory.stream().mapToInt(Review::getCount).sum();
+    return Integer.toString(sum);
+  }
+
+  private void updateView() {
+    // List<Category> categories =
+    // CategoryService.getInstance().findCategories(searchField.getValue());
+    // grid.setItems(categories);
+    //
+    // if (searchField.getValue().length() > 0) {
+    // header.setText("Search for “" + searchField.getValue() + "”");
+    // } else {
+    // header.setText("Categories");
+    // }
+  }
+
+  private void saveCategory(Category category, AbstractEditorDialog.Operation operation) {
+    CategoryService.getInstance().saveCategory(category);
+
+    Notification.show("Category successfully " + operation.getNameInText() + "ed.", 3000, Position.BOTTOM_START);
+    updateView();
+  }
+
+  private void deleteCategory(Category category) {
+    List<Review> reviewsInCategory = ReviewService.getInstance().findReviews(category.getName());
+
+    reviewsInCategory.forEach(review -> {
+      review.setCategory(CategoryService.getInstance().getUndefinedCategory());
+      ReviewService.getInstance().saveReview(review);
+    });
+    CategoryService.getInstance().deleteCategory(category);
+
+    Notification.show("Category successfully deleted.", 3000, Position.BOTTOM_START);
+    updateView();
+  }
+}

+ 168 - 0
SPSEmulator-gui/src/main/java/de/mcs/tools/sps/ui/views/emulatorview/EmulatorView2.java

@@ -0,0 +1,168 @@
+/*
+ * Copyright 2000-2017 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package de.mcs.tools.sps.ui.views.emulatorview;
+
+import java.util.List;
+
+import com.vaadin.flow.component.button.Button;
+import com.vaadin.flow.component.checkbox.CheckboxGroup;
+import com.vaadin.flow.component.checkbox.CheckboxGroupVariant;
+import com.vaadin.flow.component.grid.Grid;
+import com.vaadin.flow.component.grid.Grid.SelectionMode;
+import com.vaadin.flow.component.icon.Icon;
+import com.vaadin.flow.component.icon.VaadinIcon;
+import com.vaadin.flow.component.notification.Notification;
+import com.vaadin.flow.component.notification.Notification.Position;
+import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
+import com.vaadin.flow.component.orderedlayout.VerticalLayout;
+import com.vaadin.flow.component.textfield.TextField;
+import com.vaadin.flow.router.PageTitle;
+import com.vaadin.flow.router.Route;
+
+import de.mcs.tools.sps.backend.Category;
+import de.mcs.tools.sps.backend.CategoryService;
+import de.mcs.tools.sps.backend.Review;
+import de.mcs.tools.sps.backend.ReviewService;
+import de.mcs.tools.sps.ui.MainLayout;
+import de.mcs.tools.sps.ui.common.AbstractEditorDialog;
+
+/**
+ * Displays the list of available categories, with a search filter as well as buttons to add a new category or edit
+ * existing ones.
+ */
+@Route(value = "emulator2", layout = MainLayout.class)
+@PageTitle("SPS Emulator")
+public class EmulatorView2 extends VerticalLayout {
+
+  private final Button btStart = new Button(new Icon(VaadinIcon.STEP_FORWARD));
+  private final Button btPause = new Button(new Icon(VaadinIcon.PAUSE));
+  private final Button btStop = new Button(new Icon(VaadinIcon.STOP));
+  private final Grid<Category> grid = new Grid<>();
+
+  private final CheckboxGroup<String> cbgInput = new CheckboxGroup<>();
+  private final CheckboxGroup<String> cbgBtn = new CheckboxGroup<>();
+  private final TextField tfAdc1 = new TextField("ADC 1");
+  private final TextField tfAdc2 = new TextField("ADC 2");
+  private final CheckboxGroup<String> cbgOutput = new CheckboxGroup<>();
+
+  public EmulatorView2() {
+    initView();
+
+    addToolBar();
+    addContent();
+
+    updateView();
+  }
+
+  private void initView() {
+    addClassName("emulator-view");
+    setDefaultHorizontalComponentAlignment(Alignment.STRETCH);
+  }
+
+  private void addToolBar() {
+    HorizontalLayout viewToolbar = new HorizontalLayout();
+    viewToolbar.addClassName("view-toolbar");
+    viewToolbar.add(btStart);
+    viewToolbar.add(btPause);
+    viewToolbar.add(btStop);
+    add(viewToolbar);
+  }
+
+  private void addContent() {
+    HorizontalLayout mainContainer = new HorizontalLayout();
+
+    VerticalLayout sourceContainer = new VerticalLayout();
+    sourceContainer.setClassName("view-container");
+    sourceContainer.setAlignItems(Alignment.STRETCH);
+
+    grid.addColumn(Category::getName).setHeader("No.").setWidth("8em").setResizable(true);
+    grid.addColumn(this::getReviewCount).setHeader("Mnemonic").setWidth("6em");
+    grid.setSelectionMode(SelectionMode.NONE);
+
+    sourceContainer.add(grid);
+    mainContainer.add(sourceContainer);
+
+    VerticalLayout emulatorContainer = new VerticalLayout();
+    HorizontalLayout inputContainer = new HorizontalLayout();
+    inputContainer.setClassName("emulator-view_input-border");
+    cbgInput.setItems("Input 1", "Input 2", "Input 3", "Input 4");
+    cbgInput.addThemeVariants(CheckboxGroupVariant.LUMO_VERTICAL);
+
+    inputContainer.add(cbgInput);
+    // inputContainer.setWidth("50%");
+
+    VerticalLayout secondRowContainer = new VerticalLayout();
+
+    cbgBtn.setItems("SEL", "PRG");
+    cbgBtn.addThemeVariants(CheckboxGroupVariant.LUMO_VERTICAL);
+
+    tfAdc1.setWidth("80px");
+    tfAdc2.setWidth("80px");
+    secondRowContainer.add(cbgBtn);
+    secondRowContainer.add(tfAdc1);
+    secondRowContainer.add(tfAdc2);
+    inputContainer.add(secondRowContainer);
+    emulatorContainer.add(inputContainer);
+
+    HorizontalLayout outputContainer = new HorizontalLayout();
+    cbgOutput.setItems("Output 1", "Output 2", "Output 3", "Output 4");
+    cbgOutput.addThemeVariants(CheckboxGroupVariant.LUMO_VERTICAL);
+
+    outputContainer.add(cbgOutput);
+    emulatorContainer.add(outputContainer);
+
+    mainContainer.add(emulatorContainer);
+    add(mainContainer);
+  }
+
+  private String getReviewCount(Category category) {
+    List<Review> reviewsInCategory = ReviewService.getInstance().findReviews(category.getName());
+    int sum = reviewsInCategory.stream().mapToInt(Review::getCount).sum();
+    return Integer.toString(sum);
+  }
+
+  private void updateView() {
+    // List<Category> categories =
+    // CategoryService.getInstance().findCategories(searchField.getValue());
+    // grid.setItems(categories);
+    //
+    // if (searchField.getValue().length() > 0) {
+    // header.setText("Search for “" + searchField.getValue() + "”");
+    // } else {
+    // header.setText("Categories");
+    // }
+  }
+
+  private void saveCategory(Category category, AbstractEditorDialog.Operation operation) {
+    CategoryService.getInstance().saveCategory(category);
+
+    Notification.show("Category successfully " + operation.getNameInText() + "ed.", 3000, Position.BOTTOM_START);
+    updateView();
+  }
+
+  private void deleteCategory(Category category) {
+    List<Review> reviewsInCategory = ReviewService.getInstance().findReviews(category.getName());
+
+    reviewsInCategory.forEach(review -> {
+      review.setCategory(CategoryService.getInstance().getUndefinedCategory());
+      ReviewService.getInstance().saveReview(review);
+    });
+    CategoryService.getInstance().deleteCategory(category);
+
+    Notification.show("Category successfully deleted.", 3000, Position.BOTTOM_START);
+    updateView();
+  }
+}

+ 141 - 0
SPSEmulator-gui/src/main/java/de/mcs/tools/sps/ui/views/reviewslist/ReviewEditorDialog.java

@@ -0,0 +1,141 @@
+/*
+ * Copyright 2000-2017 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package de.mcs.tools.sps.ui.views.reviewslist;
+
+import java.time.LocalDate;
+import java.util.Objects;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+
+import com.vaadin.flow.component.combobox.ComboBox;
+import com.vaadin.flow.component.datepicker.DatePicker;
+import com.vaadin.flow.component.textfield.TextField;
+import com.vaadin.flow.data.converter.StringToIntegerConverter;
+import com.vaadin.flow.data.validator.DateRangeValidator;
+import com.vaadin.flow.data.validator.IntegerRangeValidator;
+import com.vaadin.flow.data.validator.StringLengthValidator;
+import de.mcs.tools.sps.backend.Category;
+import de.mcs.tools.sps.backend.CategoryService;
+import de.mcs.tools.sps.backend.Review;
+import de.mcs.tools.sps.ui.common.AbstractEditorDialog;
+
+/**
+ * A dialog for editing {@link Review} objects.
+ */
+public class ReviewEditorDialog extends AbstractEditorDialog<Review> {
+
+    private transient CategoryService categoryService = CategoryService
+            .getInstance();
+
+    private ComboBox<Category> categoryBox = new ComboBox<>();
+    private ComboBox<String> scoreBox = new ComboBox<>();
+    private DatePicker lastTasted = new DatePicker();
+    private TextField beverageName = new TextField();
+    private TextField timesTasted = new TextField();
+
+    public ReviewEditorDialog(BiConsumer<Review, Operation> saveHandler,
+            Consumer<Review> deleteHandler) {
+        super("review", saveHandler, deleteHandler);
+
+        createNameField();
+        createCategoryBox();
+        createDatePicker();
+        createTimesField();
+        createScoreBox();
+    }
+
+    private void createScoreBox() {
+        scoreBox.setLabel("Rating");
+        scoreBox.setRequired(true);
+        scoreBox.setAllowCustomValue(false);
+        scoreBox.setItems("1", "2", "3", "4", "5");
+        getFormLayout().add(scoreBox);
+
+        getBinder().forField(scoreBox)
+                .withConverter(new StringToIntegerConverter(0,
+                        "The score should be a number."))
+                .withValidator(new IntegerRangeValidator(
+                        "The tasting count must be between 1 and 5.", 1, 5))
+                .bind(Review::getScore, Review::setScore);
+    }
+
+    private void createDatePicker() {
+        lastTasted.setLabel("Last tasted");
+        lastTasted.setRequired(true);
+        lastTasted.setMax(LocalDate.now());
+        lastTasted.setMin(LocalDate.of(1, 1, 1));
+        lastTasted.setValue(LocalDate.now());
+        getFormLayout().add(lastTasted);
+
+        getBinder().forField(lastTasted)
+                .withValidator(Objects::nonNull,
+                        "The date should be in MM/dd/yyyy format.")
+                .withValidator(new DateRangeValidator(
+                        "The date should be neither Before Christ nor in the future.",
+                        LocalDate.of(1, 1, 1), LocalDate.now()))
+                .bind(Review::getDate, Review::setDate);
+
+    }
+
+    private void createCategoryBox() {
+        categoryBox.setLabel("Category");
+        categoryBox.setRequired(true);
+        categoryBox.setItemLabelGenerator(Category::getName);
+        categoryBox.setAllowCustomValue(false);
+        categoryBox.setItems(categoryService.findCategories(""));
+        getFormLayout().add(categoryBox);
+
+        getBinder().forField(categoryBox)
+                .withValidator(Objects::nonNull,
+                        "The category should be defined.")
+                .bind(Review::getCategory, Review::setCategory);
+    }
+
+    private void createTimesField() {
+        timesTasted.setLabel("Times tasted");
+        timesTasted.setRequired(true);
+        timesTasted.setPattern("[0-9]*");
+        timesTasted.setPreventInvalidInput(true);
+        getFormLayout().add(timesTasted);
+
+        getBinder().forField(timesTasted)
+                .withConverter(
+                        new StringToIntegerConverter(0, "Must enter a number."))
+                .withValidator(new IntegerRangeValidator(
+                        "The tasting count must be between 1 and 99.", 1, 99))
+                .bind(Review::getCount, Review::setCount);
+    }
+
+    private void createNameField() {
+        beverageName.setLabel("Beverage");
+        beverageName.setRequired(true);
+        getFormLayout().add(beverageName);
+
+        getBinder().forField(beverageName)
+                .withConverter(String::trim, String::trim)
+                .withValidator(new StringLengthValidator(
+                        "Beverage name must contain at least 3 printable characters",
+                        3, null))
+                .bind(Review::getName, Review::setName);
+    }
+
+    @Override
+    protected void confirmDelete() {
+        openConfirmationDialog("Delete review",
+                "Are you sure you want to delete the review for “" + getCurrentItem().getName() + "”?", "");
+    }
+
+}

+ 129 - 0
SPSEmulator-gui/src/main/java/de/mcs/tools/sps/ui/views/reviewslist/ReviewsList.java

@@ -0,0 +1,129 @@
+/*
+ * Copyright 2000-2017 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package de.mcs.tools.sps.ui.views.reviewslist;
+
+import java.util.List;
+
+import com.vaadin.flow.component.Tag;
+import com.vaadin.flow.component.button.Button;
+import com.vaadin.flow.component.dependency.HtmlImport;
+import com.vaadin.flow.component.html.H2;
+import com.vaadin.flow.component.html.Span;
+import com.vaadin.flow.component.notification.Notification;
+import com.vaadin.flow.component.notification.Notification.Position;
+import com.vaadin.flow.component.polymertemplate.EventHandler;
+import com.vaadin.flow.component.polymertemplate.Id;
+import com.vaadin.flow.component.polymertemplate.ModelItem;
+import com.vaadin.flow.component.polymertemplate.PolymerTemplate;
+import com.vaadin.flow.component.textfield.TextField;
+import com.vaadin.flow.data.value.ValueChangeMode;
+import com.vaadin.flow.router.PageTitle;
+import com.vaadin.flow.router.Route;
+import com.vaadin.flow.templatemodel.Encode;
+import com.vaadin.flow.templatemodel.TemplateModel;
+
+import de.mcs.tools.sps.backend.Review;
+import de.mcs.tools.sps.backend.ReviewService;
+import de.mcs.tools.sps.ui.MainLayout;
+import de.mcs.tools.sps.ui.common.AbstractEditorDialog;
+import de.mcs.tools.sps.ui.encoders.LocalDateToStringEncoder;
+import de.mcs.tools.sps.ui.encoders.LongToStringEncoder;
+import de.mcs.tools.sps.ui.views.reviewslist.ReviewsList.ReviewsModel;
+
+/**
+ * Displays the list of available categories, with a search filter as well as buttons to add a new category or edit
+ * existing ones.
+ *
+ * Implemented using a simple template.
+ */
+@Route(value = "reviews", layout = MainLayout.class)
+@PageTitle("Review List")
+@Tag("reviews-list")
+@HtmlImport("frontend://src/views/reviewslist/reviews-list.html")
+public class ReviewsList extends PolymerTemplate<ReviewsModel> {
+
+  public interface ReviewsModel extends TemplateModel {
+    @Encode(value = LongToStringEncoder.class, path = "id")
+    @Encode(value = LocalDateToStringEncoder.class, path = "date")
+    @Encode(value = LongToStringEncoder.class, path = "category.id")
+    void setReviews(List<Review> reviews);
+  }
+
+  @Id("search")
+  private TextField search;
+  @Id("newReview")
+  private Button addReview;
+  @Id("header")
+  private H2 header;
+
+  private ReviewEditorDialog reviewForm = new ReviewEditorDialog(this::saveUpdate, this::deleteUpdate);
+
+  public ReviewsList() {
+    search.setPlaceholder("Search reviews");
+    search.addValueChangeListener(e -> updateList());
+    search.setValueChangeMode(ValueChangeMode.EAGER);
+
+    addReview.addClickListener(e -> openForm(new Review(), AbstractEditorDialog.Operation.ADD));
+
+    // Set review button and edit button text from Java
+    getElement().setProperty("reviewButtonText", "New review");
+    getElement().setProperty("editButtonText", "Edit");
+
+    updateList();
+
+  }
+
+  public void saveUpdate(Review review, AbstractEditorDialog.Operation operation) {
+    ReviewService.getInstance().saveReview(review);
+    updateList();
+    Notification.show("Beverage successfully " + operation.getNameInText() + "ed.", 3000, Position.BOTTOM_START);
+  }
+
+  public void deleteUpdate(Review review) {
+    ReviewService.getInstance().deleteReview(review);
+    updateList();
+    Notification.show("Beverage successfully deleted.", 3000, Position.BOTTOM_START);
+  }
+
+  private void updateList() {
+    List<Review> reviews = ReviewService.getInstance().findReviews(search.getValue());
+    if (search.isEmpty()) {
+      header.setText("Reviews");
+      header.add(new Span(reviews.size() + " in total"));
+    } else {
+      header.setText("Search for “" + search.getValue() + "”");
+      if (!reviews.isEmpty()) {
+        header.add(new Span(reviews.size() + " results"));
+      }
+    }
+    getModel().setReviews(reviews);
+  }
+
+  @EventHandler
+  private void edit(@ModelItem Review review) {
+    openForm(review, AbstractEditorDialog.Operation.EDIT);
+  }
+
+  private void openForm(Review review, AbstractEditorDialog.Operation operation) {
+    // Add the form lazily as the UI is not yet initialized when
+    // this view is constructed
+    if (reviewForm.getElement().getParent() == null) {
+      getUI().ifPresent(ui -> ui.add(reviewForm));
+    }
+    reviewForm.open(review, operation);
+  }
+
+}

+ 6 - 0
SPSEmulator-gui/src/main/resources/banner.txt

@@ -0,0 +1,6 @@
+ ____   ____   ____   _____                    _         _                  ____  _   _  ___ 
+/ ___| |  _ \ / ___| | ____| _ __ ___   _   _ | |  __ _ | |_   ___   _ __  / ___|| | | ||_ _|
+\___ \ | |_) |\___ \ |  _|  | '_ ` _ \ | | | || | / _` || __| / _ \ | '__|| |  _ | | | | | | 
+ ___) ||  __/  ___) || |___ | | | | | || |_| || || (_| || |_ | (_) || |   | |_| || |_| | | | 
+|____/ |_|    |____/ |_____||_| |_| |_| \__,_||_| \__,_| \__| \___/ |_|    \____| \___/ |___|
+                                                                                             

+ 270 - 0
SPSEmulator-gui/src/main/webapp/frontend/src/views/emulatorview/emulator-view.html

@@ -0,0 +1,270 @@
+<!--
+  ~ Copyright 2000-2017 Vaadin Ltd.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License"); you may not
+  ~ use this file except in compliance with the License. You may obtain a copy of
+  ~ the License at
+  ~
+  ~ http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+  ~ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+  ~ License for the specific language governing permissions and limitations under
+  ~ the License.
+  -->
+
+<!-- Dependency resources -->
+<link rel="import" href="../../../bower_components/polymer/polymer-element.html">
+<!-- Added Web Component dependencies to make Vaadin Designer preview work -->
+<link rel="import" href="../../../bower_components/vaadin-text-field/src/vaadin-text-field.html">
+<link rel="import" href="../../../bower_components/vaadin-button/src/vaadin-button.html">
+<link rel="import" href="../../../bower_components/iron-icon/iron-icon.html">
+<link rel="import" href="../../../bower_components/vaadin-lumo-styles/badge.html">
+<!-- TODO Needed to show icons in Vaadin Designer preview mode for now, but can be removed soon -->
+<link rel="import" href="../../../bower_components/vaadin-lumo-styles/icons.html">
+
+
+<!-- Defines the reviews-list element -->
+<dom-module id="emulator-view"> <template>
+<style include="lumo-color lumo-typography lumo-badge view-styles">
+:host {
+	display: block;
+}
+
+#header {
+	display: flex;
+	justify-content: space-between;
+	flex-wrap: wrap;
+	align-items: baseline;
+}
+
+/* Subtitle for the header */
+#header span {
+	display: block;
+	font-size: var(- -lumo-font-size-s);
+	font-weight: 400;
+	color: var(- -lumo-secondary-text-color);
+	letter-spacing: 0;
+	margin-top: 0.3em;
+	margin-left: auto;
+	margin-right: 20px;
+}
+
+.review {
+	display: flex;
+	align-items: center;
+	width: 100%;
+	padding: var(- -lumo-space-wide-xl);
+	padding-right: var(- -lumo-space-m);
+	box-sizing: border-box;
+	margin-bottom: 8px;
+	background-color: var(- -lumo-base-color);
+	box-shadow: 0 0 0 1px var(- -lumo-shade-5pct), 0 2px 5px 0
+		var(- -lumo-shade-10pct);
+	border-radius: var(- -lumo-border-radius);
+}
+
+.review__rating {
+	flex: none;
+	align-self: flex-start;
+	margin: 0 1em 0 0;
+	position: relative;
+	cursor: default;
+}
+
+.review__score {
+	display: inline-flex;
+	align-items: center;
+	justify-content: center;
+	border-radius: var(- -lumo-border-radius);
+	font-weight: 600;
+	width: 2.5em;
+	height: 2.5em;
+	margin: 0;
+}
+
+[data-score="1"] {
+	box-shadow: inset 0 0 0 1px var(- -lumo-contrast-10pct);
+}
+
+[data-score="2"] {
+	background-color: var(- -lumo-contrast-20pct);
+}
+
+[data-score="3"] {
+	background-color: var(- -lumo-contrast-40pct);
+}
+
+[data-score="4"] {
+	background-color: var(- -lumo-contrast-60pct);
+	color: var(- -lumo-base-color);
+}
+
+[data-score="5"] {
+	background-color: var(- -lumo-contrast-80pct);
+	color: var(- -lumo-base-color);
+}
+
+.review__count {
+	position: absolute;
+	display: inline-flex;
+	align-items: center;
+	justify-content: center;
+	height: 20px;
+	min-width: 8px;
+	padding: 0 6px;
+	background: var(- -lumo-base-color);
+	color: var(- -lumo-secondary-text-color);
+	top: -10px;
+	left: -10px;
+	border-radius: var(- -lumo-border-radius);
+	margin: 0;
+	font-size: 12px;
+	font-weight: 500;
+	box-shadow: 0 0 0 1px var(- -lumo-contrast-20pct);
+}
+
+.review__count span {
+	width: 0;
+	overflow: hidden;
+	white-space: nowrap;
+}
+
+.review__rating:hover .review__count span {
+	width: auto;
+	margin-left: 0.4em;
+}
+
+.review__details {
+	width: 100%;
+	max-width: calc(100% - 8.5em);
+	flex: auto;
+	line-height: var(- -lumo-line-height-xs);
+	overflow: hidden;
+}
+
+.review__name {
+	margin: 0 0.5em 0 0;
+	white-space: nowrap;
+	overflow: hidden;
+	text-overflow: ellipsis;
+}
+
+.review__category {
+	margin: 0;
+	flex: none;
+}
+
+/* We only expect to have 10 categories at most, after which the colors start to rotate */
+.review__category {
+	color: hsla(calc(340 + 360/ 10 * var(- -category)), 40%, 20%, 1);
+	background-color: hsla(calc(340 + 360/ 10 * var(- -category)), 60%, 50%,
+		0.1);
+}
+
+.review__date {
+	white-space: nowrap;
+	line-height: var(- -lumo-line-height-xs);
+	margin: 0 1em;
+	width: 30%;
+}
+
+.review__date h5 {
+	font-size: var(- -lumo-font-size-s);
+	font-weight: 400;
+	color: var(- -lumo-secondary-text-color);
+	margin: 0;
+}
+
+.review__date p {
+	font-size: var(- -lumo-font-size-s);
+	margin: 0;
+}
+
+.review__edit {
+	align-self: flex-start;
+	flex: none;
+	margin: 0 0 0 auto;
+	width: 5em;
+}
+
+.reviews__no-matches {
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	height: 4em;
+	font-size: 22px;
+	color: var(- -lumo-tertiary-text-color);
+}
+
+/* Small viewport styles */
+@media ( max-width : 500px) {
+	.review {
+		padding: var(- -lumo-space-m);
+		padding-right: var(- -lumo-space-s);
+		flex-wrap: wrap;
+	}
+	.review__date {
+		order: 1;
+		margin-left: 3.5em;
+		margin-top: 0.5em;
+	}
+}
+</style>
+
+Hallo,
+<div class="view-toolbar">
+  <vaadin-button id="btStart" class="view-toolbar__button" theme="primary"> <iron-icon icon="lumo:plus"
+    slot="prefix"></iron-icon> <span>[[reviewButtonText]]</span> </vaadin-button>
+  <vaadin-button id="btPause" class="view-toolbar__button" theme="primary"> <iron-icon icon="lumo:plus"
+    slot="prefix"></iron-icon> <span>[[reviewButtonText]]</span> </vaadin-button>
+  <vaadin-button id="btStop" class="view-toolbar__button" theme="primary"> <iron-icon icon="lumo:plus"
+    slot="prefix"></iron-icon> <span>[[reviewButtonText]]</span> </vaadin-button>
+</div>
+
+<div class="view-container reviews">
+  <h2 id="header"></h2>
+  <template is="dom-if" if="{{!_isEmpty(reviews)}}"> <template is="dom-repeat" items="[[reviews]]">
+  <div class="review">
+    <div class="review__rating">
+      <p class="review__score" data-score$="[[item.score]]">[[item.score]]</p>
+      <p class="review__count">
+        [[item.count]] <span>times tasted</span>
+      </p>
+    </div>
+    <div class="review__details">
+      <h4 class="review__name">[[item.name]]</h4>
+      <template is="dom-if" if="[[item.category]]">
+      <p class="review__category" theme="badge small" style$="-category: [[item.category.id]];">[[item.category.name]]</p>
+      </template>
+      <template is="dom-if" if="[[!item.category]]">
+      <p class="review__category" style="-category: -1;">Undefined</p>
+      </template>
+    </div>
+    <div class="review__date">
+      <h5>Last tasted</h5>
+      <p>[[item.date]]</p>
+    </div>
+    <vaadin-button on-click="edit" class="review__edit" theme="tertiary"> <iron-icon icon="lumo:edit"></iron-icon>
+    <span>[[editButtonText]]</span> </vaadin-button>
+  </div>
+  </template> </template>
+
+  <template is="dom-if" if="{{_isEmpty(reviews)}}">
+  <div class="reviews__no-matches">No matches</div>
+  </template>
+</div>
+</template> 
+<!-- Polymer boilerplate to register the reviews-list element --> <script>
+        class ReviewListElement extends Polymer.Element {
+            static get is() {
+                return 'reviews-list'
+            }
+
+            _isEmpty(array) {
+                return array.length == 0;
+            }
+        }
+        customElements.define(ReviewListElement.is, ReviewListElement);
+    </script> </dom-module>

+ 280 - 0
SPSEmulator-gui/src/main/webapp/frontend/src/views/reviewslist/reviews-list.html

@@ -0,0 +1,280 @@
+<!--
+  ~ Copyright 2000-2017 Vaadin Ltd.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License"); you may not
+  ~ use this file except in compliance with the License. You may obtain a copy of
+  ~ the License at
+  ~
+  ~ http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+  ~ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+  ~ License for the specific language governing permissions and limitations under
+  ~ the License.
+  -->
+
+<!-- Dependency resources -->
+<link rel="import" href="../../../bower_components/polymer/polymer-element.html">
+<!-- Added Web Component dependencies to make Vaadin Designer preview work -->
+<link rel="import" href="../../../bower_components/vaadin-text-field/src/vaadin-text-field.html">
+<link rel="import" href="../../../bower_components/vaadin-button/src/vaadin-button.html">
+<link rel="import" href="../../../bower_components/iron-icon/iron-icon.html">
+<link rel="import" href="../../../bower_components/vaadin-lumo-styles/badge.html">
+<!-- TODO Needed to show icons in Vaadin Designer preview mode for now, but can be removed soon -->
+<link rel="import" href="../../../bower_components/vaadin-lumo-styles/icons.html">
+
+
+<!-- Defines the reviews-list element -->
+<dom-module id="reviews-list">
+    <template>
+        <style include="lumo-color lumo-typography lumo-badge view-styles">
+            :host {
+                display: block;
+            }
+
+            #header {
+                display: flex;
+                justify-content: space-between;
+                flex-wrap: wrap;
+                align-items: baseline;
+            }
+
+            /* Subtitle for the header */
+            #header span {
+                display: block;
+                font-size: var(--lumo-font-size-s);
+                font-weight: 400;
+                color: var(--lumo-secondary-text-color);
+                letter-spacing: 0;
+                margin-top: 0.3em;
+                margin-left: auto;
+                margin-right: 20px;
+            }
+
+            .review {
+                display: flex;
+                align-items: center;
+                width: 100%;
+                padding: var(--lumo-space-wide-xl);
+                padding-right: var(--lumo-space-m);
+                box-sizing: border-box;
+                margin-bottom: 8px;
+                background-color: var(--lumo-base-color);
+                box-shadow: 0 0 0 1px var(--lumo-shade-5pct), 0 2px 5px 0 var(--lumo-shade-10pct);
+                border-radius: var(--lumo-border-radius);
+            }
+
+            .review__rating {
+                flex: none;
+                align-self: flex-start;
+                margin: 0 1em 0 0;
+                position: relative;
+                cursor: default;
+            }
+
+            .review__score {
+                display: inline-flex;
+                align-items: center;
+                justify-content: center;
+                border-radius: var(--lumo-border-radius);
+                font-weight: 600;
+                width: 2.5em;
+                height: 2.5em;
+                margin: 0;
+            }
+
+            [data-score="1"] {
+                box-shadow: inset 0 0 0 1px var(--lumo-contrast-10pct);
+            }
+
+            [data-score="2"] {
+                background-color: var(--lumo-contrast-20pct);
+            }
+
+            [data-score="3"] {
+                background-color: var(--lumo-contrast-40pct);
+            }
+
+            [data-score="4"] {
+                background-color: var(--lumo-contrast-60pct);
+                color: var(--lumo-base-color);
+            }
+
+            [data-score="5"] {
+                background-color: var(--lumo-contrast-80pct);
+                color: var(--lumo-base-color);
+            }
+
+            .review__count {
+                position: absolute;
+                display: inline-flex;
+                align-items: center;
+                justify-content: center;
+                height: 20px;
+                min-width: 8px;
+                padding: 0 6px;
+                background: var(--lumo-base-color);
+                color: var(--lumo-secondary-text-color);
+                top: -10px;
+                left: -10px;
+                border-radius: var(--lumo-border-radius);
+                margin: 0;
+                font-size: 12px;
+                font-weight: 500;
+                box-shadow: 0 0 0 1px var(--lumo-contrast-20pct);
+            }
+
+            .review__count span {
+                width: 0;
+                overflow: hidden;
+                white-space: nowrap;
+            }
+
+            .review__rating:hover .review__count span {
+                width: auto;
+                margin-left: 0.4em;
+            }
+
+            .review__details {
+                width: 100%;
+                max-width: calc(100% - 8.5em);
+                flex: auto;
+                line-height: var(--lumo-line-height-xs);
+                overflow: hidden;
+            }
+
+            .review__name {
+                margin: 0 0.5em 0 0;
+                white-space: nowrap;
+                overflow: hidden;
+                text-overflow: ellipsis;
+            }
+
+            .review__category {
+                margin: 0;
+                flex: none;
+            }
+
+            /* We only expect to have 10 categories at most, after which the colors start to rotate */
+            .review__category {
+                color: hsla(calc(340 + 360 / 10 * var(--category)), 40%, 20%, 1);
+                background-color: hsla(calc(340 + 360 / 10 * var(--category)), 60%, 50%, 0.1);
+            }
+
+            .review__date {
+                white-space: nowrap;
+                line-height: var(--lumo-line-height-xs);
+                margin: 0 1em;
+                width: 30%;
+            }
+
+            .review__date h5 {
+                font-size: var(--lumo-font-size-s);
+                font-weight: 400;
+                color: var(--lumo-secondary-text-color);
+                margin: 0;
+            }
+
+            .review__date p {
+                font-size: var(--lumo-font-size-s);
+                margin: 0;
+            }
+
+            .review__edit {
+                align-self: flex-start;
+                flex: none;
+                margin: 0 0 0 auto;
+                width: 5em;
+            }
+
+            .reviews__no-matches {
+                display: flex;
+                align-items: center;
+                justify-content: center;
+                height: 4em;
+                font-size: 22px;
+                color: var(--lumo-tertiary-text-color);
+            }
+
+            /* Small viewport styles */
+
+            @media (max-width: 500px) {
+                .review {
+                    padding: var(--lumo-space-m);
+                    padding-right: var(--lumo-space-s);
+                    flex-wrap: wrap;
+                }
+
+                .review__date {
+                    order: 1;
+                    margin-left: 3.5em;
+                    margin-top: 0.5em;
+                }
+            }
+
+        </style>
+
+        <div class="view-toolbar">
+            <vaadin-text-field id="search" class="view-toolbar__search-field" autocapitalize=off>
+                <iron-icon icon="lumo:search" slot="prefix"></iron-icon>
+            </vaadin-text-field>
+            <vaadin-button id="newReview" class="view-toolbar__button" theme="primary">
+                <iron-icon icon="lumo:plus" slot="prefix"></iron-icon>
+                <span>[[reviewButtonText]]</span>
+            </vaadin-button>
+        </div>
+
+        <div class="view-container reviews">
+            <h2 id="header"></h2>
+            <template is="dom-if" if="{{!_isEmpty(reviews)}}">
+                <template is="dom-repeat" items="[[reviews]]">
+                    <div class="review">
+                        <div class="review__rating">
+                            <p class="review__score" data-score$="[[item.score]]">[[item.score]]</p>
+                            <p class="review__count">
+                                [[item.count]]
+                                <span>times tasted</span>
+                            </p>
+                        </div>
+                        <div class="review__details">
+                            <h4 class="review__name">[[item.name]]</h4>
+                            <template is="dom-if" if="[[item.category]]">
+                                <p class="review__category" theme="badge small" style$="--category: [[item.category.id]];">[[item.category.name]]</p>
+                            </template>
+                            <template is="dom-if" if="[[!item.category]]">
+                                <p class="review__category" style="--category: -1;">Undefined</p>
+                            </template>
+                        </div>
+                        <div class="review__date">
+                            <h5>Last tasted</h5>
+                            <p>[[item.date]]</p>
+                        </div>
+                        <vaadin-button on-click="edit" class="review__edit" theme="tertiary">
+                            <iron-icon icon="lumo:edit"></iron-icon><span>[[editButtonText]]</span>
+                        </vaadin-button>
+                    </div>
+                </template>
+            </template>
+
+            <template is="dom-if" if="{{_isEmpty(reviews)}}">
+                <div class="reviews__no-matches">No matches</div>
+            </template>
+        </div>
+    </template>
+
+    <!-- Polymer boilerplate to register the reviews-list element -->
+    <script>
+        class ReviewListElement extends Polymer.Element {
+            static get is() {
+                return 'reviews-list'
+            }
+
+            _isEmpty(array) {
+                return array.length == 0;
+            }
+        }
+        customElements.define(ReviewListElement.is, ReviewListElement);
+    </script>
+
+</dom-module>

+ 206 - 0
SPSEmulator-gui/src/main/webapp/frontend/styles/shared-styles.html

@@ -0,0 +1,206 @@
+<!--
+  ~ Copyright 2000-2017 Vaadin Ltd.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License"); you may not
+  ~ use this file except in compliance with the License. You may obtain a copy of
+  ~ the License at
+  ~
+  ~ http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+  ~ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+  ~ License for the specific language governing permissions and limitations under
+  ~ the License.
+  -->
+
+<link rel="import" href="../bower_components/vaadin-lumo-styles/color.html">
+<link rel="import" href="../bower_components/vaadin-lumo-styles/typography.html">
+
+<dom-module id="view-styles">
+    <template>
+        <style>
+            /* Stretch to fill the entire browser viewport while keeping the content constrained to
+            parent element max-width */
+
+            .view-toolbar {
+                display: flex;
+                background-color: var(--lumo-base-color);
+                box-shadow: 0 1px 0 0 var(--lumo-contrast-10pct);
+                margin: 0 calc(-50vw + 50%);
+                padding: 8px calc(50vw - 50% + 16px);
+                position: relative;
+                z-index: 1;
+                flex: none;
+            }
+
+            .view-toolbar__search-field {
+                flex: auto;
+                min-width: 0;
+                margin-right: 16px;
+            }
+            .view-container {
+                flex: auto;
+            }
+        </style>
+    </template>
+</dom-module>
+
+<custom-style>
+    <style include="view-styles">
+        html {
+            height: auto;
+            --main-layout-header-height: 64px;
+            background-color: transparent !important;
+        }
+
+        body {
+            /* Avoid horizontal scrollbars, mainly on IE11 */
+            overflow-x: hidden;
+            background-color: var(--lumo-contrast-5pct);
+        }
+
+        .main-layout {
+            display: flex;
+            flex-direction: column;
+            width: 100%;
+            height: 100%;
+            min-height: 100vh;
+            max-width: 960px;
+            margin: 0 auto;
+        }
+
+        .main-layout__title {
+            font-size: 1em;
+            margin: 0;
+            /* Allow the nav-items to take all the space so they are centered */
+            width: 0;
+            line-height: 1;
+            letter-spacing: -0.02em;
+            font-weight: 500;
+        }
+
+        .main-layout > * {
+            flex: auto;
+        }
+
+        .main-layout__header {
+            display: flex;
+            flex: none;
+            align-items: center;
+            height: var(--main-layout-header-height);
+
+            /* Stretch to fill the entire browser viewport, while keeping the content constrained to
+               parent element max-width */
+            margin: 0 calc(-50vw + 50%);
+            padding: 0 calc(50vw - 50% + 16px);
+
+            background-color: var(--lumo-base-color);
+            box-shadow: 0 1px 0 0 var(--lumo-contrast-5pct);
+        }
+
+        .main-layout__nav {
+            display: flex;
+            flex: 1;
+            justify-content: center;
+        }
+
+        .main-layout__nav-item {
+            display: inline-flex;
+            flex-direction: column;
+            align-items: center;
+            padding: 4px 8px;
+            cursor: pointer;
+            transition: 0.3s color, 0.3s transform;
+            will-change: transform;
+            -webkit-user-select: none;
+            -moz-user-select: none;
+            -ms-user-select: none;
+            user-select: none;
+            font-size: var(--lumo-font-size-s);
+            color: var(--lumo-secondary-text-color);
+            font-weight: 500;
+            line-height: 1.3;
+        }
+
+        .main-layout__nav-item:hover {
+            text-decoration: none;
+        }
+
+        .main-layout__nav-item:not([highlight]):hover {
+            color: inherit;
+        }
+
+        .main-layout__nav-item[highlight] {
+            color: var(--lumo-primary-text-color);
+            cursor: default;
+        }
+
+        .main-layout__nav-item iron-icon {
+            /* Vaadin icons are using a 16x16 grid */
+            padding: 4px;
+            box-sizing: border-box;
+            pointer-events: none;
+        }
+        
+        .emulator-view_input-border {
+           border-radius: 5px;
+           border-style: solid;
+           border-width: 1px;
+        }
+    </style>
+
+    <dom-module id="my-dialog-styles" theme-for="vaadin-dialog-overlay">
+        <template>
+            <style include="lumo-color lumo-typography">
+                h3 {
+                    margin-top: 0;
+                }
+
+                vaadin-form-layout {
+                    max-width: 30em;
+                }
+
+                .buttons {
+                    padding: var(--lumo-space-s) var(--lumo-space-l);
+                    margin: calc(var(--lumo-space-l) * -1);
+                    margin-top: var(--lumo-space-l);
+                    border-top: 1px solid var(--lumo-contrast-10pct);
+                }
+
+                .buttons > :last-child {
+                    margin-left: auto;
+                }
+
+                .buttons > :nth-last-child(2) {
+                    margin-right: var(--lumo-space-m);
+                }
+
+                .confirm-buttons {
+                    justify-content: space-between;
+                    padding: var(--lumo-space-xs) var(--lumo-space-m);
+                    margin-top: var(--lumo-space-m);
+                }
+
+                .has-padding {
+                    padding: 0 var(--lumo-space-l);
+                    margin: 0 calc(var(--lumo-space-l) * -1);
+                }
+
+                .confirm-text {
+                    max-width: 25em;
+                    line-height: var(--lumo-line-height-s);
+                }
+
+                .confirm-text > * {
+                    margin-bottom: 0.6em;
+                }
+
+                .confirm-text div:not(:first-child) {
+                    color: var(--lumo-secondary-text-color);
+                    font-size: var(--lumo-font-size-s);
+                }
+            </style>
+        </template>
+    </dom-module>
+</custom-style>

BIN
SPSEmulator-gui/src/main/webapp/icons/icon.png


+ 1 - 0
.classpath → SPSEmulator-model/.classpath

@@ -32,6 +32,7 @@
 	<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
 		<attributes>
 			<attribute name="maven.pomderived" value="true"/>
+			<attribute name="org.eclipse.jst.component.nondependency" value=""/>
 		</attributes>
 	</classpathentry>
 	<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/5"/>

+ 36 - 0
SPSEmulator-model/.project

@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>SPSEmulator-model</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.wst.common.project.facet.core.builder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.m2e.core.maven2Builder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.wst.validation.validationbuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jem.workbench.JavaEMFNature</nature>
+		<nature>org.eclipse.wst.common.modulecore.ModuleCoreNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+		<nature>org.eclipse.m2e.core.maven2Nature</nature>
+		<nature>org.eclipse.wst.common.project.facet.core.nature</nature>
+	</natures>
+</projectDescription>

+ 0 - 0
examples/Blink.tps → SPSEmulator-model/examples/Blink.tps


+ 155 - 0
SPSEmulator-model/pom.xml

@@ -0,0 +1,155 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<groupId>de.mcs.tools.sps</groupId>
+	<artifactId>SPSEmulator-model</artifactId>
+	<version>0.0.1-SNAPSHOT</version>
+	<description>SPSEmulator-model</description>
+	<name>${project.groupId}:${project.artifactId}</name>
+	<url>http://www.wk-music.de</url>
+	<licenses>
+		<license>
+			<name>The Apache License, Version 2.0</name>
+			<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+		</license>
+	</licenses>
+
+	<developers>
+		<developer>
+			<name>Wilfried Klaas</name>
+			<email>w.klaas@gmx.de</email>
+			<organization>MCS</organization>
+			<organizationUrl>http://www.wk-music.de</organizationUrl>
+		</developer>
+	</developers>
+
+	<properties>
+		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+		<timestamp>${maven.build.timestamp}</timestamp>
+		<maven.build.timestamp.format>dd.mm.yyyy HH:mm</maven.build.timestamp.format>
+		<jackson.version>2.9.8</jackson.version>
+		<jersey.client.version>2.28</jersey.client.version>
+	</properties>
+	<build>
+		<plugins>
+			<plugin>
+				<artifactId>maven-compiler-plugin</artifactId>
+				<version>3.3</version>
+				<configuration>
+					<source>1.8</source>
+					<target>1.8</target>
+				</configuration>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-surefire-plugin</artifactId>
+				<version>2.19.1</version>
+				<dependencies>
+					<dependency>
+						<groupId>org.junit.platform</groupId>
+						<artifactId>junit-platform-surefire-provider</artifactId>
+						<version>1.1.0</version>
+					</dependency>
+					<dependency>
+						<groupId>org.junit.jupiter</groupId>
+						<artifactId>junit-jupiter-engine</artifactId>
+						<version>5.1.0</version>
+					</dependency>
+				</dependencies>
+				<configuration>
+					<skipTests>true</skipTests>
+				</configuration>
+			</plugin>
+<!-- 			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-jar-plugin</artifactId>
+				<version>2.6</version>
+				<configuration>
+					<archive>
+						<manifest>
+							<mainClass>de.mcs.tools.sps.SPSAssembler</mainClass>
+							<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
+							<addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
+						</manifest>
+						<manifestEntries>
+							<Build-Time>${maven.build.timestamp}</Build-Time>
+							<Application-Name>SPSTools</Application-Name>
+							<Application-Update-Id>72</Application-Update-Id>
+							<Application-Update-Url>http\://wkla.no-ip.biz/downloader/version.php?ID\=73</Application-Update-Url>
+							<Application-Url>http\://wkla.no-ip.biz/</Application-Url>
+							<Implementation-Vendor>MCS, Media Computer Software</Implementation-Vendor>
+						</manifestEntries>
+					</archive>
+				</configuration>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-shade-plugin</artifactId>
+				<version>2.4.1</version>
+				<executions>
+					<execution>
+						<phase>package</phase>
+						<goals>
+							<goal>shade</goal>
+						</goals>
+						<configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> 
+							<mainClass></mainClass> </transformer> </transformers> </configuration>
+					</execution>
+				</executions>
+			</plugin>
+ -->			<!-- <plugin> <groupId>com.akathist.maven.plugins.launch4j</groupId> <artifactId>launch4j-maven-plugin</artifactId> <version>1.7.24</version> 
+				<executions> <execution> <id>l4j-clui</id> <phase>package</phase> <goals> <goal>launch4j</goal> </goals> <configuration> 
+				<dontWrapJar>false</dontWrapJar> <headerType>console</headerType> <jar>${project.build.directory}/${project.artifactId}-${project.version}.jar</jar> 
+				<outfile>${project.build.directory}/MCSSPSTools.exe</outfile> <downloadUrl>http://java.com/download</downloadUrl> <classPath> 
+				<mainClass>com.howtodoinjava.ApplicationMain</mainClass> <preCp>anything</preCp> </classPath> <icon>src\main\resources\MSA.ico</icon> 
+				<jre> <path>/jre</path> <minVersion>1.8.0</minVersion> <jdkPreference>jreOnly</jdkPreference> </jre> <versionInfo> <fileVersion>1.0.0.0</fileVersion> 
+				<txtFileVersion>${project.version}</txtFileVersion> <fileDescription>${project.name}</fileDescription> <copyright>2018 MCS</copyright> 
+				<productVersion>1.0.0.0</productVersion> <txtProductVersion>1.0.0.0</txtProductVersion> <productName>${project.name}</productName> 
+				<companyName>MCS</companyName> <internalName>MCSSPSTools</internalName> <originalFilename>MCSSPSTools.exe</originalFilename> 
+				</versionInfo> </configuration> </execution> </executions> </plugin> -->
+		</plugins>
+	</build>
+	<dependencies>
+		<dependency>
+			<groupId>net.sourceforge.jmeasurement2</groupId>
+			<artifactId>MCSUtils</artifactId>
+			<version>1.0.152</version>
+		</dependency>
+		<dependency>
+			<groupId>org.junit.jupiter</groupId>
+			<artifactId>junit-jupiter-api</artifactId>
+			<version>5.1.1</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>net.sourceforge.jmeasurement2</groupId>
+			<artifactId>JMeasurement</artifactId>
+			<version>1.1.225</version>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.commons</groupId>
+			<artifactId>commons-lang3</artifactId>
+			<version>3.4</version>
+		</dependency>
+		<dependency>
+			<groupId>com.fasterxml.jackson.core</groupId>
+			<artifactId>jackson-core</artifactId>
+			<version>${jackson.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>com.fasterxml.jackson.core</groupId>
+			<artifactId>jackson-annotations</artifactId>
+			<version>${jackson.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>com.fasterxml.jackson.core</groupId>
+			<artifactId>jackson-databind</artifactId>
+			<version>${jackson.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>com.fasterxml.jackson.dataformat</groupId>
+			<artifactId>jackson-dataformat-yaml</artifactId>
+			<version>${jackson.version}</version>
+		</dependency>
+	</dependencies>
+</project>

+ 86 - 0
SPSEmulator-model/src/main/java/de/mcs/tools/sps/emulator/model/CommandModel.java

@@ -0,0 +1,86 @@
+package de.mcs.tools.sps.emulator.model;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public class CommandModel {
+
+  public enum COMMAND {
+    START, STOP, NEXT, RESET, COMPILE, NN
+  };
+
+  private Set<COMMAND> availableCommands;
+  private COMMAND actualCommand;
+
+  public CommandModel() {
+    setActualCommand(COMMAND.NN);
+    setAvailableCommands(new HashSet<>());
+    evaluateAvailableCommands(getActualCommand());
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder b = new StringBuilder();
+    b.append(String.format("%s:[available: [%s], actual: %s]", this.getClass().getSimpleName(),
+        Arrays.toString(availableCommands.toArray()), actualCommand.name()));
+    return b.toString();
+  }
+
+  public void evaluateAvailableCommands(COMMAND command) {
+    getAvailableCommands().clear();
+    switch (command) {
+    case START:
+      getAvailableCommands().add(COMMAND.NEXT);
+      getAvailableCommands().add(COMMAND.STOP);
+      getAvailableCommands().add(COMMAND.RESET);
+      break;
+    case STOP:
+      getAvailableCommands().add(COMMAND.START);
+      getAvailableCommands().add(COMMAND.RESET);
+      break;
+    case NEXT:
+      getAvailableCommands().add(COMMAND.STOP);
+      getAvailableCommands().add(COMMAND.RESET);
+      break;
+    case COMPILE:
+    case RESET:
+    case NN:
+      getAvailableCommands().add(COMMAND.START);
+      getAvailableCommands().add(COMMAND.COMPILE);
+      break;
+    default:
+      break;
+    }
+  }
+
+  /**
+   * @return the availableCommands
+   */
+  public Set<COMMAND> getAvailableCommands() {
+    return availableCommands;
+  }
+
+  /**
+   * @param availableCommands
+   *          the availableCommands to set
+   */
+  public void setAvailableCommands(Set<COMMAND> availableCommands) {
+    this.availableCommands = availableCommands;
+  }
+
+  /**
+   * @return the actualCommand
+   */
+  public COMMAND getActualCommand() {
+    return actualCommand;
+  }
+
+  /**
+   * @param actualCommand
+   *          the actualCommand to set
+   */
+  public void setActualCommand(COMMAND actualCommand) {
+    this.actualCommand = actualCommand;
+  }
+}

+ 8 - 4
src/main/java/de/mcs/tools/sps/emulator/emulator/package-info.java → SPSEmulator-model/src/main/java/de/mcs/tools/sps/emulator/model/Hardware.java

@@ -1,10 +1,10 @@
 /**
  * MCS Media Computer Software
- * Copyright 2018 by Wilfried Klaas
+ * Copyright 2019 by Wilfried Klaas
  * Project: SPSEmulator
- * File: package-info.java
+ * File: Hardware.java
  * EMail: W.Klaas@gmx.de
- * Created: 25.11.2018 wklaa_000
+ * Created: 29.01.2019 wklaa_000
  * 
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -19,8 +19,12 @@
  * You should have received a copy of the GNU General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>
  */
+package de.mcs.tools.sps.emulator.model;
+
 /**
  * @author wklaa_000
  *
  */
-package de.mcs.tools.sps.emulator.emulator;
+public enum Hardware {
+  HOLTEK, ATMEGA8, ARDUINOSPS, TINYSPS
+}

+ 176 - 0
SPSEmulator-model/src/main/java/de/mcs/tools/sps/emulator/model/InputModel.java

@@ -0,0 +1,176 @@
+package de.mcs.tools.sps.emulator.model;
+
+public class InputModel {
+
+  boolean input1;
+  boolean input2;
+  boolean input3;
+  boolean input4;
+
+  boolean sel;
+  boolean prg;
+
+  byte adc1;
+  byte adc2;
+  byte rc1;
+  byte rc2;
+
+  @Override
+  public String toString() {
+    StringBuilder b = new StringBuilder();
+    b.append(String.format(
+        "%s:[Din1: %s, Din2: %s, Din3: %s Din4: %s, SEL: %s, PRG: %s, adc1: %d, adc2: %d, rc1: %d, rc2: %d]",
+        this.getClass().getSimpleName(), input1, input2, input3, input4, sel, prg, adc1, adc2, rc1, rc2));
+    return b.toString();
+  }
+
+  /**
+   * @return the input1
+   */
+  public boolean isInput1() {
+    return input1;
+  }
+
+  /**
+   * @param input1
+   *          the input1 to set
+   */
+  public void setInput1(boolean input1) {
+    this.input1 = input1;
+  }
+
+  /**
+   * @return the input2
+   */
+  public boolean isInput2() {
+    return input2;
+  }
+
+  /**
+   * @param input2
+   *          the input2 to set
+   */
+  public void setInput2(boolean input2) {
+    this.input2 = input2;
+  }
+
+  /**
+   * @return the input3
+   */
+  public boolean isInput3() {
+    return input3;
+  }
+
+  /**
+   * @param input3
+   *          the input3 to set
+   */
+  public void setInput3(boolean input3) {
+    this.input3 = input3;
+  }
+
+  /**
+   * @return the input4
+   */
+  public boolean isInput4() {
+    return input4;
+  }
+
+  /**
+   * @param input4
+   *          the input4 to set
+   */
+  public void setInput4(boolean input4) {
+    this.input4 = input4;
+  }
+
+  /**
+   * @return the sel
+   */
+  public boolean isSel() {
+    return sel;
+  }
+
+  /**
+   * @param sel
+   *          the sel to set
+   */
+  public void setSel(boolean sel) {
+    this.sel = sel;
+  }
+
+  /**
+   * @return the prg
+   */
+  public boolean isPrg() {
+    return prg;
+  }
+
+  /**
+   * @param prg
+   *          the prg to set
+   */
+  public void setPrg(boolean prg) {
+    this.prg = prg;
+  }
+
+  /**
+   * @return the adc1
+   */
+  public byte getAdc1() {
+    return adc1;
+  }
+
+  /**
+   * @param adc1
+   *          the adc1 to set
+   */
+  public void setAdc1(byte adc1) {
+    this.adc1 = adc1;
+  }
+
+  /**
+   * @return the adc2
+   */
+  public byte getAdc2() {
+    return adc2;
+  }
+
+  /**
+   * @param adc2
+   *          the adc2 to set
+   */
+  public void setAdc2(byte adc2) {
+    this.adc2 = adc2;
+  }
+
+  /**
+   * @return the rc1
+   */
+  public byte getRc1() {
+    return rc1;
+  }
+
+  /**
+   * @param rc1
+   *          the rc1 to set
+   */
+  public void setRc1(byte rc1) {
+    this.rc1 = rc1;
+  }
+
+  /**
+   * @return the rc2
+   */
+  public byte getRc2() {
+    return rc2;
+  }
+
+  /**
+   * @param rc2
+   *          the rc2 to set
+   */
+  public void setRc2(byte rc2) {
+    this.rc2 = rc2;
+  }
+}

+ 160 - 0
SPSEmulator-model/src/main/java/de/mcs/tools/sps/emulator/model/OutputModel.java

@@ -0,0 +1,160 @@
+package de.mcs.tools.sps.emulator.model;
+
+public class OutputModel {
+
+  boolean output1;
+  boolean output2;
+  boolean output3;
+  boolean output4;
+
+  byte pwm1;
+  byte pwm2;
+  byte servo1;
+  byte servo2;
+
+  byte tone;
+
+  @Override
+  public String toString() {
+    StringBuilder b = new StringBuilder();
+    b.append(String.format(
+        "%s:[Dout1: %s, Dout2: %s, Dout3: %s Dout4: %s, pwm1: %d, pwm2: %d, servo1: %d, servo2: %d, tone: %d]",
+        this.getClass().getSimpleName(), output1, output2, output3, output4, pwm1, pwm2, servo1, servo2, tone));
+    return b.toString();
+  }
+
+  /**
+   * @return the output1
+   */
+  public boolean isOutput1() {
+    return output1;
+  }
+
+  /**
+   * @param output1
+   *          the output1 to set
+   */
+  public void setOutput1(boolean output1) {
+    this.output1 = output1;
+  }
+
+  /**
+   * @return the output2
+   */
+  public boolean isOutput2() {
+    return output2;
+  }
+
+  /**
+   * @param output2
+   *          the output2 to set
+   */
+  public void setOutput2(boolean output2) {
+    this.output2 = output2;
+  }
+
+  /**
+   * @return the output3
+   */
+  public boolean isOutput3() {
+    return output3;
+  }
+
+  /**
+   * @param output3
+   *          the output3 to set
+   */
+  public void setOutput3(boolean output3) {
+    this.output3 = output3;
+  }
+
+  /**
+   * @return the output4
+   */
+  public boolean isOutput4() {
+    return output4;
+  }
+
+  /**
+   * @param output4
+   *          the output4 to set
+   */
+  public void setOutput4(boolean output4) {
+    this.output4 = output4;
+  }
+
+  /**
+   * @return the pwm1
+   */
+  public byte getPwm1() {
+    return pwm1;
+  }
+
+  /**
+   * @param pwm1
+   *          the pwm1 to set
+   */
+  public void setPwm1(byte pwm1) {
+    this.pwm1 = pwm1;
+  }
+
+  /**
+   * @return the pwm2
+   */
+  public byte getPwm2() {
+    return pwm2;
+  }
+
+  /**
+   * @param pwm2
+   *          the pwm2 to set
+   */
+  public void setPwm2(byte pwm2) {
+    this.pwm2 = pwm2;
+  }
+
+  /**
+   * @return the servo1
+   */
+  public byte getServo1() {
+    return servo1;
+  }
+
+  /**
+   * @param servo1
+   *          the servo1 to set
+   */
+  public void setServo1(byte servo1) {
+    this.servo1 = servo1;
+  }
+
+  /**
+   * @return the servo2
+   */
+  public byte getServo2() {
+    return servo2;
+  }
+
+  /**
+   * @param servo2
+   *          the servo2 to set
+   */
+  public void setServo2(byte servo2) {
+    this.servo2 = servo2;
+  }
+
+  /**
+   * @return the tone
+   */
+  public byte getTone() {
+    return tone;
+  }
+
+  /**
+   * @param tone
+   *          the tone to set
+   */
+  public void setTone(byte tone) {
+    this.tone = tone;
+  }
+}

+ 158 - 0
SPSEmulator-model/src/main/java/de/mcs/tools/sps/emulator/model/ProgramModel.java

@@ -0,0 +1,158 @@
+package de.mcs.tools.sps.emulator.model;
+
+import java.io.UnsupportedEncodingException;
+import java.util.Arrays;
+
+import org.apache.commons.lang3.StringUtils;
+
+import de.mcs.utils.SHA256Utils;
+
+public class ProgramModel {
+
+  private Hardware hardware;
+  private String[] source;
+  private String hash;
+  private byte[] bin;
+  private boolean modified;
+  private int actualLine;
+  private int[] bin2SrcLine;
+
+  public ProgramModel() {
+    hardware = Hardware.HOLTEK;
+    modified = false;
+    setActualLine(0);
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder b = new StringBuilder();
+    b.append(String.format("%s:[Hardware: %s, Hash: %s, modified: %s, actualLine: %d, Source: \"%s\", bin: 0x%x ]",
+        this.getClass().getSimpleName(), hardware.name(), hash, modified, actualLine,
+        (source != null) ? source[0] + ((source.length > 1) ? "..." : "") : "", (bin != null) ? bin.length : 0));
+    return b.toString();
+  }
+
+  /**
+   * @return the source
+   */
+  public String[] getSource() {
+    return source;
+  }
+
+  /**
+   * @param source
+   *          the source to set
+   */
+  public void setSource(String[] source) {
+    this.source = source;
+  }
+
+  /**
+   * @return the hash
+   */
+  public String getHash() {
+    return hash;
+  }
+
+  /**
+   * @param hash
+   *          the hash to set
+   */
+  public void setHash(String hash) {
+    this.hash = hash;
+  }
+
+  /**
+   * @return the bin
+   */
+  public byte[] getBin() {
+    return bin;
+  }
+
+  /**
+   * @param bin
+   *          the bin to set
+   */
+  public void setBin(byte[] bin) {
+    this.bin = bin;
+  }
+
+  /**
+   * @return the hardware
+   */
+  public Hardware getHardware() {
+    return hardware;
+  }
+
+  /**
+   * @param hardware
+   *          the hardware to set
+   */
+  public void setHardware(Hardware hardware) {
+    this.hardware = hardware;
+  }
+
+  /**
+   * @return the modified
+   */
+  public boolean isModified() {
+    return modified;
+  }
+
+  /**
+   * @param modified
+   *          the modified to set
+   */
+  public void setModified(boolean modified) {
+    this.modified = modified;
+  }
+
+  /**
+   * @return the actualLine
+   */
+  public int getActualLine() {
+    return actualLine;
+  }
+
+  /**
+   * @param actualLine
+   *          the actualLine to set
+   */
+  public void setActualLine(int actualLine) {
+    this.actualLine = actualLine;
+  }
+
+  public boolean rebuildHash() {
+    if ((source != null) && (source.length > 0)) {
+      String sourceStr = Arrays.toString(source);
+      try {
+        String newHash = SHA256Utils.shaBytesToString(SHA256Utils.shaBytes(sourceStr.getBytes("UTF-8")));
+        if (StringUtils.isEmpty(hash)) {
+          this.hash = newHash;
+          this.setModified(false);
+        } else {
+          this.setModified(true);
+        }
+      } catch (UnsupportedEncodingException e) {
+        e.printStackTrace();
+      }
+    }
+    return isModified();
+  }
+
+  /**
+   * @return the bin2SrcLine
+   */
+  public int[] getBin2SrcLine() {
+    return bin2SrcLine;
+  }
+
+  /**
+   * @param bin2SrcLine
+   *          the bin2SrcLine to set
+   */
+  public void setBin2SrcLine(int[] bin2SrcLine) {
+    this.bin2SrcLine = bin2SrcLine;
+  }
+
+}

+ 174 - 0
SPSEmulator-model/src/main/java/de/mcs/tools/sps/emulator/model/WebSessionModel.java

@@ -0,0 +1,174 @@
+/**
+ * MCS Media Computer Software
+ * Copyright 2019 by Wilfried Klaas
+ * Project: SPSEmulator
+ * File: WebSessionModel.java
+ * EMail: W.Klaas@gmx.de
+ * Created: 29.01.2019 wklaa_000
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+package de.mcs.tools.sps.emulator.model;
+
+/**
+ * @author wklaa_000
+ *
+ */
+public class WebSessionModel {
+
+  private ProgramModel program;
+  private CommandModel command;
+  private InputModel input;
+  private OutputModel output;
+  private WorkingModel work;
+  private String message;
+  private String stacktrace;
+  private boolean error;
+
+  public WebSessionModel() {
+    program = new ProgramModel();
+    command = new CommandModel();
+    input = new InputModel();
+    output = new OutputModel();
+    work = new WorkingModel();
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder b = new StringBuilder();
+    b.append(String.format(
+        "%s:[error: %s, message: %s, stacktrace, %s, program: %s, command: %s, input: %s, output: %s, work: %s]",
+        this.getClass().getSimpleName(), error, message, getStacktrace(), program, command, input, output, work));
+    return b.toString();
+  }
+
+  /**
+   * @return the input
+   */
+  public InputModel getInput() {
+    return input;
+  }
+
+  /**
+   * @param input
+   *          the input to set
+   */
+  public void setInput(InputModel input) {
+    this.input = input;
+  }
+
+  /**
+   * @return the output
+   */
+  public OutputModel getOutput() {
+    return output;
+  }
+
+  /**
+   * @param output
+   *          the output to set
+   */
+  public void setOutput(OutputModel output) {
+    this.output = output;
+  }
+
+  /**
+   * @return the work
+   */
+  public WorkingModel getWork() {
+    return work;
+  }
+
+  /**
+   * @param work
+   *          the work to set
+   */
+  public void setWork(WorkingModel work) {
+    this.work = work;
+  }
+
+  /**
+   * @return the program
+   */
+  public ProgramModel getProgram() {
+    return program;
+  }
+
+  /**
+   * @param program
+   *          the program to set
+   */
+  public void setProgram(ProgramModel program) {
+    this.program = program;
+  }
+
+  /**
+   * @return the command
+   */
+  public CommandModel getCommand() {
+    return command;
+  }
+
+  /**
+   * @param command
+   *          the command to set
+   */
+  public void setCommand(CommandModel command) {
+    this.command = command;
+  }
+
+  /**
+   * @return the message
+   */
+  public String getMessage() {
+    return message;
+  }
+
+  /**
+   * @param message
+   *          the message to set
+   */
+  public void setMessage(String message) {
+    this.message = message;
+  }
+
+  /**
+   * @return the error
+   */
+  public boolean isError() {
+    return error;
+  }
+
+  /**
+   * @param error
+   *          the error to set
+   */
+  public void setError(boolean error) {
+    this.error = error;
+  }
+
+  /**
+   * @return the stacktrace
+   */
+  public String getStacktrace() {
+    return stacktrace;
+  }
+
+  /**
+   * @param stacktrace the stacktrace to set
+   */
+  public void setStacktrace(String stacktrace) {
+    this.stacktrace = stacktrace;
+  }
+}

+ 201 - 0
SPSEmulator-model/src/main/java/de/mcs/tools/sps/emulator/model/WorkingModel.java

@@ -0,0 +1,201 @@
+package de.mcs.tools.sps.emulator.model;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class WorkingModel {
+
+  public enum WORKINGSTATE {
+    NN, EMULATE
+  }
+
+  private byte registerA;
+  private byte registerB;
+  private byte registerC;
+  private byte registerD;
+  private byte registerE;
+  private byte registerF;
+
+  private byte page;
+  private short raddress;
+  private short address;
+
+  private List<Integer> stack = new ArrayList<>();
+  private WORKINGSTATE workingstate;
+
+  @Override
+  public String toString() {
+    StringBuilder b = new StringBuilder();
+    b.append(String.format(
+        "%s:[registerA: 0x%02x, registerB: 0x%02x, registerC: 0x%02x, registerD: 0x%02x, registerE: 0x%02x, registerF: 0x%02x, page: 0x%02x, address: 0x%04x, raddress: 0x%04x, stack: %d ]",
+        this.getClass().getSimpleName(), registerA, registerB, registerC, registerD, registerE, registerF, page,
+        address, raddress, stack.size()));
+    return b.toString();
+  }
+
+  /**
+   * @return the registerA
+   */
+  public byte getRegisterA() {
+    return registerA;
+  }
+
+  /**
+   * @param registerA
+   *          the registerA to set
+   */
+  public void setRegisterA(byte registerA) {
+    this.registerA = registerA;
+  }
+
+  /**
+   * @return the registerB
+   */
+  public byte getRegisterB() {
+    return registerB;
+  }
+
+  /**
+   * @param registerB
+   *          the registerB to set
+   */
+  public void setRegisterB(byte registerB) {
+    this.registerB = registerB;
+  }
+
+  /**
+   * @return the registerC
+   */
+  public byte getRegisterC() {
+    return registerC;
+  }
+
+  /**
+   * @param registerC
+   *          the registerC to set
+   */
+  public void setRegisterC(byte registerC) {
+    this.registerC = registerC;
+  }
+
+  /**
+   * @return the registerD
+   */
+  public byte getRegisterD() {
+    return registerD;
+  }
+
+  /**
+   * @param registerD
+   *          the registerD to set
+   */
+  public void setRegisterD(byte registerD) {
+    this.registerD = registerD;
+  }
+
+  /**
+   * @return the page
+   */
+  public byte getPage() {
+    return page;
+  }
+
+  /**
+   * @param page
+   *          the page to set
+   */
+  public void setPage(byte page) {
+    this.page = page;
+  }
+
+  /**
+   * @return the raddress
+   */
+  public short getRaddress() {
+    return raddress;
+  }
+
+  /**
+   * @param raddress
+   *          the raddress to set
+   */
+  public void setRaddress(short raddress) {
+    this.raddress = raddress;
+  }
+
+  /**
+   * @return the address
+   */
+  public short getAddress() {
+    return address;
+  }
+
+  /**
+   * @param address
+   *          the address to set
+   */
+  public void setAddress(short address) {
+    this.address = address;
+  }
+
+  /**
+   * @return the registerE
+   */
+  public byte getRegisterE() {
+    return registerE;
+  }
+
+  /**
+   * @param registerE
+   *          the registerE to set
+   */
+  public void setRegisterE(byte registerE) {
+    this.registerE = registerE;
+  }
+
+  /**
+   * @return the registerF
+   */
+  public byte getRegisterF() {
+    return registerF;
+  }
+
+  /**
+   * @param registerF
+   *          the registerF to set
+   */
+  public void setRegisterF(byte registerF) {
+    this.registerF = registerF;
+  }
+
+  /**
+   * @return the stack
+   */
+  public List<Integer> getStack() {
+    return stack;
+  }
+
+  /**
+   * @param stack
+   *          the stack to set
+   */
+  public void setStack(List<Integer> stack) {
+    this.stack = stack;
+  }
+
+  /**
+   * @return the workingstate
+   */
+  public WORKINGSTATE getWorkingstate() {
+    return workingstate;
+  }
+
+  /**
+   * @param workingstate
+   *          the workingstate to set
+   */
+  public void setWorkingstate(WORKINGSTATE workingstate) {
+    this.workingstate = workingstate;
+  }
+
+}

+ 54 - 0
SPSEmulator-model/src/main/java/de/mcs/utils/JacksonUtils.java

@@ -0,0 +1,54 @@
+package de.mcs.utils;
+
+
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
+
+/**
+ * @author wklaa_000
+ *
+ */
+public class JacksonUtils {
+
+  private static ObjectMapper ymlObjectMapper;
+
+  public static ObjectMapper getYmlMapper() {
+    if (ymlObjectMapper == null) {
+      ymlObjectMapper = new ObjectMapper(new YAMLFactory());
+      ymlObjectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+      ymlObjectMapper.configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false);
+      ymlObjectMapper.setSerializationInclusion(Include.NON_NULL);
+    }
+    return ymlObjectMapper;
+  }
+
+  private static ObjectMapper jsonObjectMapper;
+
+  public static ObjectMapper getJsonMapper() {
+    if (jsonObjectMapper == null) {
+      jsonObjectMapper = new ObjectMapper();
+      jsonObjectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+      jsonObjectMapper.configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false);
+      jsonObjectMapper.setSerializationInclusion(Include.NON_NULL);
+    }
+    return jsonObjectMapper;
+  }
+
+  private static class StringMessage {
+    @JsonProperty
+    private String message;
+
+    public StringMessage(String message) {
+      this.message = message;
+    }
+  }
+
+  public static String messageToJson(String message) throws JsonProcessingException {
+    return getJsonMapper().writeValueAsString(new StringMessage(message));
+  }
+}

+ 8 - 0
SPSEmulator-model/src/main/java/de/mcs/utils/package-info.java

@@ -0,0 +1,8 @@
+/**
+ * 
+ */
+/**
+ * @author w.klaas
+ *
+ */
+package de.mcs.utils;

+ 70 - 0
SPSEmulator-model/src/test/java/de/mcs/tools/sps/emulator/model/testWebSessionModel.java

@@ -0,0 +1,70 @@
+/**
+ * MCS Media Computer Software
+ * Copyright 2019 by Wilfried Klaas
+ * Project: SPSEmulator
+ * File: testWebSessionModel.java
+ * EMail: W.Klaas@gmx.de
+ * Created: 29.01.2019 wklaa_000
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+package de.mcs.tools.sps.emulator.model;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.List;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import de.mcs.utils.JacksonUtils;
+
+/**
+ * @author wklaa_000
+ *
+ */
+class testWebSessionModel {
+
+  /**
+   * @throws java.lang.Exception
+   */
+  @BeforeEach
+  void setUp() throws Exception {
+  }
+
+  @Test
+  void test() throws IOException {
+    WebSessionModel webSessionModel = new WebSessionModel();
+
+    List<String> allLines = Files.readAllLines(new File("examples/Blink.tps").toPath());
+    webSessionModel.getProgram().setSource(allLines.toArray(new String[0]));
+
+    String modelJson = JacksonUtils.getJsonMapper().writeValueAsString(webSessionModel);
+    System.out.println(modelJson);
+    System.out.printf("size: %d\r\n", modelJson.length());
+
+    WebSessionModel sessionModel = JacksonUtils.getJsonMapper().readValue(modelJson, WebSessionModel.class);
+
+    String newModelJson = JacksonUtils.getJsonMapper().writeValueAsString(sessionModel);
+    System.out.println(newModelJson);
+    System.out.printf("size: %d\r\n", newModelJson.length());
+  }
+
+  @Test
+  void testme() {
+    int value = 2;
+    System.out.printf("0x%04x \r\n", value);
+  }
+}

+ 37 - 0
SPSEmulator-model/src/test/resources/test.tps

@@ -0,0 +1,37 @@
+.macro blink
+PORT #0B0101
+WAIT 200ms
+PORT #0B1010
+WAIT 200ms
+.endmacro
+
+:loop
+.blink
+RJMP :loop
+/* 
+Kommentar über mehrere Zeilen
+*/
+
+.macro macro1 output time
+PORT output
+WAIT time
+PORT #0x00
+WAIT time
+.endmacro
+
+;.include macro_blink
+:loop1
+.macro1 #0x0f 200ms
+
+PORT #0x0F ;Zeilenkommentar
+WAIT 200ms
+PORT #0x00
+WAIT 200ms
+RJMP :loop1
+
+;DFSB 1
+PORT #0x0F ;Zeilenkommentar
+WAIT 200ms
+PORT #0x00
+WAIT 200ms
+RTR

+ 40 - 0
SPSEmulator-service/.classpath

@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" output="target/classes" path="src/main/java">
+		<attributes>
+			<attribute name="optional" value="true"/>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
+		<attributes>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="src" output="target/test-classes" path="src/test/java">
+		<attributes>
+			<attribute name="test" value="true"/>
+			<attribute name="optional" value="true"/>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources">
+		<attributes>
+			<attribute name="test" value="true"/>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
+		<attributes>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
+		<attributes>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/5"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/SPSEmulator-model"/>
+	<classpathentry kind="output" path="target/classes"/>
+</classpath>

+ 0 - 0
.project → SPSEmulator-service/.project


+ 0 - 0
LICENSE → SPSEmulator-service/LICENSE


+ 108 - 0
SPSEmulator-service/Pop.hex

@@ -0,0 +1,108 @@
+:08000000506F700000000000C9
+:080008000000000000000145AA
+:0800100051310000000000F076
+:08001800447276310000000083
+:08002000C343687231000000C7
+:0800280000CC62746E335F6EC0
+:080030006E000062746E345F83
+:080038006E6E000062746E356B
+:080040005F6E6E00003D000040
+:080048000000000000000000B0
+:080050000000000000000000A8
+:080058000000000000000000A0
+:08006000000000000000000098
+:08006800000000000000000090
+:08007000000000000000000088
+:08007800000000000000000080
+:08008000000000000000000078
+:08008800000000000000000070
+:08009000000000000000000068
+:08009800000000000000000060
+:0800A000000000000000000058
+:0800A800000000000000000050
+:0800B000000000000000000048
+:0800B800000000000000000040
+:0800C000000000000000000038
+:0800C800000000000000000030
+:0800D000000000000000000028
+:0800D800000000000000000020
+:0800E000000000000000000018
+:0800E800000000000000000010
+:0800F000000000000000000008
+:0800F800000000000000000000
+:080100000000000000000000F7
+:080108000000000000000000EF
+:080110000000000000000000E7
+:080118000000000000000000DF
+:080120000000000000000000D7
+:080128000000000000000000CF
+:080130000000000000000000C7
+:080138000000000000000000BF
+:080140000000000000000000B7
+:080148000000000000000000AF
+:080150000000000000000000A7
+:0801580000000000000000009F
+:08016000000000000000000097
+:0801680000000000000000008F
+:08017000000000000000000087
+:0801780000000000000000007F
+:08018000000000000000000077
+:0801880000000000000000006F
+:08019000000000000000000067
+:0801980000000000000000005F
+:0801A000000000000000000057
+:0801A80000000000000000004F
+:0801B000000000000000000047
+:0801B80000000000000000003F
+:0801C000000000000000000037
+:0801C80000000000000000002F
+:0801D000000000000000000027
+:0801D80000000000000000001F
+:0801E000000000000000000017
+:0801E80000000000000000000F
+:0801F000000000000000000007
+:0801F8000000000000000000FF
+:080200000000000000000000F6
+:080208000000000000000000EE
+:080210000000000000000000E6
+:080218000000000000000000DE
+:080220000000000000000000D6
+:080228000000000000000000CE
+:080230000000000000000000C6
+:080238000000000000000000BE
+:080240000000000000000000B6
+:080248000000000000000000AE
+:080250000000000000000000A6
+:0802580000000000000000009E
+:08026000000000000000000096
+:0802680000000000000000008E
+:08027000000000000000000086
+:0802780000000000000000007E
+:08028000000000000000000076
+:0802880000000000000000006E
+:08029000000000000000000066
+:0802980000000000000000005E
+:0802A000000000000000000056
+:0802A80000000000000000004E
+:0802B000000000000000000046
+:0802B80000000000000000003E
+:0802C000000000000000000036
+:0802C80000000000000000002E
+:0802D000000000000000000026
+:0802D80000000000000000001E
+:0802E000000000000000000016
+:0802E80000000000000000000E
+:0802F000000000000000000006
+:0802F8000000000000000000FE
+:080300000000000000000000F5
+:080308000000000000000000ED
+:080310000000000000000000E5
+:080318000000000000000000DD
+:080320000000000000000000D5
+:080328000000000000000000CD
+:080330000000000000000000C5
+:080338000000000000000000BD
+:080340000000000000000000B5
+:080348000000000000000000AD
+:07035000000000000000FFA7
+:00000001FF

+ 108 - 0
SPSEmulator-service/Rock.hex

@@ -0,0 +1,108 @@
+:08000000526F636B0000000069
+:08000800000000006400014546
+:0800100051300000000000C3A4
+:08001800447276300000000084
+:08002000CC43687230000000BF
+:0800280000F043687231000092
+:080030000000F0436872320089
+:08003800000000F04368723380
+:0800400000000000F01200B006
+:080048000801B00902B00B032E
+:080050000000000000000000A8
+:080058000000000000000000A0
+:08006000000000000000000098
+:08006800000000000000000090
+:08007000000000000000001474
+:08007800B1097FB10B0000008B
+:08008000000000000000000078
+:08008800000000000000000070
+:08009000000000000000000068
+:08009800000000000000000060
+:0800A000000000000000000058
+:0800A80022B1097FB10B000039
+:0800B000000000000000000048
+:0800B800000000000000000040
+:0800C000000000000000000038
+:0800C800000000000000000030
+:0800D000000000000000000028
+:0800D8000044D100000000000B
+:0800E000000000000000000018
+:0800E800000000000000000010
+:0800F000000000000000000008
+:0800F800000000000000000000
+:080100000000000000000000F7
+:08010800000054D000000000CB
+:080110000000000000000000E7
+:080118000000000000000000DF
+:080120000000000000000000D7
+:080128000000000000000000CF
+:080130000000000000000000C7
+:0801380000000077B11E7FB149
+:080140001F8000000000000018
+:080148000000000000000000AF
+:080150000000000000000000A7
+:0801580000000000000000009F
+:08016000000000000000000097
+:0801680000000000000000008F
+:08017000000000000000000087
+:0801780000000000000000007F
+:08018000000000000000000077
+:0801880000000000000000006F
+:08019000000000000000000067
+:0801980000000000000000005F
+:0801A000000000000000000057
+:0801A80000000000000000004F
+:0801B000000000000000000047
+:0801B80000000000000000003F
+:0801C000000000000000000037
+:0801C80000000000000000002F
+:0801D000000000000000000027
+:0801D80000000000000000001F
+:0801E000000000000000000017
+:0801E80000000000000000000F
+:0801F000000000000000000007
+:0801F8000000000000000000FF
+:080200000000000000000000F6
+:080208000000000000000000EE
+:080210000000000000000000E6
+:080218000000000000000000DE
+:080220000000000000000000D6
+:080228000000000000000000CE
+:080230000000000000000000C6
+:080238000000000000000000BE
+:080240000000000000000000B6
+:080248000000000000000000AE
+:080250000000000000000000A6
+:0802580000000000000000009E
+:08026000000000000000000096
+:0802680000000000000000008E
+:08027000000000000000000086
+:0802780000000000000000007E
+:08028000000000000000000076
+:0802880000000000000000006E
+:08029000000000000000000066
+:0802980000000000000000005E
+:0802A000000000000000000056
+:0802A80000000000000000004E
+:0802B000000000000000000046
+:0802B80000000000000000003E
+:0802C000000000000000000036
+:0802C80000000000000000002E
+:0802D000000000000000000026
+:0802D80000000000000000001E
+:0802E000000000000000000016
+:0802E80000000000000000000E
+:0802F000000000000000000006
+:0802F8000000000000000000FE
+:080300000000000000000000F5
+:080308000000000000000000ED
+:080310000000000000000000E5
+:080318000000000000000000DD
+:080320000000000000000000D5
+:080328000000000000000000CD
+:080330000000000000000000C5
+:080338000000000000000000BD
+:080340000000000000000000B5
+:080348000000000000000000AD
+:07035000000000000000FFA7
+:00000001FF

+ 0 - 0
SimpleServo.tps → SPSEmulator-service/SimpleServo.tps


+ 108 - 0
SPSEmulator-service/Soul.hex

@@ -0,0 +1,108 @@
+:08000000536F756C0000000055
+:0800080000000000FF000145AB
+:0800100051320000000000CC99
+:08001800447276320000000082
+:08002000F04368723200000099
+:0800280000C362746E335F6EC9
+:080030006E000062746E345F83
+:080038006E6E000062746E356B
+:080040005F6E6E00003E00003F
+:080048000000000000000000B0
+:080050000000000000000000A8
+:080058000000000000000000A0
+:08006000000000000000000098
+:08006800000000000000000090
+:08007000000000000000000088
+:08007800000000000000000080
+:08008000000000000000000078
+:08008800000000000000000070
+:08009000000000000000000068
+:08009800000000000000000060
+:0800A000000000000000000058
+:0800A800000000000000000050
+:0800B000000000000000000048
+:0800B800000000000000000040
+:0800C000000000000000000038
+:0800C800000000000000000030
+:0800D000000000000000000028
+:0800D800000000000000000020
+:0800E000000000000000000018
+:0800E800000000000000000010
+:0800F000000000000000000008
+:0800F800000000000000000000
+:080100000000000000000000F7
+:080108000000000000000000EF
+:080110000000000000000000E7
+:080118000000000000000000DF
+:080120000000000000000000D7
+:080128000000000000000000CF
+:080130000000000000000000C7
+:080138000000000000000000BF
+:080140000000000000000000B7
+:080148000000000000000000AF
+:080150000000000000000000A7
+:0801580000000000000000009F
+:08016000000000000000000097
+:0801680000000000000000008F
+:08017000000000000000000087
+:0801780000000000000000007F
+:08018000000000000000000077
+:0801880000000000000000006F
+:08019000000000000000000067
+:0801980000000000000000005F
+:0801A000000000000000000057
+:0801A80000000000000000004F
+:0801B000000000000000000047
+:0801B80000000000000000003F
+:0801C000000000000000000037
+:0801C80000000000000000002F
+:0801D000000000000000000027
+:0801D80000000000000000001F
+:0801E000000000000000000017
+:0801E80000000000000000000F
+:0801F000000000000000000007
+:0801F8000000000000000000FF
+:080200000000000000000000F6
+:080208000000000000000000EE
+:080210000000000000000000E6
+:080218000000000000000000DE
+:080220000000000000000000D6
+:080228000000000000000000CE
+:080230000000000000000000C6
+:080238000000000000000000BE
+:080240000000000000000000B6
+:080248000000000000000000AE
+:080250000000000000000000A6
+:0802580000000000000000009E
+:08026000000000000000000096
+:0802680000000000000000008E
+:08027000000000000000000086
+:0802780000000000000000007E
+:08028000000000000000000076
+:0802880000000000000000006E
+:08029000000000000000000066
+:0802980000000000000000005E
+:0802A000000000000000000056
+:0802A80000000000000000004E
+:0802B000000000000000000046
+:0802B80000000000000000003E
+:0802C000000000000000000036
+:0802C80000000000000000002E
+:0802D000000000000000000026
+:0802D80000000000000000001E
+:0802E000000000000000000016
+:0802E80000000000000000000E
+:0802F000000000000000000006
+:0802F8000000000000000000FE
+:080300000000000000000000F5
+:080308000000000000000000ED
+:080310000000000000000000E5
+:080318000000000000000000DD
+:080320000000000000000000D5
+:080328000000000000000000CD
+:080330000000000000000000C5
+:080338000000000000000000BD
+:080340000000000000000000B5
+:080348000000000000000000AD
+:07035000000000000000FFA7
+:00000001FF

+ 34 - 0
SPSEmulator-service/examples/Blink.hex

@@ -0,0 +1,34 @@
+:0800000015271A27341F2710F8
+:08000800271F271027380000F0
+:080010000000000000000000E8
+:080018000000000000000000E0
+:080020000000000000000000D8
+:080028000000000000000000D0
+:080030000000000000000000C8
+:080038000000000000000000C0
+:080040000000000000000000B8
+:080048000000000000000000B0
+:080050000000000000000000A8
+:080058000000000000000000A0
+:08006000000000000000000098
+:08006800000000000000000090
+:08007000000000000000000088
+:08007800000000000000000080
+:08008000000000000000000078
+:08008800000000000000000070
+:08009000000000000000000068
+:08009800000000000000000060
+:0800A000000000000000000058
+:0800A800000000000000000050
+:0800B000000000000000000048
+:0800B800000000000000000040
+:0800C000000000000000000038
+:0800C800000000000000000030
+:0800D000000000000000000028
+:0800D800000000000000000020
+:0800E000000000000000000018
+:0800E800000000000000000010
+:0800F000000000000000000008
+:0800F800000000000000000000
+:06010000E81F271027E0F9
+:00000001FF

+ 37 - 0
SPSEmulator-service/examples/Blink.tps

@@ -0,0 +1,37 @@
+.macro blink
+PORT #0B0101
+WAIT 200ms
+PORT #0B1010
+WAIT 200ms
+.endmacro
+
+:loop
+.blink
+RJMP :loop
+/* 
+Kommentar über mehrere Zeilen
+*/
+
+.macro macro1 output time
+PORT output
+WAIT time
+PORT #0x00
+WAIT time
+.endmacro
+
+.include macro_blink
+:loop1
+.macro1 #0x0f 200ms
+
+PORT #0x0F ;Zeilenkommentar
+WAIT 200ms
+PORT #0x00
+WAIT 200ms
+RJMP :loop1
+
+DFSB 1
+PORT #0x0F ;Zeilenkommentar
+WAIT 200ms
+PORT #0x00
+WAIT 200ms
+RTR

+ 263 - 0
SPSEmulator-service/examples/Blink.txt

@@ -0,0 +1,263 @@
+Addr   BD   Befehl   Daten     Kommentar
+0x000  15   000X     0X0X      PORT #0B0101
+0x001  27   00X0     0XXX      WAIT 200ms
+0x002  1a   000X     X0X0      PORT #0B1010
+0x003  27   00X0     0XXX      WAIT 200ms
+0x004  34   00XX     0X00      RJMP 4
+0x005  1f   000X     XXXX      PORT #0x0f
+0x006  27   00X0     0XXX      WAIT 200ms
+0x007  10   000X     0000      PORT #0x00
+0x008  27   00X0     0XXX      WAIT 200ms
+0x009  1f   000X     XXXX      PORT #0x0F
+0x00a  27   00X0     0XXX      WAIT 200ms
+0x00b  10   000X     0000      PORT #0x00
+0x00c  27   00X0     0XXX      WAIT 200ms
+0x00d  38   00XX     X000      RJMP 8
+0x00e  00   0000     0000       
+0x00f  00   0000     0000       
+0x010  00   0000     0000       
+0x011  00   0000     0000       
+0x012  00   0000     0000       
+0x013  00   0000     0000       
+0x014  00   0000     0000       
+0x015  00   0000     0000       
+0x016  00   0000     0000       
+0x017  00   0000     0000       
+0x018  00   0000     0000       
+0x019  00   0000     0000       
+0x01a  00   0000     0000       
+0x01b  00   0000     0000       
+0x01c  00   0000     0000       
+0x01d  00   0000     0000       
+0x01e  00   0000     0000       
+0x01f  00   0000     0000       
+0x020  00   0000     0000       
+0x021  00   0000     0000       
+0x022  00   0000     0000       
+0x023  00   0000     0000       
+0x024  00   0000     0000       
+0x025  00   0000     0000       
+0x026  00   0000     0000       
+0x027  00   0000     0000       
+0x028  00   0000     0000       
+0x029  00   0000     0000       
+0x02a  00   0000     0000       
+0x02b  00   0000     0000       
+0x02c  00   0000     0000       
+0x02d  00   0000     0000       
+0x02e  00   0000     0000       
+0x02f  00   0000     0000       
+0x030  00   0000     0000       
+0x031  00   0000     0000       
+0x032  00   0000     0000       
+0x033  00   0000     0000       
+0x034  00   0000     0000       
+0x035  00   0000     0000       
+0x036  00   0000     0000       
+0x037  00   0000     0000       
+0x038  00   0000     0000       
+0x039  00   0000     0000       
+0x03a  00   0000     0000       
+0x03b  00   0000     0000       
+0x03c  00   0000     0000       
+0x03d  00   0000     0000       
+0x03e  00   0000     0000       
+0x03f  00   0000     0000       
+0x040  00   0000     0000       
+0x041  00   0000     0000       
+0x042  00   0000     0000       
+0x043  00   0000     0000       
+0x044  00   0000     0000       
+0x045  00   0000     0000       
+0x046  00   0000     0000       
+0x047  00   0000     0000       
+0x048  00   0000     0000       
+0x049  00   0000     0000       
+0x04a  00   0000     0000       
+0x04b  00   0000     0000       
+0x04c  00   0000     0000       
+0x04d  00   0000     0000       
+0x04e  00   0000     0000       
+0x04f  00   0000     0000       
+0x050  00   0000     0000       
+0x051  00   0000     0000       
+0x052  00   0000     0000       
+0x053  00   0000     0000       
+0x054  00   0000     0000       
+0x055  00   0000     0000       
+0x056  00   0000     0000       
+0x057  00   0000     0000       
+0x058  00   0000     0000       
+0x059  00   0000     0000       
+0x05a  00   0000     0000       
+0x05b  00   0000     0000       
+0x05c  00   0000     0000       
+0x05d  00   0000     0000       
+0x05e  00   0000     0000       
+0x05f  00   0000     0000       
+0x060  00   0000     0000       
+0x061  00   0000     0000       
+0x062  00   0000     0000       
+0x063  00   0000     0000       
+0x064  00   0000     0000       
+0x065  00   0000     0000       
+0x066  00   0000     0000       
+0x067  00   0000     0000       
+0x068  00   0000     0000       
+0x069  00   0000     0000       
+0x06a  00   0000     0000       
+0x06b  00   0000     0000       
+0x06c  00   0000     0000       
+0x06d  00   0000     0000       
+0x06e  00   0000     0000       
+0x06f  00   0000     0000       
+0x070  00   0000     0000       
+0x071  00   0000     0000       
+0x072  00   0000     0000       
+0x073  00   0000     0000       
+0x074  00   0000     0000       
+0x075  00   0000     0000       
+0x076  00   0000     0000       
+0x077  00   0000     0000       
+0x078  00   0000     0000       
+0x079  00   0000     0000       
+0x07a  00   0000     0000       
+0x07b  00   0000     0000       
+0x07c  00   0000     0000       
+0x07d  00   0000     0000       
+0x07e  00   0000     0000       
+0x07f  00   0000     0000       
+0x080  00   0000     0000       
+0x081  00   0000     0000       
+0x082  00   0000     0000       
+0x083  00   0000     0000       
+0x084  00   0000     0000       
+0x085  00   0000     0000       
+0x086  00   0000     0000       
+0x087  00   0000     0000       
+0x088  00   0000     0000       
+0x089  00   0000     0000       
+0x08a  00   0000     0000       
+0x08b  00   0000     0000       
+0x08c  00   0000     0000       
+0x08d  00   0000     0000       
+0x08e  00   0000     0000       
+0x08f  00   0000     0000       
+0x090  00   0000     0000       
+0x091  00   0000     0000       
+0x092  00   0000     0000       
+0x093  00   0000     0000       
+0x094  00   0000     0000       
+0x095  00   0000     0000       
+0x096  00   0000     0000       
+0x097  00   0000     0000       
+0x098  00   0000     0000       
+0x099  00   0000     0000       
+0x09a  00   0000     0000       
+0x09b  00   0000     0000       
+0x09c  00   0000     0000       
+0x09d  00   0000     0000       
+0x09e  00   0000     0000       
+0x09f  00   0000     0000       
+0x0a0  00   0000     0000       
+0x0a1  00   0000     0000       
+0x0a2  00   0000     0000       
+0x0a3  00   0000     0000       
+0x0a4  00   0000     0000       
+0x0a5  00   0000     0000       
+0x0a6  00   0000     0000       
+0x0a7  00   0000     0000       
+0x0a8  00   0000     0000       
+0x0a9  00   0000     0000       
+0x0aa  00   0000     0000       
+0x0ab  00   0000     0000       
+0x0ac  00   0000     0000       
+0x0ad  00   0000     0000       
+0x0ae  00   0000     0000       
+0x0af  00   0000     0000       
+0x0b0  00   0000     0000       
+0x0b1  00   0000     0000       
+0x0b2  00   0000     0000       
+0x0b3  00   0000     0000       
+0x0b4  00   0000     0000       
+0x0b5  00   0000     0000       
+0x0b6  00   0000     0000       
+0x0b7  00   0000     0000       
+0x0b8  00   0000     0000       
+0x0b9  00   0000     0000       
+0x0ba  00   0000     0000       
+0x0bb  00   0000     0000       
+0x0bc  00   0000     0000       
+0x0bd  00   0000     0000       
+0x0be  00   0000     0000       
+0x0bf  00   0000     0000       
+0x0c0  00   0000     0000       
+0x0c1  00   0000     0000       
+0x0c2  00   0000     0000       
+0x0c3  00   0000     0000       
+0x0c4  00   0000     0000       
+0x0c5  00   0000     0000       
+0x0c6  00   0000     0000       
+0x0c7  00   0000     0000       
+0x0c8  00   0000     0000       
+0x0c9  00   0000     0000       
+0x0ca  00   0000     0000       
+0x0cb  00   0000     0000       
+0x0cc  00   0000     0000       
+0x0cd  00   0000     0000       
+0x0ce  00   0000     0000       
+0x0cf  00   0000     0000       
+0x0d0  00   0000     0000       
+0x0d1  00   0000     0000       
+0x0d2  00   0000     0000       
+0x0d3  00   0000     0000       
+0x0d4  00   0000     0000       
+0x0d5  00   0000     0000       
+0x0d6  00   0000     0000       
+0x0d7  00   0000     0000       
+0x0d8  00   0000     0000       
+0x0d9  00   0000     0000       
+0x0da  00   0000     0000       
+0x0db  00   0000     0000       
+0x0dc  00   0000     0000       
+0x0dd  00   0000     0000       
+0x0de  00   0000     0000       
+0x0df  00   0000     0000       
+0x0e0  00   0000     0000       
+0x0e1  00   0000     0000       
+0x0e2  00   0000     0000       
+0x0e3  00   0000     0000       
+0x0e4  00   0000     0000       
+0x0e5  00   0000     0000       
+0x0e6  00   0000     0000       
+0x0e7  00   0000     0000       
+0x0e8  00   0000     0000       
+0x0e9  00   0000     0000       
+0x0ea  00   0000     0000       
+0x0eb  00   0000     0000       
+0x0ec  00   0000     0000       
+0x0ed  00   0000     0000       
+0x0ee  00   0000     0000       
+0x0ef  00   0000     0000       
+0x0f0  00   0000     0000       
+0x0f1  00   0000     0000       
+0x0f2  00   0000     0000       
+0x0f3  00   0000     0000       
+0x0f4  00   0000     0000       
+0x0f5  00   0000     0000       
+0x0f6  00   0000     0000       
+0x0f7  00   0000     0000       
+0x0f8  00   0000     0000       
+0x0f9  00   0000     0000       
+0x0fa  00   0000     0000       
+0x0fb  00   0000     0000       
+0x0fc  00   0000     0000       
+0x0fd  00   0000     0000       
+0x0fe  00   0000     0000       
+0x0ff  00   0000     0000       
+0x100  e8   XXX0     X000      DFSB 1
+0x101  1f   000X     XXXX      PORT #0x0F
+0x102  27   00X0     0XXX      WAIT 200ms
+0x103  10   000X     0000      PORT #0x00
+0x104  27   00X0     0XXX      WAIT 200ms
+0x105  e0   XXX0     0000      RTR 

+ 0 - 0
examples/Blink2.tps → SPSEmulator-service/examples/Blink2.tps


+ 0 - 0
examples/BlinkKomment.tps → SPSEmulator-service/examples/BlinkKomment.tps


+ 0 - 0
examples/Blink_LOOP.tps → SPSEmulator-service/examples/Blink_LOOP.tps


+ 0 - 0
examples/includes/macro_blink.tps → SPSEmulator-service/examples/includes/macro_blink.tps


+ 1 - 0
SPSEmulator-service/log/.gitignore

@@ -0,0 +1 @@
+/logging.log

BIN
SPSEmulator-service/mcs.keystore


+ 47 - 16
pom.xml → SPSEmulator-service/pom.xml

@@ -1,5 +1,4 @@
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 	<modelVersion>4.0.0</modelVersion>
 	<groupId>de.mcs.tools.sps</groupId>
@@ -28,7 +27,8 @@
 		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 		<timestamp>${maven.build.timestamp}</timestamp>
 		<maven.build.timestamp.format>dd.mm.yyyy HH:mm</maven.build.timestamp.format>
-		<jackson.version>2.8.5</jackson.version>
+		<jackson.version>2.9.6</jackson.version>
+		<jersey.client.version>2.25.1</jersey.client.version>
 	</properties>
 	<build>
 		<plugins>
@@ -97,19 +97,15 @@
 					</execution>
 				</executions>
 			</plugin>
-			<!-- <plugin> <groupId>com.akathist.maven.plugins.launch4j</groupId> <artifactId>launch4j-maven-plugin</artifactId> 
-				<version>1.7.24</version> <executions> <execution> <id>l4j-clui</id> <phase>package</phase> 
-				<goals> <goal>launch4j</goal> </goals> <configuration> <dontWrapJar>false</dontWrapJar> 
-				<headerType>console</headerType> <jar>${project.build.directory}/${project.artifactId}-${project.version}.jar</jar> 
-				<outfile>${project.build.directory}/MCSSPSTools.exe</outfile> <downloadUrl>http://java.com/download</downloadUrl> 
-				<classPath> <mainClass>com.howtodoinjava.ApplicationMain</mainClass> <preCp>anything</preCp> 
-				</classPath> <icon>src\main\resources\MSA.ico</icon> <jre> <path>/jre</path> 
-				<minVersion>1.8.0</minVersion> <jdkPreference>jreOnly</jdkPreference> </jre> 
-				<versionInfo> <fileVersion>1.0.0.0</fileVersion> <txtFileVersion>${project.version}</txtFileVersion> 
-				<fileDescription>${project.name}</fileDescription> <copyright>2018 MCS</copyright> 
-				<productVersion>1.0.0.0</productVersion> <txtProductVersion>1.0.0.0</txtProductVersion> 
-				<productName>${project.name}</productName> <companyName>MCS</companyName> 
-				<internalName>MCSSPSTools</internalName> <originalFilename>MCSSPSTools.exe</originalFilename> 
+			<!-- <plugin> <groupId>com.akathist.maven.plugins.launch4j</groupId> <artifactId>launch4j-maven-plugin</artifactId> <version>1.7.24</version> 
+				<executions> <execution> <id>l4j-clui</id> <phase>package</phase> <goals> <goal>launch4j</goal> </goals> <configuration> 
+				<dontWrapJar>false</dontWrapJar> <headerType>console</headerType> <jar>${project.build.directory}/${project.artifactId}-${project.version}.jar</jar> 
+				<outfile>${project.build.directory}/MCSSPSTools.exe</outfile> <downloadUrl>http://java.com/download</downloadUrl> <classPath> 
+				<mainClass>com.howtodoinjava.ApplicationMain</mainClass> <preCp>anything</preCp> </classPath> <icon>src\main\resources\MSA.ico</icon> 
+				<jre> <path>/jre</path> <minVersion>1.8.0</minVersion> <jdkPreference>jreOnly</jdkPreference> </jre> <versionInfo> <fileVersion>1.0.0.0</fileVersion> 
+				<txtFileVersion>${project.version}</txtFileVersion> <fileDescription>${project.name}</fileDescription> <copyright>2018 MCS</copyright> 
+				<productVersion>1.0.0.0</productVersion> <txtProductVersion>1.0.0.0</txtProductVersion> <productName>${project.name}</productName> 
+				<companyName>MCS</companyName> <internalName>MCSSPSTools</internalName> <originalFilename>MCSSPSTools.exe</originalFilename> 
 				</versionInfo> </configuration> </execution> </executions> </plugin> -->
 		</plugins>
 	</build>
@@ -175,5 +171,40 @@
 			<artifactId>reflections</artifactId>
 			<version>0.9.10</version>
 		</dependency>
+		<dependency>
+			<groupId>io.dropwizard</groupId>
+			<artifactId>dropwizard-core</artifactId>
+			<version>1.3.8</version>
+		</dependency>
+		<dependency>
+			<groupId>javax.xml.bind</groupId>
+			<artifactId>jaxb-api</artifactId>
+			<version>2.3.0</version>
+		</dependency>
+		<dependency>
+			<groupId>com.sun.xml.bind</groupId>
+			<artifactId>jaxb-impl</artifactId>
+			<version>2.3.0</version>
+		</dependency>
+		<dependency>
+			<groupId>org.glassfish.jaxb</groupId>
+			<artifactId>jaxb-runtime</artifactId>
+			<version>2.3.0</version>
+		</dependency>
+		<dependency>
+			<groupId>javax.activation</groupId>
+			<artifactId>activation</artifactId>
+			<version>1.1.1</version>
+		</dependency>
+		<dependency>
+			<groupId>com.google.guava</groupId>
+			<artifactId>guava</artifactId>
+			<version>27.0.1-jre</version>
+		</dependency>
+		<dependency>
+			<groupId>de.mcs.tools.sps</groupId>
+			<artifactId>SPSEmulator-model</artifactId>
+			<version>0.0.1-SNAPSHOT</version>
+		</dependency>
 	</dependencies>
 </project>

+ 216 - 0
SPSEmulator-service/src/main/java/de/mcs/tools/midicontroller/ConvertJsonData2Hex.java

@@ -0,0 +1,216 @@
+/**
+ * MCS Media Computer Software
+ * <one line to give the program's name and a brief idea of what it does.>
+ * Copyright (C) 2018 by Wilfried Klaas
+ * Project: SPSEmulator
+ * File: ConvertJsonData2Hex.java
+ * EMail: W.Klaas@gmx.de
+ * Created: 24.12.2018 wklaa
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+package de.mcs.tools.midicontroller;
+
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+
+import de.mcs.tools.midicontroller.data.ButtonData;
+import de.mcs.tools.midicontroller.data.ButtonData.TYPE;
+import de.mcs.tools.midicontroller.data.DataData;
+import de.mcs.tools.midicontroller.data.ProgramData;
+import de.mcs.tools.midicontroller.data.Programs;
+import de.mcs.tools.midicontroller.data.SequenceData;
+import de.mcs.tools.sps.utils.IntelHex;
+import de.mcs.utils.JacksonUtils;
+
+/**
+ * @author wklaa
+ *
+ */
+public class ConvertJsonData2Hex {
+
+  private static final int NUMBER_OF_SWITCHES = 6;
+  private static final int NUMBER_OF_SEQUENZES = 16;
+  private static final int NUMBER_OF_MIDICOMMANDS = 16;
+
+  /**
+   * @param args
+   * @throws Exception
+   */
+  public static void main(String[] args) throws Exception {
+    InputStream source = ClassLoader.getSystemResourceAsStream("programdata.json");
+    Programs programs = JacksonUtils.getJsonMapper().readValue(source, Programs.class);
+    int saveSize = 0;
+
+    for (ProgramData programData : programs.getPrograms()) {
+      try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
+        // out.write((byte) programs.getVersion());
+        saveSize = out.size();
+        System.out.println(programData.toString());
+        byte[] name = copyInto(getEmptyByteArray(12), getStringAsByte(programData.getName(), 12));
+
+        out.write(name);
+
+        out.write((byte) programData.getPrgNumber());
+        out.write((byte) programData.getInternalMidi());
+        out.write((byte) programData.getExternalMidi());
+
+        // bis hier sind es 15 Bytes
+        buildSwitches(out, programData);
+
+        // Sequenzes starten bei 70
+        buildSequenzes(out, programData);
+
+        // end identifier
+        out.write(0xFF);
+        out.close();
+
+        File dest = new File(String.format("%s.hex", programData.getName()));
+        System.out.printf("preset data written to file: %s\r\n", dest.getName());
+        try (BufferedOutputStream output = new BufferedOutputStream(new FileOutputStream(dest))) {
+          IntelHex intelHex = new IntelHex();
+          intelHex.writeHexStream(output, out.toByteArray());
+        }
+
+        System.out.println(String.format("actual size: %d", out.size() - saveSize));
+      }
+    }
+  }
+
+  private static void buildSequenzes(ByteArrayOutputStream out, ProgramData programData) throws Exception, IOException {
+    for (int i = 0; i < NUMBER_OF_SEQUENZES; i++) {
+      byte[] data = getEmptyByteArray(1 + 3 * NUMBER_OF_MIDICOMMANDS);
+      if ((programData.getSequences() != null) && (programData.getSequences().length > i)) {
+        int eventTyp = 0;
+        SequenceData eventData = programData.getSequences()[i];
+        switch (eventData.getType()) {
+        case INTERNAL:
+          eventTyp = 0;
+          break;
+        case EXPRESSION:
+          eventTyp = 0x70;
+          break;
+        case BUTTON:
+          eventTyp = 0x10 * (eventData.getValue());
+          break;
+        default:
+          break;
+        }
+        eventTyp = eventTyp | eventData.getEvent().ordinal();
+        data[0] = (byte) (eventTyp & 0xFF);
+        buildSequenzData(programData, data, eventData);
+      }
+      out.write(data);
+    }
+  }
+
+  private static void buildSequenzData(ProgramData programData, byte[] data, SequenceData eventData) throws Exception {
+    DataData[] datas = eventData.getDatas();
+    int pos = 1;
+    if (datas.length > NUMBER_OF_MIDICOMMANDS) {
+      throw new Exception("not enough memory.");
+    }
+    for (DataData dataData : datas) {
+      int dataType = dataData.getType().getByte();
+      if (DataData.CHANNEL.INTERNAL.equals(dataData.getChannel())) {
+        dataType = dataType + programData.getInternalMidi();
+      }
+      if (DataData.CHANNEL.EXTERNAL.equals(dataData.getChannel())) {
+        dataType = dataType + programData.getExternalMidi();
+      }
+      data[pos++] = (byte) dataType;
+      if ((pos + 1) < data.length) { // mindestens 2 noch
+                                     // platz
+        switch (dataData.getType()) {
+        case CC:
+        case NOTE_OFF:
+        case NOTE_ON:
+          data[pos++] = (byte) dataData.getData1();
+          data[pos++] = (byte) dataData.getData2();
+          break;
+        case ALL_NOTE_OFF:
+          data[pos++] = 0x78;
+          data[pos++] = 0x00;
+          break;
+        case PC:
+          data[pos++] = (byte) dataData.getData1();
+          data[pos++] = (byte) 0;
+          break;
+        default:
+          break;
+        }
+      }
+    }
+  }
+
+  private static void buildSwitches(ByteArrayOutputStream out, ProgramData programData)
+      throws UnsupportedEncodingException, IOException, Exception {
+    byte switchSettings = 0x00;
+    ButtonData[] buttons = programData.getButtons();
+    if ((buttons != null) && (buttons.length > 2)) {
+      for (int i = 0; i < NUMBER_OF_SWITCHES; i++) {
+        byte[] buttonName = getEmptyByteArray(8);
+        ButtonData buttonData;
+        if (i < buttons.length) {
+          buttonData = buttons[i];
+        } else {
+          buttonData = new ButtonData();
+          buttonData.setName(String.format("btn%d_nn", i));
+          buttonData.setColor(0);
+          buttonData.setType(TYPE.SWITCH);
+        }
+
+        buttonName = copyInto(buttonName, getStringAsByte(buttonData.getName(), 8));
+
+        out.write(buttonName);
+        out.write((byte) buttonData.getColor());
+        if (ButtonData.TYPE.SWITCH.equals(buttonData.getType())) {
+          switchSettings = (byte) (switchSettings | (0x01 << i));
+        }
+      }
+    } else {
+      throw new Exception("buttons not correct configured.");
+    }
+    // 9 pro Button = 54 pos: 54+15 69
+    out.write(switchSettings);
+  }
+
+  private static byte[] getStringAsByte(String value, int count) throws UnsupportedEncodingException {
+    String newValue = value;
+    if (newValue.length() > count) {
+      newValue = newValue.substring(0, count);
+    }
+    return newValue.getBytes("US-ASCII");
+  }
+
+  private static byte[] getEmptyByteArray(int count) {
+    byte[] value = new byte[count];
+    for (int i = 0; i < value.length; i++) {
+      value[i] = 0;
+    }
+    return value;
+  }
+
+  private static byte[] copyInto(byte[] dest, byte[] source) {
+    for (int i = 0; i < source.length; i++) {
+      dest[i] = source[i];
+    }
+    return dest;
+  }
+}

+ 0 - 0
src/main/java/de/mcs/tools/midicontroller/data/ButtonData.java → SPSEmulator-service/src/main/java/de/mcs/tools/midicontroller/data/ButtonData.java


+ 0 - 0
src/main/java/de/mcs/tools/midicontroller/data/DataData.java → SPSEmulator-service/src/main/java/de/mcs/tools/midicontroller/data/DataData.java


+ 0 - 0
src/main/java/de/mcs/tools/midicontroller/data/ProgramData.java → SPSEmulator-service/src/main/java/de/mcs/tools/midicontroller/data/ProgramData.java


+ 0 - 0
src/main/java/de/mcs/tools/midicontroller/data/Programs.java → SPSEmulator-service/src/main/java/de/mcs/tools/midicontroller/data/Programs.java


+ 0 - 0
src/main/java/de/mcs/tools/midicontroller/data/SequenceData.java → SPSEmulator-service/src/main/java/de/mcs/tools/midicontroller/data/SequenceData.java


+ 0 - 0
src/main/java/de/mcs/tools/sps/HEXTextOutputter.java → SPSEmulator-service/src/main/java/de/mcs/tools/sps/HEXTextOutputter.java


+ 0 - 0
src/main/java/de/mcs/tools/sps/IntelHEXOutputter.java → SPSEmulator-service/src/main/java/de/mcs/tools/sps/IntelHEXOutputter.java


+ 0 - 0
src/main/java/de/mcs/tools/sps/Macro.java → SPSEmulator-service/src/main/java/de/mcs/tools/sps/Macro.java


+ 0 - 0
src/main/java/de/mcs/tools/sps/Outputter.java → SPSEmulator-service/src/main/java/de/mcs/tools/sps/Outputter.java


+ 45 - 12
src/main/java/de/mcs/tools/sps/SPSAssembler.java → SPSEmulator-service/src/main/java/de/mcs/tools/sps/SPSAssembler.java

@@ -76,13 +76,13 @@ public class SPSAssembler {
   private static final String START_MACRO_DEFINITION = ".macro";
   private static final String DEFAULT_PACKAGE_FILTER = "de.mcs.tools.sps"; //$NON-NLS-1$
   private static File source;
-  private static HARDWARE destination;
   private static File destinationFile;
   private static String outputFormat;
   private static File includes;
   private static Outputter outputter;
   private static String destinationStr;
 
+  private HARDWARE destination;
   private int lineNumber;
   // private int srcLineNumber;
   private Map<String, Integer> labels;
@@ -91,6 +91,7 @@ public class SPSAssembler {
   private boolean inMacro;
   private Macro actualMacro;
   private Map<String, Macro> macros;
+  private List<Integer> lineNumbers;
 
   @SwitchOption(shortKey = 'h', longKey = "help", name = "help", help = "show this help page", required = false, defaultValue = false)
   public static void doHelp(boolean value) {
@@ -137,7 +138,7 @@ public class SPSAssembler {
    */
   public static void main(String[] args) {
     try {
-      destination = HARDWARE.HOLTEK;
+      HARDWARE destination = HARDWARE.HOLTEK;
 
       CommandlineProcessor.processCommandline(SPSAssembler.class, args);
 
@@ -173,6 +174,7 @@ public class SPSAssembler {
 
       try {
         SPSAssembler spsAssembler = new SPSAssembler();
+        spsAssembler.setDestination(destination);
         spsAssembler.doWork(source);
 
         List<Mnemonic> mnemonics = spsAssembler.getMnemonics();
@@ -206,7 +208,7 @@ public class SPSAssembler {
     for (Class<?> outClass : outputClasses) {
       SPSOutputter annotation = outClass.getAnnotation(SPSOutputter.class);
       if (annotation.name().equalsIgnoreCase(outputFormat)) {
-        outputter = (Outputter) outClass.newInstance();
+        outputter = (Outputter) outClass.getConstructor().newInstance();
       }
     }
 
@@ -286,15 +288,24 @@ public class SPSAssembler {
     labels = new HashMap<>();
     inBlockComment = false;
     mnemonics = new ArrayList<>();
+    lineNumbers = new ArrayList<>();
+
     inMacro = false;
     actualMacro = null;
     macros = new HashMap<>();
+    destination = HARDWARE.HOLTEK;
   }
 
-  private void doWork(File source) throws IOException, SyntaxError {
+  public void doWork(File source) throws IOException, SyntaxError {
     System.out.printf("start parsing file: %s\r\n", source.getName());
 
     List<String> sourceFile = Files.readAllLines(source.toPath(), Charset.forName("UTF-8"));
+    doCompile(sourceFile);
+  }
+
+  public void doCompile(List<String> source) throws SyntaxError, IOException {
+    ArrayList<String> sourceFile = new ArrayList<>();
+    sourceFile.addAll(source);
     for (int i = 0; i < sourceFile.size(); i++) {
       String line = sourceFile.get(i);
 
@@ -323,7 +334,7 @@ public class SPSAssembler {
 
   private void paddingSubroutines() throws SyntaxError {
     // checking location of Subroutines
-    if (destination.equals(HARDWARE.ARDUINOSPS) || destination.equals(HARDWARE.TINYSPS)) {
+    if (getDestination().equals(HARDWARE.ARDUINOSPS) || getDestination().equals(HARDWARE.TINYSPS)) {
       int position = 0;
       boolean foundSub = false;
       for (int i = 0; i < mnemonics.size(); i++) {
@@ -338,6 +349,7 @@ public class SPSAssembler {
         int count = 256 - position;
         for (int i = 0; i < count; i++) {
           mnemonics.add(position, new NOP(""));
+          lineNumbers.add(position, -1);
         }
       }
     }
@@ -354,7 +366,7 @@ public class SPSAssembler {
       prgSize++;
     }
     int maxSize = 128;
-    switch (destination) {
+    switch (getDestination()) {
     case ARDUINOSPS:
     case TINYSPS:
     case ATMEGA8:
@@ -365,10 +377,10 @@ public class SPSAssembler {
     }
     if (prgSize > maxSize) {
       throw new HardwareException(
-          String.format("Program exceeding size (%d) for hardeware %s (%d).", prgSize, destination, maxSize));
+          String.format("Program exceeding size (%d) for hardeware %s (%d).", prgSize, getDestination(), maxSize));
     }
 
-    switch (destination) {
+    switch (getDestination()) {
     case ARDUINOSPS:
       maxSize = 1024;
     case TINYSPS:
@@ -379,7 +391,7 @@ public class SPSAssembler {
     }
     if (mnemonics.size() > maxSize) {
       throw new HardwareException(
-          String.format("Program exceeding size (%d) for hardeware %s (%d).", prgSize, destination, maxSize));
+          String.format("Program exceeding size (%d) for hardeware %s (%d).", prgSize, getDestination(), maxSize));
     }
   }
 
@@ -410,10 +422,11 @@ public class SPSAssembler {
   private void checkingHardware() throws SyntaxError {
     for (Iterator<Mnemonic> iterator = mnemonics.iterator(); iterator.hasNext();) {
       Mnemonic mnemonic = iterator.next();
-      if (!mnemonic.allowedHardware().contains(destination)) {
+      if (!mnemonic.allowedHardware().contains(getDestination())) {
         throw new SyntaxError(mnemonic.getLineNumber(),
             String.format("the mnemonic \"%s\" with the argument \"%s\" is not availble on the choosen hardware \"%s\"",
-                mnemonic.getName(), mnemonic.getArgument() == null ? "" : mnemonic.getArgument(), destination.name()));
+                mnemonic.getName(), mnemonic.getArgument() == null ? "" : mnemonic.getArgument(),
+                getDestination().name()));
       }
     }
   }
@@ -476,6 +489,7 @@ public class SPSAssembler {
           if (mnemonic != null) {
             lineNumber++;
             mnemonics.add(mnemonic);
+            lineNumbers.add(srcLineNumber);
           }
         }
       }
@@ -597,7 +611,7 @@ public class SPSAssembler {
     return line;
   }
 
-  private List<Mnemonic> getMnemonics() {
+  public List<Mnemonic> getMnemonics() {
     return mnemonics;
   }
 
@@ -608,4 +622,23 @@ public class SPSAssembler {
   private Map<String, Integer> getLabels() {
     return labels;
   }
+
+  /**
+   * @return the destination
+   */
+  public HARDWARE getDestination() {
+    return destination;
+  }
+
+  /**
+   * @param destination
+   *          the destination to set
+   */
+  public void setDestination(HARDWARE destination) {
+    this.destination = destination;
+  }
+
+  public List<Integer> getLines() {
+    return lineNumbers;
+  }
 }

+ 0 - 0
src/main/java/de/mcs/tools/sps/TPSTextOutputter.java → SPSEmulator-service/src/main/java/de/mcs/tools/sps/TPSTextOutputter.java


+ 0 - 0
src/main/java/de/mcs/tools/sps/annotations/SPSOutputter.java → SPSEmulator-service/src/main/java/de/mcs/tools/sps/annotations/SPSOutputter.java


+ 0 - 0
src/main/java/de/mcs/tools/sps/annotations/package-info.java → SPSEmulator-service/src/main/java/de/mcs/tools/sps/annotations/package-info.java


+ 221 - 0
SPSEmulator-service/src/main/java/de/mcs/tools/sps/emulator/AbstractEmulator.java

@@ -0,0 +1,221 @@
+/**
+ * MCS Media Computer Software
+ * Copyright 2019 by Wilfried Klaas
+ * Project: SPSEmulator
+ * File: AbstractEmulator.java
+ * EMail: W.Klaas@gmx.de
+ * Created: 31.01.2019 wklaa_000
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+package de.mcs.tools.sps.emulator;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import de.mcs.tools.sps.SPSAssembler;
+import de.mcs.tools.sps.emulator.model.WebSessionModel;
+import de.mcs.tools.sps.emulator.model.WorkingModel;
+import de.mcs.tools.sps.emulator.model.WorkingModel.WORKINGSTATE;
+import de.mcs.tools.sps.exceptions.SyntaxError;
+import de.mcs.tools.sps.mnemonic.Mnemonic;
+import de.mcs.utils.Logger;
+
+/**
+ * @author wklaa_000
+ *
+ */
+public abstract class AbstractEmulator implements Emulator {
+
+  Logger log = Logger.getLogger(this.getClass());
+
+  private WebSessionModel model;
+
+  public AbstractEmulator(WebSessionModel model) {
+    this.model = model;
+  }
+
+  /**
+   * @return the model
+   */
+  public WebSessionModel getModel() {
+    return model;
+  }
+
+  /**
+   * starting the emulatiing process. Resetting all registers, addresses, stacks, outputs...
+   * 
+   * @return return true, if everything is ok.
+   */
+  @Override
+  public boolean start() {
+    WorkingModel work = model.getWork();
+    work.setAddress((short) 0);
+    work.setRaddress((short) 0);
+    work.setPage((byte) 0);
+    work.setRegisterA((byte) 0);
+    work.setRegisterB((byte) 0);
+    work.setRegisterC((byte) 0);
+    work.setRegisterD((byte) 0);
+    work.setRegisterE((byte) 0);
+    work.setRegisterF((byte) 0);
+    work.setStack(new ArrayList<>());
+    work.setWorkingstate(WORKINGSTATE.EMULATE);
+
+    model.getProgram().setActualLine(0);
+
+    model.getOutput().setOutput1(false);
+    model.getOutput().setOutput2(false);
+    model.getOutput().setOutput3(false);
+    model.getOutput().setOutput4(false);
+    model.getOutput().setPwm1((byte) 0);
+    model.getOutput().setPwm2((byte) 0);
+    model.getOutput().setServo1((byte) 0);
+    model.getOutput().setServo2((byte) 0);
+    model.getOutput().setTone((byte) 0);
+
+    return true;
+  }
+
+  /**
+   * processing the next command from the program
+   * 
+   * @return return true, if everything is ok.
+   */
+  @Override
+  public boolean next() {
+    byte commandData = model.getProgram().getBin()[model.getProgram().getActualLine()];
+    log.debug("new command 0x%02x", commandData);
+
+    int command = (commandData & 0xF0);
+    byte data = (byte) (commandData & 0x0F);
+
+    switch (command) {
+    case 0x00:
+      doNop(data);
+      break;
+    case 0x10:
+      doPort(data);
+      break;
+    case 0x20:
+      doDelay(data);
+      break;
+    case 0x30:
+      doJumpBack(data);
+      break;
+    case 0x40:
+      doSetA(data);
+      break;
+    case 0x50:
+      doIsA(data);
+      break;
+    case 0x60:
+      doAIs(data);
+      break;
+    case 0x70:
+      doCalc(data);
+      break;
+    case 0x80:
+      doPage(data);
+      break;
+    case 0x90:
+      doJump(data);
+      break;
+    case 0xA0:
+      doCCount(data);
+      break;
+    case 0xB0:
+      doDCount(data);
+      break;
+    case 0xC0:
+      doSkip(data);
+      break;
+    case 0xD0:
+      doCall(data);
+      break;
+    case 0xE0:
+      doCallSub(data);
+      break;
+    case 0xF0:
+      doByte(data);
+      break;
+
+    default:
+      break;
+    }
+    model.getWork().setAddress((short) (model.getWork().getAddress() + 1));
+    return false;
+  }
+
+  protected abstract void doSetA(byte data);
+
+  protected abstract void doCalc(byte data);
+
+  protected abstract void doCall(byte data);
+
+  protected abstract void doCCount(byte data);
+
+  protected abstract void doNop(byte data);
+
+  protected abstract void doPort(byte data);
+
+  protected abstract void doDelay(byte data);
+
+  protected abstract void doJumpBack(byte data);
+
+  protected abstract void doAIs(byte data);
+
+  protected abstract void doIsA(byte data);
+
+  protected abstract void doPage(byte data);
+
+  protected abstract void doJump(byte data);
+
+  protected abstract void doDCount(byte data);
+
+  protected abstract void doSkip(byte data);
+
+  protected abstract void doCallSub(byte data);
+
+  protected abstract void doByte(byte data);
+
+  @Override
+  public boolean stop() {
+    model.getWork().setWorkingstate(WORKINGSTATE.NN);
+    return true;
+  }
+
+  @Override
+  public boolean compile() throws SyntaxError, IOException {
+    SPSAssembler spsAssembler = new SPSAssembler();
+    List<String> source = Arrays.asList(model.getProgram().getSource());
+    spsAssembler.doCompile(source);
+    List<Mnemonic> mnemonics = spsAssembler.getMnemonics();
+    List<Integer> lineNumbers = spsAssembler.getLines();
+    byte[] bin = new byte[mnemonics.size()];
+    int[] bin2SrcLine = new int[mnemonics.size()];
+    for (int i = 0; i < mnemonics.size(); i++) {
+      Mnemonic mnemonic = mnemonics.get(i);
+      bin[i] = (byte) mnemonic.getByte();
+      bin2SrcLine[i] = lineNumbers.get(i);
+    }
+    model.getProgram().setBin(bin);
+    model.getProgram().setBin2SrcLine(bin2SrcLine);
+
+    return false;
+  }
+
+}

+ 12 - 0
SPSEmulator-service/src/main/java/de/mcs/tools/sps/emulator/BaseHealthCheck.java

@@ -0,0 +1,12 @@
+package de.mcs.tools.sps.emulator;
+
+import com.codahale.metrics.health.HealthCheck;
+
+public class BaseHealthCheck extends HealthCheck {
+
+  @Override
+  protected Result check() throws Exception {
+    return Result.healthy();
+  }
+
+}

+ 42 - 0
SPSEmulator-service/src/main/java/de/mcs/tools/sps/emulator/Emulator.java

@@ -0,0 +1,42 @@
+/**
+ * MCS Media Computer Software
+ * Copyright 2019 by Wilfried Klaas
+ * Project: SPSEmulator
+ * File: Emulator.java
+ * EMail: W.Klaas@gmx.de
+ * Created: 31.01.2019 wklaa_000
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+package de.mcs.tools.sps.emulator;
+
+import java.io.IOException;
+
+import de.mcs.tools.sps.exceptions.SyntaxError;
+
+/**
+ * @author wklaa_000
+ *
+ */
+public interface Emulator {
+
+  boolean start();
+
+  boolean next();
+
+  boolean stop();
+
+  boolean compile() throws SyntaxError, IOException;
+
+}

+ 47 - 0
SPSEmulator-service/src/main/java/de/mcs/tools/sps/emulator/EmulatorFactory.java

@@ -0,0 +1,47 @@
+/**
+ * MCS Media Computer Software
+ * Copyright 2019 by Wilfried Klaas
+ * Project: SPSEmulator
+ * File: EmulatorFactory.java
+ * EMail: W.Klaas@gmx.de
+ * Created: 31.01.2019 wklaa_000
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+package de.mcs.tools.sps.emulator;
+
+import de.mcs.tools.sps.emulator.holtek.HoltekEmulator;
+import de.mcs.tools.sps.emulator.model.Hardware;
+import de.mcs.tools.sps.emulator.model.WebSessionModel;
+
+/**
+ * @author wklaa_000
+ *
+ */
+public class EmulatorFactory {
+
+  public static Emulator getEmulator(WebSessionModel model) {
+    Emulator emulator = null;
+    Hardware hardware = model.getProgram().getHardware();
+    switch (hardware) {
+    case HOLTEK:
+      emulator = new HoltekEmulator(model);
+      break;
+    default:
+      break;
+    }
+    return emulator;
+  }
+
+}

+ 45 - 0
SPSEmulator-service/src/main/java/de/mcs/tools/sps/emulator/WebEmulatorApplication.java

@@ -0,0 +1,45 @@
+/**
+ * MCS Media Computer Software
+ * Copyright 2019 by Wilfried Klaas
+ * Project: SPSEmulator
+ * File: WebEmulatorApp.java
+ * EMail: W.Klaas@gmx.de
+ * Created: 29.01.2019 wklaa_000
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+package de.mcs.tools.sps.emulator;
+
+import de.mcs.tools.sps.emulator.resources.EmulatorResource;
+import io.dropwizard.Application;
+import io.dropwizard.setup.Environment;
+
+/**
+ * @author wklaa_000
+ *
+ */
+public class WebEmulatorApplication extends Application<WebEmulatorConfiguration> {
+
+  public static void main(String[] args) throws Exception {
+    new WebEmulatorApplication().run(args);
+  }
+
+  @Override
+  public void run(WebEmulatorConfiguration configuration, Environment environment) throws Exception {
+    final EmulatorResource resource = new EmulatorResource();
+    environment.jersey().register(resource);
+    environment.healthChecks().register("BaseHealthCheck", new BaseHealthCheck());
+  }
+
+}

+ 55 - 0
SPSEmulator-service/src/main/java/de/mcs/tools/sps/emulator/WebEmulatorConfiguration.java

@@ -0,0 +1,55 @@
+/**
+ * MCS Media Computer Software
+ * Copyright 2019 by Wilfried Klaas
+ * Project: SPSEmulator
+ * File: WebÊmulatorConfiguration.java
+ * EMail: W.Klaas@gmx.de
+ * Created: 29.01.2019 wklaa_000
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+package de.mcs.tools.sps.emulator;
+
+import org.hibernate.validator.constraints.NotEmpty;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import io.dropwizard.Configuration;
+
+/**
+ * @author wklaa_000
+ *
+ */
+public class WebEmulatorConfiguration extends Configuration {
+
+  @NotEmpty
+  private String nodename;
+
+  /**
+   * @return the nodename
+   */
+  @JsonProperty
+  public String getNodename() {
+    return nodename;
+  }
+
+  /**
+   * @param nodename
+   *          the nodename to set
+   */
+  @JsonProperty
+  public void setNodename(String nodename) {
+    this.nodename = nodename;
+  }
+}

+ 0 - 0
src/main/java/de/mcs/tools/sps/emulator/exceptions/WrongProgramSizeException.java → SPSEmulator-service/src/main/java/de/mcs/tools/sps/emulator/exceptions/WrongProgramSizeException.java


+ 0 - 0
src/main/java/de/mcs/tools/sps/emulator/exceptions/package-info.java → SPSEmulator-service/src/main/java/de/mcs/tools/sps/emulator/exceptions/package-info.java


+ 313 - 0
SPSEmulator-service/src/main/java/de/mcs/tools/sps/emulator/holtek/HoltekEmulator.java

@@ -0,0 +1,313 @@
+/**
+ * MCS Media Computer Software
+ * Copyright 2019 by Wilfried Klaas
+ * Project: SPSEmulator
+ * File: HoltekEmulator.java
+ * EMail: W.Klaas@gmx.de
+ * Created: 31.01.2019 wklaa_000
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+package de.mcs.tools.sps.emulator.holtek;
+
+import de.mcs.tools.sps.emulator.AbstractEmulator;
+import de.mcs.tools.sps.emulator.Emulator;
+import de.mcs.tools.sps.emulator.model.WebSessionModel;
+import de.mcs.tools.sps.emulator.model.WorkingModel;
+
+/**
+ * @author wklaa_000
+ *
+ */
+public class HoltekEmulator extends AbstractEmulator implements Emulator {
+
+  long[] DELAY_TIMES = { 1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 30000, 60000 };
+
+  public HoltekEmulator(WebSessionModel model) {
+    super(model);
+  }
+
+  @Override
+  protected void doSetA(byte data) {
+    getModel().getWork().setRegisterA(data);
+  }
+
+  @Override
+  protected void doCalc(byte data) {
+    WorkingModel work = getModel().getWork();
+    switch (data) {
+    case 1:
+      work.setRegisterA((byte) (work.getRegisterA() + 1));
+      break;
+    case 2:
+      work.setRegisterA((byte) (work.getRegisterA() - 1));
+      break;
+    case 3:
+      work.setRegisterA((byte) (work.getRegisterA() + work.getRegisterB()));
+      break;
+    case 4:
+      work.setRegisterA((byte) (work.getRegisterA() - work.getRegisterB()));
+      break;
+    case 5:
+      work.setRegisterA((byte) (work.getRegisterA() * work.getRegisterB()));
+      break;
+    case 6:
+      work.setRegisterA((byte) (work.getRegisterA() / work.getRegisterB()));
+      break;
+    case 7:
+      work.setRegisterA((byte) (work.getRegisterA() & work.getRegisterB()));
+      break;
+    case 8:
+      work.setRegisterA((byte) (work.getRegisterA() | work.getRegisterB()));
+      break;
+    case 9:
+      work.setRegisterA((byte) (work.getRegisterA() ^ work.getRegisterB()));
+      break;
+    case 10:
+      work.setRegisterA((byte) (~work.getRegisterA()));
+      break;
+    default:
+      break;
+    }
+  }
+
+  @Override
+  protected void doCall(byte data) {
+    getModel().getWork().setRaddress(getModel().getWork().getAddress());
+    doJump(data);
+  }
+
+  @Override
+  protected void doNop(byte data) {
+    return;
+  }
+
+  @Override
+  protected void doPort(byte data) {
+    getModel().getOutput().setOutput1((data & 0x01) > 0);
+    getModel().getOutput().setOutput2((data & 0x02) > 0);
+    getModel().getOutput().setOutput3((data & 0x04) > 0);
+    getModel().getOutput().setOutput4((data & 0x08) > 0);
+  }
+
+  @Override
+  protected void doDelay(byte data) {
+    try {
+      Thread.sleep(DELAY_TIMES[data]);
+    } catch (InterruptedException e) {
+      e.printStackTrace();
+    }
+  }
+
+  @Override
+  protected void doJumpBack(byte data) {
+    short address = getModel().getWork().getAddress();
+    address -= data;
+    if (address < 0) {
+      address = 0;
+    }
+    getModel().getWork().setAddress((short) (address - 1));
+  }
+
+  @Override
+  protected void doAIs(byte data) {
+    WorkingModel work = getModel().getWork();
+    switch (data) {
+    case 1:
+      work.setRegisterA(work.getRegisterB());
+      break;
+    case 2:
+      work.setRegisterA(work.getRegisterC());
+      break;
+    case 3:
+      work.setRegisterA(work.getRegisterD());
+      break;
+    case 4:
+      byte value = (byte) (getModel().getInput().isInput1() ? 1 : 0);
+      value += (byte) (getModel().getInput().isInput2() ? 2 : 0);
+      value += (byte) (getModel().getInput().isInput3() ? 4 : 0);
+      value += (byte) (getModel().getInput().isInput4() ? 8 : 0);
+      work.setRegisterA(value);
+      break;
+    case 5:
+      work.setRegisterA((byte) (getModel().getInput().isInput1() ? 1 : 0));
+      break;
+    case 6:
+      work.setRegisterA((byte) (getModel().getInput().isInput2() ? 1 : 0));
+      break;
+    case 7:
+      work.setRegisterA((byte) (getModel().getInput().isInput3() ? 1 : 0));
+      break;
+    case 8:
+      work.setRegisterA((byte) (getModel().getInput().isInput4() ? 1 : 0));
+      break;
+    case 9:
+      work.setRegisterA((byte) (getModel().getInput().getAdc1()));
+      break;
+    case 10:
+      work.setRegisterA((byte) (getModel().getInput().getAdc2()));
+      break;
+    default:
+      break;
+    }
+  }
+
+  @Override
+  protected void doIsA(byte data) {
+    WorkingModel work = getModel().getWork();
+    switch (data) {
+    case 0:
+      byte temp = work.getRegisterA();
+      work.setRegisterA(work.getRegisterB());
+      work.setRegisterB(temp);
+      break;
+    case 1:
+      work.setRegisterB(work.getRegisterA());
+      break;
+    case 2:
+      work.setRegisterC(work.getRegisterA());
+      break;
+    case 3:
+      work.setRegisterD(work.getRegisterA());
+      break;
+    case 4:
+      doPort(work.getRegisterA());
+      break;
+    case 5:
+      getModel().getOutput().setOutput1((work.getRegisterA() & 0x01) > 0);
+      break;
+    case 6:
+      getModel().getOutput().setOutput2((work.getRegisterA() & 0x02) > 0);
+      break;
+    case 7:
+      getModel().getOutput().setOutput3((work.getRegisterA() & 0x04) > 0);
+      break;
+    case 8:
+      getModel().getOutput().setOutput4((work.getRegisterA() & 0x08) > 0);
+      break;
+    case 9:
+      getModel().getOutput().setPwm1((byte) ((work.getRegisterA() & 0x0F) << 4));
+      break;
+    case 10:
+      getModel().getOutput().setPwm2((byte) ((work.getRegisterA() & 0x0F) << 4));
+      break;
+    default:
+      break;
+    }
+  }
+
+  @Override
+  protected void doPage(byte data) {
+    byte value = (byte) (data & 0x07);
+    getModel().getWork().setPage(value);
+  }
+
+  @Override
+  protected void doJump(byte data) {
+    int value = data + (16 * getModel().getWork().getPage());
+    getModel().getWork().setAddress((short) (value - 1));
+  }
+
+  @Override
+  protected void doCCount(byte data) {
+    byte value = getModel().getWork().getRegisterC();
+    if (value > 0) {
+      value -= 1;
+      getModel().getWork().setRegisterC(value);
+      doJump(data);
+    }
+  }
+
+  @Override
+  protected void doDCount(byte data) {
+    byte value = getModel().getWork().getRegisterD();
+    if (value > 0) {
+      value -= 1;
+      getModel().getWork().setRegisterD(value);
+      doJump(data);
+    }
+  }
+
+  @Override
+  protected void doSkip(byte data) {
+    WorkingModel work = getModel().getWork();
+    boolean skip = false;
+    switch (data) {
+    case 1:
+      skip = (work.getRegisterA() > work.getRegisterB());
+      break;
+    case 2:
+      skip = (work.getRegisterA() < work.getRegisterB());
+      break;
+    case 3:
+      skip = (work.getRegisterA() == work.getRegisterB());
+      break;
+    case 4:
+      skip = getModel().getInput().isInput1();
+      break;
+    case 5:
+      skip = getModel().getInput().isInput2();
+      break;
+    case 6:
+      skip = getModel().getInput().isInput3();
+      break;
+    case 7:
+      skip = getModel().getInput().isInput4();
+      break;
+    case 8:
+      skip = !getModel().getInput().isInput1();
+      break;
+    case 9:
+      skip = !getModel().getInput().isInput2();
+      break;
+    case 10:
+      skip = !getModel().getInput().isInput3();
+      break;
+    case 11:
+      skip = !getModel().getInput().isInput4();
+      break;
+    case 12:
+      skip = !getModel().getInput().isPrg();
+      break;
+    case 13:
+      skip = !getModel().getInput().isSel();
+      break;
+    case 14:
+      skip = getModel().getInput().isPrg();
+      break;
+    case 15:
+      skip = getModel().getInput().isSel();
+      break;
+    default:
+      break;
+    }
+    if (skip) {
+      int value = getModel().getWork().getAddress();
+      getModel().getWork().setAddress((short) (value + 1));
+    }
+  }
+
+  @Override
+  protected void doCallSub(byte data) {
+    if (data == 0) {
+      getModel().getWork().setAddress(getModel().getWork().getRaddress());
+    }
+  }
+
+  @Override
+  protected void doByte(byte data) {
+
+  }
+
+}

+ 86 - 0
SPSEmulator-service/src/main/java/de/mcs/tools/sps/emulator/model/CommandModel.java

@@ -0,0 +1,86 @@
+package de.mcs.tools.sps.emulator.model;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public class CommandModel {
+
+  public enum COMMAND {
+    START, STOP, NEXT, RESET, COMPILE, NN
+  };
+
+  private Set<COMMAND> availableCommands;
+  private COMMAND actualCommand;
+
+  public CommandModel() {
+    setActualCommand(COMMAND.NN);
+    setAvailableCommands(new HashSet<>());
+    evaluateAvailableCommands(getActualCommand());
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder b = new StringBuilder();
+    b.append(String.format("%s:[available: [%s], actual: %s]", this.getClass().getSimpleName(),
+        Arrays.toString(availableCommands.toArray()), actualCommand.name()));
+    return b.toString();
+  }
+
+  public void evaluateAvailableCommands(COMMAND command) {
+    getAvailableCommands().clear();
+    switch (command) {
+    case START:
+      getAvailableCommands().add(COMMAND.NEXT);
+      getAvailableCommands().add(COMMAND.STOP);
+      getAvailableCommands().add(COMMAND.RESET);
+      break;
+    case STOP:
+      getAvailableCommands().add(COMMAND.START);
+      getAvailableCommands().add(COMMAND.RESET);
+      break;
+    case NEXT:
+      getAvailableCommands().add(COMMAND.STOP);
+      getAvailableCommands().add(COMMAND.RESET);
+      break;
+    case COMPILE:
+    case RESET:
+    case NN:
+      getAvailableCommands().add(COMMAND.START);
+      getAvailableCommands().add(COMMAND.COMPILE);
+      break;
+    default:
+      break;
+    }
+  }
+
+  /**
+   * @return the availableCommands
+   */
+  public Set<COMMAND> getAvailableCommands() {
+    return availableCommands;
+  }
+
+  /**
+   * @param availableCommands
+   *          the availableCommands to set
+   */
+  public void setAvailableCommands(Set<COMMAND> availableCommands) {
+    this.availableCommands = availableCommands;
+  }
+
+  /**
+   * @return the actualCommand
+   */
+  public COMMAND getActualCommand() {
+    return actualCommand;
+  }
+
+  /**
+   * @param actualCommand
+   *          the actualCommand to set
+   */
+  public void setActualCommand(COMMAND actualCommand) {
+    this.actualCommand = actualCommand;
+  }
+}

+ 30 - 0
SPSEmulator-service/src/main/java/de/mcs/tools/sps/emulator/model/Hardware.java

@@ -0,0 +1,30 @@
+/**
+ * MCS Media Computer Software
+ * Copyright 2019 by Wilfried Klaas
+ * Project: SPSEmulator
+ * File: Hardware.java
+ * EMail: W.Klaas@gmx.de
+ * Created: 29.01.2019 wklaa_000
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+package de.mcs.tools.sps.emulator.model;
+
+/**
+ * @author wklaa_000
+ *
+ */
+public enum Hardware {
+  HOLTEK, ATMEGA8, ARDUINOSPS, TINYSPS
+}

+ 176 - 0
SPSEmulator-service/src/main/java/de/mcs/tools/sps/emulator/model/InputModel.java

@@ -0,0 +1,176 @@
+package de.mcs.tools.sps.emulator.model;
+
+public class InputModel {
+
+  boolean input1;
+  boolean input2;
+  boolean input3;
+  boolean input4;
+
+  boolean sel;
+  boolean prg;
+
+  byte adc1;
+  byte adc2;
+  byte rc1;
+  byte rc2;
+
+  @Override
+  public String toString() {
+    StringBuilder b = new StringBuilder();
+    b.append(String.format(
+        "%s:[Din1: %s, Din2: %s, Din3: %s Din4: %s, SEL: %s, PRG: %s, adc1: %d, adc2: %d, rc1: %d, rc2: %d]",
+        this.getClass().getSimpleName(), input1, input2, input3, input4, sel, prg, adc1, adc2, rc1, rc2));
+    return b.toString();
+  }
+
+  /**
+   * @return the input1
+   */
+  public boolean isInput1() {
+    return input1;
+  }
+
+  /**
+   * @param input1
+   *          the input1 to set
+   */
+  public void setInput1(boolean input1) {
+    this.input1 = input1;
+  }
+
+  /**
+   * @return the input2
+   */
+  public boolean isInput2() {
+    return input2;
+  }
+
+  /**
+   * @param input2
+   *          the input2 to set
+   */
+  public void setInput2(boolean input2) {
+    this.input2 = input2;
+  }
+
+  /**
+   * @return the input3
+   */
+  public boolean isInput3() {
+    return input3;
+  }
+
+  /**
+   * @param input3
+   *          the input3 to set
+   */
+  public void setInput3(boolean input3) {
+    this.input3 = input3;
+  }
+
+  /**
+   * @return the input4
+   */
+  public boolean isInput4() {
+    return input4;
+  }
+
+  /**
+   * @param input4
+   *          the input4 to set
+   */
+  public void setInput4(boolean input4) {
+    this.input4 = input4;
+  }
+
+  /**
+   * @return the sel
+   */
+  public boolean isSel() {
+    return sel;
+  }
+
+  /**
+   * @param sel
+   *          the sel to set
+   */
+  public void setSel(boolean sel) {
+    this.sel = sel;
+  }
+
+  /**
+   * @return the prg
+   */
+  public boolean isPrg() {
+    return prg;
+  }
+
+  /**
+   * @param prg
+   *          the prg to set
+   */
+  public void setPrg(boolean prg) {
+    this.prg = prg;
+  }
+
+  /**
+   * @return the adc1
+   */
+  public byte getAdc1() {
+    return adc1;
+  }
+
+  /**
+   * @param adc1
+   *          the adc1 to set
+   */
+  public void setAdc1(byte adc1) {
+    this.adc1 = adc1;
+  }
+
+  /**
+   * @return the adc2
+   */
+  public byte getAdc2() {
+    return adc2;
+  }
+
+  /**
+   * @param adc2
+   *          the adc2 to set
+   */
+  public void setAdc2(byte adc2) {
+    this.adc2 = adc2;
+  }
+
+  /**
+   * @return the rc1
+   */
+  public byte getRc1() {
+    return rc1;
+  }
+
+  /**
+   * @param rc1
+   *          the rc1 to set
+   */
+  public void setRc1(byte rc1) {
+    this.rc1 = rc1;
+  }
+
+  /**
+   * @return the rc2
+   */
+  public byte getRc2() {
+    return rc2;
+  }
+
+  /**
+   * @param rc2
+   *          the rc2 to set
+   */
+  public void setRc2(byte rc2) {
+    this.rc2 = rc2;
+  }
+}

+ 160 - 0
SPSEmulator-service/src/main/java/de/mcs/tools/sps/emulator/model/OutputModel.java

@@ -0,0 +1,160 @@
+package de.mcs.tools.sps.emulator.model;
+
+public class OutputModel {
+
+  boolean output1;
+  boolean output2;
+  boolean output3;
+  boolean output4;
+
+  byte pwm1;
+  byte pwm2;
+  byte servo1;
+  byte servo2;
+
+  byte tone;
+
+  @Override
+  public String toString() {
+    StringBuilder b = new StringBuilder();
+    b.append(String.format(
+        "%s:[Dout1: %s, Dout2: %s, Dout3: %s Dout4: %s, pwm1: %d, pwm2: %d, servo1: %d, servo2: %d, tone: %d]",
+        this.getClass().getSimpleName(), output1, output2, output3, output4, pwm1, pwm2, servo1, servo2, tone));
+    return b.toString();
+  }
+
+  /**
+   * @return the output1
+   */
+  public boolean isOutput1() {
+    return output1;
+  }
+
+  /**
+   * @param output1
+   *          the output1 to set
+   */
+  public void setOutput1(boolean output1) {
+    this.output1 = output1;
+  }
+
+  /**
+   * @return the output2
+   */
+  public boolean isOutput2() {
+    return output2;
+  }
+
+  /**
+   * @param output2
+   *          the output2 to set
+   */
+  public void setOutput2(boolean output2) {
+    this.output2 = output2;
+  }
+
+  /**
+   * @return the output3
+   */
+  public boolean isOutput3() {
+    return output3;
+  }
+
+  /**
+   * @param output3
+   *          the output3 to set
+   */
+  public void setOutput3(boolean output3) {
+    this.output3 = output3;
+  }
+
+  /**
+   * @return the output4
+   */
+  public boolean isOutput4() {
+    return output4;
+  }
+
+  /**
+   * @param output4
+   *          the output4 to set
+   */
+  public void setOutput4(boolean output4) {
+    this.output4 = output4;
+  }
+
+  /**
+   * @return the pwm1
+   */
+  public byte getPwm1() {
+    return pwm1;
+  }
+
+  /**
+   * @param pwm1
+   *          the pwm1 to set
+   */
+  public void setPwm1(byte pwm1) {
+    this.pwm1 = pwm1;
+  }
+
+  /**
+   * @return the pwm2
+   */
+  public byte getPwm2() {
+    return pwm2;
+  }
+
+  /**
+   * @param pwm2
+   *          the pwm2 to set
+   */
+  public void setPwm2(byte pwm2) {
+    this.pwm2 = pwm2;
+  }
+
+  /**
+   * @return the servo1
+   */
+  public byte getServo1() {
+    return servo1;
+  }
+
+  /**
+   * @param servo1
+   *          the servo1 to set
+   */
+  public void setServo1(byte servo1) {
+    this.servo1 = servo1;
+  }
+
+  /**
+   * @return the servo2
+   */
+  public byte getServo2() {
+    return servo2;
+  }
+
+  /**
+   * @param servo2
+   *          the servo2 to set
+   */
+  public void setServo2(byte servo2) {
+    this.servo2 = servo2;
+  }
+
+  /**
+   * @return the tone
+   */
+  public byte getTone() {
+    return tone;
+  }
+
+  /**
+   * @param tone
+   *          the tone to set
+   */
+  public void setTone(byte tone) {
+    this.tone = tone;
+  }
+}

+ 158 - 0
SPSEmulator-service/src/main/java/de/mcs/tools/sps/emulator/model/ProgramModel.java

@@ -0,0 +1,158 @@
+package de.mcs.tools.sps.emulator.model;
+
+import java.io.UnsupportedEncodingException;
+import java.util.Arrays;
+
+import org.apache.commons.lang3.StringUtils;
+
+import de.mcs.utils.SHA256Utils;
+
+public class ProgramModel {
+
+  private Hardware hardware;
+  private String[] source;
+  private String hash;
+  private byte[] bin;
+  private boolean modified;
+  private int actualLine;
+  private int[] bin2SrcLine;
+
+  public ProgramModel() {
+    hardware = Hardware.HOLTEK;
+    modified = false;
+    setActualLine(0);
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder b = new StringBuilder();
+    b.append(String.format("%s:[Hardware: %s, Hash: %s, modified: %s, actualLine: %d, Source: \"%s\", bin: 0x%x ]",
+        this.getClass().getSimpleName(), hardware.name(), hash, modified, actualLine,
+        (source != null) ? source[0] + ((source.length > 1) ? "..." : "") : "", (bin != null) ? bin.length : 0));
+    return b.toString();
+  }
+
+  /**
+   * @return the source
+   */
+  public String[] getSource() {
+    return source;
+  }
+
+  /**
+   * @param source
+   *          the source to set
+   */
+  public void setSource(String[] source) {
+    this.source = source;
+  }
+
+  /**
+   * @return the hash
+   */
+  public String getHash() {
+    return hash;
+  }
+
+  /**
+   * @param hash
+   *          the hash to set
+   */
+  public void setHash(String hash) {
+    this.hash = hash;
+  }
+
+  /**
+   * @return the bin
+   */
+  public byte[] getBin() {
+    return bin;
+  }
+
+  /**
+   * @param bin
+   *          the bin to set
+   */
+  public void setBin(byte[] bin) {
+    this.bin = bin;
+  }
+
+  /**
+   * @return the hardware
+   */
+  public Hardware getHardware() {
+    return hardware;
+  }
+
+  /**
+   * @param hardware
+   *          the hardware to set
+   */
+  public void setHardware(Hardware hardware) {
+    this.hardware = hardware;
+  }
+
+  /**
+   * @return the modified
+   */
+  public boolean isModified() {
+    return modified;
+  }
+
+  /**
+   * @param modified
+   *          the modified to set
+   */
+  public void setModified(boolean modified) {
+    this.modified = modified;
+  }
+
+  /**
+   * @return the actualLine
+   */
+  public int getActualLine() {
+    return actualLine;
+  }
+
+  /**
+   * @param actualLine
+   *          the actualLine to set
+   */
+  public void setActualLine(int actualLine) {
+    this.actualLine = actualLine;
+  }
+
+  public boolean rebuildHash() {
+    if ((source != null) && (source.length > 0)) {
+      String sourceStr = Arrays.toString(source);
+      try {
+        String newHash = SHA256Utils.shaBytesToString(SHA256Utils.shaBytes(sourceStr.getBytes("UTF-8")));
+        if (StringUtils.isEmpty(hash)) {
+          this.hash = newHash;
+          this.setModified(false);
+        } else {
+          this.setModified(!newHash.equals(hash));
+        }
+      } catch (UnsupportedEncodingException e) {
+        e.printStackTrace();
+      }
+    }
+    return isModified();
+  }
+
+  /**
+   * @return the bin2SrcLine
+   */
+  public int[] getBin2SrcLine() {
+    return bin2SrcLine;
+  }
+
+  /**
+   * @param bin2SrcLine
+   *          the bin2SrcLine to set
+   */
+  public void setBin2SrcLine(int[] bin2SrcLine) {
+    this.bin2SrcLine = bin2SrcLine;
+  }
+
+}

+ 174 - 0
SPSEmulator-service/src/main/java/de/mcs/tools/sps/emulator/model/WebSessionModel.java

@@ -0,0 +1,174 @@
+/**
+ * MCS Media Computer Software
+ * Copyright 2019 by Wilfried Klaas
+ * Project: SPSEmulator
+ * File: WebSessionModel.java
+ * EMail: W.Klaas@gmx.de
+ * Created: 29.01.2019 wklaa_000
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+package de.mcs.tools.sps.emulator.model;
+
+/**
+ * @author wklaa_000
+ *
+ */
+public class WebSessionModel {
+
+  private ProgramModel program;
+  private CommandModel command;
+  private InputModel input;
+  private OutputModel output;
+  private WorkingModel work;
+  private String message;
+  private String stacktrace;
+  private boolean error;
+
+  public WebSessionModel() {
+    program = new ProgramModel();
+    command = new CommandModel();
+    input = new InputModel();
+    output = new OutputModel();
+    work = new WorkingModel();
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder b = new StringBuilder();
+    b.append(String.format(
+        "%s:[error: %s, message: %s, stacktrace, %s, program: %s, command: %s, input: %s, output: %s, work: %s]",
+        this.getClass().getSimpleName(), error, message, getStacktrace(), program, command, input, output, work));
+    return b.toString();
+  }
+
+  /**
+   * @return the input
+   */
+  public InputModel getInput() {
+    return input;
+  }
+
+  /**
+   * @param input
+   *          the input to set
+   */
+  public void setInput(InputModel input) {
+    this.input = input;
+  }
+
+  /**
+   * @return the output
+   */
+  public OutputModel getOutput() {
+    return output;
+  }
+
+  /**
+   * @param output
+   *          the output to set
+   */
+  public void setOutput(OutputModel output) {
+    this.output = output;
+  }
+
+  /**
+   * @return the work
+   */
+  public WorkingModel getWork() {
+    return work;
+  }
+
+  /**
+   * @param work
+   *          the work to set
+   */
+  public void setWork(WorkingModel work) {
+    this.work = work;
+  }
+
+  /**
+   * @return the program
+   */
+  public ProgramModel getProgram() {
+    return program;
+  }
+
+  /**
+   * @param program
+   *          the program to set
+   */
+  public void setProgram(ProgramModel program) {
+    this.program = program;
+  }
+
+  /**
+   * @return the command
+   */
+  public CommandModel getCommand() {
+    return command;
+  }
+
+  /**
+   * @param command
+   *          the command to set
+   */
+  public void setCommand(CommandModel command) {
+    this.command = command;
+  }
+
+  /**
+   * @return the message
+   */
+  public String getMessage() {
+    return message;
+  }
+
+  /**
+   * @param message
+   *          the message to set
+   */
+  public void setMessage(String message) {
+    this.message = message;
+  }
+
+  /**
+   * @return the error
+   */
+  public boolean isError() {
+    return error;
+  }
+
+  /**
+   * @param error
+   *          the error to set
+   */
+  public void setError(boolean error) {
+    this.error = error;
+  }
+
+  /**
+   * @return the stacktrace
+   */
+  public String getStacktrace() {
+    return stacktrace;
+  }
+
+  /**
+   * @param stacktrace the stacktrace to set
+   */
+  public void setStacktrace(String stacktrace) {
+    this.stacktrace = stacktrace;
+  }
+}

+ 201 - 0
SPSEmulator-service/src/main/java/de/mcs/tools/sps/emulator/model/WorkingModel.java

@@ -0,0 +1,201 @@
+package de.mcs.tools.sps.emulator.model;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class WorkingModel {
+
+  public enum WORKINGSTATE {
+    NN, EMULATE
+  }
+
+  private byte registerA;
+  private byte registerB;
+  private byte registerC;
+  private byte registerD;
+  private byte registerE;
+  private byte registerF;
+
+  private byte page;
+  private short raddress;
+  private short address;
+
+  private List<Integer> stack = new ArrayList<>();
+  private WORKINGSTATE workingstate;
+
+  @Override
+  public String toString() {
+    StringBuilder b = new StringBuilder();
+    b.append(String.format(
+        "%s:[registerA: 0x%02x, registerB: 0x%02x, registerC: 0x%02x, registerD: 0x%02x, registerE: 0x%02x, registerF: 0x%02x, page: 0x%02x, address: 0x%04x, raddress: 0x%04x, stack: %d ]",
+        this.getClass().getSimpleName(), registerA, registerB, registerC, registerD, registerE, registerF, page,
+        address, raddress, stack.size()));
+    return b.toString();
+  }
+
+  /**
+   * @return the registerA
+   */
+  public byte getRegisterA() {
+    return registerA;
+  }
+
+  /**
+   * @param registerA
+   *          the registerA to set
+   */
+  public void setRegisterA(byte registerA) {
+    this.registerA = registerA;
+  }
+
+  /**
+   * @return the registerB
+   */
+  public byte getRegisterB() {
+    return registerB;
+  }
+
+  /**
+   * @param registerB
+   *          the registerB to set
+   */
+  public void setRegisterB(byte registerB) {
+    this.registerB = registerB;
+  }
+
+  /**
+   * @return the registerC
+   */
+  public byte getRegisterC() {
+    return registerC;
+  }
+
+  /**
+   * @param registerC
+   *          the registerC to set
+   */
+  public void setRegisterC(byte registerC) {
+    this.registerC = registerC;
+  }
+
+  /**
+   * @return the registerD
+   */
+  public byte getRegisterD() {
+    return registerD;
+  }
+
+  /**
+   * @param registerD
+   *          the registerD to set
+   */
+  public void setRegisterD(byte registerD) {
+    this.registerD = registerD;
+  }
+
+  /**
+   * @return the page
+   */
+  public byte getPage() {
+    return page;
+  }
+
+  /**
+   * @param page
+   *          the page to set
+   */
+  public void setPage(byte page) {
+    this.page = page;
+  }
+
+  /**
+   * @return the raddress
+   */
+  public short getRaddress() {
+    return raddress;
+  }
+
+  /**
+   * @param raddress
+   *          the raddress to set
+   */
+  public void setRaddress(short raddress) {
+    this.raddress = raddress;
+  }
+
+  /**
+   * @return the address
+   */
+  public short getAddress() {
+    return address;
+  }
+
+  /**
+   * @param address
+   *          the address to set
+   */
+  public void setAddress(short address) {
+    this.address = address;
+  }
+
+  /**
+   * @return the registerE
+   */
+  public byte getRegisterE() {
+    return registerE;
+  }
+
+  /**
+   * @param registerE
+   *          the registerE to set
+   */
+  public void setRegisterE(byte registerE) {
+    this.registerE = registerE;
+  }
+
+  /**
+   * @return the registerF
+   */
+  public byte getRegisterF() {
+    return registerF;
+  }
+
+  /**
+   * @param registerF
+   *          the registerF to set
+   */
+  public void setRegisterF(byte registerF) {
+    this.registerF = registerF;
+  }
+
+  /**
+   * @return the stack
+   */
+  public List<Integer> getStack() {
+    return stack;
+  }
+
+  /**
+   * @param stack
+   *          the stack to set
+   */
+  public void setStack(List<Integer> stack) {
+    this.stack = stack;
+  }
+
+  /**
+   * @return the workingstate
+   */
+  public WORKINGSTATE getWorkingstate() {
+    return workingstate;
+  }
+
+  /**
+   * @param workingstate
+   *          the workingstate to set
+   */
+  public void setWorkingstate(WORKINGSTATE workingstate) {
+    this.workingstate = workingstate;
+  }
+
+}

+ 0 - 0
src/main/java/de/mcs/tools/sps/emulator/package-info.java → SPSEmulator-service/src/main/java/de/mcs/tools/sps/emulator/package-info.java


+ 89 - 0
SPSEmulator-service/src/main/java/de/mcs/tools/sps/emulator/resources/EmulatorResource.java

@@ -0,0 +1,89 @@
+package de.mcs.tools.sps.emulator.resources;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response.Status;
+
+import de.mcs.tools.sps.emulator.Emulator;
+import de.mcs.tools.sps.emulator.EmulatorFactory;
+import de.mcs.tools.sps.emulator.model.CommandModel.COMMAND;
+import de.mcs.tools.sps.emulator.model.WebSessionModel;
+import de.mcs.tools.sps.emulator.model.WorkingModel.WORKINGSTATE;
+
+@Path("/emulator")
+@Produces(MediaType.APPLICATION_JSON)
+public class EmulatorResource {
+
+  @GET
+  public WebSessionModel newSession() {
+    return new WebSessionModel();
+  }
+
+  @POST
+  public WebSessionModel getSession(WebSessionModel model) {
+    model = checkModel(model);
+    Emulator emulator = EmulatorFactory.getEmulator(model);
+    COMMAND actualCommand = model.getCommand().getActualCommand();
+    try {
+      switch (actualCommand) {
+      case NN:
+        break;
+      case START:
+        emulator.start();
+        break;
+      case NEXT:
+        emulator.next();
+        break;
+      case STOP:
+        emulator.stop();
+        break;
+      case COMPILE:
+        emulator.compile();
+        break;
+      default:
+        break;
+      }
+    } catch (Exception e) {
+      String stackTrace = (e != null) ? "\n" + getStacktraceAsString(e) : "";
+      String errorMessage = String.format("%s%s", e.getClass().getName(),
+          e.getMessage() == null ? "" : ":" + e.getMessage());
+      model.setMessage(errorMessage);
+      model.setStacktrace(stackTrace);
+      model.setError(true);
+      e.printStackTrace();
+    }
+    if (model.isError()) {
+      throw new WebApplicationException(model.getMessage(), Status.BAD_REQUEST);
+    }
+    return model;
+  }
+
+  private WebSessionModel checkModel(WebSessionModel model) {
+    model.getProgram().rebuildHash();
+    COMMAND actualCommand = model.getCommand().getActualCommand();
+    model.getCommand().evaluateAvailableCommands(actualCommand);
+    if (model.getProgram().isModified() && WORKINGSTATE.EMULATE.equals(model.getWork().getWorkingstate())) {
+      model.setError(false);
+      model.setMessage("emulator active: can't changed the source.");
+    }
+    return model;
+  }
+
+  private String getStacktraceAsString(Throwable e) {
+    String stackTrace = "";
+    if (e != null) {
+      StringWriter writer = new StringWriter();
+      e.printStackTrace(new PrintWriter(writer));
+      stackTrace += writer.toString();
+    }
+    return stackTrace;
+  }
+
+}

+ 0 - 0
src/main/java/de/mcs/tools/sps/exceptions/HardwareException.java → SPSEmulator-service/src/main/java/de/mcs/tools/sps/exceptions/HardwareException.java


+ 0 - 0
src/main/java/de/mcs/tools/sps/exceptions/IllegalArgument.java → SPSEmulator-service/src/main/java/de/mcs/tools/sps/exceptions/IllegalArgument.java


Vissa filer visades inte eftersom för många filer har ändrats