Skip to content

NT client goes into infinite ping/pong loop #8533

@mcm001

Description

@mcm001

Describe the bug

While testing PhotonVision HEAD, running a NT server, against Glass 2026 beta 1, I saw a NT connection formed but no data flowing.

To Reproduce
Some of the time (i was unable to reproduce this):

  1. start photonvision from HEAD as a NT client
  2. change it to NT server
  3. Open Glass, connect as NT client
  4. observe topics announced, but no data

When in this bad state, I can swap Photon from server -> client -> server and data flows nominally (Glass updates values, we see value updates in Wireshark).

In this Wireshark capture, we see pings exchanged every 100ms, but the only non-keepalive mesasges were:

(.venv) PS C:\Users\matth\Documents\GitHub\photonvision> & C:/Users/matth/Documents/GitHub/photonvision/.venv/Scripts/python.exe c:/Users/matth/Documents/GitHub/photonvision/analyze-nt4.py
[2026-01-01T18:50:50.072823000-0800] [62149 -> 5810] Binary Payload
{'topic_id': -1, 'timestamp': 0, 'value': 2}
[2026-01-01T18:50:50.072854000-0800] [5810 -> 62149] Binary Payload
{'topic_id': -1, 'timestamp': 832980544, 'value': 2}
[2026-01-01T18:50:50.179727000-0800] [62149 -> 5810] Text Payload: [{"method":"subscribe","params":{"options":{},"topics":["/SmartDashboard/PhotonAlerts/.type"],"subuid":1}},{"method":"subscribe","params":{"options":{"prefix":true},"topics":["","$"],"subuid":-1}},{"method":"subscribe","params":{"options":{"topicsonly":true,"prefix":true},"topics":[""],"subuid":-2}}]
[2026-01-01T18:50:50.188128000-0800] [5810 -> 62149] Text Payload: [{"method":"announce","params":{"id":6,"name":"/SmartDashboard/PhotonAlerts/.type","properties":{"SmartDashboard":"Alerts"},"type":"string"}}]
[2026-01-01T18:50:50.188224000-0800] [5810 -> 62149] Text Payload: [{"method":"announce","params":{"id":109,"name":"$pub$/.schema/photonstruct:MultiTargetPNPResult:541096947e9f3ca2d3f425ff7b04aa7b","properties":{"retained":true},"type":"msgpack"}},{"method":"announce","params":{"id":110,"name":"$sub$/.schema/photonstruct:MultiTargetPNPResult:541096947e9f3ca2d3f425ff7b04aa7b","properties":{"retained":true},"type":"msgpack"}},{"method":"announce","params":{"id":111,"name":"/.schema/photonstruct:PnpResult:ae4d655c0a3104d88df4f5db144c1e86","properties":{"retained":true},"type":"photonstructschema"}},{"method":"announce","params":{"id":112,"name":"$pub$/.schema/photonstruct:PnpResult:ae4d655c0a3104d88df4f5db144c1e86","properties":{"retained":true},"type":"msgpack"}},{"method":"announce","params":{"id":113,"name":"$sub$/.schema/photonstruct:PnpResult:ae4d655c0a3104d88df4f5db144c1e86","properties":{"retained":true},"type":"msgpack"}},{"method":"announce","params":{"id":114,"name":"/.schema/struct:Transform3d","properties":{"retained":true},"type":"structschema"}},{"method":"announce","params":{"id":115,"name":"$pub$/.schema/struct:Transform3d","properties":{"retained":true},"type":"msgpack"}},{"method":"announce","params":{"id":116,"name":"$sub$/.schema/struct:Transform3d","properties":{"retained":true},"type":"msgpack"}},{"method":"announce","params":{"id":117,"name":"/.schema/struct:Translation3d","properties":{"retained":true},"type":"structschema"}}]
[2026-01-01T18:50:50.188276000-0800] [5810 -> 62149] Text Payload: [{"method":"announce","params":{"id":215,"name":"$sub$/CameraPublisher/DESKTOP-PNNHDMI_Port_1183_Input_MJPEG_Server/modes","properties":{"retained":true},"type":"msgpack"}},{"method":"announce","params":{"id":216,"name":"/CameraPublisher/DESKTOP-PNNHDMI_Port_1184_Output_MJPEG_Server/source","properties":{},"type":"string"}},{"method":"announce","params":{"id":217,"name":"$pub$/CameraPublisher/DESKTOP-PNNHDMI_Port_1184_Output_MJPEG_Server/source","properties":{"retained":true},"type":"msgpack"}},{"method":"announce","params":{"id":218,"name":"$sub$/CameraPublisher/DESKTOP-PNNHDMI_Port_1184_Output_MJPEG_Server/source","properties":{"retained":true},"type":"msgpack"}},{"method":"announce","params":{"id":219,"name":"/photonvision/Microsoft_LifeCam_HD-3000 (Duplicate)/rawBytes","properties":{"message_uuid":"4b2ff16a964b5e2bf04be0c1454d91c4"},"type":"photonstruct:PhotonPipelineResult:4b2ff16a964b5e2bf04be0c1454d91c4"}},{"method":"announce","params":{"id":220,"name":"$pub$/photonvision/Microsoft_LifeCam_HD-3000 (Duplicate)/rawBytes","properties":{"retained":true},"type":"msgpack"}},{"method":"announce","params":{"id":221,"name":"$sub$/photonvision/Microsoft_LifeCam_HD-3000 (Duplicate)/rawBytes","properties":{"retained":true},"type":"msgpack"}},{"method":"announce","params":{"id":222,"name":"/CameraPublisher/DESKTOP-PNNHDMI_Port_1184_Output_MJPEG_Server/description","properties":{},"type":"string"}}]
[2026-01-01T18:50:50.188307000-0800] [5810 -> 62149] Binary Payload
{'topic_id': 1, 'timestamp': 326095327, 'value': 5}
[2026-01-01T18:50:50.188339000-0800] [5810 -> 62149] Binary Payload
{'topic_id': 102, 'timestamp': 327644941, 'value': 5}
{'topic_id': 103, 'timestamp': 326094752, 'value': 5}
{'topic_id': 104, 'timestamp': 833087934, 'value': 5}
{'topic_id': 105, 'timestamp': 0, 'value': 5}
{'topic_id': 106, 'timestamp': 326094756, 'value': 5}
{'topic_id': 107, 'timestamp': 833087935, 'value': 5}
[2026-01-01T18:50:50.288077000-0800] [62149 -> 5810] Text Payload: [{"method":"subscribe","params":{"options":{},"topics":["/SmartDashboard/PhotonAlerts/.type"],"subuid":2}},{"method":"unsubscribe","params":{"subuid":2}}]
[2026-01-01T18:50:50.316590000-0800] [5810 -> 62149] Binary Payload
{'topic_id': 8, 'timestamp': 833195887, 'value': 5}
{'topic_id': 305, 'timestamp': 833195893, 'value': 5}
[2026-01-01T18:51:25.267756000-0800] [62149 -> 5810] Text Payload: [{"method":"publish","params":{"name":"/photonvision/Microsoft_LifeCam_HD-3000/hasTarget","properties":{},"pubuid":0,"type":"boolean"}}]
[2026-01-01T18:51:25.292865000-0800] [5810 -> 62149] Binary Payload
{'topic_id': 160, 'timestamp': 868175535, 'value': 5}
{'topic_id': 304, 'timestamp': 868175538, 'value': 5}
[2026-01-01T18:51:26.894370000-0800] [62149 -> 5810] Binary Payload
{'topic_id': 0, 'timestamp': 869756045, 'value': 0}
(.venv) PS C:\Users\matth\Documents\GitHub\photonvision> 

