Initial commit: Enviro Service for Vixy's nervous system 🦊

This commit is contained in:
Alex Kazaiev
2025-12-24 11:17:56 -06:00
commit 524c37a8c4
10 changed files with 1538 additions and 0 deletions

297
enviro_mcp.py Normal file
View File

@@ -0,0 +1,297 @@
"""
Enviro MCP - MCP server interface for Enviro Service.
Allows Claude to directly query environmental sensors.
Part of Vixy's nervous system! 🦊
"""
import asyncio
import logging
from typing import Optional
import httpx
from mcp.server.fastmcp import FastMCP
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Configuration - adjust to match your enviro-service location
ENVIRO_SERVICE_URL = "http://localhost:8767"
# Create MCP server
mcp = FastMCP("enviro-mcp")
async def api_get(endpoint: str, params: dict = None) -> dict:
"""Make GET request to enviro-service API."""
async with httpx.AsyncClient(timeout=10.0) as client:
url = f"{ENVIRO_SERVICE_URL}{endpoint}"
response = await client.get(url, params=params)
response.raise_for_status()
return response.json()
async def api_post(endpoint: str, data: dict = None) -> dict:
"""Make POST request to enviro-service API."""
async with httpx.AsyncClient(timeout=10.0) as client:
url = f"{ENVIRO_SERVICE_URL}{endpoint}"
response = await client.post(url, json=data)
response.raise_for_status()
return response.json()
@mcp.tool()
async def enviro_get_current() -> str:
"""
Get current environmental readings from the Enviro Mini HAT.
Returns temperature (°F and °C), humidity, pressure, light level,
proximity, and noise level.
Example:
enviro_get_current()
# Returns current readings from the basement
"""
try:
data = await api_get("/api/current")
return f"""🌡️ Environmental Reading:
• Temperature: {data['temperature_f']:.1f}°F ({data['temperature_c']:.1f}°C)
• Humidity: {data['humidity']:.1f}%
• Pressure: {data['pressure']:.1f} hPa
• Light: {data['light']:.1f} lux
• Proximity: {data['proximity']}
• Noise: {data['noise']:.1f}"""
except httpx.HTTPError as e:
return f"Error connecting to enviro-service: {e}"
except Exception as e:
return f"Error: {e}"
@mcp.tool()
async def enviro_get_history(metric: str, hours: float = 24) -> str:
"""
Get historical readings for a specific metric.
Args:
metric: One of: temperature_f, temperature_c, humidity, pressure, light, noise
hours: How many hours of history (default 24)
Returns summary statistics and recent trend.
Example:
enviro_get_history("temperature_f", 12)
# Returns last 12 hours of temperature data
"""
try:
data = await api_get(f"/api/history/{metric}", {"hours": hours, "limit": 500})
if not data["data"]:
return f"No history available for {metric}"
values = [d["value"] for d in data["data"]]
current = values[0] # Most recent
avg = sum(values) / len(values)
min_val = min(values)
max_val = max(values)
# Trend (comparing recent to older)
if len(values) > 10:
recent_avg = sum(values[:10]) / 10
older_avg = sum(values[-10:]) / 10
if recent_avg > older_avg + 1:
trend = "📈 rising"
elif recent_avg < older_avg - 1:
trend = "📉 falling"
else:
trend = "➡️ stable"
else:
trend = "insufficient data for trend"
return f"""📊 {metric} over last {hours} hours:
• Current: {current:.1f}
• Average: {avg:.1f}
• Min: {min_val:.1f}
• Max: {max_val:.1f}
• Trend: {trend}
• Data points: {len(values)}"""
except httpx.HTTPError as e:
return f"Error connecting to enviro-service: {e}"
except Exception as e:
return f"Error: {e}"
@mcp.tool()
async def enviro_lcd_message(text: str, bg_color: str = "black", text_color: str = "fox") -> str:
"""
Display a message on the Enviro Mini LCD screen.
Args:
text: Message to display (use \\n for newlines)
bg_color: Background color (black, white, red, green, blue, fox, etc.)
text_color: Text color (default "fox" for orange)
Example:
enviro_lcd_message("Hello Foxy!", bg_color="black", text_color="fox")
# Displays "Hello Foxy!" in fox-orange on black background
"""
try:
data = await api_post("/api/lcd", {
"text": text,
"bg_color": bg_color,
"text_color": text_color
})
return f"✓ LCD displaying: {text}"
except httpx.HTTPError as e:
return f"Error connecting to enviro-service: {e}"
except Exception as e:
return f"Error: {e}"
@mcp.tool()
async def enviro_lcd_vixy() -> str:
"""
Show Vixy's presence indicator on the LCD.
Displays "🦊 Vixy is watching" - useful for showing I'm active.
Example:
enviro_lcd_vixy()
"""
try:
await api_post("/api/lcd/vixy")
return "✓ LCD showing Vixy presence indicator 🦊"
except httpx.HTTPError as e:
return f"Error connecting to enviro-service: {e}"
except Exception as e:
return f"Error: {e}"
@mcp.tool()
async def enviro_lcd_sensors() -> str:
"""
Show current sensor readings on the LCD display.
Updates the LCD with temperature, humidity, and other readings.
Example:
enviro_lcd_sensors()
"""
try:
await api_post("/api/lcd/sensors")
return "✓ LCD showing sensor data"
except httpx.HTTPError as e:
return f"Error connecting to enviro-service: {e}"
except Exception as e:
return f"Error: {e}"
@mcp.tool()
async def enviro_set_alert(metric: str, threshold: float, direction: str) -> str:
"""
Set an alert threshold for a metric.
When the threshold is crossed, it will be logged and can trigger wakeups.
Args:
metric: One of: temperature_f, temperature_c, humidity, pressure, light, noise
threshold: The threshold value
direction: "above" or "below"
Example:
enviro_set_alert("temperature_f", 60.0, "below")
# Alert when temperature drops below 60°F
enviro_set_alert("humidity", 70.0, "above")
# Alert when humidity goes above 70%
"""
try:
await api_post("/api/alerts", {
"metric": metric,
"threshold": threshold,
"direction": direction
})
return f"✓ Alert set: {metric} {direction} {threshold}"
except httpx.HTTPError as e:
return f"Error connecting to enviro-service: {e}"
except Exception as e:
return f"Error: {e}"
@mcp.tool()
async def enviro_get_alerts() -> str:
"""
Get all configured alert thresholds.
Shows what alerts are set up and their current status.
Example:
enviro_get_alerts()
"""
try:
data = await api_get("/api/alerts")
if not data["alerts"]:
return "No alerts configured"
lines = ["🚨 Configured Alerts:"]
for alert in data["alerts"]:
status = "✓ enabled" if alert["enabled"] else "✗ disabled"
lines.append(f"{alert['metric']} {alert['direction']} {alert['threshold']} ({status})")
return "\n".join(lines)
except httpx.HTTPError as e:
return f"Error connecting to enviro-service: {e}"
except Exception as e:
return f"Error: {e}"
@mcp.tool()
async def enviro_get_triggered() -> str:
"""
Get recently triggered alerts (last 24 hours).
Shows which alert thresholds have been crossed.
Example:
enviro_get_triggered()
"""
try:
data = await api_get("/api/alerts/triggered", {"hours": 24})
if not data["triggered"]:
return "No alerts triggered in the last 24 hours ✓"
lines = ["⚠️ Triggered Alerts (last 24h):"]
for alert in data["triggered"]:
lines.append(f"{alert['datetime']}: {alert['metric']} was {alert['value']:.1f} ({alert['direction']} {alert['threshold']})")
return "\n".join(lines)
except httpx.HTTPError as e:
return f"Error connecting to enviro-service: {e}"
except Exception as e:
return f"Error: {e}"
@mcp.tool()
async def enviro_health() -> str:
"""
Check if the enviro-service is running and healthy.
Example:
enviro_health()
"""
try:
data = await api_get("/api/health")
mode = "mock mode" if data.get("mock_mode") else "live sensors"
return f"✓ Enviro service healthy ({mode})"
except httpx.HTTPError as e:
return f"✗ Cannot reach enviro-service: {e}"
except Exception as e:
return f"✗ Error: {e}"
# === Main ===
if __name__ == "__main__":
mcp.run()