import json import threading from http.server import HTTPServer, BaseHTTPRequestHandler from datetime import datetime, timezone import logging import time import socket import os from config import HEALTH_CHECK_PORT, HEALTH_CHECK_ENABLED class HealthCheckHandler(BaseHTTPRequestHandler): def do_GET(self): if self.path == '/health': self.send_health_response() elif self.path == '/sensors': self.send_sensors_status() else: self.send_response(404) self.end_headers() def send_health_response(self): """Send basic health check response""" try: # Try to get sensor status try: from sensor_tracker import get_sensor_tracker sensor_tracker = get_sensor_tracker() summary = sensor_tracker.get_summary() health_data = { "status": "healthy", "timestamp": datetime.now(timezone.utc).isoformat(), "service": "modbus-mqtt-bridge", "version": "1.0.0", "sensors": { "total": summary.get('total_sensors', 0), "online": summary.get('online_sensors', 0), "health_percentage": summary.get('health_percentage', 0.0) } } # Mark as degraded if no sensors online but service is running if summary.get('total_sensors', 0) > 0 and summary.get('online_sensors', 0) == 0: health_data["status"] = "degraded" health_data["message"] = "All sensors offline" except Exception as e: # If sensor tracker not available, still report healthy health_data = { "status": "healthy", "timestamp": datetime.now(timezone.utc).isoformat(), "service": "modbus-mqtt-bridge", "version": "1.0.0", "message": "Service running, sensor status unavailable" } except Exception as e: health_data = { "status": "unhealthy", "timestamp": datetime.now(timezone.utc).isoformat(), "service": "modbus-mqtt-bridge", "error": str(e) } self.send_response(200) self.send_header('Content-type', 'application/json') self.end_headers() self.wfile.write(json.dumps(health_data, indent=2).encode()) def send_sensors_status(self): """Send detailed sensor status""" try: from sensor_tracker import get_all_sensor_status sensors_status = get_all_sensor_status() self.send_response(200) self.send_header('Content-type', 'application/json') self.end_headers() self.wfile.write(json.dumps(sensors_status, indent=2).encode()) except Exception as e: error_response = { "error": "Failed to get sensor status", "message": str(e), "timestamp": datetime.now(timezone.utc).isoformat() } self.send_response(500) self.send_header('Content-type', 'application/json') self.end_headers() self.wfile.write(json.dumps(error_response, indent=2).encode()) def log_message(self, format, *args): """Override to reduce noise""" pass class HealthCheckServer: def __init__(self, port=None): self.port = port or int(os.getenv('HEALTH_CHECK_PORT', HEALTH_CHECK_PORT)) self.server = None self.thread = None self.started = False def start(self): """Start the health check server""" if not HEALTH_CHECK_ENABLED: logging.info("Health check server is disabled") return False try: logging.info(f"Starting health check server on port {self.port}") self.server = HTTPServer(('0.0.0.0', self.port), HealthCheckHandler) self.thread = threading.Thread(target=self._serve, daemon=True) self.thread.start() # Give server time to start time.sleep(1) self.started = True logging.info(f"Health check server running on http://0.0.0.0:{self.port}/health") return True except Exception as e: logging.error(f"Failed to start health check server: {e}") return False def _serve(self): """Serve requests""" try: if self.server: self.server.serve_forever() except Exception as e: logging.error(f"Health check server error: {e}") self.started = False def stop(self): """Stop the server""" if self.server: self.server.shutdown() self.server.server_close() self.started = False logging.info("Health check server stopped") def is_running(self): """Check if server is running""" return self.started and self.thread and self.thread.is_alive()