Claude generated comprehensive GPIO handling.... supposedly

This commit is contained in:
Marc Boivin 2025-06-25 07:51:44 -04:00
parent 087abca062
commit 21405819f0
3 changed files with 146 additions and 4 deletions

View File

@ -145,6 +145,19 @@ sudo usermod -a -G gpio $USER
# Then log out and back in # Then log out and back in
``` ```
**GPIO pin conflicts:**
```bash
# Show safe GPIO pins and current configuration
uv run python run.py gpio-info
# Show pins during GPIO service startup
uv run python run.py gpio --show-pins
```
**Safe GPIO pins for buttons:** 4, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27
**Avoid these pins:** 5, 6, 7, 9 (SPI), 14, 15 (UART), 2, 3 (I2C)
**Form validation errors (422):** **Form validation errors (422):**
- Check FastAPI endpoint parameter types - Check FastAPI endpoint parameter types
- Handle empty strings vs None for optional fields - Handle empty strings vs None for optional fields

View File

@ -36,6 +36,13 @@ def main():
choices=['DEBUG', 'INFO', 'WARNING', 'ERROR'], choices=['DEBUG', 'INFO', 'WARNING', 'ERROR'],
help='Logging level') help='Logging level')
gpio_parser.add_argument('--log-file', help='Log file path') gpio_parser.add_argument('--log-file', help='Log file path')
gpio_parser.add_argument('--show-pins', action='store_true', help='Show GPIO pin information and exit')
# GPIO info command
gpio_info_parser = subparsers.add_parser('gpio-info', help='Show GPIO pin information')
gpio_info_parser.add_argument('--log-level', default='INFO',
choices=['DEBUG', 'INFO', 'WARNING', 'ERROR'],
help='Logging level')
# Database management commands # Database management commands
db_parser = subparsers.add_parser('db', help='Database management') db_parser = subparsers.add_parser('db', help='Database management')
@ -68,6 +75,8 @@ def main():
return run_web_server(args) return run_web_server(args)
elif args.command == 'gpio': elif args.command == 'gpio':
return asyncio.run(run_gpio_service(args)) return asyncio.run(run_gpio_service(args))
elif args.command == 'gpio-info':
return show_gpio_info(args)
elif args.command == 'db': elif args.command == 'db':
return run_db_command(args) return run_db_command(args)
else: else:
@ -103,10 +112,17 @@ def run_web_server(args):
async def run_gpio_service(args): async def run_gpio_service(args):
"""Run the GPIO control service""" """Run the GPIO control service"""
from .gpio import ECMController from .gpio import ECMController, ButtonManager
from .utils.logging_config import configure_logging_from_settings from .utils.logging_config import configure_logging_from_settings
logger = get_logger('gpio') logger = get_logger('gpio')
# Handle --show-pins option
if hasattr(args, 'show_pins') and args.show_pins:
button_manager = ButtonManager()
print(button_manager.get_safe_pins_info())
return 0
logger.info("Starting GPIO control service") logger.info("Starting GPIO control service")
# Configure logging from database settings # Configure logging from database settings
@ -126,6 +142,40 @@ async def run_gpio_service(args):
return 0 return 0
def show_gpio_info(args):
"""Show GPIO pin information"""
from .gpio import ButtonManager
button_manager = ButtonManager()
print(button_manager.get_safe_pins_info())
# Also show current button configuration from database
try:
db = DatabaseManager()
buttons = db.execute_query("SELECT * FROM buttons ORDER BY name")
if buttons:
print("\nCurrent Button Configuration:")
for button in buttons:
status = "✅ SAFE" if button['gpio_pin'] in button_manager.SAFE_GPIO_PINS else "⚠️ RISKY"
recipe_info = ""
if button['recipe_id']:
recipe = db.execute_query("SELECT name FROM recipes WHERE id = ?", (button['recipe_id'],))
if recipe:
recipe_info = f"{recipe[0]['name']}"
print(f" Button {button['id']}: {button['name']} on GPIO {button['gpio_pin']} {status}{recipe_info}")
if button['gpio_pin'] in button_manager.RESERVED_GPIO_PINS:
print(f" ⚠️ Warning: {button_manager.RESERVED_GPIO_PINS[button['gpio_pin']]}")
else:
print("\nNo buttons configured yet.")
except Exception as e:
print(f"\nError reading button configuration: {e}")
return 0
def run_db_command(args): def run_db_command(args):
"""Run database management commands""" """Run database management commands"""
logger = get_logger('db') logger = get_logger('db')

View File

