ieeeColors = ({
navy: "#2C3E50",
teal: "#16A085",
orange: "#E67E22",
gray: "#7F8C8D",
lightGray: "#ECF0F1",
white: "#FFFFFF",
red: "#E74C3C",
green: "#27AE60",
purple: "#9B59B6",
blue: "#3498DB"
})
// ============================================================================
// STATE MANAGEMENT
// ============================================================================
// Session Configuration State
mutable sessionConfig = ({
clientId: "sensor-device-001",
cleanSession: false,
sessionExpiryInterval: 3600,
willTopic: "devices/sensor-device-001/status",
willMessage: "offline",
willQos: 1,
willRetain: true,
mqttVersion: "5.0"
})
// Broker State
mutable brokerState = ({
activeSessions: [],
storedSessions: [],
totalMemoryUsed: 0,
maxSessions: 100
})
// Client State
mutable clientState = ({
connected: false,
sessionPresent: false,
subscriptions: [],
pendingPublish: [],
pendingAck: [],
connectionTime: null,
lastActivity: null
})
// Timeline Events
mutable timelineEvents = []
// Current Scenario
mutable currentScenario = "none"
// Simulation Running
mutable simulationRunning = false
// ============================================================================
// SESSION CONFIGURATION PANEL
// ============================================================================
viewof configPanel = {
const container = htl.html`<div style="
background: linear-gradient(135deg, ${ieeeColors.navy} 0%, #1a252f 100%);
border-radius: 12px;
padding: 24px;
color: white;
margin-bottom: 20px;
">
<h3 style="margin: 0 0 20px 0; color: ${ieeeColors.teal};">
<span style="margin-right: 10px;">⚙</span>Session Configuration
</h3>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 20px;">
<!-- Client ID -->
<div style="background: rgba(255,255,255,0.1); padding: 16px; border-radius: 8px;">
<label style="display: block; margin-bottom: 8px; font-weight: 600; color: ${ieeeColors.lightGray};">
Client ID
</label>
<input type="text" id="clientIdInput" value="sensor-device-001" style="
width: 100%;
padding: 10px;
border: 2px solid ${ieeeColors.teal};
border-radius: 6px;
background: rgba(255,255,255,0.95);
color: ${ieeeColors.navy};
font-size: 14px;
box-sizing: border-box;
" />
<small style="color: ${ieeeColors.gray}; display: block; margin-top: 6px;">
Unique identifier for this client
</small>
</div>
<!-- MQTT Version -->
<div style="background: rgba(255,255,255,0.1); padding: 16px; border-radius: 8px;">
<label style="display: block; margin-bottom: 8px; font-weight: 600; color: ${ieeeColors.lightGray};">
MQTT Version
</label>
<select id="mqttVersionSelect" style="
width: 100%;
padding: 10px;
border: 2px solid ${ieeeColors.teal};
border-radius: 6px;
background: rgba(255,255,255,0.95);
color: ${ieeeColors.navy};
font-size: 14px;
">
<option value="3.1.1">MQTT 3.1.1</option>
<option value="5.0" selected>MQTT 5.0</option>
</select>
<small style="color: ${ieeeColors.gray}; display: block; margin-top: 6px;">
Protocol version affects available features
</small>
</div>
<!-- Clean Session Toggle -->
<div style="background: rgba(255,255,255,0.1); padding: 16px; border-radius: 8px;">
<label style="display: block; margin-bottom: 8px; font-weight: 600; color: ${ieeeColors.lightGray};">
Clean Session / Clean Start
</label>
<div style="display: flex; align-items: center; gap: 12px;">
<label style="
display: flex;
align-items: center;
cursor: pointer;
padding: 8px 16px;
border-radius: 20px;
background: ${ieeeColors.red};
transition: all 0.3s;
" id="cleanSessionToggle">
<input type="checkbox" id="cleanSessionCheck" style="display: none;" />
<span id="cleanSessionLabel" style="color: white; font-weight: 600;">FALSE (Persistent)</span>
</label>
</div>
<small style="color: ${ieeeColors.gray}; display: block; margin-top: 6px;">
TRUE = Fresh start | FALSE = Resume session
</small>
</div>
<!-- Session Expiry Interval (MQTT 5.0) -->
<div style="background: rgba(255,255,255,0.1); padding: 16px; border-radius: 8px;" id="expiryContainer">
<label style="display: block; margin-bottom: 8px; font-weight: 600; color: ${ieeeColors.lightGray};">
Session Expiry Interval (seconds)
</label>
<input type="range" id="expirySlider" min="0" max="86400" value="3600" style="width: 100%;" />
<div style="display: flex; justify-content: space-between; margin-top: 8px;">
<span style="color: ${ieeeColors.gray}; font-size: 12px;">0 (Immediate)</span>
<span id="expiryValue" style="color: ${ieeeColors.orange}; font-weight: 600;">3600s (1 hour)</span>
<span style="color: ${ieeeColors.gray}; font-size: 12px;">86400 (24h)</span>
</div>
<small style="color: ${ieeeColors.gray}; display: block; margin-top: 6px;">
MQTT 5.0 only - How long broker keeps session after disconnect
</small>
</div>
</div>
<!-- Will Message Configuration -->
<div style="margin-top: 20px; background: rgba(255,255,255,0.05); padding: 16px; border-radius: 8px; border-left: 4px solid ${ieeeColors.orange};">
<h4 style="margin: 0 0 12px 0; color: ${ieeeColors.orange};">Last Will & Testament (LWT)</h4>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 12px;">
<div>
<label style="display: block; margin-bottom: 4px; font-size: 12px; color: ${ieeeColors.lightGray};">Will Topic</label>
<input type="text" id="willTopicInput" value="devices/sensor-device-001/status" style="
width: 100%;
padding: 8px;
border: 1px solid ${ieeeColors.gray};
border-radius: 4px;
font-size: 12px;
box-sizing: border-box;
" />
</div>
<div>
<label style="display: block; margin-bottom: 4px; font-size: 12px; color: ${ieeeColors.lightGray};">Will Message</label>
<input type="text" id="willMessageInput" value="offline" style="
width: 100%;
padding: 8px;
border: 1px solid ${ieeeColors.gray};
border-radius: 4px;
font-size: 12px;
box-sizing: border-box;
" />
</div>
<div>
<label style="display: block; margin-bottom: 4px; font-size: 12px; color: ${ieeeColors.lightGray};">Will QoS</label>
<select id="willQosSelect" style="
width: 100%;
padding: 8px;
border: 1px solid ${ieeeColors.gray};
border-radius: 4px;
font-size: 12px;
">
<option value="0">QoS 0</option>
<option value="1" selected>QoS 1</option>
<option value="2">QoS 2</option>
</select>
</div>
<div>
<label style="display: block; margin-bottom: 4px; font-size: 12px; color: ${ieeeColors.lightGray};">Will Retain</label>
<select id="willRetainSelect" style="
width: 100%;
padding: 8px;
border: 1px solid ${ieeeColors.gray};
border-radius: 4px;
font-size: 12px;
">
<option value="true" selected>Yes (Retain)</option>
<option value="false">No</option>
</select>
</div>
</div>
</div>
</div>`;
// Event handlers
const cleanCheck = container.querySelector('#cleanSessionCheck');
const cleanToggle = container.querySelector('#cleanSessionToggle');
const cleanLabel = container.querySelector('#cleanSessionLabel');
const expirySlider = container.querySelector('#expirySlider');
const expiryValue = container.querySelector('#expiryValue');
const expiryContainer = container.querySelector('#expiryContainer');
const mqttVersion = container.querySelector('#mqttVersionSelect');
cleanToggle.onclick = () => {
cleanCheck.checked = !cleanCheck.checked;
if (cleanCheck.checked) {
cleanToggle.style.background = ieeeColors.green;
cleanLabel.textContent = 'TRUE (Clean Start)';
} else {
cleanToggle.style.background = ieeeColors.red;
cleanLabel.textContent = 'FALSE (Persistent)';
}
mutable sessionConfig = {...sessionConfig, cleanSession: cleanCheck.checked};
};
expirySlider.oninput = () => {
const val = parseInt(expirySlider.value);
let label = `${val}s`;
if (val >= 3600) label = `${Math.round(val/3600)}h`;
if (val === 0) label = 'Immediate';
if (val === 86400) label = '24 hours';
expiryValue.textContent = label;
mutable sessionConfig = {...sessionConfig, sessionExpiryInterval: val};
};
mqttVersion.onchange = () => {
const version = mqttVersion.value;
expiryContainer.style.opacity = version === '5.0' ? '1' : '0.4';
expirySlider.disabled = version !== '5.0';
mutable sessionConfig = {...sessionConfig, mqttVersion: version};
};
container.querySelector('#clientIdInput').oninput = (e) => {
mutable sessionConfig = {...sessionConfig, clientId: e.target.value};
};
return container;
}
// ============================================================================
// SCENARIO SELECTOR
// ============================================================================
viewof scenarioSelector = {
const scenarios = [
{
id: "clean-connect",
name: "Clean Session Connect",
description: "Fresh start - broker discards any stored state",
icon: "🔄",
color: ieeeColors.teal
},
{
id: "persistent-reconnect",
name: "Persistent Session Reconnect",
description: "Resume with stored subscriptions and queued messages",
icon: "🔗",
color: ieeeColors.green
},
{
id: "session-takeover",
name: "Session Takeover",
description: "Same Client ID, new connection - previous client disconnected",
icon: "⚠",
color: ieeeColors.orange
},
{
id: "session-expiry",
name: "Session Expiry",
description: "Session times out after disconnect (MQTT 5.0)",
icon: "⏲",
color: ieeeColors.purple
},
{
id: "offline-delivery",
name: "Offline Message Delivery",
description: "QoS 1/2 messages queued while client offline",
icon: "📥",
color: ieeeColors.blue
}
];
const container = htl.html`<div style="
background: ${ieeeColors.lightGray};
border-radius: 12px;
padding: 20px;
margin-bottom: 20px;
">
<h3 style="margin: 0 0 16px 0; color: ${ieeeColors.navy};">
<span style="margin-right: 10px;">🎬</span>Simulation Scenarios
</h3>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px;">
${scenarios.map(s => htl.html`
<button class="scenario-btn" data-scenario="${s.id}" style="
padding: 16px;
border: 2px solid ${s.color};
border-radius: 8px;
background: white;
cursor: pointer;
text-align: left;
transition: all 0.3s;
">
<div style="font-size: 24px; margin-bottom: 8px;">${htl.html([s.icon])}</div>
<div style="font-weight: 600; color: ${ieeeColors.navy}; margin-bottom: 4px;">${s.name}</div>
<div style="font-size: 11px; color: ${ieeeColors.gray}; line-height: 1.4;">${s.description}</div>
</button>
`)}
</div>
<div style="margin-top: 16px; text-align: center;">
<button id="runScenarioBtn" style="
padding: 12px 32px;
background: ${ieeeColors.navy};
color: white;
border: none;
border-radius: 6px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
" disabled>
<span style="margin-right: 8px;">►</span>Run Selected Scenario
</button>
<button id="resetBtn" style="
padding: 12px 24px;
background: ${ieeeColors.gray};
color: white;
border: none;
border-radius: 6px;
font-size: 14px;
cursor: pointer;
margin-left: 12px;
">
Reset
</button>
</div>
</div>`;
let selectedScenario = null;
const buttons = container.querySelectorAll('.scenario-btn');
const runBtn = container.querySelector('#runScenarioBtn');
const resetBtn = container.querySelector('#resetBtn');
buttons.forEach(btn => {
btn.onmouseenter = () => {
btn.style.transform = 'translateY(-2px)';
btn.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)';
};
btn.onmouseleave = () => {
btn.style.transform = 'translateY(0)';
btn.style.boxShadow = 'none';
};
btn.onclick = () => {
buttons.forEach(b => {
b.style.background = 'white';
b.style.borderWidth = '2px';
});
btn.style.background = `${scenarios.find(s => s.id === btn.dataset.scenario).color}20`;
btn.style.borderWidth = '3px';
selectedScenario = btn.dataset.scenario;
runBtn.disabled = false;
mutable currentScenario = selectedScenario;
};
});
runBtn.onclick = () => {
if (selectedScenario) {
mutable simulationRunning = true;
runScenario(selectedScenario);
}
};
resetBtn.onclick = () => {
mutable timelineEvents = [];
mutable clientState = {
connected: false,
sessionPresent: false,
subscriptions: [],
pendingPublish: [],
pendingAck: [],
connectionTime: null,
lastActivity: null
};
mutable brokerState = {
activeSessions: [],
storedSessions: [],
totalMemoryUsed: 0,
maxSessions: 100
};
mutable simulationRunning = false;
};
return container;
}
// ============================================================================
// SCENARIO RUNNER
// ============================================================================
runScenario = (scenarioId) => {
const clientId = sessionConfig.clientId;
const cleanSession = sessionConfig.cleanSession;
const expiry = sessionConfig.sessionExpiryInterval;
let events = [];
let newClientState = {...clientState};
let newBrokerState = {...brokerState};
const timestamp = () => new Date().toLocaleTimeString();
switch(scenarioId) {
case "clean-connect":
events = [
{ time: timestamp(), type: "connect", message: `CONNECT from ${clientId} with Clean Session=TRUE`, detail: "Client requests fresh session", status: "sent" },
{ time: timestamp(), type: "broker", message: "Broker discards any existing session state", detail: `Session for ${clientId} cleared`, status: "processing" },
{ time: timestamp(), type: "connack", message: "CONNACK with Session Present=FALSE", detail: "No previous session found/kept", status: "received" },
{ time: timestamp(), type: "subscribe", message: "SUBSCRIBE to sensors/+/temperature", detail: "QoS 1 subscription request", status: "sent" },
{ time: timestamp(), type: "suback", message: "SUBACK confirming QoS 1", detail: "Subscription stored (session-lifetime only)", status: "received" },
{ time: timestamp(), type: "info", message: "Session established - will be discarded on disconnect", detail: "No persistent storage", status: "info" }
];
newClientState = {
connected: true,
sessionPresent: false,
subscriptions: [{topic: "sensors/+/temperature", qos: 1}],
pendingPublish: [],
pendingAck: [],
connectionTime: new Date(),
lastActivity: new Date()
};
newBrokerState.activeSessions = [{
clientId: clientId,
cleanSession: true,
subscriptions: [{topic: "sensors/+/temperature", qos: 1}],
pendingMessages: [],
connectedSince: new Date(),
memoryUsed: 124
}];
break;
case "persistent-reconnect":
// Simulate pre-existing session
const storedSession = {
clientId: clientId,
subscriptions: [
{topic: "sensors/+/temperature", qos: 1},
{topic: "alerts/#", qos: 2}
],
pendingMessages: [
{id: 1, topic: "sensors/room1/temperature", payload: "23.5", qos: 1},
{id: 2, topic: "sensors/room2/temperature", payload: "21.2", qos: 1},
{id: 3, topic: "alerts/fire", payload: "ALARM", qos: 2}
],
lastDisconnect: new Date(Date.now() - 300000) // 5 min ago
};
events = [
{ time: timestamp(), type: "info", message: "Previous session exists on broker", detail: `${storedSession.pendingMessages.length} messages queued, 2 subscriptions stored`, status: "info" },
{ time: timestamp(), type: "connect", message: `CONNECT from ${clientId} with Clean Session=FALSE`, detail: "Client requests session resumption", status: "sent" },
{ time: timestamp(), type: "broker", message: "Broker finds existing session", detail: `Session age: 5 minutes`, status: "processing" },
{ time: timestamp(), type: "connack", message: "CONNACK with Session Present=TRUE", detail: "Subscriptions and messages preserved", status: "received" },
{ time: timestamp(), type: "publish", message: "Delivering queued message: sensors/room1/temperature", detail: "QoS 1, Payload: 23.5", status: "received" },
{ time: timestamp(), type: "publish", message: "Delivering queued message: sensors/room2/temperature", detail: "QoS 1, Payload: 21.2", status: "received" },
{ time: timestamp(), type: "publish", message: "Delivering queued message: alerts/fire", detail: "QoS 2, Payload: ALARM", status: "received" },
{ time: timestamp(), type: "info", message: "All queued messages delivered", detail: "Session fully restored", status: "success" }
];
newClientState = {
connected: true,
sessionPresent: true,
subscriptions: storedSession.subscriptions,
pendingPublish: [],
pendingAck: [],
connectionTime: new Date(),
lastActivity: new Date()
};
newBrokerState.activeSessions = [{
clientId: clientId,
cleanSession: false,
subscriptions: storedSession.subscriptions,
pendingMessages: [],
connectedSince: new Date(),
memoryUsed: 512
}];
break;
case "session-takeover":
events = [
{ time: timestamp(), type: "info", message: `Existing connection active for ${clientId}`, detail: "Connected from IP 192.168.1.100", status: "warning" },
{ time: timestamp(), type: "connect", message: `New CONNECT from ${clientId}`, detail: "From IP 192.168.1.105 (different source)", status: "sent" },
{ time: timestamp(), type: "broker", message: "Session takeover initiated", detail: "Broker must disconnect existing client", status: "processing" },
{ time: timestamp(), type: "disconnect", message: "Disconnecting existing client", detail: "Reason: Session taken over by another client", status: "warning" },
{ time: timestamp(), type: "broker", message: "Session state transferred", detail: "Subscriptions and pending messages preserved", status: "processing" },
{ time: timestamp(), type: "connack", message: "CONNACK to new client", detail: "Session Present=TRUE (if persistent session)", status: "received" },
{ time: timestamp(), type: "info", message: "Session takeover complete", detail: "New client now owns the session", status: "success" }
];
newClientState = {
connected: true,
sessionPresent: !cleanSession,
subscriptions: cleanSession ? [] : [{topic: "sensors/#", qos: 1}],
pendingPublish: [],
pendingAck: [],
connectionTime: new Date(),
lastActivity: new Date()
};
break;
case "session-expiry":
events = [
{ time: timestamp(), type: "info", message: "Client disconnects gracefully", detail: `Session Expiry: ${expiry}s`, status: "info" },
{ time: timestamp(), type: "disconnect", message: "DISCONNECT sent", detail: "Session stored on broker", status: "sent" },
{ time: timestamp(), type: "broker", message: "Session stored with expiry timer", detail: `Will expire in ${expiry} seconds`, status: "processing" },
{ time: timestamp(), type: "timer", message: "Time passes... (simulated)", detail: `${expiry} seconds elapsed`, status: "info" },
{ time: timestamp(), type: "broker", message: "Session expiry triggered", detail: "Cleaning up session resources", status: "warning" },
{ time: timestamp(), type: "broker", message: "Subscriptions removed", detail: "2 subscriptions deleted", status: "processing" },
{ time: timestamp(), type: "broker", message: "Pending messages discarded", detail: "5 QoS 1/2 messages lost", status: "warning" },
{ time: timestamp(), type: "info", message: "Session fully expired", detail: "Next connect will get Session Present=FALSE", status: "error" }
];
newClientState = {
connected: false,
sessionPresent: false,
subscriptions: [],
pendingPublish: [],
pendingAck: [],
connectionTime: null,
lastActivity: null
};
newBrokerState.storedSessions = [];
break;
case "offline-delivery":
events = [
{ time: timestamp(), type: "info", message: "Client establishes persistent session", detail: "Subscribes to sensors/# with QoS 1", status: "info" },
{ time: timestamp(), type: "disconnect", message: "Client goes offline", detail: "Network disconnection", status: "warning" },
{ time: timestamp(), type: "broker", message: "Session stored, awaiting reconnect", detail: "Expiry: ${expiry}s", status: "processing" },
{ time: timestamp(), type: "publish", message: "Message arrives: sensors/temp (QoS 1)", detail: "Queued for offline client", status: "info" },
{ time: timestamp(), type: "publish", message: "Message arrives: sensors/humidity (QoS 1)", detail: "Queued for offline client", status: "info" },
{ time: timestamp(), type: "publish", message: "Message arrives: sensors/pressure (QoS 0)", detail: "DISCARDED - QoS 0 not queued", status: "error" },
{ time: timestamp(), type: "publish", message: "Message arrives: sensors/co2 (QoS 2)", detail: "Queued for offline client", status: "info" },
{ time: timestamp(), type: "broker", message: "Queue status: 3 messages pending", detail: "Memory used: 1.2 KB", status: "info" },
{ time: timestamp(), type: "connect", message: "Client reconnects", detail: "Clean Session=FALSE", status: "sent" },
{ time: timestamp(), type: "connack", message: "CONNACK Session Present=TRUE", detail: "Resuming session", status: "received" },
{ time: timestamp(), type: "publish", message: "Delivering: sensors/temp", detail: "QoS 1 delivery", status: "received" },
{ time: timestamp(), type: "publish", message: "Delivering: sensors/humidity", detail: "QoS 1 delivery", status: "received" },
{ time: timestamp(), type: "publish", message: "Delivering: sensors/co2", detail: "QoS 2 delivery (4-way handshake)", status: "received" },
{ time: timestamp(), type: "info", message: "All queued messages delivered", detail: "Queue empty, session active", status: "success" }
];
newClientState = {
connected: true,
sessionPresent: true,
subscriptions: [{topic: "sensors/#", qos: 1}],
pendingPublish: [],
pendingAck: [],
connectionTime: new Date(),
lastActivity: new Date()
};
break;
}
mutable timelineEvents = events;
mutable clientState = newClientState;
mutable brokerState = newBrokerState;
}
// ============================================================================
// VISUAL STATE DIAGRAM
// ============================================================================
viewof stateDiagram = {
const width = 900;
const height = 450;
const container = htl.html`<div style="
background: white;
border-radius: 12px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
">
<h3 style="margin: 0 0 16px 0; color: ${ieeeColors.navy};">
<span style="margin-right: 10px;">📊</span>Session State Visualization
</h3>
<svg id="stateSvg" width="${width}" height="${height}" style="display: block; margin: 0 auto;"></svg>
</div>`;
const svg = container.querySelector('#stateSvg');
// Draw the state diagram
svg.innerHTML = `
<!-- Background Grid -->
<defs>
<pattern id="grid" width="20" height="20" patternUnits="userSpaceOnUse">
<path d="M 20 0 L 0 0 0 20" fill="none" stroke="${ieeeColors.lightGray}" stroke-width="0.5"/>
</pattern>
<marker id="arrowhead" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="${ieeeColors.navy}"/>
</marker>
<marker id="arrowOrange" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="${ieeeColors.orange}"/>
</marker>
</defs>
<rect width="100%" height="100%" fill="url(#grid)"/>
<!-- Client Side -->
<g transform="translate(50, 50)">
<rect x="0" y="0" width="280" height="350" rx="12" fill="${ieeeColors.navy}10" stroke="${ieeeColors.navy}" stroke-width="2"/>
<text x="140" y="30" text-anchor="middle" font-weight="bold" fill="${ieeeColors.navy}" font-size="16">CLIENT STATE</text>
<!-- Connection Status -->
<rect x="20" y="50" width="240" height="50" rx="6" fill="${clientState.connected ? ieeeColors.green : ieeeColors.red}20" stroke="${clientState.connected ? ieeeColors.green : ieeeColors.red}" stroke-width="2"/>
<circle cx="45" cy="75" r="8" fill="${clientState.connected ? ieeeColors.green : ieeeColors.red}"/>
<text x="60" y="80" fill="${ieeeColors.navy}" font-size="14">${clientState.connected ? 'Connected' : 'Disconnected'}</text>
<text x="240" y="80" text-anchor="end" fill="${ieeeColors.gray}" font-size="11">${clientState.connected ? 'Active' : 'Offline'}</text>
<!-- Session Present -->
<rect x="20" y="110" width="240" height="40" rx="6" fill="${ieeeColors.lightGray}" stroke="${ieeeColors.gray}"/>
<text x="30" y="135" fill="${ieeeColors.navy}" font-size="13">Session Present:</text>
<text x="240" y="135" text-anchor="end" fill="${clientState.sessionPresent ? ieeeColors.green : ieeeColors.orange}" font-weight="bold" font-size="13">${clientState.sessionPresent ? 'TRUE' : 'FALSE'}</text>
<!-- Subscriptions -->
<rect x="20" y="160" width="240" height="70" rx="6" fill="white" stroke="${ieeeColors.teal}" stroke-width="1.5"/>
<text x="30" y="180" fill="${ieeeColors.teal}" font-size="12" font-weight="bold">Subscriptions (${clientState.subscriptions.length})</text>
${clientState.subscriptions.slice(0, 2).map((s, i) => `
<text x="30" y="${198 + i * 16}" fill="${ieeeColors.navy}" font-size="11">${s.topic} (QoS ${s.qos})</text>
`).join('')}
${clientState.subscriptions.length > 2 ? `<text x="30" y="230" fill="${ieeeColors.gray}" font-size="10">+${clientState.subscriptions.length - 2} more...</text>` : ''}
<!-- Message Queues -->
<rect x="20" y="240" width="115" height="90" rx="6" fill="white" stroke="${ieeeColors.blue}" stroke-width="1.5"/>
<text x="78" y="260" text-anchor="middle" fill="${ieeeColors.blue}" font-size="11" font-weight="bold">TX Queue</text>
<text x="78" y="285" text-anchor="middle" fill="${ieeeColors.navy}" font-size="20">${clientState.pendingPublish.length}</text>
<text x="78" y="305" text-anchor="middle" fill="${ieeeColors.gray}" font-size="10">pending</text>
<rect x="145" y="240" width="115" height="90" rx="6" fill="white" stroke="${ieeeColors.purple}" stroke-width="1.5"/>
<text x="202" y="260" text-anchor="middle" fill="${ieeeColors.purple}" font-size="11" font-weight="bold">Pending ACK</text>
<text x="202" y="285" text-anchor="middle" fill="${ieeeColors.navy}" font-size="20">${clientState.pendingAck.length}</text>
<text x="202" y="305" text-anchor="middle" fill="${ieeeColors.gray}" font-size="10">awaiting</text>
</g>
<!-- Connection Lines -->
<g>
<line x1="340" y1="200" x2="440" y2="200" stroke="${ieeeColors.navy}" stroke-width="3" marker-end="url(#arrowhead)" stroke-dasharray="${clientState.connected ? '0' : '8,4'}"/>
<line x1="440" y1="220" x2="340" y2="220" stroke="${ieeeColors.orange}" stroke-width="3" marker-end="url(#arrowOrange)" stroke-dasharray="${clientState.connected ? '0' : '8,4'}"/>
<text x="390" y="180" text-anchor="middle" fill="${ieeeColors.gray}" font-size="10">MQTT</text>
<text x="390" y="250" text-anchor="middle" fill="${ieeeColors.gray}" font-size="10">${clientState.connected ? 'Active' : 'No Connection'}</text>
</g>
<!-- Broker Side -->
<g transform="translate(450, 50)">
<rect x="0" y="0" width="380" height="350" rx="12" fill="${ieeeColors.teal}10" stroke="${ieeeColors.teal}" stroke-width="2"/>
<text x="190" y="30" text-anchor="middle" font-weight="bold" fill="${ieeeColors.teal}" font-size="16">BROKER SESSION STATE</text>
<!-- Active Sessions -->
<rect x="20" y="50" width="170" height="130" rx="6" fill="white" stroke="${ieeeColors.green}" stroke-width="1.5"/>
<text x="105" y="70" text-anchor="middle" fill="${ieeeColors.green}" font-size="12" font-weight="bold">Active Sessions</text>
<text x="105" y="105" text-anchor="middle" fill="${ieeeColors.navy}" font-size="28">${brokerState.activeSessions.length}</text>
<text x="105" y="125" text-anchor="middle" fill="${ieeeColors.gray}" font-size="10">connected clients</text>
${brokerState.activeSessions.length > 0 ? `
<rect x="30" y="140" width="150" height="30" rx="4" fill="${ieeeColors.green}20"/>
<text x="105" y="160" text-anchor="middle" fill="${ieeeColors.green}" font-size="10">${brokerState.activeSessions[0]?.clientId || 'N/A'}</text>
` : ''}
<!-- Stored Sessions -->
<rect x="200" y="50" width="170" height="130" rx="6" fill="white" stroke="${ieeeColors.orange}" stroke-width="1.5"/>
<text x="285" y="70" text-anchor="middle" fill="${ieeeColors.orange}" font-size="12" font-weight="bold">Stored Sessions</text>
<text x="285" y="105" text-anchor="middle" fill="${ieeeColors.navy}" font-size="28">${brokerState.storedSessions.length}</text>
<text x="285" y="125" text-anchor="middle" fill="${ieeeColors.gray}" font-size="10">offline clients</text>
<text x="285" y="160" text-anchor="middle" fill="${ieeeColors.gray}" font-size="10">Awaiting reconnect</text>
<!-- Subscription Storage -->
<rect x="20" y="190" width="170" height="80" rx="6" fill="white" stroke="${ieeeColors.blue}" stroke-width="1.5"/>
<text x="105" y="210" text-anchor="middle" fill="${ieeeColors.blue}" font-size="12" font-weight="bold">Stored Subscriptions</text>
<text x="105" y="245" text-anchor="middle" fill="${ieeeColors.navy}" font-size="24">${brokerState.activeSessions.reduce((acc, s) => acc + s.subscriptions?.length || 0, 0)}</text>
<text x="105" y="262" text-anchor="middle" fill="${ieeeColors.gray}" font-size="10">topic filters</text>
<!-- Message Queue -->
<rect x="200" y="190" width="170" height="80" rx="6" fill="white" stroke="${ieeeColors.purple}" stroke-width="1.5"/>
<text x="285" y="210" text-anchor="middle" fill="${ieeeColors.purple}" font-size="12" font-weight="bold">Message Queue</text>
<text x="285" y="245" text-anchor="middle" fill="${ieeeColors.navy}" font-size="24">${brokerState.activeSessions.reduce((acc, s) => acc + s.pendingMessages?.length || 0, 0)}</text>
<text x="285" y="262" text-anchor="middle" fill="${ieeeColors.gray}" font-size="10">QoS 1/2 pending</text>
<!-- Memory Usage -->
<rect x="20" y="280" width="350" height="50" rx="6" fill="${ieeeColors.lightGray}" stroke="${ieeeColors.gray}"/>
<text x="30" y="300" fill="${ieeeColors.navy}" font-size="12">Memory Used:</text>
<rect x="120" y="290" width="200" height="16" rx="3" fill="white" stroke="${ieeeColors.gray}"/>
<rect x="120" y="290" width="${Math.min(200, (brokerState.totalMemoryUsed / 10000) * 200)}" height="16" rx="3" fill="${ieeeColors.teal}"/>
<text x="330" y="302" fill="${ieeeColors.navy}" font-size="11">${brokerState.activeSessions.reduce((acc, s) => acc + (s.memoryUsed || 0), 0)} bytes</text>
</g>
<!-- Legend -->
<g transform="translate(50, 410)">
<text x="0" y="15" fill="${ieeeColors.gray}" font-size="11">Legend:</text>
<circle cx="70" cy="12" r="6" fill="${ieeeColors.green}"/>
<text x="82" y="16" fill="${ieeeColors.gray}" font-size="10">Connected</text>
<circle cx="160" cy="12" r="6" fill="${ieeeColors.red}"/>
<text x="172" y="16" fill="${ieeeColors.gray}" font-size="10">Disconnected</text>
<rect x="250" y="6" width="12" height="12" fill="${ieeeColors.teal}20" stroke="${ieeeColors.teal}"/>
<text x="268" y="16" fill="${ieeeColors.gray}" font-size="10">Broker State</text>
<rect x="360" y="6" width="12" height="12" fill="${ieeeColors.navy}20" stroke="${ieeeColors.navy}"/>
<text x="378" y="16" fill="${ieeeColors.gray}" font-size="10">Client State</text>
</g>
`;
return container;
}
// ============================================================================
// INTERACTIVE TIMELINE
// ============================================================================
viewof timeline = {
const container = htl.html`<div style="
background: white;
border-radius: 12px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
">
<h3 style="margin: 0 0 16px 0; color: ${ieeeColors.navy};">
<span style="margin-right: 10px;">🕔</span>Event Timeline
</h3>
<div id="timelineContent" style="
max-height: 400px;
overflow-y: auto;
padding-right: 10px;
">
${timelineEvents.length === 0 ? `
<div style="text-align: center; padding: 60px 20px; color: ${ieeeColors.gray};">
<div style="font-size: 48px; margin-bottom: 16px;">📝</div>
<div style="font-size: 16px;">Select and run a scenario to see the event timeline</div>
</div>
` : `
<div style="position: relative; padding-left: 30px;">
<div style="position: absolute; left: 12px; top: 0; bottom: 0; width: 2px; background: ${ieeeColors.lightGray};"></div>
${timelineEvents.map((event, i) => {
const colors = {
connect: ieeeColors.teal,
disconnect: ieeeColors.red,
connack: ieeeColors.green,
publish: ieeeColors.blue,
subscribe: ieeeColors.purple,
suback: ieeeColors.purple,
broker: ieeeColors.orange,
timer: ieeeColors.gray,
info: ieeeColors.navy,
warning: ieeeColors.orange,
error: ieeeColors.red,
success: ieeeColors.green
};
const icons = {
connect: '🔗',
disconnect: '🚫',
connack: '✅',
publish: '📤',
subscribe: '📖',
suback: '☑',
broker: '🌏',
timer: '⏲',
info: 'ℹ',
warning: '⚠',
error: '❌',
success: '🎉'
};
const color = colors[event.type] || ieeeColors.gray;
const icon = icons[event.type] || '•';
return `
<div style="
position: relative;
margin-bottom: 16px;
padding: 12px 16px;
background: ${color}10;
border-radius: 8px;
border-left: 4px solid ${color};
animation: slideIn 0.3s ease-out ${i * 0.1}s both;
">
<div style="
position: absolute;
left: -38px;
top: 50%;
transform: translateY(-50%);
width: 24px;
height: 24px;
background: ${color};
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
color: white;
">${icon}</div>
<div style="display: flex; justify-content: space-between; align-items: flex-start;">
<div>
<div style="font-weight: 600; color: ${ieeeColors.navy}; margin-bottom: 4px;">${event.message}</div>
<div style="font-size: 12px; color: ${ieeeColors.gray};">${event.detail}</div>
</div>
<div style="
font-size: 10px;
color: ${ieeeColors.gray};
background: ${ieeeColors.lightGray};
padding: 2px 8px;
border-radius: 10px;
white-space: nowrap;
">${event.time}</div>
</div>
</div>
`;
}).join('')}
</div>
`}
</div>
<style>
@keyframes slideIn {
from { opacity: 0; transform: translateX(-20px); }
to { opacity: 1; transform: translateX(0); }
}
</style>
</div>`;
return container;
}
// ============================================================================
// STATE COMPARISON PANEL
// ============================================================================
viewof comparisonPanel = {
const container = htl.html`<div style="
background: ${ieeeColors.lightGray};
border-radius: 12px;
padding: 20px;
margin-bottom: 20px;
">
<h3 style="margin: 0 0 20px 0; color: ${ieeeColors.navy};">
<span style="margin-right: 10px;">⚖</span>Clean Session vs Persistent Session Comparison
</h3>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px;">
<!-- Clean Session -->
<div style="
background: white;
border-radius: 12px;
padding: 20px;
border-top: 4px solid ${ieeeColors.teal};
">
<h4 style="margin: 0 0 16px 0; color: ${ieeeColors.teal}; display: flex; align-items: center;">
<span style="margin-right: 10px; font-size: 24px;">🔄</span>
Clean Session (TRUE)
</h4>
<div style="margin-bottom: 16px;">
<div style="font-weight: 600; color: ${ieeeColors.navy}; margin-bottom: 8px;">On Connect:</div>
<ul style="margin: 0; padding-left: 20px; color: ${ieeeColors.gray}; font-size: 13px;">
<li>Broker discards any existing session</li>
<li>All previous subscriptions deleted</li>
<li>Pending QoS 1/2 messages discarded</li>
<li>Session Present = FALSE always</li>
</ul>
</div>
<div style="margin-bottom: 16px;">
<div style="font-weight: 600; color: ${ieeeColors.navy}; margin-bottom: 8px;">On Disconnect:</div>
<ul style="margin: 0; padding-left: 20px; color: ${ieeeColors.gray}; font-size: 13px;">
<li>Session immediately destroyed</li>
<li>No state preserved</li>
<li>Messages stop being stored</li>
</ul>
</div>
<div style="
background: ${ieeeColors.teal}15;
padding: 12px;
border-radius: 6px;
margin-top: 12px;
">
<div style="font-weight: 600; color: ${ieeeColors.teal}; font-size: 12px; margin-bottom: 4px;">BEST FOR:</div>
<ul style="margin: 0; padding-left: 16px; color: ${ieeeColors.navy}; font-size: 12px;">
<li>Stateless clients</li>
<li>Testing/debugging</li>
<li>Short-lived connections</li>
<li>Memory-constrained brokers</li>
</ul>
</div>
</div>
<!-- Persistent Session -->
<div style="
background: white;
border-radius: 12px;
padding: 20px;
border-top: 4px solid ${ieeeColors.orange};
">
<h4 style="margin: 0 0 16px 0; color: ${ieeeColors.orange}; display: flex; align-items: center;">
<span style="margin-right: 10px; font-size: 24px;">💾</span>
Persistent Session (FALSE)
</h4>
<div style="margin-bottom: 16px;">
<div style="font-weight: 600; color: ${ieeeColors.navy}; margin-bottom: 8px;">On Connect:</div>
<ul style="margin: 0; padding-left: 20px; color: ${ieeeColors.gray}; font-size: 13px;">
<li>Broker looks for existing session</li>
<li>Previous subscriptions restored</li>
<li>Queued messages delivered</li>
<li>Session Present indicates if found</li>
</ul>
</div>
<div style="margin-bottom: 16px;">
<div style="font-weight: 600; color: ${ieeeColors.navy}; margin-bottom: 8px;">On Disconnect:</div>
<ul style="margin: 0; padding-left: 20px; color: ${ieeeColors.gray}; font-size: 13px;">
<li>Session state preserved</li>
<li>Subscriptions maintained</li>
<li>QoS 1/2 messages queued</li>
<li>Expiry timer starts (MQTT 5.0)</li>
</ul>
</div>
<div style="
background: ${ieeeColors.orange}15;
padding: 12px;
border-radius: 6px;
margin-top: 12px;
">
<div style="font-weight: 600; color: ${ieeeColors.orange}; font-size: 12px; margin-bottom: 4px;">BEST FOR:</div>
<ul style="margin: 0; padding-left: 16px; color: ${ieeeColors.navy}; font-size: 12px;">
<li>Mobile/IoT devices</li>
<li>Unreliable networks</li>
<li>Critical message delivery</li>
<li>Reducing resubscription traffic</li>
</ul>
</div>
</div>
</div>
<!-- What's Lost vs Preserved -->
<div style="
margin-top: 20px;
background: white;
border-radius: 12px;
padding: 20px;
">
<h4 style="margin: 0 0 16px 0; color: ${ieeeColors.navy};">State Preservation Summary</h4>
<table style="width: 100%; border-collapse: collapse; font-size: 13px;">
<thead>
<tr style="background: ${ieeeColors.navy}; color: white;">
<th style="padding: 10px; text-align: left; border-radius: 6px 0 0 0;">State Element</th>
<th style="padding: 10px; text-align: center;">Clean Session</th>
<th style="padding: 10px; text-align: center; border-radius: 0 6px 0 0;">Persistent Session</th>
</tr>
</thead>
<tbody>
<tr style="background: ${ieeeColors.lightGray};">
<td style="padding: 10px;">Subscriptions</td>
<td style="padding: 10px; text-align: center; color: ${ieeeColors.red};">❌ Lost</td>
<td style="padding: 10px; text-align: center; color: ${ieeeColors.green};">✅ Preserved</td>
</tr>
<tr>
<td style="padding: 10px;">QoS 1 Messages (unacked)</td>
<td style="padding: 10px; text-align: center; color: ${ieeeColors.red};">❌ Discarded</td>
<td style="padding: 10px; text-align: center; color: ${ieeeColors.green};">✅ Queued</td>
</tr>
<tr style="background: ${ieeeColors.lightGray};">
<td style="padding: 10px;">QoS 2 Messages (in-flight)</td>
<td style="padding: 10px; text-align: center; color: ${ieeeColors.red};">❌ Discarded</td>
<td style="padding: 10px; text-align: center; color: ${ieeeColors.green};">✅ Resumed</td>
</tr>
<tr>
<td style="padding: 10px;">QoS 0 Messages</td>
<td style="padding: 10px; text-align: center; color: ${ieeeColors.orange};">— Never stored</td>
<td style="padding: 10px; text-align: center; color: ${ieeeColors.orange};">— Never stored</td>
</tr>
<tr style="background: ${ieeeColors.lightGray};">
<td style="padding: 10px;">Packet Identifiers</td>
<td style="padding: 10px; text-align: center; color: ${ieeeColors.red};">❌ Reset</td>
<td style="padding: 10px; text-align: center; color: ${ieeeColors.green};">✅ Preserved</td>
</tr>
<tr>
<td style="padding: 10px;">Broker Memory Usage</td>
<td style="padding: 10px; text-align: center; color: ${ieeeColors.green};">🟢 Minimal</td>
<td style="padding: 10px; text-align: center; color: ${ieeeColors.orange};">🟠 Higher</td>
</tr>
</tbody>
</table>
</div>
</div>`;
return container;
}
// ============================================================================
// BROKER STATE VIEW
// ============================================================================
viewof brokerView = {
const container = htl.html`<div style="
background: linear-gradient(135deg, ${ieeeColors.teal} 0%, #0d6854 100%);
border-radius: 12px;
padding: 24px;
margin-bottom: 20px;
color: white;
">
<h3 style="margin: 0 0 20px 0; color: white;">
<span style="margin-right: 10px;">🌏</span>Broker State Dashboard
</h3>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px;">
<!-- Active Sessions -->
<div style="
background: rgba(255,255,255,0.15);
border-radius: 10px;
padding: 16px;
">
<div style="font-size: 12px; opacity: 0.8; margin-bottom: 4px;">Active Sessions</div>
<div style="font-size: 32px; font-weight: bold;">${brokerState.activeSessions.length}</div>
<div style="font-size: 11px; opacity: 0.7;">of ${brokerState.maxSessions} max</div>
<div style="
margin-top: 10px;
height: 6px;
background: rgba(255,255,255,0.2);
border-radius: 3px;
">
<div style="
height: 100%;
width: ${(brokerState.activeSessions.length / brokerState.maxSessions) * 100}%;
background: ${ieeeColors.green};
border-radius: 3px;
"></div>
</div>
</div>
<!-- Stored Sessions -->
<div style="
background: rgba(255,255,255,0.15);
border-radius: 10px;
padding: 16px;
">
<div style="font-size: 12px; opacity: 0.8; margin-bottom: 4px;">Stored Sessions</div>
<div style="font-size: 32px; font-weight: bold;">${brokerState.storedSessions.length}</div>
<div style="font-size: 11px; opacity: 0.7;">offline clients</div>
</div>
<!-- Total Subscriptions -->
<div style="
background: rgba(255,255,255,0.15);
border-radius: 10px;
padding: 16px;
">
<div style="font-size: 12px; opacity: 0.8; margin-bottom: 4px;">Total Subscriptions</div>
<div style="font-size: 32px; font-weight: bold;">${brokerState.activeSessions.reduce((acc, s) => acc + (s.subscriptions?.length || 0), 0)}</div>
<div style="font-size: 11px; opacity: 0.7;">topic filters</div>
</div>
<!-- Memory Usage -->
<div style="
background: rgba(255,255,255,0.15);
border-radius: 10px;
padding: 16px;
">
<div style="font-size: 12px; opacity: 0.8; margin-bottom: 4px;">Session Memory</div>
<div style="font-size: 32px; font-weight: bold;">${(brokerState.activeSessions.reduce((acc, s) => acc + (s.memoryUsed || 0), 0) / 1024).toFixed(1)}</div>
<div style="font-size: 11px; opacity: 0.7;">KB used</div>
</div>
</div>
<!-- Session List -->
<div style="margin-top: 20px;">
<div style="font-size: 14px; font-weight: 600; margin-bottom: 12px;">Session Details</div>
${brokerState.activeSessions.length === 0 && brokerState.storedSessions.length === 0 ? `
<div style="
background: rgba(255,255,255,0.1);
border-radius: 8px;
padding: 20px;
text-align: center;
opacity: 0.7;
">
No active sessions. Run a scenario to see broker state.
</div>
` : `
<div style="display: flex; flex-direction: column; gap: 10px;">
${brokerState.activeSessions.map(s => `
<div style="
background: rgba(255,255,255,0.1);
border-radius: 8px;
padding: 12px;
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
gap: 12px;
">
<div style="
width: 10px;
height: 10px;
border-radius: 50%;
background: ${ieeeColors.green};
"></div>
<div>
<div style="font-weight: 600;">${s.clientId}</div>
<div style="font-size: 11px; opacity: 0.7;">
${s.subscriptions?.length || 0} subs | ${s.pendingMessages?.length || 0} pending | ${s.memoryUsed || 0} bytes
</div>
</div>
<div style="
background: ${s.cleanSession ? ieeeColors.teal : ieeeColors.orange};
padding: 4px 10px;
border-radius: 12px;
font-size: 10px;
font-weight: 600;
">${s.cleanSession ? 'CLEAN' : 'PERSISTENT'}</div>
</div>
`).join('')}
${brokerState.storedSessions.map(s => `
<div style="
background: rgba(255,255,255,0.05);
border-radius: 8px;
padding: 12px;
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
gap: 12px;
opacity: 0.7;
">
<div style="
width: 10px;
height: 10px;
border-radius: 50%;
background: ${ieeeColors.orange};
"></div>
<div>
<div style="font-weight: 600;">${s.clientId}</div>
<div style="font-size: 11px; opacity: 0.7;">
Stored | Awaiting reconnect
</div>
</div>
<div style="
background: ${ieeeColors.gray};
padding: 4px 10px;
border-radius: 12px;
font-size: 10px;
">OFFLINE</div>
</div>
`).join('')}
</div>
`}
</div>
</div>`;
return container;
}
// ============================================================================
// MQTT 5.0 FEATURES PANEL
// ============================================================================
viewof mqtt5Features = {
const container = htl.html`<div style="
background: white;
border-radius: 12px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
border-left: 4px solid ${ieeeColors.purple};
">
<h3 style="margin: 0 0 20px 0; color: ${ieeeColors.purple};">
<span style="margin-right: 10px;">🌟</span>MQTT 5.0 Session Features
</h3>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 20px;">
<!-- Session Expiry Interval -->
<div style="
background: ${ieeeColors.lightGray};
border-radius: 10px;
padding: 16px;
">
<div style="
display: flex;
align-items: center;
margin-bottom: 12px;
">
<span style="
font-size: 24px;
margin-right: 12px;
">⏲</span>
<div>
<div style="font-weight: 600; color: ${ieeeColors.navy};">Session Expiry Interval</div>
<div style="font-size: 11px; color: ${ieeeColors.gray};">Property 0x11</div>
</div>
</div>
<p style="margin: 0 0 12px 0; font-size: 13px; color: ${ieeeColors.gray}; line-height: 1.5;">
Specifies how long the broker should maintain session state after the client disconnects.
</p>
<div style="
background: white;
padding: 10px;
border-radius: 6px;
font-family: monospace;
font-size: 12px;
">
<div style="color: ${ieeeColors.purple};">Session Expiry Interval: 3600</div>
<div style="color: ${ieeeColors.gray}; margin-top: 4px;">// Keep session for 1 hour after disconnect</div>
</div>
<div style="
margin-top: 12px;
padding: 8px;
background: ${ieeeColors.purple}15;
border-radius: 4px;
font-size: 11px;
color: ${ieeeColors.navy};
">
<strong>Values:</strong> 0 = immediate expiry, 0xFFFFFFFF = never expire
</div>
</div>
<!-- Reason Codes -->
<div style="
background: ${ieeeColors.lightGray};
border-radius: 10px;
padding: 16px;
">
<div style="
display: flex;
align-items: center;
margin-bottom: 12px;
">
<span style="
font-size: 24px;
margin-right: 12px;
">📝</span>
<div>
<div style="font-weight: 600; color: ${ieeeColors.navy};">Reason Codes</div>
<div style="font-size: 11px; color: ${ieeeColors.gray};">Enhanced error reporting</div>
</div>
</div>
<p style="margin: 0 0 12px 0; font-size: 13px; color: ${ieeeColors.gray}; line-height: 1.5;">
Detailed reason codes explain why operations succeeded or failed.
</p>
<div style="display: flex; flex-wrap: wrap; gap: 6px;">
<span style="
background: ${ieeeColors.green}20;
color: ${ieeeColors.green};
padding: 4px 8px;
border-radius: 4px;
font-size: 11px;
">0x00 Success</span>
<span style="
background: ${ieeeColors.orange}20;
color: ${ieeeColors.orange};
padding: 4px 8px;
border-radius: 4px;
font-size: 11px;
">0x8E Session Taken Over</span>
<span style="
background: ${ieeeColors.red}20;
color: ${ieeeColors.red};
padding: 4px 8px;
border-radius: 4px;
font-size: 11px;
">0x97 Quota Exceeded</span>
</div>
</div>
<!-- Request/Response -->
<div style="
background: ${ieeeColors.lightGray};
border-radius: 10px;
padding: 16px;
">
<div style="
display: flex;
align-items: center;
margin-bottom: 12px;
">
<span style="
font-size: 24px;
margin-right: 12px;
">🔁</span>
<div>
<div style="font-weight: 600; color: ${ieeeColors.navy};">Request/Response</div>
<div style="font-size: 11px; color: ${ieeeColors.gray};">Correlation Data</div>
</div>
</div>
<p style="margin: 0 0 12px 0; font-size: 13px; color: ${ieeeColors.gray}; line-height: 1.5;">
Match responses to requests using Response Topic and Correlation Data properties.
</p>
<div style="
background: white;
padding: 10px;
border-radius: 6px;
font-family: monospace;
font-size: 11px;
">
<div style="color: ${ieeeColors.blue};">Response Topic: reply/client-123</div>
<div style="color: ${ieeeColors.purple};">Correlation Data: req-456</div>
</div>
</div>
<!-- User Properties -->
<div style="
background: ${ieeeColors.lightGray};
border-radius: 10px;
padding: 16px;
">
<div style="
display: flex;
align-items: center;
margin-bottom: 12px;
">
<span style="
font-size: 24px;
margin-right: 12px;
">🏷</span>
<div>
<div style="font-weight: 600; color: ${ieeeColors.navy};">User Properties</div>
<div style="font-size: 11px; color: ${ieeeColors.gray};">Custom key-value pairs</div>
</div>
</div>
<p style="margin: 0 0 12px 0; font-size: 13px; color: ${ieeeColors.gray}; line-height: 1.5;">
Attach metadata to any packet type for application-specific needs.
</p>
<div style="
background: white;
padding: 10px;
border-radius: 6px;
font-family: monospace;
font-size: 11px;
">
<div style="color: ${ieeeColors.teal};">device-type: "temperature-sensor"</div>
<div style="color: ${ieeeColors.teal};">firmware: "v2.1.0"</div>
<div style="color: ${ieeeColors.teal};">location: "building-a"</div>
</div>
</div>
</div>
<!-- MQTT 5.0 vs 3.1.1 -->
<div style="
margin-top: 20px;
padding: 16px;
background: ${ieeeColors.navy}10;
border-radius: 8px;
">
<div style="font-weight: 600; color: ${ieeeColors.navy}; margin-bottom: 12px;">MQTT 5.0 Session Improvements over 3.1.1</div>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 12px;">
<div style="display: flex; align-items: flex-start; gap: 8px;">
<span style="color: ${ieeeColors.green};">✓</span>
<span style="font-size: 13px; color: ${ieeeColors.gray};">Configurable session expiry (was infinite only)</span>
</div>
<div style="display: flex; align-items: flex-start; gap: 8px;">
<span style="color: ${ieeeColors.green};">✓</span>
<span style="font-size: 13px; color: ${ieeeColors.gray};">Clean Start flag (renamed from Clean Session)</span>
</div>
<div style="display: flex; align-items: flex-start; gap: 8px;">
<span style="color: ${ieeeColors.green};">✓</span>
<span style="font-size: 13px; color: ${ieeeColors.gray};">Detailed disconnect reason codes</span>
</div>
<div style="display: flex; align-items: flex-start; gap: 8px;">
<span style="color: ${ieeeColors.green};">✓</span>
<span style="font-size: 13px; color: ${ieeeColors.gray};">Server-side session expiry override</span>
</div>
</div>
</div>
</div>`;
return container;
}
// ============================================================================
// BEST PRACTICES PANEL
// ============================================================================
viewof bestPractices = {
const practices = [
{
title: "When to Use Clean Session",
icon: "🔄",
color: ieeeColors.teal,
items: [
"Stateless sensor devices that always re-subscribe",
"Testing and debugging scenarios",
"High-throughput publishers that don't need delivery guarantees",
"Memory-constrained broker environments",
"Temporary monitoring or diagnostic clients"
]
},
{
title: "Client ID Strategies",
icon: "🆔",
color: ieeeColors.blue,
items: [
"Use stable, predictable IDs for persistent sessions (e.g., device-{MAC})",
"Include device type in ID for easier management (temp-sensor-001)",
"Avoid random IDs for persistent sessions - they create orphaned sessions",
"Keep IDs under 23 bytes for maximum compatibility",
"Document your naming convention for operational clarity"
]
},
{
title: "Session Timeout Recommendations",
icon: "⏲",
color: ieeeColors.orange,
items: [
"Mobile apps: 1-24 hours (balance offline delivery vs memory)",
"IoT sensors: Match to expected maximum offline duration",
"Critical alerts: Consider infinite expiry (0xFFFFFFFF)",
"High-frequency sensors: Short expiry or clean session",
"Set reasonable limits to prevent broker memory exhaustion"
]
},
{
title: "Memory Management",
icon: "💾",
color: ieeeColors.purple,
items: [
"Monitor broker memory usage per session",
"Set max message queue size per client",
"Use message expiry for time-sensitive data",
"Implement session eviction policies",
"Consider QoS 0 for high-volume, low-criticality messages"
]
}
];
const container = htl.html`<div style="
background: white;
border-radius: 12px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
">
<h3 style="margin: 0 0 20px 0; color: ${ieeeColors.navy};">
<span style="margin-right: 10px;">💡</span>Best Practices & Recommendations
</h3>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 20px;">
${practices.map(p => `
<div style="
background: ${ieeeColors.lightGray};
border-radius: 10px;
padding: 16px;
border-top: 3px solid ${p.color};
">
<div style="
display: flex;
align-items: center;
margin-bottom: 12px;
">
<span style="font-size: 24px; margin-right: 10px;">${p.icon}</span>
<span style="font-weight: 600; color: ${ieeeColors.navy};">${p.title}</span>
</div>
<ul style="margin: 0; padding-left: 20px;">
${p.items.map(item => `
<li style="
color: ${ieeeColors.gray};
font-size: 12px;
margin-bottom: 6px;
line-height: 1.4;
">${item}</li>
`).join('')}
</ul>
</div>
`).join('')}
</div>
<!-- Common Mistakes -->
<div style="
margin-top: 20px;
padding: 20px;
background: ${ieeeColors.red}10;
border-radius: 10px;
border-left: 4px solid ${ieeeColors.red};
">
<div style="
font-weight: 600;
color: ${ieeeColors.red};
margin-bottom: 12px;
display: flex;
align-items: center;
">
<span style="margin-right: 10px; font-size: 20px;">⚠</span>
Common Mistakes to Avoid
</div>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 16px;">
<div style="display: flex; align-items: flex-start; gap: 8px;">
<span style="color: ${ieeeColors.red}; font-size: 16px;">❌</span>
<div>
<div style="font-weight: 500; color: ${ieeeColors.navy}; font-size: 13px;">Using random Client IDs with persistent sessions</div>
<div style="font-size: 11px; color: ${ieeeColors.gray};">Creates orphaned sessions consuming broker memory</div>
</div>
</div>
<div style="display: flex; align-items: flex-start; gap: 8px;">
<span style="color: ${ieeeColors.red}; font-size: 16px;">❌</span>
<div>
<div style="font-weight: 500; color: ${ieeeColors.navy}; font-size: 13px;">Ignoring Session Present flag</div>
<div style="font-size: 11px; color: ${ieeeColors.gray};">May lead to duplicate subscriptions or missed setup</div>
</div>
</div>
<div style="display: flex; align-items: flex-start; gap: 8px;">
<span style="color: ${ieeeColors.red}; font-size: 16px;">❌</span>
<div>
<div style="font-weight: 500; color: ${ieeeColors.navy}; font-size: 13px;">No session expiry limits</div>
<div style="font-size: 11px; color: ${ieeeColors.gray};">Abandoned devices accumulate sessions forever</div>
</div>
</div>
<div style="display: flex; align-items: flex-start; gap: 8px;">
<span style="color: ${ieeeColors.red}; font-size: 16px;">❌</span>
<div>
<div style="font-weight: 500; color: ${ieeeColors.navy}; font-size: 13px;">Using Clean Session for critical messages</div>
<div style="font-size: 11px; color: ${ieeeColors.gray};">Messages lost during brief disconnects</div>
</div>
</div>
</div>
</div>
</div>`;
return container;
}
// ============================================================================
// QUICK REFERENCE CARD
// ============================================================================
viewof quickRef = {
const container = htl.html`<div style="
background: linear-gradient(135deg, ${ieeeColors.navy} 0%, #1a252f 100%);
border-radius: 12px;
padding: 24px;
color: white;
">
<h3 style="margin: 0 0 20px 0; color: ${ieeeColors.teal};">
<span style="margin-right: 10px;">📋</span>Quick Reference Card
</h3>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px;">
<!-- Connect Flags -->
<div style="background: rgba(255,255,255,0.1); border-radius: 8px; padding: 16px;">
<div style="font-weight: 600; margin-bottom: 12px; color: ${ieeeColors.lightGray};">CONNECT Packet Flags</div>
<table style="width: 100%; font-size: 12px;">
<tr style="border-bottom: 1px solid rgba(255,255,255,0.2);">
<td style="padding: 6px 0; color: ${ieeeColors.teal};">Clean Session</td>
<td style="padding: 6px 0;">Bit 1</td>
<td style="padding: 6px 0; color: ${ieeeColors.gray};">MQTT 3.1.1</td>
</tr>
<tr style="border-bottom: 1px solid rgba(255,255,255,0.2);">
<td style="padding: 6px 0; color: ${ieeeColors.teal};">Clean Start</td>
<td style="padding: 6px 0;">Bit 1</td>
<td style="padding: 6px 0; color: ${ieeeColors.gray};">MQTT 5.0</td>
</tr>
<tr style="border-bottom: 1px solid rgba(255,255,255,0.2);">
<td style="padding: 6px 0; color: ${ieeeColors.orange};">Will Flag</td>
<td style="padding: 6px 0;">Bit 2</td>
<td style="padding: 6px 0; color: ${ieeeColors.gray};">LWT enabled</td>
</tr>
<tr style="border-bottom: 1px solid rgba(255,255,255,0.2);">
<td style="padding: 6px 0; color: ${ieeeColors.orange};">Will QoS</td>
<td style="padding: 6px 0;">Bits 3-4</td>
<td style="padding: 6px 0; color: ${ieeeColors.gray};">0, 1, or 2</td>
</tr>
<tr>
<td style="padding: 6px 0; color: ${ieeeColors.orange};">Will Retain</td>
<td style="padding: 6px 0;">Bit 5</td>
<td style="padding: 6px 0; color: ${ieeeColors.gray};">Retain will msg</td>
</tr>
</table>
</div>
<!-- CONNACK Codes -->
<div style="background: rgba(255,255,255,0.1); border-radius: 8px; padding: 16px;">
<div style="font-weight: 600; margin-bottom: 12px; color: ${ieeeColors.lightGray};">CONNACK Session Present</div>
<div style="margin-bottom: 16px;">
<div style="display: flex; justify-content: space-between; padding: 8px; background: rgba(255,255,255,0.1); border-radius: 4px; margin-bottom: 8px;">
<span style="color: ${ieeeColors.teal};">0 (FALSE)</span>
<span style="font-size: 11px; color: ${ieeeColors.gray};">No session found/kept</span>
</div>
<div style="display: flex; justify-content: space-between; padding: 8px; background: rgba(255,255,255,0.1); border-radius: 4px;">
<span style="color: ${ieeeColors.green};">1 (TRUE)</span>
<span style="font-size: 11px; color: ${ieeeColors.gray};">Existing session resumed</span>
</div>
</div>
<div style="font-size: 11px; color: ${ieeeColors.gray}; border-top: 1px solid rgba(255,255,255,0.2); padding-top: 12px;">
<strong>Note:</strong> If Clean Session=TRUE in CONNECT, Session Present will always be FALSE in CONNACK.
</div>
</div>
<!-- Session Expiry Values -->
<div style="background: rgba(255,255,255,0.1); border-radius: 8px; padding: 16px;">
<div style="font-weight: 600; margin-bottom: 12px; color: ${ieeeColors.lightGray};">Session Expiry Values (MQTT 5.0)</div>
<div style="display: flex; flex-direction: column; gap: 8px;">
<div style="display: flex; justify-content: space-between; padding: 6px 10px; background: ${ieeeColors.red}30; border-radius: 4px;">
<span style="font-family: monospace;">0</span>
<span style="font-size: 11px;">Expire immediately on disconnect</span>
</div>
<div style="display: flex; justify-content: space-between; padding: 6px 10px; background: ${ieeeColors.orange}30; border-radius: 4px;">
<span style="font-family: monospace;">1-86400</span>
<span style="font-size: 11px;">1 second to 24 hours</span>
</div>
<div style="display: flex; justify-content: space-between; padding: 6px 10px; background: ${ieeeColors.green}30; border-radius: 4px;">
<span style="font-family: monospace;">0xFFFFFFFF</span>
<span style="font-size: 11px;">Never expire (infinite)</span>
</div>
</div>
</div>
<!-- State Matrix -->
<div style="background: rgba(255,255,255,0.1); border-radius: 8px; padding: 16px;">
<div style="font-weight: 600; margin-bottom: 12px; color: ${ieeeColors.lightGray};">Session State Matrix</div>
<table style="width: 100%; font-size: 11px; text-align: center;">
<tr style="background: rgba(255,255,255,0.1);">
<th style="padding: 6px;">Clean</th>
<th style="padding: 6px;">Existing</th>
<th style="padding: 6px;">Result</th>
</tr>
<tr>
<td style="padding: 6px; color: ${ieeeColors.green};">TRUE</td>
<td style="padding: 6px;">No</td>
<td style="padding: 6px;">New session, SP=0</td>
</tr>
<tr style="background: rgba(255,255,255,0.05);">
<td style="padding: 6px; color: ${ieeeColors.green};">TRUE</td>
<td style="padding: 6px;">Yes</td>
<td style="padding: 6px;">Discard old, SP=0</td>
</tr>
<tr>
<td style="padding: 6px; color: ${ieeeColors.red};">FALSE</td>
<td style="padding: 6px;">No</td>
<td style="padding: 6px;">New session, SP=0</td>
</tr>
<tr style="background: rgba(255,255,255,0.05);">
<td style="padding: 6px; color: ${ieeeColors.red};">FALSE</td>
<td style="padding: 6px;">Yes</td>
<td style="padding: 6px; color: ${ieeeColors.teal};">Resume, SP=1</td>
</tr>
</table>
</div>
</div>
</div>`;
return container;
}