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 @@ + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + Openclipart + + + attention + 2008-02-04T19:22:40 + + https://openclipart.org/detail/12607/attention-by-anonymous-12607 + + + Anonymous + + + + + attention + icon + warning + + + + + + + + + + + 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 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + Openclipart + + + + + + + + + + + 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 @@ + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + 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 + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

Bathroom 1

+ +
+ 00 + °C +
+
+ + 00°C + +
+ + +
+

Room 3

+ +
+ 00 + °C +
+
+ + 00°C + +
+ + +
+

Kitchen

+ +
+ 00 + °C +
+
+ + 00°C + +
+ + +
+

Utility

+ +
+ 00 + °C +
+ + +
+

Bathroom 2

+ +
+ 00 + °C +
+
+ + 00°C + +
+ + +
+

Bedroom

+ +
+ 00 + °C +
+
+ + 00°C + +
+ + +
+

Hall

+ +
+ 00 + °C +
+
+ + 00°C + +
+ + +
+

Dining Room

+ +
+ 00 + °C +
+
+ + 00°C + +
+ + +
+

Living Room

+ +
+ 00 + °C +
+
+ + 00°C + +
+ + +
+

Room 1

+ +
+ 00 + °C +
+
+ + 00°C + +
+ + +
+

Room 2

+ +
+ 00 + °C +
+
+ + 00°C + +
+ + +
+

Patio

+ +
+ 00 + °C +
+ +
+
+ +
+ Room selected for heating by Uppaal Stratego + (last updated: ). +
+
+
+ +
+

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; +}