it feels okay’ish in my implementation in C for Quaternion ToEulerAngles (need more testing) but your code doesn’t look good indeed. try writing a function and feeding it each element at a time because you will need to call it with -X, -Z, -Y and W (i.e. Qc[0], Qc[2], Qc[1], Qc[3])Now we have local_velocity, and acceleration, I'm trying to figure out roll/pitch/yaw which I thought we had natively but I was wrong....
I guess the answer is somewhere here https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles with the EulerAngles ToEulerAngles(Quaternion q) but my python code is not behaving as expected. Tried with Qc insteaf of Q, swapped Z/Y axis.... still no relevant values.
Bad code:
sinr_cosp = 2*(Qc[3]*Qc[0]+Qc[1]*Qc[2])
cosr_cosp = 1-2*(Qc[0]*Qc[0]+Qc[1]*Qc[1])
roll = math.atan2(sinr_cosp,cosr_cosp)
sinp = 2*(Qc[3]*Qc[1]-Qc[2]*Qc[0])
if abs(sinp) >= 1:
pitch = math.copysign(np.pi/2 , sinp)
else:
pitch = math.asin(sinp)
siny_cosp = 2*(Qc[3]*Qc[2]+Qc[0]*Qc[1])
cosy_cosp = 1-2*(Qc[1]*Qc[1]+Qc[2]*Qc[2])
yaw = math.atan2(siny_cosp,cosy_cosp)
Once this last "detail" will be fixed, I'll do some code clean-up and publish it and probably write a short story on how all this was possible !
You are in for a wild ride. There are many way of describing quaternions and there are even more ways of describing euler angles. All of those will mess up your code if not accounted for.I'm trying to figure out roll/pitch/yaw
import socket
import sys
import struct
import math
import datetime
# from https://github.com/oconnor663/pure_python_salsa_chacha
def mask32(x):
return x & 0xFFFFFFFF
def add32(x, y):
return mask32(x + y)
def left_rotate(x, n):
return mask32(x << n) | (x >> (32 - n))
# a, b, c, and d are indexes into the 16-word block
def quarter_round(block, a, b, c, d):
block[b] ^= left_rotate(add32(block[a], block[d]), 7)
block[c] ^= left_rotate(add32(block[b], block[a]), 9)
block[d] ^= left_rotate(add32(block[c], block[b]), 13)
block[a] ^= left_rotate(add32(block[d], block[c]), 18)
def salsa20_permute(block):
for doubleround in range(10):
quarter_round(block, 0, 4, 8, 12) # column 1
quarter_round(block, 5, 9, 13, 1) # column 2
quarter_round(block, 10, 14, 2, 6) # column 3
quarter_round(block, 15, 3, 7, 11) # column 4
quarter_round(block, 0, 1, 2, 3) # row 1
quarter_round(block, 5, 6, 7, 4) # row 2
quarter_round(block, 10, 11, 8, 9) # row 3
quarter_round(block, 15, 12, 13, 14) # row 4
def words_from_bytes(b):
assert len(b) % 4 == 0
return [int.from_bytes(b[4 * i : 4 * i + 4], "little") for i in range(len(b) // 4)]
def bytes_from_words(w):
return b"".join(word.to_bytes(4, "little") for word in w)
def salsa20_block(key, nonce, blocknum):
# This implementation doesn't support 16-byte keys.
assert len(key) == 32
assert len(nonce) == 8
assert blocknum < 2 ** 64
constant_words = words_from_bytes(b"expand 32-byte k")
key_words = words_from_bytes(key)
nonce_words = words_from_bytes(nonce)
# fmt: off
original_block = [
constant_words[0], key_words[0], key_words[1], key_words[2],
key_words[3], constant_words[1], nonce_words[0], nonce_words[1],
mask32(blocknum), mask32(blocknum >> 32), constant_words[2], key_words[4],
key_words[5], key_words[6], key_words[7], constant_words[3],
]
# fmt: on
permuted_block = list(original_block)
salsa20_permute(permuted_block)
for i in range(len(permuted_block)):
permuted_block[i] = add32(permuted_block[i], original_block[i])
return bytes_from_words(permuted_block)
def salsa20_stream(key, nonce, length):
output = bytearray()
blocknum = 0
while length > 0:
block = salsa20_block(key, nonce, blocknum)
take = min(length, len(block))
output.extend(block[:take])
length -= take
blocknum += 1
return output
def salsa20_xor(key, nonce, message):
stream = salsa20_stream(key, nonce, len(message))
return bytes(x ^ y for x, y in zip(message, stream))
#https://github.com/Nenkai/PDTools/blob/master/SimulatorInterface/SimulatorInterface.cs
SendDelaySeconds = 10
ReceivePort = 33740
SendPort = 33739
port = ReceivePort
if len(sys.argv) == 2:
# Get "IP address of Server" and also the "port number" from
ip = sys.argv[1]
else:
print("Run like : python3 gt7racedata.py <playstation-ip>")
exit(1)
# Create a UDP socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# Bind the socket to the port
server_address = ('0.0.0.0', port)
s.bind(server_address)
s.settimeout(10)
#https://github.com/Nenkai/PDTools/blob/master/PDTools.Crypto/SimulationInterface/SimulatorInterfaceCryptorGT7.cs
def salsa20_dec(dat):
KEY = b'Simulator Interface Packet GT7 ver 0.0'
oiv = dat[0x40:0x44]
iv1 = int.from_bytes(oiv, byteorder='little') # Seed IV is always located there
iv2 = iv1 ^ 0xDEADBEAF #// Notice DEADBEAF, not DEADBEEF
IV = bytearray()
IV.extend(iv2.to_bytes(4, 'little'))
IV.extend(iv1.to_bytes(4, 'little'))
ddata = salsa20_xor(KEY[0:32], bytes(IV), dat)
#check magic number
magic = int.from_bytes(ddata[0:4], byteorder='little')
if magic != 0x47375330:
return bytearray(b'')
return ddata
def send_hb(s):
#send HB
send_data = 'A'
s.sendto(send_data.encode('utf-8'), (ip, SendPort))
print('send heartbeat')
send_hb(s)
#see https://www.gtplanet.net/forum/threads/gt7-is-compatible-with-motion-rig.410728/post-13823560
def quat_conj(Q):
return (-Q[0],-Q[1],-Q[2],Q[3])
def quat_vec(Q):
return (Q[0],Q[1],Q[2])
def quat_scalar(Q):
return Q[3]
def cross(A,B):
return (A[1]*B[2]-A[2]*B[1],A[2]*B[0]-A[0]*B[2],A[0]*B[1]-A[1]*B[0])
def add(A,B):
return (A[0]+B[0],A[1]+B[1],A[2]+B[2])
def sub(A,B):
return (A[0]-B[0],A[1]-B[1],A[2]-B[2])
def scale(A,s):
return (A[0]*s,A[1]*s,A[2]*s)
def quat_rot(V,Q):
Qv = quat_vec(Q)
U = cross(Qv,V)
w = quat_scalar(Q)
P = add(U,scale(V,w))
T = scale(Qv,2)
RR = cross(T,P)
R = add(V,RR)
return R
def roll_pitch_yaw(Q):
# see http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToEuler/Quaternions.pdf
# permute quaternion coefficients to determine
# order of roll/pitch/yaw rotation axes, scalar comes first always
P=(Q[3],Q[2],Q[0],Q[1])
# e is for handedness of axes
e = -1
x_p = 2*(P[0]*P[2] + e*P[1]*P[3])
pitch = math.asin(x_p)
if math.isclose(math.fabs(pitch),math.pi/2):
# handle singularity when pitch is +-90°
yaw = 0
roll = math.atan2(P[1],P[0])
else:
y_r = 2*(P[0]*P[1] - e*P[2]*P[3])
x_r = 1 - 2*(P[1]*P[1] + P[2]*P[2])
roll = math.atan2(y_r,x_r)
y_y = 2*(P[0]*P[3] - e*P[1]*P[2])
x_y = 1 - 2*(P[2]*P[2] + P[3]*P[3])
yaw =math.atan2(y_y,x_y)
return (roll,pitch,yaw)
print("Ctrl+C to exit the program")
dp_prev = 0
pknt = 0
Vp = (0,0,0)
while True:
try:
data, address = s.recvfrom(4096)
pknt = pknt + 1
print("received: %d bytes" % len(data))
ddata = salsa20_dec(data)
if len(ddata) > 0:
magic = struct.unpack_from('i',ddata,0)
P = struct.unpack_from('fff',ddata,0x4)
print('Position',P)
V = struct.unpack_from('fff',ddata,0x10)
print('Global Velocity',V)
Q = struct.unpack_from('ffff',ddata,0x1C)
Qc = quat_conj(Q)
Vl = quat_rot(V,Qc)
print("Local Velocity:",Vl)
dV = sub(Vl,Vp)
A = scale(dV,60)
print("Acceleration:",A)
G = scale(A,1.0/9.81)
print("G-Forces:",G)
Vp = Vl
roll,pitch,yaw = roll_pitch_yaw(Q)
print("Roll:",roll*180/math.pi)
print("Pitch:",pitch*180/math.pi)
print("Yaw:",yaw*180/math.pi)
if pknt > 900:
send_hb(s)
pknt = 0
except Exception as e:
print(e)
send_hb(s)
pknt = 0
pass
These are the g-forces resulting from car acceleration. The g-force from earth gravity is missing, but you could add it with little effort. Though i'm not sure how exactly the g-forces affect tire wear, i thought that tire wear is mainly driven by tire temperature.@tarnheld So this are the actual G-Forces?
With that its possible to simulate the tyre degradation?
These are the g-forces resulting from car acceleration. The g-force from earth gravity is missing, but you could add it with little effort. Though i'm not sure how exactly the g-forces affect tire wear, i thought that tire wear is mainly driven by tire temperature.
they were fast to pick up the reverse engineering info and update their software. luckily there will be mentions of @Nenkai and others (not likely)Noting that SIMRIG motion rigs software seems to support GT7 and GT Sport:
On the contrary, just got a reply from SIMRIG, and they do indeed mention Nenkai in their About-window:they were fast to pick up the reverse engineering info and update their software. luckily there will be mentions of @Nenkai and others (not likely)
are you driving with a SIMRIG? how is it? :-)=On the contrary, just got a reply from SIMRIG, and they do indeed mention Nenkai in their About-window:
View attachment 1189041
I should be so lucky! No, that screenshot was sent to me by SIMRIG after I asked them.are you driving with a SIMRIG? how is it?
if you need help building one, let me know. I built mine myselfI should be so lucky! No, that screenshot was sent to me by SIMRIG after I asked them.
Traceback (most recent call last):
File "D:\Program Files\gt7dashboard-main\gt7communication.py", line 186, in run
data, address = s.recvfrom(4096)
TimeoutError: timed out
That's saying you aren't receiving the data from the Playstation. You say it works fine from your phone, Do you have the phone open too? It's likely the PlayStation will only send the data to the first device that connects.Hello guys,
I finally found time to give it a new try.
But I don't get it running completly.
Thanks to your support I was able to get the Bokeh page running, but no data arrive.
This is the current outcome in the CMD:
Traceback (most recent call last): File "D:\Program Files\gt7dashboard-main\gt7communication.py", line 186, in run data, address = s.recvfrom(4096) TimeoutError: timed out
IP Adress for PS is ok is workeing on my Andriod device
Any idea?
What is the command you used?Hello guys,
I finally found time to give it a new try.
But I don't get it running completly.
Thanks to your support I was able to get the Bokeh page running, but no data arrive.
This is the current outcome in the CMD:
Traceback (most recent call last): File "D:\Program Files\gt7dashboard-main\gt7communication.py", line 186, in run data, address = s.recvfrom(4096) TimeoutError: timed out
IP Adress for PS is ok is workeing on my Andriod device
Any idea?
Just a few quick thoughts here, from a Mac user (although I have this running on an old Windows laptop):Ok, no phone conected. no change.
Comand in Powershell:
$Env:GT7_PLAYSTATION_IP="192.1XX.XXX.XXX"
bokeh serve .
PS D:\Program Files\gt7dashboard-main> bokeh serve .
2022-09-22 23:01:55,243 Starting Bokeh server version 2.4.3 (running on Tornado 6.2)
2022-09-22 23:01:55,245 User authentication hooks NOT provided (default user enabled)
2022-09-22 23:01:55,250 Bokeh app running at: http://localhost:5006/gt7dashboard-main
2022-09-22 23:01:55,250 Starting Bokeh server with process id: 18280
2022-09-22 23:02:19,553 Error running application handler <bokeh.application.handlers.directory.DirectoryHandler object at 0x000002552C5569E0>: No IP set in env var GT7_PLAYSTATION_IP
File 'main.py', line 438, in <module>:
raise Exception("No IP set in env var GT7_PLAYSTATION_IP") Traceback (most recent call last):
File "C:\Users\rdrol\AppData\Local\Programs\Python\Python310\lib\site-packages\bokeh\application\handlers\code_runner.py", line 231, in run
exec(self._code, module.__dict__)
File "D:\Program Files\gt7dashboard-main\main.py", line 438, in <module>
raise Exception("No IP set in env var GT7_PLAYSTATION_IP")
Exception: No IP set in env var GT7_PLAYSTATION_IP
PS X:\xxxxxxxx\gt7dashboard-main> $Env:GT7_PLAYSTATION_IP="192.168.2.107"
PS X:\xxxxxxxx\gt7dashboard-main> bokeh serve .
2022-09-22 23:05:19,323 Starting Bokeh server version 2.4.3 (running on Tornado 6.2)
2022-09-22 23:05:19,325 User authentication hooks NOT provided (default user enabled)
2022-09-22 23:05:19,329 Bokeh app running at: http://localhost:5006/gt7dashboard-main
2022-09-22 23:05:19,329 Starting Bokeh server with process id: 17452
2022-09-22 23:05:26,797 E-1001 (BAD_COLUMN_NAME): Glyph refers to nonexistent column name. This could either be due to a misspelling or typo, or due to an expected column being missing. : key "x" value "distance", key "y" value "timedelta" [renderer: GlyphRenderer(id='1187', ...)]
2022-09-22 23:05:26,797 E-1001 (BAD_COLUMN_NAME): Glyph refers to nonexistent column name. This could either be due to a misspelling or typo, or due to an expected column being missing. : key "x" value "raceline_z", key "y" value "raceline_x" [renderer: GlyphRenderer(id='1476', ...)]
2022-09-22 23:05:26,797 E-1001 (BAD_COLUMN_NAME): Glyph refers to nonexistent column name. This could either be due to a misspelling or typo, or due to an expected column being missing. : key "x" value "raceline_z", key "y" value "raceline_x" [renderer: GlyphRenderer(id='1494', ...)]
2022-09-22 23:05:26,933 WebSocket connection opened
2022-09-22 23:05:26,934 ServerConnection created
Traceback (most recent call last):
File "X:\xxxxxxxx\gt7dashboard-main\gt7communication.py", line 186, in run
data, address = s.recvfrom(4096)
TimeoutError: timed out
A couple of things here, off the top of my head:Ok I can ping the IP address , so I assume no Firewall issue
I don't know to get the "GT7_Playstation_IP" set in CMD....sorry I'm a noob
If I skip the Quation marks, I get this error:
set GT7_PLAYSTATION_IP=123.45.67.89
(of course replace with YOUR ip address here)set
in the CMD exe.No, there is no such data in the telemetry from the game. Unfortunately, but for what it is this "API" was never really aimed at consumer level interaction to start with. We will in all likelihood have to make do with what it is, as it is. At least it enables motion rigs for GT Sport and GT7, which is at least something.This is the most informative thread I've ever read. Thank you all who contributed. The SRS API definition contains wheel terrain contact type (asphalt, rumble strip or other) for each tire. Is it possible to get this data from GT7? Thanks.
Great to hear! Never give up (never surrender)Haaaa, now it works!!!!