Click the above Front-End Q, follow the public account
ReplyJoin Group, join the Front-End Q technical exchange group
About the text, Author: Dreyas
Link: https://juejin.cn/post/7203180003471081531

The above image is an online temperature and humidity visualization project I worked on for a week. It shows the temperature and humidity trends for the previous day and updates the current temperature and humidity in real-time.
Overview and Basic Explanation
The technologies used in this project include:
-
Front-End: jq, less, echarts, mqtt.js -
Back-End: eggjs, egg-emqtt -
Database: mysql -
Server: emqx (mqtt broker) -
Hardware: -
Board: wemos D1 R2 (designed based on Arduino Uno R3, equipped with esp8266 wifi module) -
Debugging Tools: mqttx, Arduino IDE v2.0.3 using Arduino C for development
Essential knowledge:
-
nodejs (eggjs framework) is sufficient if you can handle business logic -
mysql basic insert and query statements are sufficient -
basic syntax of C language is sufficient -
understanding how the mqtt protocol works is sufficient -
basic understanding of Arduino boards or any other circuit boards is sufficient
Let me briefly introduce the above knowledge points:
-
For those without back-end learning experience, I recommend a full-stack project for your reference: vue-xmw-admin-pro, which uses Front-End VUE, Back-End eggjs, mysql, redis, and is very helpful for full-stack learning.
-
You only need to know the simplest insert and query statements for mysql. In this project, using mongodb would actually be more appropriate, but I used mysql for convenience.
-
Even if you do not know the basic syntax of C, you can quickly understand it in an hour, knowing how to define variables, functions, and return values is sufficient.
-
MQTT (Message Queuing Telemetry Transport) is a network protocol (long connection, meaning that besides the client can actively communicate with the server, the server can also actively initiate communication with the client), also based on TCP/IP, suitable for low-computing-power hardware devices, based on the publish/subscribe messaging protocol. A specific example is as follows:
image-20230104170333941 When a client wants to publish a message, the diagram looks like this:
image-20230104171235368 As shown in the above image, when a client goes online after verification, it also needs to subscribe to a topic. When a client publishes a message to a topic, only the clients subscribed to that topic will receive the broker’s forwarding.
For a simple example: you, I, and he report our names and student IDs to the security guard (broker), and the security guard agrees to let us play in the guard room for a while. The guard room has countless blackboards (topics), and each of us can request the guard: if someone writes on a blackboard, please inform me. The guard will remember everyone’s requests, for example, when you write on a blackboard (you send a message to a topic), everyone who requested to be informed will be notified by the guard about what you wrote (if you also requested to be informed, then it includes yourself).
-
The development board can be programmed, and the program can use simple code to control the high and low levels of a pin or read data from a pin.
Getting Started
-
Purchase a wemos d1 development board and a DHT11 temperature and humidity sensor, totaling 19.3 yuan. -
To program the wemos d1 using the Arduino IDE (hereinafter referred to as IDE), you need to download the esp8266 dependency. See: Install esp8266 SDK in Arduino IDE -
In the IDE menu, select: File>Preferences>Other Board Manager URLs fill in: arduino.esp8266.com/stable/pack…, and you can also change it to Chinese. -
Install the ch340 driver, see: win10 install CH340 driver, tested to work on win11 as well. -
Use a micro-usb cable to connect the computer and the development board, in the IDE menu select: Tools>Board>esp8266>LOLIN(WEMOS) D1 R2 & mini -
Select the port, press win+x, open Device Manager, check which port your ch340 is on, and select the corresponding port in the IDE. -
When the IDE shows LOLIN(WEMOS) D1 R2 & mini on comXX at the bottom right, the connection is successful. -
Open the IDE menu: File>Examples>esp8266>blink, at this point the IDE will open a new window, click the upload button in the upper left of the new window, wait for the upload to complete, when the light on the board flashes, it indicates: the environment, settings, and board are all fine, you can start programming. If an error occurs, then something must have gone wrong, and I believe you can find out what the problem is based on the error message. If you really can’t find the problem, then you may have bought a faulty board (the failure rate is quite high).
The wemos d1 pin has a 3.3v power output, three or more GND grounding ports. When installing the DHT11 sensor component, the positive pole needs to be plugged into the 3.3v port, the negative pole into the GND port, and the middle data line into any digital input port, such as D5 (the PIN value of D5 is 14, which will be used later).
To use the DHT11 sensor, you need to install the library: DHT sensor library by Adafruit, which can be directly searched and installed in the library manager on the left sidebar of the IDE.
Below is a simple example to obtain DHT11 data. If everything is normal, the serial monitor will output temperature and humidity data every second.
arduino
#include "DHT.h" // This is a dependency or library, or it can be called a driver
#include "string.h"
#define DHTPIN 14 // DHT11 data pin connected to D5 pin D5 pin's PIN value is 14
#define DHTTYPE DHT11 // Define DHT11 sensor
DHT dht(DHTPIN, DHTTYPE); // Initialize sensor
void setup() {
Serial.begin(115200);
// wemos d1's baud rate is 115200
pinMode(BUILTIN_LED, OUTPUT); // Set an output LED
dht.begin(); // Start sensor
}
char* getDHT11Data() {
float h = dht.readHumidity(); // Get humidity value
float t = dht.readTemperature(); // Get temperature value
static char data[100];
if (isnan(h) || isnan(t)) {
Serial.println("Failed to read from DHT sensor!");
sprintf(data, "Temperature: %.1f, Humidity: %.1f", 0.0, 0.0); // If any value is missing, return two 0.0, so we know the sensor might be faulty
return data;
}
sprintf(data, "Temperature: %.1f, Humidity: %.1f", t, h); // If normal, get the value and format it as a string
return data;
}
void loop() {
char* data = getDHT11Data(); // Get sensor value
Serial.println("got: " + String(data)); // Print topic content
delay(1000); // Delay one second for each loop
}
Continue
At this point, if you are using a regular Arduino Uno R3 board, you can finish here.
After obtaining the data, you can do other things based on the data, such as turning on a relay connected to the D6 pin, which controls a humidifier.
If you, like me, are using a board with wifi capability, you can continue to follow along.
We continue with step-by-step operations:
Device Side:
-
Import the esp8266 library (the installation process has been mentioned above)
-
arduino #include "ESP8266WiFi.h"
-
Install the mqtt client library, directly search for PubSubClient in the library store, download the one by Nick O’Leary, after downloading:
-
arduino #include "PubSubClient.h"
-
At this point, all library files have been installed and imported.
-
Set the wifi ssid (name) and password, for example:
-
ini char* ssid = "2104"; char* passwd = "13912428897";
-
Try to connect to wifi.
-
scss WiFiClient espClient; int isConnect = 0; void connectWIFI() { isConnect = 0; WiFi.mode(WIFI_STA); // Not sure what this means, just write it as is WiFi.begin(ssid, passwd); // Try to connect int timeCount = 0; // Attempt count while (WiFi.status() != WL_CONNECTED) { // If not connected, continue looping for (int i = 200; i <= 255; i++) { analogWrite(BUILTIN_LED, i); delay(2); } for (int i = 255; i >= 200; i--) { analogWrite(BUILTIN_LED, i); delay(2); } // The above two loops take about 200ms, just controlling the LED to blink Serial.println("wifi connecting......" + String(timeCount)); timeCount++; isConnect = 1; // Each time, the connection status code needs to be set, only set to 0 when it cannot connect // digitalWrite(BUILTIN_LED, LOW); if (timeCount >= 200) { // If not connected after 40000 milliseconds, stop trying isConnect = 0; // Set status code to 0 break; } } if (isConnect == 1) { Serial.println("Connect to wifi successfully!" + String("SSID is ") + WiFi.SSID()); Serial.println(String("mac address is ") + WiFi.macAddress()); // digitalWrite(BUILTIN_LED, LOW); analogWrite(BUILTIN_LED, 250); // Set LED to always on, 250 brightness is suitable for me settMqttConfig(); // Try to connect to mqtt server, detailed code in the next step } else { analogWrite(BUILTIN_LED, 255); // Set LED to always off, don't ask me why 255 is always off, because my light turns off at high level // Connection to wifi failed, wait a minute to reconnect delay(60000); } }
-
Try to connect to mqtt.
-
arduino const char* mqtt_server = "larryblog.top"; // This is my server, when you see this article, it may have expired, as my server has 11 days left const char* TOPIC = "testtopic"; // Set the message topic const char* client_id = "mqttx_3b2687d2"; // client_id must be unique, can be named arbitrarily, equivalent to your nickname PubSubClient client(espClient); void settMqttConfig() { client.setServer(mqtt_server, 1883); // Set the MQTT server and the port used, 1883 is the default MQTT port client.setCallback(onMessage); // Set the receiving function, when the subscribed topic has messages, it will enter this function Serial.println("try connect mqtt broker"); client.connect(client_id, "wemos", "aa995231030"); // The last two parameters are username and password client.subscribe(TOPIC); // Subscribe to the topic Serial.println("mqtt connected"); // If everything is normal, it will connect } // Receiving function void onMessage(char* topic, byte* payload, unsigned int length) { Serial.print("Message arrived ["); Serial.print(topic); // Print topic information Serial.print("]:"); char* payloadStr = (char*)malloc(length + 1); memcpy(payloadStr, payload, length); payloadStr[length] = '\0'; Serial.println(payloadStr); // Print topic content if (strcmp(payloadStr, (char*)"getDHTData") == 0) { char* data = getDHT11Data(); Serial.println("got: " + String(data)); // Print topic content client.publish("wemos/dht11", data); } free(payloadStr); // Free memory }
-
Send messages.
-
arduino client.publish("home/status/", "{device:client_id,'status':'on'}"); // Note, here sending messages to another topic, the message content indicates the device is online, when other clients (such as web clients) subscribe to this topic, they will receive this message
At this point, the code on the board is basically written, the complete code is as follows:
scss
#include "ESP8266WiFi.h"
#include "PubSubClient.h"
#include "DHT.h"
#include "string.h"
#define DHTPIN 14 // DHT11 data pin connected to D5 pin
#define DHTTYPE DHT11 // DHT11 sensor
DHT dht(DHTPIN, DHTTYPE);
char* ssid = "2104";
char* passwd = "13912428897";
const char* mqtt_server = "larryblog.top";
const char* TOPIC = "testtopic"; // Subscription message topic
const char* client_id = "mqttx_3b2687d2";
int isConnect = 0;
WiFiClient espClient;
PubSubClient client(espClient);
long lastMsg = 0;
void setup() {
Serial.begin(115200);
// Set WiFi to station mode
connectWIFI();
pinMode(BUILTIN_LED, OUTPUT);
dht.begin();
}
char* getDHT11Data() {
float h = dht.readHumidity();
float t = dht.readTemperature();
static char data[100];
if (isnan(h) || isnan(t)) {
Serial.println("Failed to read from DHT sensor!");
sprintf(data, "Temperature: %.1f, Humidity: %.1f", 0.0, 0.0);
return data;
}
sprintf(data, "Temperature: %.1f, Humidity: %.1f", t, h);
return data;
}
void connectWIFI() {
isConnect = 0;
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, passwd);
int timeCount = 0;
while (WiFi.status() != WL_CONNECTED) {
for (int i = 200; i <= 255; i++) {
analogWrite(BUILTIN_LED, i);
delay(2);
}
for (int i = 255; i >= 200; i--) {
analogWrite(BUILTIN_LED, i);
delay(2);
}
// The above two loops take about 200ms
Serial.println("wifi connecting......" + String(timeCount));
timeCount++;
isConnect = 1;
if (timeCount >= 200) {
isConnect = 0;
break;
}
}
if (isConnect == 1) {
Serial.println("Connect to wifi successfully!" + String("SSID is ") + WiFi.SSID());
Serial.println(String("mac address is ") + WiFi.macAddress());
analogWrite(BUILTIN_LED, 250);
settMqttConfig();
} else {
analogWrite(BUILTIN_LED, 255);
delay(60000);
}
}
void settMqttConfig() {
client.setServer(mqtt_server, 1883);
client.setCallback(onMessage);
Serial.println("try connect mqtt broker");
client.connect(client_id, "wemos", "aa995231030");
client.subscribe(TOPIC);
Serial.println("mqtt connected");
}
void onMessage(char* topic, byte* payload, unsigned int length) {
Serial.print("Message arrived [");
Serial.print(topic);
Serial.print("]:");
char* payloadStr = (char*)malloc(length + 1);
memcpy(payloadStr, payload, length);
payloadStr[length] = '\0';
Serial.println(payloadStr);
if (strcmp(payloadStr, (char*)"getDHTData") == 0) {
char* data = getDHT11Data();
Serial.println("got: " + String(data));
client.publish("wemos/dht11", data);
}
free(payloadStr);
}
void publishDhtData() {
char* data = getDHT11Data();
Serial.println("got: " + String(data));
client.publish("wemos/dht11", data);
delay(2000);
}
void reconnect() {
Serial.print("Attempting MQTT connection...");
if (client.connect(client_id, "wemos", "aa995231030")) {
Serial.println("reconnected successfully");
client.subscribe(TOPIC);
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
delay(5000);
}
}
void loop() {
if (!client.connected() && isConnect == 1) {
reconnect();
}
if (WiFi.status() != WL_CONNECTED) {
connectWIFI();
}
client.loop();
publishDhtData();
long now = millis();
if (now - lastMsg > 2000) {
lastMsg = now;
client.publish("home/status/", "{device:client_id,'status':'on'}");
}
delay(1000);
}
Server
The previous operations may have been confusing, but don’t worry, through the server settings, you will understand the mqtt mechanism more thoroughly.
We need to deploy the mqtt broker on the server, which is the message center server for mqtt.
Search for emqx online, click EMQX: Large-scale Distributed IoT MQTT Message Server, this is a software with a visual interface that is very beautiful, operates very smoothly, and has powerful functions, making it easy to use. Click to download immediately and choose the version suitable for your server system:

