├── LICENSE ├── README.md ├── index.html ├── js └── index.js ├── screenshot-mobile.png ├── screenshot.png └── test-messages.py /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2025 Fabian Affolter 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [](https://api.gitsponsors.com/api/badge/link?p=M6YTOZpYXP5QfsPAcN1WtjxBeUMoAYG1hSbALWg+0ACRLfuU00JzAshil3XKiVKDCWmq8GYaEA1Y0KMUMbow+NBX0Y9Pi41tgCFXM9FerKn/dyzHyrXl5KFh0xDcLA5R) 2 | 3 | # mqtt-panel 4 | 5 | A simple web interface which is able to subscribe to a MQTT topic and display 6 | the information. 7 | 8 | The screenshot shows an example how to keep track on what's going in your 9 | apartment or your house. It's not about controlling, this setup is about 10 | observing various states. 11 | 12 | ![screenshot](screenshot.png) 13 | 14 | ![screenshot](screenshot-mobile.png) 15 | 16 | What to see `mqtt-panel` in action -> http://youtu.be/Qb0UJa9kf2g 17 | 18 | The web page is using [bootstrap](http://getbootstrap.com/) with 19 | [jQuery](http://jquery.com/). 20 | 21 | ## Prerequisites/Installation 22 | 23 | ### Get the files 24 | 25 | Clone the `mqtt-panel` [repository](https://github.com/fabaff/mqtt-panel) 26 | 27 | ```bash 28 | $ git clone git@github.com:fabaff/mqtt-panel.git 29 | ``` 30 | 31 | ### Dependencies 32 | 33 | `mqtt-panel` is using the listed projects to provide its functionality: 34 | 35 | - [paho-mqtt](https://www.eclipse.org/paho/clients/python/) 36 | - [mqtt](https://github.com/adamvr/MQTT.js/) 37 | 38 | If you are using Fedora and want to generate MQTT messages, install the 39 | `paho-mqtt` Python bindings for `test-messages.py`. 40 | 41 | ```bash 42 | $ sudo dnf -y install python-paho-mqtt 43 | ``` 44 | 45 | ### MQTT broker/server 46 | 47 | A MQTT broker/server with Websocket support is needed. 48 | 49 | - [mosquitto](http://mosquitto.org/) - An Open Source MQTT v3.1/3.11 broker 50 | - [mosca](http://mcollina.github.io/mosca/) - A multi-transport MQTT broker 51 | for node.js (Project seems abandoned) 52 | 53 | ## Running mqtt-panel 54 | 55 | 1. Make sure that your MQTT broker/server is running and listening. 56 | 2. Adjust `var host = '127.0.0.1';` and `var port = 3000;` in the file 57 | `js/index.js` to match your setup. Also, `var topic = '#';`. 58 | 3. Open `index.html` in a modern web browser. 59 | 60 | ## Generate MQTT messages 61 | 62 | Start the `./test-messages.py` script to publish test messages if you have 63 | no other source for messages. Depending on your broker you may need to set 64 | the supported version, on line 33: `protocol=mqtt.MQTTv311` and adjust 65 | the broker and its port. 66 | 67 | For manually sending messages to your MQTT broker/server you can use 68 | `mosquitto_pub` from `mosquitto`. 69 | 70 | ```bash 71 | $ mosquitto_pub -V mqttv311 -h localhost -d -t home/front/door -m "false" 72 | ``` 73 | 74 | To check if the messages are are ok, subscribe to the topic **home/#** with 75 | `mosquitto_sub`. 76 | 77 | ```bash 78 | $ mosquitto_sub -V mqttv311 -h localhost -d -t home/# 79 | ``` 80 | 81 | ## Credits 82 | 83 | `mqtt-panel` was inspired by the ideas of: 84 | 85 | - [mqtt-svg-dash](https://github.com/jpmens/mqtt-svg-dash) by [Jan-Piet Mens](http://jpmens.net/) 86 | - [Robert Hekkers](http://blog.hekkers.net/2012/10/13/realtime-data-with-mqtt-node-js-mqtt-js-and-socket-io/) 87 | 88 | ## License 89 | 90 | `mqtt-panel` licensed under MIT, for more details check LICENSE. 91 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Home conditions 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 22 |
23 |
24 | Loading... 25 |
26 |
27 |
28 |
29 |
30 |
31 | Entrance 32 |
33 | Unknown 34 |
35 |
36 |
37 |
38 |
39 | Back door 40 |
41 | Unknown 42 |
43 |
44 |
45 |
46 |
47 | Basement temperature 48 |
49 | Unknown 50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | Living room temperature 60 | Unknown 61 |
62 |
63 | 64 |
65 |
66 |
67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 |
Entrance(no value received)
Back door(no value received)
Basement temperature(no value received)
Living room temperature(no value received)
Last MQTT messageno message recieved
91 |
92 |
93 |
94 | 99 |
100 | 101 | 102 | -------------------------------------------------------------------------------- /js/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2013-2024 Fabian Affolter 3 | Licensed under MIT 4 | */ 5 | 6 | let host = 'localhost'; 7 | let port = 9001; 8 | let topic = '#'; 9 | let useTLS = false; 10 | let cleansession = true; 11 | let reconnectTimeout = 3000; 12 | let tempData = new Array(); 13 | let mqtt; 14 | 15 | function MQTTconnect() { 16 | if (typeof path == "undefined") { 17 | path = '/'; 18 | } 19 | mqtt = new Paho.MQTT.Client(host, port, path, "mqtt_panel" + parseInt(Math.random() * 100, 10)); 20 | let options = { 21 | timeout: 3, 22 | useSSL: useTLS, 23 | cleanSession: cleansession, 24 | onSuccess: onConnect, 25 | onFailure: function (message) { 26 | $('#status').html("Connection failed: " + message.errorMessage + "Retrying...") 27 | .attr('class', 'alert alert-danger'); 28 | setTimeout(MQTTconnect, reconnectTimeout); 29 | } 30 | }; 31 | 32 | mqtt.onConnectionLost = onConnectionLost; 33 | mqtt.onMessageArrived = onMessageArrived; 34 | console.log("Host: " + host + ", Port: " + port + ", Path: " + path + " TLS: " + useTLS); 35 | mqtt.connect(options); 36 | }; 37 | 38 | function onConnect() { 39 | $('#status').html('Connected to ' + host + ':' + port + path) 40 | .attr('class', 'alert alert-success'); 41 | mqtt.subscribe(topic, { qos: 0 }); 42 | $('#topic').html(topic); 43 | }; 44 | 45 | function onConnectionLost(response) { 46 | setTimeout(MQTTconnect, reconnectTimeout); 47 | $('#status').html("Connection lost. Reconnecting...") 48 | .attr('class', 'alert alert-warning'); 49 | }; 50 | 51 | function onMessageArrived(message) { 52 | let topic = message.destinationName; 53 | let payload = message.payloadString; 54 | console.log("Topic: " + topic + ", Message payload: " + payload); 55 | $('#message').html(topic + ', ' + payload); 56 | let topics = topic.split('/'); 57 | let area = topics[1]; 58 | 59 | switch (area) { 60 | case 'front': 61 | $('#value1').html('(Switch value: ' + payload + ')'); 62 | if (payload == 'true') { 63 | $('#label1').text('Closed'); 64 | $('#label1').removeClass('badge-danger').addClass('badge-success'); 65 | } else { 66 | $('#label1').text('Open'); 67 | $('#label1').removeClass('badge-success').addClass('badge-danger'); 68 | } 69 | break; 70 | case 'back': 71 | $('#value2').html('(Switch value: ' + payload + ')'); 72 | if (payload == 'true') { 73 | $('#label2').text('Closed'); 74 | $('#label2').removeClass('badge-danger').addClass('badge-success'); 75 | } else { 76 | $('#label2').text('Open'); 77 | $('#label2').removeClass('badge-success').addClass('badge-danger'); 78 | } 79 | break; 80 | case 'living': 81 | $('#livingTempSensor').html('(Sensor value: ' + payload + ')'); 82 | $('#livingTempLabel').text(payload + ' °C'); 83 | $('#livingTempLabel').addClass('badge-default'); 84 | 85 | tempData.push({ 86 | "timestamp": Date().slice(16, 21), 87 | "temperature": parseInt(payload) 88 | }); 89 | if (tempData.length >= 10) { 90 | tempData.shift() 91 | } 92 | drawChart(tempData); 93 | 94 | break; 95 | case 'basement': 96 | $('#basementTempSensor').html('(Sensor value: ' + payload + ')'); 97 | if (payload >= 25) { 98 | $('#basementTempLabel').text(payload + ' °C - too hot'); 99 | $('#basementTempLabel').removeClass('badge-warning badge-success badge-info badge-primary').addClass('badge-danger'); 100 | } else if (payload >= 21) { 101 | $('#basementTempLabel').text(payload + ' °C - hot'); 102 | $('#basementTempLabel').removeClass('badge-danger badge-success badge-info badge-primary').addClass('badge-warning'); 103 | } else if (payload >= 18) { 104 | $('#basementTempLabel').text(payload + ' °C - normal'); 105 | $('#basementTempLabel').removeClass('badge-danger badge-warning badge-info badge-primary').addClass('badge-success'); 106 | } else if (payload >= 15) { 107 | $('#basementTempLabel').text(payload + ' °C - low'); 108 | $('#basementTempLabel').removeClass('badge-danger badge-warning badge-success badge-primary').addClass('badge-info'); 109 | } else if (mpayload <= 12) { 110 | $('#basementTempLabel').text(payload + ' °C - too low'); 111 | $('#basementTempLabel').removeClass('badge-danger badge-warning badge-success badge-info').addClass('badge-primary'); 112 | basementTemp.push(parseInt(payload)); 113 | if (basementTemp.length >= 20) { 114 | basementTemp.shift() 115 | } 116 | } 117 | break; 118 | default: 119 | console.log('Error: Data do not match the MQTT topic.'); 120 | break; 121 | } 122 | }; 123 | 124 | function drawChart(data) { 125 | let ctx = document.getElementById("tempChart").getContext("2d"); 126 | 127 | 128 | let temperatures = [] 129 | let timestamps = [] 130 | 131 | data.map((entry) => { 132 | temperatures.push(entry.temperature); 133 | timestamps.push(entry.timestamp); 134 | }); 135 | 136 | let chart = new Chart(ctx, { 137 | type: 'line', 138 | data: { 139 | labels: timestamps, 140 | datasets: [{ 141 | backgroundColor: 'rgb(255, 99, 132)', 142 | borderColor: 'rgb(255, 99, 132)', 143 | data: temperatures 144 | }] 145 | }, 146 | options: { 147 | legend: { 148 | display: false 149 | } 150 | } 151 | }); 152 | } 153 | 154 | $(document).ready(function () { 155 | drawChart(tempData); 156 | MQTTconnect(); 157 | }); 158 | -------------------------------------------------------------------------------- /screenshot-mobile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fabaff/mqtt-panel/c2530ad69dc5038d4ef595d1cd4ef6f144c12579/screenshot-mobile.png -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fabaff/mqtt-panel/c2530ad69dc5038d4ef595d1cd4ef6f144c12579/screenshot.png -------------------------------------------------------------------------------- /test-messages.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # 3 | # test-messages.py - This script publish a random MQTT messages every 2 s. 4 | # 5 | # Copyright (c) 2013-2024, Fabian Affolter 6 | # Released under the MIT license. See LICENSE file for details. 7 | # 8 | import random 9 | import time 10 | import paho.mqtt.client as mqtt 11 | 12 | timestamp = int(time.time()) 13 | 14 | broker = "127.0.0.1" 15 | port = 1883 16 | element = "home" 17 | areas = ["front", "back", "kitchen", "basement", "living"] 18 | entrances = ["door", "window"] 19 | states = ["true", "false"] 20 | 21 | print(f"Messages are published on topic {element}/#... -> CTRL + C to shutdown") 22 | 23 | while True: 24 | area = random.choice(areas) 25 | if area in ["basement", "living"]: 26 | topic = element + "/" + area + "/temp" 27 | message = random.randrange(0, 30, 1) 28 | else: 29 | topic = element + "/" + area + "/" + random.choice(entrances) 30 | message = random.choice(states) 31 | 32 | mqtt_client = mqtt.Client("mqtt-panel-test", protocol=mqtt.MQTTv311) 33 | mqtt_client.connect(broker, port=int(port)) 34 | mqtt_client.publish(topic, message) 35 | time.sleep(2) 36 | --------------------------------------------------------------------------------