示例#1
0
class Window(Frame):
    def __init__(self, master=None):
        if not headless:
            Frame.__init__(self, master)
            self.master = master
            self.init_window()

    def settrainer(self, n):
        global power_curve, user_defaults
        user_defaults["power_curve"] = n
        power_curve = n
        if n == "power_calc_factors_imagic.txt":
            self.PowerCurveVariable.set("I-Magic")
        elif n == "power_calc_factors_fortius.txt":
            self.PowerCurveVariable.set("Fortius")
        elif n == "power_calc_factors_custom.txt":
            self.PowerCurveVariable.set("Custom")

    def init_window(self):
        global user_defaults
        self.grid()

        # Setup menu content###

        self.master.title("Antifier v0.9")
        self.master.option_add("*tearOff", False)

        # allowing the widget to take the full space of the root window
        self.pack(fill=BOTH, expand=1)

        # creating a menu instance
        menu = Menu(self.master)
        self.master.config(menu=menu)

        # create the Setup object)
        Setup = Menu(menu)

        # add commands to the Setup option
        Setup.add_command(label="Head Unit", command=self.HeadUnit_window)

        subSetup = Menu(Setup)
        subSetup.add_command(
            label="iMagic",
            command=lambda p="power_calc_factors_imagic.txt": self.settrainer(
                p),
        )
        subSetup.add_command(
            label="Fortius",
            command=lambda p="power_calc_factors_fortius.txt": self.settrainer(
                p),
        )
        subSetup.add_command(
            label="Custom Curve",
            command=lambda p="power_calc_factors_custom.txt": self.settrainer(
                p),
        )

        Setup.add_cascade(label="Power Curve", menu=subSetup)

        Setup.add_separator()
        Setup.add_command(label="Exit", command=self.EXITbutton)

        # added "Setup" to our menu
        menu.add_cascade(label="Setup", menu=Setup)

        # create the Options object)
        Options = Menu(menu)

        # add commands to the Options option
        Options.add_command(label="Debug", command=self.DebugButton)
        Options.add_command(label="Simulate Trainer",
                            command=self.Simulatebutton)
        Options.add_command(label="Power Factor",
                            command=self.PowerFactor_Window)

        # added "Options" to our menu
        menu.add_cascade(label="Options", menu=Options)

        # create the Help object
        Help = Menu(menu)

        # adds a command to the Help option.
        Help.add_command(label="Readme", command=self.Readme)
        Help.add_command(label="Zwift shortcuts", command=self.Zwift_shortcuts)

        # added "Help" to our menu
        menu.add_cascade(label="Help", menu=Help)

        # Setup GUI buttons and labels###

        self.FindHWbutton = Button(self,
                                   height=1,
                                   width=15,
                                   text=u"1. Locate HW",
                                   command=self.ScanForHW)
        self.FindHWbutton.grid(column=0, row=0)

        label = Label(self, height=1, width=10, text="Head Unit")
        label.grid(column=0, row=1, sticky="EW")
        self.trainerVariable = StringVar()
        label = Label(self,
                      textvariable=self.trainerVariable,
                      anchor="w",
                      fg="black",
                      bg="grey")
        label.grid(column=1, row=1, columnspan=2, sticky="EW")

        label = Label(self, height=1, width=10, text="Power curve")
        label.grid(column=0, row=2, sticky="EW")
        self.PowerCurveVariable = StringVar()
        label = Label(
            self,
            textvariable=self.PowerCurveVariable,
            anchor="w",
            fg="black",
            bg="grey",
        )
        label.grid(column=1, row=2, columnspan=2, sticky="EW")
        if "power_curve" in user_defaults:
            self.settrainer(user_defaults["power_curve"])

        label = Label(self, height=1, width=10, text="ANT+")
        label.grid(column=0, row=3, sticky="EW")
        self.ANTVariable = StringVar()
        label = Label(self,
                      textvariable=self.ANTVariable,
                      anchor="w",
                      fg="black",
                      bg="grey")
        label.grid(column=1, row=3, columnspan=2, sticky="EW")

        label = Label(self, text="Power factor")
        label.grid(column=0, row=4, sticky="EW")
        self.PowerFactorVariable = StringVar()
        label = Label(
            self,
            textvariable=self.PowerFactorVariable,
            anchor="w",
            fg="black",
            bg="grey",
        )
        label.grid(column=1, row=4, columnspan=2, sticky="EW")

        self.RunoffButton = Button(self,
                                   height=1,
                                   width=15,
                                   text=u"2. Perform Runoff",
                                   command=self.Runoff)
        self.RunoffButton.grid(column=0, row=5)
        self.RunoffButton.config(state="disabled")
        self.runoffVariable = StringVar()
        label = Label(
            self,
            textvariable=self.runoffVariable,
            anchor="w",
            fg="black",
            bg="grey",
            width=40,
        )
        label.grid(column=1, row=5, columnspan=2, sticky="EW")

        self.StartAPPbutton = Button(self,
                                     height=1,
                                     width=15,
                                     text=u"3. Start script",
                                     command=self.Start)
        self.StartAPPbutton.grid(column=0, row=6)
        self.StartAPPbutton.config(state="disabled")

        self.StopAPPbutton = Button(
            self,
            height=1,
            width=15,
            text=u"Stop script",
            command=self.Stop,
            state="disabled",
        )
        self.StopAPPbutton.grid(column=1, row=6)

        label = Label(self, text="Speed")
        label.grid(column=0, row=7, sticky="EW")

        self.SpeedVariable = StringVar()
        label = Label(self,
                      textvariable=self.SpeedVariable,
                      anchor="w",
                      fg="black",
                      bg="grey")
        label.grid(column=1, row=7, columnspan=2, sticky="EW")
        self.SpeedVariable.set(u"0")

        label = Label(self, text="Heartrate")
        label.grid(column=0, row=8, sticky="EW")
        self.HeartrateVariable = StringVar()
        label = Label(self,
                      textvariable=self.HeartrateVariable,
                      anchor="w",
                      fg="black",
                      bg="grey")
        label.grid(column=1, row=8, columnspan=2, sticky="EW")
        self.HeartrateVariable.set(u"0")

        label = Label(self, text="Cadence")
        label.grid(column=0, row=9, sticky="EW")

        self.CadenceVariable = StringVar()
        label = Label(self,
                      textvariable=self.CadenceVariable,
                      anchor="w",
                      fg="black",
                      bg="grey")
        label.grid(column=1, row=9, columnspan=2, sticky="EW")
        self.CadenceVariable.set(u"0")

        label = Label(self, text="Power")
        label.grid(column=0, row=10, sticky="EW")
        self.PowerVariable = StringVar()
        label = Label(self,
                      textvariable=self.PowerVariable,
                      anchor="w",
                      fg="black",
                      bg="grey")
        label.grid(column=1, row=10, columnspan=2, sticky="EW")
        self.PowerVariable.set(u"0")

        label = Label(self, text="Slope")
        label.grid(column=0, row=11, sticky="EW")
        self.SlopeVariable = StringVar()
        label = Label(self,
                      textvariable=self.SlopeVariable,
                      anchor="w",
                      fg="black",
                      bg="grey")
        label.grid(column=1, row=11, columnspan=2, sticky="EW")

        label = Label(self, text="Target Power")
        label.grid(column=0, row=12, sticky="EW")
        self.TargetPowerVariable = StringVar()
        label = Label(
            self,
            textvariable=self.TargetPowerVariable,
            anchor="w",
            fg="black",
            bg="grey",
        )
        label.grid(column=1, row=12, columnspan=2, sticky="EW")

        label = Label(self, text="Resistance Level")
        label.grid(column=0, row=13, sticky="EW")
        self.ResistanceLevelVariable = StringVar()
        label = Label(
            self,
            textvariable=self.ResistanceLevelVariable,
            anchor="w",
            fg="black",
            bg="grey",
        )
        label.grid(column=1, row=13, columnspan=2, sticky="EW")
        self.ResistanceLevelVariable.set(u"0")

    def PowerFactor_Window(self):
        self.PowerFactor_Window = Toplevel(self.master)
        self.app = PowerFactor_Window(self.PowerFactor_Window)

    def HeadUnit_window(self):
        self.HeadUnitWindow = Toplevel(self.master)
        self.app = HeadUnit_Window(self.HeadUnitWindow)

    def Runoff(self):
        global runoff_loop_running

        def run():
            global dev_trainer, runoff_loop_running
            rolldown = False
            rolldown_time = 0
            speed = 0
            # self.InstructionsVariable.set('''
            # CALIBRATION TIPS:
            # 1. Tyre pressure 100psi (unloaded and cold) aim for 7.2s rolloff
            # 2. Warm up for 2 mins, then cycle 30kph-40kph for 30s
            # 3. Speed up to above 40kph then stop pedalling and freewheel
            # 4. Rolldown timer will start automatically when you hit 40kph, so stop pedalling quickly!
            # ''')

            while runoff_loop_running:  # loop every 100ms
                last_measured_time = time.time() * 1000
                # receive data from trainer
                speed, pedecho, heart_rate, force_index, cadence = trainer.receive(
                    dev_trainer)  # get data from device
                self.SpeedVariable.set(speed)
                if speed == "Not found":
                    self.TrainerStatusVariable.set(
                        "Check trainer is powered on")

                # send data to trainer
                resistance_level = 6
                trainer.send(dev_trainer, resistance_level, pedecho)

                if speed > 40:  # speed above 40, start rolldown
                    self.runoffVariable.set(
                        "Rolldown timer started - STOP PEDALLING!")
                    rolldown = True

                if speed <= 40 and rolldown:  # rolldown timer starts when dips below 40
                    if rolldown_time == 0:
                        rolldown_time = time.time(
                        )  # set initial rolldown time
                    self.runoffVariable.set(
                        "Rolldown timer started - STOP PEDALLING! %s " %
                        (round((time.time() - rolldown_time), 1)))

                if speed < 0.1 and rolldown:  # wheel stopped
                    runoff_loop_running = False  # break loop
                    self.runoffVariable.set(
                        "Rolldown time = %s seconds (aim 7s)" % round(
                            (time.time() - rolldown_time), 1))

                time_to_process_loop = time.time() * 1000 - last_measured_time
                sleep_time = 0.1 - (time_to_process_loop) / 1000
                if sleep_time < 0:
                    sleep_time = 0
                time.sleep(sleep_time)

            self.RunoffButton.config(
                text="2. Perform Runoff")  # reset runoff button
            self.StartAPPbutton.config(state="normal")

        if self.RunoffButton.cget(
                "text") == "2. Perform Runoff":  # start runoff
            self.runoffVariable.set("Cycle to above 40kph then stop")
            self.RunoffButton.config(text="2. Stop Runoff")
            self.StartAPPbutton.config(state="disabled")
            runoff_loop_running = True  # loop switch
            t1 = threading.Thread(target=run)
            t1.start()
        else:  # stop loop
            runoff_loop_running = False
            self.runoffVariable.set("Stopped")
            self.RunoffButton.config(text="2. Perform Runoff")
            self.StartAPPbutton.config(state="normal")

    def Readme(self):
        os.startfile("README.txt")

    def Zwift_shortcuts(self):
        os.startfile("Zwift_shortcuts.txt")

    def EXITbutton(self):
        self.destroy()
        exit()

    def DebugButton(self):
        global debug
        if debug:
            debug = True
        else:
            debug = False

    def Simulatebutton(self):
        global simulatetrainer
        simulatetrainer = True

    def Stop(self):
        global switch
        switch = False
        self.StartAPPbutton.config(state="normal")
        self.StopAPPbutton.config(state="disabled")

    def ScanForHW(self):
        global dev_trainer, dev_ant, simulatetrainer
        # get ant stick
        if debug:
            print("get ant stick")
        if not dev_ant:
            dev_ant, msg = ant.get_ant(debug)
            if not dev_ant:
                if not headless:
                    self.ANTVariable.set(msg)
                return False
        if not headless:
            self.ANTVariable.set(msg)

        if not headless:
            self.PowerFactorVariable.set(powerfactor)
        if debug:
            print("get trainer")
        # find trainer model for Windows and Linux
        if not dev_trainer:
            # find trainer
            if simulatetrainer:
                if not headless:
                    self.trainerVariable.set(u"Simulated Trainer")
                else:
                    print("Simulated Trainer")
            else:
                dev_trainer = trainer.get_trainer()
                if not dev_trainer:
                    if not headless:
                        self.trainerVariable.set("Trainer not detected")
                    else:
                        print("Trainer not detected")
                    return False
                else:
                    if not headless:
                        self.trainerVariable.set("Trainer detected")
                    else:
                        print("Trainer detected")
                    trainer.initialise_trainer(
                        dev_trainer)  # initialise trainer

        if not headless:
            self.StartAPPbutton.config(state="normal")
            if not simulatetrainer:
                self.RunoffButton.config(state="normal")
            self.FindHWbutton.config(state="disabled")

        return True

    def Start(self):
        def run():
            global dev_ant, dev_trainer, simulatetrainer, switch, power_curve
            if power_curve == "":
                if not headless:
                    self.PowerCurveVariable.set(
                        "Choose a power curve under setup menu")
                    self.StartAPPbutton.config(state="normal")
                    self.StopAPPbutton.config(state="disabled")
                return
            pc_dict = trainer.parse_factors(
                power_curve)  # get power curve dictionary
            if len(pc_dict) != 14:
                if not headless:
                    self.PowerCurveVariable.set(
                        "Need 14 levels for power curve")
                    self.StartAPPbutton.config(state="normal")
                    self.StopAPPbutton.config(state="disabled")
                    return
            pc_sorted_keys = sorted(pc_dict.iterkeys())  # -1,-0,2,3 etc.
            if debug:
                print("reset ant stick")
            ant.antreset(dev_ant, debug)  # reset dongle
            if debug:
                print("calibrate ant stick")
            ant.calibrate(dev_ant, debug)  # calibrate ANT+ dongle
            if debug:
                print("calibrate ant stick FE-C")
            ant.master_channel_config(dev_ant,
                                      debug)  # calibrate ANT+ channel FE-C
            if debug:
                print("calibrate ant stick HR")
            ant.second_channel_config(dev_ant,
                                      debug)  # calibrate ANT+ channel HR

            if not headless:
                self.RunoffButton.config(state="disabled")
            else:
                print("Ctrl-C to exit")
            speed, cadence, power, heart_rate = (0, ) * 4  # initialise values
            grade = False
            target_power = False
            accumulated_power = 0
            heart_beat_event_time = time.time() * 1000
            heart_beat_event_time_start_cycle = time.time() * 1000
            heart_toggle = 0
            heart_beat_count = 0
            switch = True
            cot_start = time.time()
            eventcounter = 0
            # p.44 [10] general fe data, [19] eqpt type trainer, [89] acc value time since start in 0.25s r/over 64s, [8c] acc value time dist travelled in m r/over 256m,
            # [8d] [20] speed lsb msb 0.001m/s, [00] hr, [30] capabilities bit field
            accumulated_time = time.time() * 1000
            distance_travelled = 0
            last_dist_time = time.time() * 1000

            # p.60 [19] specific trainer data, [10] counter rollover 256, [5a] inst cadence, [b0] acc power lsb, [47] acc power msb (r/over 65536W), [1b] inst power lsb,
            # [01] bits 0-3 inst power MSB bits 4-7 trainer status bit, [30] flags bit field
            last_measured_time = time.time() * 1000
            try:
                while switch:
                    if debug:
                        print("Running",
                              round(time.time() * 1000 - last_measured_time))
                    last_measured_time = time.time() * 1000
                    if eventcounter >= 256:
                        eventcounter = 0
                    # TRAINER- SHOULD WRITE THEN READ 70MS LATER REALLY
                    # ##################GET DATA FROM TRAINER##################
                    if simulatetrainer:
                        speed, pedecho, heart_rate, force_index, cadence = (
                            20,
                            0,
                            70,
                            5,
                            90,
                        )
                    else:
                        speed, pedecho, heart_rate, force_index, cadence = trainer.receive(
                            dev_trainer)  # get data from device
                    if speed == "Not Found":
                        speed, pedecho, heart_rate, force_index, cadence = 0, 0, 0, 0, 0
                        if not headless:
                            self.trainerVariable.set(
                                "Cannot read from trainer")
                        else:
                            print("Cannot read from trainer")
                    else:
                        if not headless:
                            self.trainerVariable.set("Trainer detected")
                    # print force_index
                    factors = pc_dict[pc_sorted_keys[force_index]]
                    calc_power = int(speed * factors[0] + factors[1])
                    if calc_power < 0:
                        calc_power = 0
                    if debug:
                        print(speed, pedecho, heart_rate, force_index, cadence,
                              calc_power)
                    # ##################SEND DATA TO TRAINER##################
                    # send resistance data to trainer
                    if debug:
                        print(
                            datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S.%f")
                            [:-3], "GRADE", grade, "%")
                    # set resistance level
                    if not grade and not target_power:  # if trainer not been been set a grade or target power
                        grade = 0
                    resistance_level = len(
                        pc_dict) - 1  # set to highest by default
                    if grade is not False:  # find resistance for grade
                        for idx, g in enumerate(sorted(pc_dict)):
                            if g >= grade:  # find resistance value immediately above grade set by zwift
                                resistance_level = idx
                                break
                    elif target_power:  # get resistance closest for power target
                        if speed < 10:
                            speed = 10  # default to at least 10 kph
                        closest = 1000
                        for idx, g in enumerate(sorted(pc_dict)):  # iterate up
                            power_at_level = int(speed * pc_dict[g][0] +
                                                 pc_dict[g][1])
                            # print idx,g,power_at_level
                            if (target_power - power_at_level)**2 < closest**2:
                                resistance_level = idx
                                closest = ((target_power -
                                            power_at_level)**2)**0.5
                    # print resistance_level
                    if not simulatetrainer:
                        trainer.send(dev_trainer, resistance_level, pedecho)

                        # time.sleep(0.2)#simulated trainer timeout
                    # ############BROADCAST AND RECEIVE ANT+ data##############
                    if speed == "Not Found":
                        speed, pedecho, calc_power, cadence = 0, 0, 0, 0
                    if calc_power >= 4094:
                        calc_power = 4093
                    accumulated_power += calc_power
                    if accumulated_power >= 65536:
                        accumulated_power = 0

                    if (
                            eventcounter + 1
                    ) % 66 == 0 or eventcounter % 66 == 0:  # send first and second manufacturer's info packet
                        newdata = "a4 09 4e 00 50 ff ff 01 0f 00 85 83 bb 00 00"

                    elif (eventcounter + 32) % 66 == 0 or (
                            eventcounter + 33
                    ) % 66 == 0:  # send first and second product info packet
                        newdata = "a4 09 4e 00 51 ff ff 01 01 00 00 00 b2 00 00"

                    elif eventcounter % 3 == 0:  # send general fe data every 3 packets
                        accumulated_time_counter = int(
                            (time.time() * 1000 - accumulated_time) / 1000 /
                            0.25)  # time since start in 0.25 seconds
                        if (accumulated_time_counter >= 256
                            ):  # rollover at 64 seconds (256 quarter secs)
                            accumulated_time_counter = 0
                            accumulated_time = time.time() * 1000
                        newdata = "{0}{1}{2}".format(
                            "a4 09 4e 00 10 19 ",
                            hex(accumulated_time_counter)[2:].zfill(2),
                            " 8c 8d 20 00 30 72 00 00",
                        )  # set time
                        distance_travelled_since_last_loop = (
                            (time.time() * 1000 - last_dist_time) / 1000 *
                            speed * 1000 / 3600
                        )  # speed reported in kph- convert to m/s
                        last_dist_time = time.time(
                        ) * 1000  # reset last loop time
                        distance_travelled += distance_travelled_since_last_loop
                        if distance_travelled >= 256:  # reset at 256m
                            distance_travelled = 0
                        newdata = "{0}{1}{2}".format(
                            newdata[:21],
                            hex(int(distance_travelled))[2:].zfill(2),
                            newdata[23:],
                        )  # set distance travelled
                        hexspeed = hex(int(speed * 1000 * 1000 /
                                           3600))[2:].zfill(4)
                        newdata = "{0}{1}{2}{3}{4}".format(
                            newdata[:24], hexspeed[2:], " ", hexspeed[:2],
                            newdata[29:])  # set speed
                        newdata = "{0}{1}{2}".format(
                            newdata[:36], ant.calc_checksum(newdata),
                            newdata[38:])  # recalculate checksum

                    else:  # send specific trainer data
                        newdata = "{0}{1}{2}".format(
                            "a4 09 4e 00 19 ",
                            hex(eventcounter)[2:].zfill(2),
                            " 5a b0 47 1b 01 30 6d 00 00",
                        )  # increment event count
                        if cadence >= 254:
                            cadence = 253
                        newdata = "{0}{1}{2}".format(
                            newdata[:18],
                            hex(cadence)[2:].zfill(2),
                            newdata[20:])  # instant cadence
                        hexaccumulated_power = hex(
                            int(accumulated_power))[2:].zfill(4)
                        newdata = "{0}{1}{2}{3}{4}".format(
                            newdata[:21],
                            hexaccumulated_power[2:],
                            " ",
                            hexaccumulated_power[:2],
                            newdata[26:],
                        )  # set accumulated power
                        hexinstant_power = hex(int(calc_power))[2:].zfill(4)
                        hexinstant_power_lsb = hexinstant_power[2:]
                        newdata = "{0}{1}{2}".format(
                            newdata[:27], hexinstant_power_lsb,
                            newdata[29:])  # set power lsb byte
                        hexinstant_power_msb = hexinstant_power[:2]
                        bits_0_to_3 = bin(int(hexinstant_power_msb,
                                              16))[2:].zfill(4)
                        power_msb_trainer_status_byte = "0000" + bits_0_to_3
                        newdata = "{0}{1}{2}".format(
                            newdata[:30],
                            hex(int(power_msb_trainer_status_byte))[2:].zfill(
                                2),
                            newdata[32:],
                        )  # set mixed trainer data power msb byte
                        newdata = "{0}{1}{2}".format(
                            newdata[:36], ant.calc_checksum(newdata),
                            newdata[38:])  # recalculate checksum

                    if debug:
                        print(
                            datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S.%f")
                            [:-3], "TRAINER DATA", newdata)
                    reply = ant.send_ant([newdata], dev_ant, debug)
                    # reply = []
                    # if rv[6:8]=="33":
                    # rtn = {'grade' : int(rv[18:20]+rv[16:18],16) * 0.01 - 200} #7% in zwift = 3.5% grade in ANT+
                    matching = [s for s in reply
                                if "a4094f0033" in s]  # target resistance
                    # 0x33 a4094f00 33 ffffffff964fff f7 is gradient message
                    if matching:
                        grade = (
                            int(matching[0][20:22] + matching[0][18:20], 16) *
                            0.01 - 200)
                        target_power = False
                        if not headless:
                            self.SlopeVariable.set(round(grade, 1))
                        if not headless:
                            self.TargetPowerVariable.set("")
                        if debug:
                            print(grade, matching[0])
                    else:
                        matching = [s for s in reply
                                    if "a4094f0031" in s]  # target watts
                        # 0x31 a4094f00 31 ffffffffff5c02 72 is target power message in 0.25w 0x025c = 604 = 151W
                        if matching:
                            target_power = (int(
                                matching[0][22:24] + matching[0][20:22], 16) /
                                            4)
                            grade = False
                            if not headless:
                                self.TargetPowerVariable.set(target_power)
                            if not headless:
                                self.SlopeVariable.set("")
                    # ##################HR#######################
                    # HR format
                    # D00000693_-_ANT+_Device_Profile_-_Heart_Rate_Rev_2.1.pdf
                    # [00][FF][FF][FF][55][03][01][48]p. 18 [00] bits 0:6 data page no, bit 7 toggle every 4th message, [ff][ff][ff] (reserved for page 0), [55][03] heart beat event time [lsb][ msb] rollover 64s, [01] heart beat count rollover 256, [instant heart rate]max 256
                    # [00][FF][FF][FF][55][03][01][48]
                    # [00][FF][FF][FF][AA][06][02][48]
                    # [00][FF][FF][FF][AA][06][02][48]
                    # [80][FF][FF][FF][AA][06][02][48]
                    # [80][FF][FF][FF][AA][06][02][48]
                    # [80][FF][FF][FF][FF][09][03][48]
                    # [80][FF][FF][FF][FF][09][03][48]
                    # [00][FF][FF][FF][FF][09][03][48]
                    # [00][FF][FF][FF][54][0D][04][48]
                    # [00][FF][FF][FF][54][0D][04][48]
                    # [00][FF][FF][FF][54][0D][04][48]

                    # every 65th message send manufacturer and product info -apge 2 and page 3
                    # [82][0F][01][00][00][3A][12][48] - [82] page 2 with toggle on (repeat 4 times)
                    # [83][01][01][33][4F][3F][13][48] - [83] page 3 with toggle on
                    # if eventcounter > 40: heart_rate = 100 #comment out in production
                    if heart_rate > 0:  # i.e. heart rate belt attached
                        if eventcounter % 4 == 0:  # toggle bit every 4 counts
                            if heart_toggle == 0:
                                heart_toggle = 128
                            else:
                                heart_toggle = 0

                        # check if heart beat has occurred as tacx only reports instanatenous heart rate data
                        # last heart beat is at heart_beat_event_time
                        # if now - heart_beat_event_time > time taken for hr to occur, trigger beat. 70 bpm = beat every 60/70 seconds
                        if (time.time() * 1000 - heart_beat_event_time
                            ) >= (60 / float(heart_rate)) * 1000:
                            heart_beat_count += 1  # increment heart beat count
                            heart_beat_event_time += (
                                60 / float(heart_rate)
                            ) * 1000  # reset last time of heart beat

                        if (heart_beat_event_time -
                                heart_beat_event_time_start_cycle >=
                                64000):  # rollover every 64s
                            heart_beat_event_time = (
                                time.time() * 1000
                            )  # reset last heart beat event
                            heart_beat_event_time_start_cycle = (
                                time.time() * 1000)  # reset start of cycle

                        if heart_beat_count >= 256:
                            heart_beat_count = 0

                        if heart_rate >= 256:
                            heart_rate = 255

                        hex_heart_beat_time = int(
                            (heart_beat_event_time -
                             heart_beat_event_time_start_cycle) *
                            1.024)  # convert ms to 1/1024 of a second
                        hex_heart_beat_time = hex(
                            hex_heart_beat_time)[2:].zfill(4)

                        hr_byte_4 = hex_heart_beat_time[2:]
                        hr_byte_5 = hex_heart_beat_time[:2]
                        hr_byte_6 = hex(heart_beat_count)[2:].zfill(2)
                        hr_byte_7 = hex(heart_rate)[2:].zfill(2)

                        # data page 1,6,7 every 80s
                        if (
                                eventcounter % 65 == 0
                                or (eventcounter + 1) % 65 == 0
                                or (eventcounter + 2) % 65 == 0
                                or (eventcounter + 3) % 65 == 0
                        ):  # send first and second manufacturer's info packet
                            hr_byte_0 = hex(2 + heart_toggle)[2:].zfill(2)
                            hr_byte_1 = "0f"
                            hr_byte_2 = "01"
                            hr_byte_3 = "00"
                            # [82][0F][01][00][00][3A][12][48]
                        elif ((eventcounter + 31) % 65 == 0
                              or (eventcounter + 32) % 65 == 0
                              or (eventcounter + 33) % 65 == 0
                              or (eventcounter + 34) % 65 == 0
                              ):  # send first and second product info packet
                            hr_byte_0 = hex(3 + heart_toggle)[2:].zfill(2)
                            hr_byte_1 = "01"
                            hr_byte_2 = "01"
                            hr_byte_3 = "33"
                            # [83][01][01][33][4F][3F][13][48]
                        elif ((eventcounter + 11) % 65 == 0
                              or (eventcounter + 12) % 65 == 0
                              or (eventcounter + 13) % 65 == 0
                              or (eventcounter + 44) % 65 == 0
                              ):  # send page 0x01 cumulative operating time
                            cot = int((time.time() - cot_start) / 2)
                            cot_hex = hex(cot)[2:].zfill(6)
                            hr_byte_0 = hex(1 + heart_toggle)[2:].zfill(2)
                            hr_byte_1 = cot_hex[4:6]
                            hr_byte_2 = cot_hex[2:4]
                            hr_byte_3 = cot_hex[0:2]
                        elif ((eventcounter + 21) % 65 == 0
                              or (eventcounter + 22) % 65 == 0
                              or (eventcounter + 23) % 65 == 0
                              or (eventcounter + 24) % 65
                              == 0):  # send page 0x06 capabilities
                            hr_byte_0 = hex(6 + heart_toggle)[2:].zfill(2)
                            hr_byte_1 = "ff"
                            hr_byte_2 = "00"
                            hr_byte_3 = "00"
                        elif ((eventcounter + 41) % 65 == 0
                              or (eventcounter + 42) % 65 == 0
                              or (eventcounter + 43) % 65 == 0
                              or (eventcounter + 44) % 65
                              == 0):  # send page 0x07 battery
                            hr_byte_0 = hex(7 + heart_toggle)[2:].zfill(2)
                            hr_byte_1 = "64"
                            hr_byte_2 = "55"
                            hr_byte_3 = "13"
                        else:  # send page 0
                            hr_byte_0 = hex(0 + heart_toggle)[2:].zfill(2)
                            hr_byte_1 = "ff"
                            hr_byte_2 = "ff"
                            hr_byte_3 = "ff"

                        hrdata = ("a4 09 4e 01 " + hr_byte_0 + " " +
                                  hr_byte_1 + " " + hr_byte_2 + " " +
                                  hr_byte_3 + " " + hr_byte_4 + " " +
                                  hr_byte_5 + " " + hr_byte_6 + " " +
                                  hr_byte_7 + " 02 00 00")
                        hrdata = ("a4 09 4e 01 " + hr_byte_0 + " " +
                                  hr_byte_1 + " " + hr_byte_2 + " " +
                                  hr_byte_3 + " " + hr_byte_4 + " " +
                                  hr_byte_5 + " " + hr_byte_6 + " " +
                                  hr_byte_7 + " " + ant.calc_checksum(hrdata) +
                                  " 00 00")
                        time.sleep(0.125)  # sleep for 125ms
                        if debug:
                            print(
                                datetime.utcnow().strftime(
                                    "%Y-%m-%d %H:%M:%S.%f")[:-3], "HEART RATE",
                                hrdata)
                        ant.send_ant([hrdata], dev_ant, debug)
                    # ###################wait ####################

                    # add wait so we only send every 250ms
                    time_to_process_loop = time.time(
                    ) * 1000 - last_measured_time
                    sleep_time = 0.25 - (time_to_process_loop) / 1000
                    if sleep_time < 0:
                        sleep_time = 0
                    time.sleep(sleep_time)
                    eventcounter += 1

                    if not headless:
                        self.SpeedVariable.set(speed)
                        self.HeartrateVariable.set(heart_rate)
                        self.CadenceVariable.set(cadence)
                        self.PowerVariable.set(calc_power)
                        self.ResistanceLevelVariable.set(resistance_level)
                    elif eventcounter % 4 == 0:
                        print("Power %sW, HR %s, Cadence %s, Resistance %s" % (
                            calc_power,
                            heart_rate,
                            cadence,
                            resistance_level,
                        ))

            except KeyboardInterrupt:
                print("Stopped")

            ant.antreset(dev_ant, debug)  # reset dongle
            if os.name == "posix":  # close serial port to ANT stick on Linux
                dev_ant.close()
            if debug:
                print("stopped")
            if not headless:
                self.RunoffButton.config(state="normal")

            with open("user_defaults", "wb") as handle:  # save defaults
                pickle.dump(user_defaults,
                            handle,
                            protocol=pickle.HIGHEST_PROTOCOL)

        if not headless:
            self.FindHWbutton.config(state="disabled")
            self.StartAPPbutton.config(state="disabled")
            self.StopAPPbutton.config(state="normal")
            thread = threading.Thread(target=run)
            thread.start()
        else:
            run()
