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("")
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)
def run(): global dev_ant, dev_trainer, simulatetrainer, switch, power_curve if power_curve == "": if not headless: self.PowerCurveVariable.set( "Choose a power curve under setup menu") self.StartAPPbutton.config(state="normal") self.StopAPPbutton.config(state="disabled") return pc_dict = trainer.parse_factors( power_curve) # get power curve dictionary if len(pc_dict) != 14: if not headless: self.PowerCurveVariable.set( "Need 14 levels for power curve") self.StartAPPbutton.config(state="normal") self.StopAPPbutton.config(state="disabled") return pc_sorted_keys = sorted(pc_dict.iterkeys()) # -1,-0,2,3 etc. if debug: print("reset ant stick") ant.antreset(dev_ant, debug) # reset dongle if debug: print("calibrate ant stick") ant.calibrate(dev_ant, debug) # calibrate ANT+ dongle if debug: print("calibrate ant stick FE-C") ant.master_channel_config(dev_ant, debug) # calibrate ANT+ channel FE-C if debug: print("calibrate ant stick HR") ant.second_channel_config(dev_ant, debug) # calibrate ANT+ channel HR if not headless: self.RunoffButton.config(state="disabled") else: print("Ctrl-C to exit") speed, cadence, power, heart_rate = (0, ) * 4 # initialise values grade = False target_power = False accumulated_power = 0 heart_beat_event_time = time.time() * 1000 heart_beat_event_time_start_cycle = time.time() * 1000 heart_toggle = 0 heart_beat_count = 0 switch = True cot_start = time.time() eventcounter = 0 # p.44 [10] general fe data, [19] eqpt type trainer, [89] acc value time since start in 0.25s r/over 64s, [8c] acc value time dist travelled in m r/over 256m, # [8d] [20] speed lsb msb 0.001m/s, [00] hr, [30] capabilities bit field accumulated_time = time.time() * 1000 distance_travelled = 0 last_dist_time = time.time() * 1000 # p.60 [19] specific trainer data, [10] counter rollover 256, [5a] inst cadence, [b0] acc power lsb, [47] acc power msb (r/over 65536W), [1b] inst power lsb, # [01] bits 0-3 inst power MSB bits 4-7 trainer status bit, [30] flags bit field last_measured_time = time.time() * 1000 try: while switch: if debug: print("Running", round(time.time() * 1000 - last_measured_time)) last_measured_time = time.time() * 1000 if eventcounter >= 256: eventcounter = 0 # TRAINER- SHOULD WRITE THEN READ 70MS LATER REALLY # ##################GET DATA FROM TRAINER################## if simulatetrainer: speed, pedecho, heart_rate, force_index, cadence = ( 20, 0, 70, 5, 90, ) else: speed, pedecho, heart_rate, force_index, cadence = trainer.receive( dev_trainer) # get data from device if speed == "Not Found": speed, pedecho, heart_rate, force_index, cadence = 0, 0, 0, 0, 0 if not headless: self.trainerVariable.set( "Cannot read from trainer") else: print("Cannot read from trainer") else: if not headless: self.trainerVariable.set("Trainer detected") # print force_index factors = pc_dict[pc_sorted_keys[force_index]] calc_power = int(speed * factors[0] + factors[1]) if calc_power < 0: calc_power = 0 if debug: print(speed, pedecho, heart_rate, force_index, cadence, calc_power) # ##################SEND DATA TO TRAINER################## # send resistance data to trainer if debug: print( datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S.%f") [:-3], "GRADE", grade, "%") # set resistance level if not grade and not target_power: # if trainer not been been set a grade or target power grade = 0 resistance_level = len( pc_dict) - 1 # set to highest by default if grade is not False: # find resistance for grade for idx, g in enumerate(sorted(pc_dict)): if g >= grade: # find resistance value immediately above grade set by zwift resistance_level = idx break elif target_power: # get resistance closest for power target if speed < 10: speed = 10 # default to at least 10 kph closest = 1000 for idx, g in enumerate(sorted(pc_dict)): # iterate up power_at_level = int(speed * pc_dict[g][0] + pc_dict[g][1]) # print idx,g,power_at_level if (target_power - power_at_level)**2 < closest**2: resistance_level = idx closest = ((target_power - power_at_level)**2)**0.5 # print resistance_level if not simulatetrainer: trainer.send(dev_trainer, resistance_level, pedecho) # time.sleep(0.2)#simulated trainer timeout # ############BROADCAST AND RECEIVE ANT+ data############## if speed == "Not Found": speed, pedecho, calc_power, cadence = 0, 0, 0, 0 if calc_power >= 4094: calc_power = 4093 accumulated_power += calc_power if accumulated_power >= 65536: accumulated_power = 0 if ( eventcounter + 1 ) % 66 == 0 or eventcounter % 66 == 0: # send first and second manufacturer's info packet newdata = "a4 09 4e 00 50 ff ff 01 0f 00 85 83 bb 00 00" elif (eventcounter + 32) % 66 == 0 or ( eventcounter + 33 ) % 66 == 0: # send first and second product info packet newdata = "a4 09 4e 00 51 ff ff 01 01 00 00 00 b2 00 00" elif eventcounter % 3 == 0: # send general fe data every 3 packets accumulated_time_counter = int( (time.time() * 1000 - accumulated_time) / 1000 / 0.25) # time since start in 0.25 seconds if (accumulated_time_counter >= 256 ): # rollover at 64 seconds (256 quarter secs) accumulated_time_counter = 0 accumulated_time = time.time() * 1000 newdata = "{0}{1}{2}".format( "a4 09 4e 00 10 19 ", hex(accumulated_time_counter)[2:].zfill(2), " 8c 8d 20 00 30 72 00 00", ) # set time distance_travelled_since_last_loop = ( (time.time() * 1000 - last_dist_time) / 1000 * speed * 1000 / 3600 ) # speed reported in kph- convert to m/s last_dist_time = time.time( ) * 1000 # reset last loop time distance_travelled += distance_travelled_since_last_loop if distance_travelled >= 256: # reset at 256m distance_travelled = 0 newdata = "{0}{1}{2}".format( newdata[:21], hex(int(distance_travelled))[2:].zfill(2), newdata[23:], ) # set distance travelled hexspeed = hex(int(speed * 1000 * 1000 / 3600))[2:].zfill(4) newdata = "{0}{1}{2}{3}{4}".format( newdata[:24], hexspeed[2:], " ", hexspeed[:2], newdata[29:]) # set speed newdata = "{0}{1}{2}".format( newdata[:36], ant.calc_checksum(newdata), newdata[38:]) # recalculate checksum else: # send specific trainer data newdata = "{0}{1}{2}".format( "a4 09 4e 00 19 ", hex(eventcounter)[2:].zfill(2), " 5a b0 47 1b 01 30 6d 00 00", ) # increment event count if cadence >= 254: cadence = 253 newdata = "{0}{1}{2}".format( newdata[:18], hex(cadence)[2:].zfill(2), newdata[20:]) # instant cadence hexaccumulated_power = hex( int(accumulated_power))[2:].zfill(4) newdata = "{0}{1}{2}{3}{4}".format( newdata[:21], hexaccumulated_power[2:], " ", hexaccumulated_power[:2], newdata[26:], ) # set accumulated power hexinstant_power = hex(int(calc_power))[2:].zfill(4) hexinstant_power_lsb = hexinstant_power[2:] newdata = "{0}{1}{2}".format( newdata[:27], hexinstant_power_lsb, newdata[29:]) # set power lsb byte hexinstant_power_msb = hexinstant_power[:2] bits_0_to_3 = bin(int(hexinstant_power_msb, 16))[2:].zfill(4) power_msb_trainer_status_byte = "0000" + bits_0_to_3 newdata = "{0}{1}{2}".format( newdata[:30], hex(int(power_msb_trainer_status_byte))[2:].zfill( 2), newdata[32:], ) # set mixed trainer data power msb byte newdata = "{0}{1}{2}".format( newdata[:36], ant.calc_checksum(newdata), newdata[38:]) # recalculate checksum if debug: print( datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S.%f") [:-3], "TRAINER DATA", newdata) reply = ant.send_ant([newdata], dev_ant, debug) # reply = [] # if rv[6:8]=="33": # rtn = {'grade' : int(rv[18:20]+rv[16:18],16) * 0.01 - 200} #7% in zwift = 3.5% grade in ANT+ matching = [s for s in reply if "a4094f0033" in s] # target resistance # 0x33 a4094f00 33 ffffffff964fff f7 is gradient message if matching: grade = ( int(matching[0][20:22] + matching[0][18:20], 16) * 0.01 - 200) target_power = False if not headless: self.SlopeVariable.set(round(grade, 1)) if not headless: self.TargetPowerVariable.set("") if debug: print(grade, matching[0]) else: matching = [s for s in reply if "a4094f0031" in s] # target watts # 0x31 a4094f00 31 ffffffffff5c02 72 is target power message in 0.25w 0x025c = 604 = 151W if matching: target_power = (int( matching[0][22:24] + matching[0][20:22], 16) / 4) grade = False if not headless: self.TargetPowerVariable.set(target_power) if not headless: self.SlopeVariable.set("") # ##################HR####################### # HR format # D00000693_-_ANT+_Device_Profile_-_Heart_Rate_Rev_2.1.pdf # [00][FF][FF][FF][55][03][01][48]p. 18 [00] bits 0:6 data page no, bit 7 toggle every 4th message, [ff][ff][ff] (reserved for page 0), [55][03] heart beat event time [lsb][ msb] rollover 64s, [01] heart beat count rollover 256, [instant heart rate]max 256 # [00][FF][FF][FF][55][03][01][48] # [00][FF][FF][FF][AA][06][02][48] # [00][FF][FF][FF][AA][06][02][48] # [80][FF][FF][FF][AA][06][02][48] # [80][FF][FF][FF][AA][06][02][48] # [80][FF][FF][FF][FF][09][03][48] # [80][FF][FF][FF][FF][09][03][48] # [00][FF][FF][FF][FF][09][03][48] # [00][FF][FF][FF][54][0D][04][48] # [00][FF][FF][FF][54][0D][04][48] # [00][FF][FF][FF][54][0D][04][48] # every 65th message send manufacturer and product info -apge 2 and page 3 # [82][0F][01][00][00][3A][12][48] - [82] page 2 with toggle on (repeat 4 times) # [83][01][01][33][4F][3F][13][48] - [83] page 3 with toggle on # if eventcounter > 40: heart_rate = 100 #comment out in production if heart_rate > 0: # i.e. heart rate belt attached if eventcounter % 4 == 0: # toggle bit every 4 counts if heart_toggle == 0: heart_toggle = 128 else: heart_toggle = 0 # check if heart beat has occurred as tacx only reports instanatenous heart rate data # last heart beat is at heart_beat_event_time # if now - heart_beat_event_time > time taken for hr to occur, trigger beat. 70 bpm = beat every 60/70 seconds if (time.time() * 1000 - heart_beat_event_time ) >= (60 / float(heart_rate)) * 1000: heart_beat_count += 1 # increment heart beat count heart_beat_event_time += ( 60 / float(heart_rate) ) * 1000 # reset last time of heart beat if (heart_beat_event_time - heart_beat_event_time_start_cycle >= 64000): # rollover every 64s heart_beat_event_time = ( time.time() * 1000 ) # reset last heart beat event heart_beat_event_time_start_cycle = ( time.time() * 1000) # reset start of cycle if heart_beat_count >= 256: heart_beat_count = 0 if heart_rate >= 256: heart_rate = 255 hex_heart_beat_time = int( (heart_beat_event_time - heart_beat_event_time_start_cycle) * 1.024) # convert ms to 1/1024 of a second hex_heart_beat_time = hex( hex_heart_beat_time)[2:].zfill(4) hr_byte_4 = hex_heart_beat_time[2:] hr_byte_5 = hex_heart_beat_time[:2] hr_byte_6 = hex(heart_beat_count)[2:].zfill(2) hr_byte_7 = hex(heart_rate)[2:].zfill(2) # data page 1,6,7 every 80s if ( eventcounter % 65 == 0 or (eventcounter + 1) % 65 == 0 or (eventcounter + 2) % 65 == 0 or (eventcounter + 3) % 65 == 0 ): # send first and second manufacturer's info packet hr_byte_0 = hex(2 + heart_toggle)[2:].zfill(2) hr_byte_1 = "0f" hr_byte_2 = "01" hr_byte_3 = "00" # [82][0F][01][00][00][3A][12][48] elif ((eventcounter + 31) % 65 == 0 or (eventcounter + 32) % 65 == 0 or (eventcounter + 33) % 65 == 0 or (eventcounter + 34) % 65 == 0 ): # send first and second product info packet hr_byte_0 = hex(3 + heart_toggle)[2:].zfill(2) hr_byte_1 = "01" hr_byte_2 = "01" hr_byte_3 = "33" # [83][01][01][33][4F][3F][13][48] elif ((eventcounter + 11) % 65 == 0 or (eventcounter + 12) % 65 == 0 or (eventcounter + 13) % 65 == 0 or (eventcounter + 44) % 65 == 0 ): # send page 0x01 cumulative operating time cot = int((time.time() - cot_start) / 2) cot_hex = hex(cot)[2:].zfill(6) hr_byte_0 = hex(1 + heart_toggle)[2:].zfill(2) hr_byte_1 = cot_hex[4:6] hr_byte_2 = cot_hex[2:4] hr_byte_3 = cot_hex[0:2] elif ((eventcounter + 21) % 65 == 0 or (eventcounter + 22) % 65 == 0 or (eventcounter + 23) % 65 == 0 or (eventcounter + 24) % 65 == 0): # send page 0x06 capabilities hr_byte_0 = hex(6 + heart_toggle)[2:].zfill(2) hr_byte_1 = "ff" hr_byte_2 = "00" hr_byte_3 = "00" elif ((eventcounter + 41) % 65 == 0 or (eventcounter + 42) % 65 == 0 or (eventcounter + 43) % 65 == 0 or (eventcounter + 44) % 65 == 0): # send page 0x07 battery hr_byte_0 = hex(7 + heart_toggle)[2:].zfill(2) hr_byte_1 = "64" hr_byte_2 = "55" hr_byte_3 = "13" else: # send page 0 hr_byte_0 = hex(0 + heart_toggle)[2:].zfill(2) hr_byte_1 = "ff" hr_byte_2 = "ff" hr_byte_3 = "ff" hrdata = ("a4 09 4e 01 " + hr_byte_0 + " " + hr_byte_1 + " " + hr_byte_2 + " " + hr_byte_3 + " " + hr_byte_4 + " " + hr_byte_5 + " " + hr_byte_6 + " " + hr_byte_7 + " 02 00 00") hrdata = ("a4 09 4e 01 " + hr_byte_0 + " " + hr_byte_1 + " " + hr_byte_2 + " " + hr_byte_3 + " " + hr_byte_4 + " " + hr_byte_5 + " " + hr_byte_6 + " " + hr_byte_7 + " " + ant.calc_checksum(hrdata) + " 00 00") time.sleep(0.125) # sleep for 125ms if debug: print( datetime.utcnow().strftime( "%Y-%m-%d %H:%M:%S.%f")[:-3], "HEART RATE", hrdata) ant.send_ant([hrdata], dev_ant, debug) # ###################wait #################### # add wait so we only send every 250ms time_to_process_loop = time.time( ) * 1000 - last_measured_time sleep_time = 0.25 - (time_to_process_loop) / 1000 if sleep_time < 0: sleep_time = 0 time.sleep(sleep_time) eventcounter += 1 if not headless: self.SpeedVariable.set(speed) self.HeartrateVariable.set(heart_rate) self.CadenceVariable.set(cadence) self.PowerVariable.set(calc_power) self.ResistanceLevelVariable.set(resistance_level) elif eventcounter % 4 == 0: print("Power %sW, HR %s, Cadence %s, Resistance %s" % ( calc_power, heart_rate, cadence, resistance_level, )) except KeyboardInterrupt: print("Stopped") ant.antreset(dev_ant, debug) # reset dongle if os.name == "posix": # close serial port to ANT stick on Linux dev_ant.close() if debug: print("stopped") if not headless: self.RunoffButton.config(state="normal") with open("user_defaults", "wb") as handle: # save defaults pickle.dump(user_defaults, handle, protocol=pickle.HIGHEST_PROTOCOL)
if 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
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()
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"