@ -96,6 +96,29 @@ class CoffeeMachineController:
class ButtonManager: class ButtonManager:
"""Manages physical buttons and their GPIO pins""" """Manages physical buttons and their GPIO pins"""
# GPIO pins that are typically safe to use for input buttons on Raspberry Pi
SAFE_GPIO_PINS = [4, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27]
# GPIO pins that are often reserved or have special functions
RESERVED_GPIO_PINS = {
0: "I2C ID EEPROM - SDA",
1: "I2C ID EEPROM - SCL",
2: "I2C1 SDA",
3: "I2C1 SCL",
5: "SPI0 CE1 (may be reserved)",
6: "SPI0 CE2 (may be reserved)",
7: "SPI0 CE1 (may be reserved)",
8: "SPI0 CE0",
9: "SPI0 MISO",
10: "SPI0 MOSI",
11: "SPI0 SCLK",
12: "PWM0 (PCM_CLK)",
13: "PWM1 (PCM_FS)",
14: "UART TXD",
15: "UART RXD",
16: "SPI1 CE2"
}
def __init__(self, use_mock: bool = False): def __init__(self, use_mock: bool = False):
self.use_mock = use_mock self.use_mock = use_mock
self.buttons: Dict[int, GPIOButton | MockGPIO] = {} self.buttons: Dict[int, GPIOButton | MockGPIO] = {}
@ -103,26 +126,76 @@ class ButtonManager:
def add_button(self, button_id: int, gpio_pin: int, handler: Callable): def add_button(self, button_id: int, gpio_pin: int, handler: Callable):
"""Add a button with its handler""" """Add a button with its handler"""
# Validate GPIO pin
if not self.use_mock and not self._validate_gpio_pin(gpio_pin):
logger.warning(f"GPIO pin {gpio_pin} may not be suitable for button input")
try: try:
if self.use_mock: if self.use_mock:
button = MockGPIO(gpio_pin) button = MockGPIO(gpio_pin)
logger.info(f"Button {button_id} added on GPIO {gpio_pin} (MOCK)")
else: else:
# Try with pull_up first
button = GPIOButton(gpio_pin, pull_up=True, bounce_time=0.2) button = GPIOButton(gpio_pin, pull_up=True, bounce_time=0.2)
logger.info(f"Button {button_id} added on GPIO {gpio_pin} (HARDWARE)")
button.when_pressed = lambda: handler(button_id) button.when_pressed = lambda: handler(button_id)
self.buttons[button_id] = button self.buttons[button_id] = button
self.button_handlers[button_id] = handler self.button_handlers[button_id] = handler
logger.info(f"Button {button_id} added on GPIO {gpio_pin}")
except Exception as e: except Exception as e:
logger.error(f"Failed to add button {button_id} on GPIO {gpio_pin}: {e}") logger.error(f"Failed to add button {button_id} on GPIO {gpio_pin}: {e}")
# Fall back to mock
# Provide helpful error information
if gpio_pin in self.RESERVED_GPIO_PINS:
logger.error(f"GPIO {gpio_pin} is typically reserved for: {self.RESERVED_GPIO_PINS[gpio_pin]}")
logger.error(f"Consider using one of these safer GPIO pins: {self.SAFE_GPIO_PINS}")
# Try without pull_up as fallback
if not self.use_mock:
try:
logger.info(f"Retrying GPIO {gpio_pin} without internal pull-up (external pull-up required)")
button = GPIOButton(gpio_pin, pull_up=False, bounce_time=0.2)
button.when_pressed = lambda: handler(button_id)
self.buttons[button_id] = button
self.button_handlers[button_id] = handler
logger.info(f"Button {button_id} added on GPIO {gpio_pin} (no pull-up)")
return
except Exception as e2:
logger.error(f"Retry also failed for GPIO {gpio_pin}: {e2}")
# Final fallback to mock
logger.warning(f"Falling back to mock button for button {button_id}")
button = MockGPIO(gpio_pin) button = MockGPIO(gpio_pin)
button.when_pressed = lambda: handler(button_id) button.when_pressed = lambda: handler(button_id)
self.buttons[button_id] = button self.buttons[button_id] = button
self.button_handlers[button_id] = handler self.button_handlers[button_id] = handler
def _validate_gpio_pin(self, gpio_pin: int) -> bool:
"""Validate if a GPIO pin is suitable for button input"""
if gpio_pin in self.RESERVED_GPIO_PINS:
return False
if gpio_pin not in range(0, 28): # Valid GPIO range for most Pi models
return False
return True
def get_safe_pins_info(self) -> str:
"""Get information about safe GPIO pins to use"""
safe_pins = ", ".join(map(str, self.SAFE_GPIO_PINS))
reserved_info = "\n".join([f" GPIO {pin}: {desc}" for pin, desc in self.RESERVED_GPIO_PINS.items()])
return f"""
GPIO Pin Recommendations:
Safe pins for buttons: {safe_pins}
Reserved/Special function pins to avoid:
{reserved_info}
Note: Always check your specific Raspberry Pi model documentation.
External pull-up resistors (10) are recommended for reliable button operation.
"""
def remove_button(self, button_id: int): def remove_button(self, button_id: int):
"""Remove a button""" """Remove a button"""
if button_id in self.buttons: if button_id in self.buttons:
@ -151,6 +224,12 @@ class ECMController:
"""Initialize the controller""" """Initialize the controller"""
logger.info("Initializing ECM Controller...") logger.info("Initializing ECM Controller...")
# Show GPIO pin information for troubleshooting
if not self.use_mock:
logger.info("GPIO Pin Information:")
logger.info(f"Safe GPIO pins for buttons: {self.button_manager.SAFE_GPIO_PINS}")
logger.info("If you're getting GPIO errors, consider updating your button GPIO pins in the web interface")
# Initialize scale # Initialize scale
scale_address = self._get_setting('scale_address', '') scale_address = self._get_setting('scale_address', '')
if scale_address: if scale_address: