.. include:: /index.rst
:start-after: start_hello_message
:end-before: end_hello_message
.. _py_voice_controlled_fan:
(Example) Voice-Controlled Smart Fan
==================================================
**Introduction**
This project creates an intelligent **Voice-Controlled Smart Fan** that combines speech recognition, AI processing, and motor control. The system allows users to control fan speed using natural voice commands and provides multiple control methods:
1. **Voice Commands** using speech-to-text for hands-free operation
2. **Physical Button** for manual speed adjustment
3. **AI Interpretation** using OpenAI's GPT to understand natural language
4. **Auditory Feedback** with a buzzer for button presses
5. **Dual Control Interface** supporting both voice and physical interaction
.. raw:: html
The smart fan understands commands like "make it faster," "slow down please," or "turn off the fan" and responds with appropriate actions and verbal confirmation.
You can combine various input and output modules to create voice-controlled smart devices. See:
* :ref:`py_online_llm`
* :ref:`py_stt_whisper`
* :ref:`py_motor`
----------------------------------------------
**What You'll need**
The following components are required for this project:
.. list-table::
:widths: 30 20
:header-rows: 1
* - COMPONENT
- PURCHASE LINK
* - :ref:`cpn_motor`
- |link_motor_buy|
* - :ref:`cpn_button`
- |link_button_buy|
* - :ref:`cpn_buzzer`
- \-
* - :ref:`cpn_fusion_hat`
- \-
* - :ref:`cpn_wires`
- |link_wires_buy|
* - Raspberry Pi
- \-
----------------------------------------------
**Wiring Diagram**
Connect the components to the Fusion HAT+ as follows:
.. image:: img/fzz/llm_fan_bb.png
:width: 80%
:align: center
----------------------------------------------
.. include:: python_online_llms.rst
:start-after: start_setup_openai
:end-before: end_setup_openai
----------------------------------------------
**Run the Example**
#. Run the code
.. raw:: html
.. code-block:: shell
cd ~/ai-lab-kit/llm
sudo python3 llm_openai_fan.py
#. Control the fan
You can control the fan using voice commands, the button, or natural language.
* Voice Commands:
- "Make it faster" / "Increase speed" → Sets to maximum (100%)
- "Slow down" / "Reduce speed" → Sets to low (25%)
- "Medium speed please" → Sets to medium (50%)
- "Turn off" / "Stop" → Stops motor (0%)
- "What's the current speed?" → Reports current speed
- "Make it cooler" → Interprets as a request for higher speed
* Button Control:
- Each press increases speed by 10%
- At 100%, the next press cycles back to 0%
- An audible beep confirms each press
- The current speed percentage is displayed on screen
* Natural Language Understanding:
The AI can also understand variations such as:
- "I'm feeling hot, can you make it faster?"
- "Could you please turn the fan down a bit?"
- "It's too windy in here!"
- "Set it to half speed"
--------
**Code**
Here is the full Python script for the Voice-Controlled Smart Fan:
.. raw:: html
.. code-block:: python
from fusion_hat.llm import OpenAI
from secret import OPENAI_API_KEY
from fusion_hat.motor import Motor
from fusion_hat.modules import Buzzer
from fusion_hat.pin import Pin
import random, time
from fusion_hat.stt import STT
# Initialize Speech-to-Text with English language
stt = STT(language="en-us")
# Initialize motor on port M0
motor = Motor('M0')
# Initialize button on GPIO 17 with pull-up and debounce
button = Pin(17, mode=Pin.IN, pull=Pin.PULL_UP, bounce_time=0.05)
# Initialize buzzer on GPIO 4
buzzer = Buzzer(Pin(4))
# Global speed variable (0-100%)
speed = 0
# Function for auditory feedback
def beep():
buzzer.on()
time.sleep(0.1)
buzzer.off()
# Debounce variables for button
last_triggered = 0
# Button callback function
def speed_up():
global speed, last_triggered
# Debounce: ignore if pressed within 500ms
if time.time() - last_triggered < 0.5:
return
last_triggered = time.time()
# Increase speed by 10%
speed += 10
# Wrap around at 100% (go back to 0)
if speed > 100:
motor.stop()
speed = 0
else:
motor.power(speed)
# Auditory feedback
beep()
# Print current speed
print(f"Speed set to: {speed}%")
# Attach callback to button
button.when_activated = speed_up
# Function to parse natural language response and set appropriate speed
def parse_response_for_speed(text_response):
"""
Parse the LLM's natural language response to determine speed setting.
Looks for keywords related to different speed levels.
Returns the speed level to set (100, 50, 25, or 0)
"""
text_lower = text_response.lower()
# Check for "stop" or "off" keywords - highest priority
if any(word in text_lower for word in ['stop', 'off', 'zero', '0%', 'turn off', 'shut off', 'halt']):
return 0
# Check for "slow" or "low" keywords
if any(word in text_lower for word in ['slow', 'low', '25%', 'quarter', 'minimum', 'gentle']):
return 25
# Check for "medium" or "half" keywords
if any(word in text_lower for word in ['medium', 'half', '50%', 'moderate', 'normal']):
return 50
# Check for "fast" or "high" or "full" keywords
if any(word in text_lower for word in ['fast', 'high', 'full', '100%', 'maximum', 'top']):
return 100
# If no specific keywords found, return -1 to indicate no speed change
return -1
# Setup LLM with specific instructions for fan control
INSTRUCTIONS = '''
You are a fan control assistant. Your task is to interpret the user's speech input and respond with natural language.
### Input Format:
The user will speak their command for fan control.
### CRITICAL RULES:
1. **BE DECISIVE**: Always take clear action based on user requests. Do NOT ask follow-up questions.
2. **NO CLARIFICATION QUESTIONS**: Never ask "Would you like me to..." or "Should I..." questions.
3. **ASSUME INTENT**: If the user's request is ambiguous, make a reasonable assumption and take action.
4. **CONFIRM ACTION**: Always state what action you are taking in your response.
### Response Guidelines:
1. Respond naturally and conversationally to the user's request.
2. Acknowledge what the user asked for.
3. Use clear language about what action you're taking.
4. Use keywords in your response that indicate speed levels:
- For maximum speed: use words like "fast", "high", "full speed", "maximum"
- For medium speed: use words like "medium", "half speed", "50%"
- For low speed: use words like "slow", "low", "quarter speed", "25%"
- For stopping: use words like "stop", "off", "zero", "turning off"
5. If the user asks about current status, respond with helpful information.
### Example Responses:
**When asked to go fast:**
"I'll set the fan to maximum speed for you. Full speed activated!"
**When asked to slow down:**
"Reducing the fan speed to low. Enjoy the gentle breeze."
**When asked for medium speed:**
"Setting the fan to medium speed. This should be comfortable."
**When asked to stop:**
"Stopping the fan now. The motor is turned off."
**When asked about status:**
"Your fan is currently at 50% speed. Would you like me to adjust it?"
'''
WELCOME = "Hello, I am a fan control assistant. You can ask me to set the fan to fast, medium, slow, or stop it completely. You can also press the button to increase the speed by 10% or decrease it by 10%. If you ask about the current status, I will tell you the current speed. If you don't know what to do, you can ask me for instructions. Good luck!"
# Initialize OpenAI LLM
llm = OpenAI(
api_key=OPENAI_API_KEY,
model="gpt-4o",
)
# Set how many messages to keep
llm.set_max_messages(20)
# Set instructions
llm.set_instructions(INSTRUCTIONS)
# Set welcome message
llm.set_welcome(WELCOME)
print(WELCOME)
# Main loop for voice control
while True:
print("Say something")
# Listen for speech input
for result in stt.listen(stream=True):
if result["done"]:
# Print final recognized text
print(f"\r\x1b[Kfinal: {result['final']}")
# Get the recognized speech
input_text = result['final']
# Add current speed context to the input
contextual_input = f"Current speed is {speed}%. User says: {input_text}"
# Get response from LLM
response = llm.prompt(contextual_input, stream=True)
# Collect the full response
full_response = ""
for next_word in response:
if next_word:
print(next_word, end="", flush=True)
full_response += next_word
print("\n") # Add newline after response
# Parse the response to determine speed setting
new_speed = parse_response_for_speed(full_response)
# Apply speed change if detected
if new_speed >= 0:
speed = new_speed
motor.power(speed)
print(f"Speed set to: {speed}%")
else:
print("No speed change detected in response")
else:
# Print partial recognition results
print(f"\r\x1b[Kpartial: {result['partial']}", end="", flush=True)
----------------------------------------------
**Understanding the Code**
1. Speech-to-Text Initialization
The system uses STT (Speech-to-Text) for voice recognition:
.. code-block:: python
stt = STT(language="en-us")
for result in stt.listen(stream=True):
if result["done"]:
input_text = result['final']
else:
print(f"partial: {result['partial']}")
This provides real-time speech recognition with partial results as you speak.
2. Motor Control Setup
The fan motor is controlled via PWM on port M0:
.. code-block:: python
motor = Motor('M0')
# Set speed as percentage (0-100)
motor.power(speed)
# Stop the motor completely
motor.stop()
3. Button with Debounce
The button includes debounce to prevent multiple triggers:
.. code-block:: python
button = Pin(17, mode=Pin.IN, pull=Pin.PULL_UP, bounce_time=0.05)
last_triggered = 0
def speed_up():
global speed, last_triggered
if time.time() - last_triggered < 0.5: # 500ms debounce
return
last_triggered = time.time()
4. Auditory Feedback
A buzzer provides audible confirmation:
.. code-block:: python
buzzer = Buzzer(Pin(4))
def beep():
buzzer.on()
time.sleep(0.1)
buzzer.off()
5. Keyword Parsing Function
The system parses AI responses for speed commands:
.. code-block:: python
def parse_response_for_speed(text_response):
text_lower = text_response.lower()
# Check for "stop" or "off" keywords
if any(word in text_lower for word in ['stop', 'off', 'zero']):
return 0
# Check for "slow" or "low" keywords
if any(word in text_lower for word in ['slow', 'low', '25%']):
return 25
# Similar checks for medium and fast
return -1 # No speed change
6. Contextual Input to AI
The current speed is included in the prompt for context-aware responses:
.. code-block:: python
contextual_input = f"Current speed is {speed}%. User says: {input_text}"
response = llm.prompt(contextual_input, stream=True)
7. Streaming Response Processing
AI responses are processed word-by-word:
.. code-block:: python
full_response = ""
for next_word in response:
if next_word:
print(next_word, end="", flush=True)
full_response += next_word
8. Dual Control Logic
The system supports both voice and button control:
.. code-block:: python
# Voice control in main loop
new_speed = parse_response_for_speed(full_response)
if new_speed >= 0:
speed = new_speed
motor.power(speed)
# Button control via callback
def speed_up():
speed += 10
if speed > 100:
speed = 0
motor.power(speed)
9. Clear Terminal Output
Uses ANSI escape codes for clean console display:
.. code-block:: python
print(f"\r\x1b[Kpartial: {result['partial']}", end="", flush=True)
- ``\r``: Carriage return (go to start of line)
- ``\x1b[K``: Clear from cursor to end of line
- ``end=""``: No newline
- ``flush=True``: Immediate display
10. Intelligent AI Instructions
The AI is specifically instructed to be decisive and avoid clarification questions:
.. code-block:: python
INSTRUCTIONS = '''
CRITICAL RULES:
1. BE DECISIVE: Always take clear action based on user requests.
2. NO CLARIFICATION QUESTIONS: Never ask "Would you like me to..." questions.
3. ASSUME INTENT: If ambiguous, make reasonable assumption and take action.
4. CONFIRM ACTION: Always state what action you are taking.
'''
----------------------------------------------
**Troubleshooting**
- Motor not spinning
- Verify motor connections: M0 port, correct polarity
- Test motor directly: ``motor.power(50)`` should spin at 50%
- Ensure speed variable is being set (0-100 range)
- Button not responding
- Check wiring: GPIO 17 to button, other side to 3.3V
- Verify pull-up configuration
- Test with simple script: print when button state changes
- Check debounce time (0.5 seconds may be too long)
- No buzzer sound
- Test buzzer directly: ``buzzer.on()`` should produce continuous tone
- Check if buzzer is piezo (needs PWM) or active (works with DC)
- AI not understanding commands
- Check API key in ``secret.py``
- Verify internet connection
- Examine AI instructions: ensure they're properly formatted
- Test with simpler commands first
- Speed changes unexpectedly
- Check button debounce: may be triggering multiple times
- Verify keyword parsing: some phrases may trigger unintended speeds
- Add print statements to trace speed changes
- Poor speech recognition accuracy
- Reduce background noise
- Speak clearly and at moderate pace
- Consider using external USB microphone for better quality
- Adjust STT parameters if available
- Motor makes noise but doesn't spin
- Check if motor is stuck or blocked
- Verify power supply voltage matches motor requirements
- Some motors need capacitor across terminals for smooth operation
----------------------------------------------
This voice-controlled fan demonstrates how natural language processing, physical controls, and intelligent systems can create intuitive and accessible smart home devices that respond to human needs and preferences!