Exemple #1
0
    def __init__(self, window, direction_str, track_information):
        self.debug = False
        self.terminate = False
        self.track_information = track_information
        window_title = "Utsikt {0}".format(direction_translator[direction_str])
        self.main_direction = Video.direction_map_to_int[direction_str]
        self.viewing_direction = self.main_direction
        print("Main direction is: {0}".format(
            Video.direction_map_to_str[self.main_direction]))
        self.force_update = False
        self.connected = False
        self.reconfiguring = False
        self.speed = 0.0
        self.stopped = True
        self.distance = 0.0
        self.current_section = track_information.start_section
        self.pos_time = None
        self.delay = int(
            1.0 / 30.0
        )  # Mostly we are dealing with 30fps video, so set a default delay based on that
        self.last_frame_time_shown = -1

        self.buttons = {}
        self.button_states = {
            Video.LEFT: False,
            Video.FRONT: False,
            Video.RIGHT: False
        }

        self.sub_client_communicator = SubClientCommunicator(
            args, pos_receiver=self.on_pos_updates)
        self.sub_client_communicator.start()

        # open video source (by default this will try to open the computer webcam)
        self.view_video_caps = {
            Video.LEFT: None,
            Video.FRONT: None,
            Video.RIGHT: None
        }
        self.view_video_caps[self.viewing_direction] = MyVideoCapture(
            self.track_information, self.viewing_direction)

        self.window = window
        self.window.title(window_title)
        self.canvas = None

        self.update()

        if self.debug:
            self.frame_label = None

        self.window.mainloop()
        self.terminate = True
Exemple #2
0
    def __init__(self):
        self.terminate = False
        self.all_rebuses = []

        self.window = Tk()
        self.window.title("Rebusar och fiskben")
        self.layout()

        self.sub_client_communicator = SubClientCommunicator(
            args, status_receiver=self.on_status_updates)
        self.sub_client_communicator.start()

        self.window.mainloop()
        self.terminate = True
        self.sub_client_communicator.stop()
    def __init__(self, args):
        self.client_index = args.client_index
        self.rally_configuration = ClientRallyConfig(args.rally_configuration, args.data_path)
        self.track_information = self.rally_configuration.track_information

        self.window = None
        self.latest_status_information = None
        self.terminate = False

        self.buttons = {}
        self.descriptions = {}

        self.layout()

        self.sub_client_communicator = SubClientCommunicator(args, status_receiver=self.on_status_updates)
Exemple #4
0
    def __init__(self, client_index, track_information):
        self.debug_mode = False
        self.my_client_index = client_index
        self.connected = False
        self.speed = 0.0
        self.distance = 0.0
        self.accumulated_movement = 0.0
        self.track_information = track_information
        self.current_section = track_information.start_section
        self.looking_for_rebus = False
        self.prev_sample = None
        self.rally_is_started = False
        self.afternoon_is_started = False
        self.rally_stage = clientprotocol_pb2.ServerPositionUpdate.NOT_STARTED
        self.current_gas = 0.0
        self.current_brake = 0.0
        self.backup_timeout = None
        self.backing = False
        self.stopped = True
        self.indicator_light = SteeringWindow.NONE
        self.showing_message = False

        self.window = None
        self.speed_variable = None
        self.notice_label = None
        self.speed_indicator_image = None
        self.speed_indicator_label = None
        self.turn_indicator_images = {}
        self.turn_indicator_labels = {}
        self.pedals_image = None
        self.pedals_label = None
        self.backup_image = None
        self.backup_label = None
        self.f_indicators = None
        self.speed_label = None

        self.speed_locked = None

        self.layout()

        self.sub_client_communicator = SubClientCommunicator(args, pos_receiver=self.on_pos_update)
        self.sub_client_communicator.start()
