hi there,
i tried to make two code in two different uibuilder node, now wanted to make it in single code for single uibuilder but there is some issued which i dont understand how to solve it
so for payload which i send to uibuilder from function node
msg.topic = "control";
msg.payload = {
"FRONT_ZONE_TEMP": Math.floor(Math.random() * 500),
"MIDDLE_ZONE_TEMP": Math.floor(Math.random() * 500),
"BACK_ZONE_TEMP": Math.floor(Math.random() * 500),
"BLOWER_FAN1": Math.round(Math.random()), // Random 0 or 1
"BLOWER_FAN2": Math.round(Math.random()), // Random 0 or 1
"door": true, // Alternates between true/false
"cont": true // Matches door state (both true or both false)
};
return msg;
so below is index.js code of first uibuilder node
'use strict';
let chartConfigurations = [];
let deviceStatus = {};
document.addEventListener("DOMContentLoaded", function() {
let progressBar = document.getElementById("loading-bar");
let progress = 0;
// Simulate loading progress
let interval = setInterval(function() {
progress += 10;
progressBar.style.width = progress + "%";
progressBar.setAttribute("aria-valuenow", progress);
if (progress >= 100) {
clearInterval(interval);
setTimeout(() => {
document.getElementById("loading-container").style.display = "none";
document.getElementById("content").style.display = "block";
}, 500);
}
}, 300);
});
// Function to update the UI based on deviceStatus
function updateUI(container) {
container.innerHTML = ''; // Clear previous UI elements
// Iterate through the stored statuses and update the UI
Object.entries(deviceStatus).forEach(([host, info]) => {
const {
name,
status
} = info;
// Create a new container for the status box and the IP address
const statusContainer = document.createElement('div');
statusContainer.className = 'text-center mx-2'; // Center text and add margin
const statusIndicator = document.createElement('div');
statusIndicator.className = `status-box ${status === 'online' ? 'bg-success blink-fast' : 'bg-danger blink-slow'}`;
// Append the status indicator to the container
statusContainer.appendChild(statusIndicator);
// Create a container for the name and IP address
const nameContainer = document.createElement('div');
nameContainer.className = 'text-center text-bold'; // Apply bold text
// Create and append the device name above the IP address
const nameText = document.createElement('p');
nameText.textContent = name || 'Unknown Name'; // Display the name, or "Unknown Name" if not available
nameText.className = 'font-weight-bold'; // Make the name bold
nameContainer.appendChild(nameText);
// Create and append the host/IP address below the name
const hostText = document.createElement('p');
hostText.textContent = host;
nameContainer.appendChild(hostText);
// Append the name container to the status container
statusContainer.appendChild(nameContainer);
// Append the status container to the main ping status container
container.appendChild(statusContainer);
});
}
// Fetch stored chart configurations from server
function fetchConfigurationsFromServer() {
fetch('/fetch-config')
.then(response => response.json())
.then(data => {
console.log('Fetched configurations:', data);
chartConfigurations = data;
initializeCharts();
})
.catch(error => console.error('Error fetching configurations:', error));
}
// Auto-refresh configurations on page load
document.addEventListener('DOMContentLoaded', () => {
fetchConfigurationsFromServer();
});
// Handle incoming messages from Node-RED
uibuilder.onChange('msg', (msg) => {
if (msg.payload) {
console.log('Received payload:', msg.payload); // Log the received payload
updateCharts(msg.payload); // Update charts based on new payload data
} else {
console.error('No payload received in message.');
}
console.log('Received message:', msg); // Debugging log
const pingStatusContainer = document.getElementById('ping-status');
// Validate msg.payload is an object
if (msg.payload && typeof msg.payload === 'object') {
const {
host,
name = 'Unknown Name',
status = 'offline'
} = msg.payload; // Provide defaults if undefined
if (!host) {
//console.warn("Missing 'host' in payload:", msg.payload);
return; // Skip processing if host is missing
}
// Update global device status tracking
deviceStatus[host] = {
name,
status
};
// Update UI
updateUI(pingStatusContainer);
} else {
console.error('Invalid payload format:', msg.payload);
}
console.log("Updated Device Status:", deviceStatus);
});
// Load chart configurations from the server
function loadConfigurationsFromServer() {
fetch('/fetch-config')
.then(response => response.json())
.then(data => {
chartConfigurations = data;
console.log('Loaded chart configurations from server:', chartConfigurations);
initializeCharts(); // Reinitialize charts with the fetched configurations
})
.catch(err => {
console.error('Error fetching configurations:', err);
});
}
document.addEventListener('DOMContentLoaded', () => {
loadConfigurationsFromServer(); // Load configurations on page load
});
// Dynamically create charts based on configurations
function initializeCharts() {
const chartsContainer = document.getElementById('charts-container');
if (!chartsContainer) {
console.error('Charts container not found!');
return;
}
chartsContainer.innerHTML = ''; // Clear any existing charts
if (Array.isArray(chartConfigurations) && chartConfigurations.length > 0) {
chartConfigurations.forEach((config, configIndex) => {
if (config.payloadNames && Array.isArray(config.payloadNames)) {
config.payloadNames.forEach((payloadName, keyIndex) => {
const chartWrapper = document.createElement('div');
chartWrapper.className = 'chart-wrapper';
const chartCanvas = document.createElement('canvas');
chartCanvas.id = `chart-${configIndex}-${keyIndex}`;
chartWrapper.appendChild(chartCanvas);
chartsContainer.appendChild(chartWrapper);
const ctx = chartCanvas.getContext('2d');
const dataset = {
label: payloadName,
data: [], // Empty dataset initially
borderColor: `rgba(${Math.random() * 255}, ${Math.random() * 255}, ${Math.random() * 255}, 1)`,
backgroundColor: `rgba(${Math.random() * 255}, ${Math.random() * 255}, ${Math.random() * 255}, 0.2)`,
borderWidth: 2,
};
new Chart(ctx, {
type: config.chartType || 'line', // Default to 'line' if no type is provided
data: {
labels: [], // Labels are dynamically updated
datasets: [dataset],
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
labels: {
color: 'black', // Set legend text color to black
generateLabels: (chart) => {
return chart.data.datasets.map((dataset, index) => {
let lastValue;
// Handle different data types
if (dataset.data.length > 0) {
const lastDataPoint = dataset.data[dataset.data.length - 1];
if (typeof lastDataPoint === 'object' && lastDataPoint !== null) {
// For Scatter/Bubble charts, format the object {x, y, r} or {x, y}
const x = lastDataPoint.x !== undefined ? `x: ${lastDataPoint.x}` : '';
const y = lastDataPoint.y !== undefined ? `y: ${lastDataPoint.y}` : '';
lastValue = `${x}, ${y}`.trim();
} else {
// For other chart types, use the raw value directly
lastValue = lastDataPoint;
}
} else {
lastValue = 'N/A'; // Default to 'N/A' if no value exists
}
return {
text: `${dataset.label}: ${lastValue}`,
fillStyle: dataset.borderColor, // Use dataset color as fill style
fontColor: 'black', // Set text color explicitly to black
hidden: !chart.isDatasetVisible(index),
};
});
},
},
},
tooltip: {
callbacks: {
label: (tooltipItem) => {
const point = tooltipItem.raw;
return `${tooltipItem.dataset.label}: ${point}`;
},
},
},
},
},
});
});
}
});
} else {
console.warn('No valid chart configurations found.');
}
}
// Update chart data dynamically when new payload is received
function updateCharts(payload) {
console.log('Updating charts with new payload:', payload);
chartConfigurations.forEach((config, configIndex) => {
if (config.payloadNames && Array.isArray(config.payloadNames)) {
config.payloadNames.forEach((key, keyIndex) => {
const chartId = `chart-${configIndex}-${keyIndex}`;
const chart = Chart.getChart(chartId);
if (!chart) {
console.warn(`Chart ${chartId} not found.`);
return;
}
if (payload[key] !== undefined) {
const currentTime = new Date().toLocaleTimeString();
if (config.chartType === 'bubble') {
// For Bubble chart: {x, y, r}
chart.data.datasets[0].data.push({
x: Math.random() * 100, // Example value, replace as needed
y: payload[key],
r: Math.random() * 10 + 5, // Example radius
});
} else if (config.chartType === 'scatter') {
// For Scatter chart: {x, y}
chart.data.datasets[0].data.push({
x: Math.random() * 100, // Example value, replace as needed
y: payload[key],
});
} else {
// For other chart types
chart.data.labels.push(currentTime);
chart.data.datasets[0].data.push(payload[key]);
}
// Keep only the last 10 data points
if (chart.data.labels.length > 10) {
chart.data.labels = chart.data.labels.slice(-10);
chart.data.datasets[0].data = chart.data.datasets[0].data.slice(-10);
}
chart.update();
}
});
}
});
}
// for header and footer
// Function to load an external HTML file into a div
function loadHTML(id, file) {
fetch(file)
.then(response => response.text())
.then(data => {
document.getElementById(id).innerHTML = data;
})
.catch(error => console.error("Error loading " + file, error));
}
and below is another index.js code
// State variables to track the last-known state of the blower fans
const lastFanState = {
'BLOWER_FAN1': 0,
'BLOWER_FAN2': 0,
};
// Listen for incoming messages and process them
uibuilder.onChange('msg', function(msg) {
if (!msg || !msg.payload) {
console.error("No payload in message.");
return;
}
console.log('Message received from Node-RED:', msg);
// Update temperatures and fire particles directly using real names
updateFireParticles('fire-1', 'temp-reading-1', 'BACK ZONE TEMP', msg.payload['BACK_ZONE_TEMP']);
updateFireParticles('fire-2', 'temp-reading-2', 'MIDDLE ZONE TEMP', msg.payload['MIDDLE_ZONE_TEMP']);
updateFireParticles('fire-3', 'temp-reading-3', 'FRONT ZONE TEMP', msg.payload['FRONT_ZONE_TEMP']);
// Update fan rotations only if the state changes
updateFanRotation('fan1', msg.payload.BLOWER_FAN1, 'BLOWER_FAN1');
updateFanRotation('fan2', msg.payload.BLOWER_FAN2, 'BLOWER_FAN2');
// Update dynamic data display if additional data exists
updateData(msg.payload);
// Update the last updated time
updateDateTime();
});
// Function to update fire particles and temperature readings
function updateFireParticles(fireId, tempReadingId, zoneName, temperature) {
const fire = document.getElementById(fireId);
const tempReading = document.getElementById(tempReadingId);
if (!fire || !tempReading) {
console.error(`Element with ID ${fireId} or ${tempReadingId} not found.`);
return;
}
if (temperature === 0) {
fire.style.display = 'none';
tempReading.textContent = `${zoneName}: No fire (0°C)`;
return;
} else {
fire.style.display = 'block';
}
// Determine fire scale and particle count based on temperature
let color, scale, numParticles;
if (temperature >= 500) {
color = 'red';
scale = 1.5; // Large fire
numParticles = 50; // Maximum particles
} else if (temperature >= 250) {
color = 'orange';
scale = 1.2; // Medium fire
numParticles = 30; // Moderate particles
} else {
color = 'yellow';
scale = 0.8 + (temperature / 250) * 0.4; // Smaller fire, dynamically scaled
numParticles = Math.max(10, Math.floor((temperature / 250) * 30)); // Minimum 10 particles
}
// Adjust fire size (scale)
fire.style.transform = `scale(${scale})`;
// Set fire color (if applicable) and particle count
fire.style.backgroundColor = color; // Assuming fire color is represented by background color
fire.dataset.particles = numParticles; // Store particle count in a custom data attribute
// Update the temperature reading
tempReading.textContent = `${zoneName}: ${temperature} °C`;
}
// Function to update fan rotation based on blower status
function updateFanRotation(fanId, fanStatus, fanName) {
const fan = document.getElementById(fanId);
if (!fan) {
console.error(`Fan element with ID ${fanId} not found.`);
return;
}
const spinElement = fan.querySelector('.spin');
if (!spinElement) {
console.error(`Spin element not found inside fan ${fanId}.`);
return;
}
// Debugging log
console.log(`${fanName} received status:`, fanStatus);
// Ensure only updates when the status changes
if (lastFanState[fanName] !== fanStatus) {
if (fanStatus === 1) {
spinElement.style.animation = 'spin 1s linear infinite'; // Ensure animation runs
console.log(`${fanName} is ON.`);
} else { // Ensure OFF state is properly handled
spinElement.style.animation = 'none'; // Stop animation immediately
console.log(`${fanName} is OFF.`);
}
// Update the last-known state
lastFanState[fanName] = fanStatus;
}
}
// Function to update dynamic data display
function updateData(data) {
const dataContent = document.getElementById('data-content');
if (!dataContent) return;
dataContent.innerHTML = '';
for (const [key, value] of Object.entries(data)) {
const card = document.createElement('div');
card.className = 'col-md-4';
card.innerHTML = `
<div class="card">
<div class="card-body">
<h5 class="card-title">${key}</h5>
<p class="card-text">Value: ${value}</p>
</div>
</div>
`;
dataContent.appendChild(card);
}
}
// Function to update the last updated date and time
function updateDateTime() {
const now = new Date();
const dateTimeElement = document.getElementById('date-time');
if (dateTimeElement) {
dateTimeElement.innerText = `Last Updated: ${now.toLocaleString()}`;
}
}
// Automatically update date and time every second
setInterval(updateDateTime, 1000);
// Function to request fresh data from Node-RED
function requestData() {
uibuilder.send({
topic: 'requestData',
payload: {}
});
}
// Wait for message from Node-RED
// Listen for messages from Node-RED
uibuilder.onChange('msg', function(msg) {
console.log("Received message from Node-RED:", msg); // Debugging log
// Ensure the message has the correct topic
if (msg.topic === "control") {
updateUI(msg.payload);
}
});
// Function to update the UI based on received payload
function updateUI(payload) {
let doorElement = document.querySelector('.door');
let contElement = document.querySelector('.cont');
let shapeElement = document.querySelector('.shape-dark-outline');
if (!doorElement || !contElement || !shapeElement) {
console.error("One or more elements not found!");
return;
}
if (payload.door) {
// Step 1: Open the door
doorElement.style.top = "35%";
console.log("Door is OPEN");
// Step 2: Move cont smoothly with conveyor animation
shapeElement.style.animation = "conveyor-belt 5s linear infinite";
contElement.style.transition = "left 5s linear"; // Smooth movement
contElement.style.left = "28em";
console.log("Cont moving with animation");
// Step 3: Hold at left:28em for 3 seconds before disappearing
setTimeout(() => {
console.log("Cont is holding at left: 28em");
// Step 4: Make cont disappear after holding for 3 seconds
setTimeout(() => {
contElement.style.display = "none"; // Hide cont
shapeElement.style.animation = "none"; // Stop conveyor animation
console.log("Cont disappeared, conveyor animation stopped");
// Step 5: Reset everything after 2 more seconds (Total: 10s)
setTimeout(() => {
doorElement.style.top = "42%"; // Reset door position
contElement.style.display = "block"; // Show cont again
contElement.style.left = "15em"; // Reset cont position
contElement.style.transition = "none"; // Remove transition for instant reset
console.log("Reset complete, system ready for next cycle");
}, 2000);
}, 3000); // Hold at 28em for 3 seconds before disappearing
}, 5000); // Move to 28em over 5 seconds
}
}
which both i have to merged in single code so that it can work.