Added event clearing and source tracking
- Added clear events endpoint that preserves latest gate status - Added source tracking (api/mqtt) for gate triggers - Added clear events button to frontend - Updated MQTT handler to use mqtt source
This commit is contained in:
parent
b372618cf7
commit
9ff21046d7
|
|
@ -251,7 +251,7 @@ app.add_middleware(
|
||||||
)
|
)
|
||||||
|
|
||||||
# Gate control
|
# Gate control
|
||||||
async def trigger_gate():
|
async def trigger_gate(source: str = "api"):
|
||||||
"""Trigger the gate relay"""
|
"""Trigger the gate relay"""
|
||||||
try:
|
try:
|
||||||
settings = app.state.current_settings
|
settings = app.state.current_settings
|
||||||
|
|
@ -268,7 +268,7 @@ async def trigger_gate():
|
||||||
|
|
||||||
# Log event
|
# Log event
|
||||||
timestamp = datetime.now().isoformat()
|
timestamp = datetime.now().isoformat()
|
||||||
await add_event(timestamp, "gate triggered", "api")
|
await add_event(timestamp, "gate triggered", source)
|
||||||
|
|
||||||
# Wait for specified duration
|
# Wait for specified duration
|
||||||
await asyncio.sleep(trigger_duration)
|
await asyncio.sleep(trigger_duration)
|
||||||
|
|
@ -282,7 +282,7 @@ async def trigger_gate():
|
||||||
logger.error(f"Failed to trigger gate: {e}", exc_info=True)
|
logger.error(f"Failed to trigger gate: {e}", exc_info=True)
|
||||||
# Log failure
|
# Log failure
|
||||||
timestamp = datetime.now().isoformat()
|
timestamp = datetime.now().isoformat()
|
||||||
await add_event(timestamp, "gate trigger failed", "api", False)
|
await add_event(timestamp, "gate trigger failed", source, False)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
last_open_time = None
|
last_open_time = None
|
||||||
|
|
@ -437,6 +437,10 @@ async def cleanup_old_events():
|
||||||
"DELETE FROM events WHERE timestamp < ?",
|
"DELETE FROM events WHERE timestamp < ?",
|
||||||
(cutoff_date,)
|
(cutoff_date,)
|
||||||
)
|
)
|
||||||
|
await db.execute(
|
||||||
|
"DELETE FROM gate_status WHERE timestamp < ?",
|
||||||
|
(cutoff_date,)
|
||||||
|
)
|
||||||
await db.commit()
|
await db.commit()
|
||||||
logger.info(f"Cleaned up {count} events older than {cutoff_date}")
|
logger.info(f"Cleaned up {count} events older than {cutoff_date}")
|
||||||
|
|
||||||
|
|
@ -462,7 +466,7 @@ async def handle_mqtt_command(should_open: bool):
|
||||||
|
|
||||||
status_pin = settings.gpio.statusPin
|
status_pin = settings.gpio.statusPin
|
||||||
if should_open != (GPIO.input(status_pin) == GPIO.HIGH):
|
if should_open != (GPIO.input(status_pin) == GPIO.HIGH):
|
||||||
await trigger_gate()
|
await trigger_gate("mqtt")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error handling MQTT command: {e}")
|
logger.error(f"Error handling MQTT command: {e}")
|
||||||
|
|
||||||
|
|
@ -471,17 +475,40 @@ async def handle_mqtt_command(should_open: bool):
|
||||||
async def trigger():
|
async def trigger():
|
||||||
"""Trigger the gate"""
|
"""Trigger the gate"""
|
||||||
try:
|
try:
|
||||||
success = await trigger_gate()
|
result = await trigger_gate("api")
|
||||||
timestamp = datetime.now().isoformat()
|
if result:
|
||||||
|
status = await get_status()
|
||||||
# Get current status after trigger
|
return {"success": True, "isOpen": status["isOpen"]}
|
||||||
settings = app.state.current_settings or Settings()
|
return {"success": False}
|
||||||
current_status = GPIO.input(settings.gpio.statusPin) == GPIO.HIGH
|
|
||||||
|
|
||||||
return {"success": success, "timestamp": timestamp, "isOpen": current_status}
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Error triggering gate", exc_info=True)
|
logger.error(f"Failed to trigger gate: {e}", exc_info=True)
|
||||||
raise HTTPException(status_code=500, detail="Failed to trigger gate")
|
return {"success": False}
|
||||||
|
|
||||||
|
@app.post("/api/events/clear")
|
||||||
|
async def clear_events():
|
||||||
|
"""Clear all events from the database except the latest gate status"""
|
||||||
|
try:
|
||||||
|
async with DBConnection() as db:
|
||||||
|
# Delete all events
|
||||||
|
await db.execute("DELETE FROM events")
|
||||||
|
|
||||||
|
# Keep only the most recent gate status
|
||||||
|
await db.execute("""
|
||||||
|
DELETE FROM gate_status
|
||||||
|
WHERE timestamp NOT IN (
|
||||||
|
SELECT timestamp
|
||||||
|
FROM gate_status
|
||||||
|
ORDER BY timestamp DESC
|
||||||
|
LIMIT 1
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
await db.commit()
|
||||||
|
|
||||||
|
logger.info("Event logs cleared (preserved latest gate status)")
|
||||||
|
return {"success": True}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to clear events: {e}", exc_info=True)
|
||||||
|
return {"success": False}
|
||||||
|
|
||||||
@app.get("/api/status")
|
@app.get("/api/status")
|
||||||
async def get_status():
|
async def get_status():
|
||||||
|
|
@ -491,6 +518,8 @@ async def get_status():
|
||||||
# HIGH (3.3V) means gate is open (receiving voltage)
|
# HIGH (3.3V) means gate is open (receiving voltage)
|
||||||
# LOW (0V) means gate is closed (no voltage)
|
# LOW (0V) means gate is closed (no voltage)
|
||||||
is_open = GPIO.input(settings.gpio.statusPin) == GPIO.HIGH
|
is_open = GPIO.input(settings.gpio.statusPin) == GPIO.HIGH
|
||||||
|
|
||||||
|
# Update state machine
|
||||||
gate_status.update(is_open)
|
gate_status.update(is_open)
|
||||||
|
|
||||||
return {"isOpen": gate_status.is_open, "lastChanged": gate_status.last_changed}
|
return {"isOpen": gate_status.is_open, "lastChanged": gate_status.last_changed}
|
||||||
|
|
|
||||||
|
|
@ -198,7 +198,29 @@ function App() {
|
||||||
|
|
||||||
{/* Recent Events */}
|
{/* Recent Events */}
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
<h2 className="text-lg font-semibold mb-2">Recent Events</h2>
|
<div className="flex justify-between items-center mb-2">
|
||||||
|
<h2 className="text-lg font-semibold">Recent Events</h2>
|
||||||
|
<button
|
||||||
|
onClick={async () => {
|
||||||
|
try {
|
||||||
|
if (window.confirm('Are you sure you want to clear all events?')) {
|
||||||
|
await api.clearEvents();
|
||||||
|
// Reload events
|
||||||
|
const eventsData = await api.getEvents();
|
||||||
|
setEvents(eventsData.events);
|
||||||
|
setHasMoreEvents(eventsData.hasMore);
|
||||||
|
setTotalEvents(eventsData.total);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
setError('Failed to clear events');
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="text-sm text-red-600 hover:text-red-800"
|
||||||
|
>
|
||||||
|
Clear Events
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div className="border rounded-lg divide-y divide-gray-200 max-h-96 overflow-y-auto">
|
<div className="border rounded-lg divide-y divide-gray-200 max-h-96 overflow-y-auto">
|
||||||
{events.map((event, index) => (
|
{events.map((event, index) => (
|
||||||
<div key={index} className="px-3 py-2 hover:bg-gray-50 flex items-center justify-between text-sm">
|
<div key={index} className="px-3 py-2 hover:bg-gray-50 flex items-center justify-between text-sm">
|
||||||
|
|
|
||||||
|
|
@ -49,3 +49,13 @@ export async function updateSettings(settings: Settings): Promise<{ success: boo
|
||||||
}
|
}
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function clearEvents(): Promise<{ success: boolean }> {
|
||||||
|
const response = await fetch(`${API_BASE}/events/clear`, {
|
||||||
|
method: 'POST',
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to clear events');
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue