diff --git a/gt7dashboard/gt7communication.py b/gt7dashboard/gt7communication.py index 4b8a881..e79d1f2 100644 --- a/gt7dashboard/gt7communication.py +++ b/gt7dashboard/gt7communication.py @@ -13,140 +13,11 @@ from salsa20 import Salsa20_xor -from gt7dashboard.gt7helper import seconds_to_lap_time +from gt7dashboard.gt7data import GTData +from gt7dashboard.gt7helper import divide_laps, seconds_to_lap_time, should_save_lap from gt7dashboard.gt7lap import Lap -class GTData: - def __init__(self, ddata): - if not ddata: - return - - self.package_id = struct.unpack('i', ddata[0x70:0x70 + 4])[0] - self.best_lap = struct.unpack('i', ddata[0x78:0x78 + 4])[0] - self.last_lap = struct.unpack('i', ddata[0x7C:0x7C + 4])[0] - self.current_lap = struct.unpack('h', ddata[0x74:0x74 + 2])[0] - self.current_gear = struct.unpack('B', ddata[0x90:0x90 + 1])[0] & 0b00001111 - self.suggested_gear = struct.unpack('B', ddata[0x90:0x90 + 1])[0] >> 4 - self.fuel_capacity = struct.unpack('f', ddata[0x48:0x48 + 4])[0] - self.current_fuel = struct.unpack('f', ddata[0x44:0x44 + 4])[0] # fuel - self.boost = struct.unpack('f', ddata[0x50:0x50 + 4])[0] - 1 - - self.tyre_diameter_FL = struct.unpack('f', ddata[0xB4:0xB4 + 4])[0] - self.tyre_diameter_FR = struct.unpack('f', ddata[0xB8:0xB8 + 4])[0] - self.tyre_diameter_RL = struct.unpack('f', ddata[0xBC:0xBC + 4])[0] - self.tyre_diameter_RR = struct.unpack('f', ddata[0xC0:0xC0 + 4])[0] - - self.type_speed_FL = abs(3.6 * self.tyre_diameter_FL * struct.unpack('f', ddata[0xA4:0xA4 + 4])[0]) - self.type_speed_FR = abs(3.6 * self.tyre_diameter_FR * struct.unpack('f', ddata[0xA8:0xA8 + 4])[0]) - self.type_speed_RL = abs(3.6 * self.tyre_diameter_RL * struct.unpack('f', ddata[0xAC:0xAC + 4])[0]) - self.tyre_speed_RR = abs(3.6 * self.tyre_diameter_RR * struct.unpack('f', ddata[0xB0:0xB0 + 4])[0]) - - self.car_speed = 3.6 * struct.unpack('f', ddata[0x4C:0x4C + 4])[0] - - if self.car_speed > 0: - self.tyre_slip_ratio_FL = '{:6.2f}'.format(self.type_speed_FL / self.car_speed) - self.tyre_slip_ratio_FR = '{:6.2f}'.format(self.type_speed_FR / self.car_speed) - self.tyre_slip_ratio_RL = '{:6.2f}'.format(self.type_speed_RL / self.car_speed) - self.tyre_slip_ratio_RR = '{:6.2f}'.format(self.tyre_speed_RR / self.car_speed) - - self.time_on_track = timedelta( - seconds=round(struct.unpack('i', ddata[0x80:0x80 + 4])[0] / 1000)) # time of day on track - - self.total_laps = struct.unpack('h', ddata[0x76:0x76 + 2])[0] # total laps - - self.current_position = struct.unpack('h', ddata[0x84:0x84 + 2])[0] # current position - self.total_positions = struct.unpack('h', ddata[0x86:0x86 + 2])[0] # total positions - - self.car_id = struct.unpack('i', ddata[0x124:0x124 + 4])[0] # car id - - self.throttle = struct.unpack('B', ddata[0x91:0x91 + 1])[0] / 2.55 # throttle - self.rpm = struct.unpack('f', ddata[0x3C:0x3C + 4])[0] # rpm - self.rpm_rev_warning = struct.unpack('H', ddata[0x88:0x88 + 2])[0] # rpm rev warning - - self.brake = struct.unpack('B', ddata[0x92:0x92 + 1])[0] / 2.55 # brake - - self.boost = struct.unpack('f', ddata[0x50:0x50 + 4])[0] - 1 # boost - - self.rpm_rev_limiter = struct.unpack('H', ddata[0x8A:0x8A + 2])[0] # rpm rev limiter - - self.estimated_top_speed = struct.unpack('h', ddata[0x8C:0x8C + 2])[0] # estimated top speed - - self.clutch = struct.unpack('f', ddata[0xF4:0xF4 + 4])[0] # clutch - self.clutch_engaged = struct.unpack('f', ddata[0xF8:0xF8 + 4])[0] # clutch engaged - self.rpm_after_clutch = struct.unpack('f', ddata[0xFC:0xFC + 4])[0] # rpm after clutch - - self.oil_temp = struct.unpack('f', ddata[0x5C:0x5C + 4])[0] # oil temp - self.water_temp = struct.unpack('f', ddata[0x58:0x58 + 4])[0] # water temp - - self.oil_pressure = struct.unpack('f', ddata[0x54:0x54 + 4])[0] # oil pressure - self.ride_height = 1000 * struct.unpack('f', ddata[0x38:0x38 + 4])[0] # ride height - - self.tyre_temp_FL = struct.unpack('f', ddata[0x60:0x60 + 4])[0] # tyre temp FL - self.tyre_temp_FR = struct.unpack('f', ddata[0x64:0x64 + 4])[0] # tyre temp FR - - self.suspension_fl = struct.unpack('f', ddata[0xC4:0xC4 + 4])[0] # suspension FL - self.suspension_fr = struct.unpack('f', ddata[0xC8:0xC8 + 4])[0] # suspension FR - - self.tyre_temp_rl = struct.unpack('f', ddata[0x68:0x68 + 4])[0] # tyre temp RL - self.tyre_temp_rr = struct.unpack('f', ddata[0x6C:0x6C + 4])[0] # tyre temp RR - - self.suspension_rl = struct.unpack('f', ddata[0xCC:0xCC + 4])[0] # suspension RL - self.suspension_rr = struct.unpack('f', ddata[0xD0:0xD0 + 4])[0] # suspension RR - - self.gear_1 = struct.unpack('f', ddata[0x104:0x104 + 4])[0] # 1st gear - self.gear_2 = struct.unpack('f', ddata[0x108:0x108 + 4])[0] # 2nd gear - self.gear_3 = struct.unpack('f', ddata[0x10C:0x10C + 4])[0] # 3rd gear - self.gear_4 = struct.unpack('f', ddata[0x110:0x110 + 4])[0] # 4th gear - self.gear_5 = struct.unpack('f', ddata[0x114:0x114 + 4])[0] # 5th gear - self.gear_6 = struct.unpack('f', ddata[0x118:0x118 + 4])[0] # 6th gear - self.gear_7 = struct.unpack('f', ddata[0x11C:0x11C + 4])[0] # 7th gear - self.gear_8 = struct.unpack('f', ddata[0x120:0x120 + 4])[0] # 8th gear - - # self.struct.unpack('f', ddata[0x100:0x100+4])[0] # ??? gear - - self.position_x = struct.unpack('f', ddata[0x04:0x04 + 4])[0] # pos X - self.position_y = struct.unpack('f', ddata[0x08:0x08 + 4])[0] # pos Y - self.position_z = struct.unpack('f', ddata[0x0C:0x0C + 4])[0] # pos Z - - self.velocity_x = struct.unpack('f', ddata[0x10:0x10 + 4])[0] # velocity X - self.velocity_y = struct.unpack('f', ddata[0x14:0x14 + 4])[0] # velocity Y - self.velocity_z = struct.unpack('f', ddata[0x18:0x18 + 4])[0] # velocity Z - - self.rotation_pitch = struct.unpack('f', ddata[0x1C:0x1C + 4])[0] # rot Pitch - self.rotation_yaw = struct.unpack('f', ddata[0x20:0x20 + 4])[0] # rot Yaw - self.rotation_roll = struct.unpack('f', ddata[0x24:0x24 + 4])[0] # rot Roll - - self.angular_velocity_x = struct.unpack('f', ddata[0x2C:0x2C + 4])[0] # angular velocity X - self.angular_velocity_y = struct.unpack('f', ddata[0x30:0x30 + 4])[0] # angular velocity Y - self.angular_velocity_z = struct.unpack('f', ddata[0x34:0x34 + 4])[0] # angular velocity Z - - self.is_paused = bin(struct.unpack('B', ddata[0x8E:0x8E + 1])[0])[-2] == '1' - self.in_race = bin(struct.unpack('B', ddata[0x8E:0x8E + 1])[0])[-1] == '1' - - # struct.unpack('f', ddata[0x28:0x28+4])[0] # rot ??? - - # bin(struct.unpack('B', ddata[0x8E:0x8E+1])[0])[2:] # various flags (see https://github.com/Nenkai/PDTools/blob/master/PDTools.SimulatorInterface/SimulatorPacketG7S0.cs) - # bin(struct.unpack('B', ddata[0x8F:0x8F+1])[0])[2:] # various flags (see https://github.com/Nenkai/PDTools/blob/master/PDTools.SimulatorInterface/SimulatorPacketG7S0.cs) - # bin(struct.unpack('B', ddata[0x93:0x93+1])[0])[2:] # 0x93 = ??? - - # struct.unpack('f', ddata[0x94:0x94+4])[0] # 0x94 = ??? - # struct.unpack('f', ddata[0x98:0x98+4])[0] # 0x98 = ??? - # struct.unpack('f', ddata[0x9C:0x9C+4])[0] # 0x9C = ??? - # struct.unpack('f', ddata[0xA0:0xA0+4])[0] # 0xA0 = ??? - - # struct.unpack('f', ddata[0xD4:0xD4+4])[0] # 0xD4 = ??? - # struct.unpack('f', ddata[0xD8:0xD8+4])[0] # 0xD8 = ??? - # struct.unpack('f', ddata[0xDC:0xDC+4])[0] # 0xDC = ??? - # struct.unpack('f', ddata[0xE0:0xE0+4])[0] # 0xE0 = ??? - - # struct.unpack('f', ddata[0xE4:0xE4+4])[0] # 0xE4 = ??? - # struct.unpack('f', ddata[0xE8:0xE8+4])[0] # 0xE8 = ??? - # struct.unpack('f', ddata[0xEC:0xEC+4])[0] # 0xEC = ??? - # struct.unpack('f', ddata[0xF0:0xF0+4])[0] # 0xF0 = ??? - - def to_json(self): - return json.dumps(self, indent=4, sort_keys=True, default=str) class Session(): def __init__(self): @@ -179,6 +50,7 @@ def __init__(self, playstation_ip): self.current_lap = Lap() self.session = Session() self.laps = [] + self.package_id = 0 self.last_data = GTData(None) # This is used to record race data in any case. This will override the "in_race" flag. @@ -201,49 +73,64 @@ def run(self): s.bind(('0.0.0.0', self.receive_port)) self._send_hb(s) s.settimeout(10) - previous_lap = -1 package_id = 0 package_nr = 0 while not self._shall_restart and self._shall_run: try: + # Receive data from the socket data, address = s.recvfrom(4096) + # Increment the package number package_nr = package_nr + 1 - ddata = salsa20_dec(data) - if len(ddata) > 0 and struct.unpack('i', ddata[0x70:0x70 + 4])[0] > package_id: - self.last_data = GTData(ddata) - self._last_time_data_received = time.time() + # Decrypt the received data using Salsa20 + ddata = salsa20_dec(data) - package_id = struct.unpack('i', ddata[0x70:0x70 + 4])[0] - bstlap = struct.unpack('i', ddata[0x78:0x78 + 4])[0] - lstlap = struct.unpack('i', ddata[0x7C:0x7C + 4])[0] - curlap = struct.unpack('h', ddata[0x74:0x74 + 2])[0] + # Check if the decrypted data length is greater than 0 and if the package ID is greater than the current package ID + if len(ddata) > 0 and struct.unpack('i', ddata[0x70:0x70 + 4])[0] > package_id: + # Update the last time data was received to the current time + self._last_time_data_received = time.time() + new_data_tl = GTData(ddata) - if curlap == 0: - self.session.special_packet_time = 0 + package_id = new_data_tl.package_id - if curlap > 0 and (self.last_data.in_race or self.always_record_data): + is_new_lap = divide_laps(self.last_data, new_data_tl) - if curlap != previous_lap: - # New lap - previous_lap = curlap + bstlap = new_data_tl.best_lap + lstlap = new_data_tl.last_lap - self.session.special_packet_time += lstlap - self.current_lap.lap_ticks * 1000.0 / 60.0 - self.session.best_lap = bstlap + self.current_lap.is_replay = self.always_record_data + if new_data_tl.current_lap == 0: + self.session.special_packet_time = 0 + + #(new_data_tl.in_race or self.always_record_data) + + if is_new_lap: + self.session.special_packet_time += lstlap - self.current_lap.lap_ticks * 1000.0 / 60.0 + self.session.best_lap = bstlap + + if new_data_tl.last_lap > 0: + # Regular finished laps (crossing the finish line in races or time trials) + # have their lap time stored in last_lap + self.current_lap.lap_finish_time = new_data_tl.last_lap + else: + # Manual laps have no time assigned, so take current live time as lap finish time. + # Finish time is tracked in seconds while live time is tracked in ms + self.current_lap.lap_finish_time = self.current_lap.lap_live_time * 1000 + + if should_save_lap(self.last_data, new_data_tl, self.current_lap): self.finish_lap() - - else: - curLapTime = 0 - # Reset lap self.current_lap = Lap() - self._log_data(self.last_data) + # Update the last received data with the new GTData + self.last_data = new_data_tl + self._log_data(new_data_tl) if package_nr > 100: self._send_hb(s) package_nr = 0 + except (OSError, TimeoutError) as e: # Handler for package exceptions self._send_hb(s) @@ -279,6 +166,10 @@ def get_last_data(self) -> GTData: if time.time() > timeout: break + def get_last_data_once(self) -> GTData: + if self.last_data is not None: + return self.last_data + def get_laps(self) -> List[Lap]: return self.laps @@ -292,8 +183,8 @@ def load_laps(self, laps: List[Lap], to_last_position = False, to_first_position def _log_data(self, data): - if not (data.in_race or self.always_record_data): - return + # if not (data.in_race or self.always_record_data): + # return if data.is_paused: return @@ -373,32 +264,23 @@ def _log_data(self, data): # Adapted from https://www.gtplanet.net/forum/threads/gt7-is-compatible-with-motion-rig.410728/post-13810797 self.current_lap.lap_live_time = (self.current_lap.lap_ticks * 1. / 60.) - (self.session.special_packet_time / 1000.) - self.current_lap.data_time.append(self.current_lap.lap_live_time) self.current_lap.car_id = data.car_id + def finish_lap(self, manual=False): """ Finishes a lap with info we only know after crossing the line after each lap """ - if manual: - # Manual laps have no time assigned, so take current live time as lap finish time. - # Finish time is tracked in seconds while live time is tracked in ms - self.current_lap.lap_finish_time = self.current_lap.lap_live_time * 1000 - else: - # Regular finished laps (crossing the finish line in races or time trials) - # have their lap time stored in last_lap - self.current_lap.lap_finish_time = self.last_data.last_lap - # Track recording meta data self.current_lap.is_replay = self.always_record_data self.current_lap.is_manual = manual self.current_lap.fuel_at_end = self.last_data.current_fuel self.current_lap.fuel_consumed = self.current_lap.fuel_at_start - self.current_lap.fuel_at_end - self.current_lap.lap_finish_time = self.current_lap.lap_finish_time + # self.current_lap.lap_finish_time = self.current_lap.lap_finish_time self.current_lap.total_laps = self.last_data.total_laps self.current_lap.title = seconds_to_lap_time(self.current_lap.lap_finish_time / 1000) self.current_lap.car_id = self.last_data.car_id @@ -406,18 +288,16 @@ def finish_lap(self, manual=False): # TODO Proper pythonic name self.current_lap.EstimatedTopSpeed = self.last_data.estimated_top_speed + self.current_lap.lap_end_timestamp = datetime.datetime.now() + # if len(self.laps) > 1: + # self.current_lap = equalizer_lap(self.laps[0], self.current_lap) - # Race is not in 0th lap, which is before starting the race. - # We will only persist those laps that have crossed the starting line at least once - # And those laps which have data for speed logged. This will prevent empty laps. - # TODO Correct this comment, this is about Laptime not lap numbers - if self.current_lap.lap_finish_time > 0 and len(self.current_lap.data_speed) > 0: - self.laps.insert(0, self.current_lap) + self.laps.insert(0, self.current_lap) - # Make a copy of this lap and call the callback function if set - if self.lap_callback_function: - self.lap_callback_function(copy.deepcopy(self.current_lap)) + # Make a copy of this lap and call the callback function if set + if self.lap_callback_function: + self.lap_callback_function(copy.deepcopy(self.current_lap)) # Reset current lap with an empty one self.current_lap = Lap() diff --git a/gt7dashboard/gt7data.py b/gt7dashboard/gt7data.py new file mode 100644 index 0000000..64b95c1 --- /dev/null +++ b/gt7dashboard/gt7data.py @@ -0,0 +1,140 @@ +from datetime import timedelta +import json +import struct + + +class GTData: + def __init__(self, ddata): + if not ddata: + ddata = bytearray(0x124 + 4) + + self.package_id = struct.unpack('i', ddata[0x70:0x70 + 4])[0] + self.best_lap = struct.unpack('i', ddata[0x78:0x78 + 4])[0] + self.last_lap = struct.unpack('i', ddata[0x7C:0x7C + 4])[0] + self.current_lap = struct.unpack('h', ddata[0x74:0x74 + 2])[0] + self.current_gear = struct.unpack('B', ddata[0x90:0x90 + 1])[0] & 0b00001111 + self.suggested_gear = struct.unpack('B', ddata[0x90:0x90 + 1])[0] >> 4 + self.fuel_capacity = struct.unpack('f', ddata[0x48:0x48 + 4])[0] + self.current_fuel = struct.unpack('f', ddata[0x44:0x44 + 4])[0] # fuel + self.boost = struct.unpack('f', ddata[0x50:0x50 + 4])[0] - 1 + + self.tyre_diameter_FL = struct.unpack('f', ddata[0xB4:0xB4 + 4])[0] + self.tyre_diameter_FR = struct.unpack('f', ddata[0xB8:0xB8 + 4])[0] + self.tyre_diameter_RL = struct.unpack('f', ddata[0xBC:0xBC + 4])[0] + self.tyre_diameter_RR = struct.unpack('f', ddata[0xC0:0xC0 + 4])[0] + + self.type_speed_FL = abs(3.6 * self.tyre_diameter_FL * struct.unpack('f', ddata[0xA4:0xA4 + 4])[0]) + self.type_speed_FR = abs(3.6 * self.tyre_diameter_FR * struct.unpack('f', ddata[0xA8:0xA8 + 4])[0]) + self.type_speed_RL = abs(3.6 * self.tyre_diameter_RL * struct.unpack('f', ddata[0xAC:0xAC + 4])[0]) + self.tyre_speed_RR = abs(3.6 * self.tyre_diameter_RR * struct.unpack('f', ddata[0xB0:0xB0 + 4])[0]) + + self.car_speed = 3.6 * struct.unpack('f', ddata[0x4C:0x4C + 4])[0] + + self.tyre_slip_ratio_FL = 0 + self.tyre_slip_ratio_FR = 0 + self.tyre_slip_ratio_RL = 0 + self.tyre_slip_ratio_RR = 0 + + if self.car_speed > 0: + self.tyre_slip_ratio_FL = '{:6.2f}'.format(self.type_speed_FL / self.car_speed) + self.tyre_slip_ratio_FR = '{:6.2f}'.format(self.type_speed_FR / self.car_speed) + self.tyre_slip_ratio_RL = '{:6.2f}'.format(self.type_speed_RL / self.car_speed) + self.tyre_slip_ratio_RR = '{:6.2f}'.format(self.tyre_speed_RR / self.car_speed) + + self.time_on_track = timedelta( + seconds=round(struct.unpack('i', ddata[0x80:0x80 + 4])[0] / 1000)) # time of day on track + + self.total_laps = struct.unpack('h', ddata[0x76:0x76 + 2])[0] # total laps + + self.current_position = struct.unpack('h', ddata[0x84:0x84 + 2])[0] # current position + self.total_positions = struct.unpack('h', ddata[0x86:0x86 + 2])[0] # total positions + + self.car_id = struct.unpack('i', ddata[0x124:0x124 + 4])[0] # car id + + self.throttle = struct.unpack('B', ddata[0x91:0x91 + 1])[0] / 2.55 # throttle + self.rpm = struct.unpack('f', ddata[0x3C:0x3C + 4])[0] # rpm + self.rpm_rev_warning = struct.unpack('H', ddata[0x88:0x88 + 2])[0] # rpm rev warning + + self.brake = struct.unpack('B', ddata[0x92:0x92 + 1])[0] / 2.55 # brake + + self.boost = struct.unpack('f', ddata[0x50:0x50 + 4])[0] - 1 # boost + + self.rpm_rev_limiter = struct.unpack('H', ddata[0x8A:0x8A + 2])[0] # rpm rev limiter + + self.estimated_top_speed = struct.unpack('h', ddata[0x8C:0x8C + 2])[0] # estimated top speed + + self.clutch = struct.unpack('f', ddata[0xF4:0xF4 + 4])[0] # clutch + self.clutch_engaged = struct.unpack('f', ddata[0xF8:0xF8 + 4])[0] # clutch engaged + self.rpm_after_clutch = struct.unpack('f', ddata[0xFC:0xFC + 4])[0] # rpm after clutch + + self.oil_temp = struct.unpack('f', ddata[0x5C:0x5C + 4])[0] # oil temp + self.water_temp = struct.unpack('f', ddata[0x58:0x58 + 4])[0] # water temp + + self.oil_pressure = struct.unpack('f', ddata[0x54:0x54 + 4])[0] # oil pressure + self.ride_height = 1000 * struct.unpack('f', ddata[0x38:0x38 + 4])[0] # ride height + + self.tyre_temp_FL = struct.unpack('f', ddata[0x60:0x60 + 4])[0] # tyre temp FL + self.tyre_temp_FR = struct.unpack('f', ddata[0x64:0x64 + 4])[0] # tyre temp FR + + self.suspension_fl = struct.unpack('f', ddata[0xC4:0xC4 + 4])[0] # suspension FL + self.suspension_fr = struct.unpack('f', ddata[0xC8:0xC8 + 4])[0] # suspension FR + + self.tyre_temp_rl = struct.unpack('f', ddata[0x68:0x68 + 4])[0] # tyre temp RL + self.tyre_temp_rr = struct.unpack('f', ddata[0x6C:0x6C + 4])[0] # tyre temp RR + + self.suspension_rl = struct.unpack('f', ddata[0xCC:0xCC + 4])[0] # suspension RL + self.suspension_rr = struct.unpack('f', ddata[0xD0:0xD0 + 4])[0] # suspension RR + + self.gear_1 = struct.unpack('f', ddata[0x104:0x104 + 4])[0] # 1st gear + self.gear_2 = struct.unpack('f', ddata[0x108:0x108 + 4])[0] # 2nd gear + self.gear_3 = struct.unpack('f', ddata[0x10C:0x10C + 4])[0] # 3rd gear + self.gear_4 = struct.unpack('f', ddata[0x110:0x110 + 4])[0] # 4th gear + self.gear_5 = struct.unpack('f', ddata[0x114:0x114 + 4])[0] # 5th gear + self.gear_6 = struct.unpack('f', ddata[0x118:0x118 + 4])[0] # 6th gear + self.gear_7 = struct.unpack('f', ddata[0x11C:0x11C + 4])[0] # 7th gear + self.gear_8 = struct.unpack('f', ddata[0x120:0x120 + 4])[0] # 8th gear + + # self.struct.unpack('f', ddata[0x100:0x100+4])[0] # ??? gear + + self.position_x = struct.unpack('f', ddata[0x04:0x04 + 4])[0] # pos X + self.position_y = struct.unpack('f', ddata[0x08:0x08 + 4])[0] # pos Y + self.position_z = struct.unpack('f', ddata[0x0C:0x0C + 4])[0] # pos Z + + self.velocity_x = struct.unpack('f', ddata[0x10:0x10 + 4])[0] # velocity X + self.velocity_y = struct.unpack('f', ddata[0x14:0x14 + 4])[0] # velocity Y + self.velocity_z = struct.unpack('f', ddata[0x18:0x18 + 4])[0] # velocity Z + + self.rotation_pitch = struct.unpack('f', ddata[0x1C:0x1C + 4])[0] # rot Pitch + self.rotation_yaw = struct.unpack('f', ddata[0x20:0x20 + 4])[0] # rot Yaw + self.rotation_roll = struct.unpack('f', ddata[0x24:0x24 + 4])[0] # rot Roll + + self.angular_velocity_x = struct.unpack('f', ddata[0x2C:0x2C + 4])[0] # angular velocity X + self.angular_velocity_y = struct.unpack('f', ddata[0x30:0x30 + 4])[0] # angular velocity Y + self.angular_velocity_z = struct.unpack('f', ddata[0x34:0x34 + 4])[0] # angular velocity Z + + self.is_paused = bin(struct.unpack('B', ddata[0x8E:0x8E + 1])[0])[-2] == '1' + self.in_race = bin(struct.unpack('B', ddata[0x8E:0x8E + 1])[0])[-1] == '1' + + # struct.unpack('f', ddata[0x28:0x28+4])[0] # rot ??? + + # bin(struct.unpack('B', ddata[0x8E:0x8E+1])[0])[2:] # various flags (see https://github.com/Nenkai/PDTools/blob/master/PDTools.SimulatorInterface/SimulatorPacketG7S0.cs) + # bin(struct.unpack('B', ddata[0x8F:0x8F+1])[0])[2:] # various flags (see https://github.com/Nenkai/PDTools/blob/master/PDTools.SimulatorInterface/SimulatorPacketG7S0.cs) + # bin(struct.unpack('B', ddata[0x93:0x93+1])[0])[2:] # 0x93 = ??? + + # struct.unpack('f', ddata[0x94:0x94+4])[0] # 0x94 = ??? + # struct.unpack('f', ddata[0x98:0x98+4])[0] # 0x98 = ??? + # struct.unpack('f', ddata[0x9C:0x9C+4])[0] # 0x9C = ??? + # struct.unpack('f', ddata[0xA0:0xA0+4])[0] # 0xA0 = ??? + + # struct.unpack('f', ddata[0xD4:0xD4+4])[0] # 0xD4 = ??? + # struct.unpack('f', ddata[0xD8:0xD8+4])[0] # 0xD8 = ??? + # struct.unpack('f', ddata[0xDC:0xDC+4])[0] # 0xDC = ??? + # struct.unpack('f', ddata[0xE0:0xE0+4])[0] # 0xE0 = ??? + + # struct.unpack('f', ddata[0xE4:0xE4+4])[0] # 0xE4 = ??? + # struct.unpack('f', ddata[0xE8:0xE8+4])[0] # 0xE8 = ??? + # struct.unpack('f', ddata[0xEC:0xEC+4])[0] # 0xEC = ??? + # struct.unpack('f', ddata[0xF0:0xF0+4])[0] # 0xF0 = ??? + + def to_json(self): + return json.dumps(self, indent=4, sort_keys=True, default=str) \ No newline at end of file diff --git a/gt7dashboard/gt7diagrams.py b/gt7dashboard/gt7diagrams.py index 68cb161..aaa452b 100644 --- a/gt7dashboard/gt7diagrams.py +++ b/gt7dashboard/gt7diagrams.py @@ -2,7 +2,7 @@ import bokeh from bokeh.layouts import layout -from bokeh.models import ColumnDataSource, Label, Scatter, Column, Line, TableColumn, DataTable, Range1d +from bokeh.models import ColumnDataSource, Label, Scatter, Column, Line, TableColumn, DataTable, Range1d, WheelZoomTool, PanTool from bokeh.plotting import figure from gt7dashboard import gt7helper @@ -39,6 +39,7 @@ def get_throttle_braking_race_line_diagram(): data={"raceline_z_throttle": [], "raceline_x_throttle": []} ), ) + breaking_line = s_race_line.line( x="raceline_x_braking", y="raceline_z_braking", @@ -185,6 +186,8 @@ def __init__(self, width=400): # This is the number of default laps, # last lap, best lap and median lap self.number_of_default_laps = 3 + self.wheel_zoom_x = WheelZoomTool(dimensions='width') + self.x_pan = PanTool(dimensions='width') tooltips = [ @@ -220,8 +223,11 @@ def __init__(self, width=400): width=width, height=250, tooltips=tooltips, - active_drag="box_zoom", + active_drag="auto" ) + self.f_speed.add_tools(self.wheel_zoom_x,self.x_pan) + self.f_speed.toolbar.active_scroll = self.wheel_zoom_x + self.f_speed.toolbar.active_drag = self.x_pan self.f_speed_variance = figure( y_axis_label="Spd.Dev.", @@ -230,8 +236,11 @@ def __init__(self, width=400): width=width, height=int(self.f_speed.height / 4), tooltips=self.tooltips_speed_variance, - active_drag="box_zoom", + active_drag="auto", ) + self.f_speed_variance.add_tools(self.wheel_zoom_x,self.x_pan) + self.f_speed_variance.toolbar.active_scroll = self.wheel_zoom_x + self.f_speed_variance.toolbar.active_drag = self.x_pan self.f_time_diff = figure( title="Time Diff - Last, Reference", @@ -240,8 +249,11 @@ def __init__(self, width=400): width=width, height=int(self.f_speed.height / 2), tooltips=tooltips_timedelta, - active_drag="box_zoom", + active_drag="auto" ) + self.f_time_diff.add_tools(self.wheel_zoom_x,self.x_pan) + self.f_time_diff.toolbar.active_scroll = self.wheel_zoom_x + self.f_time_diff.toolbar.active_drag = self.x_pan self.f_throttle = figure( x_range=self.f_speed.x_range, @@ -249,16 +261,23 @@ def __init__(self, width=400): width=width, height=int(self.f_speed.height / 2), tooltips=tooltips, - active_drag="box_zoom", + active_drag="auto" ) + self.f_throttle.add_tools(self.wheel_zoom_x,self.x_pan) + self.f_throttle.toolbar.active_scroll = self.wheel_zoom_x + self.f_throttle.toolbar.active_drag = self.x_pan + self.f_braking = figure( x_range=self.f_speed.x_range, y_axis_label="Braking", width=width, height=int(self.f_speed.height / 2), tooltips=tooltips, - active_drag="box_zoom", + active_drag="auto" ) + self.f_braking.add_tools(self.wheel_zoom_x,self.x_pan) + self.f_braking.toolbar.active_scroll = self.wheel_zoom_x + self.f_braking.toolbar.active_drag = self.x_pan self.f_coasting = figure( x_range=self.f_speed.x_range, @@ -266,8 +285,11 @@ def __init__(self, width=400): width=width, height=int(self.f_speed.height / 2), tooltips=tooltips, - active_drag="box_zoom", + active_drag="auto", ) + self.f_coasting.add_tools(self.wheel_zoom_x,self.x_pan) + self.f_coasting.toolbar.active_scroll = self.wheel_zoom_x + self.f_coasting.toolbar.active_drag = self.x_pan self.f_tires = figure( x_range=self.f_speed.x_range, @@ -275,8 +297,11 @@ def __init__(self, width=400): width=width, height=int(self.f_speed.height / 2), tooltips=tooltips, - active_drag="box_zoom", + active_drag="auto", ) + self.f_tires.add_tools(self.wheel_zoom_x,self.x_pan) + self.f_tires.toolbar.active_scroll = self.wheel_zoom_x + self.f_tires.toolbar.active_drag = self.x_pan self.f_rpm = figure( x_range=self.f_speed.x_range, @@ -284,8 +309,11 @@ def __init__(self, width=400): width=width, height=int(self.f_speed.height / 2), tooltips=tooltips, - active_drag="box_zoom", + active_drag="auto", ) + self.f_rpm.add_tools(self.wheel_zoom_x,self.x_pan) + self.f_rpm.toolbar.active_scroll = self.wheel_zoom_x + self.f_rpm.toolbar.active_drag = self.x_pan self.f_gear = figure( x_range=self.f_speed.x_range, @@ -293,8 +321,11 @@ def __init__(self, width=400): width=width, height=int(self.f_speed.height / 2), tooltips=tooltips, - active_drag="box_zoom", + active_drag="auto", ) + self.f_gear.add_tools(self.wheel_zoom_x,self.x_pan) + self.f_gear.toolbar.active_scroll = self.wheel_zoom_x + self.f_gear.toolbar.active_drag = self.x_pan self.f_boost = figure( x_range=self.f_speed.x_range, @@ -302,8 +333,11 @@ def __init__(self, width=400): width=width, height=int(self.f_speed.height / 2), tooltips=tooltips, - active_drag="box_zoom", + active_drag="auto", ) + self.f_boost.add_tools(self.wheel_zoom_x,self.x_pan) + self.f_boost.toolbar.active_scroll = self.wheel_zoom_x + self.f_boost.toolbar.active_drag = self.x_pan self.f_yaw_rate = figure( x_range=self.f_speed.x_range, @@ -311,8 +345,11 @@ def __init__(self, width=400): width=width, height=int(self.f_speed.height / 2), tooltips=tooltips, - active_drag="box_zoom", + active_drag="auto", ) + self.f_yaw_rate.add_tools(self.wheel_zoom_x,self.x_pan) + self.f_yaw_rate.toolbar.active_scroll = self.wheel_zoom_x + self.f_yaw_rate.toolbar.active_drag = self.x_pan self.f_speed.toolbar.autohide = True diff --git a/gt7dashboard/gt7help.py b/gt7dashboard/gt7help.py index 0efa0ae..248ea0d 100644 --- a/gt7dashboard/gt7help.py +++ b/gt7dashboard/gt7help.py @@ -74,5 +74,5 @@ def get_help_div(help_text_resource): def get_help_text_resource(help_text_resource): return f""" -
?⃝
+
?
""" diff --git a/gt7dashboard/gt7helper.py b/gt7dashboard/gt7helper.py index 975d0da..9b7a122 100644 --- a/gt7dashboard/gt7helper.py +++ b/gt7dashboard/gt7helper.py @@ -16,6 +16,7 @@ from scipy.signal import find_peaks from tabulate import tabulate +from gt7dashboard.gt7data import GTData from gt7dashboard.gt7lap import Lap from gt7dashboard import gt7helper @@ -766,3 +767,81 @@ def calculate_laps_left_on_fuel(current_lap, last_lap) -> float: laps_left: float fuel_consumed_last_lap = last_lap.fuel_at_start - last_lap.fuel_at_end laps_left = current_lap.fuel - (last_lap.laps_to_go * fuel_consumed_last_lap) + + + +def divide_laps(old_data: GTData, new_data: GTData): + if old_data.current_lap != new_data.current_lap or old_data.total_laps != new_data.total_laps: + print(f"new lap = {old_data.current_lap}/{old_data.total_laps} -> {new_data.current_lap}/{new_data.total_laps}") + return True + + if old_data.total_laps == new_data.total_laps and old_data.current_lap == new_data.current_lap: + return False + +def should_save_lap(old_data: GTData, new_data: GTData, lap : Lap ): + + if old_data.current_lap <= 0 : + print(f"Lap not saved because old_data.current_lap = {old_data.current_lap}") + return False + + if old_data.in_race != 1 and not lap.is_replay: + print(f"Lap not saved because old_data.in_race = {old_data.in_race} or lap.is_replay = {lap.is_replay}") + return False + + if lap.lap_live_time < 5: + print(f"Lap not saved because lap.lap_live_time = {lap.lap_live_time}") + return False + + if len(lap.data_speed) <= 0: + print(f"Lap not saved because len(lap.data_speed) = {len(lap.data_speed)}") + return False + + print(f"Lap saved") + return True + +def equalizer_lap(reference_lap: Lap, current_lap:Lap): + when_to_cut = 0 + start_position = { + "x": round(reference_lap.data_position_x[0],3), + "y": round(reference_lap.data_position_y[0],3), + "z": round(reference_lap.data_position_z[0],3) + } + print(f"Start position: {start_position}") + print(f"Reference data size: {len(reference_lap.data_position_x)} lap_ticks: {reference_lap.lap_ticks} is_replay: {reference_lap.is_replay}") + print(f"Current data size: {len(current_lap.data_position_x)} lap_ticks: {current_lap.lap_ticks} is_replay: {current_lap.is_replay}") + + # check what lap is shorter + range_lap = min(len(reference_lap.data_position_x), len(current_lap.data_position_x)) + + for i in range(range_lap): + + if round(current_lap.data_position_x[i],3) == start_position["x"]: + when_to_cut = i + print(f"Equalizing position x from tick {i} to {current_lap.lap_ticks}") + if round(current_lap.data_position_y[i],3) == start_position["y"]: + when_to_cut = i + print(f"Equalizing position y from tick {i} to {current_lap.lap_ticks}") + if round(current_lap.data_position_z[i],3) == start_position["z"]: + when_to_cut = i + print(f"Equalizing position z from tick {i} to {current_lap.lap_ticks}") + + print(f"Cutting lap at tick {when_to_cut}") + + current_lap.data_position_x = current_lap.data_position_x[when_to_cut:] + current_lap.data_position_y = current_lap.data_position_y[when_to_cut:] + current_lap.data_position_z = current_lap.data_position_z[when_to_cut:] + + current_lap.data_throttle = current_lap.data_throttle[when_to_cut:] + current_lap.data_braking = current_lap.data_braking[when_to_cut:] + current_lap.data_coasting = current_lap.data_coasting[when_to_cut:] + current_lap.data_speed = current_lap.data_speed[when_to_cut:] + current_lap.data_time = current_lap.data_time[when_to_cut:] + current_lap.data_rpm = current_lap.data_rpm[when_to_cut:] + current_lap.data_gear = current_lap.data_gear[when_to_cut:] + current_lap.data_tires = current_lap.data_tires[when_to_cut:] + + current_lap.data_boost = current_lap.data_boost[when_to_cut:] + current_lap.data_rotation_yaw = current_lap.data_rotation_yaw[when_to_cut:] + current_lap.data_absolute_yaw_rate_per_second = current_lap.data_absolute_yaw_rate_per_second[when_to_cut:] + + return current_lap \ No newline at end of file diff --git a/gt7dashboard/test/test_gt7communication.py b/gt7dashboard/test/test_gt7communication.py index 45f6754..5cf565e 100644 --- a/gt7dashboard/test/test_gt7communication.py +++ b/gt7dashboard/test/test_gt7communication.py @@ -1,3 +1,4 @@ +import logging import os import time import unittest @@ -5,12 +6,13 @@ from gt7dashboard import gt7communication from gt7dashboard.gt7lap import Lap -PLAYSTATION_IP = "ps5wifi" +PLAYSTATION_IP = "192.168.15.96" # check if host is up def is_host_up(ip: str) -> bool: response = os.system("ping -c 1 " + PLAYSTATION_IP) + logging.debug("Response: %s" % response) #and then check the response... if response == 0: @@ -30,8 +32,11 @@ def setUpClass(self) -> None: self.gt7comm.start() # Sleep until connection is setup # TODO Add timeout + logging.debug("Waiting for connection") + while not self.gt7comm.is_connected(): time.sleep(0.1) + logging.debug("Is connected: %s" % self.gt7comm.is_connected()) @classmethod def tearDownClass(self) -> None: diff --git a/main.py b/main.py index 00a134c..235b660 100644 --- a/main.py +++ b/main.py @@ -2,6 +2,7 @@ import itertools import logging import os +from random import randint import time from typing import List @@ -26,6 +27,7 @@ from gt7dashboard.gt7help import get_help_div from gt7dashboard.gt7helper import ( + equalizer_lap, load_laps_from_pickle, save_laps_to_pickle, list_lap_files_from_path, @@ -33,6 +35,7 @@ ) from gt7dashboard.gt7lap import Lap + # set logging level to debug logger = logging.getLogger('main.py') logger.setLevel(logging.DEBUG) @@ -110,7 +113,7 @@ def update_header_line(div: Div, last_lap: Lap, reference_lap: Lap): def update_lap_change(): """ Is called whenever a lap changes. - It detects if the telemetry date retrieved is the same as the data displayed. + It detects if the telemetry data retrieved is the same as the data displayed. If true, it updates all the visual elements. """ global g_laps_stored @@ -119,61 +122,75 @@ def update_lap_change(): global g_telemetry_update_needed global g_reference_lap_selected - update_start_time = time.time() - - laps = app.gt7comm.get_laps() + update_start_time = time.time() # Record the start time of the update process + laps = app.gt7comm.get_laps() # Retrieve the latest laps from the telemetry + + # logger.debug(laps) + # Check if the session has changed and update tuning info if needed if app.gt7comm.session != g_session_stored: update_tuning_info() g_session_stored = copy.copy(app.gt7comm.session) + # Check if the connection status has changed and update connection info if needed if app.gt7comm.is_connected() != g_connection_status_stored: update_connection_info() g_connection_status_stored = copy.copy(app.gt7comm.is_connected()) - # This saves on cpu time, 99.9% of the time this is true + # Save on CPU time, as 99.9% of the time the laps are the same and no update is needed if laps == g_laps_stored and not g_telemetry_update_needed: return logger.debug("Rerendering laps") - reference_lap = Lap() + reference_lap = Lap() # Initialize a reference lap object if len(laps) > 0: - - last_lap = laps[0] + last_lap = laps[0] # Get the most recent lap if len(laps) > 1: + + # Get the reference lap using a helper function reference_lap = gt7helper.get_last_reference_median_lap( laps, reference_lap_selected=g_reference_lap_selected )[1] + last_lap = equalizer_lap(reference_lap, last_lap) + + # Update the speed peak and valley diagram with the last lap and reference lap div_speed_peak_valley_diagram.text = get_speed_peak_and_valley_diagram(last_lap, reference_lap) + # Update the header line with the last lap and reference lap update_header_line(div_header_line, last_lap, reference_lap) logger.debug("Updating of %d laps" % len(laps)) + # Update the time table and log the time taken start_time = time.time() update_time_table(laps) logger.debug("Updating time table took %dms" % ((time.time() - start_time) * 1000)) + # Update the reference lap select and log the time taken start_time = time.time() update_reference_lap_select(laps) logger.debug("Updating reference lap select took %dms" % ((time.time() - start_time) * 1000)) + # Update the speed velocity graph and log the time taken start_time = time.time() update_speed_velocity_graph(laps) logger.debug("Updating speed velocity graph took %dms" % ((time.time() - start_time) * 1000)) + # Update the race lines and log the time taken start_time = time.time() update_race_lines(laps, reference_lap) logger.debug("Updating race lines took %dms" % ((time.time() - start_time) * 1000)) + # Log the total time taken for the entire update process logger.debug("End of updating laps, whole Update took %dms" % ((time.time() - update_start_time) * 1000)) - g_laps_stored = laps.copy() - g_telemetry_update_needed = False + g_laps_stored = laps.copy() # Store the latest laps + g_telemetry_update_needed = False # Reset the telemetry update flag + def update_speed_velocity_graph(laps: List[Lap]): @@ -208,13 +225,15 @@ def update_speed_velocity_graph(laps: List[Lap]): # Update breakpoints # Adding Brake Points is slow when rendering, this is on Bokehs side about 3s brake_points_enabled = os.environ.get("GT7_ADD_BRAKEPOINTS") == "true" + brake_points_enabled = True + try: + if brake_points_enabled and len(last_lap.data_braking) > 0: + update_break_points(last_lap, s_race_line, "blue") - if brake_points_enabled and len(last_lap.data_braking) > 0: - update_break_points(last_lap, s_race_line, "blue") - - if brake_points_enabled and len(reference_lap.data_braking) > 0: - update_break_points(reference_lap, s_race_line, "magenta") - + if brake_points_enabled and len(reference_lap.data_braking) > 0: + update_break_points(reference_lap, s_race_line, "magenta") + except Exception as e: + logger.error(f"Error adding brake points: {e}") def update_break_points(lap: Lap, race_line: figure, color: str): brake_points_x, brake_points_y = gt7helper.get_brake_points(lap) @@ -328,31 +347,38 @@ def get_race_lines_layout(number_of_race_lines): # Share the gt7comm connection between sessions by storing them as an application attribute if not hasattr(app, "gt7comm"): + # Retrieve the PlayStation IP address and the path to load laps from environment variables playstation_ip = os.environ.get("GT7_PLAYSTATION_IP") load_laps_path = os.environ.get("GT7_LOAD_LAPS_PATH") + # If no IP address is set in the environment variable, use the broadcast address if not playstation_ip: playstation_ip = "255.255.255.255" logger.info(f"No IP set in env var GT7_PLAYSTATION_IP using broadcast at {playstation_ip}") + # Initialize the GT7Communication object with the PlayStation IP address app.gt7comm = gt7communication.GT7Communication(playstation_ip) + # Load laps from a pickle file if the path is set if load_laps_path: app.gt7comm.load_laps( load_laps_from_pickle(load_laps_path), replace_other_laps=True ) + # Start the GT7 communication thread app.gt7comm.start() else: - # Reuse existing thread + # Reuse the existing GT7 communication thread if not app.gt7comm.is_connected(): logger.info("Restarting gt7communcation because of no connection") + # Restart the GT7 communication thread if there is no connection app.gt7comm.restart() else: - # Existing thread has connection, proceed + # If the existing thread has a connection, proceed without any action pass + # def init_lap_times_source(): # global lap_times_source # lap_times_source.data = gt7helper.pd_data_frame_from_lap([], best_lap_time=app.gt7comm.session.last_lap) @@ -415,7 +441,8 @@ def table_row_selection_callback(attrname, old, new): match_aspect=True, width=race_line_width, height=race_line_width, - active_drag="box_zoom", + active_drag="auto", + active_scroll="wheel_zoom", tooltips=race_line_tooltips, ) @@ -497,9 +524,6 @@ def table_row_selection_callback(attrname, old, new): ] ) - - - l2, race_lines, race_lines_data = get_race_lines_layout(number_of_race_lines=1) l3 = layout( @@ -510,11 +534,118 @@ def table_row_selection_callback(attrname, old, new): sizing_mode="stretch_width", ) +data_dict = { + "metric": [], + "values": [] +} + +source = ColumnDataSource(data=data_dict) + + +columns = [ + TableColumn(field="metric", title="Metric"), + TableColumn(field="values", title="Value") +] + +data_table = DataTable(source=source, columns=columns, width=400, height=3000) + +def update_telemetry_table(): + current_data = app.gt7comm.get_last_data_once() + + if current_data is None: + return + + telemetry = current_data + + new_data_table = [ + ("Package ID", telemetry.package_id), + ("Best Lap", telemetry.best_lap), + ("Last Lap", telemetry.last_lap), + ("Laps", f"{telemetry.current_lap}/{telemetry.total_laps}"), + ("Current Gear", telemetry.current_gear), + ("Suggested Gear", telemetry.suggested_gear), + ("Fuel Capacity", telemetry.fuel_capacity), + ("Current Fuel", telemetry.current_fuel), + ("Boost", telemetry.boost), + ("Type Speed FL", telemetry.type_speed_FL), + ("Type Speed FR", telemetry.type_speed_FR), + ("Type Speed RL", telemetry.type_speed_RL), + ("Tyre Speed RR", telemetry.tyre_speed_RR), + ("Car Speed", telemetry.car_speed), + ("Tyre Slip Ratio FL", telemetry.tyre_slip_ratio_FL), + ("Tyre Slip Ratio FR", telemetry.tyre_slip_ratio_FR), + ("Tyre Slip Ratio RL", telemetry.tyre_slip_ratio_RL), + ("Tyre Slip Ratio RR", telemetry.tyre_slip_ratio_RR), + ("Current Position", telemetry.current_position), + ("Total Positions", telemetry.total_positions), + ("Is Paused", telemetry.is_paused), + ("In Race", telemetry.in_race), + ("Car ID", telemetry.car_id), + ("Throttle", telemetry.throttle), + ("RPM", telemetry.rpm), + ("RPM Rev Warning", telemetry.rpm_rev_warning), + ("Brake", telemetry.brake), + ("RPM Rev Limiter", telemetry.rpm_rev_limiter), + ("Estimated Top Speed", telemetry.estimated_top_speed), + ("Clutch", telemetry.clutch), + ("Clutch Engaged", telemetry.clutch_engaged), + ("RPM After Clutch", telemetry.rpm_after_clutch), + ("Ride Height", telemetry.ride_height), + ("Tyre Temp FL", telemetry.tyre_temp_FL), + ("Tyre Temp FR", telemetry.tyre_temp_FR), + ("Suspension FL", telemetry.suspension_fl), + ("Suspension FR", telemetry.suspension_fr), + ("Tyre Temp RL", telemetry.tyre_temp_rl), + ("Tyre Temp RR", telemetry.tyre_temp_rr), + ("Suspension RL", telemetry.suspension_rl), + ("Suspension RR", telemetry.suspension_rr), + ("Position X", telemetry.position_x), + ("Position Y", telemetry.position_y), + ("Position Z", telemetry.position_z), + ("Velocity X", telemetry.velocity_x), + ("Velocity Y", telemetry.velocity_y), + ("Velocity Z", telemetry.velocity_z), + ("Rotation Pitch", telemetry.rotation_pitch), + ("Rotation Yaw", telemetry.rotation_yaw), + ("Rotation Roll", telemetry.rotation_roll), + ("Angular Velocity X", telemetry.angular_velocity_x), + ("Angular Velocity Y", telemetry.angular_velocity_y), + ("Angular Velocity Z", telemetry.angular_velocity_z), + ("Oil Temp", telemetry.oil_temp), + ("Water Temp", telemetry.water_temp), + ("Oil Pressure", telemetry.oil_pressure), + # ("Time on Track", telemetry.time_on_track), + # ("Gear 1", telemetry.gear_1), + # ("Gear 2", telemetry.gear_2), + # ("Gear 3", telemetry.gear_3), + # ("Gear 4", telemetry.gear_4), + # ("Gear 5", telemetry.gear_5), + # ("Gear 6", telemetry.gear_6), + # ("Gear 7", telemetry.gear_7), + # ("Gear 8", telemetry.gear_8), + # ("Tyre Diameter FL", telemetry.tyre_diameter_FL), + # ("Tyre Diameter FR", telemetry.tyre_diameter_FR), + # ("Tyre Diameter RL", telemetry.tyre_diameter_RL), + # ("Tyre Diameter RR", telemetry.tyre_diameter_RR), + ] + + source.data = { + "metric": [item[0] for item in new_data_table], + "values": [item[1] for item in new_data_table] +} + + +# Create a layout +l4 = layout([ + [data_table] +]) + # Setup the tabs tab1 = TabPanel(child=l1, title="Get Faster") tab2 = TabPanel(child=l2, title="Race Lines") tab3 = TabPanel(child=l3, title="Race") -tabs = Tabs(tabs=[tab1, tab2, tab3]) +tab4 = TabPanel(child=l4, title="Dashboard") +tabs = Tabs(tabs=[tab1, tab2, tab3, tab4]) curdoc().add_root(tabs) curdoc().title = "GT7 Dashboard" @@ -522,3 +653,12 @@ def table_row_selection_callback(attrname, old, new): # This will only trigger once per lap, but we check every second if anything happened curdoc().add_periodic_callback(update_lap_change, 1000) curdoc().add_periodic_callback(update_fuel_map, 5000) + +curdoc().add_periodic_callback(update_telemetry_table, 200) + +# def update_packate_id(): +# print(app.gt7comm.get_package_id()) + +# curdoc().add_periodic_callback(update_packate_id, 500) + +# curdoc().add_periodic_callback \ No newline at end of file diff --git a/pytest.ini b/pytest.ini index 2c25935..abb1a53 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,4 +1,7 @@ [pytest] +log_cli = true +log_level = DEBUG +log_cli_level = DEBUG minversion = 7.0 pythonpath = src testpaths = diff --git a/test_bokeh.py2 b/test_bokeh.py2 new file mode 100644 index 0000000..9ffbb70 --- /dev/null +++ b/test_bokeh.py2 @@ -0,0 +1,29 @@ +from bokeh.plotting import figure, output_file, show +from bokeh.models import WheelZoomTool + +# Output to an HTML file +output_file("line_plot_with_x_wheel_zoom.html") + +# Create a figure object +p = figure( + title="Simple Line Plot with X-Axis Wheel Zoom", + x_axis_label='X-Axis', + y_axis_label='Y-Axis' + + ) + +# Create a WheelZoomTool for x-axis only +wheel_zoom = WheelZoomTool(dimensions='width') + +# Add the wheel zoom tool to the figure +p.add_tools(wheel_zoom) + + +# Set the created wheel_zoom tool as the active_scroll tool +p.toolbar.active_scroll = wheel_zoom + +# Add a line renderer with legend and line thickness +p.line([1, 2, 3, 4, 5], [6, 7, 2, 4, 5], legend_label="Line", line_width=2) + +# Show the results +show(p)