Skip to content

Commit

Permalink
#456 Gearbox overlay
Browse files Browse the repository at this point in the history
  • Loading branch information
WouterJD committed Jan 27, 2024
1 parent caaf3b3 commit c73fb46
Show file tree
Hide file tree
Showing 2 changed files with 171 additions and 33 deletions.
171 changes: 151 additions & 20 deletions pythoncode/FortiusAntGui.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
# ----------------------------------------------------------------------
Expand Down Expand Up @@ -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
# ------------------------------------------------------------------------------
Expand Down
33 changes: 20 additions & 13 deletions pythoncode/antDongle.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
]
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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)
]
Expand All @@ -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)

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit c73fb46

Please sign in to comment.