Exemple #5
0
class App:
    def __init__(self, window, direction_str, track_information):
        self.debug = False
        self.terminate = False
        self.track_information = track_information
        window_title = "Utsikt {0}".format(direction_translator[direction_str])
        self.main_direction = Video.direction_map_to_int[direction_str]
        self.viewing_direction = self.main_direction
        print("Main direction is: {0}".format(
            Video.direction_map_to_str[self.main_direction]))
        self.force_update = False
        self.connected = False
        self.reconfiguring = False
        self.speed = 0.0
        self.stopped = True
        self.distance = 0.0
        self.current_section = track_information.start_section
        self.pos_time = None
        self.delay = int(
            1.0 / 30.0
        )  # Mostly we are dealing with 30fps video, so set a default delay based on that
        self.last_frame_time_shown = -1

        self.buttons = {}
        self.button_states = {
            Video.LEFT: False,
            Video.FRONT: False,
            Video.RIGHT: False
        }

        self.sub_client_communicator = SubClientCommunicator(
            args, pos_receiver=self.on_pos_updates)
        self.sub_client_communicator.start()

        # open video source (by default this will try to open the computer webcam)
        self.view_video_caps = {
            Video.LEFT: None,
            Video.FRONT: None,
            Video.RIGHT: None
        }
        self.view_video_caps[self.viewing_direction] = MyVideoCapture(
            self.track_information, self.viewing_direction)

        self.window = window
        self.window.title(window_title)
        self.canvas = None

        self.update()

        if self.debug:
            self.frame_label = None

        self.window.mainloop()
        self.terminate = True

    def current_video_cap(self):
        # TODO: make sure there is an object here!
        return self.view_video_caps[self.viewing_direction]

    def change_video(self):
        self.reconfiguring = True

        # Forget about the videos that aren't shown
        vid_cap = self.current_video_cap()
        self.view_video_caps = {
            Video.LEFT: None,
            Video.FRONT: None,
            Video.RIGHT: None
        }
        self.view_video_caps[self.viewing_direction] = vid_cap

        vid_cap.change_section(self.current_section, self.distance)
        self.force_update = True
        if self.canvas is None:
            # Create a canvas that can fit the above video source size
            self.canvas = tkinter.Canvas(self.window,
                                         width=vid_cap.width,
                                         height=vid_cap.height)
            self.canvas.grid(row=0, column=0, columnspan=3, sticky=tkinter.W)

            self.buttons[Video.LEFT] = tkinter.Button(self.window,
                                                      text="Titta åt vänster")
            self.buttons[Video.LEFT].bind(
                "<ButtonPress-1>", partial(self.video_button_on, Video.LEFT))
            self.buttons[Video.LEFT].bind(
                "<ButtonRelease-1>", partial(self.video_button_off,
                                             Video.LEFT))
            self.buttons[Video.LEFT].bind(
                "<Leave>", partial(self.video_button_off, Video.LEFT))
            self.buttons[Video.LEFT].grid(row=1, column=0, sticky=tkinter.W)

            self.buttons[Video.FRONT] = tkinter.Button(self.window,
                                                       text="Titta framåt")
            self.buttons[Video.FRONT].bind(
                "<ButtonPress-1>", partial(self.video_button_on, Video.FRONT))
            self.buttons[Video.FRONT].bind(
                "<ButtonRelease-1>", partial(self.video_button_off,
                                             Video.FRONT))
            self.buttons[Video.FRONT].bind(
                "<Leave>", partial(self.video_button_off, Video.FRONT))
            self.buttons[Video.FRONT].grid(row=1, column=1)

            self.buttons[Video.RIGHT] = tkinter.Button(self.window,
                                                       text="Titta åt höger")
            self.buttons[Video.RIGHT].bind(
                "<ButtonPress-1>", partial(self.video_button_on, Video.RIGHT))
            self.buttons[Video.RIGHT].bind(
                "<ButtonRelease-1>", partial(self.video_button_off,
                                             Video.RIGHT))
            self.buttons[Video.RIGHT].bind(
                "<Leave>", partial(self.video_button_off, Video.RIGHT))
            self.buttons[Video.RIGHT].grid(row=1, column=2, sticky=tkinter.E)

            self.update_view_buttons()

            if self.debug:
                self.frame_label = tkinter.Label(self.window,
                                                 text="FRAME COUNT")
                self.frame_label.place(x=0, y=0)

        self.reconfiguring = False

    def video_button_on(self, direction, event):
        self.button_states[direction] = True
        self.update_view()

    def video_button_off(self, direction, event):
        self.button_states[direction] = False
        self.update_view()

    def update_view(self):
        direction = self.main_direction
        # Choose the first pressed button to override the default view direction, there really should only be one pressed button...
        for key in self.button_states:
            if self.button_states[key]:
                direction = key
                break

        if self.viewing_direction != direction:
            self.viewing_direction = direction
            if self.view_video_caps[direction] is None:
                self.view_video_caps[direction] = MyVideoCapture(
                    self.track_information, self.viewing_direction)
                self.view_video_caps[direction].change_section(
                    self.current_section, self.distance)
            self.force_update = True

    def update_view_buttons(self):
        if self.terminate:
            return
        global allowed_when_stopped, allowed_when_moving
        section = self.track_information.get_section(self.current_section)
        for direction in self.buttons:
            state = "normal"
            if self.stopped:
                if not allowed_when_stopped[direction]:
                    state = "disabled"
            else:
                if not allowed_when_moving[direction]:
                    state = "disabled"
            if section.get_video(direction) is None:
                state = "disabled"
            self.buttons[direction].config(state=state)

    def on_pos_updates(self, status_information):
        self.pos_time = datetime.datetime.now()
        self.speed = status_information.speed
        self.stopped = status_information.stopped
        self.distance = status_information.distance
        if status_information.force_update:
            self.force_update = True
        if not self.connected:
            self.current_section = status_information.current_section
            self.change_video()
            self.connected = True
            self.force_update = True
        else:
            if self.current_section != status_information.current_section:
                self.current_section = status_information.current_section
                self.change_video()
        self.update_view_buttons()

    def seek(self, vid_cap, video_target_msecs, video_speed):
        # TODO: calculate how far into the future we should seek based on average seek time
        seek_time_ms = 1000
        # How many frame would be shown in one realtime second with the current speed?
        seek_extra_frames = vid_cap.fps * self.speed / video_speed
        # How much time does that correspond to in the video?
        seek_extra_ms = seek_extra_frames / vid_cap.fps
        vid_cap.seek_ms(video_target_msecs + seek_extra_ms)

    def update(self):
        delay = self._update()
        if delay is None or not delay:
            self.window.after(self.delay, self.update)
        else:
            self.window.after(delay, self.update)

    def _update(self):
        # wait for position data to start arriving
        if not self.connected or self.pos_time is None:
            return False
        # Changing video, so don't show anything
        if self.reconfiguring:
            return False

        vid_cap = self.current_video_cap()

        if self.force_update:
            self.last_frame_time_shown = -1

        now = datetime.datetime.now()
        diff = now - self.pos_time
        interpolated_distance = self.distance + self.speed * diff.total_seconds(
        )
        section = self.track_information.get_section(self.current_section)
        video_speed = 50.0 / 3.6
        if section is not None:
            video_speed = section.get_current_video_speed(
                interpolated_distance)
            if interpolated_distance < section.get_start_distance():
                interpolated_distance = section.get_start_distance()
        video_target_secs = self.track_information.get_section(
            self.current_section).calculate_other_video_second_from_distance(
                interpolated_distance, self.viewing_direction)
        video_target_msecs = video_target_secs * 1000

        wait = 1
        if self.speed < 0.0:
            # We are backing, no fancy methods to handle this, simply seek always when we need a new frame
            if abs(video_target_msecs -
                   self.last_frame_time_shown) < vid_cap.one_frame_ms:
                # We are still close to what we last displayed
                ret, frame, frame_time = vid_cap.get_frame(self.force_update)
                wait = vid_cap.one_frame_ms
            else:
                # We are backing and need a new frame, so seek and force the next frame
                vid_cap.seek_ms(video_target_msecs)
                ret, frame, frame_time = vid_cap.get_frame(True)
                wait = 1  # Draw as quickly as possible
        else:
            # Either standing still or moving forward
            if abs(video_target_msecs -
                   self.last_frame_time_shown) < vid_cap.one_frame_ms:
                # We are still close to what we last displayed
                ret, frame, frame_time = vid_cap.get_frame(self.force_update)
                wait = vid_cap.one_frame_ms
            else:
                # We need to show a new frame
                diff_ms = video_target_msecs - self.last_frame_time_shown
                if diff_ms > 0.0:
                    behind = diff_ms
                    # We are behind what we should show
                    if behind > 1000:
                        # We are more than one second behind than what should be shown
                        # Seek to a time that is a bit into the future
                        self.seek(vid_cap, video_target_msecs, video_speed)
                        ret, frame, frame_time = vid_cap.get_frame(True)
                        wait = 1  # Not sure, but should probably try do display again as soon as possible, to find out where we stand
                    else:
                        # Hope that we can catch up with the video soon
                        ret, frame, frame_time = vid_cap.get_frame(True)
                        wait = 1  # We are behind, so don't wait around to show the next frame
                elif diff_ms < 0.0:
                    # We are ahead of what we should show
                    ahead_ms = -diff_ms

                    # How long would it take for the bus to arrive at the same time with the current speed?
                    if abs(self.speed) > 0.01:
                        travel_length = video_speed * ahead_ms / 1000
                        t = travel_length / self.speed
                        t_ms = t * 1000
                    else:
                        t_ms = 10000
                    if t_ms > 3000:
                        # It would take more than three wall clock seconds for the bus to catch up with the
                        # video, so seek and force a frame update
                        self.seek(vid_cap, video_target_msecs, video_speed)
                        ret, frame, frame_time = vid_cap.get_frame(True)
                        wait = 1
                    else:
                        # In not too long we could possibly catch up with the video
                        # TODO: should probably return a new frame here every now and then, to not make the video too jumpy
                        ret, frame, frame_time = vid_cap.get_frame(False)
                        wait = vid_cap.one_frame_ms

        self.force_update = False
        self.last_frame_time_shown = frame_time

        if ret:
            self.photo = PIL.ImageTk.PhotoImage(
                image=PIL.Image.fromarray(frame))
            self.canvas.create_image(0, 0, image=self.photo, anchor=tkinter.NW)
        if self.debug:
            self.frame_label["text"] = str(vid_cap.frame_number)

        return min(int(wait), 100)