Decoder code:

import pyshark
import asyncio

loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)

def decode_nt4_message(payload):
    """
    Decodes an NT4 message payload based on the NT4 specification.

    Args:
        payload (bytes): The binary payload to decode.

    Returns:
        None
    """
    import msgpack

    try:
        # Use an unpacker to handle multiple MessagePack objects in the payload
        unpacker = msgpack.Unpacker()
        unpacker.feed(payload)

        for decoded_message in unpacker:
            # Example structure based on NT4 spec (adjust as needed):
            # Assuming the payload contains a topic ID, timestamp, and value
            if isinstance(decoded_message, list) and len(decoded_message) >= 3:
                topic_id = decoded_message[0]  # Topic ID
                timestamp = decoded_message[1]  # Timestamp in microseconds
                value = decoded_message[2]  # Value (could be int, float, string, etc.)

                data = {
                    "topic_id": topic_id,
                    "timestamp": timestamp,
                    "value": value
                }
                print(data)
            else:
                raise ValueError("Unexpected message format")

    except Exception as e:
        print(f"Failed to decode NT4 message: {e}")
        return None

def analyze_websocket_pcap(file_name):
    cap = pyshark.FileCapture(file_name, display_filter="websocket")

    try:
        for packet in cap:
            if "websocket" in packet:
                opcode = int(packet.websocket.opcode)
                payload = packet.websocket.payload
                # print(
                #     f"[{packet.frame_info.time}] WebSocket Packet: Opcode {opcode} - Payload length: {packet.websocket.payload_length}"
                # )

                try:
                    if opcode == 0x1:  # Text frame
                        decoded_payload = bytes.fromhex(payload.replace(':', '')).decode('utf-8')
                        print(f"[{packet.frame_info.time}] [{packet.tcp.srcport} -> {packet.tcp.dstport}] Text Payload: {decoded_payload}")
                    elif opcode == 0x2:  # Binary frame
                        decoded_payload = bytes.fromhex(payload.replace(':', ''))
                        print(f"[{packet.frame_info.time}] [{packet.tcp.srcport} -> {packet.tcp.dstport}] Binary Payload")
                        decode_nt4_message(decoded_payload)
                    elif opcode == 0x9:  # Ping frame
                        # print("  Ping Frame")
                        pass
                    elif opcode == 0xA:  # Pong frame
                        # print("  Pong Frame")
                        pass
                    else:
                        print(f"  Unknown Opcode: {opcode}")
                except Exception as e:
                    print(e)
                    print(f"  Payload (raw hex): {payload}")

    except Exception as e:
        print(f"An error occurred: {e}")
    finally:
        cap.close()


if __name__ == "__main__":
    analyze_websocket_pcap("networktables_photonserver_glassclient.pcapng")

Expected behavior

Screenshots
If applicable, add screenshots to help explain your problem.

Desktop (please complete the following information):

  • OS: Windows 11

Additional context

Pcap file:

networktables_photonserver_glassclient.zip

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions