/ components / paddock / telemetry / models.py
models.py
  1  import datetime
  2  
  3  from dirtyfields import DirtyFieldsMixin
  4  from django.db import models
  5  from django_prometheus.models import ExportModelOperationsMixin
  6  from model_utils.models import TimeStampedModel
  7  from picklefield.fields import PickledObjectField
  8  
  9  
 10  class Driver(ExportModelOperationsMixin("driver"), TimeStampedModel):
 11      class Meta:
 12          ordering = [
 13              "name",
 14          ]
 15  
 16      name = models.CharField(max_length=200, unique=True)
 17  
 18      def __str__(self):
 19          return self.name
 20  
 21  
 22  class Game(TimeStampedModel):
 23      class Meta:
 24          ordering = [
 25              "name",
 26          ]
 27  
 28      name = models.CharField(max_length=200, unique=True)
 29  
 30      def __str__(self):
 31          return self.name
 32  
 33  
 34  class Track(TimeStampedModel):
 35      class Meta:
 36          ordering = [
 37              "name",
 38          ]
 39  
 40      name = models.CharField(max_length=200)
 41      length = models.IntegerField(default=0)
 42  
 43      game = models.ForeignKey(Game, on_delete=models.CASCADE, related_name="tracks")
 44  
 45      def __str__(self):
 46          return self.name
 47  
 48  
 49  class Car(TimeStampedModel):
 50      class Meta:
 51          ordering = [
 52              "name",
 53          ]
 54  
 55      name = models.CharField(max_length=200)
 56  
 57      game = models.ForeignKey(Game, on_delete=models.CASCADE, related_name="cars")
 58      car_class = models.ForeignKey("CarClass", on_delete=models.CASCADE, related_name="cars", null=True)
 59  
 60      def __str__(self):
 61          return self.name
 62  
 63  
 64  class CarClass(TimeStampedModel):
 65      class Meta:
 66          ordering = [
 67              "name",
 68          ]
 69  
 70      name = models.CharField(max_length=200)
 71  
 72      game = models.ForeignKey(Game, on_delete=models.CASCADE, related_name="car_classes")
 73  
 74      def __str__(self):
 75          return self.name
 76  
 77  
 78  class SessionType(TimeStampedModel):
 79      type = models.CharField(max_length=200, unique=True)
 80  
 81      def __str__(self):
 82          return self.type
 83  
 84  
 85  class Session(ExportModelOperationsMixin("session"), DirtyFieldsMixin, TimeStampedModel):
 86      session_id = models.CharField(max_length=200)
 87      start = models.DateTimeField(default=datetime.datetime.now)
 88      end = models.DateTimeField(default=datetime.datetime.now)
 89  
 90      driver = models.ForeignKey(Driver, on_delete=models.CASCADE, related_name="sessions")
 91      session_type = models.ForeignKey(SessionType, on_delete=models.CASCADE, related_name="sessions")
 92      game = models.ForeignKey(Game, on_delete=models.CASCADE, related_name="sessions")
 93  
 94      class Meta:
 95          unique_together = (
 96              "driver",
 97              "session_id",
 98              "session_type",
 99              "game",
100          )
101  
102      def __str__(self):
103          return self.session_id
104  
105  
106  class FastLap(ExportModelOperationsMixin("fastlap"), TimeStampedModel):
107      game = models.ForeignKey(Game, on_delete=models.CASCADE, related_name="fast_laps")
108      car = models.ForeignKey(Car, on_delete=models.CASCADE, related_name="fast_laps")
109      track = models.ForeignKey(Track, on_delete=models.CASCADE, related_name="fast_laps")
110      driver = models.ForeignKey(Driver, on_delete=models.CASCADE, related_name="fast_laps", null=True)
111      # add binary field to hold arbitrary data
112      data = PickledObjectField(null=True)
113  
114      class Meta:
115          ordering = ["game", "car", "track"]
116  
117      def __str__(self):
118          return f"{self.game} {self.car} {self.track}"
119  
120  
121  class Lap(ExportModelOperationsMixin("lap"), DirtyFieldsMixin, TimeStampedModel):
122      number = models.IntegerField()
123      start = models.DateTimeField(default=datetime.datetime.now)
124      end = models.DateTimeField(default=datetime.datetime.now)
125      time = models.FloatField(default=0)
126      length = models.IntegerField(default=0)
127      valid = models.BooleanField(default=False)
128  
129      session = models.ForeignKey(Session, on_delete=models.CASCADE, related_name="laps")
130      track = models.ForeignKey(Track, on_delete=models.CASCADE, related_name="laps")
131      car = models.ForeignKey(Car, on_delete=models.CASCADE, related_name="laps")
132      fast_lap = models.ForeignKey(FastLap, on_delete=models.CASCADE, related_name="laps", null=True)
133  
134      class Meta:
135          ordering = [
136              "number",
137          ]
138          unique_together = ("session", "start")
139  
140      def __str__(self):
141          return (
142              f"{self.number}: {self.start.strftime('%H:%M:%S')} - {self.end.strftime('%H:%M:%S')} "
143              + f"{self.time}s {self.length}m valid: {self.valid}"
144          )
145  
146      def time_human(self):
147          minutes = int(self.time // 60)
148          seconds = round(self.time % 60, 2)
149          # milliseconds = int((coach_lap_time % 1) * 1000)
150          time_string = ""
151          if minutes > 1:
152              time_string += f"{minutes} minutes "
153          elif minutes == 1:
154              time_string += f"{minutes} minute "
155  
156          time_string += f"{seconds:.2f} seconds "
157  
158          return time_string
159  
160  
161  class FastLapSegment(TimeStampedModel):
162      turn = models.CharField(max_length=200)
163      start = models.IntegerField(default=0)
164      end = models.IntegerField(default=0)
165      brake = models.IntegerField(default=0)
166      turn_in = models.IntegerField(default=0)
167      force = models.IntegerField(default=0)
168      gear = models.IntegerField(default=0)
169      # stop is the time the brake force starts to decrease
170      stop = models.IntegerField(default=0)
171      accelerate = models.IntegerField(default=0)
172      speed = models.IntegerField(default=0)
173      mark = models.CharField(max_length=256, default="")
174  
175      fast_lap = models.ForeignKey(FastLap, on_delete=models.CASCADE, related_name="fast_lap_segments")
176  
177      def __str__(self):
178          repr = f"{self.turn}: {self.start} - {self.end} brake: {self.brake} "
179          repr += f"turn_in: {self.turn_in} force: {self.force} gear: {self.gear} stop: {self.stop} "
180          repr += f"acc: {self.accelerate} speed: {self.speed} mark: {self.mark}"
181          return repr
182  
183  
184  class Coach(ExportModelOperationsMixin("coach"), TimeStampedModel):
185      driver = models.OneToOneField(
186          Driver,
187          on_delete=models.CASCADE,
188          primary_key=True,
189      )
190  
191      error = models.TextField(default="")
192      status = models.TextField(default="")
193      enabled = models.BooleanField(default=False)
194  
195      fast_lap = models.ForeignKey(FastLap, on_delete=models.SET_NULL, related_name="coaches", null=True)
196  
197      MODE_DEFAULT = "default"
198      MODE_TRACK_GUIDE = "track_guide"
199      MODE_TRACK_GUIDE_APP = "track_guide_app"
200      MODE_DEBUG_APP = "debug_app"
201      MODE_DEBUG = "debug"
202      MODE_ONLY_BRAKE = "only_brake"
203      MODE_ONLY_BRAKE_DEBUG = "only_brake_debug"
204      MODE_COPILOTS = "copilots"
205      MODE_CHOICES = [
206          (MODE_DEFAULT, "Default"),
207          (MODE_COPILOTS, "Copilots"),
208          (MODE_TRACK_GUIDE, "Track Guide"),
209          (MODE_TRACK_GUIDE_APP, "Track Guide Application"),
210          (MODE_DEBUG_APP, "Debug Application"),
211          (MODE_DEBUG, "Debug"),
212          (MODE_ONLY_BRAKE, "Only Brakepoints"),
213          (MODE_ONLY_BRAKE_DEBUG, "Only Brakepoints (Debug))"),
214      ]
215      mode = models.CharField(max_length=64, default=MODE_DEFAULT, choices=MODE_CHOICES)
216  
217      def __str__(self):
218          return self.driver.name
219  
220  
221  class Landmark(TimeStampedModel):
222      name = models.CharField(max_length=200)
223      start = models.IntegerField(null=True)
224      end = models.IntegerField(null=True)
225      is_overtaking_spot = models.BooleanField(null=True)
226      from_cc = models.BooleanField(default=False)
227  
228      KIND_MISC = "misc"
229      KIND_SEGMENT = "segment"
230      KIND_TURN = "turn"
231      KIND_CHOICES = [
232          (KIND_MISC, "Misc"),
233          (KIND_SEGMENT, "Segment"),
234          (KIND_TURN, "Turn"),
235      ]
236  
237      kind = models.CharField(max_length=64, default=KIND_MISC, choices=KIND_CHOICES)
238  
239      track = models.ForeignKey(Track, on_delete=models.CASCADE, related_name="landmarks")
240  
241      def __str__(self):
242          return self.name
243  
244  
245  class TrackGuide(TimeStampedModel):
246      name = models.CharField(max_length=200)
247      description = models.TextField(default="")
248      car = models.ForeignKey(Car, on_delete=models.CASCADE)
249      track = models.ForeignKey(Track, on_delete=models.CASCADE)
250  
251      def car_game(self):
252          return self.car.game
253  
254      def __str__(self):
255          return f"{self.name}"
256  
257  
258  class TrackGuideNote(TimeStampedModel):
259      track_guide = models.ForeignKey(TrackGuide, on_delete=models.CASCADE, related_name="notes")
260  
261      landmark = models.ForeignKey(Landmark, on_delete=models.CASCADE, related_name="notes", null=True)
262      segment = models.IntegerField(default=0)
263      finish_at = models.TextField(null=True)
264      at = models.CharField(max_length=64, default="")
265  
266      priority = models.IntegerField(default=0)
267      ref_id = models.CharField(max_length=64, default="")
268      ref_eval = models.CharField(max_length=64, default="")
269  
270      sort_key = models.CharField(max_length=64, default="")
271      mode = models.CharField(max_length=64, default="")
272  
273      message = models.TextField(default="")
274      eval = models.TextField(default="")
275      notes = models.TextField(default="")
276      score = models.CharField(max_length=64, default="")
277  
278      def __str__(self):
279          repr = f"{self.segment or self.landmark} - at {self.at or self.finish_at}: {self.message}"
280          return repr