diff --git a/casek-floor-heating/casek-backend/main.js b/casek-floor-heating/casek-backend/main.js
new file mode 100755
index 0000000..524139f
--- /dev/null
+++ b/casek-floor-heating/casek-backend/main.js
@@ -0,0 +1,175 @@
+load("jquery.js")
+
+/*
+ * Mappings between Wappsto names and Uppaal Stratego ids
+ */
+
+const d2s_temperature = {
+ "TEMP AND HUMIDITY 1": 3,
+ "TEMP AND HUMIDITY 2": 10,
+ "TEMP AND HUMIDITY 3": 1,
+ "TEMP AND HUMIDITY 4": 9,
+ "TEMP AND HUMIDITY 5": 2,
+ "TEMP AND HUMIDITY 6": 4,
+ "TEMP AND HUMIDITY 7": 8,
+ "TEMP AND HUMIDITY 8": 5,
+ "TEMP AND HUMIDITY 9": 6,
+ "TEMP AND HUMIDITY10": 7,
+ "TEMP AND HUMIDITY11": 11,
+ "TEMP AND HUMIDITY12": 0,
+};
+
+const d2s_target = {
+ "SETPOINT1": 3,
+ "SETPOINT2": 10,
+ "SETPOINT3": 11,
+ "SETPOINT4": 1,
+ "SETPOINT5": 9,
+ "SETPOINT6": 6,
+ "SETPOINT7": 5,
+ "SETPOINT8": 2,
+ "SETPOINT9": 4,
+ "SETPOINT10": 8,
+};
+
+const d2s_valves = {
+ "RL 1": 3,
+ "RL 2": 10,
+ "RL 3": 11,
+ "RL 4": 1,
+ "RL 5": 9,
+ "RL 6": 6,
+ "RL 7": 5,
+ "RL 8": 2,
+ "RL 9": 4,
+ "RL 10": 8,
+};
+
+/*
+ * Get Wappsto devices and data
+ */
+
+const dev_sensors = getDevice({name: "TEMP AND HUMIDITY"}, {quantity: 12});
+const dev_target = getDevice({name: "Relay Box Set Points"}, {quantity: 1})[0];
+const dev_valves = getDevice({name: "Relay Box"}, {quantity: 2})[1];
+let data = getData()[0];
+
+function run_stratego() {
+ // Initial values
+ let t = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
+ let tg = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
+ let te = [0, 0, 0, 0];
+ let v = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
+ let cap = [9, 11, 13, 12, 11, 13, 9, 7, 14, 19, 31];
+ let max = 80;
+
+ // Get data from temperature device
+ for (let i in dev_sensors) {
+ // Get objects from Wappsto
+ let sensor = dev_sensors[i];
+ let name = sensor.get("name").toUpperCase();
+
+ // Get values from Wappsto
+ let temperature = Number(
+ sensor.get("value")
+ .findWhere({type: "Temperature"}).get("state")
+ .findWhere({type: "Report"}).get("data")
+ );
+
+ // Update arrays
+ if (d2s_temperature[name] === 0) {
+ te[0] = temperature;
+ } else {
+ t[d2s_temperature[name] - 1] = temperature;
+ }
+ }
+
+ // Get data from target relays
+ dev_target.get("value").each(function (value) {
+ // Get objects from Wappsto
+ let name = value.get("name").toUpperCase();
+
+ // Get values from Wappsto
+ let target = Number(
+ value.get("state").findWhere({type: "Report"}).get("data")
+ );
+
+ // Update arrays
+ tg[d2s_target[name] - 1] = target;
+ });
+
+ // Get data from valve relays
+ dev_valves.get("value").each(function (value) {
+ // Get objects from Wappsto
+ let name = value.get("name").toUpperCase();
+
+ // Filter values we don't need
+ if (name.startsWith("RL")) {
+ // Get values from Wappsto
+ let valve = Number(
+ value.get("state").findWhere({type: "Report"}).get("data")
+ );
+
+ // Update arrays
+ v[d2s_valves[name] - 1] = valve;
+ }
+ });
+
+ // Update weather forecast array (just use current temperature for now)
+ te[1] = te[0];
+ te[2] = te[0];
+ te[3] = te[0];
+
+ // Print values
+ console.log("t: [ " + t.map(x => (x<10?" ":"") + x.toFixed(1)).join(', ') + " ]");
+ console.log("tg: [ " + tg.map(x => (x<10?" ":"") + x.toFixed(1)).join(', ') + " ]");
+ console.log("te: [ " + te.map(x => (x<10?" ":"") + x.toFixed(1)).join(', ') + " ]");
+ console.log("v: [ " + v.map(x => (x<10?" ":"") + x.toFixed(0)).join(', ') + " ]");
+ console.log("cap: [ " + cap.map(x => (x<10?" ":"") + x.toFixed(0)).join(', ') + " ]");
+ console.log("max: " + max);
+
+ // Build URL
+ let url = "/";
+ t.forEach(function (x) {
+ url += x + "_"
+ });
+ tg.forEach(function (x) {
+ url += x + "_"
+ });
+ te.forEach(function (x) {
+ url += x + "_"
+ });
+ v.forEach(function (x) {
+ url += x + "_"
+ });
+ cap.forEach(function (x) {
+ url += x + "_"
+ });
+ url += max;
+
+ // Call Uppaal Stratego webservice
+ $.ajax({
+ method: "GET",
+ url: "/external/casek-uppaal" + url + "?proto=http",
+ headers: {
+ "x-session": sessionID
+ }
+ }).done(function (strategy) {
+ console.log("s: [ " + strategy.slice(1, -1).split(" ").map(
+ x => (Number(x)<10?" ":"") + Number(x).toFixed(0)).join(', ') + " ]"
+ );
+ data.save({
+ ":id": data.get(":id"),
+ ":type": data.get(":type"),
+ "strategy": strategy,
+ "timestamp": Date.now(),
+ }, {
+ wait: true,
+ patch: true
+ });
+ }).fail(function (error) {});
+}
+
+// Run Uppaal Stratego now and in regular intervals
+run_stratego();
+setInterval(function() { run_stratego(); }, (15*60)*1000);
diff --git a/casek-floor-heating/casek-frontend/README.md b/casek-floor-heating/casek-frontend/README.md
new file mode 100644
index 0000000..9157368
--- /dev/null
+++ b/casek-floor-heating/casek-frontend/README.md
@@ -0,0 +1,4 @@
+The files 'att.svg', 'bat000.svg', 'bat020.svg', 'bat040.svg',
+'bat060.svg', 'bat080.svg', 'bat100.svg', 'clock.svg', and 'flame.svg'
+were acquired from [openclipart.org](), thus are released under the
+[Creative Commons Zero 1.0 Public Domain License](http://creativecommons.org/publicdomain/zero/1.0/).
diff --git a/casek-floor-heating/casek-frontend/att.svg b/casek-floor-heating/casek-frontend/att.svg
new file mode 100755
index 0000000..4b5c8a3
--- /dev/null
+++ b/casek-floor-heating/casek-frontend/att.svg
@@ -0,0 +1,51 @@
+
+
+
diff --git a/casek-floor-heating/casek-frontend/bat000.svg b/casek-floor-heating/casek-frontend/bat000.svg
new file mode 100755
index 0000000..a66d932
--- /dev/null
+++ b/casek-floor-heating/casek-frontend/bat000.svg
@@ -0,0 +1,6 @@
+
+
diff --git a/casek-floor-heating/casek-frontend/bat020.svg b/casek-floor-heating/casek-frontend/bat020.svg
new file mode 100755
index 0000000..de7cf94
--- /dev/null
+++ b/casek-floor-heating/casek-frontend/bat020.svg
@@ -0,0 +1,9 @@
+
+
diff --git a/casek-floor-heating/casek-frontend/bat040.svg b/casek-floor-heating/casek-frontend/bat040.svg
new file mode 100755
index 0000000..109b66a
--- /dev/null
+++ b/casek-floor-heating/casek-frontend/bat040.svg
@@ -0,0 +1,10 @@
+
+
diff --git a/casek-floor-heating/casek-frontend/bat060.svg b/casek-floor-heating/casek-frontend/bat060.svg
new file mode 100755
index 0000000..1e8e67b
--- /dev/null
+++ b/casek-floor-heating/casek-frontend/bat060.svg
@@ -0,0 +1,13 @@
+
+
diff --git a/casek-floor-heating/casek-frontend/bat080.svg b/casek-floor-heating/casek-frontend/bat080.svg
new file mode 100755
index 0000000..b9ae591
--- /dev/null
+++ b/casek-floor-heating/casek-frontend/bat080.svg
@@ -0,0 +1,14 @@
+
+
diff --git a/casek-floor-heating/casek-frontend/bat100.svg b/casek-floor-heating/casek-frontend/bat100.svg
new file mode 100755
index 0000000..1d93511
--- /dev/null
+++ b/casek-floor-heating/casek-frontend/bat100.svg
@@ -0,0 +1,15 @@
+
+
diff --git a/casek-floor-heating/casek-frontend/casek_logo.png b/casek-floor-heating/casek-frontend/casek_logo.png
new file mode 100755
index 0000000..4d13282
Binary files /dev/null and b/casek-floor-heating/casek-frontend/casek_logo.png differ
diff --git a/casek-floor-heating/casek-frontend/ciss_logo.png b/casek-floor-heating/casek-frontend/ciss_logo.png
new file mode 100755
index 0000000..95761b2
Binary files /dev/null and b/casek-floor-heating/casek-frontend/ciss_logo.png differ
diff --git a/casek-floor-heating/casek-frontend/clock.svg b/casek-floor-heating/casek-frontend/clock.svg
new file mode 100755
index 0000000..ecbb5e4
--- /dev/null
+++ b/casek-floor-heating/casek-frontend/clock.svg
@@ -0,0 +1,118 @@
+
+
diff --git a/casek-floor-heating/casek-frontend/flame.svg b/casek-floor-heating/casek-frontend/flame.svg
new file mode 100755
index 0000000..5b8db3f
--- /dev/null
+++ b/casek-floor-heating/casek-frontend/flame.svg
@@ -0,0 +1,175 @@
+
+
+
+
diff --git a/casek-floor-heating/casek-frontend/index.html b/casek-floor-heating/casek-frontend/index.html
new file mode 100755
index 0000000..fe2a9a5
--- /dev/null
+++ b/casek-floor-heating/casek-frontend/index.html
@@ -0,0 +1,246 @@
+
+
+
+
+
+
+ Casek: Floor Heating Prototype
+
+
+
+
+
+
+
+
+
+
+
+
Prototype produced by
+

+

+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/casek-floor-heating/casek-frontend/script.js b/casek-floor-heating/casek-frontend/script.js
new file mode 100755
index 0000000..6b62c80
--- /dev/null
+++ b/casek-floor-heating/casek-frontend/script.js
@@ -0,0 +1,173 @@
+/*
+ * Mappings between Wappsto names and HTML ids
+ */
+
+const d2i_temperature = {
+ "TEMP AND HUMIDITY 1": "t3",
+ "TEMP AND HUMIDITY 2": "t10",
+ "TEMP AND HUMIDITY 3": "t1",
+ "TEMP AND HUMIDITY 4": "t9",
+ "TEMP AND HUMIDITY 5": "t2",
+ "TEMP AND HUMIDITY 6": "t4",
+ "TEMP AND HUMIDITY 7": "t8",
+ "TEMP AND HUMIDITY 8": "t5",
+ "TEMP AND HUMIDITY 9": "t6",
+ "TEMP AND HUMIDITY10": "t7",
+ "TEMP AND HUMIDITY11": "t11",
+ "TEMP AND HUMIDITY12": "t0",
+};
+
+const d2i_clock = {
+ "TEMP AND HUMIDITY 1": "c3",
+ "TEMP AND HUMIDITY 2": "c10",
+ "TEMP AND HUMIDITY 3": "c1",
+ "TEMP AND HUMIDITY 4": "c9",
+ "TEMP AND HUMIDITY 5": "c2",
+ "TEMP AND HUMIDITY 6": "c4",
+ "TEMP AND HUMIDITY 7": "c8",
+ "TEMP AND HUMIDITY 8": "c5",
+ "TEMP AND HUMIDITY 9": "c6",
+ "TEMP AND HUMIDITY10": "c7",
+ "TEMP AND HUMIDITY11": "c11",
+ "TEMP AND HUMIDITY12": "c0",
+};
+
+const d2i_battery = {
+ "TEMP AND HUMIDITY 1": "b3",
+ "TEMP AND HUMIDITY 2": "b10",
+ "TEMP AND HUMIDITY 3": "b1",
+ "TEMP AND HUMIDITY 4": "b9",
+ "TEMP AND HUMIDITY 5": "b2",
+ "TEMP AND HUMIDITY 6": "b4",
+ "TEMP AND HUMIDITY 7": "b8",
+ "TEMP AND HUMIDITY 8": "b5",
+ "TEMP AND HUMIDITY 9": "b6",
+ "TEMP AND HUMIDITY10": "b7",
+ "TEMP AND HUMIDITY11": "b11",
+ "TEMP AND HUMIDITY12": "b0",
+};
+
+const d2i_target = {
+ "SETPOINT1": "tg3",
+ "SETPOINT2": "tg10",
+ "SETPOINT3": "tg11",
+ "SETPOINT4": "tg1",
+ "SETPOINT5": "tg9",
+ "SETPOINT6": "tg6",
+ "SETPOINT7": "tg5",
+ "SETPOINT8": "tg2",
+ "SETPOINT9": "tg4",
+ "SETPOINT10": "tg8",
+};
+
+/*
+ * Get Wappsto devices and data
+ */
+
+const dev_sensors = getDevice({name: "TEMP AND HUMIDITY"}, {quantity: 12});
+const dev_target = getDevice({name: "Relay Box Set Points"}, {quantity: 1})[0];
+let data = getData()[0];
+
+/**
+ * Function for updating the display with the strategy from Uppaal Stratego
+ */
+function update_strategy() {
+ let strategy = data.get("strategy");
+ let timestamp = new Date(data.get("timestamp"));
+ let valves = strategy.slice(1, -1).split(" ");
+ for (let i in valves) {
+ let element = document.getElementById("v" + (Number(i)+1));
+ if (Number(valves[i]) > 0) {
+ element.style.display = "block";
+ } else {
+ element.style.display = "none";
+ }
+ }
+ document.getElementById("c_strategy").innerHTML = timestamp.toUTCString();
+}
+
+/**
+ * Function for updating the display with data from Wappsto
+ */
+function update_display() {
+ // Update with info from sensors
+ for (let i in dev_sensors) {
+ // Get objects from Wappsto
+ let sensor = dev_sensors[i];
+ let values = sensor.get("value");
+ let report = values
+ .findWhere({type: "Temperature"}).get("state")
+ .findWhere({type: "Report"});
+ let name = sensor.get("name").toUpperCase();
+
+ // Get values from Wappsto
+ let temperature = Number(
+ report.get("data")
+ );
+ let timestamp = new Date(
+ report.get("timestamp")
+ );
+ let battery = Number(
+ values
+ .findWhere({type: "Battery"}).get("state")
+ .findWhere({type: "Report"}).get("data")
+ );
+
+ // Get HTML elements
+ let t_span = document.getElementById(d2i_temperature[name]);
+ let b_img = document.getElementById(d2i_battery[name]);
+ let c_img = document.getElementById(d2i_clock[name]);
+
+ // Update display
+ t_span.innerHTML = temperature.toFixed(1);
+ b_img.title = "Battery Level: " + battery.toFixed(0) + "%";
+ if (battery > 96) {
+ b_img.src = document.getElementById("bat100").src;
+ } else if (battery > 88) {
+ b_img.src = document.getElementById("bat080").src;
+ } else if (battery > 80) {
+ b_img.src = document.getElementById("bat060").src;
+ } else if (battery > 70) {
+ b_img.src = document.getElementById("bat040").src;
+ } else if (battery > 50) {
+ b_img.src = document.getElementById("bat020").src;
+ } else {
+ b_img.src = document.getElementById("bat000").src;
+ }
+ c_img.title = "";
+ if ((Date.now() - timestamp.getTime()) / 1000 > 3600) {
+ c_img.src = document.getElementById("attention").src;
+ c_img.title = "Data is more than an hour old!\n";
+ } else {
+ c_img.src = document.getElementById("clock").src;
+ }
+ c_img.title += "Last updated: " + timestamp.toUTCString();
+ }
+
+ // Update set points
+ dev_target.get("value").each(function (value) {
+ let target = Math.round(
+ value.get("state").findWhere({type: "Report"}).get("data")
+ );
+ let name = value.get("name").toUpperCase();
+ let tg_span = document.getElementById(d2i_target[name]);
+ tg_span.innerHTML = target.toFixed(0);
+ });
+}
+
+/**
+ * Function that will be called on load of HTML body
+ */
+function on_load() {
+ // Register callback for backend changes and update it once
+ data.on("change", function () {
+ update_strategy();
+ });
+ update_strategy();
+
+ // Add regular updates of display
+ setInterval(function () {
+ update_display();
+ }, 2000);
+ update_display();
+}
diff --git a/casek-floor-heating/casek-frontend/seluxit_logo.png b/casek-floor-heating/casek-frontend/seluxit_logo.png
new file mode 100755
index 0000000..eacded6
Binary files /dev/null and b/casek-floor-heating/casek-frontend/seluxit_logo.png differ
diff --git a/casek-floor-heating/casek-frontend/style.css b/casek-floor-heating/casek-frontend/style.css
new file mode 100755
index 0000000..c1bb0ac
--- /dev/null
+++ b/casek-floor-heating/casek-frontend/style.css
@@ -0,0 +1,210 @@
+
+/* Reset */
+
+* {
+ border-spacing: 0;
+ font-family: Tahoma, Geneva, sans-serif;
+ margin: 0;
+ padding: 0;
+}
+
+/* Global */
+
+body {
+ background-color: #00b0f2;
+ height: 100%;
+ margin: 3.6vh 2vw;
+ overflow: hidden;
+}
+
+h1 {
+ font-family: Verdana, Geneva, sans-serif;
+}
+
+.sm_caps {
+ font-variant: small-caps;
+}
+
+/* Page Header */
+
+#page_header {
+ margin-bottom: 3.6vh;
+ position: relative;
+ height: 9vh;
+}
+
+#page_header img {
+ max-width: 14vw;
+ max-height: 9vh;
+ margin-right: 2vw;
+}
+
+#page_header .box {
+ background-color: white;
+ border-radius: 25px;
+ height: 100%;
+ margin: auto;
+ position: absolute;
+ right: 0;
+ text-align: center;
+ top: 0;
+ width: 80vw;
+}
+
+#page_header h1 {
+ font-size: 5vh;
+ font-weight: normal;
+ max-height: 6vh;
+ overflow: hidden;
+}
+
+#page_header h2 {
+ font-size: 1.5vh;
+ font-style: italic;
+ font-weight: normal;
+ max-height: 2vh;
+ overflow: hidden;
+}
+
+/* House Table */
+
+#house {
+ background-color: white;
+ border-radius: 25px;
+ padding: 2vw 2vw 0;
+}
+
+#house table {
+ border-collapse: collapse;
+ height: 42vw;
+ margin: auto;
+ width: 90vw;
+}
+
+#house td {
+ border: 2.8pt solid #666666;
+ color: #007099;
+ position: relative;
+ text-align: center;
+ vertical-align: middle;
+}
+
+#house td.no_border {
+ border: 0 none black;
+}
+
+#house h1 {
+ color: #007099;
+ font-size: 1.2vw;
+ font-weight: normal;
+ left: 0;
+ margin: 0.2vw;
+ position: absolute;
+ top: 0;
+}
+
+#house #r0 {
+ border-bottom-style: none;
+}
+
+/* House displays */
+
+#house .t_current {
+ font-size: 3.4vw;
+ }
+
+#house .t_current .t_current_unit {
+ font-size: 1.7vw;
+}
+
+#house .battery {
+ bottom: 0;
+ margin: 0.2vw;
+ position: absolute;
+ left: 0;
+ width: 0.8vw;
+}
+
+#house .clock {
+ margin: 0.2vw;
+ position: absolute;
+ right: 0;
+ top: 0;
+ width: 1.2vw;
+}
+
+#house .target {
+ font-size: 1.6vw;
+}
+
+#house .target .t_current_unit {
+ font-size: 0.8vw;
+}
+
+#house .target button{
+ background-color: white;
+ border: #007099 solid 1pt;
+ color: #007099;
+ font-family: "Arial Black", Gadget, sans-serif;
+ font-size: 1.4vw;
+ font-weight: bold;
+ height: 2vw;
+ width: 2vw;
+}
+
+#house .flame {
+ bottom: 0;
+ /*display: none;*/
+ left: 1vw;
+ margin: 0.2vw;
+ position: absolute;
+ width: 1vw;
+}
+
+/* Legend */
+
+#legend {
+ height: 2vw;
+}
+
+#legend div {
+ padding-top: 0.5vw;
+ font-size: 1vw;
+}
+
+#legend .flame_legend {
+ float: left;
+ margin: 0.4vw;
+ width: 1vw;
+}
+
+/* Credits */
+
+#credits {
+ background-color: white;
+ border-left: black solid 1pt;
+ border-radius: 25px 0 0 0;
+ border-top: black solid 1pt;
+ bottom: 0;
+ padding-left: 0.8vw;
+ padding-top: 0.8vw;
+ position: absolute;
+ right: 0;
+}
+
+#credits h1 {
+ font-size: 0.8vw;
+ font-weight: normal;
+ margin-left: 0.6vw;
+}
+
+#credits img {
+ height: 4vh;
+ margin: 0.5vw;
+}
+
+/* Secret stuff */
+
+#hidden {
+ display: none;
+}