Here I will explain using Ubuntu and Windows as examples, other systems are likely similar.
On Ubuntu, it is recommended to use apt to download, follow the steps in the image above, and if you encounter any other issues, please solve them yourself.
-
sudo ufw status to check open ports, generally, you will only see a few ports you have opened manually, or only ports 80 and 443 -
sudo ufw allow 18083 this port is for the emqx dashboard, after opening this port, you can access the emqx dashboard control panel from the external network
When you see the image as shown, it means that it has been successfully opened.
On Windows, directly download the installation package, upload it to the server, and double-click to install.
-
Open “Advanced Security Windows Defender Firewall”, click Inbound Rules> New Rule -
Click Port > Next -
Click TCP, Specific local ports, enter 18083, click Next -
Keep clicking Next until the last step, enter a name, it is recommended to enter emqx.

When you see the image as shown, it means you have configured it successfully.
After completing the installation of the server program and the firewall port configuration, we need to configure the security policy in the server background, here I will take Alibaba Cloud as an example:
If you are using an ECS cloud host, click on Instances> Click on your server name> Security Group> Configure Rules> Manually Add
Add the following:

If you are using a lightweight server, click Security> Firewall> Add Rule, and it is similar to ECS settings.
After completing the configuration, you can try to access your emqx control panel in your local browser.

