From d310ba2e97e38ffd442e3bed6baecfdc49f89020 Mon Sep 17 00:00:00 2001 From: Nathaniel Hargrave Date: Sat, 10 Oct 2026 01:57:38 -0400 Subject: [PATCH 1/2] First pass interface manager ui --- .../panels/InterfaceManagerPanel.tsx | 267 ++++++++++++++++++ src/components/panels/MosaicDashboard.tsx | 2 + 2 files changed, 269 insertions(+) create mode 100644 src/components/panels/InterfaceManagerPanel.tsx diff --git a/src/components/panels/InterfaceManagerPanel.tsx b/src/components/panels/InterfaceManagerPanel.tsx new file mode 100644 index 0000000..7c3c9c5 --- /dev/null +++ b/src/components/panels/InterfaceManagerPanel.tsx @@ -0,0 +1,267 @@ +'use client'; + +import React, { useEffect, useState } from 'react'; +import ROSLIB from 'roslib'; +import { useROS } from '@/ros/ROSContext'; + +enum CANState { + ERROR_ACTIVE, + ERROR_WARNING, + ERROR_PASSIVE, + BUS_OFF, + STOPPED, + SLEEPING +} + +type CANStatus = { + up: boolean; + state: CANState; + txerr: number; + rxerr: number; + tx_packets: number; + tx_packet_errs: number; + tx_packet_drops: number; + rx_packets: number; + rx_packet_errs: number; + rx_packet_drops: number; + restarts: number; + bus_error: number; + arbitration_lost: number; + error_warning: number; + error_passive: number; + bus_off: number; +}; + +type USBDevice = { + bus: number; + dev: number; + vendor: number; + product: number; + name: string; +}; + +type USBList = { + devices: USBDevice[]; +}; + +const InterfaceManagerPanel: React.FC = () => { + const { ros } = useROS(); + const [can0, setCan0] = useState(null); + const [can1, setCan1] = useState(null); + const [usbList, setUsbList] = useState(null); + + useEffect(() => { + if (!ros) return; + + const can0 = new ROSLIB.Topic({ + ros, + name: '/can0_status', + messageType: 'interfaces/msg/CANStatus', + }); + + const can1 = new ROSLIB.Topic({ + ros, + name: '/can1_status', + messageType: 'interfaces/msg/CANStatus', + }); + + const usbList = new ROSLIB.Topic({ + ros, + name: '/usb_list', + messageType: 'interfaces/msg/USBList', + }); + + const handleCan0 = (msg: any) => { + setCan0(msg); + } + + const handleCan1 = (msg: any) => { + setCan1(msg); + } + + const handleUsbList = (msg: any) => { + setUsbList(msg); + } + + can0.subscribe(handleCan0); + can1.subscribe(handleCan1); + usbList.subscribe(handleUsbList); + + return () => { + can0.unsubscribe(handleCan0); + can1.unsubscribe(handleCan1); + usbList.unsubscribe(handleUsbList); + }; + }, [ros]); + + const stateStyle = (can: CANStatus | null) => { + let background = '#6c757d'; + let color = '#f1f1f1'; + if (can && !can.up) { + background = '#ef4444'; + }else if (can) { + switch (can.state) { + case CANState.ERROR_ACTIVE: + background = '#22c55e'; + color = '#0a0a0a'; + break; + + case CANState.ERROR_WARNING: + case CANState.ERROR_PASSIVE: + background = '#ffc107'; + color = '#0a0a0a'; + break; + + case CANState.BUS_OFF: + background = '#ef4444'; + break; + + default: + break; + } + } + return { + background: background, + color: color, + borderRadius: '4px', + textAlign: 'center' as const, + margin: '4px', + display: 'block', + }; + }; + + return ( +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CANCAN0CAN1
Up? + + + +
State{ can0 ? CANState[can0.state] : '-'}{ can1 ? CANState[can1.state] : '-'}
Bit errors (TX/RX){ can0 ? `${can0.txerr} / ${can0.rxerr}` : '-'}{ can1 ? `${can1.txerr} / ${can1.rxerr}` : '-'}
Packets (TX/RX){ can0 ? `${can0.tx_packets} / ${can0.rx_packets}` : '-'}{ can1 ? `${can1.tx_packets} / ${can1.rx_packets}` : '-'}
Packet Errors (TX/RX){ can0 ? `${can0.tx_packet_errs} / ${can0.rx_packet_errs}` : '-'}{ can1 ? `${can1.tx_packet_errs} / ${can1.rx_packet_errs}` : '-'}
Packet Drops (TX/RX){ can0 ? `${can0.tx_packet_drops} / ${can0.rx_packet_drops}` : '-'}{ can1 ? `${can1.tx_packet_drops} / ${can1.rx_packet_drops}` : '-'}
Restarts / Bus Err / Arb. Lost{ can0 ? `${can0.restarts} / ${can0.bus_error} / ${can0.arbitration_lost}` : '-'}{ can1 ? `${can1.restarts} / ${can1.bus_error} / ${can1.arbitration_lost}` : '-'}
Error Warning / Error Passive / Bus Off{ can0 ? `${can0.error_warning} / ${can0.error_passive} / ${can0.bus_off}` : '-'}{ can1 ? `${can1.error_warning} / ${can1.error_passive} / ${can1.bus_off}` : '-'}
+

USB Devices

+ + + + + + + + + + + {usbList ? usbList.devices.map((dev) => ( + + + + + + + )) : ( + + )} + +
Bus #Device #IDName
{ dev.bus }{ dev.dev }{ `${dev.vendor.toString(16).padStart(4, "0")}:${dev.product.toString(16).padStart(4, "0")}` }{ dev.name }
-
+ +
+ ) +}; + +export default InterfaceManagerPanel; diff --git a/src/components/panels/MosaicDashboard.tsx b/src/components/panels/MosaicDashboard.tsx index 0bf130c..01ccc89 100644 --- a/src/components/panels/MosaicDashboard.tsx +++ b/src/components/panels/MosaicDashboard.tsx @@ -30,6 +30,7 @@ import MorseTransmissionPanel from './MorseTransmissionPanel'; import RtpStats from './RtpStats'; import TopicEchoPanel from './TopicEchoPanel'; import DriveThrottlePanel from './DriveThrottlePanel'; +import InterfaceManagerPanel from './InterfaceManagerPanel'; import { ROVER_IP } from '@/constants'; @@ -70,6 +71,7 @@ const TILE_REGISTRY = { headlightControlPanel: { title: 'Headlights', render: () => }, morseTransmissionPanel: { title: 'Morse Transmission', render: () => }, echoPanel: { title: 'Topic Echo', render: () => }, + interfaceManagerPanel: { title: 'Interface Manager', render: () => }, } satisfies Record; type TileType = keyof typeof TILE_REGISTRY; From c112e9aa47f58f91bde6e847eacee1769c23e077 Mon Sep 17 00:00:00 2001 From: "Software Lead (Base Station)" Date: Thu, 22 Oct 2026 19:51:00 -0400 Subject: [PATCH 2/2] Add to debug preset --- src/app/page.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/page.tsx b/src/app/page.tsx index 10154e0..a5f73ff 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -86,11 +86,11 @@ const layouts = [ first: 'webRTCClient:4', second: { direction: 'column', - first: 'nodeStatusPanel:5', - second: 'rosMonitor:6', + first: 'interfaceManagerPanel:5', + second: 'nodeStatusPanel:6', splitPercentage: 70 }, - splitPercentage: 70, + splitPercentage: 60, }, splitPercentage: 30, },