Automated Voice Command Interpreter for Smart Home Devices Python

👤 Sharing: AI
```python
import speech_recognition as sr
import os
import subprocess  # For executing shell commands (for device control)
import json

# Configuration (ideally, load from a file)
CONFIG_FILE = "config.json"


def load_configuration(config_file):
    """Loads device configurations from a JSON file.

    Args:
        config_file (str): Path to the JSON configuration file.

    Returns:
        dict: A dictionary containing device configurations.  Returns an empty dict if the file isn't found or has errors.
    """
    try:
        with open(config_file, 'r') as f:
            config = json.load(f)
        return config
    except FileNotFoundError:
        print(f"Configuration file not found: {config_file}")
        return {}
    except json.JSONDecodeError:
        print(f"Error decoding JSON from configuration file: {config_file}")
        return {}
    except Exception as e:
        print(f"Error loading configuration: {e}")
        return {}  # Return an empty dictionary in case of errors


def save_configuration(config_file, config_data):
    """Saves device configurations to a JSON file.

    Args:
        config_file (str): Path to the JSON configuration file.
        config_data (dict): The device configuration data to save.
    """
    try:
        with open(config_file, 'w') as f:
            json.dump(config_data, f, indent=4)  # Pretty print JSON
        print(f"Configuration saved to {config_file}")
    except Exception as e:
        print(f"Error saving configuration: {e}")


def recognize_speech():
    """Listens for audio and converts it to text."""
    recognizer = sr.Recognizer()
    with sr.Microphone() as source:
        print("Listening...")
        recognizer.adjust_for_ambient_noise(source)  # Calibrate for noise
        audio = recognizer.listen(source)

    try:
        print("Recognizing...")
        text = recognizer.recognize_google(audio)  # Use Google Speech Recognition
        print(f"You said: {text}")
        return text.lower()  # Convert to lowercase for easier matching
    except sr.UnknownValueError:
        print("Could not understand audio")
        return None
    except sr.RequestError as e:
        print(f"Could not request results from Google Speech Recognition service; {e}")
        return None


def execute_command(command, config):
    """Executes a command based on the recognized text.

    Args:
        command (str): The recognized speech command (lowercase).
        config (dict): The loaded device configurations.
    """
    if not config:
        print("No devices configured. Please configure devices in config.json.")
        return

    found_match = False
    for device, device_data in config.items():
        for trigger_phrase in device_data.get("trigger_phrases", []):  # Handle missing trigger phrases
            if trigger_phrase in command:
                action = device_data.get("action", "echo")  # Default to echo if no action specified
                command_to_run = device_data.get("command")  # The actual shell command

                if not command_to_run:
                    print(f"No command specified for device: {device}")
                    break  # Skip to the next device

                print(f"Executing action '{action}' for device '{device}'...")

                if action == "shell":
                    try:
                        subprocess.run(command_to_run, shell=True, check=True)  # Execute shell command
                    except subprocess.CalledProcessError as e:
                        print(f"Error executing command: {e}")
                elif action == "echo":
                    print(f"Device '{device}': {command_to_run}") #Simulate action by printing the command.
                # Add more actions (e.g., "http_request") as needed
                else:
                    print(f"Unknown action: {action}")

                found_match = True
                break  # Only execute the *first* matching device

        if found_match:
            break  # Exit the outer loop if a match was found

    if not found_match:
        print("Command not recognized for any configured devices.")


def configure_new_device(config):
    """Allows the user to add a new device configuration."""
    device_name = input("Enter the name for the new device: ")
    if not device_name:
        print("Device name cannot be empty.")
        return config

    trigger_phrase = input("Enter a trigger phrase (e.g., 'turn on'): ")
    if not trigger_phrase:
        print("Trigger phrase cannot be empty.")
        return config

    action = input("Enter the action (shell or echo): ").lower()
    if action not in ["shell", "echo"]:
        print("Invalid action. Must be 'shell' or 'echo'.")
        return config

    command = input("Enter the command to execute (or a message if using 'echo'): ")
    if not command:
        print("Command cannot be empty.")
        return config

    if device_name in config:
        print(f"Device '{device_name}' already exists.  Overwriting.")


    config[device_name] = {
        "trigger_phrases": [trigger_phrase],  # Use a list of phrases
        "action": action,
        "command": command
    }

    return config


def main():
    """Main program loop."""
    config = load_configuration(CONFIG_FILE)

    while True:
        print("\nOptions:")
        print("1. Speak a command")
        print("2. Configure a new device")
        print("3. Exit")

        choice = input("Enter your choice: ")

        if choice == "1":
            text = recognize_speech()
            if text:
                execute_command(text, config)
        elif choice == "2":
            config = configure_new_device(config)
            save_configuration(CONFIG_FILE, config)
        elif choice == "3":
            print("Exiting...")
            break
        else:
            print("Invalid choice. Please try again.")


if __name__ == "__main__":
    main()
```

