Help for ocpp.csms.view_charger_status

Sample CLI

gway ocpp.csms view-charger-status

References

Full Code

def view_charger_status(*, action=None, charger_id=None, **_):
    """
    Card-based OCPP dashboard: summary of all charger connections.
    """
    if request.method == "POST":
        action = request.forms.get("action")
        charger_id = request.forms.get("charger_id")
        if action and charger_id:
            try:
                dispatch_action(charger_id, action)
            except Exception as e:
                gw.error(f"Failed to dispatch action {action} to {charger_id}: {e}")
            return redirect(request.fullpath or "/ocpp/charger-status")

    all_chargers = set(_active_cons) | set(_transactions)
    html = ["<h1>OCPP Status Dashboard</h1>"]

    # --- Show abnormal statuses if present ---
    if _abnormal_status:
        html.append(
            '<div style="color:#fff;background:#b22;padding:12px;font-weight:bold;margin-bottom:18px">'
            "⚠️ Abnormal Charger Status Detected:<ul style='margin:0'>"
        )
        for cid, err in sorted(_abnormal_status.items()):
            status = err.get("status", "")
            error_code = err.get("errorCode", "")
            info = err.get("info", "")
            ts = err.get("timestamp", "")
            msg = f"<b>{cid}</b>: {status}/{error_code}"
            if info: msg += f" ({info})"
            if ts: msg += f" <span style='font-size:0.9em;color:#eee'>@{ts}</span>"
            html.append(f"<li>{msg}</li>")
        html.append("</ul></div>")

    if not all_chargers:
        html.append('<p><em>No chargers connected or transactions seen yet.</em></p>')
    else:
        html.append('<div class="ocpp-dashboard">')
        for cid in sorted(all_chargers):
            ws_live = cid in _active_cons
            tx      = _transactions.get(cid)
            connected   = '🟢' if ws_live else '🔴'
            tx_id       = tx.get("transactionId") if tx else '-'
            meter_start = tx.get("meterStart")       if tx else '-'
            latest      = (
                tx.get("meterStop")
                if tx and tx.get("meterStop") is not None
                else (tx["MeterValues"][-1].get("meter") if tx and tx.get("MeterValues") else 'None')
            )
            power  = power_consumed(tx)
            status = "Closed" if tx and tx.get("syncStop") else "Open" if tx else '-'
            # Heartbeat
            raw_hb = _latest_heartbeat.get(cid)
            if raw_hb:
                try:
                    from datetime import datetime, timezone
                    dt = datetime.fromisoformat(raw_hb.replace("Z", "+00:00")).astimezone()
                    latest_hb = dt.strftime("%Y-%m-%d %H:%M:%S")
                except Exception:
                    latest_hb = raw_hb
            else:
                latest_hb = "-"

            html.append('<div class="charger-card">')
            html.append(f'''
                <div class="charger-header">
                    <span class="charger-id">{cid}</span>
                    <span class="charger-status">{connected}</span>
                </div>
                <div class="charger-details-row">
                    <label>Txn ID:</label> <span>{tx_id}</span>
                    <label>Start:</label> <span>{meter_start}</span>
                    <label>Latest:</label> <span>{latest}</span>
                </div>
                <div class="charger-details-row">
                    <label>kWh:</label> <span>{power}</span>
                    <label>Status:</label> <span>{status}</span>
                    <label>Last HB:</label> <span>{latest_hb}</span>
                </div>
                <form method="post" action="" class="charger-action-row">
                    <input type="hidden" name="charger_id" value="{cid}">
                    <select name="action" id="action-{cid}" aria-label="Action">
                        <option value="remote_stop">Stop</option>
                        <option value="reset_soft">Soft Reset</option>
                        <option value="reset_hard">Hard Reset</option>
                        <option value="disconnect">Disconnect</option>
                    </select>
                    <button type="submit" name="do" value="send">Send</button>
                    <button type="submit" name="do" value="details" class="details-btn" data-target="details-{cid}">Details</button>
                    <button type="submit" name="do" value="graph" class="graph-btn">Graph</button>
                </form>
                <div id="details-{cid}" class="charger-details-panel hidden">
                    <pre>{json.dumps(tx or {}, indent=2)}</pre>
                </div>
            ''')
            html.append('</div>')
        html.append('</div>')  # end .ocpp-dashboard

    # WebSocket URL bar
    ws_url = gw.build_ws_url()
    html.append(f"""
    <div class="ocpp-wsbar">
      <input type="text" id="ocpp-ws-url" value="{ws_url}" readonly
        style="flex:1;font-family:monospace;font-size:1em;
               padding:10px 6px;background:#222;color:#fff;
               border:1px solid #333;border-radius:5px;min-width:160px;max-width:530px;"/>
      <button id="copy-ws-url-btn"
        style="padding:6px 16px;font-size:1em;border-radius:5px;
               border:1px solid #444;background:#444;color:#fff;cursor:pointer">
        Copy
      </button>
    </div>
    """)
    return "".join(html)