Files
MultiRoombaRover/server/src/services/discordBotService/batteryEmbeds.js
T
2026-05-01 01:32:02 -04:00

121 lines
4.7 KiB
JavaScript

// 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,
};