Esempio n. 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("")
Esempio n. 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)
Esempio n. 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)
Esempio n. 4
0
        if reply == "a4016f20ea" or reply == "a4016f00ca":  #found ANT+ stick
            serial_port = p
            ant_stick_found = True
        else:
            dev_ant.close()  #not correct reply to reset
        if ant_stick_found == True: break

    if ant_stick_found == False:
        print 'Could not find ANT+ device. Check output of "lsusb | grep 0fcf" and "ls /dev/ttyUSB*"'
        sys.exit()

else:
    print "OS not Supported"
    sys.exit()

ant.calibrate(dev_ant)  #calibrate ANT+ dongle

#calibrate as power sensor
string = [
    "a4 03 42 00 00 00 e5 00 00",  #42 assign channel
    "a4 05 51 00 00 00 0b 00 fb 00 00",  #51 set channel id, 0b device=power sensor
    "a4 02 45 00 39 da 00 00",  #45 channel freq
    "a4 03 43 00 f6 1f 0d 00 00",  #43 msg period
    "a4 02 71 00 00 d7 00 00",  #71 Set Proximity Search chann number 0 search threshold 0
    "a4 02 63 00 0a cf 00 00",  #63 low priority search channel number 0 timeout 0
    "a4 02 44 00 02 e0 00 00",  #44 Host Command/Response 
    "a4 01 4b 00 ee 00 00"  #4b ANT_OpenChannel message ID channel = 0 D00001229_Fitness_Modules_ANT+_Application_Note_Rev_3.0.pdf
]
ant.send(string, dev_ant, True)

