diff --git a/smart-home-sensors/background/main.js b/smart-home-sensors/background/main.js new file mode 100644 index 0000000..cd7013b --- /dev/null +++ b/smart-home-sensors/background/main.js @@ -0,0 +1,175 @@ +const Wappsto = require("wapp-api"); + +const wappsto = new Wappsto(); + +const wappstoConsole = require("wapp-api/console"); +wappstoConsole.start(); + +let device, sensors, led, data; + +let getNetwork = async () => { + try { + const collection = await wappsto.get( + "network", + { + name: "Smart Home" + }, + { + quantity: 1, + expand: 5, + subscribe: true + } + ); + device = collection.get("0.device"); + } catch (error) { + console.log(error); + } +}; + +getNetwork().then(() => { + sensors = device.find({ name: "Sensors" }).get("value"); + led = device.find({ name: "LED" }).get("value"); + + initSensorListerners(); + initButtonListener(); + initBrightnessListener(); +}); + +let getData = async () => { + try { + const collection = await wappsto.get( + "data", + {}, + { + expand: 5, + subscribe: true + } + ); + data = collection.first(); + } catch (error) { + console.log(error); + } +}; + +getData().then(() => { + data.on("change", () => { + updateSensorData(data.get("sensorToUpdate")); + }); +}); + +function initSensorListerners() { + if (sensors) { + sensors.each(sensorValue => { + let sensorState = sensorValue.get("state").find({ type: "Report" }); + + sensorState.on("change:data", () => { + let currentlySelectedSensor = data.get("sensorToUpdate"); + let sensorName = sensorValue.get("name"); + + if (currentlySelectedSensor === sensorName) { + updateSensorData(currentlySelectedSensor); + } else if (currentlySelectedSensor === "temperature-CO2") { + updateSensorData(currentlySelectedSensor); + } + }); + }); + } +} + +function updateSensorData(sensorValueToBeUpdated) { + let panel = led + .find({ name: "LED Panel" }) + .get("state") + .find({ type: "Control" }); + + if (sensors) { + if (sensorValueToBeUpdated === "temperature-CO2") { + let temp_value = sensors.find({ name: "Temperature" }); + let temp_data = temp_value + .get("state") + .find({ type: "Report" }) + .get("data"); + + let co2_value = sensors.find({ name: "CO2" }); + let co2_data = co2_value + .get("state") + .find({ type: "Report" }) + .get("data"); + + panel.save( + { + data: `${+Number(temp_data).toFixed(2)} ${temp_value.get("number.unit")} ${+Number(co2_data).toFixed(2)} ${co2_value.get("number.unit")}` + }, + { patch: true } + ); + } else { + let foundSensorValue = sensors.find({ name: sensorValueToBeUpdated }); + + if (foundSensorValue) { + let data = foundSensorValue + .get("state") + .find({ type: "Report" }) + .get("data"); + + panel.save({ + data: `${+Number(data).toFixed(2)} ${foundSensorValue.get("number.unit")}` + }); + } else { + console.log("Sensor could not be found"); + } + } + } +} + +function initButtonListener() { + if (led) { + let button = led + .find({ name: "Button" }) + .get("state") + .find({ type: "Report" }); + let brightness = led + .find({ name: "Brightness" }) + .get("state") + .find({ type: "Control" }); + + button.on("change:data", model => { + let isButtonOn = model.get("data"); + let userBrightnessOption = data.get("brightnessOption"); + let setting = 0; + + if (isButtonOn === "1") { + setting = userBrightnessOption; + } + + brightness.save({ data: setting.toString() }, { patch: true }); + }); + } +} + +function initBrightnessListener() { + if (led) { + let button = led + .find({ name: "Button" }) + .get("state") + .find({ type: "Report" }); + let brightnessState = led + .find({ name: "Brightness" }) + .get("state") + .find({ type: "Report" }); + let userBrightnessOption = data.get("brightnessOption"); + + brightnessState.on("change:data", () => { + let currentBrightnessOption = brightnessState.get("data"); + let currentButtonOption = button.get("data"); + + if (currentButtonOption === "1") { + if (currentBrightnessOption !== userBrightnessOption) { + data.save( + { brightnessOption: currentBrightnessOption }, + { patch: true } + ); + } + } + }); + } +} diff --git a/smart-home-sensors/background/package.json b/smart-home-sensors/background/package.json new file mode 100644 index 0000000..92db5ab --- /dev/null +++ b/smart-home-sensors/background/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "wapp-api": "^1.2.2" + } +} diff --git a/smart-home-sensors/foreground/index.html b/smart-home-sensors/foreground/index.html index 954dd26..c9e9aa3 100644 --- a/smart-home-sensors/foreground/index.html +++ b/smart-home-sensors/foreground/index.html @@ -1,46 +1,67 @@ - - + + Smart Home - - - - + + + + - + - + - +
-
-