Directly enter the domain name:18083, the initial username is admin, the initial password is public, after logging in, you will see the following screen.

Next, you need to configure the client login name and password, for example, the username and password written in the device above are set in this system.
Click Access Control> Authentication > Create, then just keep clicking Next until the end, and you will see the following screen.

Click User Management, add users, and the username and password are customizable. These usernames and passwords can be assigned to the device side, client side, server side, and testing side, you can refer to my configuration.

userClient is for the front-end page, server is for the back-end, 995231030 is my personal super user, wemos is for the device, which is the username and password inputted during the device connection.
Thus, the emqx control panel configuration is complete.
Download mqttx to try connecting as a test client.

Click connect, you will find that it cannot connect at all…
Because, 1883 (the default mqtt port) is also not opened, of course, open it in the same way as opening 18083.
At the same time, it is also recommended to open:
-
1803 websocket default port -
1804 websockets default port -
3306 mysql default port
The last four ports will be used.
Once you have opened them, try connecting mqttx to the broker again, and you will find that you can connect.

The function of this page is also easy to understand, we add subscriptions on the left side, and the messages of that topic will appear in the chat box on the right side.

If you remember, in the device code above, we sent a device online notification to home/status/ every second in the loop, and we received that message here.
When you see these messages, it indicates that your device, server, and emqx control panel are all running smoothly.
Front-End, Back-End, and Database
Front-End
There is not much to say about the front-end; we use echarts to display data. Since the amount is small, we do not use any frameworks, directly using jq and echarts. Here, I will mainly talk about how the front-end connects to mqtt.
First, import the mqtt library.
xml
<script src="https://cdn.bootcdn.net/ajax/libs/mqtt/4.1.0/mqtt.min.js"></script>
Then set the connection parameters.
javascript
const options = {
clean: true, // true: clear session, false: keep session
connectTimeout: 4000, // timeout
clientId: 'userClient_' + generateRandomString(),
// The front-end client may be numerous, so we generate a random 6-character alphanumeric clientId to ensure it is not duplicated
username: 'userClient',
password: 'aa995231030',
}
function generateRandomString() {
let result = '';
let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let charactersLength = characters.length;
for (let i = 0; i < 6; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
Connect.
ini
// const connectUrl = 'mqtt://larryblog.top/mqtt' You can use the mqtt protocol, but you may encounter ssl cross-domain issues. If you do not use https, you can ignore this item, just use mqtt directly.
const connectUrl = 'wss://larryblog.top/mqtt' // Note, here we use nginx for forwarding, which will be explained later.
const client = mqtt.connect(connectUrl, options)
Since there is not much front-end code, I will paste it directly.
HTML:
index.html
xml
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet/less" href="./style.less">
<link rel="stylesheet" href="//at.alicdn.com/t/c/font_3712319_bzaequy11dn.css">
<script src="https://cdn.bootcdn.net/ajax/libs/less.js/4.1.3/less.js"></script>
<title>wemos d1 test</title>
</head>
<body>
<div class="app" id="app">
<div id="deviceStatus">
<span class="statusLight"></span>
<span id="statusText">Loading device status</span>
<!-- <span class="iconfont icon-xinxi"></span> -->
</div>
<div class="container">
<div class="Temperature">
<div id="echartsViewTemperature"></div>
<span>Current temperature:</span>
<span id="Temperature">loading...</span>
</div>
<div class="Humidity">
<div id="echartsViewHumidity"></div>
<span>Current humidity:</span>
<span id="Humidity">loading...</span>
</div>
</div>
</div>
</body>
<script src="./showTip.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.4/moment.min.js"></script>
<script src="https://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/mqtt/4.1.0/mqtt.min.js"></script>
<script src="https://cdn.staticfile.org/echarts/4.7.0/echarts.js"></script>
<script src="./echarts.js?v=1.0.0"></script>
<script src="./mqttController.js"></script>
</html>
mqttController.js
javascript
// const mqtt = require('mqtt')
$(document).ready(() => {
// Welcome to request my open interface. When the device is not online, the latest 2000 pieces of data will be returned
$.post("https://larryblog.top/api", {
topic: "getWemosDhtData",
skip: 0
},
(data, textStatus, jqXHR) => {
setData(data.res)
// console.log("line:77 data==> ", data)
},
);
// for (let i = 0; i <= 10; i++) {
// toast.showToast(1, "test")
// }
const options = {
clean: true, // true: clear session, false: keep session
connectTimeout: 4000, // timeout
// Authentication information
clientId: 'userClient_' + generateRandomString(),
username: 'userClient',
password: 'aa995231030',
// You are welcome to use my open mqtt broker(My server is weak but come on you). When connecting, remember to give yourself a personalized clientId to prevent being squeezed out
// Topic rule:
// baseName/deviceId/events
}
// Connection string, specify the connection method through the protocol
// ws Unencrypted WebSocket connection
// wss Encrypted WebSocket connection
// mqtt Unencrypted TCP connection
// mqtts Encrypted TCP connection
// wxs WeChat mini-program connection
// alis Alipay mini-program connection
let timer;
let isShowTip = 1;
const connectUrl = 'wss://larryblog.top/mqtt';
const client = mqtt.connect(connectUrl, options);
client.on('connect', (error) => {
console.log('Connected:', error);
toast.showToast("Broker Connected");
timer = setTimeout(onTimeout, 3500);
// Subscribe to the topic
client.subscribe('wemos/dht11', function (err) {
if (!err) {
// Publish a message
client.publish('testtopic', 'getDHTData');
}
});
client.subscribe('home/status/');
client.publish('testtopic', 'Hello mqtt');
});
client.on('reconnect', (error) => {
console.log('Reconnecting:', error);
toast.showToast(3, "reconnecting...");
});
client.on('error', (error) => {
console.log('Connection failed:', error);
toast.showToast(2, "connection failed");
});
client.on('message', (topic, message) => {
// console.log('Received message:', topic, message.toString())
switch (topic) {
case "wemos/dht11":
const str = message.toString();
const arr = str.split(", "); // Split the string
const obj = Object.fromEntries(arr.map(s => s.split(": "))); // Convert to an object
document.getElementById("Temperature").innerHTML = obj.Temperature + " ℃";
optionTemperature.xAxis.data.push(moment().format("MM-DD/HH:mm:ss"));
optionTemperature.xAxis.data.length >= 100 && optionTemperature.xAxis.data.shift();
optionTemperature.series[0].data.length >= 100 && optionTemperature.series[0].data.shift();
optionTemperature.series[0].data.push(parseFloat(obj.Temperature));
ChartTemperature.setOption(optionTemperature, true);
document.getElementById("Humidity").innerHTML = obj.Humidity + " %RH";
optionHumidity.xAxis.data.push(moment().format("MM-DD/HH:mm:ss"));
optionHumidity.xAxis.data.length >= 100 && optionHumidity.xAxis.data.shift();
optionHumidity.series[0].data.length >= 100 && optionHumidity.series[0].data.shift();
optionHumidity.series[0].data.push(parseFloat(obj.Humidity));
ChartHumidity.setOption(optionHumidity, true);
break;
case "home/status/":
$("#statusText").text("device online");
deviceOnline();
$(".statusLight").removeClass("off");
$(".statusLight").addClass("on");
clearTimeout(timer);
timer = setTimeout(onTimeout, 3500);
break;
}
});
function deviceOnline() {
if (isShowTip) {
toast.showToast(1, "device online");
}
isShowTip = 0;
}
function setData(data) {
// console.log("line:136 data==> ", data)
for (let i = data.length - 1; i >= 0; i--) {
let item = data[i];
// console.log("line:138 item==> ", item)
optionTemperature.series[0].data.push(item.temperature);
optionHumidity.series[0].data.push(item.humidity);
optionHumidity.xAxis.data.push(moment(item.updateDatetime).format("MM-DD/HH:mm:ss"));
optionTemperature.xAxis.data.push(moment(item.updateDatetime).format("MM-DD/HH:mm:ss"));
}
ChartTemperature.setOption(optionTemperature);
ChartHumidity.setOption(optionHumidity);
}
function onTimeout() {
$("#statusText").text("device offline");
toast.showToast(3, "device offline");
isShowTip = 1;
document.getElementById("Temperature").innerHTML = "No data";
document.getElementById("Humidity").innerHTML = "No data";
$(".statusLight").removeClass("on");
$(".statusLight").addClass("off");
}
function generateRandomString() {
let result = '';
let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let charactersLength = characters.length;
for (let i = 0; i < 6; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
});
showTip.js is a package I published on npm, you can download it yourself if needed.
style.less
css
* {
padding: 0;
margin: 0;
color: #fff;
}
.app {
background: #1b2028;
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
#deviceStatus {
display: flex;
align-items: center;
gap: 10px;
padding: 20px;
.statusLight {
display: block;
height: 10px;
width: 10px;
border-radius: 100px;
background: #b8b8b8;
&.on {
background: #00a890;
}
&.off {
background: #b8b8b8;
}
}
}
.container {
width: 100%;
height: 0;
flex: 1;
display: flex;
@media screen and (max-width: 768px) {
flex-direction: column;
}
>div {
flex: 1;
height: 100%;
text-align: center;
#echartsViewTemperature,
#echartsViewHumidity {
width: 80%;
height: 50%;
margin: 10px auto;
// background: #eee;
}
}
}
}
echarts.js is a file I wrote myself, please do not follow my naming convention, it is a bad example.
yaml
let optionTemperature = null;
let ChartTemperature = null;
$(document).ready(() => {
setTimeout(() => {
// waiting
ChartTemperature = echarts.init(document.getElementById('echartsViewTemperature'));
ChartHumidity = echarts.init(document.getElementById('echartsViewHumidity'));
// Specify the configuration items and data for the chart
optionTemperature = {
textStyle: {
color: '#fff'
},
tooltip: {
trigger: 'axis',
// transitionDuration: 0,
backgroundColor: '#fff',
textStyle: {
color: "#333",
align: "left"
},
},
xAxis: {
min: 0,
data: [],
boundaryGap: false,
splitLine: {
show: false
},
axisLine: {
lineStyle: {
color: '#fff'
}
}
},
yAxis: {
splitLine: {
show: false
},
axisTick: {
show: false // Hide y-axis tick marks
},
axisLine: {
show: false,
lineStyle: {
color: '#fff'
}
}
},
grid: {
// To let the ruler and tooltip extend outside the chart, expand the chart outward a bit
left: '10%',
right: '5%',
bottom: '5%',
top: '5%',
containLabel: true,
},
series: [{
// clipOverflow: false,
name: 'Temperature',
type: 'line',
smooth: true,
symbol: 'none',
data: [],
itemStyle: {
color: '#00a890'
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [{
offset: 0,
color: '#00a89066' // Color at 0%
}, {
offset: 1,
color: '#00a89000' // Color at 100%
}],
global: false // Default is false
}
},
hoverAnimation: true,
label: {
show: false,
},
markLine: {
symbol: ['none', 'none'],
data: [
{
type: 'average',
name: 'Average',
},
],
},
}]
};
optionHumidity = {
textStyle: {
color: '#fff'
},
tooltip: {
trigger: 'axis',
backgroundColor: '#fff',
textStyle: {
color: "#333",
align: "left"
},
},
xAxis: {
min: 0,
data: [],
boundaryGap: false,
splitLine: {
show: false
},
axisTick: {
//x-axis tick related settings
alignWithLabel: true,
},
axisLine: {
lineStyle: {
color: '#fff'
}
}
},
yAxis: {
splitLine: {
show: false
},
axisTick: {
show: false // Hide y-axis tick marks
},
axisLine: {
show: false,
lineStyle: {
color: '#fff'
}
}
},
grid: {
// To let the ruler and tooltip extend outside the chart, expand the chart outward a bit
left: '5%',
right: '5%',
bottom: '5%',
top: '5%',
containLabel: true,
},
// toolbox: {
// feature: {
// dataZoom: {},
// brush: {
// type: ['lineX', 'clear'],
// },
// },
// },
series: [{
clipOverflow: false,
name: 'Humidity',
type: 'line',
smooth: true,
symbol: 'none',
data: [],
itemStyle: {
color: '#ffa74b'
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [{
offset: 0,
color: '#ffa74b66' // Color at 0%
}, {
offset: 1,
color: '#ffa74b00' // Color at 100%
}],
global: false // Default is false
}
},
hoverAnimation: true,
label: {
show: false,
},
markLine: {
symbol: ['none', 'none'],
data: [
{
type: 'average',
name: 'Average',
},
],
},
}]
};
// Use the specified configuration item and data to display the chart.
ChartTemperature.setOption(optionTemperature);
ChartHumidity.setOption(optionHumidity);
}, 100);
});
When you reach here, you should be able to display every message sent from your board on your front-end page, but it still does not reach the level of the densely packed data shown in the first image. I am not keeping the page open for a day; instead, I have stored some data using the back-end and database.
Back-End
The back-end is divided into two parts: a nodejs back-end program and an nginx proxy. Here, I will first talk about the proxy because the front-end connection requires this proxy.
Nginx
If you are not using an https connection, you can skip this section and use the unencrypted mqtt protocol directly. If you have your own domain name and have applied for an ssl certificate, you can refer to my nginx configuration, as follows:
ini
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
##
# SSL Settings
##
server {
listen 80;
server_name jshub.cn;
# Redirect requests to https
rewrite ^(.*)$ https://$host$1 permanent;
}
server {
listen 443 ssl;
server_name jshub.cn;
location / {
root /larryzhu/web/release/toolbox;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
location /mqtt {
proxy_pass http://localhost:8083;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# SSL protocol version
ssl_protocols TLSv1.2;
# Certificate
ssl_certificate /larryzhu/web/keys/9263126_jshub.cn.pem;
# Private key
ssl_certificate_key /larryzhu/web/keys/9263126_jshub.cn.key;
# ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
# ssl_ciphers AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256;
# Default setting reduces packet capture interference
# ssl_session_tickets off;
# return 200 "https ok \n";
}
}
Note that this is only part of the configuration; do not overwrite your entire configuration.
If you do not know how to use nginx, it means you do not need to configure ssl, just use the mqtt protocol directly.
Back-End Program Part
Here, I will take the egg.js framework as an example.
First, you need to download the egg.js plugin egg-emqtt, which can be downloaded directly using npm. For detailed configuration and enabling methods, refer to MQTT series practice two: Using mqtt in EGG.
The method in the above tutorial is not comprehensive, you can download my example and write according to it, as the content is relatively complex. The address is: gitee.com/zhu_yongbo/…
It also includes methods for connecting to a mysql database, which contains the server address, mysql open port, username, and password. My server has less than ten days left, so if someone sees my article, they can do whatever they want with my server; there is no important data.
MySQL
For MySQL, you only need one library and one table to complete all the work.

As shown in the image, it is not complicated; you can create the database according to mine.
One important point is that because MySQL itself is not suitable for storing large amounts of data, and our data is repeated quite a bit, you can consider a compression algorithm or add an event (check the data amount each time you insert). For example, my board has been running normally for a few days (inserting one piece of data every two seconds), and I can see that it has accumulated about 700,000 pieces of data. If I had not set up the insert event, this amount of data would have significantly affected query speed.
You can refer to my event; the statement is as follows:
sql
DELIMITER $$
CREATE TRIGGER delete_oldest_data
AFTER INSERT ON wemosd1_dht11
FOR EACH ROW
BEGIN
-- If the data amount exceeds 43200 (which is a day's worth of data for inserting one piece every two seconds), call the stored procedure to delete the oldest piece of data.
IF (SELECT COUNT(*) FROM wemosd1_dht11) > 43200 THEN
CALL delete_oldest();
END IF;
END$$
DELIMITER ;
-- Create stored procedure
CREATE PROCEDURE delete_oldest()
BEGIN
-- Delete the oldest piece of data
delete from wemosd1_dht11 order by id asc limit 1
END$$
DELIMITER ;
BTW: This was taught to me by chatGPT, and I only made a small modification.
This will delete the data with the smallest id, and will cause the id to grow larger and larger. The advantage is that you can see how many pieces of data have been accumulated. But if you do not want the id to accumulate, you can choose to rebuild the id. For specific methods, it is recommended to consult chatGPT.
Conclusion
Thus, we have completed the connectivity of the front-end, back-end, and device side.
Let’s summarize how the data comes to our eyes step by step:
First, the wemos d1 development board reads the temperature and humidity values from the DHT11 sensor, then the development board broadcasts the data to a certain topic via mqtt, both our front-end and back-end subscribe to this topic, and the back-end stores the processed data in MySQL, while the front-end directly uses echarts to display it. When the front-end starts, it can also query historical data from the back-end program, such as the first 8000 pieces of data. The subsequent changes are provided by the online development board, and we get a real-time temperature and humidity online display that can also see historical data.
If you think this is awesome, give me a thumbs up.
Author: Dreyas
Link: https://juejin.cn/post/7203180003471081531

Previous Recommendations




