|
| 1 | +""" |
| 2 | +udp_server.py |
| 3 | +
|
| 4 | +Creates a UDP server that listens for incoming client requests and responds with humidity and temperature data retrieved from the sensor. |
| 5 | +The server uses multithreading to handle concurrent client requests. Extensive logging and error handling routines have been added. |
| 6 | +""" |
| 7 | + |
| 8 | +import socket |
| 9 | +import json |
| 10 | +import sys |
| 11 | +import threading |
| 12 | +import logging |
| 13 | +from config import UDP_IP, UDP_PORT, RESPONSE_TIMEOUT, ENABLE_LOGGING, LOG_FILE, DEBUG_MODE |
| 14 | +from sensor_reader import SensorReader |
| 15 | + |
| 16 | +# Configure logging for the UDP server |
| 17 | +logger = logging.getLogger("UDPServer") |
| 18 | +logger.setLevel(logging.DEBUG if DEBUG_MODE else logging.INFO) |
| 19 | +console_handler = logging.StreamHandler() |
| 20 | +file_handler = logging.FileHandler(LOG_FILE) |
| 21 | +formatter = logging.Formatter('%(asctime)s - UDPServer - %(levelname)s - %(message)s') |
| 22 | +console_handler.setFormatter(formatter) |
| 23 | +file_handler.setFormatter(formatter) |
| 24 | +logger.addHandler(console_handler) |
| 25 | +logger.addHandler(file_handler) |
| 26 | + |
| 27 | +def handle_client(sock, data, address, sensor_reader): |
| 28 | + """ |
| 29 | + Handle an individual client's UDP request in a separate thread. |
| 30 | + This function reads the sensor data and replies with a JSON payload. |
| 31 | + """ |
| 32 | + try: |
| 33 | + received_message = data.decode().strip() |
| 34 | + logger.info("Received request from %s: %s", address, received_message) |
| 35 | + sensor_data = sensor_reader.get_data() |
| 36 | + response = json.dumps(sensor_data) |
| 37 | + sock.sendto(response.encode(), address) |
| 38 | + logger.info("Sent response to %s: %s", address, response) |
| 39 | + except Exception as e: |
| 40 | + logger.exception("Error handling client %s: %s", address, e) |
| 41 | + |
| 42 | +def monitor_performance(sensor_reader): |
| 43 | + """ |
| 44 | + Periodically logs system performance and sensor data for monitoring. |
| 45 | + This function runs in its own thread. |
| 46 | + """ |
| 47 | + while True: |
| 48 | + try: |
| 49 | + data = sensor_reader.get_data() |
| 50 | + logger.debug("Performance Monitor - Sensor Data: %s", data) |
| 51 | + except Exception as e: |
| 52 | + logger.error("Error in performance monitoring: %s", e) |
| 53 | + # Sleep between performance logs |
| 54 | + for _ in range(5): |
| 55 | + try: |
| 56 | + # This loop also checks for interruption |
| 57 | + threading.Event().wait(timeout=1) |
| 58 | + except KeyboardInterrupt: |
| 59 | + logger.info("Performance monitor interrupted.") |
| 60 | + break |
| 61 | + |
| 62 | +def start_udp_server(): |
| 63 | + """ |
| 64 | + Start the UDP server to handle incoming UDP requests. |
| 65 | + This function initializes the socket, sensor reader, and monitoring thread. |
| 66 | + """ |
| 67 | + # Create UDP socket |
| 68 | + try: |
| 69 | + server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) |
| 70 | + server_socket.bind((UDP_IP, UDP_PORT)) |
| 71 | + server_socket.settimeout(RESPONSE_TIMEOUT) |
| 72 | + logger.info("UDP Server listening on %s:%s", UDP_IP, UDP_PORT) |
| 73 | + except Exception as e: |
| 74 | + logger.exception("Failed to create or bind UDP socket: %s", e) |
| 75 | + sys.exit(1) |
| 76 | + |
| 77 | + # Initialize sensor reader and start its thread |
| 78 | + sensor_reader = SensorReader() |
| 79 | + sensor_reader.start() |
| 80 | + |
| 81 | + # Start performance monitoring in a separate thread |
| 82 | + monitor_thread = threading.Thread(target=monitor_performance, args=(sensor_reader,), daemon=True) |
| 83 | + monitor_thread.start() |
| 84 | + logger.info("Performance monitoring thread started.") |
| 85 | + |
| 86 | + try: |
| 87 | + while True: |
| 88 | + try: |
| 89 | + data, address = server_socket.recvfrom(1024) |
| 90 | + # Log raw request received for debugging purposes |
| 91 | + logger.debug("Raw data received: %s from %s", data, address) |
| 92 | + # Create a new thread for each incoming client request |
| 93 | + client_thread = threading.Thread( |
| 94 | + target=handle_client, args=(server_socket, data, address, sensor_reader)) |
| 95 | + client_thread.start() |
| 96 | + except socket.timeout: |
| 97 | + logger.debug("Socket timeout waiting for client data. Continuing...") |
| 98 | + continue |
| 99 | + except Exception as e: |
| 100 | + logger.exception("Error receiving data: %s", e) |
| 101 | + except KeyboardInterrupt: |
| 102 | + logger.info("UDP server shutting down due to keyboard interrupt...") |
| 103 | + finally: |
| 104 | + sensor_reader.stop() |
| 105 | + server_socket.close() |
| 106 | + logger.info("UDP server closed. Exiting.") |
| 107 | + |
| 108 | +def main(): |
| 109 | + """ |
| 110 | + Entry point for the UDP server application. |
| 111 | + Additional preliminary logging and self-test calls can be added here. |
| 112 | + """ |
| 113 | + logger.info("Starting UDP server application.") |
| 114 | + start_udp_server() |
| 115 | + logger.info("UDP server application has stopped.") |
| 116 | + |
| 117 | +# Extra function for future expansion, not currently used. |
| 118 | +def advanced_request_handler(): |
| 119 | + """ |
| 120 | + Placeholder for an advanced UDP request handler that might implement |
| 121 | + additional protocol features, such as authentication or encryption. |
| 122 | + """ |
| 123 | + pass |
| 124 | + |
| 125 | +# for further networking features can be integrated. |
| 126 | +# |
| 127 | +# def udp_multicast_listener(): |
| 128 | +# """ |
| 129 | +# Example function to implement an UDP multicast listener in the future. |
| 130 | +# """ |
| 131 | +# pass |
| 132 | +# |
| 133 | +# End placeholder for extended networking features. |
| 134 | + |
| 135 | +if __name__ == "__main__": |
| 136 | + try: |
| 137 | + main() |
| 138 | + except Exception as e: |
| 139 | + logger.exception("Fatal error in UDP server: %s", e) |
| 140 | + sys.exit(1) |
0 commit comments