Пример #1
0
        def run():
            # self.CalibrateButton.config(state="disabled")
            global dev_ant
            # find ANT stick
            self.ANTStatusVariable.set("Looking for ANT dongle")
            dev_ant, msg = ant.get_ant(False)
            if not dev_ant:
                self.ANTStatusVariable.set("ANT dongle not found")
                return

            self.ANTStatusVariable.set("Initialising ANT dongle")
            ant.antreset(dev_ant, False)
            ant.calibrate(dev_ant, False)  # calibrate ANT+ dongle
            ant.powerdisplay(dev_ant, False)  # calibrate as power display
            self.ANTStatusVariable.set("ANT dongle initialised")
            self.InstructionsVariable.set(
                "Place pedals in positions instructed by power meter manufacturer. Calibration will start in 5 seconds"
            )
            time.sleep(5)
            self.ANTStatusVariable.set("Sending calibration request")
            ant.send_ant(
                ["a4 09 4f 00 01 aa ff ff ff ff ff ff 49 00 00"], dev_ant, False
            )
            i = 0
            while i < 40:  # wait 10 seconds
                if i % 4 == 0:
                    self.ANTStatusVariable.set(
                        "Sending calibration request %s" % (10 - (i / 4))
                    )
                read_val = ant.read_ant(dev_ant, False)
                matching = [
                    s for s in read_val if "a4094f0001" in s
                ]  # calibration response
                if matching:
                    if matching[0][10:12] == "ac":
                        self.ANTStatusVariable.set("Calibration successful")
                        self.CalibratedVariable.set("True")
                        self.FindHWbutton.config(state="normal")
                    elif matching[0][10:12] == "af":
                        self.ANTStatusVariable.set("Calibration failed")
                        self.CalibratedVariable.set("False")
                    else:
                        self.ANTStatusVariable.set("Unknown calibration response")
                        self.CalibratedVariable.set("False")
                    i = 999
                i += 1
                time.sleep(0.25)
            if i == 40:  # timeout
                self.ANTStatusVariable.set("No calibration data received- try again")
                self.CalibratedVariable.set("False")
            self.CalibrateButton.config(state="normal")
            self.InstructionsVariable.set("")
Пример #2
0
        def run():
            global dev_trainer, dev_ant
            power = 0
            resistance_level = 0
            save_data = []

            if not dev_trainer:  # if trainer not already captured
                dev_trainer = trainer.get_trainer()
                if not dev_trainer:
                    self.TrainerStatusVariable.set("Trainer not detected")
                    return
                else:
                    self.TrainerStatusVariable.set("Trainer detected")
                    trainer.initialise_trainer(dev_trainer)  # initialise trainer

            # find ANT stick
            if not dev_ant:
                dev_ant, msg = ant.get_ant(False)
                if not dev_ant:
                    self.ANTStatusVariable.set(u"no ANT dongle found")
                    return False
            self.ANTStatusVariable.set(u"ANT dongle found")

            ant.antreset(dev_ant, False)  # reset dongle
            ant.calibrate(dev_ant, False)  # calibrate ANT+ dongle
            ant.powerdisplay(dev_ant, False)  # calibrate as power display

            iterations = 0
            rest = 1
            stop_loop = False

            # ##################DATA LOOP FROM ANT STICK###################
            while self.StartText.get() == "Stop":
                # print iterations
                last_measured_time = time.time() * 1000
                if iterations == 240:  # inc resistance level every 60s (240 iterations)
                    iterations = 0
                    rest = 1  # go into rest mode
                    resistance_level += 1
                    if resistance_level == 14:
                        stop_loop = True
                if stop_loop:
                    self.StartText.set(u"Start")
                    self.InstructionsVariable.set("Test finished. Please exit")
                    break
                if rest > 0:
                    rest += 1
                    self.InstructionsVariable.set(
                        "Rest for %s seconds at a slow spin in an easy gear"
                        % int(round((40 - rest) / 4))
                    )
                    if rest == 40:
                        rest = 0
                else:
                    iterations += 1
                    self.InstructionsVariable.set(
                        "Over next %s seconds gradually increase your power from easy to near maximum"
                        % int(round((240 - iterations) / 4))
                    )

                try:
                    read_val = ant.read_ant(dev_ant, False)
                    matching = [
                        s for s in read_val if "a4094e0010" in s
                    ]  # a4094e0010ecff00be4e000010 #10 power page be 4e accumulated power 00 00 iunstant power
                    if matching:
                        power = int(matching[0][22:24], 16) * 256 + int(
                            matching[0][20:22], 16
                        )
                    # receive data from trainer
                    speed, pedecho, heart_rate, calc_power, cadence = trainer.receive(
                        dev_trainer
                    )  # get data from device
                    if speed == "Not found":
                        self.TrainerStatusVariable.set("Check trainer is powered on")
                        speed = 0
                    # send data to trainer
                    trainer.send(dev_trainer, resistance_level, pedecho)

                    self.PowerVariable.set(power)
                    self.SpeedVariable.set(speed)
                    self.ResistanceVariable.set(resistance_level)

                    if rest == 0 and speed > 0:  # in calibration mode and moving
                        save_data.append([resistance_level, speed, power])

                except usb.core.USBError:  # nothing from stick
                    pass

                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)
            # ##################END DATA LOOP FROM ANT STICK###############
            # ant.send(["a4 01 4a 00 ef 00 00"],dev_ant, False)#reset ANT+ dongle
            with open("calibration.pickle", "wb") as handle:
                pickle.dump(save_data, handle, protocol=pickle.HIGHEST_PROTOCOL)

            msg = produce_power_curve_file(save_data)
            self.InstructionsVariable.set(msg)
