import socket import traceback import threading from protocol import decode_message, encode_message from state import ModuleState def parse_bool(value): if isinstance(value, bool): return value if isinstance(value, (int, float)): return bool(value) if isinstance(value, str): s = value.strip().lower() if s in ("1", "true", "yes", "on"): return True if s in ("0", "false", "no", "off"): return False raise ValueError("Valor booleano inválido") def parse_frame_type(value): valid = {"RAW_BRUTO", "RGB", "RGBNIR"} if value not in valid: raise ValueError(f"frame_type inválido: {value}") return value def parse_output_dtype(value): valid = {"uint8", "uint16", "float32"} if value not in valid: raise ValueError(f"output_dtype inválido: {value}") return value def parse_capture_mode(value): valid = {"AUTO", "SINGLE", "DUAL"} if value not in valid: raise ValueError(f"capture_mode inválido: {value}") return value def parse_bayer_pattern(value): valid = {"GBRG", "GRBG", "RGGB", "BGGR"} if value not in valid: raise ValueError(f"bayer_pattern inválido: {value}") return value class ModuleServer: def __init__(self, host="0.0.0.0", port=5000): self.host = host self.port = port self.state = ModuleState() self.running = False self.lock = threading.RLock() from camera_manager import CameraManager from trigger_manager import TriggerManager from frame_service import FrameService from stream_sender import StreamSender self.camera = CameraManager(self.state) self.trigger = TriggerManager( pin=self.state.trigger_pin, active_high=self.state.trigger_active_high, pulse_ms=self.state.trigger_pulse_ms ) self.frame_service = FrameService(self.state, self.trigger, self.camera) self.stream_sender = StreamSender(self.state, self.frame_service) def _mark_reconfigure_needed(self): if hasattr(self.camera, "mark_reconfigure_needed"): self.camera.mark_reconfigure_needed() def _get_active_cameras(self): return [cam for cam in self.state.cameras if cam.connected and cam.enabled] def _get_primary_camera(self): active = self._get_active_cameras() if active: return active[0] if self.state.cameras: return self.state.cameras[0] return None def _payload_dict(self): payload = self.state.payload return { "payload_format_version": self.state.payload_format_version, "frame_type": self.state.frame_type, "output_dtype": self.state.output_dtype, "output_layout": payload.layout, "output_channels": payload.channels, "output_channel_names": payload.channel_names, "output_width": payload.width, "output_height": payload.height, "payload_sources": payload.sources, } def _source_dict(self): cam = self._get_primary_camera() if cam is None: return { "source_camera": None, "source_width": 0, "source_height": 0, "source_bayer_pattern": None, "source_bit_depth": None, } return { "source_camera": cam.id, "source_width": cam.width, "source_height": cam.height, "source_bayer_pattern": cam.bayer_pattern, "source_bit_depth": cam.bit_depth, } def _build_config_response(self): return { "ok": True, "module": self.state.module_name, "version": self.state.version, "status": self.state.status, "initialized": self.state.initialized, "streaming": self.state.streaming, "fps": self.state.fps, "jpeg_quality": self.state.jpeg_quality, "capture_mode": self.state.capture_mode, "detected_mode": self.state.detected_mode, "camera_count_detected": self.state.camera_count_detected, "camera_count_active": self.state.camera_count_active, "active_camera_ids": self.state.active_camera_ids, "cameras": [cam.to_dict() for cam in self.state.cameras], **self._payload_dict(), **self._source_dict(), } def handle_command(self, msg: dict) -> dict: with self.lock: cmd = msg.get("cmd") self.state.last_command = cmd try: if cmd == "ping": return {"ok": True, "reply": "pong"} if cmd == "get_status": return {"ok": True, **self.state.to_dict()} if cmd == "get_config": return self._build_config_response() if cmd == "begin": frame_type = parse_frame_type(msg.get("frame_type", self.state.frame_type)) output_dtype = parse_output_dtype(msg.get("output_dtype", self.state.output_dtype)) capture_mode = parse_capture_mode(msg.get("capture_mode", self.state.capture_mode)) self.state.frame_type = frame_type self.state.output_dtype = output_dtype self.state.capture_mode = capture_mode self.state.update_payload_spec() if self.state.initialized: return { "ok": True, "status": self.state.status, "initialized": True, **self._payload_dict(), **self._source_dict(), } self.state.status = "initializing" self.state.status_detail = None self.trigger.begin() self.camera.begin() self.state.initialized = True self.state.status = "ready" self.state.last_error = None return { "ok": True, "status": self.state.status, "initialized": True, **self._payload_dict(), **self._source_dict(), } if cmd == "stop": self.state.status = "stopping" if getattr(self.stream_sender, "is_running", False): self.stream_sender.stop() self.camera.stop() self.trigger.stop() self.state.initialized = False self.state.streaming = False self.state.status = "idle" self.state.status_detail = None return {"ok": True, "status": self.state.status} if cmd == "capture_frame": data = self.frame_service.capture_frame_base64() return {"ok": True, **data} if cmd == "set_fps": value = int(msg.get("value")) if value <= 0 or value > 120: return {"ok": False, "error": "fps inválido"} self.state.fps = value self._mark_reconfigure_needed() return {"ok": True, "fps": self.state.fps} if cmd == "set_jpeg_quality": value = int(msg.get("value")) if value < 1 or value > 100: return {"ok": False, "error": "jpeg_quality inválido"} self.state.jpeg_quality = value return {"ok": True, "jpeg_quality": self.state.jpeg_quality} if cmd == "set_frame_type": frame_type = parse_frame_type(msg.get("value")) self.state.frame_type = frame_type self.state.update_payload_spec() self._mark_reconfigure_needed() return { "ok": True, "frame_type": self.state.frame_type, **self._payload_dict(), } if cmd == "set_output_dtype": output_dtype = parse_output_dtype(msg.get("value")) self.state.output_dtype = output_dtype self.state.update_payload_spec() self._mark_reconfigure_needed() return { "ok": True, "output_dtype": self.state.output_dtype, **self._payload_dict(), } if cmd == "set_capture_mode": capture_mode = parse_capture_mode(msg.get("value")) self.state.capture_mode = capture_mode self.state.update_payload_spec() self._mark_reconfigure_needed() return { "ok": True, "capture_mode": self.state.capture_mode, "detected_mode": self.state.detected_mode, **self._payload_dict(), } if cmd == "set_camera_enabled": index = int(msg.get("index")) enabled = parse_bool(msg.get("enabled")) self.state.set_camera_enabled(index, enabled) self._mark_reconfigure_needed() cam = self.state.get_camera_by_index(index) return { "ok": True, "camera": cam.to_dict(), "camera_count_active": self.state.camera_count_active, "active_camera_ids": self.state.active_camera_ids, **self._payload_dict(), } if cmd == "set_camera_bayer": index = int(msg.get("index")) pattern = parse_bayer_pattern(msg.get("pattern")) cam = self.state.get_camera_by_index(index) if cam is None: return {"ok": False, "error": f"Câmera de índice {index} não existe"} cam.bayer_pattern = pattern self.state.update_payload_spec() self._mark_reconfigure_needed() return { "ok": True, "camera": cam.to_dict(), **self._source_dict(), } if cmd == "set_camera_resolution": index = int(msg.get("index")) width = int(msg.get("width")) height = int(msg.get("height")) if width <= 0 or height <= 0: return {"ok": False, "error": "resolução inválida"} cam = self.state.get_camera_by_index(index) if cam is None: return {"ok": False, "error": f"Câmera de índice {index} não existe"} cam.width = width cam.height = height self.state.update_payload_spec() self._mark_reconfigure_needed() return { "ok": True, "camera": cam.to_dict(), **self._payload_dict(), **self._source_dict(), } if cmd == "start_stream": host = msg.get("host") port = int(msg.get("port")) fps = float(msg.get("fps", self.state.fps)) if not host: return {"ok": False, "error": "host obrigatório"} if port <= 0 or port > 65535: return {"ok": False, "error": "porta inválida"} if fps <= 0: return {"ok": False, "error": "fps inválido"} if getattr(self.stream_sender, "is_running", False): return { "ok": True, "streaming": True, "host": self.state.stream_host, "port": self.state.stream_port, "fps": self.state.stream_fps, **self._payload_dict(), **self._source_dict(), } self.stream_sender.start(host, port, fps) self.state.streaming = True self.state.stream_host = host self.state.stream_port = port self.state.stream_fps = fps self.state.status = "streaming" return { "ok": True, "streaming": True, "host": host, "port": port, "fps": fps, **self._payload_dict(), **self._source_dict(), } if cmd == "stop_stream": if getattr(self.stream_sender, "is_running", False): self.stream_sender.stop() self.state.streaming = False self.state.stream_host = None self.state.stream_port = None self.state.stream_fps = None self.state.status = "ready" if self.state.initialized else "idle" return { "ok": True, "streaming": False, "status": self.state.status } if cmd == "set_ae_enable": self.state.ae_enable = parse_bool(msg.get("value")) if self.camera.initialized: self.camera.apply_controls() return {"ok": True, "ae_enable": self.state.ae_enable} if cmd == "set_awb_enable": self.state.awb_enable = parse_bool(msg.get("value")) if self.camera.initialized: self.camera.apply_controls() return {"ok": True, "awb_enable": self.state.awb_enable} if cmd == "set_exposure_time": value = msg.get("value") if value is not None: value = int(value) if value <= 0: return {"ok": False, "error": "ExposureTime inválido"} self.state.exposure_time_us = value if self.camera.initialized: self.camera.apply_controls() return {"ok": True, "exposure_time_us": self.state.exposure_time_us} if cmd == "set_analogue_gain": value = msg.get("value") if value is not None: value = float(value) if value <= 0: return {"ok": False, "error": "AnalogueGain inválido"} self.state.analogue_gain = value if self.camera.initialized: self.camera.apply_controls() return {"ok": True, "analogue_gain": self.state.analogue_gain} if cmd == "set_colour_gains": r_gain = msg.get("r_gain") b_gain = msg.get("b_gain") if r_gain is None or b_gain is None: return {"ok": False, "error": "r_gain e b_gain são obrigatórios"} r_gain = float(r_gain) b_gain = float(b_gain) if r_gain <= 0 or b_gain <= 0: return {"ok": False, "error": "ColourGains inválidos"} self.state.colour_gains = [r_gain, b_gain] if self.camera.initialized: self.camera.apply_controls() return {"ok": True, "colour_gains": self.state.colour_gains} if cmd == "clear_exposure_time": self.state.exposure_time_us = None if self.camera.initialized: self.camera.apply_controls() return {"ok": True, "exposure_time_us": None} if cmd == "clear_analogue_gain": self.state.analogue_gain = None if self.camera.initialized: self.camera.apply_controls() return {"ok": True, "analogue_gain": None} if cmd == "clear_colour_gains": self.state.colour_gains = None if self.camera.initialized: self.camera.apply_controls() return {"ok": True, "colour_gains": None} if cmd == "get_camera_controls": return { "ok": True, "ae_enable": self.state.ae_enable, "awb_enable": self.state.awb_enable, "exposure_time_us": self.state.exposure_time_us, "analogue_gain": self.state.analogue_gain, "colour_gains": self.state.colour_gains, "fps": self.state.fps, } if cmd == "get_sensor_modes": modes = self.camera.get_sensor_modes() return {"ok": True, "sensor_modes": modes} return {"ok": False, "error": f"Comando desconhecido: {cmd}"} except Exception as e: self.state.set_error(str(e)) return {"ok": False, "error": str(e)} def client_thread(self, conn, addr): print(f"[INFO] Cliente conectado: {addr}") buffer = b"" try: conn.settimeout(1.0) while self.running: try: chunk = conn.recv(4096) except socket.timeout: continue if not chunk: break buffer += chunk while b"\n" in buffer: line, buffer = buffer.split(b"\n", 1) if not line.strip(): continue try: msg = decode_message(line.decode("utf-8")) response = self.handle_command(msg) except Exception as e: response = {"ok": False, "error": str(e)} conn.sendall(encode_message(response)) except Exception as e: print(f"[ERRO] Cliente {addr}: {e}") traceback.print_exc() finally: try: conn.close() except Exception: pass print(f"[INFO] Cliente desconectado: {addr}") def start(self): self.running = True with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_socket: server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server_socket.bind((self.host, self.port)) server_socket.listen(5) server_socket.settimeout(1.0) print(f"[INFO] Servidor ouvindo em {self.host}:{self.port}") while self.running: try: conn, addr = server_socket.accept() except socket.timeout: continue t = threading.Thread( target=self.client_thread, args=(conn, addr), daemon=True ) t.start()