initial project setup

This commit is contained in:
glenn schrooyen
2025-09-11 22:08:12 +02:00
parent 8cc588dc92
commit 21e4972ab1
46 changed files with 2755 additions and 1 deletions

View File

@@ -0,0 +1,21 @@
FROM python:3.11-slim
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y \
gcc \
&& rm -rf /var/lib/apt/lists/*
# Copy requirements and install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy application code
COPY . .
# Expose port
EXPOSE 8000
# Run the application
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]

View File

@@ -0,0 +1,21 @@
FROM python:3.11-slim
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y \
gcc \
&& rm -rf /var/lib/apt/lists/*
# Copy requirements and install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy application code
COPY . .
# Expose port
EXPOSE 8000
# Run in development mode with hot reload
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]

View File

@@ -0,0 +1,25 @@
# Service Adapters
Python FastAPI service for integrating with external homelab services.
## Purpose
- Integrate with Home Assistant, Frigate, Immich, n8n
- Transform external service data into standardized format
- Publish events to the message bus
- Provide unified API for service data
## Technology Stack
- **Language**: Python 3.11
- **Framework**: FastAPI
- **Port**: 8000
- **Message Bus**: Redis
## Features
- Home Assistant entity integration
- Frigate event processing
- Immich asset management
- n8n workflow triggers
- Event publishing to Redis
## Development Status
**Complete** - Core functionality implemented

View File

@@ -0,0 +1,171 @@
from fastapi import FastAPI, HTTPException, BackgroundTasks
from fastapi.middleware.cors import CORSMiddleware
import asyncio
import redis
import json
from datetime import datetime
import os
from dotenv import load_dotenv
# Load environment variables
load_dotenv()
app = FastAPI(
title="LabFusion Service Adapters",
description="Service integration adapters for Home Assistant, Frigate, Immich, and other homelab services",
version="1.0.0"
)
# CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Redis connection
redis_client = redis.Redis(
host=os.getenv("REDIS_HOST", "localhost"),
port=int(os.getenv("REDIS_PORT", 6379)),
decode_responses=True
)
# Service configurations
SERVICES = {
"home_assistant": {
"url": os.getenv("HOME_ASSISTANT_URL", "https://homeassistant.local:8123"),
"token": os.getenv("HOME_ASSISTANT_TOKEN", ""),
"enabled": bool(os.getenv("HOME_ASSISTANT_TOKEN"))
},
"frigate": {
"url": os.getenv("FRIGATE_URL", "http://frigate.local:5000"),
"token": os.getenv("FRIGATE_TOKEN", ""),
"enabled": bool(os.getenv("FRIGATE_TOKEN"))
},
"immich": {
"url": os.getenv("IMMICH_URL", "http://immich.local:2283"),
"api_key": os.getenv("IMMICH_API_KEY", ""),
"enabled": bool(os.getenv("IMMICH_API_KEY"))
},
"n8n": {
"url": os.getenv("N8N_URL", "http://n8n.local:5678"),
"webhook_url": os.getenv("N8N_WEBHOOK_URL", ""),
"enabled": bool(os.getenv("N8N_WEBHOOK_URL"))
}
}
@app.get("/")
async def root():
return {"message": "LabFusion Service Adapters API", "version": "1.0.0"}
@app.get("/health")
async def health_check():
return {"status": "healthy", "timestamp": datetime.now().isoformat()}
@app.get("/services")
async def get_services():
"""Get status of all configured services"""
service_status = {}
for service_name, config in SERVICES.items():
service_status[service_name] = {
"enabled": config["enabled"],
"url": config["url"],
"status": "unknown" # Would check actual service status
}
return service_status
@app.get("/home-assistant/entities")
async def get_ha_entities():
"""Get Home Assistant entities"""
if not SERVICES["home_assistant"]["enabled"]:
raise HTTPException(status_code=503, detail="Home Assistant integration not configured")
# This would make actual API calls to Home Assistant
# For now, return mock data
return {
"entities": [
{
"entity_id": "sensor.cpu_usage",
"state": "45.2",
"attributes": {"unit_of_measurement": "%", "friendly_name": "CPU Usage"}
},
{
"entity_id": "sensor.memory_usage",
"state": "2.1",
"attributes": {"unit_of_measurement": "GB", "friendly_name": "Memory Usage"}
}
]
}
@app.get("/frigate/events")
async def get_frigate_events():
"""Get Frigate detection events"""
if not SERVICES["frigate"]["enabled"]:
raise HTTPException(status_code=503, detail="Frigate integration not configured")
# This would make actual API calls to Frigate
# For now, return mock data
return {
"events": [
{
"id": "event_123",
"timestamp": datetime.now().isoformat(),
"camera": "front_door",
"label": "person",
"confidence": 0.95
}
]
}
@app.get("/immich/assets")
async def get_immich_assets():
"""Get Immich photo assets"""
if not SERVICES["immich"]["enabled"]:
raise HTTPException(status_code=503, detail="Immich integration not configured")
# This would make actual API calls to Immich
# For now, return mock data
return {
"assets": [
{
"id": "asset_123",
"filename": "photo_001.jpg",
"created_at": datetime.now().isoformat(),
"tags": ["person", "outdoor"],
"faces": ["Alice", "Bob"]
}
]
}
@app.post("/publish-event")
async def publish_event(event_data: dict, background_tasks: BackgroundTasks):
"""Publish an event to the message bus"""
try:
event = {
"timestamp": datetime.now().isoformat(),
"service": event_data.get("service", "unknown"),
"event_type": event_data.get("event_type", "unknown"),
"metadata": json.dumps(event_data.get("metadata", {}))
}
# Publish to Redis
redis_client.lpush("events", json.dumps(event))
return {"status": "published", "event": event}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.get("/events")
async def get_events(limit: int = 100):
"""Get recent events from the message bus"""
try:
events = redis_client.lrange("events", 0, limit - 1)
return {"events": [json.loads(event) for event in events]}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)

View File

@@ -0,0 +1,14 @@
fastapi==0.104.1
uvicorn[standard]==0.24.0
pydantic==2.5.0
httpx==0.25.2
redis==5.0.1
psycopg2-binary==2.9.9
sqlalchemy==2.0.23
alembic==1.13.1
python-multipart==0.0.6
python-jose[cryptography]==3.3.0
passlib[bcrypt]==1.7.4
python-dotenv==1.0.0
websockets==12.0
aiofiles==23.2.1