option('host'); $port = max(1, (int) $this->option('port')); $timeout = max(1, (int) $this->option('timeout')); $readBytes = max(64, min(65535, (int) $this->option('read-bytes'))); $reconnect = (bool) $this->option('reconnect'); $reconnectDelay = max(1, (int) $this->option('reconnect-delay')); $maxIdleTimeouts = max(0, (int) $this->option('max-idle-timeouts')); $startupSend = (string) ($this->option('startup-send') ?? ''); $startupSendHex = strtoupper(preg_replace('/[^0-9A-Fa-f]/', '', (string) ($this->option('startup-send-hex') ?? '')) ?? ''); $logFile = (string) $this->option('log-file'); $printHex = (bool) $this->option('print-hex'); $printText = (bool) $this->option('print-text'); if ($startupSend !== '' && $startupSendHex !== '') { $this->error('Usa solo uno tra --startup-send e --startup-send-hex.'); return self::FAILURE; } if ($startupSendHex !== '' && strlen($startupSendHex) % 2 !== 0) { $this->error('Il payload HEX deve avere un numero pari di caratteri.'); return self::FAILURE; } $startupPayload = $startupSendHex !== '' ? (hex2bin($startupSendHex) ?: ''): $startupSend; $absoluteLogFile = $this->normalizeLogFilePath($logFile); $this->info("Bridge Panasonic CSTA verso {$host}:{$port}"); $this->line('Log raw frames: ' . $absoluteLogFile); do { $socket = @stream_socket_client("tcp://{$host}:{$port}", $errno, $errstr, $timeout); if (! is_resource($socket)) { $this->error("Connessione fallita ({$errno}): {$errstr}"); if (! $reconnect) { return self::FAILURE; } sleep($reconnectDelay); continue; } stream_set_timeout($socket, $timeout); stream_set_blocking($socket, true); $this->info('Connessione CSTA stabilita.'); $this->appendFrameLog($absoluteLogFile, [ 'type' => 'connection.opened', 'host' => $host, 'port' => $port, 'ts' => now()->toIso8601String(), ]); if ($startupPayload !== '') { @fwrite($socket, $startupPayload); $this->appendFrameLog($absoluteLogFile, [ 'type' => 'connection.startup_payload', 'host' => $host, 'port' => $port, 'ts' => now()->toIso8601String(), 'payload_hex' => strtoupper(bin2hex($startupPayload)), 'payload_text' => $this->renderPrintable($startupPayload), ]); } $idleTimeouts = 0; while (! feof($socket)) { $chunk = fread($socket, $readBytes); $chunk = is_string($chunk) ? $chunk : ''; if ($chunk === '') { $meta = stream_get_meta_data($socket); if (($meta['timed_out'] ?? false) === true) { $idleTimeouts++; $this->warn("Timeout lettura CSTA ({$idleTimeouts})"); if ($maxIdleTimeouts > 0 && $idleTimeouts >= $maxIdleTimeouts) { $this->warn('Soglia timeout raggiunta, riconnessione in corso.'); break; } continue; } break; } $idleTimeouts = 0; $hex = strtoupper(bin2hex($chunk)); $text = $this->renderPrintable($chunk); $record = [ 'type' => 'frame.received', 'host' => $host, 'port' => $port, 'ts' => now()->toIso8601String(), 'size' => strlen($chunk), 'payload_hex' => $hex, 'payload_text' => $text, ]; $this->appendFrameLog($absoluteLogFile, $record); if ($printHex) { $this->line('HEX ' . implode(' ', str_split($hex, 2))); } if ($printText) { $this->line('TEXT ' . ($text !== '' ? $text : '[binary/non-printable]')); } } fclose($socket); $this->appendFrameLog($absoluteLogFile, [ 'type' => 'connection.closed', 'host' => $host, 'port' => $port, 'ts' => now()->toIso8601String(), ]); $this->warn('Socket CSTA chiusa.'); if (! $reconnect) { return self::SUCCESS; } sleep($reconnectDelay); } while (true); } private function normalizeLogFilePath(string $path): string { if (str_starts_with($path, '/')) { return $path; } return base_path($path); } /** * @param array $payload */ private function appendFrameLog(string $logFile, array $payload): void { $dir = dirname($logFile); if (! is_dir($dir)) { @mkdir($dir, 0775, true); } $json = json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); if (! is_string($json) || $json === '') { return; } @file_put_contents($logFile, $json . PHP_EOL, FILE_APPEND); Log::info('Panasonic CSTA bridge', [ 'event' => $payload['type'] ?? 'unknown', 'host' => $payload['host'] ?? null, 'port' => $payload['port'] ?? null, 'size' => $payload['size'] ?? null, ]); } private function renderPrintable(string $payload): string { $printable = preg_replace('/[^\P{C}\n\r\t]/u', '.', $payload); return is_string($printable) ? trim($printable) : ''; } }