Exemple #6
0
        return
    txt = variable.get()
    #print("Photo text changed to {0}".format(txt))
    if txt is None or len(txt) == 0:
        txt = "0"
    txt = txt.strip()
    i = 0
    try:
        i = int(txt)
    except ValueError:
        variable.set("")
        messagebox.showerror("Ogiltligt foto", "Foton måste vara bara siffror")
    send_photo_answer(section, index, i)


sub_client_communicator = SubClientCommunicator(args, status_receiver=on_status_updates)
sub_client_communicator.start()

window = Tk()
window.title("Plockblankett")

messages_text = Text(window, height=5, width=80)
messages_text.insert(END,
                     "Fotoplock och tallriksplock redovisas på denna blankett.\nSkriv tallrikarnas bokstäver i ringarna, och fotonas nummer i rektanglarna.\nSkriv på rätt rad för att visa mellan vilka kontroller ni hittade plocket.\nPlocken skall nedtecknas i rätt ordning.")
messages_text.config(state=DISABLED)
messages_text.grid(row=0, column=0)

FontOfEntryList = tkinter.font.Font(family="Calibri", size=20)

sections = track_information.get_section_titles()
Exemple #7
0
class RebusWindow:
    def __init__(self):
        self.terminate = False
        self.all_rebuses = []

        self.window = Tk()
        self.window.title("Rebusar och fiskben")
        self.layout()

        self.sub_client_communicator = SubClientCommunicator(
            args, status_receiver=self.on_status_updates)
        self.sub_client_communicator.start()

        self.window.mainloop()
        self.terminate = True
        self.sub_client_communicator.stop()

    def layout(self):
        messages_text = Text(self.window, height=5, width=80)
        messages_text.insert(
            END,
            "I detta fönstret dyker rebusar upp när förmiddagen och eftermiddagen börjar,\nsamt när ni hittar rebusar längs med vägen.\n"
            "Om ni inte kan lösa en rebus kan ni \"klippa upp\" en hjälprebus (kostar \n25 prickar) eller lösningen (kostar 45 prickar).\n"
            "Klicka på knapparna för att klippa upp motsvarande hjälp/lösning."
        )
        messages_text.config(state=DISABLED)
        messages_text.grid(row=0, column=0)

        rest = Frame(self.window)
        for section_number in track_information.get_all_section_numbers():
            rebus_frame = Frame(rest, pady=3, padx=3)
            rebus_frame.grid(row=section_number, column=0)
            rebus = Rebus(rebus_frame, section_number, Rebus.NORMAL, self)
            self.all_rebuses.append(rebus)

            help_frame = Frame(rest, bg="black", pady=3, padx=3)
            help_frame.grid(row=section_number, column=1)
            help_rebus = Rebus(help_frame, section_number, Rebus.HELP, self)
            self.all_rebuses.append(help_rebus)

            solution_frame = Frame(rest, bg="black", pady=3, padx=3)
            solution_frame.grid(row=section_number, column=2)
            solution = Rebus(solution_frame, section_number, Rebus.SOLUTION,
                             self)
            self.all_rebuses.append(solution)
        rest.grid(row=1, column=0)

    def get_rebus(self, section, rebus_type):
        for rebus in self.all_rebuses:
            if rebus.section_number == section and rebus.rebus_type == rebus_type:
                return rebus
        return None

    def on_status_updates(self, status_information):
        if self.terminate:
            return
        for rebus_status in status_information.rebus_statuses.rebus_statuses.values(
        ):
            #print("{0}".format(rebus_status))
            for rebus_type in Rebus.rebus_types:
                txt, extra = rebus_status.get_text(rebus_type)
                if txt is None or len(txt) == 0:
                    continue
                if extra is not None:
                    txt = "{0}\n{1}".format(txt, extra)
                if rebus_type == Rebus.SOLUTION:
                    txt = "{0}\n{1}".format(
                        txt,
                        "\nSkriv in detta i GUI:t 'Testa rebuslösning'\n för att få reda på vart ni ska åka!"
                    )
                rebus = self.get_rebus(rebus_status.section, rebus_type)
                if rebus is not None:
                    rebus.set_text(txt)

    def request_a_solution(self, section, rebus_type):
        global args
        client_to_server = clientprotocol_pb2.ClientToServer()
        client_to_server.counter = args.client_index
        client_to_server.open_rebus_solution.SetInParent()
        client_to_server.open_rebus_solution.user_id = args.user_id
        client_to_server.open_rebus_solution.section = section
        if rebus_type == Rebus.HELP:
            client_to_server.open_rebus_solution.open_help = True
        else:
            client_to_server.open_rebus_solution.open_solution = True
        self.sub_client_communicator.send(client_to_server)
