298 lines
8.7 KiB
Python
298 lines
8.7 KiB
Python
"""
|
|
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()
|