Compare commits

..

16 Commits

13 changed files with 285 additions and 50 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
/build
/dist
OutGaugeInterpreter.spec

View File

@@ -0,0 +1,47 @@
BEAMNG_DATA_FORMAT = "I4sHBBfffffffIIfff16s16sxxxx"
def decodeFlag(flag):
flagBin = str(bin(flag))
flags = {"showTurbo":newBool(flagBin[2]),"showKM":not newBool(flagBin[1]),"showBAR":not newBool(flagBin[0])}
return flags
def newBool(string):
if(string == "0"):
return False
if(string == "1"):
return True
def decodeLights(lightsAvailable,lightsActive):
lightsAvBin = str(bin(lightsAvailable))[2:][::-1]
lightsActBin = str(bin(lightsActive))[2:][::-1]
lights = {}
totalLights = ["shift_light","full_beam","handbrake","pit_limiter","tc","left_turn","right_turn","both_turns","oil_warn","battery_warn","abs","spare_light"]
for i in range(0,12):
try:
lights[totalLights[i]] = newBool(lightsActBin[i])
except Exception:
lights[totalLights[i]] = False
return lights
def unpackData(unpackedData):
carData = {"time":unpackedData[0],
"carName":unpackedData[1].decode("utf-8"),
"flags": decodeFlag(unpackedData[2]),
"gear": unpackedData[3],
"PLID": unpackedData[4],
"speed": unpackedData[5],
"rpm": unpackedData[6],
"turboPressure":unpackedData[7],
"engTemp":unpackedData[8],
"fuel":unpackedData[9],
"oilPressure":unpackedData[10],
"oilTemp":unpackedData[11],
"lights":decodeLights(unpackedData[12],unpackedData[13]),
"throttle": unpackedData[14],
"brake": unpackedData[15],
"clutch": unpackedData[16],
"misc1": unpackedData[17],
"misc2": unpackedData[18]
}
return carData

View File

@@ -0,0 +1,117 @@
import struct
FORZA_DATA_FORMAT = '<iIfffffffffffffffffffffffffffiiiiffffffffffffffffffffiiiiifffffffffffffffffHBBBBBBbbbffffi'
def unpackData(unpackedData):
carData = {
"IsRaceOn":unpackedData[0], #// = 1 when race is on. = 0 when in menus/race stopped …
#// Can overflow to 0 eventually
"TimestampMS": unpackedData[1],
"EngineMaxRpm": unpackedData[2],
"EngineIdleRpm": unpackedData[3],
"CurrentEngineRpm": unpackedData[4],
#// In the car's local space; X = right, Y = up, Z = forward
"AccelerationX": unpackedData[5],
"AccelerationY": unpackedData[6],
"AccelerationZ": unpackedData[7],
#// In the car's local space; X = right, Y = up, Z = forward
"VelocityX": unpackedData[8],
"VelocityY": unpackedData[9],
"VelocityZ": unpackedData[10],
#// In the car's local space; X = pitch, Y = yaw, Z = roll
"AngularVelocityX": unpackedData[11],
"AngularVelocityY": unpackedData[12],
"AngularVelocityZ": unpackedData[13],
"Yaw": unpackedData[14],
"Pitch": unpackedData[15],
"Roll": unpackedData[16],
#// Suspension travel normalized: 0.0f = max stretch; 1.0 = max compression
"NormalizedSuspensionTravelFrontLeft": unpackedData[17],
"NormalizedSuspensionTravelFrontRight": unpackedData[18],
"NormalizedSuspensionTravelRearLeft": unpackedData[19],
"NormalizedSuspensionTravelRearRight": unpackedData[20],
#// Tire normalized slip ratio, = 0 means 100% grip and |ratio| > 1.0 means loss of grip.
"TireSlipRatioFrontLeft": unpackedData[21],
"TireSlipRatioFrontRight": unpackedData[22],
"TireSlipRatioRearLeft": unpackedData[23],
"TireSlipRatioRearRight": unpackedData[24],
#// Wheels rotation speed radians/sec.
"WheelRotationSpeedFrontLeft": unpackedData[25],
"WheelRotationSpeedFrontRight": unpackedData[26],
"WheelRotationSpeedRearLeft": unpackedData[27],
"WheelRotationSpeedRearRight": unpackedData[28],
#// = 1 when wheel is on rumble strip, = 0 when off.
"WheelOnRumbleStripFrontLeft": unpackedData[29],
"WheelOnRumbleStripFrontRight": unpackedData[30],
"WheelOnRumbleStripRearLeft": unpackedData[31],
"WheelOnRumbleStripRearRight": unpackedData[32],
#// = from 0 to 1, where 1 is the deepest puddle
"WheelInPuddleDepthFrontLeft": unpackedData[33],
"WheelInPuddleDepthFrontRight": unpackedData[34],
"WheelInPuddleDepthRearLeft": unpackedData[35],
"WheelInPuddleDepthRearRight": unpackedData[36],
#// Non-dimensional surface rumble values passed to controller force feedback
"SurfaceRumbleFrontLeft": unpackedData[37],
"SurfaceRumbleFrontRight": unpackedData[38],
"SurfaceRumbleRearLeft": unpackedData[39],
"SurfaceRumbleRearRight": unpackedData[40],
#// Tire normalized slip angle, = 0 means 100% grip and |angle| > 1.0 means loss of grip.
"TireSlipAngleFrontLeft": unpackedData[41],
"TireSlipAngleFrontRight": unpackedData[42],
"TireSlipAngleRearLeft": unpackedData[43],
"TireSlipAngleRearRight": unpackedData[44],
#// Tire normalized combined slip, = 0 means 100% grip and |slip| > 1.0 means loss of grip.
"TireCombinedSlipFrontLeft": unpackedData[45],
"TireCombinedSlipFrontRight": unpackedData[46],
"TireCombinedSlipRearLeft": unpackedData[47],
"TireCombinedSlipRearRight": unpackedData[48],
#// Actual suspension travel in meters
"SuspensionTravelMetersFrontLeft": unpackedData[49],
"SuspensionTravelMetersFrontRight": unpackedData[50],
"SuspensionTravelMetersRearLeft": unpackedData[51],
"SuspensionTravelMetersRearRight": unpackedData[52],
#// Unique ID of the car make/model
"CarOrdinal": unpackedData[53],
#// Between 0 (D -- worst cars) and 7 (X class -- best cars) inclusive
"CarClass": unpackedData[54],
#// Between 100 (worst car) and 999 (best car) inclusive
"CarPerformanceIndex": unpackedData[55],
#// 0 = FWD, 1 = RWD, 2 = AWD
"DrivetrainType": unpackedData[56],
#// Number of cylinders in the engine
"NumCylinders": unpackedData[57],
"PositionX": unpackedData[58],
"PositionY": unpackedData[59],
"PositionZ": unpackedData[60],
"Speed": unpackedData[61],
"Power": unpackedData[62],
"Torque": unpackedData[63],
"TireTempFrontLeft": unpackedData[64],
"TireTempFrontRight": unpackedData[65],
"TireTempRearLeft": unpackedData[66],
"TireTempRearRight": unpackedData[67],
"Boost": unpackedData[68],
"Fuel": unpackedData[69],
"DistanceTraveled": unpackedData[70],
"BestLap": unpackedData[71],
"LastLap": unpackedData[72],
"CurrentLap": unpackedData[73],
"CurrentRaceTime": unpackedData[74],
"LapNumber": unpackedData[75],
"RacePosition": unpackedData[76],
"Accel": unpackedData[77],
"Brake": unpackedData[78],
"Clutch": unpackedData[79],
"HandBrake": unpackedData[80],
"Gear": unpackedData[81],
"Steer": unpackedData[82],
"NormalizedDrivingLine": unpackedData[83],
"NormalizedAIBrakeDifference": unpackedData[84],
"TireWearFrontLeft": unpackedData[85],
"TireWearFrontRight": unpackedData[86],
"TireWearRearLeft": unpackedData[87],
"TireWearRearRight": unpackedData[88],
#// ID for track
"TrackOrdinal": unpackedData[89]
}
return carData

1
GAME_METHODS/__init__.py Normal file
View File

