Building industrial automation systems is like creating the brain and nervous system of a modern factory! π€ Alpine Linux provides a reliable, lightweight platform perfect for controlling machinery, monitoring production, and optimizing industrial processes. Letβs build a complete industrial automation system! π
What is Industrial Automation? π€
Industrial automation includes:
- SCADA systems - Supervisory Control and Data Acquisition
- PLC communication - Programmable Logic Controllers
- Real-time monitoring - Production line tracking
- Process control - Automated manufacturing
- Data collection - Performance analytics
Think of it as making factories smart and self-managing! ποΈ
Installing Core Components π¦
Set up the automation platform:
# Update package list
sudo apk update
# Install Python for automation scripts
sudo apk add python3 py3-pip python3-dev
sudo apk add gcc musl-dev linux-headers
# Install Node.js for real-time systems
sudo apk add nodejs npm
# Install database for data logging
sudo apk add postgresql postgresql-client
sudo apk add influxdb telegraf
# Install message broker
sudo apk add mosquitto mosquitto-clients
# Install development tools
sudo apk add git make cmake
Setting Up Modbus Communication π
Connect to industrial devices:
# Create project directory
mkdir -p ~/industrial-automation/{plc,scada,data,scripts}
cd ~/industrial-automation
# Install Modbus libraries
pip install pymodbus asyncio
pip install pyserial minimalmodbus
# Modbus server (simulating PLC)
cat > plc/modbus_server.py << 'EOF'
#!/usr/bin/env python3
from pymodbus.server.asynchronous import StartTcpServer
from pymodbus.device import ModbusDeviceIdentification
from pymodbus.datastore import ModbusSequentialDataBlock
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
import asyncio
import random
class IndustrialPLC:
def __init__(self):
# Initialize data blocks
self.coils = ModbusSequentialDataBlock(0, [0]*100)
self.discrete_inputs = ModbusSequentialDataBlock(0, [0]*100)
self.holding_registers = ModbusSequentialDataBlock(0, [0]*100)
self.input_registers = ModbusSequentialDataBlock(0, [0]*100)
# Create context
self.context = ModbusSlaveContext(
di=self.discrete_inputs,
co=self.coils,
hr=self.holding_registers,
ir=self.input_registers
)
self.server_context = ModbusServerContext(slaves=self.context, single=True)
async def update_values(self):
"""Simulate sensor readings and machine states"""
while True:
# Temperature sensor (register 0-1)
temp = int(20 + random.random() * 10) * 100 # 20-30Β°C
self.context.setValues(3, 0, [temp])
# Pressure sensor (register 2-3)
pressure = int(100 + random.random() * 50) * 100 # 100-150 PSI
self.context.setValues(3, 2, [pressure])
# Motor speed (register 4-5)
speed = int(1000 + random.random() * 500) # 1000-1500 RPM
self.context.setValues(3, 4, [speed])
# Machine status (coil 0: 1=running, 0=stopped)
status = 1 if random.random() > 0.1 else 0
self.context.setValues(1, 0, [status])
# Production counter (register 10)
counter = self.context.getValues(3, 10, 1)[0]
if status: # If machine is running
counter = (counter + 1) % 65536
self.context.setValues(3, 10, [counter])
await asyncio.sleep(1)
def start_server(self):
"""Start Modbus TCP server"""
print("Starting Industrial PLC Simulator on port 5020...")
# Start update task
loop = asyncio.get_event_loop()
loop.create_task(self.update_values())
# Start Modbus server
StartTcpServer(self.server_context, address=("0.0.0.0", 5020))
if __name__ == "__main__":
plc = IndustrialPLC()
plc.start_server()
EOF
chmod +x plc/modbus_server.py
SCADA System Implementation π
Create supervisory control system:
# SCADA backend
cat > scada/scada_server.js << 'EOF'
const express = require('express');
const ModbusRTU = require('modbus-serial');
const WebSocket = require('ws');
const { InfluxDB, Point } = require('@influxdata/influxdb-client');
const mqtt = require('mqtt');
const app = express();
const server = require('http').createServer(app);
const wss = new WebSocket.Server({ server });
// Modbus client for PLC communication
const plcClient = new ModbusRTU();
// InfluxDB client for time-series data
const influxClient = new InfluxDB({
url: 'http://localhost:8086',
token: 'your-token'
});
const writeApi = influxClient.getWriteApi('industrial', 'factory_data');
// MQTT client for device communication
const mqttClient = mqtt.connect('mqtt://localhost:1883');
// System configuration
const config = {
plc: {
host: 'localhost',
port: 5020,
unitId: 1
},
alarms: {
tempHigh: 28,
tempLow: 22,
pressureHigh: 140,
pressureLow: 110
}
};
// Connect to PLC
async function connectPLC() {
try {
await plcClient.connectTCP(config.plc.host, { port: config.plc.port });
plcClient.setID(config.plc.unitId);
console.log('Connected to PLC');
} catch (error) {
console.error('PLC connection error:', error);
setTimeout(connectPLC, 5000);
}
}
// Read PLC data
async function readPLCData() {
try {
// Read holding registers (temperature, pressure, speed)
const registers = await plcClient.readHoldingRegisters(0, 10);
// Read coils (machine status)
const coils = await plcClient.readCoils(0, 10);
const data = {
temperature: registers.data[0] / 100,
pressure: registers.data[2] / 100,
motorSpeed: registers.data[4],
productionCount: registers.data[10],
machineRunning: coils.data[0],
timestamp: new Date()
};
// Check alarms
checkAlarms(data);
// Store in database
storeData(data);
// Send to connected clients
broadcast(data);
// Publish to MQTT
mqttClient.publish('factory/plc/data', JSON.stringify(data));
return data;
} catch (error) {
console.error('Read error:', error);
return null;
}
}
// Check for alarm conditions
function checkAlarms(data) {
const alarms = [];
if (data.temperature > config.alarms.tempHigh) {
alarms.push({
type: 'HIGH_TEMPERATURE',
value: data.temperature,
threshold: config.alarms.tempHigh,
severity: 'warning'
});
}
if (data.pressure > config.alarms.pressureHigh) {
alarms.push({
type: 'HIGH_PRESSURE',
value: data.pressure,
threshold: config.alarms.pressureHigh,
severity: 'critical'
});
}
if (!data.machineRunning) {
alarms.push({
type: 'MACHINE_STOPPED',
severity: 'info'
});
}
if (alarms.length > 0) {
broadcast({ type: 'alarms', alarms });
// Log alarms
alarms.forEach(alarm => {
console.log(`ALARM: ${alarm.type} - ${alarm.severity}`);
});
}
}
// Store data in InfluxDB
function storeData(data) {
const point = new Point('factory_metrics')
.floatField('temperature', data.temperature)
.floatField('pressure', data.pressure)
.intField('motorSpeed', data.motorSpeed)
.intField('productionCount', data.productionCount)
.booleanField('machineRunning', data.machineRunning)
.timestamp(data.timestamp);
writeApi.writePoint(point);
}
// WebSocket broadcast
function broadcast(data) {
wss.clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify(data));
}
});
}
// API endpoints
app.use(express.json());
app.use(express.static('public'));
app.get('/api/status', async (req, res) => {
const data = await readPLCData();
res.json(data);
});
app.post('/api/control', async (req, res) => {
const { action, value } = req.body;
try {
switch (action) {
case 'start':
await plcClient.writeCoil(0, true);
res.json({ success: true, message: 'Machine started' });
break;
case 'stop':
await plcClient.writeCoil(0, false);
res.json({ success: true, message: 'Machine stopped' });
break;
case 'setSpeed':
await plcClient.writeRegister(4, value);
res.json({ success: true, message: `Speed set to ${value}` });
break;
default:
res.status(400).json({ error: 'Invalid action' });
}
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.get('/api/history', async (req, res) => {
const { start = '-1h', stop = 'now()' } = req.query;
const queryApi = influxClient.getQueryApi('industrial');
const query = `
from(bucket: "factory_data")
|> range(start: ${start}, stop: ${stop})
|> filter(fn: (r) => r._measurement == "factory_metrics")
`;
const results = [];
await queryApi.collectRows(query).then(rows => {
results.push(...rows);
});
res.json(results);
});
// WebSocket connection handling
wss.on('connection', (ws) => {
console.log('New WebSocket connection');
ws.on('message', async (message) => {
const cmd = JSON.parse(message);
if (cmd.type === 'control') {
// Handle control commands
try {
await plcClient.writeCoil(cmd.address, cmd.value);
ws.send(JSON.stringify({ success: true }));
} catch (error) {
ws.send(JSON.stringify({ error: error.message }));
}
}
});
});
// Start services
connectPLC();
setInterval(readPLCData, 1000); // Poll every second
server.listen(3000, () => {
console.log('SCADA server running on port 3000');
});
EOF
# Install dependencies
cd scada
npm init -y
npm install express ws modbus-serial @influxdata/influxdb-client mqtt
cd ..
Real-time HMI Dashboard π₯οΈ
Create operator interface:
# HMI Interface
cat > scada/public/index.html << 'EOF'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Industrial SCADA System</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: Arial, sans-serif;
background: #1a1a1a;
color: #fff;
}
.header {
background: #2c3e50;
padding: 1rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.container {
padding: 1rem;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1rem;
}
.widget {
background: #2c3e50;
border-radius: 8px;
padding: 1rem;
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
}
.metric {
text-align: center;
padding: 2rem;
}
.metric-value {
font-size: 3rem;
font-weight: bold;
margin: 0.5rem 0;
}
.metric-label {
color: #95a5a6;
text-transform: uppercase;
font-size: 0.9rem;
}
.status-indicator {
display: inline-block;
width: 20px;
height: 20px;
border-radius: 50%;
margin-right: 0.5rem;
}
.status-running { background: #27ae60; }
.status-stopped { background: #e74c3c; }
.control-panel {
display: flex;
gap: 1rem;
justify-content: center;
margin-top: 1rem;
}
.btn {
padding: 0.75rem 1.5rem;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
color: white;
transition: opacity 0.2s;
}
.btn:hover { opacity: 0.8; }
.btn-start { background: #27ae60; }
.btn-stop { background: #e74c3c; }
.btn-reset { background: #3498db; }
.alarm {
background: #e74c3c;
padding: 0.75rem;
margin: 0.5rem 0;
border-radius: 4px;
animation: blink 1s infinite;
}
@keyframes blink {
50% { opacity: 0.5; }
}
.chart-container {
position: relative;
height: 300px;
}
.production-stats {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
margin-top: 1rem;
}
.stat-item {
background: rgba(255,255,255,0.1);
padding: 1rem;
border-radius: 4px;
text-align: center;
}
</style>
</head>
<body>
<header class="header">
<h1>Industrial SCADA System</h1>
<div>
<span id="connection-status">
<span class="status-indicator status-stopped"></span>
Disconnected
</span>
<span style="margin-left: 2rem;">Time: <span id="current-time"></span></span>
</div>
</header>
<div class="container">
<!-- Machine Status -->
<div class="widget">
<h2>Machine Status</h2>
<div class="metric">
<div id="machine-status">
<span class="status-indicator status-stopped"></span>
<span>STOPPED</span>
</div>
</div>
<div class="control-panel">
<button class="btn btn-start" onclick="controlMachine('start')">START</button>
<button class="btn btn-stop" onclick="controlMachine('stop')">STOP</button>
<button class="btn btn-reset" onclick="controlMachine('reset')">RESET</button>
</div>
</div>
<!-- Temperature -->
<div class="widget">
<h2>Temperature</h2>
<div class="metric">
<div class="metric-value" id="temperature">--</div>
<div class="metric-label">Β°C</div>
</div>
<div class="chart-container">
<canvas id="temp-chart"></canvas>
</div>
</div>
<!-- Pressure -->
<div class="widget">
<h2>Pressure</h2>
<div class="metric">
<div class="metric-value" id="pressure">--</div>
<div class="metric-label">PSI</div>
</div>
<div class="chart-container">
<canvas id="pressure-chart"></canvas>
</div>
</div>
<!-- Motor Speed -->
<div class="widget">
<h2>Motor Speed</h2>
<div class="metric">
<div class="metric-value" id="motor-speed">--</div>
<div class="metric-label">RPM</div>
</div>
<input type="range" min="0" max="2000" value="1000"
oninput="setMotorSpeed(this.value)">
</div>
<!-- Production Stats -->
<div class="widget">
<h2>Production Statistics</h2>
<div class="production-stats">
<div class="stat-item">
<h3>Units Produced</h3>
<div class="metric-value" id="production-count">0</div>
</div>
<div class="stat-item">
<h3>Efficiency</h3>
<div class="metric-value" id="efficiency">0%</div>
</div>
<div class="stat-item">
<h3>Uptime</h3>
<div class="metric-value" id="uptime">0%</div>
</div>
<div class="stat-item">
<h3>OEE</h3>
<div class="metric-value" id="oee">0%</div>
</div>
</div>
</div>
<!-- Alarms -->
<div class="widget">
<h2>Active Alarms</h2>
<div id="alarms-container">
<p style="color: #95a5a6;">No active alarms</p>
</div>
</div>
</div>
<script>
// WebSocket connection
let ws;
let charts = {};
let dataHistory = {
temperature: [],
pressure: []
};
// Initialize charts
function initCharts() {
// Temperature chart
const tempCtx = document.getElementById('temp-chart').getContext('2d');
charts.temperature = new Chart(tempCtx, {
type: 'line',
data: {
labels: [],
datasets: [{
label: 'Temperature',
data: [],
borderColor: '#e74c3c',
tension: 0.1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: false,
min: 15,
max: 35
}
}
}
});
// Pressure chart
const pressureCtx = document.getElementById('pressure-chart').getContext('2d');
charts.pressure = new Chart(pressureCtx, {
type: 'line',
data: {
labels: [],
datasets: [{
label: 'Pressure',
data: [],
borderColor: '#3498db',
tension: 0.1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: false,
min: 80,
max: 160
}
}
}
});
}
// Connect WebSocket
function connect() {
ws = new WebSocket('ws://localhost:3000');
ws.onopen = () => {
console.log('Connected to SCADA server');
updateConnectionStatus(true);
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'alarms') {
updateAlarms(data.alarms);
} else {
updateDisplay(data);
}
};
ws.onclose = () => {
console.log('Disconnected from SCADA server');
updateConnectionStatus(false);
setTimeout(connect, 5000);
};
}
// Update display with new data
function updateDisplay(data) {
// Update values
document.getElementById('temperature').textContent = data.temperature.toFixed(1);
document.getElementById('pressure').textContent = data.pressure.toFixed(1);
document.getElementById('motor-speed').textContent = data.motorSpeed;
document.getElementById('production-count').textContent = data.productionCount;
// Update machine status
const statusEl = document.getElementById('machine-status');
if (data.machineRunning) {
statusEl.innerHTML = '<span class="status-indicator status-running"></span>RUNNING';
} else {
statusEl.innerHTML = '<span class="status-indicator status-stopped"></span>STOPPED';
}
// Update charts
const time = new Date(data.timestamp).toLocaleTimeString();
// Temperature chart
if (charts.temperature.data.labels.length > 20) {
charts.temperature.data.labels.shift();
charts.temperature.data.datasets[0].data.shift();
}
charts.temperature.data.labels.push(time);
charts.temperature.data.datasets[0].data.push(data.temperature);
charts.temperature.update();
// Pressure chart
if (charts.pressure.data.labels.length > 20) {
charts.pressure.data.labels.shift();
charts.pressure.data.datasets[0].data.shift();
}
charts.pressure.data.labels.push(time);
charts.pressure.data.datasets[0].data.push(data.pressure);
charts.pressure.update();
// Calculate efficiency (simplified)
const efficiency = data.machineRunning ? 85 + Math.random() * 10 : 0;
document.getElementById('efficiency').textContent = efficiency.toFixed(1) + '%';
}
// Update alarms
function updateAlarms(alarms) {
const container = document.getElementById('alarms-container');
if (alarms.length === 0) {
container.innerHTML = '<p style="color: #95a5a6;">No active alarms</p>';
} else {
container.innerHTML = alarms.map(alarm =>
`<div class="alarm">
${alarm.type}: ${alarm.value ? alarm.value.toFixed(1) : ''}
${alarm.threshold ? `(Threshold: ${alarm.threshold})` : ''}
</div>`
).join('');
}
}
// Control functions
async function controlMachine(action) {
const response = await fetch('/api/control', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action })
});
const result = await response.json();
console.log(result.message);
}
async function setMotorSpeed(speed) {
const response = await fetch('/api/control', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'setSpeed', value: parseInt(speed) })
});
}
// Update connection status
function updateConnectionStatus(connected) {
const statusEl = document.getElementById('connection-status');
if (connected) {
statusEl.innerHTML = '<span class="status-indicator status-running"></span>Connected';
} else {
statusEl.innerHTML = '<span class="status-indicator status-stopped"></span>Disconnected';
}
}
// Update time
function updateTime() {
document.getElementById('current-time').textContent =
new Date().toLocaleString();
}
// Initialize
initCharts();
connect();
setInterval(updateTime, 1000);
</script>
</body>
</html>
EOF
OPC UA Server π
Implement industrial standard protocol:
# Install OPC UA libraries
pip install asyncua
# OPC UA server
cat > plc/opcua_server.py << 'EOF'
#!/usr/bin/env python3
import asyncio
from asyncua import Server, ua
from asyncua.common.methods import uamethod
import random
class IndustrialOPCUAServer:
def __init__(self):
self.server = Server()
self.temperature = 25.0
self.pressure = 120.0
self.motor_speed = 1200
self.production_count = 0
self.machine_running = False
async def init(self):
await self.server.init()
self.server.set_endpoint("opc.tcp://0.0.0.0:4840/industrial/server")
self.server.set_server_name("Industrial Automation OPC UA Server")
# Set security policy
self.server.set_security_policy([ua.SecurityPolicyType.NoSecurity])
# Register namespace
uri = "http://industrial.automation.opcua"
idx = await self.server.register_namespace(uri)
# Create objects
objects = self.server.get_objects_node()
# Production Line object
production_line = await objects.add_object(idx, "ProductionLine")
# Add variables
self.temp_var = await production_line.add_variable(
idx, "Temperature", self.temperature, ua.VariantType.Float
)
await self.temp_var.set_writable()
self.pressure_var = await production_line.add_variable(
idx, "Pressure", self.pressure, ua.VariantType.Float
)
await self.pressure_var.set_writable()
self.speed_var = await production_line.add_variable(
idx, "MotorSpeed", self.motor_speed, ua.VariantType.Int32
)
await self.speed_var.set_writable()
self.count_var = await production_line.add_variable(
idx, "ProductionCount", self.production_count, ua.VariantType.Int32
)
self.status_var = await production_line.add_variable(
idx, "MachineStatus", self.machine_running, ua.VariantType.Boolean
)
await self.status_var.set_writable()
# Add methods
await production_line.add_method(
idx, "StartMachine", self.start_machine, [], []
)
await production_line.add_method(
idx, "StopMachine", self.stop_machine, [], []
)
await production_line.add_method(
idx, "ResetCounter", self.reset_counter, [], []
)
# Add alarms
self.temp_alarm = await production_line.add_object(idx, "TemperatureAlarm")
self.alarm_active = await self.temp_alarm.add_variable(
idx, "Active", False, ua.VariantType.Boolean
)
@uamethod
async def start_machine(self, parent):
self.machine_running = True
await self.status_var.write_value(True)
print("Machine started via OPC UA")
@uamethod
async def stop_machine(self, parent):
self.machine_running = False
await self.status_var.write_value(False)
print("Machine stopped via OPC UA")
@uamethod
async def reset_counter(self, parent):
self.production_count = 0
await self.count_var.write_value(0)
print("Counter reset via OPC UA")
async def update_values(self):
"""Simulate process values"""
while True:
if self.machine_running:
# Update temperature
self.temperature += random.uniform(-0.5, 0.5)
self.temperature = max(20, min(35, self.temperature))
await self.temp_var.write_value(self.temperature)
# Update pressure
self.pressure += random.uniform(-2, 2)
self.pressure = max(100, min(150, self.pressure))
await self.pressure_var.write_value(self.pressure)
# Update production count
self.production_count += 1
await self.count_var.write_value(self.production_count)
# Check alarms
if self.temperature > 30:
await self.alarm_active.write_value(True)
else:
await self.alarm_active.write_value(False)
await asyncio.sleep(1)
async def run(self):
await self.init()
async with self.server:
print("OPC UA Server started at opc.tcp://0.0.0.0:4840")
# Start simulation
await asyncio.create_task(self.update_values())
if __name__ == "__main__":
server = IndustrialOPCUAServer()
asyncio.run(server.run())
EOF
chmod +x plc/opcua_server.py
Data Analytics and Reporting π
Process production data:
# Analytics script
cat > scripts/production_analytics.py << 'EOF'
#!/usr/bin/env python3
import pandas as pd
import numpy as np
from influxdb_client import InfluxDBClient
import matplotlib.pyplot as plt
from datetime import datetime, timedelta
import seaborn as sns
class ProductionAnalytics:
def __init__(self):
self.client = InfluxDBClient(
url="http://localhost:8086",
token="your-token",
org="industrial"
)
self.query_api = self.client.query_api()
def get_production_data(self, hours=24):
"""Fetch production data from InfluxDB"""
query = f'''
from(bucket: "factory_data")
|> range(start: -{hours}h)
|> filter(fn: (r) => r._measurement == "factory_metrics")
|> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
'''
df = self.query_api.query_data_frame(query)
return df
def calculate_oee(self, df):
"""Calculate Overall Equipment Effectiveness"""
# Availability
total_time = len(df)
running_time = df['machineRunning'].sum()
availability = running_time / total_time if total_time > 0 else 0
# Performance (simplified)
ideal_speed = 1500 # RPM
actual_speed = df[df['machineRunning']]['motorSpeed'].mean()
performance = actual_speed / ideal_speed if ideal_speed > 0 else 0
# Quality (simulated - assuming 95% good products)
quality = 0.95
# OEE
oee = availability * performance * quality
return {
'availability': availability * 100,
'performance': performance * 100,
'quality': quality * 100,
'oee': oee * 100
}
def generate_report(self):
"""Generate production report"""
# Get data
df = self.get_production_data()
# Calculate metrics
oee_metrics = self.calculate_oee(df)
# Create visualizations
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
fig.suptitle('Production Analytics Report', fontsize=16)
# Temperature trend
axes[0, 0].plot(df.index, df['temperature'])
axes[0, 0].set_title('Temperature Trend')
axes[0, 0].set_ylabel('Temperature (Β°C)')
axes[0, 0].axhline(y=30, color='r', linestyle='--', label='High Limit')
axes[0, 0].legend()
# Pressure trend
axes[0, 1].plot(df.index, df['pressure'])
axes[0, 1].set_title('Pressure Trend')
axes[0, 1].set_ylabel('Pressure (PSI)')
# Production count
axes[1, 0].plot(df.index, df['productionCount'])
axes[1, 0].set_title('Production Output')
axes[1, 0].set_ylabel('Units Produced')
# OEE breakdown
oee_data = list(oee_metrics.values())
oee_labels = ['Availability', 'Performance', 'Quality', 'OEE']
colors = ['#3498db', '#2ecc71', '#f39c12', '#e74c3c']
axes[1, 1].bar(oee_labels, oee_data, color=colors)
axes[1, 1].set_title('OEE Breakdown')
axes[1, 1].set_ylabel('Percentage (%)')
axes[1, 1].set_ylim(0, 100)
plt.tight_layout()
# Save report
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
plt.savefig(f'production_report_{timestamp}.png')
# Print summary
print(f"\n=== Production Report ===")
print(f"Generated: {datetime.now()}")
print(f"\nOEE Metrics:")
for metric, value in oee_metrics.items():
print(f" {metric.capitalize()}: {value:.2f}%")
print(f"\nProduction Summary:")
print(f" Total Units: {df['productionCount'].iloc[-1]}")
print(f" Avg Temperature: {df['temperature'].mean():.2f}Β°C")
print(f" Avg Pressure: {df['pressure'].mean():.2f} PSI")
print(f" Machine Uptime: {(df['machineRunning'].sum()/len(df)*100):.2f}%")
return oee_metrics
if __name__ == "__main__":
analytics = ProductionAnalytics()
analytics.generate_report()
EOF
chmod +x scripts/production_analytics.py
Alarm Management System π¨
Handle critical alerts:
# Alarm manager
cat > scripts/alarm_manager.sh << 'EOF'
#!/bin/sh
# Industrial Alarm Management
ALARM_LOG="/var/log/industrial/alarms.log"
EMAIL_RECIPIENTS="[email protected]"
# Check system health
check_plc_connection() {
nc -z localhost 5020
return $?
}
# Monitor critical parameters
monitor_parameters() {
# Get current values from API
STATUS=$(curl -s http://localhost:3000/api/status)
# Parse values
TEMP=$(echo "$STATUS" | jq -r '.temperature')
PRESSURE=$(echo "$STATUS" | jq -r '.pressure')
RUNNING=$(echo "$STATUS" | jq -r '.machineRunning')
# Check thresholds
if [ $(echo "$TEMP > 30" | bc) -eq 1 ]; then
send_alarm "HIGH_TEMPERATURE" "Temperature is $TEMPΒ°C (threshold: 30Β°C)" "critical"
fi
if [ $(echo "$PRESSURE > 140" | bc) -eq 1 ]; then
send_alarm "HIGH_PRESSURE" "Pressure is $PRESSURE PSI (threshold: 140 PSI)" "critical"
fi
if [ "$RUNNING" = "false" ]; then
send_alarm "MACHINE_STOPPED" "Production line is stopped" "warning"
fi
}
# Send alarm notification
send_alarm() {
TYPE=$1
MESSAGE=$2
SEVERITY=$3
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
# Log alarm
echo "[$TIMESTAMP] $SEVERITY: $TYPE - $MESSAGE" >> "$ALARM_LOG"
# Send notification (email, SMS, etc.)
if [ "$SEVERITY" = "critical" ]; then
echo "$MESSAGE" | mail -s "CRITICAL ALARM: $TYPE" "$EMAIL_RECIPIENTS"
# Also send to MQTT for real-time alerts
mosquitto_pub -t "factory/alarms/$TYPE" -m "$MESSAGE"
fi
}
# Main monitoring loop
while true; do
if check_plc_connection; then
monitor_parameters
else
send_alarm "PLC_OFFLINE" "Cannot connect to PLC" "critical"
fi
sleep 5
done
EOF
chmod +x scripts/alarm_manager.sh
System Startup Configuration π
Set up automatic startup:
# Create systemd-style init scripts
cat > /etc/init.d/industrial-automation << 'EOF'
#!/sbin/openrc-run
name="Industrial Automation System"
description="Start all industrial automation services"
depend() {
need net postgresql
after firewall
}
start() {
ebegin "Starting Industrial Automation System"
# Start Modbus server
start-stop-daemon --start --background \
--make-pidfile --pidfile /run/modbus-server.pid \
--exec /usr/bin/python3 -- /home/industrial/plc/modbus_server.py
# Start OPC UA server
start-stop-daemon --start --background \
--make-pidfile --pidfile /run/opcua-server.pid \
--exec /usr/bin/python3 -- /home/industrial/plc/opcua_server.py
# Start SCADA server
start-stop-daemon --start --background \
--make-pidfile --pidfile /run/scada-server.pid \
--chdir /home/industrial/scada \
--exec /usr/bin/node -- scada_server.js
# Start alarm manager
start-stop-daemon --start --background \
--make-pidfile --pidfile /run/alarm-manager.pid \
--exec /home/industrial/scripts/alarm_manager.sh
eend $?
}
stop() {
ebegin "Stopping Industrial Automation System"
start-stop-daemon --stop --pidfile /run/modbus-server.pid
start-stop-daemon --stop --pidfile /run/opcua-server.pid
start-stop-daemon --stop --pidfile /run/scada-server.pid
start-stop-daemon --stop --pidfile /run/alarm-manager.pid
eend $?
}
EOF
chmod +x /etc/init.d/industrial-automation
rc-update add industrial-automation default
Best Practices π
- Redundancy - Implement failover systems
- Real-time constraints - Ensure deterministic response
- Security - Isolate industrial networks
- Data integrity - Validate all sensor readings
- Maintenance windows - Plan system updates carefully
Troubleshooting π§
PLC Communication Issues
# Test Modbus connection
python3 -c "
from pymodbus.client.sync import ModbusTcpClient
client = ModbusTcpClient('localhost', port=5020)
print('Connected:', client.connect())
result = client.read_holding_registers(0, 10)
print('Registers:', result.registers)
"
# Check OPC UA server
pip install opcua-client
opcua-client-gui
Performance Optimization
# Monitor system resources
htop
# Check network latency
ping -c 100 plc_address | tail -1
# Optimize database queries
psql -c "EXPLAIN ANALYZE SELECT * FROM factory_metrics WHERE time > now() - interval '1 hour';"
Quick Commands π
# Start all services
rc-service industrial-automation start
# View real-time data
mosquitto_sub -t "factory/#" -v
# Check system status
curl http://localhost:3000/api/status | jq
# Generate report
python3 scripts/production_analytics.py
# View alarms
tail -f /var/log/industrial/alarms.log
Conclusion π―
Youβve built a complete industrial automation system on Alpine Linux! From PLC communication to SCADA visualization, real-time monitoring, and data analytics, you now have the tools to automate and optimize industrial processes. Remember to prioritize safety and reliability in production environments. Happy automating! πβ¨