diff --git a/cli/pom.xml b/cli/pom.xml
index 6338690..dd509f2 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -5,7 +5,7 @@
de.spinscale.maxcube
maxcube
- ${version}
+ 0.0.2-SNAPSHOT
cli
@@ -15,7 +15,7 @@
de.spinscale.maxcube
client
- ${version}
+ ${project.parent.version}
io.airlift
diff --git a/cli/src/main/java/de/spinscale/maxcube/cli/Cli.java b/cli/src/main/java/de/spinscale/maxcube/cli/Cli.java
index 0c48087..5c01279 100644
--- a/cli/src/main/java/de/spinscale/maxcube/cli/Cli.java
+++ b/cli/src/main/java/de/spinscale/maxcube/cli/Cli.java
@@ -55,14 +55,15 @@ public static void main(String[] args) throws IOException {
// argument parsing
io.airlift.airline.Cli.CliBuilder builder = io.airlift.airline.Cli.builder("eq3")
- .withCommands(Help.class, Info.class, Discover.class, Version.class, Boost.class, Holiday.class)
- .withDescription("Tool to manage Max!EQ3 cubes from the command line")
- .withDefaultCommand(Help.class);
+ .withCommands(Help.class, Info.class, Discover.class, Version.class, Boost.class, Holiday.class,
+ ManualTemperature.class)
+ .withDescription("Tool to manage Max!EQ3 cubes from the command line")
+ .withDefaultCommand(Help.class);
builder.withGroup("report")
- .withDescription("Reporting to a configurable backend")
- .withCommands(ReportCli.class)
- .withDefaultCommand(Help.class);
+ .withDescription("Reporting to a configurable backend")
+ .withCommands(ReportCli.class)
+ .withDefaultCommand(Help.class);
io.airlift.airline.Cli gitParser = builder.build();
gitParser.parse(args).run();
@@ -138,7 +139,7 @@ public abstract static class CubeHostCommand extends Eq3Command {
@Arguments(description = "host of cube to query")
public String host;
- abstract void doRun(String host) throws Exception ;
+ abstract void doRun(String host) throws Exception;
@Override
void doRun() throws Exception {
@@ -159,7 +160,7 @@ public static class Discover extends Eq3Command {
@Arguments(description = "interface to scan on, i.e. eth0/en0", required = true)
public String networkInterface;
- @Option(name = { "-t", "timeout" } , description = "Time to wait for responses, in seconds, defaults to 2")
+ @Option(name = {"-t", "timeout"}, description = "Time to wait for responses, in seconds, defaults to 2")
public Integer timeout = 2;
public void doRun() throws Exception {
@@ -172,7 +173,7 @@ public void doRun() throws Exception {
@Command(name = "boost", description = "Boost a room")
public static class Boost extends CubeHostCommand {
- @Option(name = { "-r", "--room" } , description = "The name of the room to boost", required = true)
+ @Option(name = {"-r", "--room"}, description = "The name of the room to boost", required = true)
public String roomName;
public void doRun(String host) throws Exception {
@@ -190,13 +191,13 @@ public void doRun(String host) throws Exception {
@Command(name = "holiday", description = "Set holiday mode for a room")
public static class Holiday extends CubeHostCommand {
- @Option(name = { "-r", "--room" } , description = "The name of the room to boost", required = true)
+ @Option(name = {"-r", "--room"}, description = "The name of the room to boost", required = true)
public String roomName;
- @Option(name = { "-d", "--duration" } , description = "The duration of boosting", required = true)
+ @Option(name = {"-d", "--duration"}, description = "The duration of boosting", required = true)
public String duration;
- @Option(name = { "-t", "--temperature" } , description = "The temperature in °C", required = true)
+ @Option(name = {"-t", "--temperature"}, description = "The temperature in °C", required = true)
public Integer temperature;
public void doRun(String host) throws Exception {
@@ -215,6 +216,28 @@ public void doRun(String host) throws Exception {
}
}
+ @Command(name = "manualtemp", description = "Set the temperature for a room (manual mode)")
+ public static class ManualTemperature extends CubeHostCommand {
+
+ @Option(name = {"-r", "--room"}, description = "The name of the room to boost", required = true)
+ public String roomName;
+
+ @Option(name = {"-t", "--temperature"}, description = "The temperature in °C in '.5'-steps from 0 to 31.",
+ required = true)
+ public Double temperature;
+
+ public void doRun(String host) throws Exception {
+ try (CubeClient client = new SocketCubeClient(host)) {
+ Cube cube = client.connect();
+ Room room = cube.findRoom(roomName);
+ boolean success = client.setManualTemp(room, temperature);
+ if (!success) {
+ logger.error("Executing manualtemp call was not successful!");
+ }
+ }
+ }
+ }
+
@Command(name = "version", description = "Display version and exit")
public static class Version extends Eq3Command {
@@ -238,8 +261,10 @@ public void doRun(String host) throws Exception {
}
@Command(name = "info", description = "Return some standard information about the cube. Alias for `report cli`")
- public static class Info extends AbstractCliReport {}
+ public static class Info extends AbstractCliReport {
+ }
@Command(name = "cli", description = "Return some standard information about the cube to the terminal")
- public static class ReportCli extends AbstractCliReport {}
+ public static class ReportCli extends AbstractCliReport {
+ }
}
diff --git a/client/pom.xml b/client/pom.xml
index 46d8fb0..1b93139 100644
--- a/client/pom.xml
+++ b/client/pom.xml
@@ -5,7 +5,7 @@
de.spinscale.maxcube
maxcube
- ${version}
+ 0.0.2-SNAPSHOT
client
diff --git a/client/src/main/java/de/spinscale/maxcube/client/CubeClient.java b/client/src/main/java/de/spinscale/maxcube/client/CubeClient.java
index 1addee7..57909c4 100644
--- a/client/src/main/java/de/spinscale/maxcube/client/CubeClient.java
+++ b/client/src/main/java/de/spinscale/maxcube/client/CubeClient.java
@@ -40,4 +40,12 @@ public interface CubeClient extends Closeable {
* @return true if the command was send successfully, false otherwise
*/
boolean holiday(Room room, LocalDateTime endTime, int temperature) throws Exception;
+
+ /**
+ * Sets manually the temperature for a room
+ * @param room The room to set the temperature
+ * @param temperature The target temperature in degrees celsius
+ * @return true if the command was send successfully, false otherwise
+ */
+ boolean setManualTemp(Room room, double temperature) throws Exception;
}
diff --git a/client/src/main/java/de/spinscale/maxcube/client/SocketCubeClient.java b/client/src/main/java/de/spinscale/maxcube/client/SocketCubeClient.java
index bed5c93..7c21299 100644
--- a/client/src/main/java/de/spinscale/maxcube/client/SocketCubeClient.java
+++ b/client/src/main/java/de/spinscale/maxcube/client/SocketCubeClient.java
@@ -89,6 +89,12 @@ public boolean holiday(Room room, LocalDateTime endTime, int temperature) throws
return this.sendSetTemperatureRequest(data);
}
+ @Override
+ public boolean setManualTemp(Room room, double temperature) throws Exception {
+ String data = Generator.writeSetTemperatureRequest(room, temperature);
+ return this.sendSetTemperatureRequest(data);
+ }
+
private boolean sendSetTemperatureRequest(String base64encodedData) throws Exception {
String dataToSend = base64encodedData + "\r\n";
socket.getOutputStream().write(dataToSend.getBytes(UTF_8));
diff --git a/client/src/main/java/de/spinscale/maxcube/data/Generator.java b/client/src/main/java/de/spinscale/maxcube/data/Generator.java
index a2e7280..f722d96 100644
--- a/client/src/main/java/de/spinscale/maxcube/data/Generator.java
+++ b/client/src/main/java/de/spinscale/maxcube/data/Generator.java
@@ -44,6 +44,56 @@ public static void writeRfAddress(int address, ByteArrayOutputStream bos) {
bos.write(address);
}
+ public static String writeBoostRequest(Room room) throws IOException {
+ Device thermostat = room.findThermostat();
+ try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
+ writeSetTemperatureRequest(bos, room.getId(), thermostat.getRfaddress());
+ // mode & temperature, boost mode is 11 at the beginning, temperature does not matter
+ bos.write(192);
+
+ String base64 = Base64.getEncoder().encodeToString(bos.toByteArray());
+ return "s:" + base64;
+ }
+ }
+
+ /**
+ * https://github.com/Bouni/max-cube-protocol/blob/master/S-Message.md
+ */
+ public static String writeSetTemperatureRequest(Room room, double temperature) throws
+ IOException {
+ if (temperature < 0 || temperature > 31) {
+ throw new IllegalArgumentException("Temperature must be between 0 and 31 °C");
+ }
+
+ double doubledTemp = temperature * 2;
+ double isIntegerOrDotFive = doubledTemp % 2;
+
+ if (isIntegerOrDotFive == 1 || isIntegerOrDotFive == 0) {
+ Device thermostat = room.findThermostat();
+ try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
+ writeSetTemperatureRequest(bos, room.getId(), thermostat.getRfaddress());
+ /*
+ hex: | 66 |
+ dual: | 0110 1100 |
+ |||| ||||
+ ||++-++++-- temperature: 10 1100 -> 38 = temp * 2
+ || (to get the temperature, the value must be divided by 2: 38/2 = 19)
+ ++--------- mode:
+ 00=auto/weekly program
+ 01=manual ( => 0100 0000 => Decimal 64)
+ 10=vacation
+ 11=boost */
+ bos.write(64 + (int) doubledTemp);
+
+ String base64 = Base64.getEncoder().encodeToString(bos.toByteArray());
+ return "s:" + base64;
+ }
+ }
+ else {
+ throw new IllegalArgumentException("only xx.5 is supported");
+ }
+ }
+
public static String writeHolidayRequest(Room room, LocalDateTime endDate, int temperature) throws IOException {
if (temperature > 31) {
throw new IllegalArgumentException("Temperature must be between 0 and 31 °C");
@@ -106,22 +156,13 @@ public static void writeDateTimeUntil(LocalDateTime dateTime, ByteArrayOutputStr
bos.write(halfhours);
}
- public static String writeBoostRequest(Room room) throws IOException {
- Device thermostat = room.findThermostat();
- try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
- writeSetTemperatureRequest(bos, room.getId(), thermostat.getRfaddress());
- // mode & temperature, boost mode is 11 at the beginning, temperature does not matter
- bos.write(192);
-
- String base64 = Base64.getEncoder().encodeToString(bos.toByteArray());
- return "s:" + base64;
- }
- }
-
+ /**
+ * https://github.com/Bouni/max-cube-protocol/blob/master/S-Message.md
+ */
private static void writeSetTemperatureRequest(ByteArrayOutputStream bos, int roomId, int thermostatRfAddress) {
bos.write(0); // unknown
bos.write(4); // rf flags
- bos.write(64); // command
+ bos.write(64); // 0x40 => 64 (decimal) => Set temperature
// rf address from
Generator.writeRfAddress(0, bos);
diff --git a/client/src/test/java/de/spinscale/maxcube/client/MinaCubeClient.java b/client/src/test/java/de/spinscale/maxcube/client/MinaCubeClient.java
index 8ad5d17..b39557c 100644
--- a/client/src/test/java/de/spinscale/maxcube/client/MinaCubeClient.java
+++ b/client/src/test/java/de/spinscale/maxcube/client/MinaCubeClient.java
@@ -149,4 +149,9 @@ public void exceptionCaught(IoSession session, Throwable cause) throws Exception
session.closeNow();
}
}
+
+ @Override
+ public boolean setManualTemp(Room room, double temperature) throws Exception {
+ return false;
+ }
}
diff --git a/client/src/test/java/de/spinscale/maxcube/data/GeneratorTest.java b/client/src/test/java/de/spinscale/maxcube/data/GeneratorTest.java
index 1b6bdb9..d57a78f 100644
--- a/client/src/test/java/de/spinscale/maxcube/data/GeneratorTest.java
+++ b/client/src/test/java/de/spinscale/maxcube/data/GeneratorTest.java
@@ -174,4 +174,30 @@ public void testGeneratorHolidayRequest() throws IOException {
String output = Generator.writeHolidayRequest(room, dateTime, 19);
assertThat(output, is(expectedOutput));
}
+
+ @Test
+ public void testSetTemperatureRequest() throws IOException {
+ Room room = new Room(1, "foo", 123456);
+ room.getDevices().add(new Device(DeviceType.THERMOSTAST, "foo", "serial", 1039085));
+
+ assertThat("Illegal temperature set", testTemperatureBounds(room, -1));
+ assertThat("Illegal temperature set", testTemperatureBounds(room, 32));
+ assertThat("Illegal temperature set", testTemperatureBounds(room, 18.8));
+ assertThat("Illegal temperature set", testTemperatureBounds(room, 18.3));
+
+ String output = Generator.writeSetTemperatureRequest(room, 17.5);
+ assertThat(output, is("s:AARAAAAAD9rtAWM="));
+ }
+
+ private boolean testTemperatureBounds(Room room, double temp) throws IOException {
+ boolean exceptionWasThrown = false;
+ try{
+ Generator.writeSetTemperatureRequest(room, temp);
+ }
+ catch(IllegalArgumentException e){
+ exceptionWasThrown = true;
+ }
+ return exceptionWasThrown;
+ }
+
}
diff --git a/pom.xml b/pom.xml
index 094453e..8aa0b65 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,12 +5,13 @@
de.spinscale.maxcube
maxcube
pom
- ${version}
+ 0.0.2-SNAPSHOT
maxcube-java
cli
client
+ web
@@ -19,7 +20,6 @@
1.8
1.8
2.5.0
- 0.0.2-SNAPSHOT
@@ -243,14 +243,14 @@
true
-
+
diff --git a/setup_pi.sh b/setup_pi.sh
new file mode 100644
index 0000000..c5a983a
--- /dev/null
+++ b/setup_pi.sh
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+
+echo "*** Use 'sudo raspi-config' to setup overclocking, networking, locale and timezone ***"
+echo "*** Use 'passwd' to change default password ***"
+
+sudo systemctl enable ssh
+sudo systemctl start ssh
+sudo apt-get -y install docker git
diff --git a/web/Dockerfile b/web/Dockerfile
new file mode 100644
index 0000000..ae6db0c
--- /dev/null
+++ b/web/Dockerfile
@@ -0,0 +1,8 @@
+FROM airhacks/wildfly
+COPY ./target/maxcube-web.war ${DEPLOYMENT_DIR}
+ENV EQ3_HOST=192.168.178.28
+
+# docker exec -i -t micro /bin/bash
+# tail -f /opt/wildfly-11.0.0.Final/standalone/log/server.log
+# OR
+# docker logs micro
diff --git a/web/pom.xml b/web/pom.xml
new file mode 100644
index 0000000..af18c77
--- /dev/null
+++ b/web/pom.xml
@@ -0,0 +1,36 @@
+
+ 4.0.0
+
+ de.spinscale.maxcube
+ maxcube
+ 0.0.2-SNAPSHOT
+
+
+ war
+
+ web
+ maxcube web interface
+
+
+
+ de.spinscale.maxcube
+ cli
+ ${project.parent.version}
+
+
+ javax
+ javaee-api
+ 7.0
+ provided
+
+
+
+ 1.8
+ 1.8
+ false
+
+
+ maxcube-web
+
+
diff --git a/web/src/main/java/de/spinscale/maxcube/cli/JAXRSConfiguration.java b/web/src/main/java/de/spinscale/maxcube/cli/JAXRSConfiguration.java
new file mode 100644
index 0000000..d030a89
--- /dev/null
+++ b/web/src/main/java/de/spinscale/maxcube/cli/JAXRSConfiguration.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright [2018] [Markus Schwarz]
+ *
+ * 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.spinscale.maxcube.cli;
+
+import javax.ws.rs.ApplicationPath;
+import javax.ws.rs.core.Application;
+
+/**
+ * Configures a JAX-RS endpoint. Delete this class, if you are not exposing
+ * JAX-RS resources in your application.
+ *
+ * @author airhacks.com
+ */
+@ApplicationPath("resources")
+public class JAXRSConfiguration extends Application {
+
+}
diff --git a/web/src/main/java/de/spinscale/maxcube/cli/Thermostat.java b/web/src/main/java/de/spinscale/maxcube/cli/Thermostat.java
new file mode 100644
index 0000000..71c6cc0
--- /dev/null
+++ b/web/src/main/java/de/spinscale/maxcube/cli/Thermostat.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright [2018] [Markus Schwarz]
+ *
+ * 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.spinscale.maxcube.cli;
+
+import javax.ws.rs.*;
+import javax.ws.rs.core.MediaType;
+
+/**
+ * @author Markus Schwarz
+ */
+@Path("thermostat")
+public class Thermostat {
+
+ /* http://localhost:8080/maxcube-web/resources/thermostat */
+ @GET
+ public String message() {
+ return "Only POST requests were supported";
+ }
+
+
+ /* curl -v --data "room=Kitchen&temperature=20.5" http://localhost:8080/maxcube-web/resources/thermostat */
+ @POST
+ @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+ public String setTemperature(@FormParam("room") String room, @FormParam("temperature") Double temperature) throws Exception {
+ Cli.ManualTemperature manualTemperature = new Cli.ManualTemperature();
+ manualTemperature.roomName = room;
+ manualTemperature.temperature = temperature;
+ manualTemperature.doRun();
+ return "setTemperature() executed";
+ }
+}
diff --git a/web/src/main/webapp/WEB-INF/beans.xml b/web/src/main/webapp/WEB-INF/beans.xml
new file mode 100644
index 0000000..2777559
--- /dev/null
+++ b/web/src/main/webapp/WEB-INF/beans.xml
@@ -0,0 +1,6 @@
+
+
+
diff --git a/web/transferDockerImageToPI.sh b/web/transferDockerImageToPI.sh
new file mode 100644
index 0000000..333f91d
--- /dev/null
+++ b/web/transferDockerImageToPI.sh
@@ -0,0 +1,13 @@
+#!/usr/bin/env bash
+
+docker history airhacks/micro
+docker save -o max-web-docker-image dad12a31defd
+scp max-web-docker-image pi:
+ssh pi
+curl -sSL get.docker.com | sh
+sudo docker load -i max-web-docker-image
+
+
+# in one command:
+#
+# docker save dad12a31defd | bzip2 | ssh pi 'bunzip2 | docker load'