PyRaceview is a package for parsing messages used to power NASCAR Raceview. From these messages, detailed data such as GPS position, throttle, and steering input can be extracted.
Currently, PyRaceview does not provide client code to retrieve raw data from the Raceview websocket, but may in a future release.
Python 3.7+
pip install pyraceview
Each Raceview message starts with a 7-byte header containing some metadata
>>> from pyraceview.messages import MsgHeader
>>> msg_raw = (b'\xab\xcd\x00\x00\x01\xe5W(\x03\x1e\x9e\xb4\x01\xf9L\x83h\xc0\xcc\xf0\x12\xff\x00\x00\x02\xf8\x83\xc3#\xf0\xcd\x10\x0e\xff`'
b'\x00\x04\xf9\xa8\x03\x86P\xcc\xf0\x14\xff\x00\x00\x06\xf8\x0f\xc2\xfc0\xcd\x10\x0c\xff@\x00\x08\xf7Z\xc2\xbeP\xcd0\x12\xff'
b'\x00\x00\x0c\xf8m\xc3\x1c \xcd\x10\x0c\xff@\x00\x0e\xf9d\x03pP\xcc\xf0\x12\xff\x00\x00\x12\xf7.B\xaf \xcd0\x14\xff\x00\x00'
b'\x14\xfaV\x03\xaa\xf0\xcd\xb0\n\xff@\x00\x16\xf7\x03B\xa0@\xcd0\x12\xff\x00\x00\x18\xf7\x18\x82\xa7\xa0\xcd0\x14\xff\x00\x00'
b'\x1a\xf8\xf3\x83J\x00\xcc\xf0\x10\xff \x00\x1c\xf7\xb6\x02\xdd0\xcd0\x0e\xff \x00"\xf7\x9e\x82\xd5\x00\xcd0\x10\xff \x00$\xf7'
b'\xe4\xc2\xed\x00\xcd\x10\x0e\xff \x00&\xf8V\x83\x14\x10\xcd\x10\x0c\xff@\x00(\xf7\x87B\xcc\xf0\xcd0\x10\xff \x00*\xf8>\x83\x0c@'
b'\xcd\x10\x0c\xff@\x00,\xf7D\xc2\xb6\xb0\xcd0\x12\xff\x00\x00.\xf9\xfb\x83\x9b\xa0\xcd\x10\x14\xfe\xe0\x000\xf9\xdc\x03\x95\x10'
b'\xcc\xf0\x14\xfe\xe0\x00>\xf7\xf9\xc2\xf4\xf0\xcd\x10\x0c\xff@\x00@\xfa7C\xa5\xf0\xcd\x90\x1b\x00\x00\x00D\xf8\xdd\x83BP\xcc'
b'\xf0\x10\xff \x00J\xf8\xc6\x03;\x10\xcc\xf0\x10\xff \x00L\xf8(C\x04`\xcd\x10\x0c\xff@\x00R\xf7\xcdB\xe50\xcd\x10\x0e\xff \x00T'
b'\xfaw\x83\xaf\xa0\xcd\xb0\x0c\xff`\x00V\xf7r\x02\xc6\x10\xcd0\x12\xff\x00\x00^\xf8\x9a\xc3+0\xcd\x10\x0e\xff \x00`\xfa\x1b\x03\xa1@'
b'\xcd0\x1b\x00\x00\x00f\xf9yCw\x80\xcc\xf0\x12\xff\x00\x00|\xf9\x1e\x83X\xb0\xcc\xf0\x10\xff \x00\x84\xfa\xb8\xc3\xb9\xc0\xcd\xd0\x08'
b'\xfe\xe0\x00\x90\xf9\xc1C\x8d\xb0\xcc\xf0\x14\xff\x00\x00\x9c\xf9\x08\x83Qp\xcc\xf0\x10\xff \x00\xb0\xf6\xed\x82\x98\xc0\xcd0\x12'
b'\xff\x00\x00\xb8\xfa\x97C\xb4P\xcd\xd0\x0e\xff@\x00\xbe\xf95C`p\xcc\xf0\x12\xff\x00\x00\xc0\xf9\x91C\x7f`\xcc\xf0\x14\xff\x00\x00')
>>> hdr = MsgHeader(msg_raw)
>>> print(hdr)
Sync: 43981, Clock: 0, Size: 485, Type: WIn order to parse the entire message, we must use the header to lookup the correct message parser
>>> from pyraceview.messages import _parsers
>>> _parsers
{'a': pyraceview.messages.MsgCarStats.MsgCarStats,
'b': pyraceview.messages.MsgPitLaneExtended.MsgPitLaneExtended,
'd': pyraceview.messages.MsgPitLaneExtended.MsgPitLaneExtended,
'C': pyraceview.messages.MsgCupInfo.MsgCupInfo,
'F': pyraceview.messages.MsgPitWindow.MsgPitWindow,
'l': pyraceview.messages.MsgLapInfo.MsgLapInfo,
'O': pyraceview.messages.MsgTrackConfig.MsgTrackConfig,
'P': pyraceview.messages.MsgPitLaneEvent.MsgPitLaneEvent,
's': pyraceview.messages.MsgRaceStatus.MsgRaceStatus,
'V': pyraceview.messages.MsgVitcToLap.MsgVitcToLap,
'W': pyraceview.messages.MsgCarPosition.MsgCarPosition}
>>> parser = _parsers[hdr.byte_type]
>>> msg = parser(msg_raw)
>>> msg
<pyraceview.messages.MsgCarPosition.MsgCarPosition at 0x117763550>Messages, such as MsgCarPosition, have attributes with metadata about the message
>>> msg.num_cars
40
>>> msg.timecode
52338356Additionally, some messages contain a list of data per car
>>> len(msg.car_data)
40
>>> type(msg.car_data[0])
pyraceview.percar.PerCarPositionData.PerCarPositionDataClasses containing data per car, such as PerCarPositionData, will have an integer car_id attribute
>>> car_0 = msg.car_data[0]
>>> car_0.car_id
1To properly identify a car by "number", Raceview uses an algorithm to convert the integer car_id to a string value. In this case, id of value 1 is the '00', a valid NASCAR car number
>>> from pyraceview.util import id_to_num
>>> id_to_num(car_0.car_id)
'00'PerCarPositionData also contains the GPS position of the car
>>> car_0.pos_x, car_0.pos_y, car_0.pos_z
(-686.2, 1396.4, 81.95)To automatically parse many messages (e.g. as read from a websocket or from a file), PyRaceview provides the MsgFactory class which contains an internal buffer
>>> from pyraceview.messages import MsgFactory
>>> msg_raw = (b'\xab\xcd\x00\x00\x01\x95a\x03\x1egH(\xb0\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x16\x00\x0e\x00\x00\x00\x00\x00\x00\x00'
b'\x18\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x12\x00\x0e\x00\x00\x00\x00\x00\x00\x00,\x00\x0e\x00\x00\x00\x00\x00\x00\x00'
b'\x08\x00\x15\x00\x00\x00\x00\x00\x00\x00V\x00\x07\x00\x00\x00\x00\x00\x00\x00(\x00\x0e\x00\x00\x00\x00\x00\x00\x00"\x00'
b'\x07\x00\x00\x00\x00\x00\x00\x00\x1c\x00\x15\x00\x00\x00\x00\x00\x00\x00R\x00\x0e\x00\x00\x00\x00\x00\x00\x00$\x00\x0e'
b'\x00\x00\x00\x00\x00\x00\x00>\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x06\x00\x0e\x00\x00\x00\x00\x00\x00\x00L\x00\x0e\x00'
b'\x00\x00\x00\x00\x00\x00*\x00\x0e\x00\x00\x00\x00\x00\x00\x00&\x00\x15\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x07\x00\x00'
b'\x00\x00\x00\x00\x00\x02\x00\x07\x00\x00\x00\x00\x00\x00\x00^\x00\x0e\x00\x00\x00\x00\x00\x00\x00J\x00\x0e\x00\x00\x00'
b'\x00\x00\x00\x00D\x00\x07\x00\x00\x00\x00\x00\x00\x00\x1a\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x9c\x00\x0e\x00\x00\x00'
b'\x00\x00\x00\x00|\x00\x0e\x00\x00\x00\x00\x00\x00\x00\xbe\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x01\x00\x15\x00\x00\x00'
b'\x00\x00\x00\x00\x0e\x00\x0e\x00\x00\x00\x00\x00\x00\x00f\x00\x1b\x00\x00\x00\x00\x00\x00\x00\xc0\x00\x15\x00\x00\x00'
b'\x00\x00\x00\x00\x04\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x90\x00\x07\x00\x00\x00\x00\x00\x00\x000\x00\x0e\x00\x00\x00'
b'\x00\x00\x00\x00.\x00\x0e\x00\x00\x00\x00\x00\x00\x00`\x00\x0e\x00\x00\x00\x00\x00\x00\x00@\x00\x0e\x00\x00\x00\x00\x00'
b'\x00\x00\x14\x00\x15\x00\x00\x00\x00\x00\x00\x00T\x00\x1b\x00\x00\x00\x00\x00\x00\x00\xb8\x00\x0e\x00\x00\x00\x00\x00'
b'\x00\x00\x84\x00\x0e\x00\x00\x00\x00\x00\x00\x00\xab\xcd\x00\x00\x00zC2(\x01\x00\x00\x02\x00\x00\x04\x00\x00\x06\x00'
b'\x00\x08\x00\x00\x0c\x00\x00\x0e\x00\x00\x12\x00\x00\x14\x00\x00\x16\x00\x00\x18\x00\x00\x1a\x00\x00\x1c\x00\x00"\x00'
b'\x00$\x00\x00&\x00\x00(\x00\x00*\x00\x00,\x00\x00.\x00\x000\x00\x00>\x00\x00@\x00\x00D\x00\x00J\x00\x00L\x00\x00R\x00'
b'\x00T\x00\x00V\x00\x00^\x00\x00`\x00\x00f\x00\x00|\x00\x00\x84\x00\x00\x90\x00\x00\x9c\x00\x00\xb0\x00\x00\xb8\x00\x00'
b'\xbe\x00\x00\xc0\x00\x00\xab\xcd\x00\x02\x00\x14O\x00\t\xad~\x00\x1a\xe9\x08\xff\xff\xff\xcadaytona\x00')
>>> factory = MsgFactory(msg_raw)
>>> factory.has_message()
TrueRead all the messages that were pushed to the factory
>>> while factory.has_message():
>>> print(factory.get_message())
<pyraceview.messages.MsgCarStats.MsgCarStats object at 0x11777f400>
<pyraceview.messages.MsgCupInfo.MsgCupInfo object at 0x11777f4a8>
<pyraceview.messages.MsgTrackConfig.MsgTrackConfig object at 0x11777f550>Push more data
>>> factory.push_data(b'\xab\xcd\x00\x00\x01\xd9W\'\x03\x1ejg\x01\xf9L\x83h\xc0\xcc\xf0\x12\xff\x00\x00\x02\xf8\x83\xc3#\xf0\xcd\x10'
b'\x0e\xff`\x00\x04\xf9\xa8\x03\x86P\xcc\xf0\x14\xff\x00\x00\x08\xf7Z\xc2\xbeP\xcd0\x12\xff\x00\x00\x0c\xf8m\xc3'
b'\x1c \xcd\x10\x0c\xff@\x00\x0e\xf9d\x03pP\xcc\xf0\x12\xff\x00\x00\x12\xf7.B\xaf \xcd0\x14\xff\x00\x00\x14\xfaV'
b'\x03\xaa\xf0\xcd\xb0\n\xff@\x00\x16\xf7\x03B\xa0@\xcd0\x12\xff\x00\x00\x18\xf7\x18\x82\xa7\xa0\xcd0\x14\xff\x00'
b'\x00\x1a\xf8\xf3\x83J\x00\xcc\xf0\x10\xff \x00\x1c\xf7\xb6\x02\xdd0\xcd0\x0e\xff \x00"\xf7\x9e\x82\xd5\x00\xcd0'
b'\x10\xff \x00$\xf7\xe4\xc2\xed\x00\xcd\x10\x0e\xff \x00&\xf8V\x83\x14\x10\xcd\x10\x0c\xff@\x00(\xf7\x87B\xcc\xf0'
b'\xcd0\x10\xff \x00*\xf8>\x83\x0c@\xcd\x10\x0c\xff@\x00,\xf7D\xc2\xb6\xb0\xcd0\x12\xff\x00\x00.\xf9\xfb\x83\x9b'
b'\xa0\xcd\x10\x14\xfe\xe0\x000\xf9\xdc\x03\x95\x10\xcc\xf0\x14\xfe\xe0\x00>\xf7\xf9\x82\xf4\xe0\xcd\x10\x0c\xff@'
b'\x00@\xfa7C\xa5\xf0\xcd\x90\x1b\x00\x00\x00D\xf8\xdd\x83BP\xcc\xf0\x10\xff \x00J\xf8\xc6\x03;\x10\xcc\xf0\x10'
b'\xff \x00L\xf8(C\x04`\xcd\x10\x0c\xff@\x00R\xf7\xcdB\xe50\xcd\x10\x0e\xff \x00T\xfaw\x83\xaf\xa0\xcd\xb0\x0c\xff`'
b'\x00V\xf7r\x02\xc6\x10\xcd0\x12\xff\x00\x00^\xf8\x9a\xc3+0\xcd\x10\x0e\xff \x00`\xfa\x1b\x03\xa1@\xcd0\x1b\x00'
b'\x00\x00f\xf9y\x03w\x80\xcc\xf0\x12\xff\x00\x00|\xf9\x1e\x83X\xb0\xcc\xf0\x10\xff \x00\x84\xfa\xb8\xc3\xb9\xc0'
b'\xcd\xd0\x08\xfe\xe0\x00\x90\xf9\xc1C\x8d\xb0\xcc\xf0\x14\xff\x00\x00\x9c\xf9\x08\x83Qp\xcc\xf0\x10\xff \x00\xb0'
b'\xf6\xed\x82\x98\xc0\xcd0\x12\xff\x00\x00\xb8\xfa\x97C\xb4P\xcd\xd0\x0e\xff@\x00\xbe\xf95C`p\xcc\xf0\x12\xff\x00'
b'\x00\xc0\xf9\x91C\x7f`\xcc\xf0\x14\xff\x00\x00')
>>> factory.get_message()
<pyraceview.messages.MsgCarPosition.MsgCarPosition at 0x11777f6a0>