Hallo,
wir bekommen von unserer Leitstelle derzeit kein Einsatzende übermittelt. Dadurch bleiben Einsätze offen, bis sie jemand manuell abschließt. Mit der neuen Version der EinsatzApp funktioniert das zwar sehr gut, aber hin und wieder bleibt doch ein Einsatz offen. Dieser taucht dann im Dashboard und nun auch im neuen EinsatzManager auf.
Ich habe deshalb ein Python‑Skript geschrieben, das offene Einsätze nach einer definierten Frist automatisch beendet. Das Skript läuft als Cron‑Job auf einem Raspberry Pi. Vielleicht habt ihr eine ähnliche Anforderung und könnt das Skript direkt nutzen oder für eure Zwecke anpassen.
Python
import os
import re
import requests
from datetime import datetime
# Feuerwehr Software API token
token = <<API-KEY>>
def fetch_operations() -> list:
"""Fetch all operations from the API"""
url = "https://connectapi.feuersoftware.com/interfaces/public/operation"
params = {"onlyLatest": "false"}
headers = {"authorization": f"Bearer {token}", "content-type": "application/json"}
try:
resp = requests.get(url, params=params, headers=headers, timeout=30)
resp.raise_for_status()
return resp.json()
except requests.RequestException as e:
print(f"Error: {e}")
return []
def filter_active_operations(operations) -> dict:
"""Filter operations that don't have an 'End' attribute (still active)"""
active_ops = []
for op in operations:
if "End" not in op:
if check_minutes_since_start(op, 1440): # 1440 minutes = 24 hours
active_ops.append(op)
return active_ops
def check_minutes_since_start(operation: dict, minutes: int) -> bool:
"""Check if the operation has been active for more than the specified minutes"""
start_str = operation.get("Start")
if not start_str:
return False
try:
# Fix fractional seconds to max 6 digits (Python supports only microseconds)
start_str_fixed = re.sub(r'(\.\d{6})\d+([+-]\d{2}:\d{2}|Z)$', r'\1\2', start_str)
# Parse ISO format datetime (handles timezone info)
start_dt = datetime.fromisoformat(start_str_fixed.replace("Z", "+00:00"))
# Convert to naive datetime in local timezone for comparison
start_local = start_dt.astimezone().replace(tzinfo=None)
now = datetime.now()
elapsed_minutes = int((now - start_local).total_seconds() / 60)
if elapsed_minutes < 0:
return False # Start time is in the future
if elapsed_minutes > minutes:
return True
return False
except ValueError as e:
print(f"Error parsing date for ID {operation['Id']}: {e}")
return False
def patch_active_opearations(active_ops: list) -> None:
"""Patch all active operations to set their end time to now"""
for op in active_ops:
success = patch_operation_end_time(op)
if success:
print(f"Patched operation ID {op['Number']} successfully.")
else:
print(f"Failed to patch operation ID {op['Number']}.")
def patch_operation_end_time(operation: dict) -> bool:
"""Patch the operation to set the 'End' time to now. Add to the payload attribute End the current time in ISO format.
Replace AlarmEnabled to false and add Attribute Status with value 'update'."""
url = "https://connectapi.feuersoftware.com/interfaces/public/operation?updateStrategy=byNumber"
# prepare End timestamp with local timezone offset
now_iso = datetime.now().astimezone().isoformat()
# original payload
payload = dict(operation)
# set/overwrite required fields
payload["End"] = now_iso
payload["AlarmEnabled"] = False
payload["Status"] = "update"
headers = {"authorization": f"Bearer {token}", "content-type": "application/json"}
try:
resp = requests.post(url, json=payload, headers=headers, timeout=30)
resp.raise_for_status()
return True
except requests.RequestException as e:
print(f"Error posting operation ID {operation['Id']}: {e}")
return False
def main():
print("Fetching operations...")
operations = fetch_operations()
if not operations:
print("No operations found")
return []
active_operations = filter_active_operations(operations)
if not active_operations:
print("No active operations found that need to be ended.")
return []
updated_opsations = patch_active_opearations(active_operations)
return updated_opsations
if __name__ == "__main__":
main()
Alles anzeigen
Grüße René