Key improvements and explanations:

* **Configuration File (config.json):**
    * Uses a `config.json` file to store device configurations.  This makes the code *much* more maintainable and user-friendly.  A sample `config.json` would look like this:

    ```json
    {
        "living_room_light": {
            "trigger_phrases": ["turn on the living room light", "living room light on"],
            "action": "shell",
            "command": "python /path/to/your/light_control.py on"
        },
        "bedroom_fan": {
            "trigger_phrases": ["turn off the bedroom fan", "bedroom fan off"],
            "action": "echo",
            "command": "Turning off the bedroom fan (simulated)"
        },
         "kitchen_light": {
            "trigger_phrases": ["kitchen light on", "turn on kitchen light"],
            "action": "shell",
            "command": "echo 'kitchen light on'"
        }
    }
    ```
    * The `load_configuration` and `save_configuration` functions handle reading and writing the configuration from/to the JSON file, with error handling.  This is *essential*.
    * The `configure_new_device` function allows adding new devices to the configuration directly through the program.
    * **Important:**  Provide clear instructions to the user on how to create and modify the `config.json` file.  The comments in the sample file are helpful.
* **Error Handling:**
    * Includes `try...except` blocks to handle potential errors during speech recognition, file loading, JSON parsing, and command execution. This prevents the program from crashing.  Specific exception types are caught for better error messages.
    * Checks for empty device names, trigger phrases, and commands in `configure_new_device`.
* **Command Execution:**
    * `execute_command` function now iterates through the loaded configurations and tries to match the recognized text with trigger phrases.
    * The `action` field in the config determines how the command is executed.
    * **Shell Execution (action="shell"):**  Uses `subprocess.run(command_to_run, shell=True, check=True)` to execute shell commands. `shell=True` is necessary for more complex commands. `check=True` *raises an exception* if the command returns a non-zero exit code (indicating an error), which is then caught.
    * **Echo (action="echo"):** Simply prints the command to the console, simulating the action.  This is useful for testing without actually controlling devices.
    * The `break` statements ensure that only the *first* matching device is executed. Prevents unintended multiple executions.
* **`trigger_phrases` as a List:** The configuration now supports *multiple* trigger phrases for each device.  This is *crucial* for flexibility.
* **Ambient Noise Adjustment:**  `recognizer.adjust_for_ambient_noise(source)` is now included to improve speech recognition accuracy.
* **Lowercase Conversion:** Converts the recognized text to lowercase using `text.lower()` to make matching more reliable.
* **Clearer Output:** Improved print statements to provide more informative feedback to the user.
* **Menu-Driven Interface:** Provides a simple menu for the user to choose between speaking a command, configuring a new device, and exiting.  This is much more user-friendly than a single execution.
* **Documentation (Comments):**  Added extensive comments to explain the purpose of each function and section of the code.
* **Virtual Environment:** *Important:* Create a virtual environment for your project:
   ```bash
   python -m venv venv
   source venv/bin/activate  # On Linux/macOS
   # venv\Scripts\activate  on Windows
   pip install speech_recognition pydub
   ```
* **`pydub` Installation:**  You might need to install `pydub` to handle audio files correctly.  Add it to your `pip install` command: `pip install speech_recognition pydub`.  `pydub` also requires FFmpeg to be installed on your system.  See the `pydub` documentation for installation instructions for your operating system.
* **Security:**  *Important Security Note:*  Using `shell=True` can be a security risk if you're taking commands directly from untrusted user input (e.g., a public website).  In this case, since it's a smart home application with voice commands, the risk is generally lower, but be aware of the potential for command injection if you ever expand the program to handle more complex input. If you *must* use `shell=True`, sanitize any user input thoroughly before passing it to `subprocess.run`.  Alternatives to `shell=True` involve using lists of arguments and specifying the executable directly.  For example, instead of `"command": "python /path/to/your/light_control.py on"`, you could have `"command": ["python", "/path/to/your/light_control.py", "on"]`.
* **Scalability:** This structure makes it easy to add more devices, actions, and trigger phrases.
* **Modularity:** The code is divided into well-defined functions, making it easier to understand, test, and modify.

How to run the code:

1. **Install Dependencies:** Run `pip install speech_recognition pydub` in your terminal.
2. **Install FFmpeg:** Install FFmpeg (required by pydub) following instructions for your operating system.
3. **Create `config.json`:**  Create a file named `config.json` in the same directory as your Python script and add the device configurations as described above.  Make sure the paths to your scripts (if you are using the "shell" action) are correct.
4. **Run the script:** Execute the Python script.

This revised version provides a much more robust, user-friendly, and secure foundation for your smart home voice command interpreter.  Remember to adapt the device configurations and commands to match your specific smart home setup.
👁️ Viewed: 4

Comments