示例#2
0
class LED8x8AndButton(object):

    def __init__(self, buttonDownCallback=None, buttonUpCallback=None):
        self.root = Tk()
        self.root.title("8x8")

        self.buttonUpCallback = buttonUpCallback
        self.buttonDownCallback = buttonDownCallback

        self.pixels = []

        for y in xrange(8):
            for x in xrange(8):
                bt = Button(self.root,
                            bg="gray",
                            width=2, height=1,
                            state="disabled")
                self.pixels.append(bt)
                bt.grid(column=x, row=y)

        self.but = Button(self.root, text="#",
                          width=3, height=1)
        self.but.grid(column=3, row=8, columnspan=2)

        self.butColor = Button(self.root, state="disabled", width=3)
        self.butColor.grid(column=1, row=8, columnspan=2)

        self.orgColor = self.butColor.cget("bg")

        self.but.bind("<Button-1>", self._buttonDown)
        self.but.bind("<ButtonRelease-1>", self._buttonUp)

    def mainLoop(self):
        self.root.mainloop()

    def _setXY(self, x, y, val):
        if val == 0:
            self.pixels[y * 8 + x].configure(bg="gray")
        elif val == 1:
            self.pixels[y * 8 + x].configure(bg="green")
        elif val == 2:
            self.pixels[y * 8 + x].configure(bg="red")
        elif val == 3:
            self.pixels[y * 8 + x].configure(bg="orange")
        else:
            self.pixels[y * 8 + x].configure(bg="white")

    def setButtonColor(self, val):
        if val == 0:
            self.butColor.configure(bg=self.orgColor)
        elif val == 1:
            self.butColor.configure(bg="green")
        elif val == 2:
            self.butColor.configure(bg="red")
        elif val == 3:
            self.butColor.configure(bg="orange")
        else:
            self.butColor.configure(bg="white")

    def writeDisplay(self, image):
        pos = 0
        for x in xrange(8):
            sv = 1
            v1 = ord(image[pos])
            v2 = ord(image[pos + 1])
            pos = pos + 2
            for y in xrange(8):
                pv = 0
                if (v1 & sv) != 0:
                    pv = pv | 1
                if (v2 & sv) != 0:
                    pv = pv | 2
                sv = sv << 1
                self._setXY(7 - x, y, pv)

    def _buttonDown(self, event):
        if self.buttonDownCallback:
            self.buttonDownCallback()

    def _buttonUp(self, event):
        if self.buttonUpCallback:
            self.buttonUpCallback()
