diff --git a/server/config.example.yaml b/server/config.example.yaml index 1c889b6d..b1d10e02 100644 --- a/server/config.example.yaml +++ b/server/config.example.yaml @@ -42,6 +42,8 @@ homeAssistant: brainslugHost: "neato-vacuum.local" brainslugPort: 6053 brainslugKey: "REPLACE_WITH_ESPHOME_NOISE_PSK" + # Optional: mirror raw ESPHome lidar log output to a local file for debugging. + brainslugLogFile: "/tmp/brainslug-lidar.log" lift: # Two Home Assistant switches controlling lift direction. # Raise sequence: down off -> wait interlockMs -> up on diff --git a/server/src/services/neatoService/index.js b/server/src/services/neatoService/index.js index 840bd0f4..4aa92358 100644 --- a/server/src/services/neatoService/index.js +++ b/server/src/services/neatoService/index.js @@ -31,6 +31,7 @@ const RESUME_DELAY_MS = 3000; const brainslugHost = String(neatoConfig.brainslugHost || '').trim(); const brainslugPort = Number(neatoConfig.brainslugPort) || 6053; const brainslugKey = String(neatoConfig.brainslugKey || '').trim(); +const brainslugLogFile = String(neatoConfig.brainslugLogFile || '').trim(); let lidarRuntime = null; function entityId(domain, suffix) { @@ -281,6 +282,7 @@ lidarRuntime = host: brainslugHost, port: brainslugPort, key: brainslugKey, + logFile: brainslugLogFile, shouldPoll: () => Boolean(homeAssistantEnabled && isHomeAssistantConnected() && hasVerifiedSockets()), requestScan: requestLidarScan, }) diff --git a/server/src/services/neatoService/lidarRuntime.js b/server/src/services/neatoService/lidarRuntime.js index 5667dc4e..3d54c182 100644 --- a/server/src/services/neatoService/lidarRuntime.js +++ b/server/src/services/neatoService/lidarRuntime.js @@ -1,5 +1,6 @@ const { spawn } = require('child_process'); const EventEmitter = require('events'); +const fs = require('fs'); const SCAN_TIMEOUT_MS = 4000; const RECONNECT_DELAY_MS = 5000; @@ -41,7 +42,7 @@ function parseRotationSpeed(payload) { return Number(match[1]); } -function createLidarRuntime({ logger, host, port = 6053, key, shouldPoll, requestScan }) { +function createLidarRuntime({ logger, host, port = 6053, key, logFile = '', shouldPoll, requestScan }) { const events = new EventEmitter(); const state = { connected: false, @@ -53,6 +54,7 @@ function createLidarRuntime({ logger, host, port = 6053, key, shouldPoll, reques currentScan: null, requestInFlight: false, requestStartedAt: 0, + logStream: null, }; function emitStatus() { @@ -137,6 +139,9 @@ function createLidarRuntime({ logger, host, port = 6053, key, shouldPoll, reques } function handleStdoutChunk(chunk) { + if (state.logStream) { + state.logStream.write(String(chunk || '')); + } state.stdoutBuffer += String(chunk || ''); const lines = state.stdoutBuffer.split(/\r?\n/); state.stdoutBuffer = lines.pop() || ''; @@ -163,9 +168,31 @@ function createLidarRuntime({ logger, host, port = 6053, key, shouldPoll, reques } } + function ensureLogStream() { + if (!logFile || state.logStream) return; + try { + state.logStream = fs.createWriteStream(logFile, { flags: 'a' }); + logger.info('Neato lidar raw log capture enabled', { logFile }); + } catch (err) { + logger.warn('Failed to open Neato lidar raw log file', { logFile, error: err.message }); + state.logStream = null; + } + } + + function closeLogStream() { + if (!state.logStream) return; + try { + state.logStream.end(); + } catch (err) { + logger.warn('Failed to close Neato lidar raw log file', err.message); + } + state.logStream = null; + } + function startLogStream() { if (!host || !key) return; if (state.process) return; + ensureLogStream(); const args = [ '--from', @@ -244,6 +271,7 @@ function createLidarRuntime({ logger, host, port = 6053, key, shouldPoll, reques clearPollSoonTimer(); clearRequestTimeoutTimer(); teardownProcess(); + closeLogStream(); } function getState() {