@@ -0,0 +1 @@
__all__= ["BEAMNG_METHODS","FORZA_METHODS"]

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,63 +1,128 @@
#please use python 3.11
import socket import socket
import struct import struct
import serial import serial
import time
import platform
import sys
from enum import Enum
from GAME_METHODS import *
toPi=serial.Serial('COM5',115200) ##check if python 3.11 is running code and exit if not
def decodeFlag(flag): if not(sys.version_info[0] == 3 and sys.version_info[1] == 11):
flagBin = str(bin(flag)) raise Exception("code must be run in python verion 3.11.\ninput struct for network needs to be changed per python version.")
flags = {"showTurbo":newBool(flagBin[2]),"showKM":not newBool(flagBin[1]),"showBAR":not newBool(flagBin[0])}
return flags
def newBool(string): runningOs = platform.system()
if(string == "0"):
return False
if(string == "1"):
return True
UDP_IP = "0.0.0.0"
BEAMNG_UDP_PORT = 4444
FORZA_UDP_PORT= 4843
SERIAL_PORT = ""
firstRun= True #used to do the headers for csv files
def decodeLights(lightsAvailable,lightsActive): class GameType(Enum):
lightsAvBin = str(bin(lightsAvailable))[2:][::-1] NONE=0
lightsActBin = str(bin(lightsActive))[2:][::-1] BEAMNG = "BEAMNG"
lights = {} FORZA = "FORZA"
totalLights = ["shift_light","full_beam","handbrake","pit_limiter","tc","left_turn","right_turn","both_turns","oil_warn","battery_warn","abs","spare_light"]
for i in range(0,12): connectedArduino = False
connectedWebSocket = False
gameSelected = False
portToConnect= 0
gameType= GameType.NONE
carData = 0
csvOut = False
#Functions <-------------------------------------------------------------------------------------------
def csvWriteOut(firstRun,carData,csvFile):
if firstRun == True:
out=""
for im,nm in carData.items():
out = out + im + ","
csvFile.write(out + "\n")
outString=""
for itm,num in carData.items():
outString = outString + str(num) +","
csvFile.write(outString+"\n")
#code runs from here<-----------------------------------------------------------------------------------------------------------
if runningOs == 'Windows':
print("Windows detacted setting serial port to 'COM5'\n")
SERIAL_PORT= 'COM5'
elif runningOs == 'Linux':
print("Linux detected setting serial port to '/dev/ttyAMC0'\n")
SERIAL_PORT = '/dev/ttyACM0'
else:
print("OS detection failed setting serial port to 'COM5'\n")
SERIAL_PORT = 'COM5'
#select game
while gameSelected == False:
gameNo = input("1:BEAMNG\n2:FORZA\n\n7:Toggle CSV out ("+str(csvOut)+")\n9:SET SERIAL PORT ("+SERIAL_PORT+")\n")
if gameNo == "1":
portToConnect = BEAMNG_UDP_PORT
gameSelected = True
gameType=GameType.BEAMNG
print("BeamNG Selected")
elif gameNo == "2":
portToConnect = FORZA_UDP_PORT
gameType=GameType.FORZA
gameSelected=True
print("Forza Selected")
elif gameNo == "7":
if csvOut == True:
csvOut=False
elif csvOut ==False:
csvOut =True
elif gameNo == "9":
SERIAL_PORT = input("set serial port: ")
else:
print("please select a number from the list")
#check everything is connected
while connectedWebSocket == False:
try: try:
lights[totalLights[i]] = newBool(lightsActBin[i]) sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
except Exception: sock.bind((UDP_IP,portToConnect))
lights[totalLights[i]] = False connectedWebSocket = True
return lights except:
print("please check you are able to open the socket on this system\ntired to open port: "+portToConnect)
exit()
UDP_IP = "127.0.0.1" while connectedArduino == False:
UDP_PORT = 4444 try:
toPi=serial.Serial(SERIAL_PORT,115200,timeout=2) #connect to arduino
connectedArduino = True
except:
print("please check connection to arduino and verify the correct serial port")
time.sleep(1)
sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) if csvOut == True:
sock.bind((UDP_IP,UDP_PORT)) csvFile = open(gameType.value+"_"+str(int(time.time()))+'.csv',"a")
print("waiting for data:\n")
print("ready:\n")
while True: while True:
data, addr = sock.recvfrom(1024) data, addr = sock.recvfrom(1024)
unpackedData = struct.unpack("I4sHBBfffffffIIfff16s16sxxxx",data) if gameType == GameType.BEAMNG:
carData = {"time":unpackedData[0], unpackedData = struct.unpack(BEAMNG_METHODS.BEAMNG_DATA_FORMAT,data)
"carName":unpackedData[1].decode("utf-8"), carData = BEAMNG_METHODS.unpackData(unpackedData)
"flags": decodeFlag(unpackedData[2]),
"gear": unpackedData[3],
"PLID": unpackedData[4],
"speed": unpackedData[5],
"rpm": unpackedData[6],
"turboPressure":unpackedData[7],
"engTemp":unpackedData[8],
"fuel":unpackedData[9],
"oilPressure":unpackedData[10],
"oilTemp":unpackedData[11],
"lights":decodeLights(unpackedData[12],unpackedData[13]),
"throttle": unpackedData[14],
"brake": unpackedData[15],
"clutch": unpackedData[16],
"misc1": unpackedData[17],
"misc2": unpackedData[18]
}
#print("speed: %s" % (carData["speed"]*3.6))
kmh=carData["speed"]*3.6 kmh=carData["speed"]*3.6
if csvOut == True:
csvWriteOut(firstRun,carData,csvFile)
firstRun=False
elif gameType == GameType.FORZA:
unpackedData = struct.unpack(FORZA_METHODS.FORZA_DATA_FORMAT,data)
carData = FORZA_METHODS.unpackData(unpackedData)
kmh = carData["Speed"]*3.6
if csvOut == True:
csvWriteOut(firstRun,carData,csvFile)
firstRun=False
try:
toPi.write(str(kmh).encode()+":".encode()) toPi.write(str(kmh).encode()+":".encode())
except:
print("arduino disconnected please check connection\n")

1
createExe.sh Normal file
View File

@@ -0,0 +1 @@
pyinstaller.exe --add-data .\GAME_METHODS\*:.\GAME_METHODS\ --onefile .\OutGaugeInterpreter.py

1
requirements.txt Normal file
View File

@@ -0,0 +1 @@
pyserial==3.5