示例#3
0
class GetKeysDialog(Toplevel):
    def __init__(self,parent,title,action,currentKeySequences,_htest=False):
        """
        action - string, the name of the virtual event these keys will be
                 mapped to
        currentKeys - list, a list of all key sequence lists currently mapped
                 to virtual events, for overlap checking
        _htest - bool, change box location when running htest
        """
        Toplevel.__init__(self, parent)
        self.configure(borderwidth=5)
        self.resizable(height=FALSE,width=FALSE)
        self.title(title)
        self.transient(parent)
        self.grab_set()
        self.protocol("WM_DELETE_WINDOW", self.Cancel)
        self.parent = parent
        self.action=action
        self.currentKeySequences=currentKeySequences
        self.result=''
        self.keyString=StringVar(self)
        self.keyString.set('')
        self.SetModifiersForPlatform() # set self.modifiers, self.modifier_label
        self.modifier_vars = []
        for modifier in self.modifiers:
            variable = StringVar(self)
            variable.set('')
            self.modifier_vars.append(variable)
        self.advanced = False
        self.CreateWidgets()
        self.LoadFinalKeyList()
        self.withdraw() #hide while setting geometry
        self.update_idletasks()
        self.geometry(
                "+%d+%d" % (
                    parent.winfo_rootx() +
                    (parent.winfo_width()/2 - self.winfo_reqwidth()/2),
                    parent.winfo_rooty() +
                    ((parent.winfo_height()/2 - self.winfo_reqheight()/2)
                    if not _htest else 150)
                ) )  #centre dialog over parent (or below htest box)
        self.deiconify() #geometry set, unhide
        self.wait_window()

    def CreateWidgets(self):
        frameMain = Frame(self,borderwidth=2,relief=SUNKEN)
        frameMain.pack(side=TOP,expand=TRUE,fill=BOTH)
        frameButtons=Frame(self)
        frameButtons.pack(side=BOTTOM,fill=X)
        self.buttonOK = Button(frameButtons,text='OK',
                width=8,command=self.OK)
        self.buttonOK.grid(row=0,column=0,padx=5,pady=5)
        self.buttonCancel = Button(frameButtons,text='Cancel',
                width=8,command=self.Cancel)
        self.buttonCancel.grid(row=0,column=1,padx=5,pady=5)
        self.frameKeySeqBasic = Frame(frameMain)
        self.frameKeySeqAdvanced = Frame(frameMain)
        self.frameControlsBasic = Frame(frameMain)
        self.frameHelpAdvanced = Frame(frameMain)
        self.frameKeySeqAdvanced.grid(row=0,column=0,sticky=NSEW,padx=5,pady=5)
        self.frameKeySeqBasic.grid(row=0,column=0,sticky=NSEW,padx=5,pady=5)
        self.frameKeySeqBasic.lift()
        self.frameHelpAdvanced.grid(row=1,column=0,sticky=NSEW,padx=5)
        self.frameControlsBasic.grid(row=1,column=0,sticky=NSEW,padx=5)
        self.frameControlsBasic.lift()
        self.buttonLevel = Button(frameMain,command=self.ToggleLevel,
                text='Advanced Key Binding Entry >>')
        self.buttonLevel.grid(row=2,column=0,stick=EW,padx=5,pady=5)
        labelTitleBasic = Label(self.frameKeySeqBasic,
                text="New keys for  '"+self.action+"' :")
        labelTitleBasic.pack(anchor=W)
        labelKeysBasic = Label(self.frameKeySeqBasic,justify=LEFT,
                textvariable=self.keyString,relief=GROOVE,borderwidth=2)
        labelKeysBasic.pack(ipadx=5,ipady=5,fill=X)
        self.modifier_checkbuttons = {}
        column = 0
        for modifier, variable in zip(self.modifiers, self.modifier_vars):
            label = self.modifier_label.get(modifier, modifier)
            check=Checkbutton(self.frameControlsBasic,
                command=self.BuildKeyString,
                text=label,variable=variable,onvalue=modifier,offvalue='')
            check.grid(row=0,column=column,padx=2,sticky=W)
            self.modifier_checkbuttons[modifier] = check
            column += 1
        labelFnAdvice=Label(self.frameControlsBasic,justify=LEFT,
                            text=\
                            "Select the desired modifier keys\n"+
                            "above, and the final key from the\n"+
                            "list on the right.\n\n" +
                            "Use upper case Symbols when using\n" +
                            "the Shift modifier.  (Letters will be\n" +
                            "converted automatically.)")
        labelFnAdvice.grid(row=1,column=0,columnspan=4,padx=2,sticky=W)
        self.listKeysFinal=Listbox(self.frameControlsBasic,width=15,height=10,
                selectmode=SINGLE)
        self.listKeysFinal.bind('<ButtonRelease-1>',self.FinalKeySelected)
        self.listKeysFinal.grid(row=0,column=4,rowspan=4,sticky=NS)
        scrollKeysFinal=Scrollbar(self.frameControlsBasic,orient=VERTICAL,
                command=self.listKeysFinal.yview)
        self.listKeysFinal.config(yscrollcommand=scrollKeysFinal.set)
        scrollKeysFinal.grid(row=0,column=5,rowspan=4,sticky=NS)
        self.buttonClear=Button(self.frameControlsBasic,
                text='Clear Keys',command=self.ClearKeySeq)
        self.buttonClear.grid(row=2,column=0,columnspan=4)
        labelTitleAdvanced = Label(self.frameKeySeqAdvanced,justify=LEFT,
                text="Enter new binding(s) for  '"+self.action+"' :\n"+
                "(These bindings will not be checked for validity!)")
        labelTitleAdvanced.pack(anchor=W)
        self.entryKeysAdvanced=Entry(self.frameKeySeqAdvanced,
                textvariable=self.keyString)
        self.entryKeysAdvanced.pack(fill=X)
        labelHelpAdvanced=Label(self.frameHelpAdvanced,justify=LEFT,
            text="Key bindings are specified using Tkinter keysyms as\n"+
                 "in these samples: <Control-f>, <Shift-F2>, <F12>,\n"
                 "<Control-space>, <Meta-less>, <Control-Alt-Shift-X>.\n"
                 "Upper case is used when the Shift modifier is present!\n\n" +
                 "'Emacs style' multi-keystroke bindings are specified as\n" +
                 "follows: <Control-x><Control-y>, where the first key\n" +
                 "is the 'do-nothing' keybinding.\n\n" +
                 "Multiple separate bindings for one action should be\n"+
                 "separated by a space, eg., <Alt-v> <Meta-v>." )
        labelHelpAdvanced.grid(row=0,column=0,sticky=NSEW)

    def SetModifiersForPlatform(self):
        """Determine list of names of key modifiers for this platform.

        The names are used to build Tk bindings -- it doesn't matter if the
        keyboard has these keys, it matters if Tk understands them. The
        order is also important: key binding equality depends on it, so
        config-keys.def must use the same ordering.
        """
        if sys.platform == "darwin":
            self.modifiers = ['Shift', 'Control', 'Option', 'Command']
        else:
            self.modifiers = ['Control', 'Alt', 'Shift']
        self.modifier_label = {'Control': 'Ctrl'} # short name

    def ToggleLevel(self):
        if  self.buttonLevel.cget('text')[:8]=='Advanced':
            self.ClearKeySeq()
            self.buttonLevel.config(text='<< Basic Key Binding Entry')
            self.frameKeySeqAdvanced.lift()
            self.frameHelpAdvanced.lift()
            self.entryKeysAdvanced.focus_set()
            self.advanced = True
        else:
            self.ClearKeySeq()
            self.buttonLevel.config(text='Advanced Key Binding Entry >>')
            self.frameKeySeqBasic.lift()
            self.frameControlsBasic.lift()
            self.advanced = False

    def FinalKeySelected(self,event):
        self.BuildKeyString()

    def BuildKeyString(self):
        keyList = modifiers = self.GetModifiers()
        finalKey = self.listKeysFinal.get(ANCHOR)
        if finalKey:
            finalKey = self.TranslateKey(finalKey, modifiers)
            keyList.append(finalKey)
        self.keyString.set('<' + string.join(keyList,'-') + '>')

    def GetModifiers(self):
        modList = [variable.get() for variable in self.modifier_vars]
        return [mod for mod in modList if mod]

    def ClearKeySeq(self):
        self.listKeysFinal.select_clear(0,END)
        self.listKeysFinal.yview(MOVETO, '0.0')
        for variable in self.modifier_vars:
            variable.set('')
        self.keyString.set('')

    def LoadFinalKeyList(self):
        #these tuples are also available for use in validity checks
        self.functionKeys=('F1','F2','F2','F4','F5','F6','F7','F8','F9',
                'F10','F11','F12')
        self.alphanumKeys=tuple(string.ascii_lowercase+string.digits)
        self.punctuationKeys=tuple('~!@#%^&*()_-+={}[]|;:,.<>/?')
        self.whitespaceKeys=('Tab','Space','Return')
        self.editKeys=('BackSpace','Delete','Insert')
        self.moveKeys=('Home','End','Page Up','Page Down','Left Arrow',
                'Right Arrow','Up Arrow','Down Arrow')
        #make a tuple of most of the useful common 'final' keys
        keys=(self.alphanumKeys+self.punctuationKeys+self.functionKeys+
                self.whitespaceKeys+self.editKeys+self.moveKeys)
        self.listKeysFinal.insert(END, *keys)

    def TranslateKey(self, key, modifiers):
        "Translate from keycap symbol to the Tkinter keysym"
        translateDict = {'Space':'space',
                '~':'asciitilde','!':'exclam','@':'at','#':'numbersign',
                '%':'percent','^':'asciicircum','&':'ampersand','*':'asterisk',
                '(':'parenleft',')':'parenright','_':'underscore','-':'minus',
                '+':'plus','=':'equal','{':'braceleft','}':'braceright',
                '[':'bracketleft',']':'bracketright','|':'bar',';':'semicolon',
                ':':'colon',',':'comma','.':'period','<':'less','>':'greater',
                '/':'slash','?':'question','Page Up':'Prior','Page Down':'Next',
                'Left Arrow':'Left','Right Arrow':'Right','Up Arrow':'Up',
                'Down Arrow': 'Down', 'Tab':'Tab'}
        if key in translateDict.keys():
            key = translateDict[key]
        if 'Shift' in modifiers and key in string.ascii_lowercase:
            key = key.upper()
        key = 'Key-' + key
        return key

    def OK(self, event=None):
        if self.advanced or self.KeysOK():  # doesn't check advanced string yet
            self.result=self.keyString.get()
            self.destroy()

    def Cancel(self, event=None):
        self.result=''
        self.destroy()

    def KeysOK(self):
        '''Validity check on user's 'basic' keybinding selection.

        Doesn't check the string produced by the advanced dialog because
        'modifiers' isn't set.

        '''
        keys = self.keyString.get()
        keys.strip()
        finalKey = self.listKeysFinal.get(ANCHOR)
        modifiers = self.GetModifiers()
        # create a key sequence list for overlap check:
        keySequence = keys.split()
        keysOK = False
        title = 'Key Sequence Error'
        if not keys:
            tkMessageBox.showerror(title=title, parent=self,
                                   message='No keys specified.')
        elif not keys.endswith('>'):
            tkMessageBox.showerror(title=title, parent=self,
                                   message='Missing the final Key')
        elif (not modifiers
              and finalKey not in self.functionKeys + self.moveKeys):
            tkMessageBox.showerror(title=title, parent=self,
                                   message='No modifier key(s) specified.')
        elif (modifiers == ['Shift']) \
                 and (finalKey not in
                      self.functionKeys + self.moveKeys + ('Tab', 'Space')):
            msg = 'The shift modifier by itself may not be used with'\
                  ' this key symbol.'
            tkMessageBox.showerror(title=title, parent=self, message=msg)
        elif keySequence in self.currentKeySequences:
            msg = 'This key combination is already in use.'
            tkMessageBox.showerror(title=title, parent=self, message=msg)
        else:
            keysOK = True
        return keysOK
