diff --git a/pythoncode/FortiusAntGui.py b/pythoncode/FortiusAntGui.py index 83042d77..1cca7a15 100644 --- a/pythoncode/FortiusAntGui.py +++ b/pythoncode/FortiusAntGui.py @@ -1,7 +1,10 @@ #------------------------------------------------------------------------------- # Version info #------------------------------------------------------------------------------- -__version__ = "2022-01-05" +__version__ = "2022-01-24" +# 2024-01-24 #456 Addition of an overlay window with gearing info +# Target grade is displayed with one decimal +# #456 When power exceeds the display, it's resized # 2022-01-05 MinHeigth/MinWidth replaced by Size() for consistency # 2022-01-04 text-fields on top of other controls were flickering, because # the parent was not the on-top control #353 @@ -473,24 +476,8 @@ def __init__(self, parent, pclv): self.Power.SetMiddleTextFont(wx.Font(MiddleTextFontSize, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD)) # Assign A Font To The Center Text - Min = 0 - NrIntervals = 10 - Step = 40 - Max = Min + Step * NrIntervals - self.PowerMax = Max - self.PowerArray = numpy.array([0,0,0,0,0,0,0,0,0,0]) # Array for running everage - - intervals = range(Min, Max+1, Step) # Create The Intervals That Will Divide Our self.SpeedMeter In Sectors - self.Power.SetIntervals(intervals) - -# colours = [wx.BLACK] * NrIntervals # Assign The Same Colours To All Sectors (We Simulate A Car Control For self.Speed) -# self.Power.SetIntervalColours(colours) - - ticks = [str(interval) for interval in intervals] # Assign The Ticks: Here They Are Simply The String Equivalent Of The Intervals - self.Power.SetTicks(ticks) - self.Power.SetTicksColour(wx.WHITE) # Set The Ticks/Tick Markers Colour - self.Power.SetNumberOfSecondaryTicks(5) # We Want To Draw 5 Secondary Ticks Between The Principal Ticks - + self.PowerMax = 0 + self.DefinePowerMeter(400) self.Power.SetTicksFont(wx.Font(TicksFontSize, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) # Set The Font For The Ticks Markers @@ -591,6 +578,11 @@ def __init__(self, parent, pclv): self.txtCassette.SetBackgroundColour(bg) self.txtCassette.SetPosition(( self.CassetteX + self.CassetteWH + Margin, self.CassetteY)) + # ---------------------------------------------------------------------- + #456 Create Gearbox Overlay + # ---------------------------------------------------------------------- + self.GearboxOverlay = clsGearboxOverlay(self, self.txtCranckset, self.txtCassette, TextCtrlFont) + # ---------------------------------------------------------------------- # Add a radar graph for pedal stroke analysis # - To the right of the HeartRate text @@ -694,6 +686,49 @@ def __init__(self, parent, pclv): self.SetDoubleBuffered(True) + # -------------------------------------------------------------------------- + # D e f i n e P o w e r M e t e r (created in #456) + # -------------------------------------------------------------------------- + # Define layout of power meter, based upon provided power + # Initially, 400Watt was max of the power meter, but some guys want more... + # -------------------------------------------------------------------------- + def DefinePowerMeter(self, iPower): + if iPower > self.PowerMax: + if iPower > 800: + Min = 0 + NrIntervals = 10 + Step = 100 # max = 1000 + Ticks = 4 + elif iPower > 600: + Min = 0 + NrIntervals = 8 + Step = 100 # max = 800 + Ticks = 4 + elif iPower > 400: + Min = 0 + NrIntervals = 12 + Step = 50 # max = 600 + Ticks = 4 + else: + Min = 0 + NrIntervals = 10 + Step = 40 # max = 400 + Ticks = 5 + + Max = Min + Step * NrIntervals + self.PowerMax = Max + self.PowerArray = numpy.array([0,0,0,0,0,0,0,0,0,0]) # Array for running everage + + intervals = range(Min, Max+1, Step) # Create The Intervals That Will Divide Our self.SpeedMeter In Sectors + self.Power.SetIntervals(intervals) + +# colours = [wx.BLACK] * NrIntervals # Assign The Same Colours To All Sectors (We Simulate A Car Control For self.Speed) +# self.Power.SetIntervalColours(colours) + + ticks = [str(interval) for interval in intervals] # Assign The Ticks: Here They Are Simply The String Equivalent Of The Intervals + self.Power.SetTicks(ticks) + self.Power.SetTicksColour(wx.WHITE) # Set The Ticks/Tick Markers Colour + self.Power.SetNumberOfSecondaryTicks(Ticks) # We Want To Draw 5 Secondary Ticks Between The Principal Ticks # -------------------------------------------------------------------------- # F u n c t i o n s -- to be provided by subclass. @@ -946,6 +981,8 @@ def SetValuesGUI(self, fSpeed, iRevs, iPower, iTargetMode, iTargetPower, fTarget self.Revs.SetSpeedValue (float(min(max(0, iRevs), self.RevsMax ))) self.Power.SetSpeedValue(float(min(max(0, iPowerMean), self.PowerMax))) + self.DefinePowerMeter(iPowerMean) # This may redefine the scale + if ForceRefreshDC: # Alternating suffix makes the texts being refreshed suffix1 = '.' # str(0x32) # space @@ -989,7 +1026,7 @@ def SetValuesGUI(self, fSpeed, iRevs, iPower, iTargetMode, iTargetPower, fTarget self.txtTarget.SetValue(fTargetPower % iTargetPower + suffix) elif iTargetMode == mode_Grade: - s = "%2.0f%%" % fTargetGrade + s = "%.1f%%" % fTargetGrade #456 s += "%iW" % iTargetPower # Target power added for reference # Can be negative! self.txtTarget.SetValue(s + suffix) @@ -1083,6 +1120,12 @@ def SetValuesGUI(self, fSpeed, iRevs, iPower, iTargetMode, iTargetPower, fTarget self.txtCassette.Hide() bRefreshRequired = True + # ---------------------------------------------------------------------- + #456 Show the values in the GearboxOverlay + # ---------------------------------------------------------------------- + if self.CrancksetIndex != None and self.CassetteIndex != None: + self.GearboxOverlay.SetValues(self.IsActive(), self.txtCranckset.GetValue(), self.txtCassette.GetValue()) + # ---------------------------------------------------------------------- # Refresh if required; so that JPGs are drawn in the OnPaint() event # ---------------------------------------------------------------------- @@ -1530,6 +1573,94 @@ def OnClose(self, event): else: # No thread is running; event.Skip() # Do default actions (stop program) +# ------------------------------------------------------------------------------ +# Create the GearBox overlay frame (added #456) +# ------------------------------------------------------------------------------ +# Note: Every window has it's own class, because on creation, the object +# get's the required behaviour, so no two frames can (or should) +# be created in the same class... +# +# GearBoxOverlay +# Meant to be overlayed to the CTP window to keep track of the virtual gearing +# +# The frame is created, transparent/on top but hidden, containing the +# required fields; crackset and cassette. +# If the gearbox is used (the values change) then the frame is +# "active" and may become visible. +# When the FortiusAntGui is active, the GearBoxOverlay is not required +# and remains hidden, as soon as the Gui is NOT active, the overlay +# is show. +# When the user closes the overlay, however, it will disappear and +# not return again. +# +# ------------------------------------------------------------------------------ +class clsGearboxOverlay(wx.Frame): + bGearboxOverlayActive = False + bGearboxOverlayClosed = False + PreviousValues = None + def __init__(self, parent, pCranckset, pCassette, pTextCtrlFont): + # ---------------------------------------------------------------------- + # Create frame + # ---------------------------------------------------------------------- + frameX = 2 * Margin + pCranckset.Size[0] + 15 + frameY = 3 * Margin + 2 * pCranckset.Size[1] + 39 + + style = wx.STAY_ON_TOP | wx.FRAME_TOOL_WINDOW | wx.CAPTION | wx.DEFAULT_FRAME_STYLE + wx.Frame.__init__(self, None, title='Gearbox', size = (frameX,frameY), style = style) + self.Bind(wx.EVT_CLOSE, self.OnClose) + + self.SetTransparent( 140 ) # 0 = fully transparent, 255=fully visible + self.SetPosition((10, 10)) + + # ---------------------------------------------------------------------- + # self.Cranckset + # ---------------------------------------------------------------------- + self.txtCranckset = wx.TextCtrl(self, value="--", size=pCranckset.Size, style=wx.TE_CENTER | wx.TE_READONLY) + self.txtCranckset.SetBackgroundColour(bg) + self.txtCranckset.SetPosition(( Margin, Margin)) + + # ---------------------------------------------------------------------- + # self.Cassette + # ---------------------------------------------------------------------- + self.txtCassette = wx.TextCtrl(self, value="--", size=pCassette.Size, style=wx.TE_CENTER | wx.TE_READONLY) + self.txtCassette.SetBackgroundColour(bg) + self.txtCassette.SetPosition(( Margin, self.txtCranckset.Position[1] + pCranckset.Size[1] + Margin)) + + self.txtCranckset.SetFont(pTextCtrlFont) + self.txtCassette.SetFont(pTextCtrlFont) + + def OnClose(self, event): + if True: + self.Hide() # Close not allowed + self.bGearboxOverlayClosed = True # But do not show again + else: + event.Skip() # Do default actions + + def SetValues(self, bFortiusAndGuiIsActive, sCranckset, sCassette): + # ---------------------------------------------------------------------- + # If values are provided, different from previous, the overlay is + # activated (so users, not using the gearbox will never see it) + # ---------------------------------------------------------------------- + if self.PreviousValues != None and (sCranckset, sCassette) != self.PreviousValues: + self.bGearboxOverlayActive = True + self.PreviousValues = (sCranckset, sCassette) + + # ---------------------------------------------------------------------- + # If the user closes the overlay, it will not come back + # ---------------------------------------------------------------------- + if not self.bGearboxOverlayActive or self.bGearboxOverlayClosed: + self.Hide() + else: + # ------------------------------------------------------------------ + # The overlay is visible when FortiusAntGui is not active + # ------------------------------------------------------------------ + if bFortiusAndGuiIsActive: + self.Hide() + else: + self.txtCranckset.SetValue (sCranckset) + self.txtCassette.SetValue (sCassette) + self.Show() + # ------------------------------------------------------------------------------ # our normal wxApp-derived class, as usual # ------------------------------------------------------------------------------ diff --git a/pythoncode/antDongle.py b/pythoncode/antDongle.py index 37defb86..2a3c7e46 100644 --- a/pythoncode/antDongle.py +++ b/pythoncode/antDongle.py @@ -2,9 +2,11 @@ # Version info #--------------------------------------------------------------------------- __version__ = "2024-01-22" -# 2024-01-23 #381 Weight should be positive -# #381 HRM is searched for infinitely -# #381 HRM is transmitted through FE-C +# 2024-01-23 #381/1 Weight should be positive and <= 255 +# #381/2 HRM is searched for infinitely +# This is implemented for all slaves, for consistency. +# I hope it improves quality, if not clearly marked to remove. +# #381/3 HRM is transmitted through FE-C # 2023-03-15 Even when there is no ANT-dongle, the message queue must be # created, so that MessageQueueSize() returns zero. # 2022-08-22 Data from the ANT dongle is stored in a queue. @@ -928,6 +930,7 @@ def SlavePair_ChannelConfig(self, channel_pair, \ msg45_ChannelRfFrequency (channel_pair, RfFrequency_2457Mhz), msg43_ChannelPeriod (channel_pair, ChannelPeriod=0x1f86), msg60_ChannelTransmitPower (channel_pair, TransmitPower_0dBm), + msg44_ChannelSearchTimeout (channel_pair, 255), #381/2 Analogously to other slaves; I hope it improves msg4B_OpenChannel (channel_pair) ] self.Write(messages) # 2021-04-15 ", True, False" removed because it's inconsistent @@ -960,6 +963,7 @@ def SlaveTrainer_ChannelConfig(self, DeviceNumber): msg45_ChannelRfFrequency (channel_FE_s, RfFrequency_2457Mhz), msg43_ChannelPeriod (channel_FE_s, ChannelPeriod=8192), # 4 Hz msg60_ChannelTransmitPower (channel_FE_s, TransmitPower_0dBm), + msg44_ChannelSearchTimeout (channel_FE_s, 255), #381/2 Analogously to other slaves; I hope it improves msg4B_OpenChannel (channel_FE_s), msg4D_RequestMessage (channel_FE_s, msgID_ChannelID) ] @@ -994,7 +998,7 @@ def SlaveHRM_ChannelConfig(self, DeviceNumber): msg51_ChannelID (channel_HRM_s, DeviceNumber, DeviceTypeID_HRM, TransmissionType_Pairing), msg45_ChannelRfFrequency (channel_HRM_s, RfFrequency_2457Mhz), msg43_ChannelPeriod (channel_HRM_s, ChannelPeriod=8070), # 4,06 Hz - msg44_ChannelSearchTimeout (channel_HRM_s, 255), #381 Search infinitely for HRM + msg44_ChannelSearchTimeout (channel_HRM_s, 255), #381/2 Search infinitely for HRM msg60_ChannelTransmitPower (channel_HRM_s, TransmitPower_0dBm), msg4B_OpenChannel (channel_HRM_s), msg4D_RequestMessage (channel_HRM_s, msgID_ChannelID) @@ -1042,6 +1046,7 @@ def SlaveSCS_ChannelConfig(self, DeviceNumber): msg42_AssignChannel (channel_SCS_s, ChannelType_BidirectionalReceive, NetworkNumber=0x00), msg51_ChannelID (channel_SCS_s, DeviceNumber, DeviceTypeID_SCS, TransmissionType_Pairing), msg45_ChannelRfFrequency (channel_SCS_s, RfFrequency_2457Mhz), + msg44_ChannelSearchTimeout (channel_SCS_s, 255), #381/2 Analogously to other slaves; I hope it improves msg43_ChannelPeriod (channel_SCS_s, ChannelPeriod=8086), # 4,05 Hz msg60_ChannelTransmitPower (channel_SCS_s, TransmitPower_0dBm), msg4B_OpenChannel (channel_SCS_s), @@ -1156,6 +1161,7 @@ def SlaveVHU_ChannelConfig(self, DeviceNumber): # Listen to a Tacx Vortex He msg45_ChannelRfFrequency (channel_VHU_s, RfFrequency_2478Mhz), msg43_ChannelPeriod (channel_VHU_s, ChannelPeriod=0x0f00), msg60_ChannelTransmitPower (channel_VHU_s, TransmitPower_0dBm), + msg44_ChannelSearchTimeout (channel_VHU_s, 255), #381/2 Analogously to other slaves; I hope it improves msg4B_OpenChannel (channel_VHU_s), msg4D_RequestMessage (channel_VHU_s, msgID_ChannelID) ] @@ -1170,13 +1176,14 @@ def SlaveBLTR_ChannelConfig(self, DeviceNumber): # Listen to a Tacx Blacktrack logfile.Console('FortiusANT receives data from an ANT Tacx BlackTrack steering unit (BLTR)' + s) if debug.on(debug.Data1): logfile.Write("SlaveBLTR_ChannelConfig()") messages = [ - msg42_AssignChannel(channel_BLTR_s, ChannelType_BidirectionalReceive, NetworkNumber=0x01), - msg51_ChannelID(channel_BLTR_s, DeviceNumber, DeviceTypeID_BLTR, TransmissionType_IC), - msg45_ChannelRfFrequency(channel_BLTR_s, RfFrequency_2460Mhz), - msg43_ChannelPeriod(channel_BLTR_s, ChannelPeriod=0x2000), - msg60_ChannelTransmitPower(channel_BLTR_s, TransmitPower_0dBm), - msg4B_OpenChannel(channel_BLTR_s), - msg4D_RequestMessage(channel_BLTR_s, msgID_ChannelID) + msg42_AssignChannel (channel_BLTR_s, ChannelType_BidirectionalReceive, NetworkNumber=0x01), + msg51_ChannelID (channel_BLTR_s, DeviceNumber, DeviceTypeID_BLTR, TransmissionType_IC), + msg45_ChannelRfFrequency (channel_BLTR_s, RfFrequency_2460Mhz), + msg43_ChannelPeriod (channel_BLTR_s, ChannelPeriod=0x2000), + msg60_ChannelTransmitPower (channel_BLTR_s, TransmitPower_0dBm), + msg44_ChannelSearchTimeout (channel_BLTR_s, 255), #381/2 Analogously to other slaves; I hope it improves + msg4B_OpenChannel (channel_BLTR_s), + msg4D_RequestMessage (channel_BLTR_s, msgID_ChannelID) ] self.Write(messages) @@ -2063,7 +2070,7 @@ def msgUnpage173_01_TacxVortexHU_SerialMode (info): def msgPage220_01_TacxGeniusSetTarget (Channel, Mode, Target, Weight): DataPageNumber = 220 SubPageNumber = 0x01 - Weight = int(min(0xff, Weight)) #381 Avoid negative weigth + Weight = int(max(0,min(0xff, Weight))) #381/1 Avoid negative weigth if Mode == GNS_Mode_Slope: Target = int(Target * 10) else: @@ -2247,7 +2254,7 @@ def msgPage16_GeneralFEdata (Channel, ElapsedTime, DistanceTravelled, Speed, Hea # Old: Capabilities = 0x30 | 0x03 | 0x00 | 0x00 # IN_USE | HRM | Distance | Speed # bit 7......0 #185 Rewritten as below for better documenting bit-pattern # HRM = 0b00000011 # 0b____ __xx bits 0-1 3 = hand contact sensor (2020-12-28: Unclear why this option chosen) - HRM = 0b00000001 # 0b____ __xx bits 0-1 1 = HRM (2024-01-22: #381 transmit HRM through FE-C) + HRM = 0b00000001 # 0b____ __xx bits 0-1 1 = HRM (2024-01-22: #381/3 transmit HRM through FE-C) Distance = 0b00000000 # 0b____ _x__ bit 2 0 = No distance in byte 3 (2020-12-28: Unclear why this option chosen) VirtualSpeedFlag = 0b00000000 # 0b____ x___ bit 3 0 = Real speed in byte 4/5 (2020-12-28: Could be virtual speed) FEstate = 0b00110000 # 0b_xxx ____ bits 4-6 3 = IN USE