#Create the installation script
nano install-whatsapp.sh
#!/bin/bash
#WhatsApp Setup Script for ARM64 Debian-based Industrial Gateway
echo "Setting up WhatsApp Web Gateway..."
#Create directory for WhatsApp client
mkdir -p ~/whatsapp-client
cd ~/whatsapp-client
#Install Chromium browser for ARM64
sudo apt update
sudo apt install -y chromium
#Install Node.js and npm if not already installed
if ! command -v node &> /dev/null; then
echo "Installing Node.js..."
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt install -y nodejs
fi
#Create package.json with ARM64-compatible dependencies
cat > package.json << 'EOF'
{
"name": "whatsapp-industrial-gateway",
"version": "1.0.0",
"description": "WhatsApp client for industrial gateway on ARM64",
"main": "whatsapp.js",
"dependencies": {
"express": "^4.18.2",
"qrcode": "^1.5.1",
"whatsapp-web.js": "^1.19.5"
}
}
EOF
#Install dependencies with Puppeteer skip download flag
export PUPPETEER_SKIP_DOWNLOAD=true
npm install
#Create the main whatsapp.js file
cat > whatsapp.js << 'EOF'
const { Client, LocalAuth } = require('whatsapp-web.js');
const qrcode = require('qrcode');
const express = require('express');
const app = express();
const port = 3000;
// Configure Express to parse JSON
app.use(express.json());
// Store QR code for web display
let qrCodeData = null;
let connectionStatus = 'initializing'; // possible values: initializing, qr-ready, authenticated, ready, disconnected
console.log('Starting WhatsApp client...');
// Create a new WhatsApp client with ARM64-compatible configuration
const client = new Client({
authStrategy: new LocalAuth(),
puppeteer: {
executablePath: '/usr/bin/chromium',
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--disable-accelerated-2d-canvas',
'--no-first-run',
'--no-zygote',
'--single-process', // Important for resource-constrained devices
'--disable-gpu'
],
headless: true
}
});
// QR code event
client.on('qr', (qr) => {
console.log('QR CODE RECEIVED. Check web interface at http://localhost:3000 to scan');
// Generate QR code image
qrcode.toDataURL(qr, (err, url) => {
if (err) {
console.error('Failed to generate QR code', err);
return;
}
qrCodeData = url;
connectionStatus = 'qr-ready';
console.log('QR code generated successfully, length:', url.length);
});
});
// Connection events
client.on('ready', () => {
console.log('WhatsApp client is ready!');
connectionStatus = 'ready';
qrCodeData = null; // Clear QR code after successful connection
});
client.on('authenticated', () => {
console.log('WhatsApp authenticated');
connectionStatus = 'authenticated';
});
client.on('auth_failure', (msg) => {
console.error('WhatsApp authentication failed:', msg);
connectionStatus = 'disconnected';
});
client.on('disconnected', () => {
console.log('WhatsApp client disconnected');
connectionStatus = 'disconnected';
// Reset authentication to force new QR code
qrCodeData = null;
});
// Message handling
client.on('message', async (msg) => {
console.log(`Received message: ${msg.body}`);
// Example: Respond to specific commands
if (msg.body === '!status') {
// Send back status information
msg.reply('Gateway status: ONLINE\nAll systems operational');
}
});
// Root route - Main web interface with enhanced debugging
app.get('/', (req, res) => {
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>Industrial Gateway WhatsApp Interface</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: #f5f5f5;
}
.container {
max-width: 800px;
margin: 0 auto;
background-color: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
h1 {
color: #075e54;
margin-top: 0;
}
.status {
padding: 10px;
margin-bottom: 20px;
border-radius: 5px;
}
.status.online {
background-color: #dcf8c6;
border-left: 5px solid #25d366;
}
.status.offline {
background-color: #f8dcdc;
border-left: 5px solid #d32525;
}
.status.connecting {
background-color: #fff3cd;
border-left: 5px solid #ffc107;
}
.qr-container {
text-align: center;
margin: 20px 0;
padding: 20px;
background-color: white;
border-radius: 5px;
border: 2px solid #e0e0e0;
}
.qr-code {
max-width: 300px;
margin: 0 auto;
}
.qr-code img {
width: 100%;
max-width: 300px;
height: auto;
border: 1px solid #ddd;
}
.qr-instructions {
margin-top: 15px;
font-size: 14px;
color: #555;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input[type="text"], textarea {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
box-sizing: border-box;
}
textarea {
height: 100px;
resize: vertical;
}
button {
background-color: #075e54;
color: white;
border: none;
padding: 10px 15px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
button:hover {
background-color: #128c7e;
}
button:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
.result {
margin-top: 20px;
padding: 10px;
border-radius: 4px;
display: none;
}
.success {
background-color: #dcf8c6;
border: 1px solid #25d366;
}
.error {
background-color: #f8dcdc;
border: 1px solid #d32525;
}
.help-text {
font-size: 12px;
color: #777;
margin-top: 5px;
}
.number-format {
background-color: #f0f0f0;
padding: 10px;
border-radius: 5px;
margin-top: 10px;
font-size: 14px;
}
.test-message {
margin-top: 20px;
padding: 15px;
background-color: #e9f5f8;
border-radius: 5px;
}
.debug-info {
background-color: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 4px;
padding: 10px;
margin: 10px 0;
font-family: monospace;
font-size: 12px;
max-height: 200px;
overflow-y: auto;
}
</style>
</head>
<body>
<div class="container">
<h1>Industrial Gateway WhatsApp Interface</h1>
<div id="statusContainer" class="status offline">
<p><strong>Status:</strong> <span id="connectionStatus">Disconnected</span></p>
</div>
<div id="qrContainer" class="qr-container" style="display: none;">
<h2>Connect WhatsApp</h2>
<p>Scan this QR code with your WhatsApp mobile app to link your account:</p>
<div class="qr-code">
<img id="qrCodeImage" src="" alt="QR Code" style="display: none;">
<div id="qrCodePlaceholder" style="display: block; padding: 50px; border: 2px dashed #ccc;">
Loading QR Code...
</div>
</div>
<div class="qr-instructions">
1. Open WhatsApp on your phone<br>
2. Tap Menu or Settings and select Linked Devices<br>
3. Tap on "Link a Device"<br>
4. Point your phone camera at this screen to scan the QR code<br>
</div>
<div class="debug-info">
<strong>Debug Info:</strong><br>
<span id="debugInfo">Waiting for QR code...</span>
</div>
</div>
<div id="messageContainer">
<h2>Send Test Message</h2>
<div class="test-message">
<p>Test the connection by sending a message to a specific number.</p>
<div class="form-group">
<label for="testNumber">Phone Number:</label>
<input type="text" id="testNumber" value="36317816481">
<p class="help-text">Enter full phone number with country code (e.g., 36317816481)</p>
</div>
<div class="form-group">
<label for="testMessage">Message:</label>
<input type="text" id="testMessage" value="Test message from Industrial Gateway">
</div>
<button id="sendTestBtn" onclick="sendTestMessage()">Send Test</button>
</div>
<h2>Send Alarm Message</h2>
<form id="alarmForm">
<div class="form-group">
<label for="recipients">Recipients (comma-separated phone numbers):</label>
<input type="text" id="recipients" placeholder="Example: 36317816481,36317816482" required>
<div class="number-format">
<strong>Phone Number Format:</strong><br>
- Use full number with country code<br>
- Do NOT use + symbol or spaces<br>
- Example: 36317816481 (Hungarian number)
</div>
</div>
<div class="form-group">
<label for="message">Alarm Message:</label>
<textarea id="message" placeholder="Enter your alarm message here..." required></textarea>
</div>
<button type="submit" id="sendAlarmBtn">Send Alarm</button>
</form>
<div id="result" class="result"></div>
</div>
</div>
<script>
// Global variables
let connectionStatus = 'disconnected';
let qrCodeFetched = false; // Track if we already have QR code
// Enhanced logging
function log(message) {
console.log(new Date().toISOString() + ': ' + message);
const debugInfo = document.getElementById('debugInfo');
if (debugInfo) {
debugInfo.innerHTML += '<br>' + new Date().toISOString() + ': ' + message;
// Keep only last 10 lines to prevent overflow
const lines = debugInfo.innerHTML.split('<br>');
if (lines.length > 12) {
debugInfo.innerHTML = lines.slice(-10).join('<br>');
}
}
}
// Update UI based on connection status
function updateUIStatus(status) {
const statusContainer = document.getElementById('statusContainer');
const statusText = document.getElementById('connectionStatus');
const qrContainer = document.getElementById('qrContainer');
const sendTestBtn = document.getElementById('sendTestBtn');
const sendAlarmBtn = document.getElementById('sendAlarmBtn');
connectionStatus = status;
log('Updating UI status to: ' + status);
// Update status container
statusContainer.className = 'status';
switch(status) {
case 'initializing':
statusText.textContent = 'Initializing...';
statusContainer.classList.add('connecting');
qrContainer.style.display = 'none';
sendTestBtn.disabled = true;
sendAlarmBtn.disabled = true;
break;
case 'qr-ready':
statusText.textContent = 'Ready to connect. Please scan QR code.';
statusContainer.classList.add('connecting');
qrContainer.style.display = 'block';
sendTestBtn.disabled = true;
sendAlarmBtn.disabled = true;
// Only fetch QR if we haven't already
if (!qrCodeFetched) {
fetchQRCode();
}
break;
case 'authenticated':
statusText.textContent = 'Authenticated. Connecting...';
statusContainer.classList.add('connecting');
qrContainer.style.display = 'none';
sendTestBtn.disabled = true;
sendAlarmBtn.disabled = true;
break;
case 'ready':
statusText.textContent = 'Connected to WhatsApp';
statusContainer.classList.add('online');
qrContainer.style.display = 'none';
sendTestBtn.disabled = false;
sendAlarmBtn.disabled = false;
qrCodeFetched = false; // Reset for next time
break;
case 'disconnected':
default:
statusText.textContent = 'Disconnected';
statusContainer.classList.add('offline');
qrContainer.style.display = 'none';
sendTestBtn.disabled = true;
sendAlarmBtn.disabled = true;
qrCodeFetched = false; // Reset when disconnected
break;
}
}
// Enhanced QR code display function
function displayQRCode(qrCodeData) {
const qrImage = document.getElementById('qrCodeImage');
const qrPlaceholder = document.getElementById('qrCodePlaceholder');
if (qrCodeData) {
log('Setting QR code image source, length: ' + qrCodeData.length);
qrImage.src = qrCodeData;
qrImage.style.display = 'block';
qrPlaceholder.style.display = 'none';
qrCodeFetched = true; // Mark as fetched
// Add error handler for image loading
qrImage.onerror = function() {
log('ERROR: Failed to load QR code image');
qrImage.style.display = 'none';
qrPlaceholder.style.display = 'block';
qrPlaceholder.innerHTML = 'ERROR: Failed to load QR code image<br>Data length: ' + qrCodeData.length;
qrCodeFetched = false; // Reset on error
};
qrImage.onload = function() {
log('SUCCESS: QR code image loaded successfully');
};
} else {
log('No QR code data provided');
qrImage.style.display = 'none';
qrPlaceholder.style.display = 'block';
qrPlaceholder.innerHTML = 'No QR code data available';
qrCodeFetched = false;
}
}
// Fetch QR code and status
function fetchQRCode() {
fetch('/qr-code')
.then(response => {
if (!response.ok) {
throw new Error('HTTP ' + response.status);
}
return response.json();
})
.then(data => {
if (data.qrCode) {
displayQRCode(data.qrCode);
} else {
document.getElementById('qrCodePlaceholder').innerHTML = 'Waiting for QR code generation...';
qrCodeFetched = false;
}
// Update status
updateUIStatus(data.status);
})
.catch(error => {
log('ERROR fetching QR code: ' + error.message);
document.getElementById('qrCodePlaceholder').innerHTML = 'Error loading QR code: ' + error.message;
qrCodeFetched = false;
});
}
// Check connection status
function checkStatus() {
fetch('/status')
.then(response => response.json())
.then(data => {
const previousStatus = connectionStatus;
// Only log status changes
if (previousStatus !== data.status) {
log('Status changed: ' + previousStatus + ' -> ' + data.status);
}
updateUIStatus(data.status);
// Reset QR fetch flag when status changes away from qr-ready
if (data.status !== 'qr-ready') {
qrCodeFetched = false;
}
// Continue checking for updates with longer interval for stable connections
const interval = (data.status === 'ready') ? 10000 : 3000;
setTimeout(checkStatus, interval);
})
.catch(error => {
if (connectionStatus !== 'disconnected') {
log('ERROR checking status: ' + error.message);
}
updateUIStatus('disconnected');
setTimeout(checkStatus, 5000);
});
}
// Send test message
function sendTestMessage() {
const number = document.getElementById('testNumber').value.trim();
const message = document.getElementById('testMessage').value.trim();
if (!number || !message) {
showResult('Please enter both phone number and message.', false);
return;
}
fetch('/send-message', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
number: number,
message: message
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
showResult('Test message sent successfully!', true);
} else {
showResult('Error: ' + data.message, false);
}
})
.catch(error => {
showResult('Error: ' + error.message, false);
});
}
// Handle alarm form submission
document.getElementById('alarmForm').addEventListener('submit', function(e) {
e.preventDefault();
if (connectionStatus !== 'ready') {
showResult('WhatsApp is not connected. Please wait for connection to be established.', false);
return;
}
const recipients = document.getElementById('recipients').value
.split(',')
.map(num => num.trim())
.filter(num => num);
const message = document.getElementById('message').value;
if (!recipients.length) {
showResult('Please enter at least one recipient phone number.', false);
return;
}
fetch('/send-alarm', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
recipients: recipients,
alarmMessage: message
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
showResult('Alarm message sent successfully!', true);
} else {
showResult('Error: ' + data.message, false);
}
})
.catch(error => {
showResult('Error: ' + error.message, false);
});
});
// Display result message
function showResult(message, isSuccess) {
const resultDiv = document.getElementById('result');
resultDiv.textContent = message;
resultDiv.className = 'result ' + (isSuccess ? 'success' : 'error');
resultDiv.style.display = 'block';
setTimeout(() => {
if (isSuccess) {
document.getElementById('message').value = '';
}
}, 2000);
}
// Initialize - start by checking status
log('Initializing WhatsApp interface...');
updateUIStatus('initializing');
checkStatus();
</script>
</body>
</html>
`);
});
// API endpoint to send a direct message
app.post('/send-message', async (req, res) => {
try {
const { number, message } = req.body;
if (!number || !message) {
return res.status(400).json({
success: false,
message: 'Number and message are required'
});
}
// Format number to ensure it has country code
const formattedNumber = number.includes('@c.us')
? number
: `${number.replace(/[^\d]/g, '')}@c.us`;
// Send the message
const response = await client.sendMessage(formattedNumber, message);
console.log(`Message sent to ${number}: ${message}`);
res.json({
success: true,
message: 'Message sent successfully',
messageId: response.id._serialized
});
} catch (error) {
console.error('Error sending message:', error);
res.status(500).json({
success: false,
message: 'Failed to send message',
error: error.message
});
}
});
// API endpoint to send an alarm to multiple recipients
app.post('/send-alarm', async (req, res) => {
try {
const { recipients, alarmMessage } = req.body;
if (!recipients || !Array.isArray(recipients) || !alarmMessage) {
return res.status(400).json({
success: false,
message: 'Recipients array and alarmMessage are required'
});
}
const results = [];
// Send the message to all recipients
for (const number of recipients) {
try {
const formattedNumber = number.includes('@c.us')
? number
: `${number.replace(/[^\d]/g, '')}@c.us`;
await client.sendMessage(formattedNumber, alarmMessage);
console.log(`Alarm sent to ${number}: ${alarmMessage}`);
results.push({ number, status: 'sent' });
} catch (error) {
console.error(`Failed to send to ${number}: ${error.message}`);
results.push({ number, status: 'failed', error: error.message });
}
}
res.json({
success: true,
message: 'Alarm sent',
results
});
} catch (error) {
console.error('Error sending alarm:', error);
res.status(500).json({
success: false,
message: 'Failed to send alarm',
error: error.message
});
}
});
// Endpoint to get the current QR code for web display
app.get('/qr-code', (req, res) => {
// Only log when QR status changes or when there's no QR data
if (!qrCodeData && connectionStatus === 'qr-ready') {
console.log(`QR code requested - Status: ${connectionStatus}, Has QR: ${!!qrCodeData}`);
}
res.json({
status: connectionStatus,
qrCode: qrCodeData
});
});
// Status endpoint
app.get('/status', (req, res) => {
res.json({
status: connectionStatus,
connected: connectionStatus === 'ready',
info: client.info || null
});
});
// Start Express server
app.listen(port, () => {
console.log(`WhatsApp gateway web interface running at http://localhost:${port}`);
});
// Initialize WhatsApp client
client.initialize();
EOF
#Create a service file for automatic startup
sudo tee /etc/systemd/system/whatsapp-gateway.service > /dev/null << EOF
[Unit]
Description=WhatsApp Client for Industrial Gateway
After=network.target
[Service]
ExecStart=/usr/bin/node /home/$(whoami)/whatsapp-client/whatsapp.js
WorkingDirectory=/home/$(whoami)/whatsapp-client
StandardOutput=inherit
StandardError=inherit
Restart=always
User=$(whoami)
Environment=PUPPETEER_SKIP_DOWNLOAD=true
Environment=NODE_OPTIONS=--max_old_space_size=512
[Install]
WantedBy=multi-user.target
EOF
chmod +x whatsapp.js
echo "Installation complete!"
echo ""
echo "To start the WhatsApp gateway manually, run: node ~/whatsapp-client/whatsapp.js"
echo "To enable automatic startup at boot, run: sudo systemctl enable whatsapp-gateway.service"
echo "To start the service now, run: sudo systemctl start whatsapp-gateway.service"
echo ""
echo "Once started, access the web interface at: http://$(hostname -I | awk '{print $1}'):3000"
echo ""
echo "For security, consider restricting access to this port if exposed beyond your local network."
#Make the script executable
chmod +x install-whatsapp.sh
#Run the installation script
./install-whatsapp.sh
#Start the service manually (for testing)
node ~/whatsapp-client/whatsapp.js
#Or enable and start as a system service (recommended for production)
sudo systemctl enable whatsapp-gateway.service
sudo systemctl start whatsapp-gateway.service
#Check service status
sudo systemctl status whatsapp-gateway.service
http://{your-gateway-ip}:3000
curl -X POST http://{your-gateway-ip}:3000/send-alarm \
-H 'Content-Type: application/json' \
-d '{
"recipients": ["36123456789", "36987654321"],
"alarmMessage": "ALERT: Temperature in Boiler #3 exceeds threshold (95°C)"
}'
This flexibility allows for seamless integration with existing industrial systems and SCADA platforms.