Smart Home

+

Smart Home

-
-

Sensors

-
- +
-
@@ -93,7 +113,6 @@
CO2
-
@@ -117,7 +136,6 @@
Temperature
-
@@ -141,7 +159,6 @@
Pressure
-
@@ -162,15 +179,15 @@
Humidity

Step:

-

LED

-
- +
RGB
@@ -180,7 +197,9 @@
RGB
-
+
+ +
@@ -223,44 +242,79 @@
Brightness
-
Panel
-
- +
+ +
- - + +
@@ -279,24 +333,14 @@
Panel
-
Button
- -
- - -
-
- - + +
diff --git a/smart-home-sensors/foreground/index.js b/smart-home-sensors/foreground/index.js index c072049..09ed1ff 100644 --- a/smart-home-sensors/foreground/index.js +++ b/smart-home-sensors/foreground/index.js @@ -1,101 +1,131 @@ // Define shared across the functions variables: -var device, sensors, led, ledListen; +let device, sensors, led, ledListen, data; // Instantiate wapp-api: const wapp = new Wappsto(); // Define a color transition schema (HEX values) for Sensor icons: const colorSchema = { - CO2: ['60A664', 'EEC800', 'D86C00', 'C1312C'], - temperature: ['8AD5D7', '01B7CD', 'FDB813', 'F37020', 'C9234B'], - pressure: ['60A664', 'EEC800', 'D86C00', 'C1312C'], - humidity: ['C7F2FF', '6EADDF', '4F96D8', '357ECD', '1C68C0'] -} + CO2: ["60A664", "EEC800", "D86C00", "C1312C"], + temperature: ["8AD5D7", "01B7CD", "FDB813", "F37020", "C9234B"], + pressure: ["60A664", "EEC800", "D86C00", "C1312C"], + humidity: ["C7F2FF", "6EADDF", "4F96D8", "357ECD", "1C68C0"] +}; // Get the "network" object that contains all the necessary information for this wapp: -wapp.get('network', { - name: 'Smart Home' -}, { - expand: 5, - subscribe: true, - quantity: 1 -}).then(function(collection) { - // Extract objects and assign them to the shared variables: - device = collection.get('0.device'); - sensors = device.find({ name: 'Sensors' }).get('value'); - led = device.find({ name: 'LED' }).get('value'); - - // Call the wapp kick-off functions: - initValueListeners(); - init(); -}).catch(console.error); +wapp + .get( + "network", + { + name: "Smart Home" + }, + { + expand: 5, + subscribe: true, + quantity: 1 + } + ) + .then(function(collection) { + // Extract objects and assign them to the shared variables: + device = collection.get("0.device"); + sensors = device.find({ name: "Sensors" }).get("value"); + led = device.find({ name: "LED" }).get("value"); + + // Call the wapp kick-off functions: + initValueListeners(); + init(); + }) + .catch(console.error); + +// Get the data model to enable communication between the foreground and background +wapp + .get( + "data", + {}, + { + subscribe: true + } + ) + .then(function(collection) { + data = collection.first(); + }) + .catch(console.error); function initValueListeners() { - var panel = led.find({ name: 'LED Panel' }).get('state').find({ type: 'Control' }); + var panel = led + .find({ name: "LED Panel" }) + .get("state") + .find({ type: "Control" }); // Find all boxes that represent devices: $.each($('[id^="v-"]'), function(i, e) { e = $(e); // Convert the plain HTML element to a jQuery element: // Extract and modify some information: - var name = e.attr('id').split('v-')[1].replace('_', ' '); + var name = e + .attr("id") + .split("v-")[1] + .replace("_", " "); var schema = colorSchema[name]; - name = name.charAt(0).toUpperCase()+name.slice(1); + name = name.charAt(0).toUpperCase() + name.slice(1); var value = sensors.find({ name: name }) || led.find({ name: name }); - var state = value.get('state').find({ type: 'Report' }); + var state = value.get("state").find({ type: "Report" }); // Listen to the incoming "state" changes (events) using a WebSocket // connection and trigger the following function on such events: - state.on('change:data', function(model) { - + state.on("change:data", function(model) { // Extract some information: - var data = model.get('data'); - var min = value.get('number.min'); - var max = value.get('number.max'); + var data = model.get("data"); + var min = value.get("number.min"); + var max = value.get("number.max"); if (schema) { // Do some math to calculate the % based on the // current value and the allowed value range: var index = Math.round(((+data - min) * 100) / (max - min)); // Generate a palette of 100 colors based on the color schema for the given sensor: - var color = chroma.bezier(schema).scale().colors(100); + var color = chroma + .bezier(schema) + .scale() + .colors(100); // Find the icon and change its color: - e.find('i').css('color', color[index]); + e.find("i").css("color", color[index]); } // Convert some information: - var date = new Date(model.get('timestamp')); - var dataString = +Number(data).toFixed(2)+' '+value.get('number.unit'); + var date = new Date(model.get("timestamp")); + var dataString = + +Number(data).toFixed(2) + " " + value.get("number.unit") + " "; // Find HTML elements and change their current content // with the extracted and calculated data: - e.find('.date').html(date.toLocaleDateString()); - e.find('.time').html(date.toLocaleTimeString()); - e.find('.value').html(dataString); - e.find('.report').html(+Number(data).toFixed(2) || data); - e.find('.unit').html(value.get('number.unit')); - e.find('.min').html(min); - e.find('.max').html(max); - e.find('.step').html(value.get('number.step')); - e.find('time').timeago('update', state.get('timestamp')); + e.find(".date").html(date.toLocaleDateString()); + e.find(".time").html(date.toLocaleTimeString()); + e.find(".value").html(dataString); + e.find(".report").html(+Number(data).toFixed(2) || data); + e.find(".unit").html(value.get("number.unit")); + e.find(".min").html(min); + e.find(".max").html(max); + e.find(".step").html(value.get("number.step")); + e.find("time").timeago("update", state.get("timestamp")); // Pass the current value to the LED Panel if the selected sensor matches: - if (e.attr('id') === ledListen) { + if (e.attr("id") === ledListen) { panel.save({ data: dataString }, { patch: true }); } }); // Trigger the "state" change event manually to initialize the view: - state.emit('change:data', state); + state.emit("change:data", state); // Extract the "Control" information and display them: - var stateC = value.get('state').find({ type: 'Control' }); + var stateC = value.get("state").find({ type: "Control" }); if (stateC) { - stateC.on('change:data', function(model) { - var data = model.get('data'); - e.find('.control').html(data); + stateC.on("change:data", function(model) { + var data = model.get("data"); + e.find(".control").html(data); }); - stateC.emit('change:data', stateC); + stateC.emit("change:data", stateC); } }); } @@ -105,14 +135,14 @@ function init() { initRoundSlider(); if (led) { initPanel(); - initSwitch() + initButton(); } } $(document).ready(init); function initColorPicker() { - var htmlColorPicker = $('#colorPicker'); + var htmlColorPicker = $("#colorPicker"); var colorPicker = function() { // Remove the current Color Picker HTML element to achieve @@ -120,22 +150,22 @@ function initColorPicker() { htmlColorPicker.empty(); // Create a Color Picker HTML element with the current box width: - var iroCp = new iro.ColorPicker('#colorPicker', { + var iroCp = new iro.ColorPicker("#colorPicker", { width: htmlColorPicker.parent().width() }); // Listen to the selected Color Picker HEX values, convert // to decimal and send them to the LED strip: - iroCp.on(['input:end'], function(event) { + iroCp.on(["input:end"], function(event) { if (led) { var decimal = parseInt(event.hexString.slice(1), 16).toString(); - rgb.find({ type: 'Control' }).save({ data: decimal }, { patch: true }); + rgb.find({ type: "Control" }).save({ data: decimal }, { patch: true }); } }); if (led) { - var rgb = led.find({ name: 'RGB' }).get('state'); - var color = rgb.find({ type: 'Control' }).get('data'); + var rgb = led.find({ name: "RGB" }).get("state"); + var color = rgb.find({ type: "Control" }).get("data"); try { // Set the Color Picker pointer to the current stored value: iroCp.color.hexString = (+color).toString(16); @@ -143,15 +173,18 @@ function initColorPicker() { console.error(e); } - var rgbR = rgb.find({ type: 'Report' }); - rgbR.on('change:data', function(model) { + var rgbR = rgb.find({ type: "Report" }); + rgbR.on("change:data", function(model) { // Listen to the reported Color Picker values and display them: - var col = (+model.get('data')).toString(16); - htmlColorPicker.parent().find('i').css('color', '#'+col); + var col = (+model.get("data")).toString(16); + htmlColorPicker + .parent() + .find("i") + .css("color", "#" + col); }); - rgbR.emit('change:data', rgbR); + rgbR.emit("change:data", rgbR); } - } + }; // Listen to the window size changes: $(window).resize(colorPicker); @@ -161,23 +194,25 @@ function initColorPicker() { } function initRoundSlider() { - var htmlRoundSlider = $('#roundSlider'); + var htmlRoundSlider = $("#roundSlider"); var roundSlider = function() { // Find the current "Brightness" value to set the Slider pointer: if (led) { - var model = led.find({ name: 'Brightness' }) - .get('state').find({ type: 'Control' }); - var value = model.get('data'); + var model = led + .find({ name: "Brightness" }) + .get("state") + .find({ type: "Control" }); + var value = model.get("data"); } else { var value = 0; } htmlRoundSlider.roundSlider({ - radius: htmlRoundSlider.parent().width()/2, - handleSize: '+10', - handleShape: 'dot', - sliderType: 'min-range', + radius: htmlRoundSlider.parent().width() / 2, + handleSize: "+10", + handleShape: "dot", + sliderType: "min-range", value: +value, change: function(event) { if (led) { @@ -185,7 +220,7 @@ function initRoundSlider() { } } }); - } + }; // Listen to the window size changes, when it happens, recreate the Round Slider // HTML element to achieve the resize effect as it's not responsive on its own: @@ -197,85 +232,137 @@ function initRoundSlider() { function initPanel() { // Find HTML elements and extract some data: - var form = $('#v-LED_Panel form'); + var form = $("#v-LED_Panel form"); var input = form.find('input[type="text"]'); - var panel = led.find({ name: 'LED Panel' }).get('state').find({ type: 'Control' }); - input.val(panel.get('data')); + var panel = led + .find({ name: "LED Panel" }) + .get("state") + .find({ type: "Control" }); + input.val(panel.get("data")); // Listen to the input text field changes: - form.submit(function(event) { - // Unselect the sensor icon to avoid current text overwrites: - $('#v-LED_Panel .panel-icons input:checked').click(); - panel.save({ data: input.val() || ' ' }, { patch: true }); - event.preventDefault(); - }).keypress(function(event) { - // Listen to enter keystrokes: - if (event.which == 13) { - form.submit(); + form + .submit(function(event) { + // Unselect the sensor icon to avoid current text overwrites: + $("#v-LED_Panel .panel-icons input:checked").click(); + panel.save({ data: input.val() || " " }, { patch: true }); event.preventDefault(); - } - }); + }) + .keypress(function(event) { + // Listen to enter keystrokes: + if (event.which == 13) { + form.submit(); + event.preventDefault(); + } + }); // Listen to the icon selects, once icon is selected it will pass // the current value of the selected sensor to the LED Panel: - $('#v-LED_Panel .panel-icons input').click(function() { + $("#v-LED_Panel .panel-icons input").click(function() { if (this.value === ledListen) { this.checked = false; ledListen = undefined; } else { ledListen = this.value; - var name = this.value.split('v-')[1].replace('_', ' '); - name = name.charAt(0).toUpperCase()+name.slice(1); - var value = sensors.find({ name: name }); - var data = value.get('state').find({ type: 'Report' }).get('data'); - panel.save({ - data: +Number(data).toFixed(2)+' '+value.get('number.unit') - }, { patch: true }); + if (this.value === "temperature-CO2") { + // Save custom option used to display temperature along with CO2 on the panel + saveSelectedSensor("temperature-CO2"); + var temp_value = sensors.find({ name: "Temperature" }); + var temp_data = temp_value + .get("state") + .find({ type: "Report" }) + .get("data"); + var CO2_value = sensors.find({ name: "CO2" }); + var CO2_data = CO2_value.get("state") + .find({ type: "Report" }) + .get("data"); + + panel.save( + { + data: + +Number(temp_data).toFixed(2) + + " " + + temp_value.get("number.unit") + + " " + + +Number(CO2_data).toFixed(2) + + " " + + CO2_value.get("number.unit") + + " " + }, + { patch: true } + ); + } else { + var name = this.value.split("v-")[1].replace("_", " "); + name = name.charAt(0).toUpperCase() + name.slice(1); + var value = sensors.find({ name: name }); + saveSelectedSensor(value.get("name")); + var data = value + .get("state") + .find({ type: "Report" }) + .get("data"); + + panel.save( + { + data: + +Number(data).toFixed(2) + " " + value.get("number.unit") + " " + }, + { patch: true } + ); + } } }); } -function initSwitch() { - // Find HTML elements and extract some data: - var btn = led.find({ name: 'Button' }).get('state').find({ type: 'Report' }); - var brightness = led.find({ name: 'Brightness' }).get('state').find({ type: 'Control' }); - var switchBrigtness = $('#switch-brightness').is(':checked'); +function saveSelectedSensor(value) { + // Saves a sensor value to the data model + data.save({ sensorToUpdate: value }, { patch: true }); +} - // Listen to the physical button presses and display the current state: - btn.on('change:data', function(model) { - var isOn = model.get('data') == '1'; - $('#led-btb-onOff').prop('checked', isOn); +function saveBrightnessOption(option) { + // Saves brightness state data to the data model + data.save({ brightnessOption: option }, { patch: true }); +} - if (switchBrigtness) { - if (isOn) { - var value = $('#roundSlider').roundSlider('option', 'value'); - } else { - var value = 0; - } - brightness.save({ data: value.toString() }, { patch: true }); +function initButton() { + let btn = led + .find({ name: "Button" }) + .get("state") + .find({ type: "Report" }); + + // Listen to the physical button presses and display the current state: + btn.on("change:data", function() { + if (btn.get("data") === "1") { + $("#switch-brightness").prop("checked", true); + $("#brightness-label").html("Brightness On"); + } else { + $("#switch-brightness").prop("checked", false); + $("#brightness-label").html("Brightness Off"); } }); - btn.emit('change:data', btn); + btn.emit("change:data", btn); - // Listen to the button clicks (in the wapp): - $('#switch-brightness').change(function() { - switchBrigtness = this.checked; - }) + // Get the currently selected brightness state and save it + let value = $("#roundSlider").roundSlider("option", "value"); + saveBrightnessOption(value); } function showDetails() { // Toggle the view of boxes: - $.each($('.info'), function(i, e) { - $(e).toggle().parent().find('div:first').toggle(); + $.each($(".info"), function(i, e) { + $(e) + .toggle() + .parent() + .find("div:first") + .toggle(); }); } function updateDevices() { // Pull data from the Raspberry Pi: wapp.send({ - method: 'patch', - url: '/value', - data: { status: 'update' } + method: "patch", + url: "/value", + data: { status: "update" } }); } diff --git a/smart-home-sensors/foreground/style.css b/smart-home-sensors/foreground/style.css index d238e6f..0bda725 100644 --- a/smart-home-sensors/foreground/style.css +++ b/smart-home-sensors/foreground/style.css @@ -8,10 +8,10 @@ h3 { margin-top: 25px; } - /* Style the boxes: */ -.sensors > .grid-x > .cell, .led > .grid-x > .cell { - box-shadow: 0 2px 5px rgba(0,0,0,0.1); +.sensors > .grid-x > .cell, +.led > .grid-x > .cell { + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); border-bottom: 3px solid white; background: white; text-align: center; @@ -23,7 +23,8 @@ h3 { min-height: 290px; } -.sensors > .grid-x > .cell:hover, .led > .grid-x > .cell:hover { +.sensors > .grid-x > .cell:hover, +.led > .grid-x > .cell:hover { /* Show box bottom line on hover: */ border-bottom: 3px solid #7366f0; } @@ -31,7 +32,7 @@ h3 { .sensors > .grid-x > .cell:hover { /* Box hover annimation efect: */ transform: translateY(-8px) !important; - box-shadow: 0 1rem 3rem rgba(31,45,61,.125) !important; + box-shadow: 0 1rem 3rem rgba(31, 45, 61, 0.125) !important; } .sensors > .grid-x .type { @@ -62,36 +63,35 @@ time { outline: none; } - /* Overwrite the default Slider style: */ .iro__slider { display: none !important; } -#roundSlider .rs-range-color { - background-color: #33B5E5; +#roundSlider .rs-range-color { + background-color: #33b5e5; } -#roundSlider .rs-path-color { - background-color: #C2E9F7; +#roundSlider .rs-path-color { + background-color: #c2e9f7; } -#roundSlider .rs-handle { - background-color: #C2E9F7; +#roundSlider .rs-handle { + background-color: #c2e9f7; padding: 7px; - border: 2px solid #C2E9F7; + border: 2px solid #c2e9f7; } -#roundSlider .rs-handle.rs-focus { - border-color: #33B5E5; +#roundSlider .rs-handle.rs-focus { + border-color: #33b5e5; } -#roundSlider .rs-handle:after { - border-color: #33B5E5; - background-color: #33B5E5; +#roundSlider .rs-handle:after { + border-color: #33b5e5; + background-color: #33b5e5; } -#roundSlider .rs-border { +#roundSlider .rs-border { border-color: transparent; } @@ -104,10 +104,9 @@ time { } #roundSlider .rs-tooltip-text::after { - content: ' %'; + content: " %"; } - /* Style the "Panel" box icons: */ .panel-icons > .cell { cursor: pointer; @@ -130,10 +129,9 @@ time { margin: 30px 0; } - /* Style the "Switch brightness" button: */ .ks-cboxtags label { - border: 2px solid rgba(139, 139, 139, .3); + border: 2px solid rgba(139, 139, 139, 0.3); color: #adadad; border-radius: 25px; white-space: nowrap; @@ -144,7 +142,7 @@ time { -ms-user-select: none; user-select: none; -webkit-tap-highlight-color: transparent; - transition: all .2s ease; + transition: all 0.2s ease; } .ks-cboxtags label { @@ -181,3 +179,27 @@ time { position: absolute; opacity: 0; } + +#temp-CO2-icon { + display: inline-block; + position: relative; + width: 1.5rem; + height: 1.5rem; +} + +#temp-icon, +#CO2-icon { + font-size: 50%; +} + +#CO2-icon { + position: absolute; + bottom: 0; + right: 0; +} + +#temp-icon { + position: absolute; + top: 0; + left: 0; +}