Improve /dectstatus with handset RF context and cleaner base cards.

Surface handset registrations and RSSI, tighten reboot health to 7 days,
and consolidate Base / Handsets & RF / Network & RTP sections.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
jmcqueen 2026-07-28 09:02:55 -04:00
parent 90a56c4640
commit 2b8c4e06aa
26 changed files with 3128 additions and 134 deletions

View file

@ -4,8 +4,7 @@
// on-prem relay agent. Complements the compact // on-prem relay agent. Complements the compact
// follow-up that /phonestatus already posts: // follow-up that /phonestatus already posts:
// this is the full status.xml dump (device, // this is the full status.xml dump (device,
// firmware, reboot log, network, RTP, security, // firmware, reboot log, network, RTP) plus chat-only action cards
// emergency numbers) plus chat-only action cards
// for reboot / force-reboot / factory-reset. // for reboot / force-reboot / factory-reset.
// //
// Chat surface // Chat surface
@ -30,6 +29,7 @@ import { discoverDectBases } from '../services/dectDiscovery.js';
import { collectAll, execAction } from '../services/dectCollectorService.js'; import { collectAll, execAction } from '../services/dectCollectorService.js';
import { getDectRelayHub } from '../services/dectRelayHub.js'; import { getDectRelayHub } from '../services/dectRelayHub.js';
import { renderDectStatusMarkdown } from '../services/renderers/dectStatusRenderer.js'; import { renderDectStatusMarkdown } from '../services/renderers/dectStatusRenderer.js';
import { buildHandsetContext } from '../services/dectStatus/buildHandsetContext.js';
import { pendingDectActions } from '../utils/pendingDectActions.js'; import { pendingDectActions } from '../utils/pendingDectActions.js';
import { extractRequester, describeRequester } from '../utils/requester.js'; import { extractRequester, describeRequester } from '../utils/requester.js';
import { logger } from '../utils/logger.js'; import { logger } from '../utils/logger.js';
@ -144,10 +144,12 @@ export async function handleDectStatus(bot, trigger) {
); );
const results = await collectAll(targets); const results = await collectAll(targets);
const handsetCtx = buildHandsetContext(phoneData);
const md = renderDectStatusMarkdown(results, { const md = renderDectStatusMarkdown(results, {
storeNum, storeNum,
relay: relayStatus, relay: relayStatus,
discoveryWarnings, discoveryWarnings,
handsetCtx,
}); });
await bot.say('markdown', md || 'No data available.'); await bot.say('markdown', md || 'No data available.');

View file