Exemple #8
0
class SteeringWindow:
    NONE = 0
    LEFT = 1
    RIGHT = 2

    def __init__(self, client_index, track_information):
        self.debug_mode = False
        self.my_client_index = client_index
        self.connected = False
        self.speed = 0.0
        self.distance = 0.0
        self.accumulated_movement = 0.0
        self.track_information = track_information
        self.current_section = track_information.start_section
        self.looking_for_rebus = False
        self.prev_sample = None
        self.rally_is_started = False
        self.afternoon_is_started = False
        self.rally_stage = clientprotocol_pb2.ServerPositionUpdate.NOT_STARTED
        self.current_gas = 0.0
        self.current_brake = 0.0
        self.backup_timeout = None
        self.backing = False
        self.stopped = True
        self.indicator_light = SteeringWindow.NONE
        self.showing_message = False

        self.window = None
        self.speed_variable = None
        self.notice_label = None
        self.speed_indicator_image = None
        self.speed_indicator_label = None
        self.turn_indicator_images = {}
        self.turn_indicator_labels = {}
        self.pedals_image = None
        self.pedals_label = None
        self.backup_image = None
        self.backup_label = None
        self.f_indicators = None
        self.speed_label = None

        self.speed_locked = None

        self.layout()

        self.sub_client_communicator = SubClientCommunicator(args, pos_receiver=self.on_pos_update)
        self.sub_client_communicator.start()

    def is_locked(self):
        if not self.connected:
            return True, "Programmet har inte fått kontakt med servern än, det kan ta upp till 10 sekunder"
        if not self.rally_is_started:
            return True, "Rallyt har inte startat än"
        if self.rally_stage == clientprotocol_pb2.ServerPositionUpdate.NOT_STARTED:
            return True, "Lös första rebusen innan ni åker iväg"
        if self.rally_stage == clientprotocol_pb2.ServerPositionUpdate.AT_LUNCH:
            if not self.afternoon_is_started:
                return True, "Eftermiddagen har inte startat än"
            return True, "Lös lunchrebusen innan ni åker iväg"
        if self.rally_stage == clientprotocol_pb2.ServerPositionUpdate.AFTERNOON:
            if not self.afternoon_is_started:
                return True, "Eftermiddagen har inte startat än"
        if self.rally_stage == clientprotocol_pb2.ServerPositionUpdate.AT_END or self.rally_stage == clientprotocol_pb2.ServerPositionUpdate.ENDED:
            return True, "Ni har redan gått i mål!"
        return False, None

    def layout(self):
        self.window = Tk()
        self.window.title("Dedicated driver")

        # w = self.image1.width()
        # h = self.image1.height()
        # self.window.geometry('%dx%d+0+0' % (w, h))
        # background_label = Label(self.window, image=self.image1)
        # background_label.place(x=0, y=0, relwidth=1, relheight=1)

        messages_text = Text(self.window, height=5, width=60)
        messages_text.insert(END,
                             "Klicka (och håll) på gas/broms för att köra bussen. Ju högre upp på pedalen, "
                             "desto kraftigare gas/broms.\n"
                             "Klicka på riktningsindikatorerna för att förbereda sväng.\n"
                             "Stanna och klicka på backa-knapparna för att backa."
                             )
        messages_text.config(state=DISABLED)
        messages_text.grid(row=0, column=0)

        self.f_indicators = Frame(self.window)

        self.speed_indicator_image = ImageTk.PhotoImage(Image.open('speed_frame.png'))
        # TODO: frame around the value?
        self.speed_label = Label(self.f_indicators, text="0", font=("Arial Bold", 50), width=3)
        self.speed_variable = StringVar()
        self.speed_label["textvariable"] = self.speed_variable

        self.turn_indicator_images[SteeringWindow.LEFT] = \
            {True: ImageTk.PhotoImage(Image.open('blinkers_left_on_70.png')),
             False: ImageTk.PhotoImage(Image.open('blinkers_left_off_70.png'))}
        self.turn_indicator_images[SteeringWindow.RIGHT] = \
            {True: ImageTk.PhotoImage(Image.open('blinkers_right_on_70.png')),
             False: ImageTk.PhotoImage(Image.open('blinkers_right_off_70.png'))}

        lbl = Label(self.f_indicators, image=self.turn_indicator_images[SteeringWindow.LEFT][False], text="11")
        lbl.bind("<Button-1>", self.left_indicator_clicket)
        lbl.grid(row=0, column=0)
        self.turn_indicator_labels[SteeringWindow.LEFT] = lbl
        self.speed_label.grid(row=0, column=1)
        lbl = Label(self.f_indicators, image=self.turn_indicator_images[SteeringWindow.RIGHT][False])
        lbl.bind("<Button-1>", self.right_indicator_clicket)
        lbl.grid(row=0, column=2)
        self.turn_indicator_labels[SteeringWindow.RIGHT] = lbl

        self.f_indicators.grid(row=1, column=0)

        self.pedals_image = ImageTk.PhotoImage(Image.open('gas_and_brake.png'))
        self.pedals_label = Label(self.window, image=self.pedals_image)
        self.pedals_label.grid(row=2, column=0)
        # https://effbot.org/tkinterbook/tkinter-events-and-bindings.htm
        self.pedals_label.bind("<Button-1>", self.mouse_event)
        self.pedals_label.bind("<B1-Motion>", self.mouse_event)
        self.pedals_label.bind("<ButtonRelease-1>", self.release_mouse)

        self.backup_image = ImageTk.PhotoImage(Image.open('backup.png'))
        self.backup_label = Label(self.window, image=self.backup_image)
        self.backup_label.grid(row=3, column=0)
        self.backup_label.bind("<Button-1>", self.backup_clicked)

        self.notice_label = Label(self.window, text="", font=("Arial Bold", 30), background="blue")

        if self.debug_mode:
            button = Button(self.window, text="15")
            button.bind("<ButtonPress-1>", partial(self.fifty_button_on, 15))
            button.bind("<ButtonRelease-1>", self.fifty_button_off)
            button.bind("<Leave>", self.fifty_button_off)
            button.grid(row=4, column=0)

            button = Button(self.window, text="30")
            button.bind("<ButtonPress-1>", partial(self.fifty_button_on, 30))
            button.bind("<ButtonRelease-1>", self.fifty_button_off)
            button.bind("<Leave>", self.fifty_button_off)
            button.grid(row=4, column=1)

            button = Button(self.window, text="50")
            button.bind("<ButtonPress-1>", partial(self.fifty_button_on, 50))
            button.bind("<ButtonRelease-1>", self.fifty_button_off)
            button.bind("<Leave>", self.fifty_button_off)
            button.grid(row=4, column=2)

    def fifty_button_on(self, speed, event):
        self.speed = speed / 3.6
        self.speed_locked = speed / 3.6

    def fifty_button_off(self, event):
        self.speed = 0.0
        self.speed_locked = None

    def run(self):
        self.window.after(1, self.update_speed)
        self.window.after(100, self.send_to_client)
        self.window.mainloop()
        self.sub_client_communicator.stop()

    def on_pos_update(self, status_information):
        self.stopped = status_information.stopped or status_information.looking_for_rebus
        self.distance = status_information.distance

        if status_information.looking_for_rebus != self.looking_for_rebus:
            self.looking_for_rebus = status_information.looking_for_rebus

        self.rally_is_started = status_information.rally_is_started
        self.afternoon_is_started = status_information.afternoon_is_started
        self.rally_stage = status_information.rally_stage

        if not self.connected:
            self.current_section = status_information.current_section
            self.connected = True
        else:
            if self.current_section != status_information.current_section:
                # New section!
                self.accumulated_movement = 0.0
                self.indicator_light = SteeringWindow.NONE
                self.current_section = status_information.current_section
                self.update_turn_indicators()

        section = self.track_information.get_section(self.current_section)

        notice = None
        if self.looking_for_rebus:
            notice = "Letar efter rebus!"
            pos = [80, 250]
        elif self.backing:
            notice = "Backar..."
            pos = [140, 250]
        elif status_information.driving_message is not None and len(status_information.driving_message) > 0:
            notice = status_information.driving_message
            pos = [50, 300]
        if notice is not None:
            self.notice_label["text"] = notice
            self.notice_label.place(x=pos[0], y=pos[1])
        else:
            if self.notice_label is not None:
                self.notice_label.place_forget()

    def send_to_client(self):
        if not self.connected:
            self.window.after(1000, self.send_to_client)
            return
        client_to_server = clientprotocol_pb2.ClientToServer()
        client_to_server.counter = self.my_client_index
        client_to_server.pos_update.SetInParent()
        client_to_server.pos_update.speed = self.speed
        client_to_server.pos_update.delta_distance = self.accumulated_movement
        self.accumulated_movement = 0.0
        client_to_server.pos_update.current_section = self.current_section
        client_to_server.pos_update.indicator = self.indicator_light
        self.sub_client_communicator.send(client_to_server)
        self.window.after(1000, self.send_to_client)

    def brake_pedal(self, event, button):
        if button:
            #percent = (1.0 - (event.y - 535) / (790 - 535))
            percent = (1.0 - (event.y - 38) / (155 - 38))
        else:
            percent = 0
        self.current_brake = percent

    def gas_pedal(self, event, button):
        if button:
            locked, message = self.is_locked()
            if not locked:
                #percent = (1.0 - (event.y - 535) / (790 - 535))
                percent = (1.0 - event.y / 161)
            else:
                percent = 0
                self.show_drive_error(message)
        else:
            percent = 0
        self.current_gas = percent

    def show_drive_error(self, message):
        if self.showing_message:
            return
        self.window.lower()
        self.showing_message = True
        messagebox.showerror("Kan inte köra",
                             message,
                             parent=self.window)
        self.showing_message = False

    def mouse_event(self, event):
        #print(event.x, event.y)
        if 64 <= event.x <= 178 and 38 <= event.y <= 155:
            self.brake_pedal(event, True)
            self.gas_pedal(event, False)
        elif 277 <= event.x <= 379 and 0 <= event.y <= 161:
            self.gas_pedal(event, True)
            self.brake_pedal(event, False)
        else:
            self.brake_pedal(event, False)
            self.gas_pedal(event, False)

    def left_indicator_clicket(self, event):
        self.indicator_clicked(SteeringWindow.LEFT)

    def right_indicator_clicket(self, event):
        self.indicator_clicked(SteeringWindow.RIGHT)

    def indicator_clicked(self, which):
        if self.indicator_light == which:
            self.indicator_light = SteeringWindow.NONE
        else:
            self.indicator_light = which
        self.update_turn_indicators()

    def update_turn_indicators(self):
        print("Turn indicator: {0}".format(self.indicator_light))
        self.turn_indicator_labels[SteeringWindow.LEFT]["image"] = \
            self.turn_indicator_images[SteeringWindow.LEFT][self.indicator_light == SteeringWindow.LEFT]
        self.turn_indicator_labels[SteeringWindow.RIGHT]["image"] = \
            self.turn_indicator_images[SteeringWindow.RIGHT][self.indicator_light == SteeringWindow.RIGHT]


    def backup_clicked(self, event):
        #print(event.x, event.y)
        if 5 <= event.x <= 85 and 90 <= event.y <= 150:
            self.backup(10)
        if 115 <= event.x <= 200 and 64 <= event.y <= 150:
            self.backup(50)
        if 225 <= event.x <= 310 and 30 <= event.y <= 150:
            self.backup(100)
        if 340 <= event.x <= 420 and 5 <= event.y <= 150:
            self.backup(300)

    def backup(self, amount):
        locked, message = self.is_locked()
        if locked:
            self.show_drive_error(message)
            return

        now = datetime.datetime.now()
        if self.backup_timeout is not None:
            if now < self.backup_timeout:
                print("Unable to back more just yet, still waiting for the latest command to finish")
                return
        if not self.stopped:
            if self.showing_message:
                return
            self.window.lower()
            self.showing_message = True
            messagebox.showerror("Kan inte backa",
                                 "Du måste stå stilla innan du kan backa.",
                                 parent=self.window)
            self.showing_message = False
            return

        self.backing = True
        self.speed = -50/3.6
        secs_per_meter = 3.6/50
        secs = secs_per_meter * amount
        #print("Wait for {0} seconds".format(secs))
        self.backup_timeout = now + datetime.timedelta(seconds=secs)

    def release_mouse(self, event):
        self.brake_pedal(event, False)
        self.gas_pedal(event, False)

    def update_speed(self):
        if self.looking_for_rebus or not self.connected:
            self.window.after(100, self.update_speed)
            return

        now = datetime.datetime.now()
        if self.prev_sample is not None:
            diff = now - self.prev_sample
            secs = diff.total_seconds()
            self.accumulated_movement += self.speed * secs

            # if self.distance < 0.0:
            #     self.distance = 0.0
            # if section is not None:
            #     if self.distance < section.get_start_distance():
            #         self.distance = section.get_start_distance()
            #         if self.backing:
            #             self.backing = False
            #             self.speed = 0
            #             self.backup_timeout = None
        self.prev_sample = now

        interpolated_distance = self.distance + self.accumulated_movement
        if interpolated_distance < 0.0:
            # 0.0 is the start frame of the video (start_offset_frames into the video)
            interpolated_distance = 0.0
            if self.backing:
                self.backing = False
                self.speed = 0
                self.backup_timeout = None

        # section = self.track_information.get_section(self.current_section)
        # if section.missed_all_turns(interpolated_distance):
        #     self.driven_too_far = True

        if self.backing:
            if self.backup_timeout is not None:
                if now >= self.backup_timeout:
                    self.backing = False
                    self.speed = 0
                    self.backup_timeout = None
                #self.distance += self.speed * diff
            self.speed_variable.set(str(int(self.speed * 3.6)))
            self.window.after(100, self.update_speed)
            return

        drag_constant = 0.02  # Might mean that max speed is about 150km/h
        car_mass = 2500
        max_car_gas_force = 745 * 120  # 120hp
        max_car_brake_force = 745 * 300  # Might be a strange way to calculate brake force, but why not try...
        current_force = 0
        if self.current_gas > 0:
            current_force = self.current_gas * max_car_gas_force
        elif self.current_brake > 0:
            current_force = -(self.current_brake * max_car_brake_force)
        acc = current_force / car_mass
        drag_force = self.speed * self.speed * drag_constant
        drag_acc = drag_force  # / car_mass

        delta = (acc - drag_acc) * 0.1 / 8  # 0.1sec, but the acceleration was too quick for some reason, so divide by 10
        self.speed += delta

        # generellt motstånd
        self.speed -= 0.1
        if self.speed < 0:
            self.speed = 0
        # Max speed of 75 km/h just to help reduce video bugs
        if self.speed * 3.6 > 75.0:
            self.speed = 75.0 / 3.6

        if self.speed_locked is not None:
            self.speed = self.speed_locked

        self.speed_variable.set(str(int(self.speed * 3.6)))

        self.window.after(100, self.update_speed)
