如何解释来自 F1 2020 游戏 UDP 数据包的 python 字节字符串?

How do I interpret a python byte string coming from F1 2020 game UDP packet?

标题可能与我正在尝试解决的问题大相径庭。

我正在尝试以我理解的方式解释我从赛车游戏中收到的数据包,但老实说,我真的不知道我在看什么,也不知道要搜索什么来理解它。 我在这里收到的数据包的信息: https://forums.codemasters.com/topic/54423-f1%C2%AE-2020-udp-specification/?tab=comments#comment-532560

我正在使用 python 打印数据包,这是输出的一个片段,我不明白如何解释。

received message: b'\xe4\x07\x01\x03\x01\x07O\x90.\xea\xc2!7\x16\xa5\xbb\x02C\xda\n\x00\x00\x00\xff\x01\x00\x03:\x00\x00\x00 A\x00\x00\xdcB\xb5+\xc1@\xc82\xcc\x10\t\x00\xd9\x00\x00\x00\x00\x00\x12\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$tJ\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01

我对编码还很陌生,不确定我的下一步是什么,所以朝正确的方向推动会有助于加载,谢谢。

这是 python 代码:

import socket
UDP_IP = "127.0.0.1"
UDP_PORT = 20777
sock = socket.socket(socket.AF_INET, # Internet
              socket.SOCK_DGRAM) # UDP
sock.bind((UDP_IP, UDP_PORT))
while True:
    data, addr = sock.recvfrom(4096)
    print ("received message:", data)

您 link 访问的网站正在描述数据格式。所有数据都表示为一系列 1 和 0。一个字节是一系列 8 个 1 和 0。然而,仅仅因为您有一系列字节并不意味着您知道如何解释它们。他们代表一个角色吗?一个整数?该整数可以为负数吗?所有这些都是由首先制作数据的人定义的。

您在顶部看到的类型描述告诉您如何实际解释这一系列 1 和 0。当您看到“unit8”时,它是一个“8 位(1 字节)长的无符号整数”。换句话说,0 到 255 之间的正数。另一方面,“int8”是“8 位整数”,或者可以是正数或负数的数字(因此范围是 -128 到 127)。相同的基本思想适用于 *16 和 *64 变体,只是 16 位或 64 位。 float 表示一个浮点数(带小数部分的数字,如 1.2345),一般为 4 个字节长。此外,您需要知道解释单词中字节的顺序(left-to-right 或 right-to-left)。这称为字节序,每个计算机体系结构都有一个本地字节序(big-endian 或 little-endian)。

鉴于所有这些,您可以解释 PacketHeader。最简单的方法可能是使用 Python 中的 struct 包。详细信息可以在这里找到: https://docs.python.org/3/library/struct.html

作为概念证明,以下将解释前 24 个字节:

import struct
data = b'\xe4\x07\x01\x03\x01\x07O\x90.\xea\xc2!7\x16\xa5\xbb\x02C\xda\n\x00\x00\x00\xff\x01\x00\x03:\x00\x00\x00 A\x00\x00\xdcB\xb5+\xc1@\xc82\xcc\x10\t\x00\xd9\x00\x00\x00\x00\x00\x12\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$tJ\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01'

#Note that I am only taking the first 24 bytes. You must pass data that is
#the appropriate length to the unpack function. We don't know what everything 
#else is until after we parse out the header
header = struct.unpack('<HBBBBQfIBB', data[:24])
print(header)

您基本上想要读取前 24 个字节以获得消息的 header。从那里,您需要使用 m_packetId 字段来确定消息的其余部分是什么。例如,这个特定数据包的 packetId 为 7,这是一个“Car Status”数据包。因此,您将在该页面的下方进一步查看 struct CarStatus 的打包格式,以弄清楚如何解释消息的其余部分。数据到达时冲洗并重复。

更新:在格式字符串中,< 告诉您将字节解释为 little-endian 没有对齐(基于文档说它是 little-endian 和包装)。我建议通读上面文档中关于格式字符的整个部分,以充分理解关于对齐的所有情况,但简而言之,它会尝试将这些字节与其在内存中的表示对齐,这可能与您的格式不完全匹配指定。在这种情况下,HBBBBQ 占用的空间比您预期的多 2 个字节。这是因为您的计算机将尝试在内存中打包结构,以便它们 word-aligned。您的计算机体系结构决定字对齐方式(在 64 位计算机上,字为 64 位或 8 字节长)。 Q 需要一个完整的单词,因此打包器会尝试将 Q 之前的所有内容与一个单词对齐。然而,HBBBB只需要6个字节;因此,默认情况下,Python 将额外填充 2 个字节以确保一切都对齐。在前面使用 < 既可以确保以正确的顺序解释字节,也不会尝试对齐字节。

仅供参考,如果其他人正在寻找此信息。 python 中存在库 f1-2019-telemetry。在文档中,缺少关于“如何使用”的部分,所以这里是一个片段:

from f1_2020_telemetry.packets import *
...

udp_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
udp_socket.bind((host, port))

while True:
    udp_packet = udp_socket.recv(2048)
    packet = unpack_udp_packet(udp_packet)

    if isinstance(packet, PacketSessionData_V1):  # refer to doc for classes / attribute
        print(packet.trackTemperature) # for example
    if isinstance(packet, PacketParticipantsData_V1):
        for i, participant in enumerate(packet.participants):
            print(DriverIDs[participant.driverId]) # the library has some mapping for pilot name / track name / ...

此致,

尼古拉斯