CCCPaste

# Relay Controller rev_03

/********* Pleasedontcode.com **********

	Pleasedontcode thanks you for automatic code generation! Enjoy your code!

	- Terms and Conditions:
	You have a non-exclusive, revocable, worldwide, royalty-free license
	for personal and commercial use. Attribution is optional; modifications
	are allowed, but you're responsible for code maintenance. We're not
	liable for any loss or damage. For full terms,
	please visit pleasedontcode.com/termsandconditions.

	- Project: # Relay Controller
	- Source Code NOT compiled for: ESP8266 NodeMCU V1.0
	- Source Code created on: 2026-02-05 14:55:51

********* Pleasedontcode.com **********/

/****** SYSTEM REQUIREMENTS *****/
/****** SYSTEM REQUIREMENT 1 *****/
	/* add i2c gpio expander PCF8574 */
/****** END SYSTEM REQUIREMENTS *****/


#include "PCF8574.h"
#include "Relay.h"
#include "VoiceAssistant.h"

// Create PCF8574 instance with default I2C address 0x20
// Address range: 0x20 to 0x27 depending on A0, A1, A2 pins
PCF8574 expander(0x20);

// Create Relay instance for managing 8 relays
Relay relayController(&expander, 8);

// Create VoiceAssistant instance
VoiceAssistant* voiceAssistant = nullptr;

// WiFi Configuration
// IMPORTANT: Replace these with your actual WiFi credentials
const char* WIFI_SSID = "YourWiFiSSID";
const char* WIFI_PASSWORD = "YourWiFiPassword";

// Google Cloud Speech-to-Text API Configuration
// IMPORTANT: Replace these with your actual Google Cloud credentials
const char* GOOGLE_API_KEY = "YOUR_GOOGLE_CLOUD_API_KEY_HERE";
const char* GOOGLE_PROJECT_ID = "YOUR_GOOGLE_PROJECT_ID_HERE";

// Serial command buffer
String commandBuffer = "";

// Timestamp for periodic status monitoring
unsigned long lastStatusTime = 0;
unsigned long statusInterval = 5000;  // Print status every 5 seconds

// Voice command request flag
volatile bool voiceCommandRequested = false;
unsigned long voiceCommandRequestTime = 0;
const unsigned long voiceCommandDebounce = 1000;  // Debounce time for voice command requests

// Relay state change log
struct RelayLog {
  uint32_t timestamp;
  uint8_t relayNum;
  bool state;  // true = ON, false = OFF
  char action[20];
};

// Log buffer for relay state changes (last 10 changes)
RelayLog relayLogs[10];
uint8_t logIndex = 0;

/****** FUNCTION PROTOTYPES *****/
void setup(void);
void loop(void);
void processSerialCommand(String command);
void handleRelayCommand(String command);
void printAvailableCommands(void);
void logRelayStateChange(uint8_t relayNum, bool newState, const char* action);
void printRelayLogs(void);
void printRelayStatusMonitoring(void);
void processVoiceCommand(void);
void initializeVoiceAssistant(void);

void setup(void)
{
  // Initialize Serial communication
  Serial.begin(115200);
  delay(100);
  
  Serial.println("\n\n================================================");
  Serial.println("     PCF8574 I2C GPIO Expander with Relays     ");
  Serial.println("         + Voice Assistant Support              ");
  Serial.println("================================================");
  Serial.println();
  
  // Initialize PCF8574 with I2C address 0x20
  // Using default ESP8266 pins: SDA=4 (D2), SCL=5 (D1)
  Serial.println("Initializing PCF8574 I2C GPIO Expander...");
  
  if (expander.begin()) {
    Serial.println("[SUCCESS] PCF8574 initialized successfully!");
    Serial.print("[INFO] I2C Address: 0x");
    Serial.println(0x20, HEX);
    Serial.println("[INFO] I2C Pins: SDA=4 (D2), SCL=5 (D1)");
    
    // Initialize all relay pins to HIGH (default/inactive state)
    expander.writePort(0xFF);
    Serial.println("[INFO] All relay pins set to HIGH (inactive)");
    
    delay(500);
    
    // Initialize Relay controller
    Serial.println();
    Serial.println("Initializing Relay Controller...");
    relayController.init();
    
    Serial.println();
    Serial.println("[SUCCESS] System fully initialized and ready!");
    Serial.println();
    
    // Initialize Voice Assistant with WiFi and Google Cloud API
    delay(1000);
    Serial.println();
    initializeVoiceAssistant();
    
    delay(1000);
    
    // Print help information
    printAvailableCommands();
    
    // Initialize log timestamps
    lastStatusTime = millis();
    
  } else {
    Serial.println("[ERROR] Failed to initialize PCF8574!");
    Serial.println("[ERROR] Please check:");
    Serial.println("  - I2C connections (SDA=D2/GPIO4, SCL=D1/GPIO5)");
    Serial.println("  - PCF8574 I2C address (default 0x20, check A0/A1/A2 pins)");
    Serial.println("  - PCF8574 power supply (VCC and GND)");
    Serial.println();
    Serial.println("System will continue but relay control may not work.");
  }
  
  delay(1000);
}


void loop(void)
{
  // Check for serial commands
  if (Serial.available()) {
    char inChar = Serial.read();
    
    // Handle enter key (carriage return or newline)
    if (inChar == '\r' || inChar == '\n') {
      if (commandBuffer.length() > 0) {
        Serial.println();  // Echo newline
        Serial.print("[COMMAND] ");
        Serial.println(commandBuffer);
        processSerialCommand(commandBuffer);
        commandBuffer = "";  // Clear buffer
      }
    }
    // Handle backspace
    else if (inChar == '\b') {
      if (commandBuffer.length() > 0) {
        commandBuffer.remove(commandBuffer.length() - 1);
        Serial.print(inChar);  // Echo backspace
      }
    }
    // Add character to buffer
    else if (inChar >= 32 && inChar <= 126) {  // Printable ASCII characters
      commandBuffer += inChar;
      Serial.print(inChar);  // Echo character
    }
  }
  
  // Check if voice command was requested via serial
  if (voiceCommandRequested && (millis() - voiceCommandRequestTime >= voiceCommandDebounce)) {
    voiceCommandRequested = false;
    processVoiceCommand();
  }
  
  // Periodic status monitoring and logging
  if (millis() - lastStatusTime >= statusInterval) {
    lastStatusTime = millis();
    printRelayStatusMonitoring();
  }
  
  // Small delay to prevent watchdog timeout
  delay(10);
}


// Initialize VoiceAssistant with WiFi and Google Cloud API credentials
void initializeVoiceAssistant(void) {
  Serial.println("=== Initializing Voice Assistant System ===");
  
  // Create VoiceAssistant instance
  voiceAssistant = new VoiceAssistant(&relayController);
  
  if (voiceAssistant == nullptr) {
    Serial.println("[ERROR] Failed to allocate memory for VoiceAssistant!");
    return;
  }
  
  // Begin voice assistant initialization with WiFi and API credentials
  if (voiceAssistant->begin(WIFI_SSID, WIFI_PASSWORD, GOOGLE_API_KEY, GOOGLE_PROJECT_ID)) {
    Serial.println("[SUCCESS] Voice Assistant initialized successfully!");
    
    // Configure audio settings (16kHz, Mono, 16-bit, 10 second timeout)
    voiceAssistant->setAudioConfig(16000, 1, 16, 10000);
    
    // Print voice assistant status
    voiceAssistant->printStatus();
    voiceAssistant->printConfiguration();
    
    Serial.println("=== Voice Assistant Ready ===");
    Serial.println("[INFO] Voice commands are now available!");
    Serial.println("[INFO] Type 'voice' command to process voice commands");
    Serial.println("[INFO] Type 'vtest ' to test voice parsing");
    Serial.println();
  } else {
    Serial.println("[ERROR] Failed to initialize Voice Assistant!");
    Serial.println("[ERROR] WiFi connection failed or API credentials invalid");
    Serial.println("[INFO] System will continue with relay control only");
    
    // Clean up on failure
    delete voiceAssistant;
    voiceAssistant = nullptr;
  }
  
  Serial.println("========================================");
  Serial.println();
}


// Process voice command from microphone
void processVoiceCommand(void) {
  if (voiceAssistant == nullptr) {
    Serial.println("[ERROR] Voice Assistant not initialized!");
    return;
  }
  
  if (!voiceAssistant->isInitialized()) {
    Serial.println("[ERROR] Voice Assistant not ready!");
    return;
  }
  
  Serial.println();
  Serial.println("================================================");
  Serial.println("         Starting Voice Command Processing       ");
  Serial.println("================================================");
  
  // Process voice command from microphone
  VoiceCommandResult result = voiceAssistant->processVoiceCommand();
  
  Serial.println();
  Serial.print("[VOICE] Processing Result: ");
  
  switch(result) {
    case VOICE_CMD_SUCCESS:
      Serial.println("SUCCESS");
      Serial.println("[VOICE] Command executed successfully!");
      break;
    case VOICE_CMD_WIFI_NOT_CONNECTED:
      Serial.println("ERROR - WiFi Not Connected");
      Serial.println("[VOICE] Attempting to reconnect to WiFi...");
      voiceAssistant->reconnectWiFi();
      break;
    case VOICE_CMD_API_ERROR:
      Serial.println("ERROR - Google Cloud API Error");
      Serial.println("[VOICE] Failed to communicate with Google Cloud Speech-to-Text API");
      break;
    case VOICE_CMD_AUDIO_CAPTURE_FAILED:
      Serial.println("ERROR - Audio Capture Failed");
      Serial.println("[VOICE] Failed to capture audio from microphone");
      break;
    case VOICE_CMD_PARSE_FAILED:
      Serial.println("ERROR - Command Parse Failed");
      Serial.println("[VOICE] Could not parse the recognized speech into a valid command");
      break;
    case VOICE_CMD_INVALID_RELAY:
      Serial.println("ERROR - Invalid Relay Number");
      Serial.println("[VOICE] The recognized relay number is out of range (0-7)");
      break;
    case VOICE_CMD_TIMEOUT:
      Serial.println("ERROR - Command Timeout");
      Serial.println("[VOICE] Voice command processing timed out");
      break;
    case VOICE_CMD_BUSY:
      Serial.println("ERROR - System Busy");
      Serial.println("[VOICE] Voice Assistant is currently busy processing a command");
      break;
    default:
      Serial.println("UNKNOWN ERROR");
      break;
  }
  
  Serial.println();
  Serial.println("================================================");
  Serial.println();
  
  // Print current relay status
  relayController.printStatus();
  
  // Print voice command log
  voiceAssistant->printVoiceCommandLog();
}


// Process serial commands from user
void processSerialCommand(String command) {
  // Convert to lowercase for case-insensitive handling
  command.toLowerCase();
  
  // Trim whitespace
  command.trim();
  
  // Empty command
  if (command.length() == 0) {
    return;
  }
  
  // Help command
  if (command == "help" || command == "?") {
    printAvailableCommands();
    return;
  }
  
  // Status command - print current relay states
  if (command == "status" || command == "s") {
    Serial.println();
    relayController.printStatus();
    Serial.println();
    return;
  }
  
  // Logs command - print relay change logs
  if (command == "logs" || command == "l") {
    Serial.println();
    printRelayLogs();
    Serial.println();
    return;
  }
  
  // Clear logs command
  if (command == "clrlogs" || command == "cl") {
    logIndex = 0;
    Serial.println("[INFO] Relay logs cleared");
    Serial.println();
    return;
  }
  
  // Voice Assistant commands
  if (voiceAssistant != nullptr) {
    // Voice command - process audio from microphone
    if (command == "voice" || command == "v") {
      voiceCommandRequested = true;
      voiceCommandRequestTime = millis();
      return;
    }
    
    // Voice test command - test voice parsing with a string
    if (command.startsWith("vtest ")) {
      String testCommand = command.substring(6);
      testCommand.trim();
      
      if (testCommand.length() > 0) {
        Serial.println();
        Serial.println("=== Testing Voice Command Parsing ===");
        Serial.print("[VOICE] Test command: ");
        Serial.println(testCommand);
        
        VoiceCommandResult result = voiceAssistant->executeCommandFromString(testCommand);
        
        Serial.println();
        Serial.print("[VOICE] Parse result: ");
        switch(result) {
          case VOICE_CMD_SUCCESS:
            Serial.println("SUCCESS");
            break;
          case VOICE_CMD_PARSE_FAILED:
            Serial.println("PARSE FAILED");
            break;
          case VOICE_CMD_INVALID_RELAY:
            Serial.println("INVALID RELAY");
            break;
          default:
            Serial.println("ERROR");
            break;
        }
        
        Serial.println("[VOICE] ====================================");
        Serial.println();
        
        relayController.printStatus();
      } else {
        Serial.println("[ERROR] Please provide a test command. Usage: vtest ");
      }
      return;
    }
    
    // Voice command log command
    if (command == "vlogs" || command == "vl") {
      voiceAssistant->printVoiceCommandLog();
      return;
    }
    
    // Clear voice command logs
    if (command == "vclrlogs" || command == "vcl") {
      voiceAssistant->clearVoiceCommandLog();
      Serial.println();
      return;
    }
    
    // Voice assistant status command
    if (command == "vstatus" || command == "vs") {
      voiceAssistant->printStatus();
      return;
    }
    
    // Voice assistant configuration command
    if (command == "vconfig" || command == "vc") {
      voiceAssistant->printConfiguration();
      return;
    }
  }
  
  // Turn off all relays
  if (command == "alloff" || command == "off") {
    Serial.println();
    relayController.turnOffAll();
    logRelayStateChange(255, false, "ALL OFF");
    relayController.printStatus();
    Serial.println();
    return;
  }
  
  // Test mode - cycle through all relays
  if (command == "test" || command == "t") {
    Serial.println();
    Serial.println("[TEST] Starting relay test sequence...");
    Serial.println("[TEST] Turning ON each relay for 1 second...");
    Serial.println();
    
    for (uint8_t i = 0; i < 8; i++) {
      Serial.print("[TEST] Testing Relay ");
      Serial.println(i);
      relayController.turnOn(i);
      delay(1000);
      relayController.turnOff(i);
      delay(200);
    }
    
    Serial.println();
    Serial.println("[TEST] Relay test sequence completed!");
    Serial.println();
    return;
  }
  
  // Relay control commands
  if (command.startsWith("on ") || command.startsWith("off ") || 
      command.startsWith("toggle ") || command.startsWith("tog ")) {
    handleRelayCommand(command);
    return;
  }
  
  // Check for single digit commands (quick on/off)
  if (command.length() == 1 && isDigit(command[0])) {
    uint8_t relayNum = command[0] - '0';
    if (relayNum < 8) {
      Serial.println();
      relayController.toggle(relayNum);
      logRelayStateChange(relayNum, relayController.getState(relayNum), "TOGGLE");
      relayController.printStatus();
      Serial.println();
      return;
    }
  }
  
  // Unknown command
  Serial.println("[ERROR] Unknown command. Type 'help' for available commands.");
  Serial.println();
}


// Handle relay-specific commands (on/off/toggle)
void handleRelayCommand(String command) {
  String action = "";
  uint8_t relayNum = 255;
  
  Serial.println();
  
  // Parse "on" commands
  if (command.startsWith("on ")) {
    action = "ON";
    command = command.substring(3);
    command.trim();
    
    if (command.length() > 0 && isDigit(command[0])) {
      relayNum = command[0] - '0';
    }
  }
  // Parse "off" commands
  else if (command.startsWith("off ")) {
    action = "OFF";
    command = command.substring(4);
    command.trim();
    
    if (command.length() > 0 && isDigit(command[0])) {
      relayNum = command[0] - '0';
    }
  }
  // Parse "toggle" or "tog" commands
  else if (command.startsWith("toggle ")) {
    action = "TOGGLE";
    command = command.substring(7);
    command.trim();
    
    if (command.length() > 0 && isDigit(command[0])) {
      relayNum = command[0] - '0';
    }
  }
  else if (command.startsWith("tog ")) {
    action = "TOGGLE";
    command = command.substring(4);
    command.trim();
    
    if (command.length() > 0 && isDigit(command[0])) {
      relayNum = command[0] - '0';
    }
  }
  
  // Execute command if relay number is valid
  if (relayNum < 8) {
    if (action == "ON") {
      relayController.turnOn(relayNum);
      logRelayStateChange(relayNum, true, "ON");
    }
    else if (action == "OFF") {
      relayController.turnOff(relayNum);
      logRelayStateChange(relayNum, false, "OFF");
    }
    else if (action == "TOGGLE") {
      relayController.toggle(relayNum);
      logRelayStateChange(relayNum, relayController.getState(relayNum), "TOGGLE");
    }
    
    relayController.printStatus();
  } else {
    Serial.print("[ERROR] Invalid relay number. Use 0-7. Got: ");
    Serial.println(command);
  }
  
  Serial.println();
}


// Print available commands
void printAvailableCommands(void) {
  Serial.println();
  Serial.println("================================================");
  Serial.println("           Available Serial Commands            ");
  Serial.println("================================================");
  Serial.println();
  Serial.println("Relay Control Commands:");
  Serial.println("  on       - Turn ON relay n (0-7)");
  Serial.println("  off      - Turn OFF relay n (0-7)");
  Serial.println("  toggle   - Toggle relay n (0-7)");
  Serial.println("  tog      - Toggle relay n (0-7) [shorthand]");
  Serial.println("           - Quick toggle relay n (0-7)");
  Serial.println("  alloff      - Turn OFF all relays");
  Serial.println("  off         - Turn OFF all relays [shorthand]");
  Serial.println();
  Serial.println("Voice Assistant Commands (requires WiFi & API credentials):");
  Serial.println("  voice, v    - Process voice command from microphone");
  Serial.println("  vtest  - Test voice parsing with command string");
  Serial.println("  vlogs, vl   - Display voice command logs");
  Serial.println("  vclrlogs,vc - Clear voice command logs");
  Serial.println("  vstatus, vs - Display voice assistant status");
  Serial.println("  vconfig, vc - Display voice assistant configuration");
  Serial.println();
  Serial.println("System Commands:");
  Serial.println("  status, s   - Display current relay status");
  Serial.println("  logs, l     - Display relay change log");
  Serial.println("  clrlogs, cl - Clear relay change log");
  Serial.println("  test, t     - Run relay test sequence");
  Serial.println("  help, ?     - Display this help message");
  Serial.println();
  Serial.println("Example: Type 'on 0' to turn ON relay 0");
  Serial.println("Example: Type 'vtest turn on relay 3' to test voice parsing");
  Serial.println("================================================");
  Serial.println();
}


// Log relay state changes
void logRelayStateChange(uint8_t relayNum, bool newState, const char* action) {
  // Special handling for "ALL OFF" action
  if (relayNum == 255) {
    // Log all relays being turned off
    for (uint8_t i = 0; i < 8; i++) {
      RelayLog& log = relayLogs[logIndex % 10];
      log.timestamp = millis();
      log.relayNum = i;
      log.state = false;
      strncpy(log.action, action, sizeof(log.action) - 1);
      log.action[sizeof(log.action) - 1] = '\0';
      logIndex++;
    }
  } else {
    RelayLog& log = relayLogs[logIndex % 10];
    log.timestamp = millis();
    log.relayNum = relayNum;
    log.state = newState;
    strncpy(log.action, action, sizeof(log.action) - 1);
    log.action[sizeof(log.action) - 1] = '\0';
    logIndex++;
  }
}


// Print relay state change logs
void printRelayLogs(void) {
  Serial.println("=== Relay Change Log (Last 10 changes) ===");
  
  if (logIndex == 0) {
    Serial.println("No relay changes recorded yet.");
  } else {
    // Calculate start index for circular buffer
    uint8_t startIdx = (logIndex >= 10) ? (logIndex % 10) : 0;
    uint8_t count = (logIndex >= 10) ? 10 : logIndex;
    
    for (uint8_t i = 0; i < count; i++) {
      uint8_t idx = (startIdx + i) % 10;
      RelayLog& log = relayLogs[idx];
      
      Serial.print("[");
      Serial.print(log.timestamp / 1000);
      Serial.print("s] Relay ");
      Serial.print(log.relayNum);
      Serial.print(": ");
      Serial.print(log.state ? "ON" : "OFF");
      Serial.print(" (");
      Serial.print(log.action);
      Serial.println(")");
    }
  }
  
  Serial.println("===========================================");
}


// Print relay status monitoring with periodic updates
void printRelayStatusMonitoring(void) {
  // Calculate uptime
  uint32_t uptimeSeconds = millis() / 1000;
  uint8_t hours = uptimeSeconds / 3600;
  uint8_t minutes = (uptimeSeconds % 3600) / 60;
  uint8_t seconds = uptimeSeconds % 60;
  
  Serial.println();
  Serial.println("--------- Relay Status Monitor (Periodic) --------");
  Serial.print("Uptime: ");
  
  if (hours < 10) Serial.print("0");
  Serial.print(hours);
  Serial.print(":");
  
  if (minutes < 10) Serial.print("0");
  Serial.print(minutes);
  Serial.print(":");
  
  if (seconds < 10) Serial.print("0");
  Serial.println(seconds);
  
  // Print all relay states
  Serial.println("Current Relay States:");
  for (uint8_t i = 0; i < 8; i++) {
    Serial.print("  Relay ");
    Serial.print(i);
    Serial.print(": ");
    Serial.println(relayController.getState(i) ? "ON " : "OFF");
  }
  
  // Print number of logged changes
  Serial.print("Total logged relay changes: ");
  Serial.println(logIndex);
  
  // Print voice assistant status if available
  if (voiceAssistant != nullptr && voiceAssistant->isInitialized()) {
    Serial.print("Voice Assistant WiFi: ");
    Serial.println(voiceAssistant->isWiFiConnected() ? "CONNECTED" : "DISCONNECTED");
    Serial.print("Voice Commands Logged: ");
    Serial.println(voiceAssistant->getCommandLogCount());
    
    // Attempt to reconnect WiFi if disconnected
    if (!voiceAssistant->isWiFiConnected()) {
      Serial.println("[INFO] Attempting WiFi reconnection...");
      voiceAssistant->reconnectWiFi();
    }
  }
  
  Serial.println("--------------------------------------------------");
  Serial.println();
}

/* END CODE */