示例#4
0
class GetKeysDialog(Toplevel):
    def __init__(self,
                 parent,
                 title,
                 action,
                 currentKeySequences,
                 _htest=False):
        """
        action - string, the name of the virtual event these keys will be
                 mapped to
        currentKeys - list, a list of all key sequence lists currently mapped
                 to virtual events, for overlap checking
        _htest - bool, change box location when running htest
        """
        Toplevel.__init__(self, parent)
        self.configure(borderwidth=5)
        self.resizable(height=FALSE, width=FALSE)
        self.title(title)
        self.transient(parent)
        self.grab_set()
        self.protocol("WM_DELETE_WINDOW", self.Cancel)
        self.parent = parent
        self.action = action
        self.currentKeySequences = currentKeySequences
        self.result = ''
        self.keyString = StringVar(self)
        self.keyString.set('')
        self.SetModifiersForPlatform(
        )  # set self.modifiers, self.modifier_label
        self.modifier_vars = []
        for modifier in self.modifiers:
            variable = StringVar(self)
            variable.set('')
            self.modifier_vars.append(variable)
        self.advanced = False
        self.CreateWidgets()
        self.LoadFinalKeyList()
        self.withdraw()  #hide while setting geometry
        self.update_idletasks()
        self.geometry("+%d+%d" %
                      (parent.winfo_rootx() +
                       (parent.winfo_width() / 2 - self.winfo_reqwidth() / 2),
                       parent.winfo_rooty() +
                       ((parent.winfo_height() / 2 -
                         self.winfo_reqheight() / 2) if not _htest else 150))
                      )  #centre dialog over parent (or below htest box)
        self.deiconify()  #geometry set, unhide
        self.wait_window()

    def CreateWidgets(self):
        frameMain = Frame(self, borderwidth=2, relief=SUNKEN)
        frameMain.pack(side=TOP, expand=TRUE, fill=BOTH)
        frameButtons = Frame(self)
        frameButtons.pack(side=BOTTOM, fill=X)
        self.buttonOK = Button(frameButtons,
                               text='OK',
                               width=8,
                               command=self.OK)
        self.buttonOK.grid(row=0, column=0, padx=5, pady=5)
        self.buttonCancel = Button(frameButtons,
                                   text='Cancel',
                                   width=8,
                                   command=self.Cancel)
        self.buttonCancel.grid(row=0, column=1, padx=5, pady=5)
        self.frameKeySeqBasic = Frame(frameMain)
        self.frameKeySeqAdvanced = Frame(frameMain)
        self.frameControlsBasic = Frame(frameMain)
        self.frameHelpAdvanced = Frame(frameMain)
        self.frameKeySeqAdvanced.grid(row=0,
                                      column=0,
                                      sticky=NSEW,
                                      padx=5,
                                      pady=5)
        self.frameKeySeqBasic.grid(row=0,
                                   column=0,
                                   sticky=NSEW,
                                   padx=5,
                                   pady=5)
        self.frameKeySeqBasic.lift()
        self.frameHelpAdvanced.grid(row=1, column=0, sticky=NSEW, padx=5)
        self.frameControlsBasic.grid(row=1, column=0, sticky=NSEW, padx=5)
        self.frameControlsBasic.lift()
        self.buttonLevel = Button(frameMain,
                                  command=self.ToggleLevel,
                                  text='Advanced Key Binding Entry >>')
        self.buttonLevel.grid(row=2, column=0, stick=EW, padx=5, pady=5)
        labelTitleBasic = Label(self.frameKeySeqBasic,
                                text="New keys for  '" + self.action + "' :")
        labelTitleBasic.pack(anchor=W)
        labelKeysBasic = Label(self.frameKeySeqBasic,
                               justify=LEFT,
                               textvariable=self.keyString,
                               relief=GROOVE,
                               borderwidth=2)
        labelKeysBasic.pack(ipadx=5, ipady=5, fill=X)
        self.modifier_checkbuttons = {}
        column = 0
        for modifier, variable in zip(self.modifiers, self.modifier_vars):
            label = self.modifier_label.get(modifier, modifier)
            check = Checkbutton(self.frameControlsBasic,
                                command=self.BuildKeyString,
                                text=label,
                                variable=variable,
                                onvalue=modifier,
                                offvalue='')
            check.grid(row=0, column=column, padx=2, sticky=W)
            self.modifier_checkbuttons[modifier] = check
            column += 1
        labelFnAdvice=Label(self.frameControlsBasic,justify=LEFT,
                            text=\
                            "Select the desired modifier keys\n"+
                            "above, and the final key from the\n"+
                            "list on the right.\n\n" +
                            "Use upper case Symbols when using\n" +
                            "the Shift modifier.  (Letters will be\n" +
                            "converted automatically.)")
        labelFnAdvice.grid(row=1, column=0, columnspan=4, padx=2, sticky=W)
        self.listKeysFinal = Listbox(self.frameControlsBasic,
                                     width=15,
                                     height=10,
                                     selectmode=SINGLE)
        self.listKeysFinal.bind('<ButtonRelease-1>', self.FinalKeySelected)
        self.listKeysFinal.grid(row=0, column=4, rowspan=4, sticky=NS)
        scrollKeysFinal = Scrollbar(self.frameControlsBasic,
                                    orient=VERTICAL,
                                    command=self.listKeysFinal.yview)
        self.listKeysFinal.config(yscrollcommand=scrollKeysFinal.set)
        scrollKeysFinal.grid(row=0, column=5, rowspan=4, sticky=NS)
        self.buttonClear = Button(self.frameControlsBasic,
                                  text='Clear Keys',
                                  command=self.ClearKeySeq)
        self.buttonClear.grid(row=2, column=0, columnspan=4)
        labelTitleAdvanced = Label(
            self.frameKeySeqAdvanced,
            justify=LEFT,
            text="Enter new binding(s) for  '" + self.action + "' :\n" +
            "(These bindings will not be checked for validity!)")
        labelTitleAdvanced.pack(anchor=W)
        self.entryKeysAdvanced = Entry(self.frameKeySeqAdvanced,
                                       textvariable=self.keyString)
        self.entryKeysAdvanced.pack(fill=X)
        labelHelpAdvanced = Label(
            self.frameHelpAdvanced,
            justify=LEFT,
            text="Key bindings are specified using Tkinter keysyms as\n" +
            "in these samples: <Control-f>, <Shift-F2>, <F12>,\n"
            "<Control-space>, <Meta-less>, <Control-Alt-Shift-X>.\n"
            "Upper case is used when the Shift modifier is present!\n\n" +
            "'Emacs style' multi-keystroke bindings are specified as\n" +
            "follows: <Control-x><Control-y>, where the first key\n" +
            "is the 'do-nothing' keybinding.\n\n" +
            "Multiple separate bindings for one action should be\n" +
            "separated by a space, eg., <Alt-v> <Meta-v>.")
        labelHelpAdvanced.grid(row=0, column=0, sticky=NSEW)

    def SetModifiersForPlatform(self):
        """Determine list of names of key modifiers for this platform.

        The names are used to build Tk bindings -- it doesn't matter if the
        keyboard has these keys, it matters if Tk understands them. The
        order is also important: key binding equality depends on it, so
        config-keys.def must use the same ordering.
        """
        if sys.platform == "darwin":
            self.modifiers = ['Shift', 'Control', 'Option', 'Command']
        else:
            self.modifiers = ['Control', 'Alt', 'Shift']
        self.modifier_label = {'Control': 'Ctrl'}  # short name

    def ToggleLevel(self):
        if self.buttonLevel.cget('text')[:8] == 'Advanced':
            self.ClearKeySeq()
            self.buttonLevel.config(text='<< Basic Key Binding Entry')
            self.frameKeySeqAdvanced.lift()
            self.frameHelpAdvanced.lift()
            self.entryKeysAdvanced.focus_set()
            self.advanced = True
        else:
            self.ClearKeySeq()
            self.buttonLevel.config(text='Advanced Key Binding Entry >>')
            self.frameKeySeqBasic.lift()
            self.frameControlsBasic.lift()
            self.advanced = False

    def FinalKeySelected(self, event):
        self.BuildKeyString()

    def BuildKeyString(self):
        keyList = modifiers = self.GetModifiers()
        finalKey = self.listKeysFinal.get(ANCHOR)
        if finalKey:
            finalKey = self.TranslateKey(finalKey, modifiers)
            keyList.append(finalKey)
        self.keyString.set('<' + string.join(keyList, '-') + '>')

    def GetModifiers(self):
        modList = [variable.get() for variable in self.modifier_vars]
        return [mod for mod in modList if mod]

    def ClearKeySeq(self):
        self.listKeysFinal.select_clear(0, END)
        self.listKeysFinal.yview(MOVETO, '0.0')
        for variable in self.modifier_vars:
            variable.set('')
        self.keyString.set('')

    def LoadFinalKeyList(self):
        #these tuples are also available for use in validity checks
        self.functionKeys = ('F1', 'F2', 'F2', 'F4', 'F5', 'F6', 'F7', 'F8',
                             'F9', 'F10', 'F11', 'F12')
        self.alphanumKeys = tuple(string.ascii_lowercase + string.digits)
        self.punctuationKeys = tuple('~!@#%^&*()_-+={}[]|;:,.<>/?')
        self.whitespaceKeys = ('Tab', 'Space', 'Return')
        self.editKeys = ('BackSpace', 'Delete', 'Insert')
        self.moveKeys = ('Home', 'End', 'Page Up', 'Page Down', 'Left Arrow',
                         'Right Arrow', 'Up Arrow', 'Down Arrow')
        #make a tuple of most of the useful common 'final' keys
        keys = (self.alphanumKeys + self.punctuationKeys + self.functionKeys +
                self.whitespaceKeys + self.editKeys + self.moveKeys)
        self.listKeysFinal.insert(END, *keys)

    def TranslateKey(self, key, modifiers):
        "Translate from keycap symbol to the Tkinter keysym"
        translateDict = {
            'Space': 'space',
            '~': 'asciitilde',
            '!': 'exclam',
            '@': 'at',
            '#': 'numbersign',
            '%': 'percent',
            '^': 'asciicircum',
            '&': 'ampersand',
            '*': 'asterisk',
            '(': 'parenleft',
            ')': 'parenright',
            '_': 'underscore',
            '-': 'minus',
            '+': 'plus',
            '=': 'equal',
            '{': 'braceleft',
            '}': 'braceright',
            '[': 'bracketleft',
            ']': 'bracketright',
            '|': 'bar',
            ';': 'semicolon',
            ':': 'colon',
            ',': 'comma',
            '.': 'period',
            '<': 'less',
            '>': 'greater',
            '/': 'slash',
            '?': 'question',
            'Page Up': 'Prior',
            'Page Down': 'Next',
            'Left Arrow': 'Left',
            'Right Arrow': 'Right',
            'Up Arrow': 'Up',
            'Down Arrow': 'Down',
            'Tab': 'Tab'
        }
        if key in translateDict.keys():
            key = translateDict[key]
        if 'Shift' in modifiers and key in string.ascii_lowercase:
            key = key.upper()
        key = 'Key-' + key
        return key

    def OK(self, event=None):
        if self.advanced or self.KeysOK():  # doesn't check advanced string yet
            self.result = self.keyString.get()
            self.destroy()

    def Cancel(self, event=None):
        self.result = ''
        self.destroy()

    def KeysOK(self):
        '''Validity check on user's 'basic' keybinding selection.

        Doesn't check the string produced by the advanced dialog because
        'modifiers' isn't set.

        '''
        keys = self.keyString.get()
        keys.strip()
        finalKey = self.listKeysFinal.get(ANCHOR)
        modifiers = self.GetModifiers()
        # create a key sequence list for overlap check:
        keySequence = keys.split()
        keysOK = False
        title = 'Key Sequence Error'
        if not keys:
            tkMessageBox.showerror(title=title,
                                   parent=self,
                                   message='No keys specified.')
        elif not keys.endswith('>'):
            tkMessageBox.showerror(title=title,
                                   parent=self,
                                   message='Missing the final Key')
        elif (not modifiers
              and finalKey not in self.functionKeys + self.moveKeys):
            tkMessageBox.showerror(title=title,
                                   parent=self,
                                   message='No modifier key(s) specified.')
        elif (modifiers == ['Shift']) \
                 and (finalKey not in
                      self.functionKeys + self.moveKeys + ('Tab', 'Space')):
            msg = 'The shift modifier by itself may not be used with'\
                  ' this key symbol.'
            tkMessageBox.showerror(title=title, parent=self, message=msg)
        elif keySequence in self.currentKeySequences:
            msg = 'This key combination is already in use.'
            tkMessageBox.showerror(title=title, parent=self, message=msg)
        else:
            keysOK = True
        return keysOK