diff --git a/web/.gitkeep b/web/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/web/img/favicon.ico b/web/img/favicon.ico new file mode 100644 index 0000000..1fdd43a Binary files /dev/null and b/web/img/favicon.ico differ diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..f3b8373 --- /dev/null +++ b/web/index.html @@ -0,0 +1,58 @@ + + + + + + IDFC + + + + + + + + +
+
+

The M5 - MQTT Project

+

by Patrik, Hazel and Reinhold

+
+
+
+
+ + + + + + + + +
+
+
+ +
+
+
+
+
+
+ +
+
+
+ + diff --git a/web/main.css b/web/main.css new file mode 100644 index 0000000..963c5ce --- /dev/null +++ b/web/main.css @@ -0,0 +1,66 @@ +input[type="radio"] { + display: none; +} + +label { + padding-bottom: 7px; +} + +input[type="radio"]:checked + label { + font-weight: bold; + padding-bottom: 5px; + border-bottom: 2px solid hsl(142, 52%, 96%); +} + +.inline-grid { + display: inline-grid; +} + +#connection-grid { + grid-template-columns: max-content auto; +} + +#toaster { + position: absolute; + translate: -50%; + top: 10px; + left: 50%; + max-height: 50%; + overflow: hidden; +} + +.toast { + --duration: 3s; + background-color: rgba(0, 209, 178, .5); + display: flex; + justify-content: center; + align-items: center; + padding: 2px 10px 3px; + margin: 3px; + border-radius: 10px; + min-width: 70px; + color: white; + opacity: 0; + max-height: 40px; + animation: blend var(--duration); +} + +@keyframes blend { + 0% { + opacity: 0; + max-height: 40px; + } + 20% { + opacity: 1; + } + 80% { + opacity: 1; + } + 90% { + max-height: 40px; + } + 100% { + opacity: 0; + max-height: 0px; + } +} diff --git a/web/main.js b/web/main.js new file mode 100644 index 0000000..1c2c82c --- /dev/null +++ b/web/main.js @@ -0,0 +1,168 @@ +//const url = 'mqtt://tplinkwifi.net'; +let client; + +window.addEventListener('DOMContentLoaded', (event) => { + const tabs = document.querySelector('#tabs'); + tabs.addEventListener('change', (event) => { + if (event.target.type != 'radio') { + return; + } + + switchTab(); + }); + + const buttonSubmit = document.querySelector('#connection-submit'); + buttonSubmit.addEventListener('click', (event) => { + if (client) { + client.end(); + } + const broker = document.querySelector('#connection-broker').value; + const topic = document.querySelector('#connection-topic').value; + connect(broker, topic); + buttonSubmit.classList.add('is-loading'); + }); + + // start + document.querySelector('#connection-broker').value = 'test.mosquitto.org:8081'; + document.querySelector('#connection-topic').value = 'test/2'; + document.querySelector('#connection-submit').click(); + +}); + +function createChart(data) { + const elm = document.querySelector('#chart'); + while (elm.childElementCount > 0) { + elm.firstElementChild.remove(); + } + + const ctx = document.createElement('canvas'); + elm.appendChild(ctx); + + // generate labels + const labels = []; + for (i = 1; i <= data.length; i++) { + labels.push(i); + } + + const gridColor = '#4f4f4f'; + new Chart(ctx, { + type: 'line', + data: { + labels: labels, + datasets: [{ + data: data, + borderColor: '#d1fff8' + }] + }, + options: { + scales: { + x: { + grid: { + color: gridColor + }, + ticks: { + color: gridColor + } + }, + y: { + grid: { + color: gridColor + }, + ticks: { + color: gridColor + } + } + }, + plugins: { + legend: { + display: false + } + } + } + }); +} + +function loadValues(data) { + let elm = document.querySelector('#values'); + + while (elm.childElementCount > 0) { + elm.firstElementChild.remove(); + } + + for (let i = 0; i < data.length; i++) { + const val = document.createElement('span'); + val.innerText = data[i]; + val.classList.add('is-align-self-center'); + + const cell = document.createElement('div'); + cell.classList.add('cell', 'tag', 'has-background-dark'); + cell.appendChild(val); + + elm.appendChild(cell); + } +} + +function switchTab() { + const buttons = tabs.querySelectorAll('input[type="radio"]'); + const block = document.querySelector('#block'); + + for (let i = 0; i < buttons.length; i++) { + const name = buttons[i].getAttribute('data-tab'); + const container = block.querySelector(`.container[data-tab="${name}"]`); + if (buttons[i].checked) { + container.style.setProperty('display', 'block'); + } else { + container.style.setProperty('display', 'none'); + } + } +} + +function connect(broker, topic) { + client = mqtt.connect('wss://' + broker); + const options = { retain: false, qos: 1 }; + const submitButton = document.querySelector('#connection-submit'); + + client.on('connect', () => { + if (client.connected) { + toast('connected'); + submitButton.classList.remove('is-loading'); + } + }); + + client.on('close', () => { + console.log('close'); + toast('connection closed'); + submitButton.classList.remove('is-loading'); + }); + + client.on('error', (err) => { + console.error(err); + toast('error'); + submitButton.classList.remove('is-loading'); + }); + + client.on('message', (topic, message) => { + console.log(message); + toast('message received'); + submitButton.classList.remove('is-loading'); + const tab = document.querySelector('#chart-tab'); + tab.checked = true; + switchTab(); + createChart(message); + loadValues(message); + }); + client.subscribe(topic, options); +} + +function toast(message) { + const duration = 3; + const elm = document.createElement('p'); + elm.innerText = message; + elm.classList.add('toast'); + document.querySelector('#toaster').appendChild(elm); + + elm.style.setProperty('--duration', duration + 's'); + setTimeout(() => { + elm.remove(); + }, duration * 1000); +}