power_meter = False
Esempio n. 5
0
def main():
    #powerfactor = 1
    #debug = False
    #simulatetrainer = False
    parser = argparse.ArgumentParser(
        description=
        'Program to broadcast data from USB Tacx trainer, and to receive resistance data for the trainer'
    )
    parser.add_argument('-d',
                        '--debug',
                        help='Show debugging data',
                        required=False,
                        action='store_true')
    parser.add_argument('-s',
                        '--simulate-trainer',
                        help='Simulated trainer to test ANT+ connectivity',
                        required=False,
                        action='store_true')
    parser.add_argument(
        '-p',
        '--power-factor',
        help=
        'Adjust broadcasted power data by multiplying measured power by this factor',
        required=False,
        default="1")
    args = parser.parse_args()
    powerfactor = args.power_factor
    debug = args.debug
    simulatetrainer = args.simulate_trainer

    ###windows###
    if os.name == 'nt':
        found_available_ant_stick = True
        try:
            dev_ant = usb.core.find(idVendor=0x0fcf,
                                    idProduct=0x1009)  #get ANT+ stick (garmin)
            dev_ant.set_configuration()  #set active configuration
            try:  #check if in use
                stringl = ["a4 01 4a 00 ef 00 00"]  #reset system
                ant.send(stringl, dev_ant, debug)
                print "Using Garmin dongle..."
            except usb.core.USBError:
                print "Garmin Device is in use"
                found_available_ant_stick = False
        except AttributeError:
            print "No Garmin Device found"
            found_available_ant_stick = False

        if found_available_ant_stick == False:
            found_available_ant_stick = True
            try:
                dev_ant = usb.core.find(
                    idVendor=0x0fcf,
                    idProduct=0x1008)  #get ANT+ stick (suunto)
                dev_ant.set_configuration()  #set active configuration
                try:  #check if in use
                    stringl = ["a4 01 4a 00 ef 00 00"]  #reset system
                    ant.send(stringl, dev_ant, debug)
                    print "Using Suunto dongle..."
                except usb.core.USBError:
                    print "Suunto Device is in use"
                    found_available_ant_stick = False
            except AttributeError:
                print "No Suunto Device found"
                found_available_ant_stick = False

        if found_available_ant_stick == False:
            print "No available ANT+ device. Retry after quitting Garmin Express or other application that uses ANT+. If still fails then remove dongles for 10s then reinsert"
            sys.exit()

    ###Linux###
    elif os.name == 'posix':
        #Find ANT+ USB stick on serial (Linux)
        ant_stick_found = False
        for p in glob.glob('/dev/ttyUSB*'):
            dev_ant = serial.Serial(p, 19200, rtscts=True, dsrdtr=True)
            dev_ant.timeout = 0.1
            dev_ant.write(binascii.unhexlify(
                "a4014a00ef0000"))  #probe with reset command
            reply = binascii.hexlify(dev_ant.read(size=256))
            if reply == "a4016f20ea" or reply == "a4016f00ca":  #found ANT+ stick
                serial_port = p
                ant_stick_found = True
            else:
                dev_ant.close()  #not correct reply to reset
            if ant_stick_found == True: break

        if ant_stick_found == False:
            print 'Could not find ANT+ device. Check output of "lsusb | grep 0fcf" and "ls /dev/ttyUSB*"'
            sys.exit()

    else:
        print "OS not Supported"
        sys.exit()

    #find trainer model for Windows and Linux
    product = 0
    if not simulatetrainer:
        idpl = [0x1932, 0x1942]  #iflow, fortius
        for idp in idpl:
            dev = usb.core.find(idVendor=0x3561,
                                idProduct=idp)  #find iflow device
            if dev != None:
                product = idp
                break

        if product == 0:
            print "Trainer not found"
            sys.exit()

        dev.set_configuration()  #set active configuration

    ant.calibrate(dev_ant)  #calibrate ANT+ dongle
    ant.master_channel_config(dev_ant)  #calibrate ANT+ channel FE-C
    ant.second_channel_config(dev_ant)  #calibrate ANT+ channel HR

    resistance = 1  #set initial resistance level
    speed, cadence, power, heart_rate = (0, ) * 4  #initialise values

    #initialise TACX USB device
    byte_ints = [2, 0, 0,
                 0]  # will not read cadence until initialisation byte is sent
    byte_str = "".join(chr(n) for n in byte_ints)
    if not simulatetrainer:
        dev.write(0x02, byte_str)
    time.sleep(1)

    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

    eventcounter = 0
    fedata = "a4 09 4e 00 10 19 89 8c 8d 20 00 30 72 00 00"
    #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

    trainerdata = "a4 09 4e 00 19 00 5a b0 47 1b 01 30 6d 00 00"
    #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

    try:
        while True:
            reply = {}
            last_measured_time = time.time() * 1000
            if eventcounter >= 256:
                eventcounter = 0
            ####################GET DATA FROM TRAINER####################
            if not simulatetrainer:
                if product == 0x1932:  #if is a 1932 headunit
                    data = dev.read(0x82, 64)  #get data from device
                    if debug == True:
                        print datetime.utcnow().strftime(
                            '%Y-%m-%d %H:%M:%S.%f'
                        )[:-3], "TRAINER RX DATA", binascii.hexlify(data)
                    #get values reported by trainer
                    if len(data) == 24:
                        print "trainer possibly not powered up"
                    if len(data) > 40:
                        fs = int(data[33]) << 8 | int(data[32])
                        speed = round(fs / 2.8054 / 100, 1)  #speed kph
                        force = fromcomp((data[39] << 8) | data[38], 16)
                        if debug == True:
                            print datetime.utcnow().strftime(
                                '%Y-%m-%d %H:%M:%S.%f')[:-3], "FORCE", force
                        try:  #try to identify force value from list of possible resistance values
                            force = T1932_calibration.possfov.index(force) + 1
                            if debug == True:
                                print datetime.utcnow().strftime(
                                    '%Y-%m-%d %H:%M:%S.%f'
                                )[:-3], "FORCE INDEX", force
                            power = T1932_calibration.calcpower(speed, force)
                            power = int(
                                power * float(powerfactor)
                            )  #alter for value passed on command line
                        except ValueError:
                            pass  # do nothing if force value from trainer not recognised
                        cadence = int(data[44])
                        heart_rate = int(data[12])
            else:
                speed, cadence, power, heart_rate = (30, 90, 283, 72)
                power = int(power * float(powerfactor))
            if debug == True: print speed, cadence, power, heart_rate

            ####################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:
                if product == 0x1932:  #if is a 1932 headunit
                    level = len(
                        T1932_calibration.grade_resistance
                    ) - 1  #set resistance level to hardest as default
                    for idx, g in enumerate(
                            sorted(T1932_calibration.grade_resistance)):
                        if g >= grade * 2:  #find resistance value immediately above grade set by zwift (Zwift ANT+ grade is half that displayed on screen)
                            level = idx
                            break
                    if debug == True:
                        print datetime.utcnow().strftime(
                            '%Y-%m-%d %H:%M:%S.%f'
                        )[:-3], "RESISTANCE LEVEL", T1932_calibration.reslist[
                            level]
                    r6 = int(
                        T1932_calibration.reslist[level]) >> 8 & 0xff  #byte6
                    r5 = int(T1932_calibration.reslist[level]) & 0xff  #byte 5
                    #echo pedal cadence back to trainer
                    if len(data) > 40:
                        pedecho = data[42]
                    else:
                        pedecho = 0
                    byte_ints = [
                        0x01, 0x08, 0x01, 0x00, r5, r6, pedecho, 0x00, 0x02,
                        0x52, 0x10, 0x04
                    ]
                    if debug == True:
                        print datetime.utcnow().strftime(
                            '%Y-%m-%d %H:%M:%S.%f'
                        )[:-3], "TRAINER TX DATA", byte_ints
                    byte_str = "".join(chr(n) for n in byte_ints)
                    dev.write(0x02, byte_str)  #send data to device

            ####################BROADCAST AND RECEIVE ANT+ data####################
            if power >= 4094:
                power = 4093
            accumulated_power += 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(
                    fedata[:18],
                    hex(accumulated_time_counter)[2:].zfill(2),
                    fedata[20:])  # set time
                distance_travelled_since_last_loop = (
                    time.time() * 1000 - last_dist_time) / 1000 * speed
                last_dist_time = time.time() * 1000
                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))[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
                if debug == True:
                    print datetime.utcnow().strftime(
                        '%Y-%m-%d %H:%M:%S.%f')[:-3], "FE DATA", newdata

            else:  #send specific trainer data
                newdata = '{0}{1}{2}'.format(
                    trainerdata[:15],
                    hex(eventcounter)[2:].zfill(2),
                    trainerdata[17:])  # 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(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([newdata], dev_ant, debug)
            if "grade" in reply:
                grade = reply['grade']

            ####################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:
                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 120ms
                if debug == True:
                    print datetime.utcnow().strftime(
                        '%Y-%m-%d %H:%M:%S.%f')[:-3], "HEART RATE", hrdata
                ant.send([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
    except KeyboardInterrupt:  # interrupt power data sending with ctrl c, make sure script continues to reset device
        pass

    ant.send(["a4 01 4a 00 ef 00 00"], dev_ant, False)  #reset ANT+ dongle

    if os.name == 'posix':  #close serial port to ANT stick on Linux
        dev_ant.close()
Esempio n. 6
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"