// Discord Battery Embed Helpers // Purpose: Centralizes rover battery/status snapshot formatting and embed rendering for Discord surfaces. // Scope: Shared presentation logic for status commands and admin alert battery event posts. const { EmbedBuilder } = require('discord.js'); function formatVoltage(voltageMv) { if (voltageMv == null) return 'n/a'; return `${(voltageMv / 1000).toFixed(2)}V`; } function formatCurrent(currentMa) { if (currentMa == null) return 'n/a'; return `${currentMa}mA`; } function formatChargeState(batteryState) { if (!batteryState) return 'n/a'; const charge = batteryState.charge; const capacity = batteryState.capacity; const percent = batteryState.percentDisplay; const chargeText = charge != null && capacity != null ? `${charge}/${capacity}mAh` : 'n/a'; const percentText = percent != null ? `${percent}%` : 'n/a'; return `${chargeText} (${percentText})`; } function formatOiEmoji(oiMode) { if (oiMode === 'full') return '🕹️'; if (oiMode === 'safe') return '🧰'; if (oiMode === 'passive') return '🟢'; if (oiMode === 'off') return '⏹️'; return '❔'; } function isCharging(sensors) { const label = sensors?.chargingState?.label?.toLowerCase(); const chargingByLabel = label === 'waiting' || label === 'full charging' || label === 'trickle charging'; const code = sensors?.chargingState?.code; const chargingByCode = code === 2 || code === 3 || code === 4; return chargingByLabel || chargingByCode; } function buildRoverStatusSnapshot(record) { if (!record) return null; const sensors = record.lastSensor?.decoded || record.lastSensor?.sensors || null; const oiMode = String(sensors?.oiMode?.label || 'unknown').toLowerCase(); return { id: record.id, name: record.meta?.name || record.id, locked: record.locked, lockReason: record.lockReason, docked: Boolean(sensors?.chargingSources?.homeBase), charging: isCharging(sensors), chargingLabel: sensors?.chargingState?.label || 'unknown', voltageMv: sensors?.voltageMv ?? null, currentMa: sensors?.currentMa ?? null, batteryState: record.batteryState, oiMode, }; } function buildBatteryStatusEmbed({ color = 0x2196f3, records = [] }) { const embed = new EmbedBuilder().setTitle('Rover Battery Status').setColor(color).setTimestamp(new Date()); const snapshots = records.map((entry) => buildRoverStatusSnapshot(entry)).filter(Boolean); if (!snapshots.length) { embed.setDescription('No rovers online.'); return embed; } snapshots.forEach((snapshot) => { const lockLabel = snapshot.locked ? `locked${snapshot.lockReason ? ` (${snapshot.lockReason})` : ''}` : 'unlocked'; const dockLabel = snapshot.docked ? 'docked' : 'undocked'; const chargingLabel = snapshot.charging ? `charging (${snapshot.chargingLabel})` : 'not charging'; const lines = [ `Dock: ${dockLabel}`, `Charging: ${chargingLabel}`, `Battery: ${formatChargeState(snapshot.batteryState)}`, `Voltage: ${formatVoltage(snapshot.voltageMv)}`, `Current: ${formatCurrent(snapshot.currentMa)}`, ]; lines.push(`${formatOiEmoji(snapshot.oiMode)} OI mode: ${snapshot.oiMode}`); lines.push(`Lock: ${lockLabel}`); embed.addFields({ name: snapshot.name, value: lines.join('\n'), inline: true, }); }); return embed; } function buildBatteryCaption(type, record) { const snapshot = buildRoverStatusSnapshot(record); const base = snapshot?.name || 'unknown'; const percent = snapshot?.batteryState?.percentDisplay; const percentLabel = percent != null ? `${percent}%` : 'n/a'; const dockLabel = snapshot?.docked ? 'docked' : 'undocked'; const chargingLabel = snapshot?.charging ? 'charging' : 'not charging'; const detail = `${dockLabel}, ${chargingLabel}, ${formatVoltage(snapshot?.voltageMv ?? null)}, ${formatCurrent(snapshot?.currentMa ?? null)}, ${formatChargeState(snapshot?.batteryState ?? null)}`; switch (type) { case 'battery.warn': return `Battery warn: ${base} at ${percentLabel}. ${detail}`; case 'battery.urgent': return `Battery urgent: ${base} at ${percentLabel}. ${detail}`; case 'battery.docked': return `Docked: ${base}. ${detail}`; case 'battery.undocked': return `Undocked: ${base}. ${detail}`; case 'battery.charging.start': return `Charging started: ${base}. ${detail}`; case 'battery.charging.stop': return `Charging stopped: ${base}. ${detail}`; case 'battery.locked': return `Locked for charging: ${base}. ${detail}`; case 'battery.unlocked': return `Unlocked after charging: ${base}. ${detail}`; default: return `Battery update: ${base}. ${detail}`; } } module.exports = { buildRoverStatusSnapshot, buildBatteryStatusEmbed, buildBatteryCaption, };