class ExtraPuzzles:
    def __init__(self, args):
        self.client_index = args.client_index
        self.rally_configuration = ClientRallyConfig(args.rally_configuration, args.data_path)
        self.track_information = self.rally_configuration.track_information

        self.window = None
        self.latest_status_information = None
        self.terminate = False

        self.buttons = {}
        self.descriptions = {}

        self.layout()

        self.sub_client_communicator = SubClientCommunicator(args, status_receiver=self.on_status_updates)

    def run(self):
        self.sub_client_communicator.start()
        self.window.mainloop()
        self.terminate = True
        self.sub_client_communicator.stop()

    def layout(self):
        self.window = Tk()
        self.window.title("Extrauppgifter")

        messages_text = Text(self.window, height=2, width=80)
        messages_text.insert(END,
                             "Här hittar ni extrauppgifter som ni själva kan välja om ni vill ta er an.\n"
                             "När ni 'öppnar' en extrauppgift får ni en URL där uppgiften kan hämtas.\n"
                             )
        messages_text.config(state=DISABLED)
        messages_text.grid(row=0, column=0)

        title_font = tkinter.font.Font(family="Calibri", size=16)

        row = 1
        for extra_puzzle in self.rally_configuration.extra_puzzles.values():
            f = Frame(self.window)

            title_label = Label(f, text=extra_puzzle.title, font=title_font)
            title_label.grid(row=0, column=0, sticky=W)

            f2 = Frame(f)
            description = Text(f2, height=2, width=70)
            description.insert(END, extra_puzzle.description)
            description.config(state=DISABLED)
            description.grid(row=0, column=0, sticky=W)
            self.descriptions[extra_puzzle.id] = description
            open_task_button = Button(f2, text="Öppna", font=title_font, command=partial(self.on_open_puzzle, extra_puzzle))
            open_task_button.grid(row=0, column=1, sticky=W)
            self.buttons[extra_puzzle.id] = open_task_button

            f2.grid(row=1, column=0, sticky=W)

            f.grid(row=row, column=0, sticky=W)
            row += 1

    def on_status_updates(self, status_information):
        if self.terminate:
            return
        self.latest_status_information = status_information
        self.update_gui()

    def update_gui(self):
        if self.terminate:
            return
        if self.latest_status_information is None:
            return
        for puzzle_id in self.latest_status_information.extra_puzzles:
            instructions = self.latest_status_information.extra_puzzles[puzzle_id]
            if puzzle_id in self.buttons:
                button = self.buttons[puzzle_id]
                button["state"] = "disabled"
            if puzzle_id in self.descriptions:
                description_obj = self.descriptions[puzzle_id]
                current_text = description_obj.get(1.0, END)
                if current_text.strip() != instructions.strip():
                    description_obj.config(state=NORMAL)
                    description_obj.delete("1.0", END)
                    description_obj.insert(END, instructions)
                    description_obj.config(state=DISABLED)

    def on_open_puzzle(self, extra_puzzle):
        if messagebox.askyesno("Öppna pyssel?", extra_puzzle.question, default="no"):
            self.send_request_to_open(extra_puzzle)

    def send_request_to_open(self, extra_puzzle):
        client_to_server = clientprotocol_pb2.ClientToServer()
        client_to_server.counter = self.client_index
        client_to_server.open_extra_puzzle.SetInParent()
        client_to_server.open_extra_puzzle.puzzle_id = extra_puzzle.id
        self.sub_client_communicator.send(client_to_server)