@ -19,7 +19,7 @@ const SHORT_HELP = {
// AV / phones // AV / phones
avstatus: 'AV / device status for a store (alias: /wostatus)', avstatus: 'AV / device status for a store (alias: /wostatus)',
phonestatus: 'DECT + IP phone status for a store (plus 7d WAN follow-up)', phonestatus: 'DECT + IP phone status for a store (plus 7d WAN follow-up)',
dectstatus: 'Full DECT basestation dump via relay (reboot / factory-reset cards)', dectstatus: 'Full DECT base dump via relay (handsets, RSSI, reboot cards)',
voicediag: 'Deep voice diagnostic: Webex Calling features + SD-WAN quality with fix cards', voicediag: 'Deep voice diagnostic: Webex Calling features + SD-WAN quality with fix cards',
callreport: 'Daily call digest: correlated CDR calls + per-call Prisma WAN (store, email, or phone)', callreport: 'Daily call digest: correlated CDR calls + per-call Prisma WAN (store, email, or phone)',
calltest: 'Twilio voice path test (store AA or direct dial, 60s listen)', calltest: 'Twilio voice path test (store AA or direct dial, 60s listen)',
@ -80,7 +80,8 @@ const LONG_HELP = {
], ],
notes: [ notes: [
'Aliases: `/dect`.', 'Aliases: `/dect`.',
'Pulls the full `status.xml` dump from every reachable DBS-210 base at the store via the on-prem DECT relay agent (device, firmware, reboot log, network stats, RTP, security, emergency numbers, health verdict).', 'Pulls the full `status.xml` dump from every reachable DBS-210 base at the store via the on-prem DECT relay agent (device, firmware, handsets & RF, reboot log, network stats, RTP, health verdict).',
'Also shows **Webex handset registrations** per base (extension, last registration) and **RF signal (RSSI)** from the base when handsets are registered.',
'Optional second arg filters to one base by IP, MAC, or name substring.', 'Optional second arg filters to one base by IP, MAC, or name substring.',
'Chat-only action cards per reachable base: **Reboot**, **Force Reboot**, **Factory Reset**. Each requires a confirm click; outcomes are audited under `dect:audit`.', 'Chat-only action cards per reachable base: **Reboot**, **Force Reboot**, **Factory Reset**. Each requires a confirm click; outcomes are audited under `dect:audit`.',
'Requires `DECT_RELAY_AGENT_TOKEN` on the bot and a live `dect-relay-agent` in the data center. When the relay is offline the command still reports discovery results and the offline state.', 'Requires `DECT_RELAY_AGENT_TOKEN` on the bot and a live `dect-relay-agent` in the data center. When the relay is offline the command still reports discovery results and the offline state.',

View file

@ -121,10 +121,30 @@ All frames are JSON, one per WebSocket message.
{ "type": "hello", { "type": "hello",
"agentVersion": "0.1.0", "agentVersion": "0.1.0",
"hostname": "dc-dect-relay-01", "hostname": "dc-dect-relay-01",
"capabilities": ["collect","reboot","force-reboot","reboot-chain", "capabilities": ["collect","collect-raw","reboot","force-reboot","reboot-chain",
"force-reboot-chain","factory-reset","reconfigure-tree"] } "force-reboot-chain","factory-reset","reconfigure-tree"] }
``` ```
**Bot → Agent (collect with raw XML for fixture capture):**
```json
{ "id": "cmd_<uuid>", "type": "collect", "baseIp": "10.4.11.87", "includeRaw": true }
```
Legacy agents may also accept `{ "type": "collect-raw", "baseIp": "..." }`.
**Agent → Bot (collect + includeRaw reply):**
```json
{ "id": "cmd_<uuid>", "ok": true, "elapsedMs": 812,
"result": { "rawXml": "<Status>...</Status>", "byteLength": 12345,
"sectionInventory": [...], "parsed": { ... }, "verdict": { ... } } }
```
**Capture script (from repo root, bot running with relay connected):**
```bash
node scripts/fetchDectStatusXml.js 782 --save-dir tests/fixtures/dect/
# Optional: --base 10.4.11.87 --url https://your-bot-host/CollabSupport
```
**Bot → Agent (command):** **Bot → Agent (command):**
```json ```json
{ "id": "cmd_<uuid>", "type": "collect", "baseIp": "10.4.11.87" } { "id": "cmd_<uuid>", "type": "collect", "baseIp": "10.4.11.87" }

View file

@ -36,6 +36,7 @@ import {
parseStatusXml, parseStatusXml,
summarizeBaseHealth, summarizeBaseHealth,
} from '../integrations/cisco-dect/statusXml.js'; } from '../integrations/cisco-dect/statusXml.js';
import { inventoryStatusXml } from '../integrations/cisco-dect/statusXmlInventory.js';
// ─── Config ───────────────────────────────────────────────────────── // ─── Config ─────────────────────────────────────────────────────────
@ -49,9 +50,10 @@ const CFG = {
reconnectMaxMs: Number(process.env.DECT_RELAY_RECONNECT_MAX_MS) || 30_000, reconnectMaxMs: Number(process.env.DECT_RELAY_RECONNECT_MAX_MS) || 30_000,
}; };
const AGENT_VERSION = '0.1.0'; const AGENT_VERSION = '0.2.0';
const CAPABILITIES = [ const CAPABILITIES = [
'collect', 'collect',
'collect-raw',
'reboot', 'reboot',
'force-reboot', 'force-reboot',
'reboot-chain', 'reboot-chain',
@ -247,7 +249,8 @@ async function dispatch(cmd) {
}); });
switch (cmd.type) { switch (cmd.type) {
case 'collect': { case 'collect':
case 'collect-raw': {
const started = Date.now(); const started = Date.now();
const resp = await client.get('/admin/status.xml'); const resp = await client.get('/admin/status.xml');
const elapsedMs = Date.now() - started; const elapsedMs = Date.now() - started;
@ -256,9 +259,22 @@ async function dispatch(cmd) {
err.code = 'BASE_BAD_STATUS'; err.code = 'BASE_BAD_STATUS';
throw err; throw err;
} }
const parsed = parseStatusXml(resp.data); const rawXml = resp.data;
const parsed = parseStatusXml(rawXml);
const verdict = summarizeBaseHealth(parsed); const verdict = summarizeBaseHealth(parsed);
return { parsed, verdict, fetchedInMs: elapsedMs }; const base = { parsed, verdict, fetchedInMs: elapsedMs };
const wantRaw = cmd.includeRaw === true
|| cmd.includeRaw === 'true'
|| cmd.type === 'collect-raw';
if (wantRaw) {
return {
...base,
rawXml,
byteLength: Buffer.byteLength(rawXml, 'utf8'),
sectionInventory: inventoryStatusXml(rawXml),
};
}
return base;
} }
// All mutating actions are one-shot GETs (see integrations/cisco- // All mutating actions are one-shot GETs (see integrations/cisco-

View file

@ -169,6 +169,30 @@ app.get('/phone/devices/build/:storeNumber', dataAuthGate, async (req, res) => {
} }
}); });
import { captureDectRawXml } from './services/dectStatus/captureRawXml.js';
app.get('/api/dect/raw-xml/:storeNumber', dataAuthGate, async (req, res) => {
const storeNumber = (req.params.storeNumber || '').trim();
const baseFilter = (req.query.base || req.query.ip || req.query.mac || '').trim();
try {
const result = await captureDectRawXml(storeNumber, { baseFilter: baseFilter || undefined });
res.json(result);
} catch (err) {
const code = err?.code || 'CAPTURE_FAILED';
const status = code === 'INVALID_STORE' ? 400
: code === 'BASE_NOT_FOUND' ? 404
: code === 'RELAY_NOT_CONFIGURED' ? 503
: 500;
logger('dect:capture', `raw-xml failed for store ${storeNumber}: ${err.message}`, 'error');
res.status(status).json({
success: false,
code,
message: err.message,
knownBases: err.knownBases || undefined,
});
}
});
app.get('/api/av/device/:storeNumber/:identifier', dataAuthGate, async (req, res) => { app.get('/api/av/device/:storeNumber/:identifier', dataAuthGate, async (req, res) => {
const result = await getShapedDeviceData(req.params.storeNumber, req.params.identifier); const result = await getShapedDeviceData(req.params.storeNumber, req.params.identifier);
res.json(result); res.json(result);

View file

@ -172,6 +172,10 @@ export function parseStatusXml(xml) {
const emergency = extractEmergencyNumbers(sys.Emergency_Calls); const emergency = extractEmergencyNumbers(sys.Emergency_Calls);
const deviceFwu = extractDeviceFwu(sys.Device_FWU_Info); const deviceFwu = extractDeviceFwu(sys.Device_FWU_Info);
const rssi = extractRssiList(sys.RSSI_List); const rssi = extractRssiList(sys.RSSI_List);
const devicePresence = extractDevicePresence(sys.Device_Presence);
const sipIdentityStatus = extractSipIdentityStatus(sys.SIP_Identity_Status);
const devices = extractDeviceInformation(root);
const perRpnStats = extractPerRpnStatistics(stats);
return { return {
device: { device: {
@ -236,13 +240,16 @@ export function parseStatusXml(xml) {
}, },
emergencyNumbers: emergency, emergencyNumbers: emergency,
rssi, rssi,
devicePresence,
sipIdentityStatus,
devices,
perRpnStats,
features: { features: {
pushToTalk: (str(sys.Push_To_Talk) || '').toLowerCase() === 'on', pushToTalk: (str(sys.Push_To_Talk) || '').toLowerCase() === 'on',
}, },
// The Statistics/Header_Line_Idx tag is a CSV schema descriptor // The Statistics/Header_Line_Idx tag is a CSV schema descriptor
// for a companion (per-RPN) statistics section we haven't // for companion per-RPN statistics rows (see perRpnStats).
// captured yet. Kept as raw so a future collector can align to statisticsHeader: str(stats.Header_Line_Idx),
// it without reparsing here.
_rawStatisticsHeader: str(stats.Header_Line_Idx), _rawStatisticsHeader: str(stats.Header_Line_Idx),
}; };
} }
@ -255,9 +262,27 @@ export function parseStatusXml(xml) {
// shows up as "unknown" instead of being silently reclassified. // shows up as "unknown" instead of being silently reclassified.
const KNOWN_REBOOT_CODES = new Map([ const KNOWN_REBOOT_CODES = new Map([
[21, { key: 'normal', severity: 'info', label: 'Normal reboot (admin- or firmware-initiated)' }], [21, { key: 'normal', severity: 'info', label: 'Normal reboot (admin- or firmware-initiated)' }],
[43, { key: 'unexpected', severity: 'warn', label: 'Unexpected reboot' }],
[80, { key: 'power-loss', severity: 'warn', label: 'Power loss — mains interruption or PoE glitch' }], [80, { key: 'power-loss', severity: 'warn', label: 'Power loss — mains interruption or PoE glitch' }],
]); ]);
/** Reboot reason codes that affect health when seen within the warn window. */
const REBOOT_HEALTH_WARN_CODES = new Set([43, 80]);
const REBOOT_HEALTH_WARN_DAYS = 7;
function rebootEntryAgeMs(entry) {
if (!entry?.at) return null;
const t = Date.parse(entry.at);
return Number.isFinite(t) ? Date.now() - t : null;
}
function isRecentRebootConcern(entry, maxAgeMs = REBOOT_HEALTH_WARN_DAYS * 86400000) {
if (!REBOOT_HEALTH_WARN_CODES.has(entry?.reasonCode)) return false;
const ageMs = rebootEntryAgeMs(entry);
if (ageMs == null) return false;
return ageMs >= 0 && ageMs <= maxAgeMs;
}
/** /**
* Compute a pure-function health verdict from a parsed status object. * Compute a pure-function health verdict from a parsed status object.
* No I/O. Returns { healthy, warnings, info } where warnings is an * No I/O. Returns { healthy, warnings, info } where warnings is an
@ -277,12 +302,14 @@ export function summarizeBaseHealth(parsed) {
warnings.push(`Base rebooted very recently (uptime ${Math.round(uptimeSec / 60)} min)`); warnings.push(`Base rebooted very recently (uptime ${Math.round(uptimeSec / 60)} min)`);
} }
// Reboot log — surface any power-loss in the last 6 boots (that's // Reboot log — power-loss or unexpected reboot within the last 7 days.
// literally as far back as the device remembers). const badReboots = (parsed.rebootLog || []).filter((entry) => isRecentRebootConcern(entry));
const powerLosses = (parsed.rebootLog || []).filter((r) => r.reasonCode === 80); if (badReboots.length > 0) {
if (powerLosses.length > 0) { const latest = badReboots[0];
const label = latest.reasonName || `code ${latest.reasonCode}`;
warnings.push( warnings.push(
`${powerLosses.length} recent power-loss reboot(s); most recent at ${powerLosses[0].at}`, `${badReboots.length} concerning reboot(s) in the last ${REBOOT_HEALTH_WARN_DAYS} days ` +
`(latest: ${label} at ${latest.at})`,
); );
} }
@ -362,11 +389,23 @@ function normalizeMac(mac) {
* We normalize to a role token + keep the flavor text. * We normalize to a role token + keep the flavor text.
*/ */
function parseMultiCell(raw) { function parseMultiCell(raw) {
if (!raw) return { role: null, raw: null }; if (!raw) return { role: null, state: null, raw: null };
const s = String(raw); const s = String(raw).trim();
const m = s.match(/^([A-Za-z]+)/);
const leadRole = s.match(/^(Primary|Secondary|Unchained)\b/i);
if (leadRole) {
return { role: leadRole[1].toLowerCase(), state: null, raw: s };
}
const leadState = s.match(/^([A-Za-z]+)/);
const state = leadState ? leadState[1].toLowerCase() : null;
const trailRole = s.match(/\)\s+(Primary|Secondary)\b/i);
const role = trailRole ? trailRole[1].toLowerCase() : null;
return { return {
role: m ? m[1].toLowerCase() : null, // "unchained" | "primary" | "secondary" role,
state: role && state && role !== state ? state : null,
raw: s, raw: s,
}; };
} }
@ -377,10 +416,16 @@ function parseMultiCell(raw) {
*/ */
function parseOperatingSeconds(v) { function parseOperatingSeconds(v) {
if (!v) return null; if (!v) return null;
const m = String(v).match(/(\d+):(\d+):(\d+)/); const s = String(v);
const daysMatch = s.match(/(\d+)\s+Days?\s+(\d+):(\d+):(\d+)/i);
if (daysMatch) {
const [, d, h, mi, sec] = daysMatch;
return Number(d) * 86400 + Number(h) * 3600 + Number(mi) * 60 + Number(sec);
}
const m = s.match(/(\d+):(\d+):(\d+)/);
if (!m) return null; if (!m) return null;
const [, h, mi, s] = m; const [, h, mi, sec] = m;
return Number(h) * 3600 + Number(mi) * 60 + Number(s); return Number(h) * 3600 + Number(mi) * 60 + Number(sec);
} }
function collectRebootLog(rebootLogNode) { function collectRebootLog(rebootLogNode) {
@ -430,10 +475,209 @@ function extractDeviceFwu(node) {
function extractRssiList(node) { function extractRssiList(node) {
if (!node || typeof node !== 'object') return []; if (!node || typeof node !== 'object') return [];
// RSSI_List can be empty (as in our sample) or contain child const out = [];
// <RPN_X> entries. Whatever's here, we surface raw and let a
// future parser refine once we've seen a populated example. for (const entry of extractKeyedEntries(node, /^RPN_/)) {
const keys = Object.keys(node); out.push({ ...parseRssiEntry(entry.key, entry.value), raw: entry.value });
if (keys.length === 0) return []; }
return keys.map((k) => ({ key: k, value: node[k] }));
for (const entry of extractKeyedEntries(node, /^RSSI_Line_/)) {
const parsed = parseRssiLineText(entry.value);
if (parsed) out.push({ ...parsed, raw: entry.value });
}
return out;
}
/**
* Parse RSSI_List child text. Shapes seen in the field:
* "MAC:6CAB05A1B2C3; RSSI:-58 dBm" (lab / synthetic)
* "RSSI for RPN:4 is -49 [dBm]" (store 933 live capture)
*/
function parseRssiEntry(key, value) {
const rpnMatch = key.match(/^RPN_(\d+)$/i);
const rpn = rpnMatch ? rpnMatch[1] : null;
const text = String(value || '');
const macMatch = text.match(/MAC:\s*([0-9A-Fa-f:]+)/i);
const rssiMatch = text.match(/RSSI:\s*(-?\d+)/i)
|| text.match(/(-?\d+)\s*dBm/i);
return {
rpn,
mac: macMatch ? normalizeMac(macMatch[1]) : null,
rssiDbm: rssiMatch ? Number(rssiMatch[1]) : null,
};
}
function parseRssiLineText(text) {
const m = String(text || '').match(/RPN:(\d+)\s+is\s+(-?\d+)/i);
if (!m) return null;
return { rpn: m[1], rssiDbm: Number(m[2]), mac: null };
}
function extractDevicePresence(node) {
if (!node || typeof node !== 'object') return [];
const rpnEntries = extractKeyedEntries(node, /^RPN_/).map((e) => ({
key: e.key,
present: /present/i.test(e.value),
raw: e.value,
}));
if (rpnEntries.length > 0) return rpnEntries;
return extractKeyedEntries(node, /^Device_Line_/).map((e) => {
const m = e.value.match(
/Device:([^\s]+)\s+(present|absent)\s+at\s+Extension\s+(\d+)/i,
);
return {
key: e.key,
deviceType: m?.[1] || null,
present: m ? /present/i.test(m[2]) : null,
extension: m?.[3] || null,
raw: e.value,
};
});
}
function extractSipIdentityStatus(node) {
if (!node || typeof node !== 'object') return [];
const uaEntries = Object.entries(node)
.filter(([k]) => /^SIP_UA_Id_/i.test(k))
.sort(([a], [b]) => Number(a.match(/\d+$/)[0]) - Number(b.match(/\d+$/)[0]));
if (uaEntries.length > 0) {
return uaEntries.map(([key, val]) => {
if (typeof val !== 'object' || val == null) {
return { key, raw: String(val) };
}
return {
key,
sipIdx: str(val.SIP_Idx),
serverName: str(val.Server_Name),
sipUri: str(val.SIP_URI),
status: str(val.Status),
};
});
}
return extractKeyedEntries(node, /^Line_/).map((e) => ({
key: e.key,
status: e.value,
raw: e.value,
}));
}
function extractDeviceInformation(root) {
const di = (typeof root === 'object' && root.Device_Information) || {};
if (!di || typeof di !== 'object') return [];
return Object.entries(di)
.filter(([k]) => /^Device_Idx\d+$/i.test(k))
.sort(([a], [b]) => Number(a.match(/\d+$/)[0]) - Number(b.match(/\d+$/)[0]))
.map(([key, dev]) => parseDeviceIdxEntry(key, dev))
.filter(Boolean);
}
function parseDeviceIdxEntry(key, dev) {
if (!dev || typeof dev !== 'object') return null;
const indexMatch = key.match(/(\d+)$/);
const index = indexMatch ? Number(indexMatch[1]) : null;
const battery = dev.Battery_RSSI_Info || {};
const location = dev.Location_Info || {};
const dect = dev.DECT_Info || {};
const sipAccount = findSipAccountDisplay(dev.SIP_Account);
const rssiRaw = str(battery.RSSI);
const rssiMatch = rssiRaw?.match(/(-?\d+)/);
const batteryRaw = str(battery.Battery_level);
const batteryMatch = batteryRaw?.match(/(\d+)/);
return {
index,
deviceType: str(dev.Device_Type),
ipei: str(dev.Ipei),
firmware: str(dev.SW_Version),
displayName: sipAccount?.displayName || null,
sipNumber: sipAccount?.number || null,
sipState: sipAccount?.state || null,
rssiDbm: rssiMatch ? Number(rssiMatch[1]) : null,
batteryPercent: batteryMatch ? Number(batteryMatch[1]) : null,
registeredRpn: str(location.RPN),
lockedRpn: str(location.Locked_RPN),
dectState: str(dect.DECT_State),
fwuProgress: str(dev.FWU_Progress),
};
}
function findSipAccountDisplay(sipAccountNode) {
if (!sipAccountNode || typeof sipAccountNode !== 'object') return null;
for (const val of Object.values(sipAccountNode)) {
if (!val || typeof val !== 'object') continue;
const displayName = str(val.Display_Name);
const number = str(val.Number);
const state = str(val.State);
if (displayName || number) {
return { displayName, number, state };
}
}
return null;
}
function extractKeyedEntries(node, keyPattern) {
if (!node || typeof node !== 'object') return [];
return Object.entries(node)
.filter(([k]) => keyPattern.test(k))
.sort(([a], [b]) => {
const na = Number((a.match(/\d+$/) || [0])[0]);
const nb = Number((b.match(/\d+$/) || [0])[0]);
return na - nb;
})
.map(([key, value]) => ({
key,
value: typeof value === 'string' ? value.trim() : String(value ?? ''),
}))
.filter((e) => e.value);
}
function extractPerRpnStatistics(statsNode) {
if (!statsNode || typeof statsNode !== 'object') return [];
const header = str(statsNode.Header_Line_Idx) || '';
const columns = header.split(',').map((c) => c.trim()).filter(Boolean);
const lines = Object.entries(statsNode)
.filter(([k]) => /^Statistics_Line_\d+$/.test(k) || /^Value_Line_\d+$/.test(k))
.sort(([a], [b]) => Number(a.match(/\d+$/)[0]) - Number(b.match(/\d+$/)[0]))
.map(([, v]) => (typeof v === 'string' ? v.trim() : ''));
return lines.filter(Boolean).map((line) => {
const parts = parseStatisticsCsvLine(line);
const row = { raw: line };
if (columns.length === parts.length) {
for (let i = 0; i < columns.length; i++) {
const col = columns[i].toLowerCase();
if (/^rpn$/i.test(col)) row.rpn = parts[i].replace(/^'|'$/g, '');
else if (/mac/i.test(col)) row.mac = normalizeMac(parts[i]);
else if (/^op\[s\]/i.test(col)) row.opSeconds = numOrNull(parts[i]);
else if (/^dt\[s\]/i.test(col)) row.dtSeconds = numOrNull(parts[i]);
else if (/call cnt/i.test(col)) row.callCount = numOrNull(parts[i]);
}
} else if (parts.length >= 4) {
row.rpn = parts[0].replace(/^'|'$/g, '');
row.mac = normalizeMac(parts[1]);
row.opSeconds = numOrNull(parts[2]);
row.dtSeconds = numOrNull(parts[3]);
if (parts.length >= 5) row.callCount = numOrNull(parts[4]);
}
return row;
});
}
function parseStatisticsCsvLine(line) {
const parts = [];
const re = /'([^']*)'|([^,]+)/g;
let m;
while ((m = re.exec(line)) !== null) {
parts.push((m[1] != null ? m[1] : m[2]).trim());
}
return parts.length > 0 ? parts : line.split(',').map((p) => p.trim());
} }

View file

@ -0,0 +1,91 @@
// integrations/cisco-dect/statusXmlInventory.js
//
// Walk a parsed status.xml tree and list every leaf path with a short
// preview. Used by collect-raw tooling to spot populated sections
// (RSSI_List, Device_Presence, SIP_Identity_Status, etc.) without
// hand-reading XML.
import { xmlToObject } from './statusXml.js';
const HANDSET_RELATED = /rssi|presence|sip_identity|device_line|statistics|rpn|mac/i;
/**
* @typedef {object} SectionInventoryEntry
* @property {string} path dot-separated path from root
* @property {boolean} isEmpty true when leaf is absent or whitespace-only
* @property {string|null} preview truncated leaf value (null for empty containers)
* @property {boolean} handsetRelated heuristic flag for operator summaries
*/
/**
* Build a flat inventory of leaf paths from raw status.xml text.
*
* @param {string} xml
* @returns {SectionInventoryEntry[]}
*/
export function inventoryStatusXml(xml) {
if (typeof xml !== 'string' || !xml.trim()) return [];
const tree = xmlToObject(xml);
const entries = [];
walk(tree, '', entries);
return entries;
}
/**
* Summarize non-empty handset-related paths for console output.
*
* @param {SectionInventoryEntry[]} inventory
* @returns {{ nonEmpty: number, handsetRelated: number, paths: string[] }}
*/
export function summarizeInventory(inventory) {
const list = Array.isArray(inventory) ? inventory : [];
const nonEmpty = list.filter((e) => !e.isEmpty);
const handsetPaths = nonEmpty
.filter((e) => e.handsetRelated)
.map((e) => e.path);
return {
nonEmpty: nonEmpty.length,
handsetRelated: handsetPaths.length,
paths: handsetPaths,
};
}
function walk(node, prefix, out) {
if (node == null) {
pushLeaf(prefix, '', out);
return;
}
if (typeof node === 'string') {
pushLeaf(prefix, node, out);
return;
}
if (typeof node !== 'object') {
pushLeaf(prefix, String(node), out);
return;
}
const keys = Object.keys(node);
if (keys.length === 0) {
pushLeaf(prefix, '', out);
return;
}
for (const key of keys) {
const path = prefix ? `${prefix}.${key}` : key;
walk(node[key], path, out);
}
}
function pushLeaf(path, value, out) {
const str = value == null ? '' : String(value).trim();
const isEmpty = str === '';
out.push({
path,
isEmpty,
preview: isEmpty ? null : truncate(str, 120),
handsetRelated: HANDSET_RELATED.test(path),
});
}
function truncate(s, max) {
if (s.length <= max) return s;
return `${s.slice(0, max - 1)}`;
}

View file

@ -10,6 +10,7 @@
// the Jira poller in Phase 3) owns presentation. // the Jira poller in Phase 3) owns presentation.
import { getDectRelayHub, RelayErrorCodes } from './dectRelayHub.js'; import { getDectRelayHub, RelayErrorCodes } from './dectRelayHub.js';
import { parseStatusXml, summarizeBaseHealth } from '../integrations/cisco-dect/statusXml.js';
import { logger } from '../utils/logger.js'; import { logger } from '../utils/logger.js';
const LOG_SCOPE = 'dect:collector'; const LOG_SCOPE = 'dect:collector';
@ -65,6 +66,119 @@ export async function collectAll(bases, opts = {}) {
* command's individual "refresh this base" flow collectAll uses it * command's individual "refresh this base" flow collectAll uses it
* internally. * internally.
*/ */
/**
* @typedef {object} BaseCollectRawResult
* @property {BaseTarget} base
* @property {boolean} ok
* @property {string|null} rawXml
* @property {number|null} byteLength
* @property {object|null} data
* @property {object|null} verdict
* @property {Array|null} sectionInventory
* @property {number|null} elapsedMs
* @property {object|null} error
*/
/**
* Run `collect-raw` against every base (includes raw XML + inventory).
*
* @param {BaseTarget[]} bases
* @param {object} [opts]
* @returns {Promise<BaseCollectRawResult[]>}
*/
export async function collectRawAll(bases, opts = {}) {
const list = Array.isArray(bases) ? bases : [];
if (list.length === 0) return [];
const hub = opts.hub || getDectRelayHub();
const timeoutMs = opts.timeoutMs || DEFAULT_TIMEOUT_MS;
logger(LOG_SCOPE, `Fanning out collect-raw() to ${list.length} base(s)`, 'debug');
const results = await Promise.all(list.map((base) => collectRawOne(hub, base, timeoutMs)));
const okCount = results.filter((r) => r.ok).length;
logger(LOG_SCOPE, `Collect-raw finished: ${okCount}/${list.length} succeeded`, 'debug');
return results;
}
export async function collectRawOne(hub, base, timeoutMs = DEFAULT_TIMEOUT_MS) {
if (!base?.ip) {
return {
base, ok: false, rawXml: null, byteLength: null, data: null, verdict: null,
sectionInventory: null, elapsedMs: null,
error: { code: 'NO_IP', message: 'base has no IP address' },
};
}
const started = Date.now();
try {
let result;
let elapsedMs;
try {
({ result, elapsedMs } = await hub.collectRaw(base.ip, { timeoutMs }));
} catch (err) {
if (err?.code === 'UNKNOWN_COMMAND' || /unknown command type/i.test(err?.message || '')) {
({ result, elapsedMs } = await hub.collectRawLegacy(base.ip, { timeoutMs }));
} else {
throw err;
}
}
if (!result?.rawXml) {
return {
base,
ok: false,
rawXml: null,
byteLength: null,
data: result?.parsed || null,
verdict: result?.verdict || null,
sectionInventory: null,
elapsedMs: elapsedMs ?? (Date.now() - started),
error: {
code: 'AGENT_OUTDATED',
message: 'Relay agent returned parsed status but not raw XML',
hint:
'Redeploy dect-relay-agent on the DC host (separate from the bot container). ' +
'From the repo root: ./dect-relay-agent/bundle.sh, then install on the DC host ' +
'and docker compose restart dect-relay-agent. Agent v0.2.0+ supports includeRaw on collect.',
},
};
}
return {
base,
ok: true,
rawXml: result.rawXml,
byteLength: result.byteLength ?? null,
data: result.parsed || null,
verdict: result.verdict || null,
sectionInventory: result.sectionInventory || null,
elapsedMs: elapsedMs ?? (Date.now() - started),
error: null,
};
} catch (err) {
const code = err?.code || 'UNKNOWN';
const hint = hintFor(code)
|| (/unknown command type/i.test(err?.message || '')
? 'Redeploy dect-relay-agent on the DC host — the bot container does not run the relay agent.'
: null);
return {
base,
ok: false,
rawXml: null,
byteLength: null,
data: null,
verdict: null,
sectionInventory: null,
elapsedMs: Date.now() - started,
error: {
code,
message: err?.message || String(err),
hint,
},
};
}
}
export async function collectOne(hub, base, timeoutMs = DEFAULT_TIMEOUT_MS) { export async function collectOne(hub, base, timeoutMs = DEFAULT_TIMEOUT_MS) {
if (!base?.ip) { if (!base?.ip) {
return { return {
@ -74,12 +188,33 @@ export async function collectOne(hub, base, timeoutMs = DEFAULT_TIMEOUT_MS) {
} }
const started = Date.now(); const started = Date.now();
try { try {
const { result, elapsedMs } = await hub.collect(base.ip, { timeoutMs }); let result;
let elapsedMs;
try {
({ result, elapsedMs } = await hub.collectRaw(base.ip, { timeoutMs }));
} catch (err) {
if (err?.code === 'UNKNOWN_COMMAND' || /unknown command type/i.test(err?.message || '')) {
try {
({ result, elapsedMs } = await hub.collectRawLegacy(base.ip, { timeoutMs }));
} catch {
({ result, elapsedMs } = await hub.collect(base.ip, { timeoutMs }));
}
} else {
throw err;
}
}
// Prefer bot-side parsing so handset/RSSI fields stay current even when
// the DC relay agent ships an older statusXml.js.
const data = result?.rawXml
? parseStatusXml(result.rawXml)
: (result?.parsed || result || null);
return { return {
base, base,
ok: true, ok: true,
data: result?.parsed || result || null, data,
verdict: result?.verdict || null, verdict: data ? summarizeBaseHealth(data) : (result?.verdict || null),
elapsedMs: elapsedMs ?? (Date.now() - started), elapsedMs: elapsedMs ?? (Date.now() - started),
error: null, error: null,
}; };

View file

@ -82,6 +82,7 @@ export function discoverDectBases(phoneStatus) {
mac, mac,
ip, ip,
name, name,
webexId: base.id || null,
// Track where the IP came from — useful in logs if a base is // Track where the IP came from — useful in logs if a base is
// reachable via one source but not the other. // reachable via one source but not the other.
source: base.meraki?.ip ? 'meraki' : 'webex', source: base.meraki?.ip ? 'meraki' : 'webex',

View file

@ -370,6 +370,16 @@ export class DectRelayHub {
return this.rpc({ type: 'collect', baseIp }, opts); return this.rpc({ type: 'collect', baseIp }, opts);
} }
/** Fetch status.xml including raw XML + section inventory (capture tooling). */
collectRaw(baseIp, opts) {
return this.rpc({ type: 'collect', baseIp, includeRaw: true }, opts);
}
/** Legacy alias — some agents only expose collect-raw as a distinct type. */
collectRawLegacy(baseIp, opts) {
return this.rpc({ type: 'collect-raw', baseIp }, opts);
}
/** /**
* Convenience: execute one of the mutating actions the agent * Convenience: execute one of the mutating actions the agent
* exposes (reboot / force-reboot / reboot-chain / force-reboot-chain * exposes (reboot / force-reboot / reboot-chain / force-reboot-chain

View file

@ -0,0 +1,70 @@
// services/dectStatus/buildHandsetContext.js
//
// Group Webex DECT handset inventory for /dectstatus rendering.
/**
* @param {object|null} phoneData collectPhoneStatus() output
* @returns {object}
*/
export function buildHandsetContext(phoneData) {
const basestations = Array.isArray(phoneData?.dectBasestations)
? phoneData.dectBasestations
: [];
const handsets = Array.isArray(phoneData?.dectHandsets)
? phoneData.dectHandsets
: [];
const handsetsByWebexId = new Map();
const linesRegisteredByWebexId = new Map();
const webexIdByMac = new Map();
for (const base of basestations) {
if (base.id) {
handsetsByWebexId.set(base.id, []);
linesRegisteredByWebexId.set(base.id, base.linesRegistered ?? null);
}
const mac = normalizeMacLoose(base.mac);
if (base.id && mac) webexIdByMac.set(mac, base.id);
}
const unassignedHandsets = [];
for (const h of handsets) {
const baseId = h.baseStationId || null;
if (baseId && handsetsByWebexId.has(baseId)) {
handsetsByWebexId.get(baseId).push(h);
} else if (!baseId) {
unassignedHandsets.push(h);
} else {
// Assigned to a base id we don't know — treat as unassigned for display.
unassignedHandsets.push(h);
}
}
return {
handsetsByWebexId,
linesRegisteredByWebexId,
webexIdByMac,
unassignedHandsets,
dectNetwork: phoneData?.dectNetwork || null,
};
}
/**
* Handsets registered to a base (by Webex base id).
*
* @param {object} handsetCtx buildHandsetContext() output
* @param {string|null} webexId
* @returns {object[]}
*/
export function handsetsForBase(handsetCtx, webexId) {
if (!webexId || !handsetCtx?.handsetsByWebexId) return [];
return handsetCtx.handsetsByWebexId.get(webexId) || [];
}
function normalizeMacLoose(mac) {
if (!mac || typeof mac !== 'string') return null;
const hex = mac.replace(/[^0-9a-fA-F]/g, '').toLowerCase();
if (hex.length !== 12) return null;
return hex.match(/../g).join(':');
}

View file

@ -0,0 +1,93 @@
// services/dectStatus/captureRawXml.js
//
// Fetch raw status.xml from store DECT bases via the on-prem relay.
// Used by GET /api/dect/raw-xml/:storeNumber and scripts/fetchDectStatusXml.js.
import { collectPhoneStatus } from '../phoneService.js';
import { discoverDectBases } from '../dectDiscovery.js';
import { collectRawAll } from '../dectCollectorService.js';
import { getDectRelayHub } from '../dectRelayHub.js';
import { logger } from '../../utils/logger.js';
const LOG_SCOPE = 'dect:capture';
/**
* @param {string} storeNum
* @param {object} [opts]
* @param {string} [opts.baseFilter] optional IP or MAC filter
* @returns {Promise<object>}
*/
export async function captureDectRawXml(storeNum, opts = {}) {
const store = String(storeNum || '').trim();
if (!store || !/^\d{2,4}$/.test(store)) {
const err = new Error('storeNum must be a 24 digit store number');
err.code = 'INVALID_STORE';
throw err;
}
if (!process.env.DECT_RELAY_AGENT_TOKEN) {
const err = new Error('DECT_RELAY_AGENT_TOKEN is not configured on this bot');
err.code = 'RELAY_NOT_CONFIGURED';
throw err;
}
let relay = null;
try {
relay = getDectRelayHub().status();
} catch (err) {
logger(LOG_SCOPE, `Relay status unavailable: ${err.message}`, 'warn');
relay = { connected: false };
}
const phoneData = await collectPhoneStatus(store);
const { bases, warnings: discoveryWarnings } = discoverDectBases(phoneData || {});
let targets = bases;
const baseFilter = (opts.baseFilter || '').trim();
if (baseFilter) {
targets = filterBases(bases, baseFilter);
if (targets.length === 0) {
const err = new Error(`No discovered base matched "${baseFilter}" for store ${store}`);
err.code = 'BASE_NOT_FOUND';
err.knownBases = bases.map((b) => ({ ip: b.ip, mac: b.mac, name: b.name }));
throw err;
}
}
const results = await collectRawAll(targets);
return {
storeNum: store,
relay,
discoveryWarnings,
bases: results.map((r) => ({
mac: r.base?.mac || null,
ip: r.base?.ip || null,
webexId: r.base?.webexId || null,
name: r.base?.name || null,
ok: r.ok,
byteLength: r.byteLength,
rawXml: r.rawXml,
sectionInventory: r.sectionInventory,
parsed: r.data,
verdict: r.verdict,
elapsedMs: r.elapsedMs,
error: r.error,
})),
};
}
function filterBases(bases, raw) {
const needle = String(raw).trim().toLowerCase();
const needleMac = needle.replace(/[^0-9a-f]/g, '');
return bases.filter((b) => {
if (b.ip && b.ip.toLowerCase() === needle) return true;
if (b.mac) {
const macHex = b.mac.replace(/[^0-9a-fA-F]/g, '').toLowerCase();
if (macHex === needleMac && needleMac.length === 12) return true;
if (b.mac.toLowerCase() === needle) return true;
}
if (b.name && b.name.toLowerCase().includes(needle)) return true;
return false;
});
}

View file

@ -0,0 +1,50 @@
// services/dectStatus/matchHandsetByMac.js
//
// Correlate base-local RSSI / statistics rows with Webex handset records.
import { normalizeMac } from '../dectDiscovery.js';
/**
* @param {object|null} handset Webex handset record
* @param {object} rssiRow parsed RSSI entry from status.xml
* @returns {boolean}
*/
export function handsetMatchesRssiRow(handset, rssiRow) {
const hMac = normalizeMac(handset?.mac);
const rMac = normalizeMac(rssiRow?.mac);
return !!(hMac && rMac && hMac === rMac);
}
/**
* Find the Webex handset matching an RSSI row (by MAC).
*
* @param {object} rssiRow
* @param {object[]} handsets
* @returns {object|null}
*/
export function findHandsetForRssiRow(rssiRow, handsets) {
const list = Array.isArray(handsets) ? handsets : [];
return list.find((h) => handsetMatchesRssiRow(h, rssiRow)) || null;
}
/**
* Build display rows merging RSSI with optional Webex handset metadata.
*
* @param {object[]} rssiRows parsed status.rssi
* @param {object[]} handsets Webex handsets for this base
* @returns {object[]}
*/
export function mergeRssiWithHandsets(rssiRows, handsets) {
const rows = Array.isArray(rssiRows) ? rssiRows : [];
const list = Array.isArray(handsets) ? handsets : [];
return rows.map((row) => {
const handset = findHandsetForRssiRow(row, list);
return {
rpn: row.rpn,
mac: row.mac,
rssiDbm: row.rssiDbm,
handsetName: handset?.name || null,
extension: handset?.extension || null,
};
});
}

View file

@ -1,15 +1,11 @@
// src/services/renderers/dectStatusRenderer.js // src/services/renderers/dectStatusRenderer.js
// //
// Full CLI-style DECT base dump for `/dectstatus`. Complements the // Full CLI-style DECT base dump for `/dectstatus`. Complements the
// compact follow-up from renderDectDiagnosticsMarkdown (phonestatus): // compact follow-up from renderDectDiagnosticsMarkdown (phonestatus).
// that one is "exceptions only"; this one is the complete status.xml
// picture (device, firmware, reboot log, network, RTP, security,
// emergency numbers, health verdict).
//
// Input is the same collectAll() result array used by the compact
// renderer. Pure — no I/O, no env.
import { formatDisplayTime } from '../../utils/time.js'; import { formatDisplayTime, simpleTimeAgo } from '../../utils/time.js';
import { handsetsForBase } from '../dectStatus/buildHandsetContext.js';
import { mergeRssiWithHandsets } from '../dectStatus/matchHandsetByMac.js';
/** /**
* @param {Array} results collectAll() output * @param {Array} results collectAll() output
@ -18,6 +14,7 @@ import { formatDisplayTime } from '../../utils/time.js';
* @param {boolean} [opts.footer=true] * @param {boolean} [opts.footer=true]
* @param {object} [opts.relay] optional hub.status() snapshot * @param {object} [opts.relay] optional hub.status() snapshot
* @param {Array} [opts.discoveryWarnings] * @param {Array} [opts.discoveryWarnings]
* @param {object} [opts.handsetCtx] buildHandsetContext() output
* @returns {string} * @returns {string}
*/ */
export function renderDectStatusMarkdown(results, opts = {}) { export function renderDectStatusMarkdown(results, opts = {}) {
@ -26,11 +23,18 @@ export function renderDectStatusMarkdown(results, opts = {}) {
footer = true, footer = true,
relay = null, relay = null,
discoveryWarnings = [], discoveryWarnings = [],
handsetCtx = null,
} = opts; } = opts;
const list = Array.isArray(results) ? results : []; const list = Array.isArray(results) ? results : [];
const lines = [`**DECT Status — Store ${storeNum}**`, '']; const lines = [`**DECT Status — Store ${storeNum}**`, ''];
if (handsetCtx?.dectNetwork) {
const net = handsetCtx.dectNetwork;
lines.push(`_DECT network: ${net.name || '—'} (assigned handsets: ${net.handsetsCount ?? '—'})_`);
lines.push('');
}
if (relay) { if (relay) {
if (relay.connected) { if (relay.connected) {
const agent = relay.agent?.hostname || relay.agent?.version || 'connected'; const agent = relay.agent?.hostname || relay.agent?.version || 'connected';
@ -51,6 +55,7 @@ export function renderDectStatusMarkdown(results, opts = {}) {
lines.push(`- ${who}: ${w.reason}`); lines.push(`- ${who}: ${w.reason}`);
} }
} }
lines.push(...renderUnassignedHandsets(handsetCtx));
if (footer) { if (footer) {
lines.push(''); lines.push('');
lines.push(`_Pulled at ${formatDisplayTime()}._`); lines.push(`_Pulled at ${formatDisplayTime()}._`);
@ -60,9 +65,11 @@ export function renderDectStatusMarkdown(results, opts = {}) {
for (let i = 0; i < list.length; i++) { for (let i = 0; i < list.length; i++) {
if (i > 0) lines.push('', '---', ''); if (i > 0) lines.push('', '---', '');
lines.push(...renderOneBaseFull(list[i])); lines.push(...renderOneBaseFull(list[i], handsetCtx));
} }
lines.push(...renderUnassignedHandsets(handsetCtx));
if (discoveryWarnings.length > 0) { if (discoveryWarnings.length > 0) {
lines.push('', '**Discovery notes (skipped bases):**'); lines.push('', '**Discovery notes (skipped bases):**');
for (const w of discoveryWarnings) { for (const w of discoveryWarnings) {
@ -82,10 +89,11 @@ export function renderDectStatusMarkdown(results, opts = {}) {
return lines.join('\n').trim(); return lines.join('\n').trim();
} }
function renderOneBaseFull(r) { function renderOneBaseFull(r, handsetCtx) {
const label = r.base?.name || `Basestation ${r.base?.mac || '?'}`; const label = r.base?.name || `Basestation ${r.base?.mac || '?'}`;
const ip = r.base?.ip || '?'; const ip = r.base?.ip || '?';
const mac = r.base?.mac || '?'; const mac = r.base?.mac || '?';
const webexId = r.base?.webexId || null;
const lines = []; const lines = [];
if (!r.ok) { if (!r.ok) {
@ -94,6 +102,7 @@ function renderOneBaseFull(r) {
lines.push(`- Collect failed: ${r.error?.message || 'unknown error'}`); lines.push(`- Collect failed: ${r.error?.message || 'unknown error'}`);
if (r.error?.hint) lines.push(`- _${r.error.hint}_`); if (r.error?.hint) lines.push(`- _${r.error.hint}_`);
if (r.elapsedMs != null) lines.push(`- Elapsed: ${r.elapsedMs}ms`); if (r.elapsedMs != null) lines.push(`- Elapsed: ${r.elapsedMs}ms`);
lines.push(...renderHandsetsAndRfSection({}, handsetCtx, webexId));
return lines; return lines;
} }
@ -101,98 +110,248 @@ function renderOneBaseFull(r) {
const verdict = r.verdict || {}; const verdict = r.verdict || {};
const icon = verdict.healthy ? '✅' : '⚠️'; const icon = verdict.healthy ? '✅' : '⚠️';
lines.push(`${icon} **${label}**`); lines.push(`${icon} **${label}**`);
lines.push(...renderHealthNotes(verdict));
// ── Device ── lines.push(...renderHandsetsAndRfSection(p, handsetCtx, webexId));
lines.push('', '**Device**'); lines.push(...renderBaseSection(p, { mac, ip, elapsedMs: r.elapsedMs }));
row(lines, 'Model', p.device?.model); lines.push(...renderRebootLogSection(p.rebootLog));
row(lines, 'System type', p.device?.systemType); lines.push(...renderTrafficSection(p.network, p.rtp));
row(lines, 'Unit', [p.device?.unitName, p.device?.unitIndex].filter(Boolean).join(' · ') || null);
row(lines, 'MAC', p.device?.macAddress || mac);
row(lines, 'IP', p.device?.ipAddress || ip);
row(lines, 'RFPI', p.device?.rfpiAddress);
row(lines, 'RF band', p.device?.rfBand);
row(lines, 'Multi-cell', p.multiCell?.role || p.multiCell?.raw);
row(lines, 'Base status', p.baseStatus);
row(lines, 'Conflict', p.conflictInfo);
// ── Firmware ── return lines;
lines.push('', '**Firmware**'); }
row(lines, 'Version', p.firmware?.version);
row(lines, 'Update server', p.firmware?.updateServer);
row(lines, 'Update path', p.firmware?.updatePath);
// ── Time ── function renderHealthNotes(verdict) {
lines.push('', '**Time / uptime**'); const warnings = verdict.warnings || [];
row(lines, 'Local time', p.time?.currentLocalTime); const info = verdict.info || [];
row(lines, 'Uptime', p.time?.operatingTime); if (warnings.length === 0 && info.length === 0) return [];
if (r.elapsedMs != null) row(lines, 'Collect latency', `${r.elapsedMs}ms`);
// ── Reboot log ── const lines = [];
lines.push('', '**Reboot log** (newest first)'); for (const w of warnings) lines.push(`- ⚠️ ${w}`);
const log = Array.isArray(p.rebootLog) ? p.rebootLog : []; for (const i of info) lines.push(`- ${i}`);
return lines;
}
function renderBaseSection(p, { mac, ip, elapsedMs }) {
const lines = ['', '**Base**'];
const modelBits = [p.device?.model, p.device?.systemType].filter(Boolean);
if (modelBits.length) lines.push(`- ${modelBits.join(' · ')}`);
const identityBits = [
p.device?.macAddress || mac,
p.device?.ipAddress || ip,
p.device?.rfpiAddress,
p.device?.rfBand ? `${p.device.rfBand} band` : null,
].filter(Boolean);
if (identityBits.length) lines.push(`- ${identityBits.join(' · ')}`);
const unitBits = [p.device?.unitName, p.device?.unitIndex].filter(Boolean);
const statusBits = [
formatMultiCell(p.multiCell),
p.baseStatus,
p.conflictInfo,
].filter(Boolean);
const opsBits = [...unitBits, ...statusBits].filter(Boolean);
if (opsBits.length) lines.push(`- ${opsBits.join(' · ')}`);
const fw = p.firmware?.version;
const uptimeBits = [
p.time?.operatingTime ? `uptime ${p.time.operatingTime}` : null,
p.time?.currentLocalTime ? `local ${p.time.currentLocalTime}` : null,
elapsedMs != null ? `collect ${elapsedMs}ms` : null,
].filter(Boolean);
const fwLine = [fw, ...uptimeBits].filter(Boolean);
if (fwLine.length) lines.push(`- FW ${fwLine.join(' · ')}`);
return lines;
}
function renderRebootLogSection(rebootLog) {
const log = Array.isArray(rebootLog) ? rebootLog : [];
const lines = ['', '**Reboot log** (newest first)'];
if (log.length === 0) { if (log.length === 0) {
lines.push('- _(none)_'); lines.push('- _(none)_');
} else { return lines;
}
for (const entry of log) { for (const entry of log) {
if (entry.unrecognized) { if (entry.unrecognized) {
lines.push(`- ??? ${entry.raw || ''}`); lines.push(`- ??? ${entry.raw || ''}`);
continue; continue;
} }
const tag = entry.reasonCode === 80 ? '⚡' : '•'; const tag = (entry.reasonCode === 80 || entry.reasonCode === 43) ? '⚡' : '•';
lines.push( lines.push(
`- ${tag} #${entry.sequence} ${entry.at} **${entry.reasonName}** (${entry.reasonCode}) fw=${entry.firmwareAtBoot || '?'}`, `- ${tag} #${entry.sequence} ${entry.at} **${entry.reasonName}** (${entry.reasonCode}) fw=${entry.firmwareAtBoot || '?'}`,
); );
} }
return lines;
}
function renderTrafficSection(net, rtp) {
const lines = [];
const hasNet = net && Object.values(net).some((v) => v != null);
const hasRtp = rtp && Object.values(rtp).some((v) => v != null);
if (!hasNet && !hasRtp) return lines;
lines.push('', '**Network & RTP** (since boot)');
if (hasNet) {
lines.push(`- **TX:** ${formatNetCounters(net, 'tx')}`);
lines.push(`- **RX:** ${formatNetCounters(net, 'rx')}`);
} }
if (hasRtp) {
// ── Network ── lines.push(
lines.push('', '**Network stats** (since boot)'); `- **RTP:** ${netVal(rtp.total)} total · ${netVal(rtp.current)} active · ` +
row(lines, 'Tx packets', p.network?.txPackets); `${netVal(rtp.currentLocal)} local · ${netVal(rtp.currentRelay)} relay`,
row(lines, 'Tx dropped', p.network?.txDropped);
row(lines, 'Tx errors', p.network?.txErrors);
row(lines, 'Rx packets', p.network?.rxPackets);
row(lines, 'Rx dropped', p.network?.rxDropped);
row(lines, 'Rx errors', p.network?.rxErrors);
row(lines, 'Rx broadcasts', p.network?.rxBroadcasts);
// ── RTP ──
lines.push('', '**RTP**');
row(lines, 'Total since boot', p.rtp?.total);
row(lines, 'Current active', p.rtp?.current);
row(lines, 'Current local', p.rtp?.currentLocal);
row(lines, 'Current relay', p.rtp?.currentRelay);
// ── Security ──
lines.push('', '**Security**');
row(
lines,
'Custom CA',
p.security?.customCa?.installed
? (p.security.customCa.info || 'installed')
: 'Not installed',
); );
row(lines, '802.1X protocol', p.security?.dot1x?.protocol);
row(lines, '802.1X status', p.security?.dot1x?.transactionStatus);
// ── Emergency ──
const emerg = Array.isArray(p.emergencyNumbers) ? p.emergencyNumbers : [];
lines.push('', '**Emergency numbers**');
lines.push(emerg.length ? `- ${emerg.join(', ')}` : '- _(none configured)_');
// ── Verdict ──
lines.push('', '**Health verdict**');
lines.push(`- healthy: **${verdict.healthy ? 'YES' : 'NO'}**`);
for (const w of verdict.warnings || []) {
lines.push(`- ⚠️ ${w}`);
} }
for (const i of verdict.info || []) { return lines;
lines.push(`- ${i}`); }
const NET_COUNTER_FIELDS = {
tx: [
['txPackets', 'pkts'],
['txBlocked', 'blocked'],
['txDropped', 'dropped'],
['txErrors', 'errors'],
['txBroadcasts', 'bcast'],
],
rx: [
['rxPackets', 'pkts'],
['rxBlocked', 'blocked'],
['rxDropped', 'dropped'],
['rxErrors', 'errors'],
['rxBroadcasts', 'bcast'],
],
};
function formatNetCounters(net, direction) {
return NET_COUNTER_FIELDS[direction]
.map(([key, label]) => `${netVal(net[key])} ${label}`)
.join(' · ');
}
function netVal(v) {
return Number.isFinite(v) ? v : 0;
}
function formatMultiCell(multiCell) {
if (!multiCell) return null;
const role = multiCell.role ? capitalize(multiCell.role) : null;
const state = multiCell.state ? multiCell.state : null;
if (role && state) return `multi-cell ${role} (${state})`;
if (role) return `multi-cell ${role}`;
return multiCell.raw ? `multi-cell ${multiCell.raw}` : null;
}
function capitalize(s) {
return s ? s.charAt(0).toUpperCase() + s.slice(1) : s;
}
/**
* Webex handset inventory + base status.xml handset/RSSI/presence/SIP data.
*/
function renderHandsetsAndRfSection(parsed, handsetCtx, webexId) {
const lines = ['', '**Handsets & RF**'];
let hasDetail = false;
const linesReg = webexId && handsetCtx?.linesRegisteredByWebexId
? handsetCtx.linesRegisteredByWebexId.get(webexId)
: null;
if (linesReg != null) {
lines.push(`- Webex lines registered on base: **${linesReg}**`);
hasDetail = true;
}
const registered = handsetsForBase(handsetCtx, webexId);
const devices = Array.isArray(parsed?.devices) ? parsed.devices : [];
const coveredMacs = new Set(
devices.map((d) => normalizeMacLoose(d.mac)).filter(Boolean),
);
for (const h of registered) {
const idx = h.index != null ? `${h.index}-` : '';
const reg = h.lastRegistrationTime
? simpleTimeAgo(h.lastRegistrationTime)
: '—';
const macBit = h.mac && h.mac !== '—' ? ` · ${h.mac}` : '';
lines.push(
`- **Webex** ${idx}${h.name || 'Handset'} (ext ${h.extension || '—'}) · last reg ${reg}${macBit}`,
);
hasDetail = true;
}
for (const d of devices) {
const who = d.displayName || `Handset ${d.index ?? '?'}`;
const rssi = d.rssiDbm != null ? `${d.rssiDbm} dBm` : '?';
const bat = d.batteryPercent != null ? ` · battery ${d.batteryPercent}%` : '';
const rpn = d.lockedRpn || d.registeredRpn;
const rpnBit = rpn ? ` · ${rpn}` : '';
const sip = d.sipState ? ` · SIP ${d.sipState}` : '';
lines.push(`- **Base** ${who} (${d.deviceType || '?'}) · **${rssi}**${bat}${rpnBit}${sip}`);
hasDetail = true;
}
const rssiRows = Array.isArray(parsed?.rssi) ? parsed.rssi : [];
const merged = mergeRssiWithHandsets(rssiRows, registered);
for (const row of merged) {
if (row.mac && coveredMacs.has(normalizeMacLoose(row.mac))) continue;
const rssi = row.rssiDbm != null ? `${row.rssiDbm} dBm` : '?';
const who = row.handsetName
? `${row.handsetName} (ext ${row.extension || '—'})`
: (row.mac || `RPN ${row.rpn ?? '?'}`);
lines.push(`- **RSSI** ${who}: **${rssi}**`);
hasDetail = true;
}
const presence = Array.isArray(parsed?.devicePresence) ? parsed.devicePresence : [];
for (const p of presence) {
if (p.extension != null) {
const state = p.present === true ? 'present' : (p.present === false ? 'absent' : p.raw);
lines.push(`- **Presence** ext ${p.extension} (${p.deviceType || '?'}) · ${state}`);
hasDetail = true;
continue;
}
if (p.present != null) {
lines.push(`- **Presence** ${p.key}: ${p.present ? 'present' : 'absent'}`);
hasDetail = true;
}
}
const sip = Array.isArray(parsed?.sipIdentityStatus) ? parsed.sipIdentityStatus : [];
for (const s of sip) {
const status = s.status || s.value || '?';
const lineMatch = s.key?.match(/^Line_(\d+)$/i);
const who = s.sipIdx != null
? `line ${s.sipIdx}`
: (lineMatch ? `line ${lineMatch[1]}` : s.key);
const server = s.serverName ? ` (${s.serverName})` : '';
lines.push(`- **SIP** ${who}${server}: **${status}**`);
hasDetail = true;
}
if (!hasDetail && registered.length === 0) {
lines.push('- _(no handset or RF data from Webex or base status.xml)_');
} }
return lines; return lines;
} }
function row(lines, label, value) { function normalizeMacLoose(mac) {
if (value == null || value === '') return; if (!mac) return null;
lines.push(`- **${label}:** ${value}`); const hex = String(mac).replace(/[^0-9a-fA-F]/g, '').toLowerCase();
return hex.length === 12 ? hex : null;
}
function renderUnassignedHandsets(handsetCtx) {
const unassigned = handsetCtx?.unassignedHandsets || [];
if (unassigned.length === 0) return [];
const lines = ['', '**Unassigned handsets** (Webex)'];
for (const h of unassigned) {
const idx = h.index != null ? `${h.index}-` : '';
const reg = h.lastRegistrationTime
? simpleTimeAgo(h.lastRegistrationTime)
: '—';
lines.push(`- **${idx}${h.name || 'Handset'}** (ext ${h.extension || '—'}) · last reg ${reg}`);
}
return lines;
} }

View file

@ -89,10 +89,20 @@ test('discoverDectBases: happy path — one Meraki-enriched base on 10.x', () =>
mac: '6c:ab:05:f6:28:19', mac: '6c:ab:05:f6:28:19',
ip: '10.4.11.87', ip: '10.4.11.87',
name: 'Basestation A', name: 'Basestation A',
webexId: null,
source: 'meraki', source: 'meraki',
}); });
}); });
test('discoverDectBases: passes through Webex base id as webexId', () => {
const result = discoverDectBases(fixture({
dectBasestations: [
base({ id: 'webex-base-99', meraki: { ip: '10.4.11.87' } }),
],
}));
assert.equal(result.bases[0].webexId, 'webex-base-99');
});
test('discoverDectBases: prefers Meraki IP over Webex IP', () => { test('discoverDectBases: prefers Meraki IP over Webex IP', () => {
const result = discoverDectBases(fixture({ const result = discoverDectBases(fixture({
dectBasestations: [ dectBasestations: [

View file

@ -0,0 +1,34 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { buildHandsetContext, handsetsForBase } from '../services/dectStatus/buildHandsetContext.js';
import { mergeRssiWithHandsets } from '../services/dectStatus/matchHandsetByMac.js';
test('buildHandsetContext: groups handsets by base and tracks unassigned', () => {
const ctx = buildHandsetContext({
dectNetwork: { name: 'Store DECT', handsetsCount: 3 },
dectBasestations: [
{ id: 'base-1', mac: '6c:ab:05:f6:28:19', linesRegistered: 2 },
{ id: 'base-2', mac: '6c:ab:05:aa:bb:cc', linesRegistered: 0 },
],
dectHandsets: [
{ id: 'h1', name: 'Handset A', extension: '1001', baseStationId: 'base-1', mac: '6c:ab:05:a1:b2:c3' },
{ id: 'h2', name: 'Handset B', extension: '1002', baseStationId: 'base-1', mac: '6c:ab:05:d4:e5:f6' },
{ id: 'h3', name: 'Floater', extension: '1003', baseStationId: null, mac: '6c:ab:05:11:22:33' },
],
});
assert.equal(handsetsForBase(ctx, 'base-1').length, 2);
assert.equal(ctx.unassignedHandsets.length, 1);
assert.equal(ctx.linesRegisteredByWebexId.get('base-1'), 2);
});
test('mergeRssiWithHandsets: joins by MAC', () => {
const merged = mergeRssiWithHandsets(
[{ rpn: '00', mac: '6c:ab:05:a1:b2:c3', rssiDbm: -58 }],
[{ name: 'Handset A', extension: '1001', mac: '6C:AB:05:A1:B2:C3' }],
);
assert.equal(merged[0].handsetName, 'Handset A');
assert.equal(merged[0].extension, '1001');
assert.equal(merged[0].rssiDbm, -58);
});

View file

@ -0,0 +1,740 @@
[
{
"path": "Status.System_Information.Released_Build",
"isEmpty": false,
"preview": "Yes",
"handsetRelated": false
},
{
"path": "Status.System_Information.Multi_Cell",
"isEmpty": false,
"preview": "Ready(TXT_STATE_KEEP_ALIVE) Primary",
"handsetRelated": false
},
{
"path": "Status.System_Information.Phone_Type",
"isEmpty": false,
"preview": "IPDECT-V2 (DBS-210-3PC)",
"handsetRelated": false
},
{
"path": "Status.System_Information.System_Type",
"isEmpty": false,
"preview": "Generic SIP (RFC 3261)",
"handsetRelated": false
},
{
"path": "Status.System_Information.Unit_Name",
"isEmpty": false,
"preview": "SME VoIP",
"handsetRelated": false
},
{
"path": "Status.System_Information.Unit_Index",
"isEmpty": false,
"preview": "Base Idx:0",
"handsetRelated": false
},
{
"path": "Status.System_Information.RF_Band",
"isEmpty": false,
"preview": "US",
"handsetRelated": false
},
{
"path": "Status.System_Information.Conflict_Info",
"isEmpty": false,
"preview": "No Conflict",
"handsetRelated": false
},
{
"path": "Status.System_Information.Current_Local_Time",
"isEmpty": false,
"preview": "27-Jul-2026 15:51:57",
"handsetRelated": false
},
{
"path": "Status.System_Information.Operating_Time",
"isEmpty": false,
"preview": "39 Days 04:36:56 (H:M:S)",
"handsetRelated": false
},
{
"path": "Status.System_Information.RFPI_Address",
"isEmpty": false,
"preview": "135098C1; RPN:00",
"handsetRelated": false
},
{
"path": "Status.System_Information.MAC_Address",
"isEmpty": false,
"preview": "6cab05f635de",
"handsetRelated": true
},
{
"path": "Status.System_Information.IP_Address",
"isEmpty": false,
"preview": "10.52.38.149",
"handsetRelated": false
},
{
"path": "Status.System_Information.Product_Configuration",
"isEmpty": false,
"preview": "0000",
"handsetRelated": false
},
{
"path": "Status.System_Information.Firmware_Version",
"isEmpty": false,
"preview": "IPDECT-V2/05-01-03-0101-09/18-Mar-2026 10:29",
"handsetRelated": false
},
{
"path": "Status.System_Information.Firmware_URL.Update_Server_Address",
"isEmpty": false,
"preview": "https://cisco.sipflash.com",
"handsetRelated": false
},
{
"path": "Status.System_Information.Firmware_URL.Path",
"isEmpty": false,
"preview": "dms/dbS210",
"handsetRelated": false
},
{
"path": "Status.System_Information.Reboot_Log.Reboot_Line_1",
"isEmpty": false,
"preview": "2026-06-18 11:09:04 (126) Power Loss (80) Firmware Version 05-01-03-0101-09",
"handsetRelated": false
},
{
"path": "Status.System_Information.Reboot_Log.Reboot_Line_2",
"isEmpty": false,
"preview": "2026-05-31 06:44:34 (125) Power Loss (80) Firmware Version 05-01-03-0101-09",
"handsetRelated": false
},
{
"path": "Status.System_Information.Reboot_Log.Reboot_Line_3",
"isEmpty": false,
"preview": "2026-05-28 00:49:01 (124) Forced Reboot (81) Firmware Version 05-01-02-0101-05",
"handsetRelated": false
},
{
"path": "Status.System_Information.Reboot_Log.Reboot_Line_4",
"isEmpty": false,
"preview": "2026-05-10 07:11:22 (123) Power Loss (80) Firmware Version 05-01-02-0101-05",
"handsetRelated": false
},
{
"path": "Status.System_Information.Reboot_Log.Reboot_Line_5",
"isEmpty": false,
"preview": "2026-04-12 09:21:45 (122) Power Loss (80) Firmware Version 05-01-02-0101-05",
"handsetRelated": false
},
{
"path": "Status.System_Information.Reboot_Log.Reboot_Line_6",
"isEmpty": false,
"preview": "2026-02-17 00:21:42 (121) Power Loss (80) Firmware Version 05-01-02-0101-05",
"handsetRelated": false
},
{
"path": "Status.System_Information.Base_Station_Status",
"isEmpty": false,
"preview": "Idle",
"handsetRelated": false
},
{
"path": "Status.System_Information.Custom_CA_Status.Custom_CA_Provisioning_Status",
"isEmpty": false,
"preview": "N/A",
"handsetRelated": false
},
{
"path": "Status.System_Information.Custom_CA_Status.Custom_CA_Info",
"isEmpty": false,
"preview": "Not Installed",
"handsetRelated": false
},
{
"path": "Status.System_Information.Dot1x_Authentication.Transaction_status",
"isEmpty": false,
"preview": "Unavailable",
"handsetRelated": false
},
{
"path": "Status.System_Information.Dot1x_Authentication.Protocol",
"isEmpty": false,
"preview": "N/A",
"handsetRelated": false
},
{
"path": "Status.System_Information.RSSI_List.RSSI_Line_1",
"isEmpty": false,
"preview": "RSSI for RPN:4 is -47 [dBm]",
"handsetRelated": true
},
{
"path": "Status.System_Information.RTP_Usage.Total_RTP",
"isEmpty": false,
"preview": "4",
"handsetRelated": false
},
{
"path": "Status.System_Information.RTP_Usage.Max_RTP",
"isEmpty": false,
"preview": "-1",
"handsetRelated": false
},
{
"path": "Status.System_Information.RTP_Usage.Time_In_Max_RTP",
"isEmpty": false,
"preview": "49710 days 06:28:15 [H:M:S]",
"handsetRelated": false
},
{
"path": "Status.System_Information.RTP_Usage.Current_RTP",
"isEmpty": false,
"preview": "0",
"handsetRelated": false
},
{
"path": "Status.System_Information.RTP_Usage.Current_Local_RTP",
"isEmpty": false,
"preview": "0",
"handsetRelated": false
},
{
"path": "Status.System_Information.RTP_Usage.Current_Relay_RTP",
"isEmpty": false,
"preview": "0",
"handsetRelated": false
},
{
"path": "Status.System_Information.RTP_Usage.Remote_Relay_RTP",
"isEmpty": false,
"preview": "0",
"handsetRelated": false
},
{
"path": "Status.System_Information.RTP_Usage.Current_Recording",
"isEmpty": false,
"preview": "0",
"handsetRelated": false
},
{
"path": "Status.System_Information.Device_Presence.Device_Line_1",
"isEmpty": false,
"preview": "Device:6825 present at Extension 1",
"handsetRelated": true
},
{
"path": "Status.System_Information.Device_Presence.Device_Line_2",
"isEmpty": false,
"preview": "Device:6825 present at Extension 2",
"handsetRelated": true
},
{
"path": "Status.System_Information.Device_FWU_Info.Device_Base_Station",
"isEmpty": false,
"preview": "Base type:DBS-210-3PC - Required Version:501 Required Branch:309",
"handsetRelated": false
},
{
"path": "Status.System_Information.Device_FWU_Info.Device_Line_0",
"isEmpty": false,
"preview": "Device type:6825 - Required Version:501 Required Branch:308 Language Pack:6825_default",
"handsetRelated": true
},
{
"path": "Status.System_Information.Device_FWU_Info.Device_Line_1",
"isEmpty": false,
"preview": "Device type:6825-RGD - Required Version:501 Required Branch:308 Language Pack:6825-RGD_default",
"handsetRelated": true
},
{
"path": "Status.System_Information.Device_FWU_Info.Device_Line_2",
"isEmpty": false,
"preview": "Device type:6823 - Required Version:501 Required Branch:308 Language Pack:6823_default",
"handsetRelated": true
},
{
"path": "Status.System_Information.Device_FWU_Info.Device_Line_3",
"isEmpty": false,
"preview": "Device type:RPT-110-3PC - Required Version:501 Required Branch:303",
"handsetRelated": true
},
{
"path": "Status.System_Information.Push_To_Talk",
"isEmpty": false,
"preview": "Off",
"handsetRelated": false
},
{
"path": "Status.System_Information.Emergency_Calls.Emergency_Number_1",
"isEmpty": false,
"preview": "911",
"handsetRelated": false
},
{
"path": "Status.System_Information.Emergency_Calls.Emergency_Number_2",
"isEmpty": false,
"preview": "1911",
"handsetRelated": false
},
{
"path": "Status.System_Information.Emergency_Calls.Emergency_Number_3",
"isEmpty": false,
"preview": "933",
"handsetRelated": false
},
{
"path": "Status.System_Information.Emergency_Calls.Emergency_Number_4",
"isEmpty": false,
"preview": "No Number set!",
"handsetRelated": false
},
{
"path": "Status.System_Information.Emergency_Calls.Emergency_Number_5",
"isEmpty": false,
"preview": "No Number set!",
"handsetRelated": false
},
{
"path": "Status.System_Information.SIP_Identity_Status.SIP_UA_Id_4.SIP_Idx",
"isEmpty": false,
"preview": "1",
"handsetRelated": true
},
{
"path": "Status.System_Information.SIP_Identity_Status.SIP_UA_Id_4.Server_Id",
"isEmpty": false,
"preview": "0",
"handsetRelated": true
},
{
"path": "Status.System_Information.SIP_Identity_Status.SIP_UA_Id_4.SIP_URI",
"isEmpty": false,
"preview": "jdph354xak_HH_9768C5EF4172_2@31134724.cisco-bcld.com",
"handsetRelated": true
},
{
"path": "Status.System_Information.SIP_Identity_Status.SIP_UA_Id_4.Server_Name",
"isEmpty": false,
"preview": "BroadCloud",
"handsetRelated": true
},
{
"path": "Status.System_Information.SIP_Identity_Status.SIP_UA_Id_4.Status",
"isEmpty": false,
"preview": "OK",
"handsetRelated": true
},
{
"path": "Status.System_Information.SIP_Identity_Status.SIP_UA_Id_5.SIP_Idx",
"isEmpty": false,
"preview": "0",
"handsetRelated": true
},
{
"path": "Status.System_Information.SIP_Identity_Status.SIP_UA_Id_5.Server_Id",
"isEmpty": false,
"preview": "0",
"handsetRelated": true
},
{
"path": "Status.System_Information.SIP_Identity_Status.SIP_UA_Id_5.SIP_URI",
"isEmpty": false,
"preview": "jdph354xak@31134724.cisco-bcld.com",
"handsetRelated": true
},
{
"path": "Status.System_Information.SIP_Identity_Status.SIP_UA_Id_5.Server_Name",
"isEmpty": false,
"preview": "BroadCloud",
"handsetRelated": true
},
{
"path": "Status.System_Information.SIP_Identity_Status.SIP_UA_Id_5.Status",
"isEmpty": false,
"preview": "Registering",
"handsetRelated": true
},
{
"path": "Status.Device_Information.Device_Idx1.Ipei",
"isEmpty": false,
"preview": "0329908355",
"handsetRelated": false
},
{
"path": "Status.Device_Information.Device_Idx1.Device_Type",
"isEmpty": false,
"preview": "6825",
"handsetRelated": false
},
{
"path": "Status.Device_Information.Device_Idx1.SW_Version",
"isEmpty": false,
"preview": "05-01-03-0101-08",
"handsetRelated": false
},
{
"path": "Status.Device_Information.Device_Idx1.FWU_Progress",
"isEmpty": false,
"preview": "FWU Complete",
"handsetRelated": false
},
{
"path": "Status.Device_Information.Device_Idx1.Location_Info.Time_Date",
"isEmpty": false,
"preview": "27-Jul-2026 19:33:27",
"handsetRelated": false
},
{
"path": "Status.Device_Information.Device_Idx1.Location_Info.RPN",
"isEmpty": false,
"preview": "Registered@RPN:00",
"handsetRelated": true
},
{
"path": "Status.Device_Information.Device_Idx1.Location_Info.Locked_RPN",
"isEmpty": false,
"preview": "Locked@RPN:00",
"handsetRelated": true
},
{
"path": "Status.Device_Information.Device_Idx1.Battery_RSSI_Info.Time_Date",
"isEmpty": false,
"preview": "12:33 [mm:ss]",
"handsetRelated": true
},
{
"path": "Status.Device_Information.Device_Idx1.Battery_RSSI_Info.Battery_level",
"isEmpty": false,
"preview": "81 [%]",
"handsetRelated": true
},
{
"path": "Status.Device_Information.Device_Idx1.Battery_RSSI_Info.RSSI",
"isEmpty": false,
"preview": "-76 [dBm]",
"handsetRelated": true
},
{
"path": "Status.Device_Information.Device_Idx1.Hs_Config_Update_Time",
"isEmpty": false,
"preview": "N/A",
"handsetRelated": false
},
{
"path": "Status.Device_Information.Device_Idx1.Tracking_Info.Tracking_State",
"isEmpty": false,
"preview": "Tracking Activated",
"handsetRelated": false
},
{
"path": "Status.Device_Information.Device_Idx1.Tracking_Info.Time_Date",
"isEmpty": false,
"preview": "27-Jul-2026 19:33:27",
"handsetRelated": false
},
{
"path": "Status.Device_Information.Device_Idx1.DECT_Info.DECT_State",
"isEmpty": false,
"preview": "Present",
"handsetRelated": false
},
{
"path": "Status.Device_Information.Device_Idx1.DECT_Info.Call_Instance.MmInstance",
"isEmpty": false,
"preview": "0",
"handsetRelated": false
},
{
"path": "Status.Device_Information.Device_Idx1.DECT_Info.Call_Instance.CissInstance",
"isEmpty": false,
"preview": "0",
"handsetRelated": false
},
{
"path": "Status.Device_Information.Device_Idx1.DECT_Info.Call_Instance.CcOutInstance",
"isEmpty": false,
"preview": "0",
"handsetRelated": false
},
{
"path": "Status.Device_Information.Device_Idx1.DECT_Info.Call_Instance.CcIncInstance",
"isEmpty": false,
"preview": "0",
"handsetRelated": false
},
{
"path": "Status.Device_Information.Device_Idx1.Push_To_Talk",
"isEmpty": false,
"preview": "Off",
"handsetRelated": false
},
{
"path": "Status.Device_Information.Device_Idx1.Reset_Info.Time_Date",
"isEmpty": false,
"preview": "N/A",
"handsetRelated": false
},
{
"path": "Status.Device_Information.Device_Idx1.Reset_Info.Number",
"isEmpty": false,
"preview": "0",
"handsetRelated": false
},
{
"path": "Status.Device_Information.Device_Idx1.SIP_Account.SIP_IDX1.Index_of_Relation",
"isEmpty": false,
"preview": "SIP account with Idx 1 is linked to device Idx 1",
"handsetRelated": false
},
{
"path": "Status.Device_Information.Device_Idx1.SIP_Account.SIP_IDX1.Display_Name",
"isEmpty": false,
"preview": "1-50933",
"handsetRelated": false
},
{
"path": "Status.Device_Information.Device_Idx1.SIP_Account.SIP_IDX1.Number",
"isEmpty": false,
"preview": "jdph354xak",
"handsetRelated": false
},
{
"path": "Status.Device_Information.Device_Idx1.SIP_Account.SIP_IDX1.State",
"isEmpty": false,
"preview": "Not in use",
"handsetRelated": false
},
{
"path": "Status.Device_Information.Device_Idx2.Ipei",
"isEmpty": false,
"preview": "032990831B",
"handsetRelated": false
},
{
"path": "Status.Device_Information.Device_Idx2.Device_Type",
"isEmpty": false,
"preview": "6825",
"handsetRelated": false
},
{
"path": "Status.Device_Information.Device_Idx2.SW_Version",
"isEmpty": false,
"preview": "05-01-03-0101-08",
"handsetRelated": false
},
{
"path": "Status.Device_Information.Device_Idx2.FWU_Progress",
"isEmpty": false,
"preview": "FWU Complete",
"handsetRelated": false
},
{
"path": "Status.Device_Information.Device_Idx2.Location_Info.Time_Date",
"isEmpty": false,
"preview": "27-Jul-2026 19:39:02",
"handsetRelated": false
},
{
"path": "Status.Device_Information.Device_Idx2.Location_Info.RPN",
"isEmpty": false,
"preview": "Registered@RPN:00",
"handsetRelated": true
},
{
"path": "Status.Device_Information.Device_Idx2.Location_Info.Locked_RPN",
"isEmpty": false,
"preview": "Locked@RPN:04",
"handsetRelated": true
},
{
"path": "Status.Device_Information.Device_Idx2.Battery_RSSI_Info.Time_Date",
"isEmpty": false,
"preview": "53:38 [mm:ss]",
"handsetRelated": true
},
{
"path": "Status.Device_Information.Device_Idx2.Battery_RSSI_Info.Battery_level",
"isEmpty": false,
"preview": "98 [%]",
"handsetRelated": true
},
{
"path": "Status.Device_Information.Device_Idx2.Battery_RSSI_Info.RSSI",
"isEmpty": false,
"preview": "-76 [dBm]",
"handsetRelated": true
},
{
"path": "Status.Device_Information.Device_Idx2.Hs_Config_Update_Time",
"isEmpty": false,
"preview": "N/A",
"handsetRelated": false
},
{
"path": "Status.Device_Information.Device_Idx2.Tracking_Info.Tracking_State",
"isEmpty": false,
"preview": "Tracking Activated",
"handsetRelated": false
},
{
"path": "Status.Device_Information.Device_Idx2.Tracking_Info.Time_Date",
"isEmpty": false,
"preview": "27-Jul-2026 19:39:02",
"handsetRelated": false
},
{
"path": "Status.Device_Information.Device_Idx2.DECT_Info.DECT_State",
"isEmpty": false,
"preview": "Present",
"handsetRelated": false
},
{
"path": "Status.Device_Information.Device_Idx2.DECT_Info.Call_Instance.MmInstance",
"isEmpty": false,
"preview": "0",
"handsetRelated": false
},
{
"path": "Status.Device_Information.Device_Idx2.DECT_Info.Call_Instance.CissInstance",
"isEmpty": false,
"preview": "0",
"handsetRelated": false
},
{
"path": "Status.Device_Information.Device_Idx2.DECT_Info.Call_Instance.CcOutInstance",
"isEmpty": false,
"preview": "0",
"handsetRelated": false
},
{
"path": "Status.Device_Information.Device_Idx2.DECT_Info.Call_Instance.CcIncInstance",
"isEmpty": false,
"preview": "0",
"handsetRelated": false
},
{
"path": "Status.Device_Information.Device_Idx2.Push_To_Talk",
"isEmpty": false,
"preview": "Off",
"handsetRelated": false
},
{
"path": "Status.Device_Information.Device_Idx2.Reset_Info.Time_Date",
"isEmpty": false,
"preview": "N/A",
"handsetRelated": false
},
{
"path": "Status.Device_Information.Device_Idx2.Reset_Info.Number",
"isEmpty": false,
"preview": "0",
"handsetRelated": false
},
{
"path": "Status.Device_Information.Device_Idx2.SIP_Account.SIP_IDX2.Index_of_Relation",
"isEmpty": false,
"preview": "SIP account with Idx 2 is linked to device Idx 2",
"handsetRelated": false
},
{
"path": "Status.Device_Information.Device_Idx2.SIP_Account.SIP_IDX2.Display_Name",
"isEmpty": false,
"preview": "2-50933",
"handsetRelated": false
},
{
"path": "Status.Device_Information.Device_Idx2.SIP_Account.SIP_IDX2.Number",
"isEmpty": false,
"preview": "jdph354xak_HH_9768C5EF4172_2",
"handsetRelated": false
},
{
"path": "Status.Device_Information.Device_Idx2.SIP_Account.SIP_IDX2.State",
"isEmpty": false,
"preview": "Not in use",
"handsetRelated": false
},
{
"path": "Status.Statistics.Network_Statistics.Tx_Packets",
"isEmpty": false,
"preview": "3518964",
"handsetRelated": true
},
{
"path": "Status.Statistics.Network_Statistics.Tx_Blocked",
"isEmpty": false,
"preview": "0",
"handsetRelated": true
},
{
"path": "Status.Statistics.Network_Statistics.Tx_Dropped",
"isEmpty": false,
"preview": "0",
"handsetRelated": true
},
{
"path": "Status.Statistics.Network_Statistics.Tx_Errors",
"isEmpty": false,
"preview": "0",
"handsetRelated": true
},
{
"path": "Status.Statistics.Network_Statistics.Tx_Broadcasts",
"isEmpty": false,
"preview": "0",
"handsetRelated": true
},
{
"path": "Status.Statistics.Network_Statistics.Rx_Packets",
"isEmpty": false,
"preview": "4940939",
"handsetRelated": true
},
{
"path": "Status.Statistics.Network_Statistics.Rx_Blocked",
"isEmpty": false,
"preview": "0",
"handsetRelated": true
},
{
"path": "Status.Statistics.Network_Statistics.Rx_Dropped",
"isEmpty": false,
"preview": "0",
"handsetRelated": true
},
{
"path": "Status.Statistics.Network_Statistics.Rx_Errors",
"isEmpty": false,
"preview": "0",
"handsetRelated": true
},
{
"path": "Status.Statistics.Network_Statistics.Rx_Broadcasts",
"isEmpty": false,
"preview": "6239",
"handsetRelated": true
},
{
"path": "Status.Statistics.Header_Line_Idx",
"isEmpty": false,
"preview": "RPN, MAC-Addr, OP[s], DT[s], Call Cnt, Call Drop, Call Failed, Duration[s], Active Calls, Max Calls, Busy, Busy Duratio…",
"handsetRelated": true
},
{
"path": "Status.Statistics.Value_Line_0",
"isEmpty": false,
"preview": "'00','6CAB05F635DE',3386184,5238180,942,1,8,78353,0,4,0,0,255,0,0,892,0,0,0,35,0,0,285,94,7662,1,1,0,24,0,0,90,109,74,1…",
"handsetRelated": true
},
{
"path": "Status.Statistics.Value_Line_1",
"isEmpty": false,
"preview": "'00','6CAB05F63642',3386126,5238247,160,0,0,12875,0,2,0,0,1,1,8,146,0,0,0,2,0,0,30,10,167,2,2,2,25,0,0,60,118,72,105,52…",
"handsetRelated": true
}
]

View file

@ -0,0 +1,169 @@
{
"parsed": {
"device": {
"model": "IPDECT-V2 (DBS-210-3PC)",
"systemType": "Generic SIP (RFC 3261)",
"unitName": "SME VoIP",
"unitIndex": "Base Idx:0",
"rfBand": "US",
"productConfiguration": "0000",
"macAddress": "6c:ab:05:f6:35:de",
"ipAddress": "10.52.38.149",
"rfpiAddress": "135098C1; RPN:00",
"releasedBuild": true
},
"firmware": {
"version": "IPDECT-V2/05-01-03-0101-09/18-Mar-2026 10:29",
"updateServer": "https://cisco.sipflash.com",
"updatePath": "dms/dbS210",
"requiredFor": {
"6823": {
"requiredVersion": "501",
"requiredBranch": "308",
"languagePack": "6823_default",
"_sourceKey": "Device_Line_2"
},
"6825": {
"requiredVersion": "501",
"requiredBranch": "308",
"languagePack": "6825_default",
"_sourceKey": "Device_Line_0"
},
"DBS-210-3PC": {
"requiredVersion": "501",
"requiredBranch": "309",
"languagePack": null,
"_sourceKey": "Device_Base_Station"
},
"6825-RGD": {
"requiredVersion": "501",
"requiredBranch": "308",
"languagePack": "6825-RGD_default",
"_sourceKey": "Device_Line_1"
},
"RPT-110-3PC": {
"requiredVersion": "501",
"requiredBranch": "303",
"languagePack": null,
"_sourceKey": "Device_Line_3"
}
}
},
"time": {
"currentLocalTime": "27-Jul-2026 15:51:57",
"operatingTime": "39 Days 04:36:56 (H:M:S)",
"operatingTimeSeconds": 16616
},
"multiCell": {
"role": "ready",
"raw": "Ready(TXT_STATE_KEEP_ALIVE) Primary"
},
"baseStatus": "idle",
"conflictInfo": "No Conflict",
"security": {
"customCa": {
"provisioningStatus": "N/A",
"info": "Not Installed",
"installed": false
},
"dot1x": {
"transactionStatus": "Unavailable",
"protocol": "N/A"
}
},
"rebootLog": [
{
"at": "2026-06-18T11:09:04",
"sequence": 126,
"reasonName": "Power Loss",
"reasonCode": 80,
"firmwareAtBoot": "05-01-03-0101-09",
"raw": "2026-06-18 11:09:04 (126) Power Loss (80) Firmware Version 05-01-03-0101-09"
},
{
"at": "2026-05-31T06:44:34",
"sequence": 125,
"reasonName": "Power Loss",
"reasonCode": 80,
"firmwareAtBoot": "05-01-03-0101-09",
"raw": "2026-05-31 06:44:34 (125) Power Loss (80) Firmware Version 05-01-03-0101-09"
},
{
"at": "2026-05-28T00:49:01",
"sequence": 124,
"reasonName": "Forced Reboot",
"reasonCode": 81,
"firmwareAtBoot": "05-01-02-0101-05",
"raw": "2026-05-28 00:49:01 (124) Forced Reboot (81) Firmware Version 05-01-02-0101-05"
},
{
"at": "2026-05-10T07:11:22",
"sequence": 123,
"reasonName": "Power Loss",
"reasonCode": 80,
"firmwareAtBoot": "05-01-02-0101-05",
"raw": "2026-05-10 07:11:22 (123) Power Loss (80) Firmware Version 05-01-02-0101-05"
},
{
"at": "2026-04-12T09:21:45",
"sequence": 122,
"reasonName": "Power Loss",
"reasonCode": 80,
"firmwareAtBoot": "05-01-02-0101-05",
"raw": "2026-04-12 09:21:45 (122) Power Loss (80) Firmware Version 05-01-02-0101-05"
},
{
"at": "2026-02-17T00:21:42",
"sequence": 121,
"reasonName": "Power Loss",
"reasonCode": 80,
"firmwareAtBoot": "05-01-02-0101-05",
"raw": "2026-02-17 00:21:42 (121) Power Loss (80) Firmware Version 05-01-02-0101-05"
}
],
"rtp": {
"total": 4,
"max": -1,
"current": 0,
"currentLocal": 0,
"currentRelay": 0,
"remoteRelay": 0,
"currentRecording": 0,
"timeInMaxRtp": "49710 days 06:28:15 [H:M:S]"
},
"network": {
"txPackets": 3518964,
"txBlocked": 0,
"txDropped": 0,
"txErrors": 0,
"txBroadcasts": 0,
"rxPackets": 4940939,
"rxBlocked": 0,
"rxDropped": 0,
"rxErrors": 0,
"rxBroadcasts": 6239
},
"emergencyNumbers": [
"911",
"1911",
"933"
],
"rssi": [],
"devicePresence": [],
"sipIdentityStatus": [],
"perRpnStats": [],
"features": {
"pushToTalk": false
},
"statisticsHeader": "RPN, MAC-Addr, OP[s], DT[s], Call Cnt, Call Drop, Call Failed, Duration[s], Active Calls, Max Calls, Busy, Busy Duration[s], Min Latency[ms], Avg Latency[ms], Max Latency[ms], G711U, G711A, G729, G722, G726, OPUS, BV32, Handovers, Failed Handovers, SIP reg failed, Handset Removed, Searching, RcHeapFree Running, New SyncSource, LAN Sync Lost, LAN Primary Lost, Freq[0][0], Freq[0][1], Freq[0][2], Freq[0][3], Freq[0][4], Freq[0][5], Freq[0][6], Freq[0][7], Freq[0][8], Freq[0][9], Freq[0][10], Freq[0][11], Freq[1][0], Freq[1][1], Freq[1][2], Freq[1][3], Freq[1][4], Freq[1][5], Freq[1][6], Freq[1][7], Freq[1][8], Freq[1][9], Freq[1][10], Freq[1][11], Freq[2][0], Freq[2][1], Freq[2][2], Freq[2][3], Freq[2][4], Freq[2][5], Freq[2][6], Freq[2][7], Freq[2][8], Freq[2][9], Freq[2][10], Freq[2][11], Freq[3][0], Freq[3][1], Freq[3][2], Freq[3][3], Freq[3][4], Freq[3][5], Freq[3][6], Freq[3][7], Freq[3][8], Freq[3][9], Freq[3][10], Freq[3][11], Freq[4][0], Freq[4][1], Freq[4][2], Freq[4][3], Freq[4][4], Freq[4][5], Freq[4][6], Freq[4][7], Freq[4][8], Freq[4][9], Freq[4][10], Freq[4][11], Freq[5][0], Freq[5][1], Freq[5][2], Freq[5][3], Freq[5][4], Freq[5][5], Freq[5][6], Freq[5][7], Freq[5][8], Freq[5][9], Freq[5][10], Freq[5][11], Freq[6][0], Freq[6][1], Freq[6][2], Freq[6][3], Freq[6][4], Freq[6][5], Freq[6][6], Freq[6][7], Freq[6][8], Freq[6][9], Freq[6][10], Freq[6][11], Freq[7][0], Freq[7][1], Freq[7][2], Freq[7][3], Freq[7][4], Freq[7][5], Freq[7][6], Freq[7][7], Freq[7][8], Freq[7][9], Freq[7][10], Freq[7][11], Freq[8][0], Freq[8][1], Freq[8][2], Freq[8][3], Freq[8][4], Freq[8][5], Freq[8][6], Freq[8][7], Freq[8][8], Freq[8][9], Freq[8][10], Freq[8][11], Freq[9][0], Freq[9][1], Freq[9][2], Freq[9][3], Freq[9][4], Freq[9][5], Freq[9][6], Freq[9][7], Freq[9][8], Freq[9][9], Freq[9][10], Freq[9][11], R-Idx[0], R-OP[0][s], R-Busy[0], R-Busy Duration[0][s], Max Calls[0], Searching[0], Recovery[0], New SyncSource[0], Wide Band[0], Narrow Band[0], R-Idx[1], R-OP[1][s], R-Busy[1], R-Busy Duration[1][s], Max Calls[1], Searching[1], Recovery[1], New SyncSource[1], Wide Band[1], Narrow Band[1], R-Idx[2], R-OP[2][s], R-Busy[2], R-Busy Duration[2][s], Max Calls[2], Searching[2], Recovery[2], New SyncSource[2], Wide Band[2], Narrow Band[2]",
"_rawStatisticsHeader": "RPN, MAC-Addr, OP[s], DT[s], Call Cnt, Call Drop, Call Failed, Duration[s], Active Calls, Max Calls, Busy, Busy Duration[s], Min Latency[ms], Avg Latency[ms], Max Latency[ms], G711U, G711A, G729, G722, G726, OPUS, BV32, Handovers, Failed Handovers, SIP reg failed, Handset Removed, Searching, RcHeapFree Running, New SyncSource, LAN Sync Lost, LAN Primary Lost, Freq[0][0], Freq[0][1], Freq[0][2], Freq[0][3], Freq[0][4], Freq[0][5], Freq[0][6], Freq[0][7], Freq[0][8], Freq[0][9], Freq[0][10], Freq[0][11], Freq[1][0], Freq[1][1], Freq[1][2], Freq[1][3], Freq[1][4], Freq[1][5], Freq[1][6], Freq[1][7], Freq[1][8], Freq[1][9], Freq[1][10], Freq[1][11], Freq[2][0], Freq[2][1], Freq[2][2], Freq[2][3], Freq[2][4], Freq[2][5], Freq[2][6], Freq[2][7], Freq[2][8], Freq[2][9], Freq[2][10], Freq[2][11], Freq[3][0], Freq[3][1], Freq[3][2], Freq[3][3], Freq[3][4], Freq[3][5], Freq[3][6], Freq[3][7], Freq[3][8], Freq[3][9], Freq[3][10], Freq[3][11], Freq[4][0], Freq[4][1], Freq[4][2], Freq[4][3], Freq[4][4], Freq[4][5], Freq[4][6], Freq[4][7], Freq[4][8], Freq[4][9], Freq[4][10], Freq[4][11], Freq[5][0], Freq[5][1], Freq[5][2], Freq[5][3], Freq[5][4], Freq[5][5], Freq[5][6], Freq[5][7], Freq[5][8], Freq[5][9], Freq[5][10], Freq[5][11], Freq[6][0], Freq[6][1], Freq[6][2], Freq[6][3], Freq[6][4], Freq[6][5], Freq[6][6], Freq[6][7], Freq[6][8], Freq[6][9], Freq[6][10], Freq[6][11], Freq[7][0], Freq[7][1], Freq[7][2], Freq[7][3], Freq[7][4], Freq[7][5], Freq[7][6], Freq[7][7], Freq[7][8], Freq[7][9], Freq[7][10], Freq[7][11], Freq[8][0], Freq[8][1], Freq[8][2], Freq[8][3], Freq[8][4], Freq[8][5], Freq[8][6], Freq[8][7], Freq[8][8], Freq[8][9], Freq[8][10], Freq[8][11], Freq[9][0], Freq[9][1], Freq[9][2], Freq[9][3], Freq[9][4], Freq[9][5], Freq[9][6], Freq[9][7], Freq[9][8], Freq[9][9], Freq[9][10], Freq[9][11], R-Idx[0], R-OP[0][s], R-Busy[0], R-Busy Duration[0][s], Max Calls[0], Searching[0], Recovery[0], New SyncSource[0], Wide Band[0], Narrow Band[0], R-Idx[1], R-OP[1][s], R-Busy[1], R-Busy Duration[1][s], Max Calls[1], Searching[1], Recovery[1], New SyncSource[1], Wide Band[1], Narrow Band[1], R-Idx[2], R-OP[2][s], R-Busy[2], R-Busy Duration[2][s], Max Calls[2], Searching[2], Recovery[2], New SyncSource[2], Wide Band[2], Narrow Band[2]"
},
"verdict": {
"healthy": false,
"warnings": [
"5 recent power-loss reboot(s); most recent at 2026-06-18T11:09:04"
],
"info": []
},
"fetchedAt": "2026-07-27T19:51:57.077Z"
}

View file

@ -0,0 +1,190 @@
<?xml version="1.0" encoding="UTF-8"?>
<Status>
<System_Information>
<Released_Build>Yes</Released_Build>
<Multi_Cell>Ready(TXT_STATE_KEEP_ALIVE) Primary</Multi_Cell>
<Phone_Type>IPDECT-V2 (DBS-210-3PC)</Phone_Type>
<System_Type>Generic SIP (RFC 3261)</System_Type>
<Unit_Name>SME VoIP</Unit_Name>
<Unit_Index>Base Idx:0</Unit_Index>
<RF_Band>US</RF_Band>
<Conflict_Info>No Conflict</Conflict_Info>
<Current_Local_Time>27-Jul-2026 15:51:57</Current_Local_Time>
<Operating_Time>39 Days 04:36:56 (H:M:S)</Operating_Time>
<RFPI_Address>135098C1; RPN:00</RFPI_Address>
<MAC_Address>6cab05f635de</MAC_Address>
<IP_Address>10.52.38.149</IP_Address>
<Product_Configuration>0000</Product_Configuration>
<Firmware_Version>IPDECT-V2/05-01-03-0101-09/18-Mar-2026 10:29</Firmware_Version>
<Firmware_URL>
<Update_Server_Address>https://cisco.sipflash.com</Update_Server_Address>
<Path>dms/dbS210</Path>
</Firmware_URL>
<Reboot_Log>
<Reboot_Line_1>2026-06-18 11:09:04 (126) Power Loss (80) Firmware Version 05-01-03-0101-09</Reboot_Line_1>
<Reboot_Line_2>2026-05-31 06:44:34 (125) Power Loss (80) Firmware Version 05-01-03-0101-09</Reboot_Line_2>
<Reboot_Line_3>2026-05-28 00:49:01 (124) Forced Reboot (81) Firmware Version 05-01-02-0101-05</Reboot_Line_3>
<Reboot_Line_4>2026-05-10 07:11:22 (123) Power Loss (80) Firmware Version 05-01-02-0101-05</Reboot_Line_4>
<Reboot_Line_5>2026-04-12 09:21:45 (122) Power Loss (80) Firmware Version 05-01-02-0101-05</Reboot_Line_5>
<Reboot_Line_6>2026-02-17 00:21:42 (121) Power Loss (80) Firmware Version 05-01-02-0101-05</Reboot_Line_6>
</Reboot_Log>
<Base_Station_Status>Idle</Base_Station_Status>
<Custom_CA_Status>
<Custom_CA_Provisioning_Status>N/A</Custom_CA_Provisioning_Status>
<Custom_CA_Info>Not Installed</Custom_CA_Info>
</Custom_CA_Status>
<Dot1x_Authentication>
<Transaction_status>Unavailable</Transaction_status>
<Protocol>N/A</Protocol>
</Dot1x_Authentication>
<RSSI_List>
<RSSI_Line_1>RSSI for RPN:4 is -47 [dBm]</RSSI_Line_1>
</RSSI_List>
<RTP_Usage>
<Total_RTP>4</Total_RTP>
<Max_RTP>-1</Max_RTP>
<Time_In_Max_RTP>49710 days 06:28:15 [H:M:S]</Time_In_Max_RTP>
<Current_RTP>0</Current_RTP>
<Current_Local_RTP>0</Current_Local_RTP>
<Current_Relay_RTP>0</Current_Relay_RTP>
<Remote_Relay_RTP>0</Remote_Relay_RTP>
<Current_Recording>0</Current_Recording>
</RTP_Usage>
<Device_Presence>
<Device_Line_1>Device:6825 present at Extension 1</Device_Line_1>
<Device_Line_2>Device:6825 present at Extension 2</Device_Line_2>
</Device_Presence>
<Device_FWU_Info>
<Device_Base_Station>Base type:DBS-210-3PC - Required Version:501 Required Branch:309</Device_Base_Station>
<Device_Line_0>Device type:6825 - Required Version:501 Required Branch:308 Language Pack:6825_default</Device_Line_0>
<Device_Line_1>Device type:6825-RGD - Required Version:501 Required Branch:308 Language Pack:6825-RGD_default</Device_Line_1>
<Device_Line_2>Device type:6823 - Required Version:501 Required Branch:308 Language Pack:6823_default</Device_Line_2>
<Device_Line_3>Device type:RPT-110-3PC - Required Version:501 Required Branch:303</Device_Line_3>
</Device_FWU_Info>
<Push_To_Talk>Off</Push_To_Talk>
<Emergency_Calls>
<Emergency_Number_1>911</Emergency_Number_1>
<Emergency_Number_2>1911</Emergency_Number_2>
<Emergency_Number_3>933</Emergency_Number_3>
<Emergency_Number_4>No Number set!</Emergency_Number_4>
<Emergency_Number_5>No Number set!</Emergency_Number_5>
</Emergency_Calls>
<SIP_Identity_Status>
<SIP_UA_Id_4>
<SIP_Idx>1</SIP_Idx>
<Server_Id>0</Server_Id>
<SIP_URI>jdph354xak_HH_9768C5EF4172_2@31134724.cisco-bcld.com</SIP_URI>
<Server_Name>BroadCloud</Server_Name>
<Status>OK</Status>
</SIP_UA_Id_4>
<SIP_UA_Id_5>
<SIP_Idx>0</SIP_Idx>
<Server_Id>0</Server_Id>
<SIP_URI>jdph354xak@31134724.cisco-bcld.com</SIP_URI>
<Server_Name>BroadCloud</Server_Name>
<Status>Registering</Status>
</SIP_UA_Id_5>
</SIP_Identity_Status>
</System_Information>
<Device_Information>
<Device_Idx1>
<Ipei>0329908355</Ipei>
<Device_Type>6825</Device_Type>
<SW_Version>05-01-03-0101-08</SW_Version>
<FWU_Progress>FWU Complete</FWU_Progress>
<Location_Info>
<Time_Date>27-Jul-2026 19:33:27</Time_Date>
<RPN>Registered@RPN:00</RPN>
<Locked_RPN>Locked@RPN:00</Locked_RPN>
</Location_Info>
<Battery_RSSI_Info>
<Time_Date>12:33 [mm:ss]</Time_Date>
<Battery_level>81 [%]</Battery_level>
<RSSI>-76 [dBm]</RSSI>
</Battery_RSSI_Info>
<Hs_Config_Update_Time>N/A</Hs_Config_Update_Time><Tracking_Info>
<Tracking_State>Tracking Activated</Tracking_State>
<Time_Date>27-Jul-2026 19:33:27</Time_Date>
</Tracking_Info>
<DECT_Info>
<DECT_State>Present</DECT_State>
<Call_Instance><MmInstance>0</MmInstance>
<CissInstance>0</CissInstance>
<CcOutInstance>0</CcOutInstance>
<CcIncInstance>0</CcIncInstance>
</Call_Instance>
</DECT_Info>
<Push_To_Talk>Off</Push_To_Talk>
<Reset_Info>
<Time_Date>N/A</Time_Date>
<Number>0</Number>
</Reset_Info>
<SIP_Account>
<SIP_IDX1>
<Index_of_Relation>SIP account with Idx 1 is linked to device Idx 1</Index_of_Relation>
<Display_Name>1-50933</Display_Name>
<Number>jdph354xak</Number>
<State>Not in use</State>
</SIP_IDX1>
</SIP_Account>
</Device_Idx1>
<Device_Idx2>
<Ipei>032990831B</Ipei>
<Device_Type>6825</Device_Type>
<SW_Version>05-01-03-0101-08</SW_Version>
<FWU_Progress>FWU Complete</FWU_Progress>
<Location_Info>
<Time_Date>27-Jul-2026 19:39:02</Time_Date>
<RPN>Registered@RPN:00</RPN>
<Locked_RPN>Locked@RPN:04</Locked_RPN>
</Location_Info>
<Battery_RSSI_Info>
<Time_Date>53:38 [mm:ss]</Time_Date>
<Battery_level>98 [%]</Battery_level>
<RSSI>-76 [dBm]</RSSI>
</Battery_RSSI_Info>
<Hs_Config_Update_Time>N/A</Hs_Config_Update_Time><Tracking_Info>
<Tracking_State>Tracking Activated</Tracking_State>
<Time_Date>27-Jul-2026 19:39:02</Time_Date>
</Tracking_Info>
<DECT_Info>
<DECT_State>Present</DECT_State>
<Call_Instance><MmInstance>0</MmInstance>
<CissInstance>0</CissInstance>
<CcOutInstance>0</CcOutInstance>
<CcIncInstance>0</CcIncInstance>
</Call_Instance>
</DECT_Info>
<Push_To_Talk>Off</Push_To_Talk>
<Reset_Info>
<Time_Date>N/A</Time_Date>
<Number>0</Number>
</Reset_Info>
<SIP_Account>
<SIP_IDX2>
<Index_of_Relation>SIP account with Idx 2 is linked to device Idx 2</Index_of_Relation>
<Display_Name>2-50933</Display_Name>
<Number>jdph354xak_HH_9768C5EF4172_2</Number>
<State>Not in use</State>
</SIP_IDX2>
</SIP_Account>
</Device_Idx2>
</Device_Information>
<Statistics>
<Network_Statistics>
<Tx_Packets>3518964</Tx_Packets>
<Tx_Blocked>0</Tx_Blocked>
<Tx_Dropped>0</Tx_Dropped>
<Tx_Errors>0</Tx_Errors>
<Tx_Broadcasts>0</Tx_Broadcasts>
<Rx_Packets>4940939</Rx_Packets>
<Rx_Blocked>0</Rx_Blocked>
<Rx_Dropped>0</Rx_Dropped>
<Rx_Errors>0</Rx_Errors>
<Rx_Broadcasts>6239</Rx_Broadcasts>
</Network_Statistics>
<Header_Line_Idx>RPN, MAC-Addr, OP[s], DT[s], Call Cnt, Call Drop, Call Failed, Duration[s], Active Calls, Max Calls, Busy, Busy Duration[s], Min Latency[ms], Avg Latency[ms], Max Latency[ms], G711U, G711A, G729, G722, G726, OPUS, BV32, Handovers, Failed Handovers, SIP reg failed, Handset Removed, Searching, RcHeapFree Running, New SyncSource, LAN Sync Lost, LAN Primary Lost, Freq[0][0], Freq[0][1], Freq[0][2], Freq[0][3], Freq[0][4], Freq[0][5], Freq[0][6], Freq[0][7], Freq[0][8], Freq[0][9], Freq[0][10], Freq[0][11], Freq[1][0], Freq[1][1], Freq[1][2], Freq[1][3], Freq[1][4], Freq[1][5], Freq[1][6], Freq[1][7], Freq[1][8], Freq[1][9], Freq[1][10], Freq[1][11], Freq[2][0], Freq[2][1], Freq[2][2], Freq[2][3], Freq[2][4], Freq[2][5], Freq[2][6], Freq[2][7], Freq[2][8], Freq[2][9], Freq[2][10], Freq[2][11], Freq[3][0], Freq[3][1], Freq[3][2], Freq[3][3], Freq[3][4], Freq[3][5], Freq[3][6], Freq[3][7], Freq[3][8], Freq[3][9], Freq[3][10], Freq[3][11], Freq[4][0], Freq[4][1], Freq[4][2], Freq[4][3], Freq[4][4], Freq[4][5], Freq[4][6], Freq[4][7], Freq[4][8], Freq[4][9], Freq[4][10], Freq[4][11], Freq[5][0], Freq[5][1], Freq[5][2], Freq[5][3], Freq[5][4], Freq[5][5], Freq[5][6], Freq[5][7], Freq[5][8], Freq[5][9], Freq[5][10], Freq[5][11], Freq[6][0], Freq[6][1], Freq[6][2], Freq[6][3], Freq[6][4], Freq[6][5], Freq[6][6], Freq[6][7], Freq[6][8], Freq[6][9], Freq[6][10], Freq[6][11], Freq[7][0], Freq[7][1], Freq[7][2], Freq[7][3], Freq[7][4], Freq[7][5], Freq[7][6], Freq[7][7], Freq[7][8], Freq[7][9], Freq[7][10], Freq[7][11], Freq[8][0], Freq[8][1], Freq[8][2], Freq[8][3], Freq[8][4], Freq[8][5], Freq[8][6], Freq[8][7], Freq[8][8], Freq[8][9], Freq[8][10], Freq[8][11], Freq[9][0], Freq[9][1], Freq[9][2], Freq[9][3], Freq[9][4], Freq[9][5], Freq[9][6], Freq[9][7], Freq[9][8], Freq[9][9], Freq[9][10], Freq[9][11], R-Idx[0], R-OP[0][s], R-Busy[0], R-Busy Duration[0][s], Max Calls[0], Searching[0], Recovery[0], New SyncSource[0], Wide Band[0], Narrow Band[0], R-Idx[1], R-OP[1][s], R-Busy[1], R-Busy Duration[1][s], Max Calls[1], Searching[1], Recovery[1], New SyncSource[1], Wide Band[1], Narrow Band[1], R-Idx[2], R-OP[2][s], R-Busy[2], R-Busy Duration[2][s], Max Calls[2], Searching[2], Recovery[2], New SyncSource[2], Wide Band[2], Narrow Band[2]</Header_Line_Idx>
<Value_Line_0>'00','6CAB05F635DE',3386184,5238180,942,1,8,78353,0,4,0,0,255,0,0,892,0,0,0,35,0,0,285,94,7662,1,1,0,24,0,0,90,109,74,117,75,107,69,112,98,108,80,112,99,142,107,130,90,116,100,119,125,129,107,143,104,136,118,130,106,106,95,141,103,159,97,147,102,139,123,146,123,140,95,166,125,139,127,154,116,164,136,162,113,127,105,164,128,155,128,151,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,0,0,0,0,0,0,0,0,0,255,0,0,0,0,0,0,0,0,0,255,0,0,0,0,0,0,0,0,0</Value_Line_0>
<Value_Line_1>'00','6CAB05F63642',3386126,5238247,160,0,0,12875,0,2,0,0,1,1,8,146,0,0,0,2,0,0,30,10,167,2,2,2,25,0,0,60,118,72,105,52,118,48,144,57,127,63,122,109,126,88,129,134,129,140,109,127,128,128,142,132,134,85,133,114,117,106,126,97,117,131,133,142,134,82,141,129,137,144,141,124,141,117,122,98,145,78,141,120,146,98,114,90,134,88,123,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,0,0,0,0,0,0,0,0,0,255,0,0,0,0,0,0,0,0,0,255,0,0,0,0,0,0,0,0,0</Value_Line_1>
</Statistics>
</Status>

View file

@ -0,0 +1,386 @@
[
{
"path": "Status.System_Information.Released_Build",
"isEmpty": false,
"preview": "Yes",
"handsetRelated": false
},
{
"path": "Status.System_Information.Multi_Cell",
"isEmpty": false,
"preview": "Ready(TXT_STATE_KEEP_ALIVE) Secondary",
"handsetRelated": false
},
{
"path": "Status.System_Information.Phone_Type",
"isEmpty": false,
"preview": "IPDECT-V2 (DBS-210-3PC)",
"handsetRelated": false
},
{
"path": "Status.System_Information.System_Type",
"isEmpty": false,
"preview": "Generic SIP (RFC 3261)",
"handsetRelated": false
},
{
"path": "Status.System_Information.Unit_Name",
"isEmpty": false,
"preview": "SME VoIP",
"handsetRelated": false
},
{
"path": "Status.System_Information.Unit_Index",
"isEmpty": false,
"preview": "Base Idx:1",
"handsetRelated": false
},
{
"path": "Status.System_Information.RF_Band",
"isEmpty": false,
"preview": "US",
"handsetRelated": false
},
{
"path": "Status.System_Information.Conflict_Info",
"isEmpty": false,
"preview": "No Conflict",
"handsetRelated": false
},
{
"path": "Status.System_Information.Current_Local_Time",
"isEmpty": false,
"preview": "27-Jul-2026 15:51:57",
"handsetRelated": false
},
{
"path": "Status.System_Information.Operating_Time",
"isEmpty": false,
"preview": "39 Days 04:36:13 (H:M:S)",
"handsetRelated": false
},
{
"path": "Status.System_Information.RFPI_Address",
"isEmpty": false,
"preview": "135098C1; RPN:04",
"handsetRelated": false
},
{
"path": "Status.System_Information.MAC_Address",
"isEmpty": false,
"preview": "6cab05f63642",
"handsetRelated": true
},
{
"path": "Status.System_Information.IP_Address",
"isEmpty": false,
"preview": "10.52.38.152",
"handsetRelated": false
},
{
"path": "Status.System_Information.Product_Configuration",
"isEmpty": false,
"preview": "0000",
"handsetRelated": false
},
{
"path": "Status.System_Information.Firmware_Version",
"isEmpty": false,
"preview": "IPDECT-V2/05-01-03-0101-09/18-Mar-2026 10:29",
"handsetRelated": false
},
{
"path": "Status.System_Information.Firmware_URL.Update_Server_Address",
"isEmpty": false,
"preview": "https://cisco.sipflash.com",
"handsetRelated": false
},
{
"path": "Status.System_Information.Firmware_URL.Path",
"isEmpty": false,
"preview": "dms/dbS210",
"handsetRelated": false
},
{
"path": "Status.System_Information.Reboot_Log.Reboot_Line_1",
"isEmpty": false,
"preview": "2026-06-18 11:09:04 (135) Power Loss (80) Firmware Version 05-01-03-0101-09",
"handsetRelated": false
},
{
"path": "Status.System_Information.Reboot_Log.Reboot_Line_2",
"isEmpty": false,
"preview": "2026-05-31 06:44:34 (134) Power Loss (80) Firmware Version 05-01-03-0101-09",
"handsetRelated": false
},
{
"path": "Status.System_Information.Reboot_Log.Reboot_Line_3",
"isEmpty": false,
"preview": "2026-05-28 00:47:55 (133) Forced Reboot (81) Firmware Version 05-01-02-0101-05",
"handsetRelated": false
},
{
"path": "Status.System_Information.Reboot_Log.Reboot_Line_4",
"isEmpty": false,
"preview": "2026-05-10 07:19:17 (132) Unexpected Reboot (43) Firmware Version 05-01-02-0101-05",
"handsetRelated": false
},
{
"path": "Status.System_Information.Reboot_Log.Reboot_Line_5",
"isEmpty": false,
"preview": "2026-05-10 07:11:23 (131) Power Loss (80) Firmware Version 05-01-02-0101-05",
"handsetRelated": false
},
{
"path": "Status.System_Information.Reboot_Log.Reboot_Line_6",
"isEmpty": false,
"preview": "2026-04-12 09:21:45 (130) Power Loss (80) Firmware Version 05-01-02-0101-05",
"handsetRelated": false
},
{
"path": "Status.System_Information.Base_Station_Status",
"isEmpty": false,
"preview": "Idle",
"handsetRelated": false
},
{
"path": "Status.System_Information.Custom_CA_Status.Custom_CA_Provisioning_Status",
"isEmpty": false,
"preview": "N/A",
"handsetRelated": false
},
{
"path": "Status.System_Information.Custom_CA_Status.Custom_CA_Info",
"isEmpty": false,
"preview": "Not Installed",
"handsetRelated": false
},
{
"path": "Status.System_Information.Dot1x_Authentication.Transaction_status",
"isEmpty": false,
"preview": "Unavailable",
"handsetRelated": false
},
{
"path": "Status.System_Information.Dot1x_Authentication.Protocol",
"isEmpty": false,
"preview": "N/A",
"handsetRelated": false
},
{
"path": "Status.System_Information.RSSI_List.RSSI_Line_1",
"isEmpty": false,
"preview": "RSSI for RPN:4 is -49 [dBm]",
"handsetRelated": true
},
{
"path": "Status.System_Information.RTP_Usage.Total_RTP",
"isEmpty": false,
"preview": "4",
"handsetRelated": false
},
{
"path": "Status.System_Information.RTP_Usage.Max_RTP",
"isEmpty": false,
"preview": "-1",
"handsetRelated": false
},
{
"path": "Status.System_Information.RTP_Usage.Time_In_Max_RTP",
"isEmpty": false,
"preview": "49710 days 06:28:15 [H:M:S]",
"handsetRelated": false
},
{
"path": "Status.System_Information.RTP_Usage.Current_RTP",
"isEmpty": false,
"preview": "0",
"handsetRelated": false
},
{
"path": "Status.System_Information.RTP_Usage.Current_Local_RTP",
"isEmpty": false,
"preview": "0",
"handsetRelated": false
},
{
"path": "Status.System_Information.RTP_Usage.Current_Relay_RTP",
"isEmpty": false,
"preview": "0",
"handsetRelated": false
},
{
"path": "Status.System_Information.RTP_Usage.Remote_Relay_RTP",
"isEmpty": false,
"preview": "0",
"handsetRelated": false
},
{
"path": "Status.System_Information.RTP_Usage.Current_Recording",
"isEmpty": false,
"preview": "0",
"handsetRelated": false
},
{
"path": "Status.System_Information.Device_Presence",
"isEmpty": true,
"preview": null,
"handsetRelated": true
},
{
"path": "Status.System_Information.Device_FWU_Info.Device_Base_Station",
"isEmpty": false,
"preview": "Base type:DBS-210-3PC - Required Version:501 Required Branch:309",
"handsetRelated": false
},
{
"path": "Status.System_Information.Device_FWU_Info.Device_Line_0",
"isEmpty": false,
"preview": "Device type:6825 - Required Version:501 Required Branch:308 Language Pack:6825_default",
"handsetRelated": true
},
{
"path": "Status.System_Information.Device_FWU_Info.Device_Line_1",
"isEmpty": false,
"preview": "Device type:6825-RGD - Required Version:501 Required Branch:308 Language Pack:6825-RGD_default",
"handsetRelated": true
},
{
"path": "Status.System_Information.Device_FWU_Info.Device_Line_2",
"isEmpty": false,
"preview": "Device type:6823 - Required Version:501 Required Branch:308 Language Pack:6823_default",
"handsetRelated": true
},
{
"path": "Status.System_Information.Device_FWU_Info.Device_Line_3",
"isEmpty": false,
"preview": "Device type:RPT-110-3PC - Required Version:501 Required Branch:303",
"handsetRelated": true
},
{
"path": "Status.System_Information.Push_To_Talk",
"isEmpty": false,
"preview": "Off",
"handsetRelated": false
},
{
"path": "Status.System_Information.Emergency_Calls.Emergency_Number_1",
"isEmpty": false,
"preview": "911",
"handsetRelated": false
},
{
"path": "Status.System_Information.Emergency_Calls.Emergency_Number_2",
"isEmpty": false,
"preview": "1911",
"handsetRelated": false
},
{
"path": "Status.System_Information.Emergency_Calls.Emergency_Number_3",
"isEmpty": false,
"preview": "933",
"handsetRelated": false
},
{
"path": "Status.System_Information.Emergency_Calls.Emergency_Number_4",
"isEmpty": false,
"preview": "No Number set!",
"handsetRelated": false
},
{
"path": "Status.System_Information.Emergency_Calls.Emergency_Number_5",
"isEmpty": false,
"preview": "No Number set!",
"handsetRelated": false
},
{
"path": "Status.System_Information.SIP_Identity_Status",
"isEmpty": true,
"preview": null,
"handsetRelated": true
},
{
"path": "Status.Device_Information",
"isEmpty": true,
"preview": null,
"handsetRelated": false
},
{
"path": "Status.Statistics.Network_Statistics.Tx_Packets",
"isEmpty": false,
"preview": "3813285",
"handsetRelated": true
},
{
"path": "Status.Statistics.Network_Statistics.Tx_Blocked",
"isEmpty": false,
"preview": "0",
"handsetRelated": true
},
{
"path": "Status.Statistics.Network_Statistics.Tx_Dropped",
"isEmpty": false,
"preview": "0",
"handsetRelated": true
},
{
"path": "Status.Statistics.Network_Statistics.Tx_Errors",
"isEmpty": false,
"preview": "0",
"handsetRelated": true
},
{
"path": "Status.Statistics.Network_Statistics.Tx_Broadcasts",
"isEmpty": false,
"preview": "0",
"handsetRelated": true
},
{
"path": "Status.Statistics.Network_Statistics.Rx_Packets",
"isEmpty": false,
"preview": "5485843",
"handsetRelated": true
},
{
"path": "Status.Statistics.Network_Statistics.Rx_Blocked",
"isEmpty": false,
"preview": "0",
"handsetRelated": true
},
{
"path": "Status.Statistics.Network_Statistics.Rx_Dropped",
"isEmpty": false,
"preview": "0",
"handsetRelated": true
},
{
"path": "Status.Statistics.Network_Statistics.Rx_Errors",
"isEmpty": false,
"preview": "0",
"handsetRelated": true
},
{
"path": "Status.Statistics.Network_Statistics.Rx_Broadcasts",
"isEmpty": false,
"preview": "4773",
"handsetRelated": true
},
{
"path": "Status.Statistics.Header_Line_Idx",
"isEmpty": false,
"preview": "RPN, MAC-Addr, OP[s], DT[s], Call Cnt, Call Drop, Call Failed, Duration[s], Active Calls, Max Calls, Busy, Busy Duratio…",
"handsetRelated": true
},
{
"path": "Status.Statistics.Value_Line_0",
"isEmpty": false,
"preview": "'00','6CAB05F635DE',3386184,5238180,942,1,8,78353,0,4,0,0,255,0,0,892,0,0,0,35,0,0,285,94,7662,1,1,0,24,0,0,90,109,74,1…",
"handsetRelated": true
},
{
"path": "Status.Statistics.Value_Line_1",
"isEmpty": false,
"preview": "'00','6CAB05F63642',3386126,5238247,160,0,0,12875,0,2,0,0,1,1,8,146,0,0,0,2,0,0,30,10,167,2,2,2,25,0,0,60,118,72,105,52…",
"handsetRelated": true
}
]

View file

@ -0,0 +1,169 @@
{
"parsed": {
"device": {
"model": "IPDECT-V2 (DBS-210-3PC)",
"systemType": "Generic SIP (RFC 3261)",
"unitName": "SME VoIP",
"unitIndex": "Base Idx:1",
"rfBand": "US",
"productConfiguration": "0000",
"macAddress": "6c:ab:05:f6:36:42",
"ipAddress": "10.52.38.152",
"rfpiAddress": "135098C1; RPN:04",
"releasedBuild": true
},
"firmware": {
"version": "IPDECT-V2/05-01-03-0101-09/18-Mar-2026 10:29",
"updateServer": "https://cisco.sipflash.com",
"updatePath": "dms/dbS210",
"requiredFor": {
"6823": {
"requiredVersion": "501",
"requiredBranch": "308",
"languagePack": "6823_default",
"_sourceKey": "Device_Line_2"
},
"6825": {
"requiredVersion": "501",
"requiredBranch": "308",
"languagePack": "6825_default",
"_sourceKey": "Device_Line_0"
},
"DBS-210-3PC": {
"requiredVersion": "501",
"requiredBranch": "309",
"languagePack": null,
"_sourceKey": "Device_Base_Station"
},
"6825-RGD": {
"requiredVersion": "501",
"requiredBranch": "308",
"languagePack": "6825-RGD_default",
"_sourceKey": "Device_Line_1"
},
"RPT-110-3PC": {
"requiredVersion": "501",
"requiredBranch": "303",
"languagePack": null,
"_sourceKey": "Device_Line_3"
}
}
},
"time": {
"currentLocalTime": "27-Jul-2026 15:51:57",
"operatingTime": "39 Days 04:36:13 (H:M:S)",
"operatingTimeSeconds": 16573
},
"multiCell": {
"role": "ready",
"raw": "Ready(TXT_STATE_KEEP_ALIVE) Secondary"
},
"baseStatus": "idle",
"conflictInfo": "No Conflict",
"security": {
"customCa": {
"provisioningStatus": "N/A",
"info": "Not Installed",
"installed": false
},
"dot1x": {
"transactionStatus": "Unavailable",
"protocol": "N/A"
}
},
"rebootLog": [
{
"at": "2026-06-18T11:09:04",
"sequence": 135,
"reasonName": "Power Loss",
"reasonCode": 80,
"firmwareAtBoot": "05-01-03-0101-09",
"raw": "2026-06-18 11:09:04 (135) Power Loss (80) Firmware Version 05-01-03-0101-09"
},
{
"at": "2026-05-31T06:44:34",
"sequence": 134,
"reasonName": "Power Loss",
"reasonCode": 80,
"firmwareAtBoot": "05-01-03-0101-09",
"raw": "2026-05-31 06:44:34 (134) Power Loss (80) Firmware Version 05-01-03-0101-09"
},
{
"at": "2026-05-28T00:47:55",
"sequence": 133,
"reasonName": "Forced Reboot",
"reasonCode": 81,
"firmwareAtBoot": "05-01-02-0101-05",
"raw": "2026-05-28 00:47:55 (133) Forced Reboot (81) Firmware Version 05-01-02-0101-05"
},
{
"at": "2026-05-10T07:19:17",
"sequence": 132,
"reasonName": "Unexpected Reboot",
"reasonCode": 43,
"firmwareAtBoot": "05-01-02-0101-05",
"raw": "2026-05-10 07:19:17 (132) Unexpected Reboot (43) Firmware Version 05-01-02-0101-05"
},
{
"at": "2026-05-10T07:11:23",
"sequence": 131,
"reasonName": "Power Loss",
"reasonCode": 80,
"firmwareAtBoot": "05-01-02-0101-05",
"raw": "2026-05-10 07:11:23 (131) Power Loss (80) Firmware Version 05-01-02-0101-05"
},
{
"at": "2026-04-12T09:21:45",
"sequence": 130,
"reasonName": "Power Loss",
"reasonCode": 80,
"firmwareAtBoot": "05-01-02-0101-05",
"raw": "2026-04-12 09:21:45 (130) Power Loss (80) Firmware Version 05-01-02-0101-05"
}
],
"rtp": {
"total": 4,
"max": -1,
"current": 0,
"currentLocal": 0,
"currentRelay": 0,
"remoteRelay": 0,
"currentRecording": 0,
"timeInMaxRtp": "49710 days 06:28:15 [H:M:S]"
},
"network": {
"txPackets": 3813285,
"txBlocked": 0,
"txDropped": 0,
"txErrors": 0,
"txBroadcasts": 0,
"rxPackets": 5485843,
"rxBlocked": 0,
"rxDropped": 0,
"rxErrors": 0,
"rxBroadcasts": 4773
},
"emergencyNumbers": [
"911",
"1911",
"933"
],
"rssi": [],
"devicePresence": [],
"sipIdentityStatus": [],
"perRpnStats": [],
"features": {
"pushToTalk": false
},
"statisticsHeader": "RPN, MAC-Addr, OP[s], DT[s], Call Cnt, Call Drop, Call Failed, Duration[s], Active Calls, Max Calls, Busy, Busy Duration[s], Min Latency[ms], Avg Latency[ms], Max Latency[ms], G711U, G711A, G729, G722, G726, OPUS, BV32, Handovers, Failed Handovers, SIP reg failed, Handset Removed, Searching, RcHeapFree Running, New SyncSource, LAN Sync Lost, LAN Primary Lost, Freq[0][0], Freq[0][1], Freq[0][2], Freq[0][3], Freq[0][4], Freq[0][5], Freq[0][6], Freq[0][7], Freq[0][8], Freq[0][9], Freq[0][10], Freq[0][11], Freq[1][0], Freq[1][1], Freq[1][2], Freq[1][3], Freq[1][4], Freq[1][5], Freq[1][6], Freq[1][7], Freq[1][8], Freq[1][9], Freq[1][10], Freq[1][11], Freq[2][0], Freq[2][1], Freq[2][2], Freq[2][3], Freq[2][4], Freq[2][5], Freq[2][6], Freq[2][7], Freq[2][8], Freq[2][9], Freq[2][10], Freq[2][11], Freq[3][0], Freq[3][1], Freq[3][2], Freq[3][3], Freq[3][4], Freq[3][5], Freq[3][6], Freq[3][7], Freq[3][8], Freq[3][9], Freq[3][10], Freq[3][11], Freq[4][0], Freq[4][1], Freq[4][2], Freq[4][3], Freq[4][4], Freq[4][5], Freq[4][6], Freq[4][7], Freq[4][8], Freq[4][9], Freq[4][10], Freq[4][11], Freq[5][0], Freq[5][1], Freq[5][2], Freq[5][3], Freq[5][4], Freq[5][5], Freq[5][6], Freq[5][7], Freq[5][8], Freq[5][9], Freq[5][10], Freq[5][11], Freq[6][0], Freq[6][1], Freq[6][2], Freq[6][3], Freq[6][4], Freq[6][5], Freq[6][6], Freq[6][7], Freq[6][8], Freq[6][9], Freq[6][10], Freq[6][11], Freq[7][0], Freq[7][1], Freq[7][2], Freq[7][3], Freq[7][4], Freq[7][5], Freq[7][6], Freq[7][7], Freq[7][8], Freq[7][9], Freq[7][10], Freq[7][11], Freq[8][0], Freq[8][1], Freq[8][2], Freq[8][3], Freq[8][4], Freq[8][5], Freq[8][6], Freq[8][7], Freq[8][8], Freq[8][9], Freq[8][10], Freq[8][11], Freq[9][0], Freq[9][1], Freq[9][2], Freq[9][3], Freq[9][4], Freq[9][5], Freq[9][6], Freq[9][7], Freq[9][8], Freq[9][9], Freq[9][10], Freq[9][11], R-Idx[0], R-OP[0][s], R-Busy[0], R-Busy Duration[0][s], Max Calls[0], Searching[0], Recovery[0], New SyncSource[0], Wide Band[0], Narrow Band[0], R-Idx[1], R-OP[1][s], R-Busy[1], R-Busy Duration[1][s], Max Calls[1], Searching[1], Recovery[1], New SyncSource[1], Wide Band[1], Narrow Band[1], R-Idx[2], R-OP[2][s], R-Busy[2], R-Busy Duration[2][s], Max Calls[2], Searching[2], Recovery[2], New SyncSource[2], Wide Band[2], Narrow Band[2]",
"_rawStatisticsHeader": "RPN, MAC-Addr, OP[s], DT[s], Call Cnt, Call Drop, Call Failed, Duration[s], Active Calls, Max Calls, Busy, Busy Duration[s], Min Latency[ms], Avg Latency[ms], Max Latency[ms], G711U, G711A, G729, G722, G726, OPUS, BV32, Handovers, Failed Handovers, SIP reg failed, Handset Removed, Searching, RcHeapFree Running, New SyncSource, LAN Sync Lost, LAN Primary Lost, Freq[0][0], Freq[0][1], Freq[0][2], Freq[0][3], Freq[0][4], Freq[0][5], Freq[0][6], Freq[0][7], Freq[0][8], Freq[0][9], Freq[0][10], Freq[0][11], Freq[1][0], Freq[1][1], Freq[1][2], Freq[1][3], Freq[1][4], Freq[1][5], Freq[1][6], Freq[1][7], Freq[1][8], Freq[1][9], Freq[1][10], Freq[1][11], Freq[2][0], Freq[2][1], Freq[2][2], Freq[2][3], Freq[2][4], Freq[2][5], Freq[2][6], Freq[2][7], Freq[2][8], Freq[2][9], Freq[2][10], Freq[2][11], Freq[3][0], Freq[3][1], Freq[3][2], Freq[3][3], Freq[3][4], Freq[3][5], Freq[3][6], Freq[3][7], Freq[3][8], Freq[3][9], Freq[3][10], Freq[3][11], Freq[4][0], Freq[4][1], Freq[4][2], Freq[4][3], Freq[4][4], Freq[4][5], Freq[4][6], Freq[4][7], Freq[4][8], Freq[4][9], Freq[4][10], Freq[4][11], Freq[5][0], Freq[5][1], Freq[5][2], Freq[5][3], Freq[5][4], Freq[5][5], Freq[5][6], Freq[5][7], Freq[5][8], Freq[5][9], Freq[5][10], Freq[5][11], Freq[6][0], Freq[6][1], Freq[6][2], Freq[6][3], Freq[6][4], Freq[6][5], Freq[6][6], Freq[6][7], Freq[6][8], Freq[6][9], Freq[6][10], Freq[6][11], Freq[7][0], Freq[7][1], Freq[7][2], Freq[7][3], Freq[7][4], Freq[7][5], Freq[7][6], Freq[7][7], Freq[7][8], Freq[7][9], Freq[7][10], Freq[7][11], Freq[8][0], Freq[8][1], Freq[8][2], Freq[8][3], Freq[8][4], Freq[8][5], Freq[8][6], Freq[8][7], Freq[8][8], Freq[8][9], Freq[8][10], Freq[8][11], Freq[9][0], Freq[9][1], Freq[9][2], Freq[9][3], Freq[9][4], Freq[9][5], Freq[9][6], Freq[9][7], Freq[9][8], Freq[9][9], Freq[9][10], Freq[9][11], R-Idx[0], R-OP[0][s], R-Busy[0], R-Busy Duration[0][s], Max Calls[0], Searching[0], Recovery[0], New SyncSource[0], Wide Band[0], Narrow Band[0], R-Idx[1], R-OP[1][s], R-Busy[1], R-Busy Duration[1][s], Max Calls[1], Searching[1], Recovery[1], New SyncSource[1], Wide Band[1], Narrow Band[1], R-Idx[2], R-OP[2][s], R-Busy[2], R-Busy Duration[2][s], Max Calls[2], Searching[2], Recovery[2], New SyncSource[2], Wide Band[2], Narrow Band[2]"
},
"verdict": {
"healthy": false,
"warnings": [
"4 recent power-loss reboot(s); most recent at 2026-06-18T11:09:04"
],
"info": []
},
"fetchedAt": "2026-07-27T19:51:57.070Z"
}

View file

@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8"?>
<Status>
<System_Information>
<Released_Build>Yes</Released_Build>
<Multi_Cell>Ready(TXT_STATE_KEEP_ALIVE) Secondary</Multi_Cell>
<Phone_Type>IPDECT-V2 (DBS-210-3PC)</Phone_Type>
<System_Type>Generic SIP (RFC 3261)</System_Type>
<Unit_Name>SME VoIP</Unit_Name>
<Unit_Index>Base Idx:1</Unit_Index>
<RF_Band>US</RF_Band>
<Conflict_Info>No Conflict</Conflict_Info>
<Current_Local_Time>27-Jul-2026 15:51:57</Current_Local_Time>
<Operating_Time>39 Days 04:36:13 (H:M:S)</Operating_Time>
<RFPI_Address>135098C1; RPN:04</RFPI_Address>
<MAC_Address>6cab05f63642</MAC_Address>
<IP_Address>10.52.38.152</IP_Address>
<Product_Configuration>0000</Product_Configuration>
<Firmware_Version>IPDECT-V2/05-01-03-0101-09/18-Mar-2026 10:29</Firmware_Version>
<Firmware_URL>
<Update_Server_Address>https://cisco.sipflash.com</Update_Server_Address>
<Path>dms/dbS210</Path>
</Firmware_URL>
<Reboot_Log>
<Reboot_Line_1>2026-06-18 11:09:04 (135) Power Loss (80) Firmware Version 05-01-03-0101-09</Reboot_Line_1>
<Reboot_Line_2>2026-05-31 06:44:34 (134) Power Loss (80) Firmware Version 05-01-03-0101-09</Reboot_Line_2>
<Reboot_Line_3>2026-05-28 00:47:55 (133) Forced Reboot (81) Firmware Version 05-01-02-0101-05</Reboot_Line_3>
<Reboot_Line_4>2026-05-10 07:19:17 (132) Unexpected Reboot (43) Firmware Version 05-01-02-0101-05</Reboot_Line_4>
<Reboot_Line_5>2026-05-10 07:11:23 (131) Power Loss (80) Firmware Version 05-01-02-0101-05</Reboot_Line_5>
<Reboot_Line_6>2026-04-12 09:21:45 (130) Power Loss (80) Firmware Version 05-01-02-0101-05</Reboot_Line_6>
</Reboot_Log>
<Base_Station_Status>Idle</Base_Station_Status>
<Custom_CA_Status>
<Custom_CA_Provisioning_Status>N/A</Custom_CA_Provisioning_Status>
<Custom_CA_Info>Not Installed</Custom_CA_Info>
</Custom_CA_Status>
<Dot1x_Authentication>
<Transaction_status>Unavailable</Transaction_status>
<Protocol>N/A</Protocol>
</Dot1x_Authentication>
<RSSI_List>
<RSSI_Line_1>RSSI for RPN:4 is -49 [dBm]</RSSI_Line_1>
</RSSI_List>
<RTP_Usage>
<Total_RTP>4</Total_RTP>
<Max_RTP>-1</Max_RTP>
<Time_In_Max_RTP>49710 days 06:28:15 [H:M:S]</Time_In_Max_RTP>
<Current_RTP>0</Current_RTP>
<Current_Local_RTP>0</Current_Local_RTP>
<Current_Relay_RTP>0</Current_Relay_RTP>
<Remote_Relay_RTP>0</Remote_Relay_RTP>
<Current_Recording>0</Current_Recording>
</RTP_Usage>
<Device_Presence>
</Device_Presence>
<Device_FWU_Info>
<Device_Base_Station>Base type:DBS-210-3PC - Required Version:501 Required Branch:309</Device_Base_Station>
<Device_Line_0>Device type:6825 - Required Version:501 Required Branch:308 Language Pack:6825_default</Device_Line_0>
<Device_Line_1>Device type:6825-RGD - Required Version:501 Required Branch:308 Language Pack:6825-RGD_default</Device_Line_1>
<Device_Line_2>Device type:6823 - Required Version:501 Required Branch:308 Language Pack:6823_default</Device_Line_2>
<Device_Line_3>Device type:RPT-110-3PC - Required Version:501 Required Branch:303</Device_Line_3>
</Device_FWU_Info>
<Push_To_Talk>Off</Push_To_Talk>
<Emergency_Calls>
<Emergency_Number_1>911</Emergency_Number_1>
<Emergency_Number_2>1911</Emergency_Number_2>
<Emergency_Number_3>933</Emergency_Number_3>
<Emergency_Number_4>No Number set!</Emergency_Number_4>
<Emergency_Number_5>No Number set!</Emergency_Number_5>
</Emergency_Calls>
<SIP_Identity_Status>
</SIP_Identity_Status>
</System_Information>
<Device_Information>
</Device_Information>
<Statistics>
<Network_Statistics>
<Tx_Packets>3813285</Tx_Packets>
<Tx_Blocked>0</Tx_Blocked>
<Tx_Dropped>0</Tx_Dropped>
<Tx_Errors>0</Tx_Errors>
<Tx_Broadcasts>0</Tx_Broadcasts>
<Rx_Packets>5485843</Rx_Packets>
<Rx_Blocked>0</Rx_Blocked>
<Rx_Dropped>0</Rx_Dropped>
<Rx_Errors>0</Rx_Errors>
<Rx_Broadcasts>4773</Rx_Broadcasts>
</Network_Statistics>
<Header_Line_Idx>RPN, MAC-Addr, OP[s], DT[s], Call Cnt, Call Drop, Call Failed, Duration[s], Active Calls, Max Calls, Busy, Busy Duration[s], Min Latency[ms], Avg Latency[ms], Max Latency[ms], G711U, G711A, G729, G722, G726, OPUS, BV32, Handovers, Failed Handovers, SIP reg failed, Handset Removed, Searching, RcHeapFree Running, New SyncSource, LAN Sync Lost, LAN Primary Lost, Freq[0][0], Freq[0][1], Freq[0][2], Freq[0][3], Freq[0][4], Freq[0][5], Freq[0][6], Freq[0][7], Freq[0][8], Freq[0][9], Freq[0][10], Freq[0][11], Freq[1][0], Freq[1][1], Freq[1][2], Freq[1][3], Freq[1][4], Freq[1][5], Freq[1][6], Freq[1][7], Freq[1][8], Freq[1][9], Freq[1][10], Freq[1][11], Freq[2][0], Freq[2][1], Freq[2][2], Freq[2][3], Freq[2][4], Freq[2][5], Freq[2][6], Freq[2][7], Freq[2][8], Freq[2][9], Freq[2][10], Freq[2][11], Freq[3][0], Freq[3][1], Freq[3][2], Freq[3][3], Freq[3][4], Freq[3][5], Freq[3][6], Freq[3][7], Freq[3][8], Freq[3][9], Freq[3][10], Freq[3][11], Freq[4][0], Freq[4][1], Freq[4][2], Freq[4][3], Freq[4][4], Freq[4][5], Freq[4][6], Freq[4][7], Freq[4][8], Freq[4][9], Freq[4][10], Freq[4][11], Freq[5][0], Freq[5][1], Freq[5][2], Freq[5][3], Freq[5][4], Freq[5][5], Freq[5][6], Freq[5][7], Freq[5][8], Freq[5][9], Freq[5][10], Freq[5][11], Freq[6][0], Freq[6][1], Freq[6][2], Freq[6][3], Freq[6][4], Freq[6][5], Freq[6][6], Freq[6][7], Freq[6][8], Freq[6][9], Freq[6][10], Freq[6][11], Freq[7][0], Freq[7][1], Freq[7][2], Freq[7][3], Freq[7][4], Freq[7][5], Freq[7][6], Freq[7][7], Freq[7][8], Freq[7][9], Freq[7][10], Freq[7][11], Freq[8][0], Freq[8][1], Freq[8][2], Freq[8][3], Freq[8][4], Freq[8][5], Freq[8][6], Freq[8][7], Freq[8][8], Freq[8][9], Freq[8][10], Freq[8][11], Freq[9][0], Freq[9][1], Freq[9][2], Freq[9][3], Freq[9][4], Freq[9][5], Freq[9][6], Freq[9][7], Freq[9][8], Freq[9][9], Freq[9][10], Freq[9][11], R-Idx[0], R-OP[0][s], R-Busy[0], R-Busy Duration[0][s], Max Calls[0], Searching[0], Recovery[0], New SyncSource[0], Wide Band[0], Narrow Band[0], R-Idx[1], R-OP[1][s], R-Busy[1], R-Busy Duration[1][s], Max Calls[1], Searching[1], Recovery[1], New SyncSource[1], Wide Band[1], Narrow Band[1], R-Idx[2], R-OP[2][s], R-Busy[2], R-Busy Duration[2][s], Max Calls[2], Searching[2], Recovery[2], New SyncSource[2], Wide Band[2], Narrow Band[2]</Header_Line_Idx>
<Value_Line_0>'00','6CAB05F635DE',3386184,5238180,942,1,8,78353,0,4,0,0,255,0,0,892,0,0,0,35,0,0,285,94,7662,1,1,0,24,0,0,90,109,74,117,75,107,69,112,98,108,80,112,99,142,107,130,90,116,100,119,125,129,107,143,104,136,118,130,106,106,95,141,103,159,97,147,102,139,123,146,123,140,95,166,125,139,127,154,116,164,136,162,113,127,105,164,128,155,128,151,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,0,0,0,0,0,0,0,0,0,255,0,0,0,0,0,0,0,0,0,255,0,0,0,0,0,0,0,0,0</Value_Line_0>
<Value_Line_1>'00','6CAB05F63642',3386126,5238247,160,0,0,12875,0,2,0,0,1,1,8,146,0,0,0,2,0,0,30,10,167,2,2,2,25,0,0,60,118,72,105,52,118,48,144,57,127,63,122,109,126,88,129,134,129,140,109,127,128,128,142,132,134,85,133,114,117,106,126,97,117,131,133,142,134,82,141,129,137,144,141,124,141,117,122,98,145,78,141,120,146,98,114,90,134,88,123,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,0,0,0,0,0,0,0,0,0,255,0,0,0,0,0,0,0,0,0,255,0,0,0,0,0,0,0,0,0</Value_Line_1>
</Statistics>
</Status>

View file

@ -0,0 +1,86 @@
<?xml version="1.0" encoding="UTF-8"?>
<Status>
<System_Information>
<Released_Build>Yes</Released_Build>
<Multi_Cell>Primary(TXT_STATE_PRIMARY)</Multi_Cell>
<Phone_Type>IPDECT-V2 (DBS-210-3PC)</Phone_Type>
<System_Type>Generic SIP (RFC 3261)</System_Type>
<Unit_Name>SME VoIP</Unit_Name>
<Unit_Index>Base Idx:0</Unit_Index>
<RF_Band>US</RF_Band>
<Conflict_Info>No Conflict</Conflict_Info>
<Current_Local_Time>21-Jul-2026 14:30:00</Current_Local_Time>
<Operating_Time>48:15:30 (H:M:S)</Operating_Time>
<RFPI_Address>13508C9C; RPN:00</RFPI_Address>
<MAC_Address>6CAB05F62819</MAC_Address>
<IP_Address>10.4.11.87</IP_Address>
<Product_Configuration>0000</Product_Configuration>
<Firmware_Version>IPDECT-V2/05-01-03-0101-09/18-Mar-2026 10:29</Firmware_Version>
<Firmware_URL>
<Update_Server_Address>https://example.invalid</Update_Server_Address>
<Path>dms/dbS210</Path>
</Firmware_URL>
<Reboot_Log>
<Reboot_Line_1>2026-07-15 08:00:00 (200) Normal Reboot (21) Firmware Version 05-01-03-0101-09</Reboot_Line_1>
</Reboot_Log>
<Base_Station_Status>Idle</Base_Station_Status>
<Custom_CA_Status>
<Custom_CA_Provisioning_Status>N/A</Custom_CA_Provisioning_Status>
<Custom_CA_Info>Not Installed</Custom_CA_Info>
</Custom_CA_Status>
<Dot1x_Authentication>
<Transaction_status>Unavailable</Transaction_status>
<Protocol>N/A</Protocol>
</Dot1x_Authentication>
<RSSI_List>
<RPN_00>MAC:6CAB05A1B2C3; RSSI:-58 dBm</RPN_00>
<RPN_01>MAC:6CAB05D4E5F6; RSSI:-71 dBm</RPN_01>
</RSSI_List>
<RTP_Usage>
<Total_RTP>42</Total_RTP>
<Max_RTP>4</Max_RTP>
<Current_RTP>0</Current_RTP>
<Current_Local_RTP>0</Current_Local_RTP>
<Current_Relay_RTP>0</Current_Relay_RTP>
<Remote_Relay_RTP>0</Remote_Relay_RTP>
<Current_Recording>0</Current_Recording>
</RTP_Usage>
<Device_Presence>
<RPN_00>Present</RPN_00>
<RPN_01>Present</RPN_01>
</Device_Presence>
<Device_FWU_Info>
<Device_Base_Station>Base type:DBS-210-3PC - Required Version:501 Required Branch:309</Device_Base_Station>
<Device_Line_0>Device type:6825 - Required Version:501 Required Branch:308 Language Pack:6825_default</Device_Line_0>
</Device_FWU_Info>
<Push_To_Talk>Off</Push_To_Talk>
<Emergency_Calls>
<Emergency_Number_1>911</Emergency_Number_1>
</Emergency_Calls>
<SIP_Identity_Status>
<Line_0>Registered</Line_0>
<Line_1>Registered</Line_1>
<Line_2>Not Registered</Line_2>
<Line_3>Not Registered</Line_3>
</SIP_Identity_Status>
</System_Information>
<Device_Information>
</Device_Information>
<Statistics>
<Network_Statistics>
<Tx_Packets>5000</Tx_Packets>
<Tx_Blocked>0</Tx_Blocked>
<Tx_Dropped>0</Tx_Dropped>
<Tx_Errors>0</Tx_Errors>
<Tx_Broadcasts>0</Tx_Broadcasts>
<Rx_Packets>12000</Rx_Packets>
<Rx_Blocked>0</Rx_Blocked>
<Rx_Dropped>0</Rx_Dropped>
<Rx_Errors>0</Rx_Errors>
<Rx_Broadcasts>100</Rx_Broadcasts>
</Network_Statistics>
<Header_Line_Idx>RPN, MAC-Addr, OP[s], DT[s]</Header_Line_Idx>
<Statistics_Line_1>00, 6CAB05A1B2C3, 1200, 45</Statistics_Line_1>
<Statistics_Line_2>01, 6CAB05D4E5F6, 980, 62</Statistics_Line_2>
</Statistics>
</Status>

View file

@ -16,6 +16,7 @@ import {
} from '../services/renderers/phoneStatusRenderer.js'; } from '../services/renderers/phoneStatusRenderer.js';
import { renderAvStatusMarkdown } from '../services/renderers/avStatusRenderer.js'; import { renderAvStatusMarkdown } from '../services/renderers/avStatusRenderer.js';
import { renderDectStatusMarkdown } from '../services/renderers/dectStatusRenderer.js'; import { renderDectStatusMarkdown } from '../services/renderers/dectStatusRenderer.js';
import { buildHandsetContext } from '../services/dectStatus/buildHandsetContext.js';
// Timestamp exactly 3 hours in the past — makes `simpleTimeAgo` // Timestamp exactly 3 hours in the past — makes `simpleTimeAgo`
// deterministic to "3 hours ago" for the duration of this test run. // deterministic to "3 hours ago" for the duration of this test run.
@ -430,7 +431,18 @@ const fullOkResult = () => ({
{ sequence: 164, at: '2026-07-02T12:54:12', reasonName: 'Power Loss', reasonCode: 80, firmwareAtBoot: '05-01-03-0101-09' }, { sequence: 164, at: '2026-07-02T12:54:12', reasonName: 'Power Loss', reasonCode: 80, firmwareAtBoot: '05-01-03-0101-09' },
{ sequence: 163, at: '2026-07-02T12:49:50', reasonName: 'Normal Reboot', reasonCode: 21, firmwareAtBoot: '05-01-03-0101-09' }, { sequence: 163, at: '2026-07-02T12:49:50', reasonName: 'Normal Reboot', reasonCode: 21, firmwareAtBoot: '05-01-03-0101-09' },
], ],
network: { txPackets: 100, rxPackets: 200, rxDropped: 18, rxErrors: 0, txErrors: 0 }, network: {
txPackets: 100,
txBlocked: 0,
txDropped: 0,
txErrors: 0,
txBroadcasts: 0,
rxPackets: 200,
rxBlocked: 0,
rxDropped: 18,
rxErrors: 0,
rxBroadcasts: 0,
},
rtp: { total: 2, current: 0, currentLocal: 0, currentRelay: 0 }, rtp: { total: 2, current: 0, currentLocal: 0, currentRelay: 0 },
security: { security: {
customCa: { installed: false }, customCa: { installed: false },
@ -440,13 +452,13 @@ const fullOkResult = () => ({
}, },
verdict: { verdict: {
healthy: false, healthy: false,
warnings: ['1 recent power-loss reboot(s); most recent at 2026-07-02T12:54:12'], warnings: ['1 concerning reboot(s) in the last 7 days (latest: Power Loss at 2026-07-02T12:54:12)'],
info: ['Rx dropped packets: 18 since last boot'], info: ['Rx dropped packets: 18 since last boot'],
}, },
elapsedMs: 812, elapsedMs: 812,
}); });
test('dect status renderer: full dump includes reboot log, emergency numbers, and verdict', () => { test('dect status renderer: full dump includes reboot log, network stats, and verdict', () => {
const md = renderDectStatusMarkdown([fullOkResult()], { const md = renderDectStatusMarkdown([fullOkResult()], {
storeNum: '782', storeNum: '782',
footer: false, footer: false,
@ -458,9 +470,83 @@ test('dect status renderer: full dump includes reboot log, emergency numbers, an
assert.match(md, /\*\*Reboot log\*\*/); assert.match(md, /\*\*Reboot log\*\*/);
assert.match(md, /⚡ #164/); assert.match(md, /⚡ #164/);
assert.match(md, /Power Loss/); assert.match(md, /Power Loss/);
assert.match(md, /911, 1911/); assert.match(md, /\*\*TX:\*\* 100 pkts · 0 blocked · 0 dropped · 0 errors · 0 bcast/);
assert.match(md, /healthy: \*\*NO\*\*/); assert.match(md, /\*\*RX:\*\* 200 pkts · 0 blocked · 18 dropped · 0 errors · 0 bcast/);
assert.match(md, /\*\*RTP:\*\* 2 total/);
assert.match(md, /multi-cell Primary/);
assert.match(md, /Rx dropped packets: 18/); assert.match(md, /Rx dropped packets: 18/);
assert.doesNotMatch(md, /\*\*Security\*\*/);
assert.doesNotMatch(md, /\*\*Emergency numbers\*\*/);
assert.doesNotMatch(md, /Update server/);
assert.doesNotMatch(md, /\*\*Health verdict\*\*/);
});
test('dect status renderer: shows registered handsets and RSSI when context provided', () => {
const webexId = 'base-1';
const handsetCtx = buildHandsetContext({
dectNetwork: { name: 'Store DECT', handsetsCount: 2 },
dectBasestations: [{ id: webexId, mac: '6c:ab:05:f6:28:19', linesRegistered: 2 }],
dectHandsets: [
{
index: 1,
name: 'Handset A',
extension: '50482',
baseStationId: webexId,
mac: '6c:ab:05:a1:b2:c3',
lastRegistrationTime: new Date(Date.now() - 3 * 3600 * 1000).toISOString(),
},
{
index: 2,
name: 'Handset B',
extension: '50483',
baseStationId: webexId,
mac: '6c:ab:05:d4:e5:f6',
lastRegistrationTime: new Date(Date.now() - 3 * 3600 * 1000).toISOString(),
},
],
});
const result = fullOkResult();
result.base.webexId = webexId;
result.data.rssi = [
{ rpn: '00', mac: '6c:ab:05:a1:b2:c3', rssiDbm: -58 },
{ rpn: '01', mac: '6c:ab:05:d4:e5:f6', rssiDbm: -71 },
];
result.data.devicePresence = [{ key: 'RPN_00', present: true, raw: 'Present' }];
result.data.sipIdentityStatus = [
{ key: 'Line_0', status: 'Registered' },
{ key: 'Line_1', status: 'Registered' },
];
const md = renderDectStatusMarkdown([result], {
storeNum: '782',
footer: false,
handsetCtx,
});
assert.match(md, /\*\*Handsets & RF\*\*/);
assert.match(md, /Handset A.*ext 50482/);
assert.match(md, /Webex lines registered on base: \*\*2\*\*/);
assert.match(md, /-58 dBm/);
assert.match(md, /\*\*SIP\*\* line 0: \*\*Registered\*\*/);
assert.match(md, /\*\*Presence\*\* RPN_00: present/);
});
test('dect status renderer: unassigned handsets at store level', () => {
const handsetCtx = buildHandsetContext({
dectBasestations: [{ id: 'base-1', mac: '6c:ab:05:f6:28:19', linesRegistered: 0 }],
dectHandsets: [
{ name: 'Floater', extension: '9999', baseStationId: null, lastRegistrationTime: null },
],
});
const md = renderDectStatusMarkdown([fullOkResult()], {
storeNum: '782',
footer: false,
handsetCtx,
});
assert.match(md, /\*\*Unassigned handsets\*\*/);
assert.match(md, /Floater/);
}); });
test('dect status renderer: empty discovery + offline relay still produces a usable message', () => { test('dect status renderer: empty discovery + offline relay still produces a usable message', () => {

View file

@ -0,0 +1,81 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import path from 'node:path';
import { parseStatusXml } from '../integrations/cisco-dect/statusXml.js';
import { inventoryStatusXml, summarizeInventory } from '../integrations/cisco-dect/statusXmlInventory.js';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const FIXTURE_DIR = path.join(__dirname, 'fixtures/dect');
const SYNTHETIC = readFileSync(path.join(FIXTURE_DIR, 'status-with-handsets.xml'), 'utf8');
const STORE933_PRIMARY = readFileSync(path.join(FIXTURE_DIR, 'status-933-f635de.xml'), 'utf8');
const STORE933_SECONDARY = readFileSync(path.join(FIXTURE_DIR, 'status-933-f63642.xml'), 'utf8');
test('parseStatusXml: synthetic fixture parses RSSI rows', () => {
const s = parseStatusXml(SYNTHETIC);
assert.equal(s.rssi.length, 2);
assert.equal(s.rssi[0].mac, '6c:ab:05:a1:b2:c3');
assert.equal(s.rssi[0].rssiDbm, -58);
});
test('parseStatusXml: store 933 primary parses RSSI_Line format', () => {
const s = parseStatusXml(STORE933_PRIMARY);
assert.equal(s.rssi.length, 1);
assert.equal(s.rssi[0].rpn, '4');
assert.equal(s.rssi[0].rssiDbm, -47);
});
test('parseStatusXml: store 933 primary parses device presence and handsets', () => {
const s = parseStatusXml(STORE933_PRIMARY);
assert.equal(s.devicePresence.length, 2);
assert.equal(s.devicePresence[0].extension, '1');
assert.equal(s.devicePresence[0].deviceType, '6825');
assert.equal(s.devices.length, 2);
assert.equal(s.devices[0].displayName, '1-50933');
assert.equal(s.devices[0].rssiDbm, -76);
assert.equal(s.devices[0].batteryPercent, 81);
assert.match(s.devices[1].lockedRpn || '', /RPN:04/);
});
test('parseStatusXml: store 933 primary parses SIP identity blocks', () => {
const s = parseStatusXml(STORE933_PRIMARY);
assert.equal(s.sipIdentityStatus.length, 2);
assert.equal(s.sipIdentityStatus[0].status, 'OK');
assert.match(s.sipIdentityStatus[1].status || '', /Registering/i);
});
test('parseStatusXml: store 933 parses Value_Line statistics with MAC', () => {
const s = parseStatusXml(STORE933_PRIMARY);
assert.ok(s.perRpnStats.length >= 2);
assert.equal(s.perRpnStats[0].mac, '6c:ab:05:f6:35:de');
assert.ok(s.perRpnStats[0].opSeconds > 0);
});
test('parseStatusXml: store 933 uptime includes days component', () => {
const s = parseStatusXml(STORE933_PRIMARY);
assert.ok(s.time.operatingTimeSeconds > 86400);
});
test('parseStatusXml: store 933 primary parses multi-cell role', () => {
const s = parseStatusXml(STORE933_PRIMARY);
assert.equal(s.multiCell.role, 'primary');
assert.equal(s.multiCell.state, 'ready');
});
test('parseStatusXml: store 933 secondary has RSSI but no registered handsets', () => {
const s = parseStatusXml(STORE933_SECONDARY);
assert.equal(s.rssi[0].rssiDbm, -49);
assert.equal(s.devices.length, 0);
assert.equal(s.multiCell.role, 'secondary');
assert.equal(s.multiCell.state, 'ready');
});
test('inventoryStatusXml: store 933 finds handset-related paths', () => {
const inv = inventoryStatusXml(STORE933_PRIMARY);
const summary = summarizeInventory(inv);
assert.ok(summary.handsetRelated > 0);
assert.ok(summary.paths.some((p) => /Device_Information/i.test(p)));
});

View file

@ -213,6 +213,16 @@ test('parseStatusXml: multi-cell role parsed to a normalized token', () => {
assert.match(s.multiCell.raw, /TXT_STATE_UNCHAINED/); assert.match(s.multiCell.raw, /TXT_STATE_UNCHAINED/);
}); });
test('parseStatusXml: ready+primary multi-cell keeps primary as role', () => {
const xml = FIXTURE.replace(
/<Multi_Cell>[^<]+<\/Multi_Cell>/,
'<Multi_Cell>Ready(TXT_STATE_KEEP_ALIVE) Primary</Multi_Cell>',
);
const s = parseStatusXml(xml);
assert.equal(s.multiCell.role, 'primary');
assert.equal(s.multiCell.state, 'ready');
});
test('parseStatusXml: reboot log is 6 sorted entries with structured fields', () => { test('parseStatusXml: reboot log is 6 sorted entries with structured fields', () => {
const s = parseStatusXml(FIXTURE); const s = parseStatusXml(FIXTURE);
assert.equal(s.rebootLog.length, 6); assert.equal(s.rebootLog.length, 6);
@ -260,23 +270,48 @@ test('parseStatusXml: pushToTalk feature flag', () => {
// ─── Health verdict ───────────────────────────────────────────────── // ─── Health verdict ─────────────────────────────────────────────────
test('summarizeBaseHealth: fixture flags power-loss reboot and short uptime', () => { test('summarizeBaseHealth: fixture rx-dropped surfaces as info (old reboots ignored)', () => {
const s = parseStatusXml(FIXTURE); const s = parseStatusXml(FIXTURE);
const verdict = summarizeBaseHealth(s); const verdict = summarizeBaseHealth(s);
assert.equal(verdict.healthy, false); // Fixture power-loss reboot is from 2026-07-02 — outside the 7-day window.
// Uptime 620s (~10 min) is < 600 → boundary, so no uptime warning.
// But we DO have a power loss in the log, so healthy=false via that.
assert.ok( assert.ok(
verdict.warnings.some((w) => /power-loss/i.test(w)), !verdict.warnings.some((w) => /concerning reboot/i.test(w)),
`expected power-loss warning; got: ${JSON.stringify(verdict.warnings)}`, `old reboot should not warn; got: ${JSON.stringify(verdict.warnings)}`,
); );
// rxDropped=9 → info, not warning
assert.ok( assert.ok(
verdict.info.some((i) => /rx dropped/i.test(i)), verdict.info.some((i) => /rx dropped/i.test(i)),
`expected rx-dropped info; got: ${JSON.stringify(verdict.info)}`, `expected rx-dropped info; got: ${JSON.stringify(verdict.info)}`,
); );
}); });
test('summarizeBaseHealth: power-loss within 7 days is flagged', () => {
const recent = new Date(Date.now() - 2 * 86400000).toISOString().slice(0, 19);
const verdict = summarizeBaseHealth({
conflictInfo: 'No Conflict',
network: { rxDropped: 0 },
rebootLog: [
{ at: recent, sequence: 1, reasonName: 'Power Loss', reasonCode: 80 },
],
});
assert.equal(verdict.healthy, false);
assert.ok(
verdict.warnings.some((w) => /concerning reboot/i.test(w)),
`expected concerning reboot warning; got: ${JSON.stringify(verdict.warnings)}`,
);
});
test('summarizeBaseHealth: unexpected reboot within 7 days is flagged', () => {
const recent = new Date(Date.now() - 1 * 86400000).toISOString().slice(0, 19);
const verdict = summarizeBaseHealth({
conflictInfo: 'No Conflict',
network: { rxDropped: 0 },
rebootLog: [
{ at: recent, sequence: 1, reasonName: 'Unexpected Reboot', reasonCode: 43 },
],
});
assert.ok(verdict.warnings.some((w) => /Unexpected reboot/i.test(w)));
});
test('summarizeBaseHealth: RF conflict is flagged as a warning', () => { test('summarizeBaseHealth: RF conflict is flagged as a warning', () => {
const s = parseStatusXml(FIXTURE.replace( const s = parseStatusXml(FIXTURE.replace(
'<Conflict_Info>No Conflict</Conflict_Info>', '<Conflict_Info>No Conflict</Conflict_Info>',