android
sails
+
phpstorm
+
+
css
sublime
istio
objc
meteor
+
+
elixir
+
+
+
suse
emacs
+
azure
+
+
macos
+
astro
nest
soap
json
actix
+
swift
mvn
+
java
+
+
//
svelte
+
+
xml
βˆ‰
+
+
Ξ»
influxdb
+
+
mysql
+
graphdb
+
xcode
deno
+
*
node
+
swc
fortran
+
surrealdb
intellij
+
vercel
+
+
sse
+
zig
?
!
linux
esbuild
+
+
+
[]
+
+
meteor
koa
react
+
meteor
pytest
?
+
Back to Blog
Industrial Automation Systems on Alpine Linux 🏭
Alpine Linux Industrial Automation

Industrial Automation Systems on Alpine Linux 🏭

Published Jun 13, 2025

Learn how to build industrial automation systems on Alpine Linux. We will set up SCADA, PLC communication, real-time monitoring, and control systems for factories! βš™οΈ

26 min read
0 views
Table of Contents

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 πŸ“Œ

  1. Redundancy - Implement failover systems
  2. Real-time constraints - Ensure deterministic response
  3. Security - Isolate industrial networks
  4. Data integrity - Validate all sensor readings
  5. 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! 🏭✨