Пример #3
0
        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)
Пример #4
0
    def run():
      global dev_ant, dev_trainer, simulatetrainer, switch
      
      if debug:print "reset ant stick"
      ant.antreset(dev_ant)#reset dongle
      if debug:print "calibrate ant stick"
      ant.calibrate(dev_ant)#calibrate ANT+ dongle
      if debug:print "calibrate ant stick FE-C"
      ant.master_channel_config(dev_ant)#calibrate ANT+ channel FE-C
      if debug: print "calibrate ant stick HR"
      ant.second_channel_config(dev_ant)#calibrate ANT+ channel HR
      
      
      resistance=0#set initial resistance level
      speed,cadence,power,heart_rate=(0,)*4#initialise values
      grade = 0
      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

      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
      while switch == True:  
        if debug == True: 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, calc_power, cadence = 20, 0, 70, 200, 90
        else:
          speed, pedecho, heart_rate, calc_power, cadence = trainer.receive(dev_trainer) #get data from device
        if debug == True: print speed, pedecho, heart_rate, calc_power, cadence
        
        ####################SEND DATA TO TRAINER####################
        #send resistance data to trainer   
        if debug == True: print datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3],"GRADE", grade*2,"%"
        if not simulatetrainer:
          resistance_level = trainer.send(dev_trainer, grade, pedecho)
        else:
          resistance_level=0
          #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 == True: print datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3],"TRAINER DATA",newdata
        reply = ant.send_ant([newdata], dev_ant, debug)
        #
        #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]#a4094f0033ffffffff964ffff7 is gradient message
        if matching:
          grade = int(matching[0][20:22]+matching[0][18:20],16) * 0.01 - 200
          print grade, matching[0]
          
        ####################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 / heart_rate)*1000:
            heart_beat_count += 1#increment heart beat count
            heart_beat_event_time = time.time()*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)
          
          
          if (eventcounter + 1) % 66 == 0 or eventcounter % 66 == 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+32) % 66 == 0 or (eventcounter+33) % 66 == 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]
            
          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 == True: 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

        self.SpeedVariable.set(speed)
        self.HeartrateVariable.set(heart_rate)
        self.CadenceVariable.set(cadence)
        self.PowerVariable.set(calc_power)
        self.SlopeVariable.set(round(grade*2,1))
        self.ResistanceLevelVariable.set(resistance_level)

      if os.name == 'posix':#close serial port to ANT stick on Linux
        dev_ant.close()
        
      ant.antreset(dev_ant)#reset dongle
      print "stopped"