I have this code for the backend of a MQTT app
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const mysql = require('mysql2/promise');
const mqtt = require('mqtt');
const topicsConfig = {
'Topic1': 'Table1',
'Topic2': 'Table2',
'Topic3': 'Table3',
'Topic4': 'Table4',
'Topic5': 'Table5',
};
// Assuming this is at the top level of your index.js
let statsPerTopic = Object.keys(topicsConfig).reduce((acc, topic) => {
acc[topic] = {
messageCount: 0,
dbInsertCount: 0,
lastInsertTime: Date.now(),
};
return acc;
}, {});
// Define the MQTT broker options
const options = {
port: process.env.MQTT_PORT,
username: process.env.MQTT_USERNAME,
password: process.env.MQTT_PASSWORD,
clientId: `mqttjs_${Math.random().toString(16).substr(2, 8)}`,
protocol: 'wss',
};
// Connect to the MQTT broker
const client = mqtt.connect(process.env.MQTT_BROKER, options);
client.on('connect', function () {
console.log('Connected to the MQTT broker');
Object.keys(topicsConfig).forEach((topic) => {
client.subscribe(topic, function (err) {
if (!err) {
console.log(`Successfully subscribed to ${topic}`);
} else {
console.error(`Failed to subscribe to ${topic}:`, err);
}
});
});
});
// Latest message storage
let lastMessages = {};
// Function to insert data into the database using a new connection pool
async function insertData(topic, message) {
const table = topicsConfig[topic];
if (topic === 'ICR2431/Ch0') {
// Handle the new format (ICR2431/Ch0) in a separate function
await handleNewFormatData(message, table);
} else {
// Existing logic for handling the old format messages
const data = JSON.parse(message.toString());
let sql = `INSERT INTO `${table}` SET ?`;
let dataObj = { Topic: topic };
// Filter out values starting with "OFFSET_"
data.d.forEach((item) => {
if (!item.tag.startsWith('OFFSET_')) {
dataObj[item.tag] = item.value;
}
});
dataObj.timestamp = new Date(data.ts)
.toISOString()
.slice(0, 19)
.replace('T', ' ');
try {
const pool = mysql.createPool({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0,
});
const connection = await pool.getConnection();
await connection.query(sql, dataObj);
console.log('Data successfully inserted into the database (old format)');
connection.release();
} catch (error) {
console.error(
'Failed to insert data into the database (old format):',
error
);
}
}
}
// Function to handle the new JSON format (ICR2431/Ch0)
async function handleNewFormatData(message, table) {
try {
const data = JSON.parse(message.toString());
let timestamp;
if (data.hasOwnProperty('ts')) {
// Existing format with timezone (ts: "2024-04-19T00:24:39+0300")
timestamp = data.ts;
} else if (data.hasOwnProperty('time')) {
// New format without timezone (time: "2024-04-19 00:23:58.031")
// Parse the string and adjust based on your needs
const timeParts = data.time.split(' ');
const dateString = timeParts[0];
const timeString = timeParts[1] || '00:00:00'; // Handle missing time
timestamp = `${dateString}T${timeString}`; // Assuming format YYYY-MM-DDTHH:mm:ss
} else {
throw new Error('Missing timestamp field in new format JSON');
}
const value = parseFloat(data.value); // Convert value to a number
const SH = 6;
const TL = 4;
const SL = 0;
const TH = 20;
// Calculate b2
const b2 = (SH * TL - SL * TH) / (SH - SL);
// Calculate a2
const a2 = (TH - b2) / SH;
// Calculate value to be inserted
const valueToBeInserted = Math.round((SH - (value - b2) / a2) * 100) / 100;
const insertData = {
topic: data.topic, // Assuming "topic" field exists for ICR2431/Ch0
timestamp: timestamp, // Assuming "time" field holds the timestamp
value: valueToBeInserted,
};
let sql = `INSERT INTO `${table}` SET ?`;
const pool = mysql.createPool({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0,
});
const connection = await pool.getConnection();
await connection.query(sql, insertData);
console.log('Data successfully inserted into the database (new format)');
connection.release();
} catch (error) {
console.error(
'Failed to insert data into the database (new format):',
error
);
}
}
function calculateNextInsertTime(lastInsertTime) {
return new Date(lastInsertTime + 30 * 60 * 1000).toISOString();
}
// Example usage in a function
function updateStats(topic, message) {
if (statsPerTopic[topic]) {
statsPerTopic[topic].messageCount += 1;
statsPerTopic[topic].lastMessageTime = Date.now(); // Update the last message timestamp
} else {
console.error(`No stats entry found for topic: ${topic}`);
}
}
client.on('message', (receivedTopic, message) => {
console.log(`Received message on ${receivedTopic}:`, message.toString());
if (topicsConfig.hasOwnProperty(receivedTopic)) {
lastMessages[receivedTopic] = message; // Store the most recent message
if (statsPerTopic[receivedTopic]) {
statsPerTopic[receivedTopic].messageCount += 1;
statsPerTopic[receivedTopic].lastMessageTime = Date.now(); // Update the last message time
} else {
console.error(`No stats entry found for topic: ${receivedTopic}`);
}
}
});
function calculateNextInsertTime(lastInsertTime) {
const nextInsertTime = new Date(lastInsertTime + 30 * 60 * 1000); // 30 minutes from the last insert
return nextInsertTime;
}
// In your interval where you insert data
setInterval(() => {
const currentTime = Date.now();
Object.keys(lastMessages).forEach((topic) => {
if (lastMessages[topic]) {
insertData(topic, lastMessages[topic]);
statsPerTopic[topic].dbInsertCount += 1;
statsPerTopic[topic].lastInsertTime = currentTime; // Update last insert time
lastMessages[topic] = null; // Clear after insertion
}
});
}, 30 * 60 * 1000); // 30 minutes
// Define a different threshold for considering a topic as inactive
const INACTIVITY_THRESHOLD = 5 * 60 * 1000; // e.g., 5 minutes
// Interval for checking inactivity
setInterval(() => {
const currentTime = Date.now();
Object.keys(statsPerTopic).forEach((topic) => {
if (
currentTime - statsPerTopic[topic].lastMessageTime >
INACTIVITY_THRESHOLD
) {
// Mark the topic as inactive
statsPerTopic[topic].active = false;
} else {
// Ensure the topic is marked as active
statsPerTopic[topic].active = true;
}
});
}, INACTIVITY_THRESHOLD);
// Set up Express.js server
const app = express();
// Serve static files from the current directory
app.use(express.static(__dirname));
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname + '/index.html'));
});
app.use(cors());
app.get('/data', (req, res) => {
const currentTime = new Date().getTime(); // Get the current time in milliseconds
const preparedData = Object.keys(topicsConfig).map((topic) => {
const stats = statsPerTopic[topic];
const nextInsertTime = calculateNextInsertTime(stats.lastInsertTime);
const timeRemaining = nextInsertTime - currentTime; // Time remaining in milliseconds
const active = currentTime - stats.lastMessageTime <= INACTIVITY_THRESHOLD; // Check if the topic is active
console.log(
`Topic: ${topic}, Last Message: ${stats.lastMessageTime}, Current Time: ${currentTime}, Active: ${active}`
);
return {
topic: topic,
tableName: topicsConfig[topic],
messageCount: stats.messageCount,
dbInsertCount: stats.dbInsertCount,
timeRemaining: timeRemaining,
active: active, // Include the active status
};
});
res.json({ stats: preparedData });
});
app.listen(8080, () => {
console.log('Server is running on port 8080');
});
and this code for the front end
<!DOCTYPE html>
<html>
<head>
<title>Monitoring Page</title>
<link
href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
rel="stylesheet" />
<link rel="stylesheet" type="text/css" href="custom.css" />
</head>
<body>
<div id="panels" class="container panels-container">
<!-- Pane for Topic 1 -->
<div class="row g-5">
<div class="col-md-4">
<div class="panel" id="panel1">
<h2>Topic<br /><span>VivusDigester003/Ecu</span></h2>
<div class="info-box">
<p>DB Table: <span class="tableName">Loading...</span></p>
<p>
Messages received:
<span class="messages">Loading...</span>
</p>
<p>
Messages stored:
<span class="dbInserts">Loading...</span>
</p>
<p>
Next insert in:
<span class="timer">Loading...</span>
</p>
</div>
</div>
</div>
<!-- Pane for Topic 2 -->
<div class="col-md-4">
<div class="panel" id="panel2">
<h2>Topic<br /><span>Poursalidis/Ecu</span></h2>
<div class="info-box">
<p>DB Table: <span class="tableName">Loading...</span></p>
<p>
Messages received:
<span class="messages">Loading...</span>
</p>
<p>
Messages stored:
<span class="dbInserts">Loading...</span>
</p>
<p>
Next insert in:
<span class="timer">Loading...</span>
</p>
</div>
</div>
</div>
<!-- Pane for Topic 3 -->
<div class="col-md-4">
<div class="panel" id="panel3">
<h2>Topic<br /><span>VivusTHDigester004/Ecu</span></h2>
<div class="info-box">
<p>DB Table: <span class="tableName">Loading...</span></p>
<p>
Messages received:
<span class="messages">Loading...</span>
</p>
<p>
Messages stored:
<span class="dbInserts">Loading...</span>
</p>
<p>
Next insert in:
<span class="timer">Loading...</span>
</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="panel" id="panel4">
<h2>Topic<br /><span>data/tsiap01</span></h2>
<div class="info-box">
<p>DB Table: <span class="tableName">Loading...</span></p>
<p>
Messages received:
<span class="messages">Loading...</span>
</p>
<p>
Messages stored:
<span class="dbInserts">Loading...</span>
</p>
<p>
Next insert in:
<span class="timer">Loading...</span>
</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="panel" id="panel5">
<h2>Topic<br /><span>ICR2431/Ch0</span></h2>
<div class="info-box">
<p>DB Table: <span class="tableName">Loading...</span></p>
<p>
Messages received:
<span class="messages">Loading...</span>
</p>
<p>
Messages stored:
<span class="dbInserts">Loading...</span>
</p>
<p>
Next insert in:
<span class="timer">Loading...</span>
</p>
</div>
</div>
</div>
</div>
</div>
<script>
function updatePane(paneId, data) {
const pane = document.getElementById(paneId);
pane.querySelector('.tableName').textContent = data.tableName;
pane.querySelector('.messages').textContent = data.active
? data.messageCount
: `${data.messageCount} (stopped)`;
pane.querySelector('.dbInserts').textContent = data.dbInsertCount;
pane.querySelector('.timer').textContent = data.active
? formatTimeRemaining(data.timeRemaining)
: 'No data received';
}
function formatTimeRemaining(timeRemainingMs) {
const minutes = Math.floor(timeRemainingMs / 60000);
const seconds = Math.floor((timeRemainingMs % 60000) / 1000);
return `${minutes} minutes and ${
seconds < 10 ? '0' : ''
}${seconds} seconds`;
}
function fetchData() {
// fetch('http://XX.XXX.XX.XXXX:8080/data')
fetch('/data')
.then((response) => response.json())
.then((data) => {
if (data.stats) {
data.stats.forEach((stat) => {
if (stat.topic === 'Topic1') {
updatePane('panel1', stat);
} else if (stat.topic === 'Topic2') {
updatePane('panel2', stat);
} else if (stat.topic === 'Topic3') {
updatePane('panel3', stat);
} else if (stat.topic === 'Topic4') {
updatePane('panel4', stat);
} else if (stat.topic === 'Topic5') {
updatePane('panel5', stat);
}
});
}
})
.catch((error) => console.error('Fetch error:', error));
}
setInterval(fetchData, 5000);
</script>
</body>
</html>
It works perfectly locally, but when I upload it to a server I have to uncomment this line “// fetch(‘http://XX.XXX.XX.XXXX:8080/data’)” and declare the IP of the server and, of course, comment the “fetch(/data)” line.
Could someone explain why I have to do that?
I’m not using any subfolders on the web server. All files are located to the same folder