#include <WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <LiquidCrystal_I2C.h>
#include <EEPROM.h>
#include <time.h>
// WiFi and MQTT Configuration
const char* ssid = "Wokwi-GUEST";
const char* password = "";
const char* mqtt_server = "broker.hivemq.com";
const char* mqtt_client_id = "ProductionLine_001";
// NTP Configuration for real-time timestamps
const char* ntpServer = "pool.ntp.org";
const long gmtOffset_sec = 0;
const int daylightOffset_sec = 3600;
// Pin Definitions - Industrial Production Line Configuration
// Status LEDs - Production Line States
#define LED_RED 2 // System Fault/Emergency Stop
#define LED_GREEN_1 4 // Station 1: Material Loading
#define LED_GREEN_2 5 // Station 2: Processing/Assembly
#define LED_GREEN_3 18 // Station 3: Quality Control
#define LED_GREEN_4 16 // Station 4: Packaging/Output
// I2C LCD Display (20x4) - Production Dashboard
#define LCD_SDA 21 // I2C Data
#define LCD_SCL 22 // I2C Clock
// Sensors - Production Monitoring
#define DISTANCE_TRIG_PIN 19 // Ultrasonic for conveyor position detection
#define DISTANCE_ECHO_PIN 17 // Ultrasonic echo
#define PRODUCTION_SENSOR_PIN 23 // Production count sensor (simulated with button)
#define QUALITY_SENSOR_PIN 15 // Quality check sensor (slide switch - pass/fail)
#define TEMPERATURE_SENSOR_PIN 34 // Production temperature monitoring (potentiometer)
// Actuators - Production Control
#define CONVEYOR_RELAY_PIN 25 // Conveyor belt motor control
#define ASSEMBLY_RELAY_PIN 26 // Assembly station actuator
#define ALARM_BUZZER_PIN 27 // Production alarms
// Manual Controls - Operator Interface
#define BUTTON_START 32 // Production start button (green)
#define BUTTON_EMERGENCY 33 // Emergency stop button (red)
// Production Line Constants
#define CONVEYOR_LENGTH_CM 100 // Conveyor length in cm
#define MIN_PART_DISTANCE 10 // Minimum distance for part detection
#define MAX_PART_DISTANCE 90 // Maximum conveyor position
#define PRODUCTION_TARGET_HOURLY 100 // Target production per hour
#define EEPROM_SIZE 512 // EEPROM size for data persistence
// Enhanced Features
#define MAX_SHIFT_REPORTS 3 // Store last 3 shift reports
#define MAX_ALARM_HISTORY 10 // Store last 10 alarms
#define PREDICTIVE_MAINTENANCE_HOURS 100 // Hours before maintenance reminder
// Global Variables
WiFiClient espClient;
PubSubClient client(espClient);
LiquidCrystal_I2C lcd(0x27, 20, 4);
// Production Line State Machine
enum ProductionState {
LINE_IDLE,
MATERIAL_LOADING,
PROCESSING_ASSEMBLY,
QUALITY_CONTROL,
PACKAGING_OUTPUT,
MAINTENANCE_MODE,
FAULT_STATE,
EMERGENCY_STOP,
STARTUP_SEQUENCE,
SHUTDOWN_SEQUENCE
};
ProductionState currentState = LINE_IDLE;
bool productionEnabled = false;
bool emergencyStop = false;
bool maintenanceRequired = false;
// Enhanced Production Analytics Data
struct ProductionMetrics {
float conveyorPosition; // cm - current part position
int productionCount; // units produced
bool qualityPass; // current quality check result
int operatingTemperature; // °C - production temperature
float cycleTime; // seconds per unit
float efficiency; // % efficiency
int defectCount; // defective units
float oeeScore; // Overall Equipment Effectiveness
float vibrationLevel; // Simulated vibration monitoring
float powerConsumption; // Simulated power usage in kW
int runningHours; // Total running hours
};
ProductionMetrics metrics;
// Enhanced Process Analytics
struct ProcessAnalytics {
unsigned long totalRuntime; // Total production time
unsigned long totalDowntime; // Total downtime
int totalProduced; // Lifetime production count
int totalDefects; // Lifetime defect count
float avgCycleTime; // Average cycle time
float availability; // % availability
float performance; // % performance
float quality; // % quality rate
float targetEfficiency; // Target efficiency %
int shiftProduction[3]; // Last 3 shifts production
float shiftOEE[3]; // Last 3 shifts OEE
};
ProcessAnalytics analytics;
// Alarm System
struct AlarmRecord {
unsigned long timestamp;
String alarmType;
String description;
bool acknowledged;
};
AlarmRecord alarmHistory[MAX_ALARM_HISTORY];
int alarmCount = 0;
bool activeAlarm = false;
String currentAlarmType = "";
// Shift Management
struct ShiftData {
unsigned long startTime;
unsigned long endTime;
int production;
int defects;
float oee;
float efficiency;
String shiftType; // "DAY", "NIGHT", "WEEKEND"
};
ShiftData currentShift;
int shiftNumber = 1;
// Recipe/Production Program Management
struct ProductionRecipe {
String recipeName;
int targetUnits;
float cycleTimeTarget;
int temperatureMin;
int temperatureMax;
bool qualityCheckEnabled;
};
ProductionRecipe currentRecipe = {"Standard Production", 100, 15.0, 30, 70, true};
// LCD Display management
String lcdLine[4] = {"", "", "", ""};
int currentDisplayMode = 0; // 0=Main, 1=Analytics, 2=Alarms, 3=Recipe
unsigned long lastDisplaySwitch = 0;
// Timing Variables
unsigned long lastSensorRead = 0;
unsigned long lastMqttPublish = 0;
unsigned long lastConnectionCheck = 0;
unsigned long lastButtonCheck = 0;
unsigned long lastLcdUpdate = 0;
unsigned long lastAnalyticsUpdate = 0;
unsigned long lastAlarmCheck = 0;
unsigned long lastMaintenanceCheck = 0;
// Production timing
volatile unsigned long productionPulseCount = 0;
unsigned long lastProductionTime = 0;
unsigned long cycleStartTime = 0;
unsigned long stateStartTime = 0;
unsigned long shiftStartTime = 0;
// Connection status
bool wifiConnected = false;
bool mqttConnected = false;
bool ntpSynced = false;
// Enhanced Features
bool predictiveMaintenanceEnabled = true;
bool energyMonitoringEnabled = true;
bool shiftTrackingEnabled = true;
bool alarmSystemEnabled = true;
// Function declarations
void initializePins();
void initializeLCD();
void initializeMetrics();
void initializeEEPROM();
void initializeNTP();
void setupWiFi();
void checkConnections();
void reconnectMQTT();
void mqttCallback(char* topic, byte* payload, unsigned int length);
void collectProductionData();
void collectEnhancedSensorData();
float readConveyorPosition();
void checkOperatorControls();
void executeProductionStateMachine();
void handleIdleState();
void handleStartupSequence(unsigned long stateTime);
void handleShutdownSequence(unsigned long stateTime);
bool handleMaterialLoadingState(unsigned long stateTime);
bool handleProcessingAssemblyState(unsigned long stateTime);
bool handleQualityControlState(unsigned long stateTime);
bool handlePackagingOutputState(unsigned long stateTime);
void handleMaintenanceMode();
void handleFaultState();
void handleEmergencyStopState();
void performQualityChecks();
void calculateProcessAnalytics();
void updateProductionLEDs();
void publishProductionAnalytics();
void publishEnhancedAnalytics();
void stopAllProduction();
void performSystemDiagnostics();
void updateLcdLine(int line, String content);
void updateProductionDashboard();
void cycleDashboardMode();
void displayMainDashboard();
void displayAnalyticsDashboard();
void displayAlarmsDashboard();
void displayRecipeDashboard();
const char* stateToString(ProductionState state);
void IRAM_ATTR productionCounter();
// Enhanced feature functions
void checkPredictiveMaintenance();
void logAlarm(String type, String description);
void acknowledgeAlarm();
void updateShiftData();
void saveDataToEEPROM();
void loadDataFromEEPROM();
void generateShiftReport();
void calculateEnergyConsumption();
void performAdvancedDiagnostics();
void handleRecipeChange(String recipeName);
String getCurrentTimestamp();
void publishAlarmNotification(String type, String description);
void performVibrationAnalysis();
void optimizeProductionParameters();
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("Enhanced Industrial Production Line Control System Starting...");
// Initialize hardware and enhanced features
initializePins();
initializeLCD();
initializeEEPROM();
initializeMetrics();
// Network connectivity and time sync
setupWiFi();
initializeNTP();
client.setServer(mqtt_server, 1883);
client.setCallback(mqttCallback);
// Load persistent data
loadDataFromEEPROM();
// Initialize shift tracking
shiftStartTime = millis();
currentShift.startTime = shiftStartTime;
currentShift.shiftType = "DAY";
// Enhanced system diagnostics
performAdvancedDiagnostics();
Serial.println("Enhanced production line system initialized - Ready for Industry 4.0 operation");
logAlarm("SYSTEM", "Production line system started successfully");
}
void loop() {
unsigned long currentTime = millis();
// Network management
if (currentTime - lastConnectionCheck > 5000) {
checkConnections();
lastConnectionCheck = currentTime;
}
// Enhanced sensor data collection
if (currentTime - lastSensorRead > 300) { // Even faster sampling
collectProductionData();
collectEnhancedSensorData();
lastSensorRead = currentTime;
}
// Operator interface
if (currentTime - lastButtonCheck > 50) {
checkOperatorControls();
lastButtonCheck = currentTime;
}
// Enhanced analytics publishing
if (currentTime - lastMqttPublish > 2000) { // More frequent updates
publishProductionAnalytics();
publishEnhancedAnalytics();
lastMqttPublish = currentTime;
}
// Multi-mode display updates
if (currentTime - lastLcdUpdate > 800) {
updateProductionDashboard();
lastLcdUpdate = currentTime;
}
// Process analytics calculation
if (currentTime - lastAnalyticsUpdate > 8000) {
calculateProcessAnalytics();
updateShiftData();
lastAnalyticsUpdate = currentTime;
}
// Enhanced monitoring systems
if (currentTime - lastAlarmCheck > 5000) {
performQualityChecks();
performVibrationAnalysis();
lastAlarmCheck = currentTime;
}
if (currentTime - lastMaintenanceCheck > 30000) {
checkPredictiveMaintenance();
saveDataToEEPROM(); // Periodic data backup
lastMaintenanceCheck = currentTime;
}
// Production state machine
executeProductionStateMachine();
// Enhanced status indicators
updateProductionLEDs();
// Energy optimization
if (energyMonitoringEnabled) {
calculateEnergyConsumption();
}
client.loop();
delay(15); // Even faster loop for enhanced responsiveness
}
void initializeEEPROM() {
EEPROM.begin(EEPROM_SIZE);
Serial.println("EEPROM initialized for data persistence");
}
void initializeNTP() {
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
Serial.println("NTP time synchronization initialized");
// Wait for time sync
struct tm timeinfo;
int attempts = 0;
while (!getLocalTime(&timeinfo) && attempts < 10) {
delay(1000);
attempts++;
}
if (attempts < 10) {
ntpSynced = true;
Serial.println("NTP time synchronized successfully");
} else {
Serial.println("NTP sync failed - using internal timer");
}
}
void collectEnhancedSensorData() {
// Simulate vibration monitoring (using temperature sensor variations)
static float lastTemp = 0;
float currentTemp = metrics.operatingTemperature;
metrics.vibrationLevel = abs(currentTemp - lastTemp) * 2.0; // Simple vibration simulation
lastTemp = currentTemp;
// Simulate power consumption based on active systems
metrics.powerConsumption = 2.5; // Base consumption
if (digitalRead(CONVEYOR_RELAY_PIN)) metrics.powerConsumption += 3.2;
if (digitalRead(ASSEMBLY_RELAY_PIN)) metrics.powerConsumption += 2.8;
if (currentState == PROCESSING_ASSEMBLY) metrics.powerConsumption += 1.5;
// Add some realistic variation
metrics.powerConsumption += (random(-50, 51) / 100.0);
// Update running hours
static unsigned long lastHourUpdate = 0;
if (millis() - lastHourUpdate > 3600000) { // Every hour
metrics.runningHours++;
lastHourUpdate = millis();
}
}
void cycleDashboardMode() {
currentDisplayMode = (currentDisplayMode + 1) % 4;
lastDisplaySwitch = millis();
}
void displayMainDashboard() {
// Line 1: Production Status and Count
String line1 = stateToString(currentState);
if (line1.length() > 8) line1 = line1.substring(0, 8);
line1 += " P:" + String(metrics.productionCount);
line1 += " S:" + String(shiftNumber);
updateLcdLine(0, line1);
// Line 2: OEE Score and Efficiency
String line2 = "OEE:" + String(metrics.oeeScore, 1) + "% Ef:" + String(metrics.efficiency, 0) + "%";
updateLcdLine(1, line2);
// Line 3: Temperature and Power
String line3 = "T:" + String(metrics.operatingTemperature) + "C P:" + String(metrics.powerConsumption, 1) + "kW";
updateLcdLine(2, line3);
// Line 4: Status and Time
String line4;
if (emergencyStop) {
line4 = "*** EMERGENCY STOP **";
} else if (activeAlarm) {
line4 = "ALARM: " + currentAlarmType;
} else if (maintenanceRequired) {
line4 = "MAINTENANCE REQUIRED";
} else {
line4 = getCurrentTimestamp();
if (line4.length() > 20) line4 = line4.substring(0, 20);
}
updateLcdLine(3, line4);
}
void displayAnalyticsDashboard() {
updateLcdLine(0, "=== ANALYTICS === ");
String line2 = "Avail:" + String(analytics.availability, 1) + "% Q:" + String(analytics.quality, 1) + "%";
updateLcdLine(1, line2);
String line3 = "Cycle:" + String(analytics.avgCycleTime, 1) + "s Vib:" + String(metrics.vibrationLevel, 1);
updateLcdLine(2, line3);
String line4 = "Hrs:" + String(metrics.runningHours) + " Def:" + String(metrics.defectCount);
updateLcdLine(3, line4);
}
void displayAlarmsDashboard() {
updateLcdLine(0, "=== ALARMS === ");
if (alarmCount == 0) {
updateLcdLine(1, "No active alarms ");
updateLcdLine(2, "System normal ");
updateLcdLine(3, "All systems OK ");
} else {
// Show latest alarm
int latestIndex = (alarmCount - 1) % MAX_ALARM_HISTORY;
updateLcdLine(1, "Latest: " + alarmHistory[latestIndex].alarmType);
updateLcdLine(2, alarmHistory[latestIndex].description);
updateLcdLine(3, "Total: " + String(alarmCount));
}
}
void displayRecipeDashboard() {
updateLcdLine(0, "=== RECIPE === ");
updateLcdLine(1, currentRecipe.recipeName);
updateLcdLine(2, "Target:" + String(currentRecipe.targetUnits) + " units");
updateLcdLine(3, "Cycle:" + String(currentRecipe.cycleTimeTarget) + "s");
}
void updateProductionDashboard() {
// Auto-cycle display modes every 10 seconds, or manually via MQTT
if (millis() - lastDisplaySwitch > 10000) {
cycleDashboardMode();
}
switch (currentDisplayMode) {
case 0:
displayMainDashboard();
break;
case 1:
displayAnalyticsDashboard();
break;
case 2:
displayAlarmsDashboard();
break;
case 3:
displayRecipeDashboard();
break;
}
}
void handleStartupSequence(unsigned long stateTime) {
// Enhanced startup with system checks
updateLcdLine(0, "STARTING UP... ");
updateLcdLine(1, "Checking systems... ");
if (stateTime < 2000) {
updateLcdLine(2, "Sensors... ");
} else if (stateTime < 4000) {
updateLcdLine(2, "Actuators... ");
// Test actuators briefly
digitalWrite(CONVEYOR_RELAY_PIN, HIGH);
delay(100);
digitalWrite(CONVEYOR_RELAY_PIN, LOW);
} else if (stateTime < 6000) {
updateLcdLine(2, "Network... ");
} else {
updateLcdLine(2, "Ready! ");
updateLcdLine(3, "Press START ");
currentState = LINE_IDLE;
logAlarm("SYSTEM", "Startup sequence completed");
}
}
void handleShutdownSequence(unsigned long stateTime) {
updateLcdLine(0, "SHUTTING DOWN... ");
updateLcdLine(1, "Saving data... ");
if (stateTime < 2000) {
saveDataToEEPROM();
generateShiftReport();
} else if (stateTime < 4000) {
updateLcdLine(2, "Stopping systems... ");
stopAllProduction();
} else {
updateLcdLine(2, "Shutdown complete ");
currentState = LINE_IDLE;
logAlarm("SYSTEM", "Shutdown sequence completed");
}
}
void checkPredictiveMaintenance() {
if (!predictiveMaintenanceEnabled) return;
// Check various maintenance indicators
bool maintenanceNeeded = false;
String reason = "";
if (metrics.runningHours >= PREDICTIVE_MAINTENANCE_HOURS) {
maintenanceNeeded = true;
reason = "Scheduled maintenance due";
}
if (metrics.vibrationLevel > 15.0) {
maintenanceNeeded = true;
reason = "High vibration detected";
}
if (analytics.quality < 90.0 && analytics.totalProduced > 50) {
maintenanceNeeded = true;
reason = "Quality degradation detected";
}
if (maintenanceNeeded && !maintenanceRequired) {
maintenanceRequired = true;
logAlarm("MAINTENANCE", reason);
if (mqttConnected) {
client.publish("production/maintenance/required", "true");
client.publish("production/maintenance/reason", reason.c_str());
}
}
}
void logAlarm(String type, String description) {
if (!alarmSystemEnabled) return;
int index = alarmCount % MAX_ALARM_HISTORY;
alarmHistory[index].timestamp = millis();
alarmHistory[index].alarmType = type;
alarmHistory[index].description = description;
alarmHistory[index].acknowledged = false;
alarmCount++;
activeAlarm = true;
currentAlarmType = type;
Serial.println("ALARM: " + type + " - " + description);
publishAlarmNotification(type, description);
}
void publishAlarmNotification(String type, String description) {
if (!mqttConnected) return;
StaticJsonDocument<256> doc;
doc["timestamp"] = getCurrentTimestamp();
doc["type"] = type;
doc["description"] = description;
doc["severity"] = (type == "EMERGENCY" || type == "FAULT") ? "HIGH" : "MEDIUM";
char buffer[256];
serializeJson(doc, buffer);
client.publish("production/alarms/new", buffer);
}
void performVibrationAnalysis() {
// Simple vibration trend analysis
static float vibrationTrend[5] = {0};
static int trendIndex = 0;
vibrationTrend[trendIndex] = metrics.vibrationLevel;
trendIndex = (trendIndex + 1) % 5;
// Calculate trend
float avgVibration = 0;
for (int i = 0; i < 5; i++) {
avgVibration += vibrationTrend[i];
}
avgVibration /= 5;
if (avgVibration > 12.0) {
logAlarm("VIBRATION", "Elevated vibration levels detected");
}
}
void calculateEnergyConsumption() {
// Energy efficiency calculations
static float totalEnergy = 0;
static unsigned long lastEnergyUpdate = 0;
unsigned long deltaTime = millis() - lastEnergyUpdate;
if (deltaTime > 60000) { // Update every minute
totalEnergy += (metrics.powerConsumption * deltaTime) / 3600000.0; // kWh
lastEnergyUpdate = millis();
if (mqttConnected) {
client.publish("production/energy/total_kwh", String(totalEnergy).c_str());
client.publish("production/energy/current_kw", String(metrics.powerConsumption).c_str());
}
}
}
String getCurrentTimestamp() {
if (!ntpSynced) {
return "Time: " + String(millis() / 1000) + "s";
}
struct tm timeinfo;
if (!getLocalTime(&timeinfo)) {
return "Time sync error";
}
char buffer[20];
strftime(buffer, sizeof(buffer), "%H:%M:%S", &timeinfo);
return String(buffer);
}
void updateShiftData() {
currentShift.production = metrics.productionCount;
currentShift.defects = metrics.defectCount;
currentShift.oee = metrics.oeeScore;
currentShift.efficiency = metrics.efficiency;
// Check for shift change (every 8 hours for demo, every 30 seconds)
if (millis() - shiftStartTime > 30000) {
generateShiftReport();
// Start new shift
shiftNumber++;
shiftStartTime = millis();
currentShift.startTime = shiftStartTime;
// Reset shift metrics
metrics.productionCount = 0;
metrics.defectCount = 0;
// Determine shift type
if (shiftNumber % 3 == 1) currentShift.shiftType = "DAY";
else if (shiftNumber % 3 == 2) currentShift.shiftType = "EVENING";
else currentShift.shiftType = "NIGHT";
}
}
void generateShiftReport() {
if (!shiftTrackingEnabled) return;
currentShift.endTime = millis();
if (mqttConnected) {
StaticJsonDocument<512> report;
report["shift_number"] = shiftNumber;
report["shift_type"] = currentShift.shiftType;
report["start_time"] = currentShift.startTime;
report["end_time"] = currentShift.endTime;
report["production"] = currentShift.production;
report["defects"] = currentShift.defects;
report["oee"] = currentShift.oee;
report["efficiency"] = currentShift.efficiency;
report["duration_hours"] = (currentShift.endTime - currentShift.startTime) / 3600000.0;
char buffer[512];
serializeJson(report, buffer);
client.publish("production/shift/report", buffer);
}
Serial.println("Shift " + String(shiftNumber) + " Report Generated");
}
void saveDataToEEPROM() {
// Save critical production data to EEPROM
int address = 0;
EEPROM.put(address, analytics.totalProduced);
address += sizeof(int);
EEPROM.put(address, analytics.totalDefects);
address += sizeof(int);
EEPROM.put(address, metrics.runningHours);
address += sizeof(int);
EEPROM.put(address, shiftNumber);
EEPROM.commit();
}
void loadDataFromEEPROM() {
// Load persistent data from EEPROM
int address = 0;
EEPROM.get(address, analytics.totalProduced);
address += sizeof(int);
EEPROM.get(address, analytics.totalDefects);
address += sizeof(int);
EEPROM.get(address, metrics.runningHours);
address += sizeof(int);
EEPROM.get(address, shiftNumber);
// Validate loaded data
if (analytics.totalProduced < 0 || analytics.totalProduced > 1000000) {
analytics.totalProduced = 0;
}
}
void publishEnhancedAnalytics() {
if (!mqttConnected) return;
// Publish enhanced sensor data
client.publish("production/sensors/vibration", String(metrics.vibrationLevel).c_str());
client.publish("production/sensors/power_consumption", String(metrics.powerConsumption).c_str());
client.publish("production/sensors/running_hours", String(metrics.runningHours).c_str());
// Publish maintenance status
client.publish("production/maintenance/required", maintenanceRequired ? "true" : "false");
client.publish("production/maintenance/hours_until", String(PREDICTIVE_MAINTENANCE_HOURS - metrics.runningHours).c_str());
// Publish shift information
client.publish("production/shift/number", String(shiftNumber).c_str());
client.publish("production/shift/type", currentShift.shiftType.c_str());
client.publish("production/shift/production", String(currentShift.production).c_str());
// Publish alarm status
client.publish("production/alarms/active", activeAlarm ? "true" : "false");
client.publish("production/alarms/count", String(alarmCount).c_str());
// Publish recipe information
client.publish("production/recipe/name", currentRecipe.recipeName.c_str());
client.publish("production/recipe/target", String(currentRecipe.targetUnits).c_str());
// Publish display mode
client.publish("production/display/mode", String(currentDisplayMode).c_str());
}
void performAdvancedDiagnostics() {
Serial.println("Performing advanced production line diagnostics...");
updateLcdLine(0, "Advanced Diagnostics");
updateLcdLine(1, "Testing Systems... ");
updateLcdLine(2, "");
updateLcdLine(3, "");
// Test all LEDs in sequence
updateLcdLine(2, "LED Test... ");
int leds[] = {LED_RED, LED_GREEN_1, LED_GREEN_2, LED_GREEN_3, LED_GREEN_4};
for (int i = 0; i < 5; i++) {
digitalWrite(leds[i], HIGH);
delay(200);
digitalWrite(leds[i], LOW);
}
// Test actuators
updateLcdLine(2, "Actuator Test... ");
digitalWrite(CONVEYOR_RELAY_PIN, HIGH);
delay(300);
digitalWrite(CONVEYOR_RELAY_PIN, LOW);
digitalWrite(ASSEMBLY_RELAY_PIN, HIGH);
delay(300);
digitalWrite(ASSEMBLY_RELAY_PIN, LOW);
// Test alarm system
updateLcdLine(2, "Alarm Test... ");
tone(ALARM_BUZZER_PIN, 1000, 200);
delay(300);
// Test sensors
updateLcdLine(2, "Sensor Test... ");
collectProductionData();
collectEnhancedSensorData();
updateLcdLine(1, "Diagnostics Complete");
updateLcdLine(2, "All Systems Ready ");
updateLcdLine(3, "Industry 4.0 Ready ");
delay(2000);
currentState = STARTUP_SEQUENCE;
stateStartTime = millis();
Serial.println("Advanced diagnostics complete - Enhanced system operational");
}
// Enhanced MQTT callback with more commands
void mqttCallback(char* topic, byte* payload, unsigned int length) {
String message;
for (int i = 0; i < length; i++) {
message += (char)payload[i];
}
Serial.print("Enhanced command [");
Serial.print(topic);
Serial.print("]: ");
Serial.println(message);
// Enhanced production commands
if (String(topic) == "production/control/start") {
if (!emergencyStop) {
productionEnabled = true;
currentState = MATERIAL_LOADING;
cycleStartTime = millis();
logAlarm("SYSTEM", "Production started via MQTT");
}
}
else if (String(topic) == "production/control/stop") {
productionEnabled = false;
currentState = SHUTDOWN_SEQUENCE;
stateStartTime = millis();
logAlarm("SYSTEM", "Production stopped via MQTT");
}
else if (String(topic) == "production/control/emergency") {
emergencyStop = true;
currentState = EMERGENCY_STOP;
stopAllProduction();
logAlarm("EMERGENCY", "Emergency stop activated via MQTT");
}
else if (String(topic) == "production/control/maintenance") {
currentState = MAINTENANCE_MODE;
maintenanceRequired = false;
stopAllProduction();
logAlarm("MAINTENANCE", "Maintenance mode activated via MQTT");
}
// Recipe management
else if (String(topic) == "production/recipe/load") {
handleRecipeChange(message);
}
else if (String(topic) == "production/recipe/target") {
int newTarget = message.toInt();
if (newTarget > 0) {
currentRecipe.targetUnits = newTarget;
logAlarm("RECIPE", "Production target updated to " + String(newTarget));
}
}
// Display control
else if (String(topic) == "production/display/mode") {
int mode = message.toInt();
if (mode >= 0 && mode <= 3) {
currentDisplayMode = mode;
lastDisplaySwitch = millis();
}
}
// Alarm management
else if (String(topic) == "production/alarms/acknowledge") {
acknowledgeAlarm();
}
else if (String(topic) == "production/alarms/clear") {
activeAlarm = false;
currentAlarmType = "";
logAlarm("SYSTEM", "Alarms cleared via MQTT");
}
// Maintenance commands
else if (String(topic) == "production/maintenance/reset") {
maintenanceRequired = false;
metrics.runningHours = 0;
logAlarm("MAINTENANCE", "Maintenance reset - hours counter cleared");
}
// Enhanced feature toggles
else if (String(topic) == "production/features/predictive_maintenance") {
predictiveMaintenanceEnabled = (message == "true");
logAlarm("SYSTEM", "Predictive maintenance " + (predictiveMaintenanceEnabled ? "enabled" : "disabled"));
}
else if (String(topic) == "production/features/energy_monitoring") {
energyMonitoringEnabled = (message == "true");
logAlarm("SYSTEM", "Energy monitoring " + (energyMonitoringEnabled ? "enabled" : "disabled"));
}
else if (String(topic) == "production/features/shift_tracking") {
shiftTrackingEnabled = (message == "true");
logAlarm("SYSTEM", "Shift tracking " + (shiftTrackingEnabled ? "enabled" : "disabled"));
}
// Individual output control for IoT dashboard
else if (String(topic) == "production/output/conveyor") {
if (currentState == LINE_IDLE || currentState == MAINTENANCE_MODE) {
digitalWrite(CONVEYOR_RELAY_PIN, message == "ON" ? HIGH : LOW);
logAlarm("MANUAL", "Conveyor control: " + message);
}
}
else if (String(topic) == "production/output/assembly") {
if (currentState == LINE_IDLE || currentState == MAINTENANCE_MODE) {
digitalWrite(ASSEMBLY_RELAY_PIN, message == "ON" ? HIGH : LOW);
logAlarm("MANUAL", "Assembly control: " + message);
}
}
else if (String(topic) == "production/output/alarm") {
if (message == "ON") {
tone(ALARM_BUZZER_PIN, 1000, 2000);
} else {
noTone(ALARM_BUZZER_PIN);
}
logAlarm("MANUAL", "Alarm control: " + message);
}
// Individual LED control
else if (String(topic) == "production/led/station1") {
if (currentState == LINE_IDLE || currentState == MAINTENANCE_MODE) {
digitalWrite(LED_GREEN_1, message == "ON" ? HIGH : LOW);
}
}
else if (String(topic) == "production/led/station2") {
if (currentState == LINE_IDLE || currentState == MAINTENANCE_MODE) {
digitalWrite(LED_GREEN_2, message == "ON" ? HIGH : LOW);
}
}
else if (String(topic) == "production/led/station3") {
if (currentState == LINE_IDLE || currentState == MAINTENANCE_MODE) {
digitalWrite(LED_GREEN_3, message == "ON" ? HIGH : LOW);
}
}
else if (String(topic) == "production/led/station4") {
if (currentState == LINE_IDLE || currentState == MAINTENANCE_MODE) {
digitalWrite(LED_GREEN_4, message == "ON" ? HIGH : LOW);
}
}
else if (String(topic) == "production/led/fault") {
if (currentState == LINE_IDLE || currentState == MAINTENANCE_MODE) {
digitalWrite(LED_RED, message == "ON" ? HIGH : LOW);
}
}
// Production optimization
else if (String(topic) == "production/optimize/parameters") {
optimizeProductionParameters();
}
// System commands
else if (String(topic) == "production/system/restart") {
logAlarm("SYSTEM", "System restart requested via MQTT");
ESP.restart();
}
else if (String(topic) == "production/system/factory_reset") {
// Clear EEPROM
for (int i = 0; i < EEPROM_SIZE; i++) {
EEPROM.write(i, 0);
}
EEPROM.commit();
logAlarm("SYSTEM", "Factory reset completed");
ESP.restart();
}
}
void handleRecipeChange(String recipeName) {
if (recipeName == "HighVolume") {
currentRecipe.recipeName = "High Volume Production";
currentRecipe.targetUnits = 200;
currentRecipe.cycleTimeTarget = 12.0;
currentRecipe.temperatureMin = 35;
currentRecipe.temperatureMax = 65;
currentRecipe.qualityCheckEnabled = true;
}
else if (recipeName == "HighQuality") {
currentRecipe.recipeName = "High Quality Production";
currentRecipe.targetUnits = 80;
currentRecipe.cycleTimeTarget = 20.0;
currentRecipe.temperatureMin = 25;
currentRecipe.temperatureMax = 55;
currentRecipe.qualityCheckEnabled = true;
}
else if (recipeName == "Standard") {
currentRecipe.recipeName = "Standard Production";
currentRecipe.targetUnits = 100;
currentRecipe.cycleTimeTarget = 15.0;
currentRecipe.temperatureMin = 30;
currentRecipe.temperatureMax = 70;
currentRecipe.qualityCheckEnabled = true;
}
else {
return; // Unknown recipe
}
logAlarm("RECIPE", "Recipe changed to: " + currentRecipe.recipeName);
// Publish recipe change confirmation
if (mqttConnected) {
client.publish("production/recipe/current", currentRecipe.recipeName.c_str());
}
}
void acknowledgeAlarm() {
if (alarmCount > 0) {
int latestIndex = (alarmCount - 1) % MAX_ALARM_HISTORY;
alarmHistory[latestIndex].acknowledged = true;
// Check if all alarms are acknowledged
bool allAcknowledged = true;
int checkCount = min(alarmCount, MAX_ALARM_HISTORY);
for (int i = 0; i < checkCount; i++) {
int index = (alarmCount - 1 - i) % MAX_ALARM_HISTORY;
if (!alarmHistory[index].acknowledged) {
allAcknowledged = false;
break;
}
}
if (allAcknowledged) {
activeAlarm = false;
currentAlarmType = "";
}
logAlarm("SYSTEM", "Alarm acknowledged");
}
}
void optimizeProductionParameters() {
// AI-like production optimization based on current metrics
logAlarm("SYSTEM", "Running production optimization algorithm");
// Optimize based on current performance
if (metrics.efficiency < 70) {
// Suggest conveyor speed adjustment
if (mqttConnected) {
client.publish("production/optimization/suggestion", "Increase conveyor speed by 5%");
}
}
if (metrics.defectCount > metrics.productionCount * 0.05) {
// Suggest quality parameter adjustment
if (mqttConnected) {
client.publish("production/optimization/suggestion", "Adjust temperature range for better quality");
}
}
if (metrics.oeeScore < 60) {
// Comprehensive optimization needed
if (mqttConnected) {
client.publish("production/optimization/suggestion", "Comprehensive system optimization recommended");
}
}
// Simulate parameter optimization
analytics.targetEfficiency = min(100.0f, metrics.efficiency + 2.0f);
logAlarm("OPTIMIZATION", "Production parameters optimized");
}
// Enhanced reconnectMQTT with more subscriptions
void reconnectMQTT() {
if (!wifiConnected) return;
int attempts = 0;
while (!client.connected() && attempts < 3) {
Serial.print("Connecting to Enhanced Industrial MQTT...");
if (client.connect(mqtt_client_id)) {
Serial.println("Enhanced Industrial MQTT connected");
mqttConnected = true;
// Subscribe to enhanced production control topics
client.subscribe("production/control/start");
client.subscribe("production/control/stop");
client.subscribe("production/control/emergency");
client.subscribe("production/control/maintenance");
// Recipe management
client.subscribe("production/recipe/load");
client.subscribe("production/recipe/target");
// Display control
client.subscribe("production/display/mode");
// Alarm management
client.subscribe("production/alarms/acknowledge");
client.subscribe("production/alarms/clear");
// Maintenance commands
client.subscribe("production/maintenance/reset");
// Feature toggles
client.subscribe("production/features/predictive_maintenance");
client.subscribe("production/features/energy_monitoring");
client.subscribe("production/features/shift_tracking");
// Individual output control
client.subscribe("production/output/conveyor");
client.subscribe("production/output/assembly");
client.subscribe("production/output/alarm");
// LED control
client.subscribe("production/led/station1");
client.subscribe("production/led/station2");
client.subscribe("production/led/station3");
client.subscribe("production/led/station4");
client.subscribe("production/led/fault");
// System optimization
client.subscribe("production/optimize/parameters");
// System commands
client.subscribe("production/system/restart");
client.subscribe("production/system/factory_reset");
// Publish enhanced system online status
client.publish("production/status/online", "true", true);
client.publish("production/version", "Enhanced Industry 4.0 v2.0", true);
} else {
Serial.print("Enhanced MQTT connection failed, rc=");
Serial.println(client.state());
attempts++;
}
}
}
// Enhanced executeProductionStateMachine with new states
void executeProductionStateMachine() {
static ProductionState lastState = LINE_IDLE;
static unsigned long stateTimer = 0;
if (currentState != lastState) {
stateStartTime = millis();
stateTimer = 0;
lastState = currentState;
Serial.print("Enhanced production state: ");
Serial.println(stateToString(currentState));
// Publish state change
if (mqttConnected) {
client.publish("production/state/current", stateToString(currentState));
client.publish("production/state/timestamp", String(millis()).c_str());
}
}
stateTimer = millis() - stateStartTime;
switch (currentState) {
case LINE_IDLE:
handleIdleState();
break;
case STARTUP_SEQUENCE:
handleStartupSequence(stateTimer);
break;
case SHUTDOWN_SEQUENCE:
handleShutdownSequence(stateTimer);
break;
case MATERIAL_LOADING:
if (handleMaterialLoadingState(stateTimer)) {
currentState = PROCESSING_ASSEMBLY;
}
break;
case PROCESSING_ASSEMBLY:
if (handleProcessingAssemblyState(stateTimer)) {
currentState = QUALITY_CONTROL;
}
break;
case QUALITY_CONTROL:
if (handleQualityControlState(stateTimer)) {
currentState = PACKAGING_OUTPUT;
}
break;
case PACKAGING_OUTPUT:
if (handlePackagingOutputState(stateTimer)) {
// Check if we should continue production
if (currentShift.production >= currentRecipe.targetUnits) {
currentState = SHUTDOWN_SEQUENCE;
stateStartTime = millis();
logAlarm("PRODUCTION", "Target production reached - initiating shutdown");
} else {
currentState = MATERIAL_LOADING; // Continue production cycle
}
}
break;
case MAINTENANCE_MODE:
handleMaintenanceMode();
break;
case FAULT_STATE:
handleFaultState();
break;
case EMERGENCY_STOP:
handleEmergencyStopState();
break;
}
}
// Enhanced initializePins, initializeLCD, and initializeMetrics functions remain the same
// but let's add the missing implementations:
void initializePins() {
// Production status LEDs
pinMode(LED_RED, OUTPUT);
pinMode(LED_GREEN_1, OUTPUT);
pinMode(LED_GREEN_2, OUTPUT);
pinMode(LED_GREEN_3, OUTPUT);
pinMode(LED_GREEN_4, OUTPUT);
// Production sensors
pinMode(DISTANCE_TRIG_PIN, OUTPUT);
pinMode(DISTANCE_ECHO_PIN, INPUT);
pinMode(QUALITY_SENSOR_PIN, INPUT_PULLUP);
pinMode(TEMPERATURE_SENSOR_PIN, INPUT);
// Production actuators
pinMode(CONVEYOR_RELAY_PIN, OUTPUT);
pinMode(ASSEMBLY_RELAY_PIN, OUTPUT);
pinMode(ALARM_BUZZER_PIN, OUTPUT);
// Operator controls
pinMode(BUTTON_START, INPUT_PULLUP);
pinMode(BUTTON_EMERGENCY, INPUT_PULLUP);
// Production counter interrupt
pinMode(PRODUCTION_SENSOR_PIN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(PRODUCTION_SENSOR_PIN), productionCounter, FALLING);
// Initialize to safe state
digitalWrite(CONVEYOR_RELAY_PIN, LOW);
digitalWrite(ASSEMBLY_RELAY_PIN, LOW);
digitalWrite(ALARM_BUZZER_PIN, LOW);
// Turn off status LEDs
digitalWrite(LED_RED, LOW);
digitalWrite(LED_GREEN_1, LOW);
digitalWrite(LED_GREEN_2, LOW);
digitalWrite(LED_GREEN_3, LOW);
digitalWrite(LED_GREEN_4, LOW);
Serial.println("Enhanced industrial I/O pins initialized");
}
void initializeLCD() {
Serial.println("Initializing Enhanced Production Dashboard...");
Wire.begin(LCD_SDA, LCD_SCL);
// Try common I2C addresses for industrial displays
byte lcdAddresses[] = {0x27, 0x3F, 0x38, 0x20};
bool lcdInitialized = false;
for (int i = 0; i < 4 && !lcdInitialized; i++) {
lcd = LiquidCrystal_I2C(lcdAddresses[i], 20, 4);
lcd.init();
Wire.beginTransmission(lcdAddresses[i]);
if (Wire.endTransmission() == 0) {
lcd.backlight();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Enhanced Prod Line ");
lcd.setCursor(0, 1);
lcd.print("Industry 4.0 Ready ");
lcd.setCursor(0, 2);
lcd.print("Dashboard Loading...");
// Custom characters for enhanced display
byte degreeChar[8] = {0b00110, 0b01001, 0b01001, 0b00110, 0b00000, 0b00000, 0b00000, 0b00000};
byte arrowChar[8] = {0b00100, 0b00110, 0b00111, 0b11111, 0b11111, 0b00111, 0b00110, 0b00100};
byte warningChar[8] = {0b00100, 0b01110, 0b11111, 0b11111, 0b11111, 0b01110, 0b00100, 0b00000};
byte checkChar[8] = {0b00000, 0b00001, 0b00011, 0b10110, 0b11100, 0b01000, 0b00000, 0b00000};
lcd.createChar(0, degreeChar);
lcd.createChar(1, arrowChar);
lcd.createChar(2, warningChar);
lcd.createChar(3, checkChar);
lcdInitialized = true;
delay(2000);
}
}
if (!lcdInitialized) {
Serial.println("Enhanced production dashboard initialization failed!");
}
}
void initializeMetrics() {
metrics.conveyorPosition = 0.0;
metrics.productionCount = 0;
metrics.qualityPass = true;
metrics.operatingTemperature = 25;
metrics.cycleTime = 0.0;
metrics.efficiency = 0.0;
metrics.defectCount = 0;
metrics.oeeScore = 0.0;
metrics.vibrationLevel = 0.0;
metrics.powerConsumption = 2.5;
metrics.runningHours = 0;
analytics.totalRuntime = 0;
analytics.totalDowntime = 0;
analytics.totalProduced = 0;
analytics.totalDefects = 0;
analytics.avgCycleTime = 0.0;
analytics.availability = 100.0;
analytics.performance = 0.0;
analytics.quality = 100.0;
analytics.targetEfficiency = 85.0;
// Initialize shift data
for (int i = 0; i < 3; i++) {
analytics.shiftProduction[i] = 0;
analytics.shiftOEE[i] = 0.0;
}
// Initialize alarm history
for (int i = 0; i < MAX_ALARM_HISTORY; i++) {
alarmHistory[i].timestamp = 0;
alarmHistory[i].alarmType = "";
alarmHistory[i].description = "";
alarmHistory[i].acknowledged = true;
}
Serial.println("Enhanced production metrics initialized");
}
// Rest of the functions remain similar but enhanced
void setupWiFi() {
Serial.print("Connecting to Enhanced Industrial Network...");
updateLcdLine(0, "Enhanced Prod Line ");
updateLcdLine(1, "Connecting Network...");
updateLcdLine(2, "Industry 4.0 System ");
updateLcdLine(3, "");
WiFi.begin(ssid, password);
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 20) {
digitalWrite(LED_RED, HIGH);
delay(250);
digitalWrite(LED_RED, LOW);
delay(250);
Serial.print(".");
attempts++;
}
if (WiFi.status() == WL_CONNECTED) {
wifiConnected = true;
Serial.println("\nEnhanced industrial network connected!");
Serial.print("System IP: ");
Serial.println(WiFi.localIP());
updateLcdLine(1, "Network Connected ");
updateLcdLine(2, "IP: " + WiFi.localIP().toString());
updateLcdLine(3, "Industry 4.0 Online ");
logAlarm("SYSTEM", "Network connected successfully");
} else {
wifiConnected = false;
updateLcdLine(1, "Network Failed! ");
logAlarm("ERROR", "Network connection failed");
}
delay(2000);
}
void checkConnections() {
if (WiFi.status() != WL_CONNECTED) {
if (wifiConnected) {
logAlarm("NETWORK", "WiFi connection lost");
}
wifiConnected = false;
WiFi.reconnect();
} else {
if (!wifiConnected) {
logAlarm("NETWORK", "WiFi connection restored");
}
wifiConnected = true;
}
if (!client.connected()) {
if (mqttConnected) {
logAlarm("NETWORK", "MQTT connection lost");
}
mqttConnected = false;
reconnectMQTT();
} else {
if (!mqttConnected) {
logAlarm("NETWORK", "MQTT connection restored");
}
mqttConnected = true;
}
}
// Continue with the enhanced implementations of remaining functions...
void collectProductionData() {
// Read conveyor position (part detection)
metrics.conveyorPosition = readConveyorPosition();
// Read production temperature with enhanced filtering
static float tempBuffer[5] = {0};
static int tempIndex = 0;
int rawTemp = analogRead(TEMPERATURE_SENSOR_PIN);
float newTemp = map(rawTemp, 0, 4095, 20, 80); // 20-80°C range
tempBuffer[tempIndex] = newTemp;
tempIndex = (tempIndex + 1) % 5;
// Average temperature for stability
float tempSum = 0;
for (int i = 0; i < 5; i++) {
tempSum += tempBuffer[i];
}
metrics.operatingTemperature = tempSum / 5.0;
// Read quality sensor
metrics.qualityPass = digitalRead(QUALITY_SENSOR_PIN);
// Calculate current cycle time with recipe consideration
if (metrics.productionCount > 0) {
unsigned long totalTime = millis() - cycleStartTime;
metrics.cycleTime = (totalTime / 1000.0) / metrics.productionCount;
// Compare against recipe target
if (metrics.cycleTime > currentRecipe.cycleTimeTarget * 1.2) {
logAlarm("PERFORMANCE", "Cycle time exceeding target");
}
}
// Calculate enhanced efficiency
float actualRate = 0;
if (millis() > cycleStartTime) {
actualRate = (metrics.productionCount * 3600000.0) / (millis() - cycleStartTime + 1);
}
float targetRate = (float)currentRecipe.targetUnits;
metrics.efficiency = (actualRate / targetRate) * 100.0;
if (metrics.efficiency > 100.0) metrics.efficiency = 100.0;
}
float readConveyorPosition() {
// Enhanced ultrasonic sensor reading with error handling
float validReadings[5];
int validCount = 0;
for (int attempt = 0; attempt < 5 && validCount < 3; attempt++) {
digitalWrite(DISTANCE_TRIG_PIN, LOW);
delayMicroseconds(2);
digitalWrite(DISTANCE_TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(DISTANCE_TRIG_PIN, LOW);
long duration = pulseIn(DISTANCE_ECHO_PIN, HIGH, 30000);
if (duration > 0) {
float distance = (duration * 0.034) / 2.0;
if (distance >= MIN_PART_DISTANCE && distance <= MAX_PART_DISTANCE) {
validReadings[validCount] = distance;
validCount++;
}
}
delay(30);
}
if (validCount > 0) {
// Return median value for better accuracy
float sum = 0;
for (int i = 0; i < validCount; i++) {
sum += validReadings[i];
}
return sum / validCount;
} else {
// Enhanced simulation with realistic movement patterns
static float simulatedPos = 30.0;
static bool movingForward = true;
if (digitalRead(CONVEYOR_RELAY_PIN) == HIGH) {
if (movingForward) {
simulatedPos += 0.8;
if (simulatedPos >= MAX_PART_DISTANCE - 5) {
movingForward = false;
}
} else {
simulatedPos -= 0.8;
if (simulatedPos <= MIN_PART_DISTANCE + 5) {
movingForward = true;
}
}
}
return simulatedPos;
}
}
IRAM_ATTR void productionCounter() {
static unsigned long lastInterrupt = 0;
unsigned long currentTime = millis();
// Debounce protection
if (currentTime - lastInterrupt > 200) {
productionPulseCount++;
metrics.productionCount++;
lastInterrupt = currentTime;
}
}
void checkOperatorControls() {
static bool lastStartState = HIGH;
static bool lastEmergencyState = HIGH;
static unsigned long lastStartPress = 0;
static unsigned long lastEmergencyPress = 0;
bool currentStartState = digitalRead(BUTTON_START);
bool currentEmergencyState = digitalRead(BUTTON_EMERGENCY);
// Enhanced start button with double-click for advanced features
if (lastStartState == HIGH && currentStartState == LOW) {
unsigned long currentTime = millis();
if (currentTime - lastStartPress < 500) {
// Double click - cycle display mode
cycleDashboardMode();
logAlarm("MANUAL", "Display mode changed via button");
} else {
// Single click - start production
if (!emergencyStop && currentState == LINE_IDLE) {
productionEnabled = true;
currentState = STARTUP_SEQUENCE;
stateStartTime = millis();
cycleStartTime = millis();
logAlarm("MANUAL", "Production started via button");
}
}
lastStartPress = currentTime;
}
// Enhanced emergency stop with confirmation
if (lastEmergencyState == HIGH && currentEmergencyState == LOW) {
unsigned long currentTime = millis();
if (currentTime - lastEmergencyPress < 1000) {
// Double press within 1 second - confirm emergency stop
emergencyStop = true;
currentState = EMERGENCY_STOP;
stopAllProduction();
logAlarm("EMERGENCY", "Emergency stop activated via button (confirmed)");
} else {
// First press - warning
logAlarm("WARNING", "Emergency stop pressed - press again to confirm");
tone(ALARM_BUZZER_PIN, 2000, 300);
}
lastEmergencyPress = currentTime;
}
lastStartState = currentStartState;
lastEmergencyState = currentEmergencyState;
}
// Enhanced state machine functions with recipe integration
bool handleMaterialLoadingState(unsigned long stateTime) {
// Station 1: Material Loading with enhanced logic
digitalWrite(LED_GREEN_1, HIGH);
digitalWrite(CONVEYOR_RELAY_PIN, HIGH);
// Check if material is properly positioned
bool materialReady = (metrics.conveyorPosition > 20 && metrics.conveyorPosition < 40);
bool temperatureReady = (metrics.operatingTemperature >= currentRecipe.temperatureMin &&
metrics.operatingTemperature <= currentRecipe.temperatureMax);
if (stateTime > 3000 && materialReady && temperatureReady) {
digitalWrite(LED_GREEN_1, LOW);
logAlarm("PRODUCTION", "Material loading completed");
return true;
} else if (stateTime > 12000) {
currentState = FAULT_STATE;
logAlarm("FAULT", "Material loading timeout");
return false;
} else if (stateTime > 8000 && !temperatureReady) {
logAlarm("WARNING", "Temperature not in range for material loading");
}
return false;
}
bool handleProcessingAssemblyState(unsigned long stateTime) {
// Station 2: Processing/Assembly with recipe parameters
digitalWrite(LED_GREEN_2, HIGH);
digitalWrite(ASSEMBLY_RELAY_PIN, HIGH);
// Enhanced processing logic
bool temperatureOK = (metrics.operatingTemperature >= currentRecipe.temperatureMin &&
metrics.operatingTemperature <= currentRecipe.temperatureMax);
bool vibrationOK = (metrics.vibrationLevel < 10.0);
bool powerOK = (metrics.powerConsumption < 15.0);
float processTime = currentRecipe.cycleTimeTarget * 0.6; // 60% of cycle time for processing
if (stateTime > (processTime * 1000) && temperatureOK && vibrationOK && powerOK) {
digitalWrite(LED_GREEN_2, LOW);
digitalWrite(ASSEMBLY_RELAY_PIN, LOW);
logAlarm("PRODUCTION", "Processing/Assembly completed");
return true;
} else if (stateTime > (processTime * 1500) || !temperatureOK || !vibrationOK || !powerOK) {
currentState = FAULT_STATE;
if (!temperatureOK) logAlarm("FAULT", "Processing temperature out of range");
if (!vibrationOK) logAlarm("FAULT", "Excessive vibration during processing");
if (!powerOK) logAlarm("FAULT", "Power consumption too high");
return false;
}
return false;
}
bool handleQualityControlState(unsigned long stateTime) {
// Station 3: Enhanced Quality Control
digitalWrite(LED_GREEN_3, HIGH);
// Advanced quality checks
if (stateTime > 2000) {
bool qualityPass = metrics.qualityPass;
bool temperatureQuality = (metrics.operatingTemperature >= currentRecipe.temperatureMin &&
metrics.operatingTemperature <= currentRecipe.temperatureMax);
bool vibrationQuality = (metrics.vibrationLevel < 8.0);
// Comprehensive quality assessment
bool overallQuality = qualityPass && temperatureQuality && vibrationQuality;
if (!overallQuality) {
metrics.defectCount++;
analytics.totalDefects++;
if (!qualityPass) logAlarm("QUALITY", "Basic quality check failed");
if (!temperatureQuality) logAlarm("QUALITY", "Temperature quality check failed");
if (!vibrationQuality) logAlarm("QUALITY", "Vibration quality check failed");
// Publish detailed quality data
if (mqttConnected) {
StaticJsonDocument<256> qualityDoc;
qualityDoc["basic_quality"] = qualityPass;
qualityDoc["temperature_quality"] = temperatureQuality;
qualityDoc["vibration_quality"] = vibrationQuality;
qualityDoc["overall_pass"] = overallQuality;
qualityDoc["defect_count"] = metrics.defectCount;
char qualityBuffer[256];
serializeJson(qualityDoc, qualityBuffer);
client.publish("production/quality/detailed", qualityBuffer);
}
} else {
logAlarm("QUALITY", "Quality control passed - good unit");
}
digitalWrite(LED_GREEN_3, LOW);
return true;
}
return false;
}
bool handlePackagingOutputState(unsigned long stateTime) {
// Station 4: Enhanced Packaging/Output
digitalWrite(LED_GREEN_4, HIGH);
if (stateTime > 2500) {
// Complete production cycle with enhanced tracking
analytics.totalProduced++;
// Calculate real-time OEE
calculateProcessAnalytics();
// Update shift production
currentShift.production = metrics.productionCount;
// Log successful unit completion
logAlarm("PRODUCTION", "Unit " + String(analytics.totalProduced) + " completed successfully");
// Publish unit completion
if (mqttConnected) {
StaticJsonDocument<256> unitDoc;
unitDoc["unit_number"] = analytics.totalProduced;
unitDoc["cycle_time"] = metrics.cycleTime;
unitDoc["quality_pass"] = metrics.qualityPass;
unitDoc["temperature"] = metrics.operatingTemperature;
unitDoc["timestamp"] = getCurrentTimestamp();
char unitBuffer[256];
serializeJson(unitDoc, unitBuffer);
client.publish("production/units/completed", unitBuffer);
}
digitalWrite(LED_GREEN_4, LOW);
// Check production targets and conditions
if (!productionEnabled || emergencyStop) {
currentState = LINE_IDLE;
return false;
}
// Check if shift target reached
if (currentShift.production >= currentRecipe.targetUnits) {
logAlarm("PRODUCTION", "Shift target reached - " + String(currentRecipe.targetUnits) + " units");
return false; // Will trigger shutdown in state machine
}
return true; // Continue to next cycle
}
return false;
}
// Enhanced maintenance mode with predictive features
void handleMaintenanceMode() {
stopAllProduction();
// Enhanced maintenance mode display
static unsigned long lastBlink = 0;
static bool blinkState = false;
static int maintenanceStep = 0;
static unsigned long stepTimer = 0;
if (millis() - lastBlink > 800) {
blinkState = !blinkState;
digitalWrite(LED_GREEN_1, blinkState);
digitalWrite(LED_GREEN_2, blinkState);
digitalWrite(LED_GREEN_3, blinkState);
digitalWrite(LED_GREEN_4, blinkState);
lastBlink = millis();
}
// Maintenance procedure steps
if (millis() - stepTimer > 5000) {
maintenanceStep = (maintenanceStep + 1) % 4;
stepTimer = millis();
switch (maintenanceStep) {
case 0:
logAlarm("MAINTENANCE", "Checking conveyor system");
break;
case 1:
logAlarm("MAINTENANCE", "Checking assembly actuators");
break;
case 2:
logAlarm("MAINTENANCE", "Checking sensor calibration");
break;
case 3:
logAlarm("MAINTENANCE", "Running system diagnostics");
performAdvancedDiagnostics();
break;
}
}
// Exit maintenance mode
if (productionEnabled && !maintenanceRequired) {
currentState = STARTUP_SEQUENCE;
stateStartTime = millis();
maintenanceStep = 0;
logAlarm("MAINTENANCE", "Maintenance mode completed");
// Reset maintenance indicators
metrics.runningHours = 0;
saveDataToEEPROM();
}
}
// Enhanced fault handling with diagnostic information
void handleFaultState() {
stopAllProduction();
// Enhanced fault indication
static unsigned long lastBlink = 0;
static unsigned long lastDiagnostic = 0;
if (millis() - lastBlink > 200) {
digitalWrite(LED_RED, !digitalRead(LED_RED));
lastBlink = millis();
}
// Intermittent alarm with fault code
static unsigned long lastAlarm = 0;
static int alarmPattern = 0;
if (millis() - lastAlarm > 2000) {
// Different alarm patterns for different fault types
if (currentAlarmType == "TEMPERATURE") {
tone(ALARM_BUZZER_PIN, 1000, 200);
delay(100);
tone(ALARM_BUZZER_PIN, 1000, 200);
} else if (currentAlarmType == "VIBRATION") {
tone(ALARM_BUZZER_PIN, 1500, 300);
} else {
tone(ALARM_BUZZER_PIN, 800, 500);
}
lastAlarm = millis();
}
// Run diagnostics every 10 seconds
if (millis() - lastDiagnostic > 10000) {
performAdvancedDiagnostics();
lastDiagnostic = millis();
}
// Auto-recovery for certain faults
if (currentAlarmType == "TEMPERATURE" &&
metrics.operatingTemperature >= currentRecipe.temperatureMin &&
metrics.operatingTemperature <= currentRecipe.temperatureMax) {
logAlarm("RECOVERY", "Temperature fault cleared - auto-recovery");
currentState = LINE_IDLE;
digitalWrite(LED_RED, LOW);
noTone(ALARM_BUZZER_PIN);
}
// Manual fault reset
if (!productionEnabled) {
currentState = LINE_IDLE;
digitalWrite(LED_RED, LOW);
noTone(ALARM_BUZZER_PIN);
logAlarm("RECOVERY", "Fault manually cleared");
}
}
// Enhanced emergency stop with safety protocols
void handleEmergencyStopState() {
stopAllProduction();
// Enhanced emergency indication
digitalWrite(LED_RED, HIGH);
// All station LEDs flash in emergency pattern
static unsigned long lastFlash = 0;
static bool flashState = false;
if (millis() - lastFlash > 150) {
flashState = !flashState;
digitalWrite(LED_GREEN_1, flashState);
digitalWrite(LED_GREEN_2, flashState);
digitalWrite(LED_GREEN_3, flashState);
digitalWrite(LED_GREEN_4, flashState);
lastFlash = millis();
}
// Emergency alarm protocol
static bool emergencyAlarmActive = true;
static unsigned long emergencyAlarmStart = 0;
if (emergencyAlarmActive) {
if (emergencyAlarmStart == 0) {
emergencyAlarmStart = millis();
tone(ALARM_BUZZER_PIN, 1200);
}
// Stop alarm after 10 seconds, but keep visual indicators
if (millis() - emergencyAlarmStart > 10000) {
noTone(ALARM_BUZZER_PIN);
emergencyAlarmActive = false;
}
}
// Emergency reset protocol - requires both buttons pressed for 3 seconds
static unsigned long resetTimer = 0;
static bool resetInProgress = false;
if (digitalRead(BUTTON_START) == LOW && digitalRead(BUTTON_EMERGENCY) == LOW) {
if (!resetInProgress) {
resetInProgress = true;
resetTimer = millis();
logAlarm("EMERGENCY", "Emergency reset initiated - hold both buttons");
}
// 3-second hold required
if (millis() - resetTimer > 3000) {
emergencyStop = false;
productionEnabled = false;
currentState = LINE_IDLE;
emergencyAlarmActive = false;
resetInProgress = false;
// Clear all LEDs
digitalWrite(LED_RED, LOW);
digitalWrite(LED_GREEN_1, LOW);
digitalWrite(LED_GREEN_2, LOW);
digitalWrite(LED_GREEN_3, LOW);
digitalWrite(LED_GREEN_4, LOW);
noTone(ALARM_BUZZER_PIN);
logAlarm("RECOVERY", "Emergency stop reset - System safe to operate");
// Publish emergency reset
if (mqttConnected) {
client.publish("production/emergency/reset", "true");
client.publish("production/status/emergency", "CLEARED");
}
}
} else {
resetInProgress = false;
resetTimer = 0;
}
}
// Enhanced quality checks with predictive analysis
void performQualityChecks() {
// Temperature monitoring with trends
static float tempHistory[10] = {0};
static int tempHistoryIndex = 0;
tempHistory[tempHistoryIndex] = metrics.operatingTemperature;
tempHistoryIndex = (tempHistoryIndex + 1) % 10;
// Calculate temperature trend
float tempTrend = 0;
for (int i = 1; i < 10; i++) {
tempTrend += tempHistory[i] - tempHistory[i-1];
}
tempTrend /= 9;
// Predictive temperature alerts
if (tempTrend > 2.0 && metrics.operatingTemperature > 60) {
logAlarm("PREDICTION", "Temperature rising trend detected");
}
// Critical temperature thresholds
if (metrics.operatingTemperature > 78) {
logAlarm("CRITICAL", "Critical temperature - immediate shutdown required");
if (currentState != EMERGENCY_STOP && currentState != FAULT_STATE) {
currentState = FAULT_STATE;
}
} else if (metrics.operatingTemperature > 75) {
logAlarm("WARNING", "High temperature warning");
}
// Quality rate monitoring with statistical analysis
if (metrics.defectCount > 0 && metrics.productionCount >= 10) {
float defectRate = (float)metrics.defectCount / metrics.productionCount * 100;
float qualityThreshold = 5.0; // 5% defect rate threshold
if (defectRate > qualityThreshold) {
logAlarm("QUALITY", "Quality threshold exceeded: " + String(defectRate, 1) + "%");
// Suggest corrective actions
if (mqttConnected) {
StaticJsonDocument<256> correctionDoc;
correctionDoc["defect_rate"] = defectRate;
correctionDoc["threshold"] = qualityThreshold;
correctionDoc["suggestion"] = "Review process parameters and perform quality audit";
char correctionBuffer[256];
serializeJson(correctionDoc, correctionBuffer);
client.publish("production/quality/correction_needed", correctionBuffer);
}
}
}
// Vibration analysis with frequency detection
performVibrationAnalysis();
// Power consumption analysis
if (metrics.powerConsumption > 12.0) {
logAlarm("ENERGY", "High power consumption detected: " + String(metrics.powerConsumption) + "kW");
}
// OEE monitoring
if (metrics.oeeScore < 40.0 && metrics.productionCount > 5) {
logAlarm("PERFORMANCE", "Low OEE score detected: " + String(metrics.oeeScore) + "%");
}
}
// Enhanced analytics calculation with Industry 4.0 metrics
void calculateProcessAnalytics() {
unsigned long currentTime = millis();
float operationHours = (currentTime - cycleStartTime) / 3600000.0;
if (analytics.totalProduced > 0 && operationHours > 0) {
// Availability calculation (includes planned and unplanned downtime)
unsigned long plannedProductionTime = currentTime - cycleStartTime;
unsigned long actualRunningTime = plannedProductionTime - analytics.totalDowntime;
analytics.availability = (float)actualRunningTime / plannedProductionTime * 100;
if (analytics.availability > 100) analytics.availability = 100;
// Performance calculation (actual vs ideal production rate)
float idealCycleTime = currentRecipe.cycleTimeTarget;
float actualCycleTime = analytics.avgCycleTime;
if (actualCycleTime > 0) {
analytics.performance = (idealCycleTime / actualCycleTime) * 100;
if (analytics.performance > 100) analytics.performance = 100;
}
// Quality calculation (good units / total units)
int goodUnits = analytics.totalProduced - analytics.totalDefects;
analytics.quality = (float)goodUnits / analytics.totalProduced * 100;
// Overall Equipment Effectiveness (OEE)
metrics.oeeScore = (analytics.availability * analytics.performance * analytics.quality) / 10000.0;
// Advanced metrics
analytics.avgCycleTime = (float)(currentTime - cycleStartTime) / 1000.0 / analytics.totalProduced;
// Production rate analysis
float currentProductionRate = (float)analytics.totalProduced / operationHours;
float targetProductionRate = (float)currentRecipe.targetUnits / 8.0; // 8-hour shift
// Efficiency against target
metrics.efficiency = (currentProductionRate / targetProductionRate) * 100;
if (metrics.efficiency > 100) metrics.efficiency = 100;
// Update shift OEE history
int currentShiftIndex = (shiftNumber - 1) % 3;
analytics.shiftOEE[currentShiftIndex] = metrics.oeeScore;
analytics.shiftProduction[currentShiftIndex] = currentShift.production;
}
// Predictive maintenance calculations
if (predictiveMaintenanceEnabled) {
// Calculate maintenance score based on multiple factors
float maintenanceScore = 100.0;
maintenanceScore -= (metrics.runningHours / (float)PREDICTIVE_MAINTENANCE_HOURS) * 30;
maintenanceScore -= (metrics.vibrationLevel / 20.0) * 25;
maintenanceScore -= ((100 - analytics.quality) / 100.0) * 20;
maintenanceScore -= (metrics.defectCount / (float)max(1, metrics.productionCount)) * 25;
if (maintenanceScore < 30 && !maintenanceRequired) {
maintenanceRequired = true;
logAlarm("PREDICTIVE", "Predictive maintenance threshold reached");
}
// Publish maintenance score
if (mqttConnected) {
client.publish("production/predictive/maintenance_score", String(maintenanceScore).c_str());
}
}
}
// Enhanced LED management with status patterns
void updateProductionLEDs() {
static unsigned long lastUpdate = 0;
static bool ledState = false;
static int breathingCounter = 0;
if (millis() - lastUpdate > 100) {
lastUpdate = millis();
ledState = !ledState;
breathingCounter++;
// Connection status indication on red LED
if (!wifiConnected || !mqttConnected) {
// Fast blink for connection issues
digitalWrite(LED_RED, (breathingCounter % 5) == 0 ? HIGH : LOW);
} else if (activeAlarm && currentState != FAULT_STATE && currentState != EMERGENCY_STOP) {
// Slow blink for active alarms
digitalWrite(LED_RED, (breathingCounter % 20) == 0 ? HIGH : LOW);
}
// Station LEDs show production flow in idle/maintenance mode
if (currentState == LINE_IDLE || currentState == MAINTENANCE_MODE) {
// Breathing effect for idle state
int brightness = (breathingCounter % 100);
if (brightness > 50) brightness = 100 - brightness;
// Simulate breathing effect with on/off pattern
bool breathe = (brightness > 25);
if (currentState == LINE_IDLE) {
digitalWrite(LED_GREEN_1, breathe);
digitalWrite(LED_GREEN_2, breathe);
digitalWrite(LED_GREEN_3, breathe);
digitalWrite(LED_GREEN_4, breathe);
}
}
// Energy efficiency indication
if (energyMonitoringEnabled && currentState != MAINTENANCE_MODE) {
// Show power consumption on unused LEDs during production
bool highPower = metrics.powerConsumption > 8.0;
// This could be used for additional LED indicators if available
}
}
}
// Comprehensive analytics publishing
void publishProductionAnalytics() {
if (!mqttConnected) return;
// Main production telemetry
StaticJsonDocument<1536> doc;
doc["timestamp"] = getCurrentTimestamp();
doc["conveyor_position"] = metrics.conveyorPosition;
doc["production_count"] = metrics.productionCount;
doc["operating_temperature"] = metrics.operatingTemperature;
doc["quality_pass"] = metrics.qualityPass;
doc["cycle_time"] = metrics.cycleTime;
doc["efficiency"] = metrics.efficiency;
doc["defect_count"] = metrics.defectCount;
doc["oee_score"] = metrics.oeeScore;
doc["vibration_level"] = metrics.vibrationLevel;
doc["power_consumption"] = metrics.powerConsumption;
doc["running_hours"] = metrics.runningHours;
// System status
doc["conveyor_running"] = digitalRead(CONVEYOR_RELAY_PIN);
doc["assembly_active"] = digitalRead(ASSEMBLY_RELAY_PIN);
doc["production_state"] = stateToString(currentState);
doc["emergency_stop"] = emergencyStop;
doc["maintenance_required"] = maintenanceRequired;
doc["wifi_connected"] = wifiConnected;
doc["mqtt_connected"] = mqttConnected;
doc["ntp_synced"] = ntpSynced;
// Enhanced analytics
doc["total_produced"] = analytics.totalProduced;
doc["total_defects"] = analytics.totalDefects;
doc["availability"] = analytics.availability;
doc["performance"] = analytics.performance;
doc["quality_rate"] = analytics.quality;
doc["avg_cycle_time"] = analytics.avgCycleTime;
doc["target_efficiency"] = analytics.targetEfficiency;
// Shift information
doc["shift_number"] = shiftNumber;
doc["shift_type"] = currentShift.shiftType;
doc["shift_production"] = currentShift.production;
doc["shift_oee"] = currentShift.oee;
// Recipe information
doc["recipe_name"] = currentRecipe.recipeName;
doc["recipe_target"] = currentRecipe.targetUnits;
doc["recipe_cycle_time"] = currentRecipe.cycleTimeTarget;
// Alarm status
doc["active_alarm"] = activeAlarm;
doc["alarm_type"] = currentAlarmType;
doc["alarm_count"] = alarmCount;
// Feature status
doc["predictive_maintenance_enabled"] = predictiveMaintenanceEnabled;
doc["energy_monitoring_enabled"] = energyMonitoringEnabled;
doc["shift_tracking_enabled"] = shiftTrackingEnabled;
char buffer[1536];
serializeJson(doc, buffer);
client.publish("production/telemetry/comprehensive", buffer);
// Individual KPI topics for dashboard widgets
client.publish("production/kpi/oee", String(metrics.oeeScore).c_str());
client.publish("production/kpi/efficiency", String(metrics.efficiency).c_str());
client.publish("production/kpi/availability", String(analytics.availability).c_str());
client.publish("production/kpi/performance", String(analytics.performance).c_str());
client.publish("production/kpi/quality_rate", String(analytics.quality).c_str());
client.publish("production/kpi/production_count", String(metrics.productionCount).c_str());
client.publish("production/kpi/defect_rate",
String(metrics.productionCount > 0 ? (float)metrics.defectCount / metrics.productionCount * 100 : 0).c_str());
}
void stopAllProduction() {
digitalWrite(CONVEYOR_RELAY_PIN, LOW);
digitalWrite(ASSEMBLY_RELAY_PIN, LOW);
noTone(ALARM_BUZZER_PIN);
// Don't turn off LEDs in certain states to maintain status indication
if (currentState != MAINTENANCE_MODE && currentState != FAULT_STATE && currentState != EMERGENCY_STOP) {
digitalWrite(LED_GREEN_1, LOW);
digitalWrite(LED_GREEN_2, LOW);
digitalWrite(LED_GREEN_3, LOW);
digitalWrite(LED_GREEN_4, LOW);
}
}
void updateLcdLine(int line, String content) {
// Ensure exactly 20 characters for clean display
while (content.length() < 20) {
content += " ";
}
if (content.length() > 20) {
content = content.substring(0, 20);
}
// Update only if content changed to reduce I2C traffic
if (lcdLine[line] != content) {
lcdLine[line] = content;
lcd.setCursor(0, line);
lcd.print(content);
}
}
const char* stateToString(ProductionState state) {
switch (state) {
case LINE_IDLE: return "IDLE";
case STARTUP_SEQUENCE: return "STARTING";
case SHUTDOWN_SEQUENCE: return "SHUTDOWN";
case MATERIAL_LOADING: return "LOADING";
case PROCESSING_ASSEMBLY: return "ASSEMBLY";
case QUALITY_CONTROL: return "QC_CHECK";
case PACKAGING_OUTPUT: return "PACKAGING";
case MAINTENANCE_MODE: return "MAINT";
case FAULT_STATE: return "FAULT";
case EMERGENCY_STOP: return "E-STOP";
default: return "UNKNOWN";
}
}