1+ import os
2+ import signal
3+ import sys
4+
15from modules .app_logger import app_logger
6+ from modules .utils .cmd import start_process
27from modules ._qt_qtwidgets import QtCore , QtWidgets , QtGui
38from .pyqt_menu_widget import MenuWidget , ListWidget , ListItemWidget
49
@@ -9,6 +14,7 @@ def setup_menu(self):
914 button_conf = (
1015 # Name(page_name), button_attribute, connected functions, layout
1116 ("ANT+ Sensors" , "submenu" , self .ant_sensors_menu ),
17+ ("BLE Sensors" , "submenu" , self .ble_sensors_menu ),
1218 ("Wheel Size" , "submenu" , self .adjust_wheel_circumference ),
1319 ("Auto Light" , "toggle" , lambda : self .onoff_auto_light (True )),
1420 ("Auto Stop" , None , None ),
@@ -27,6 +33,9 @@ def ant_sensors_menu(self):
2733 return
2834 self .change_page ("ANT+ Sensors" )
2935
36+ def ble_sensors_menu (self ):
37+ self .change_page ("BLE Sensors" )
38+
3039 def adjust_wheel_circumference (self ):
3140 self .change_page ("Wheel Size" , preprocess = True )
3241
@@ -38,6 +47,7 @@ def onoff_auto_light(self, change=True):
3847 self .config .G_ANT ["USE_AUTO_LIGHT" ] = not self .config .G_ANT ["USE_AUTO_LIGHT" ]
3948 self .buttons ["Auto Light" ].change_toggle (self .config .G_ANT ["USE_AUTO_LIGHT" ])
4049
50+
4151class ANTMenuWidget (MenuWidget ):
4252 def setup_menu (self ):
4353 button_conf = []
@@ -186,3 +196,130 @@ def setup_ui(self):
186196 # outer layout (custom)
187197 self .outer_layout .insertWidget (0 , icon )
188198 self .enter_signal .connect (self .parentWidget ().button_func )
199+
200+
201+ class BLEMenuWidget (MenuWidget ):
202+ fake_trainer_proc = None
203+
204+ def setup_menu (self ):
205+ button_conf = (
206+ # Name(page_name), button_attribute, connected functions, layout
207+ ("Zwift Click V2" , "toggle" , lambda : self .onoff_zwift_click_v2 (True )),
208+ ("Fake Trainer for Zwift" , "toggle" , lambda : self .onoff_fake_trainer (True )),
209+ )
210+ self .add_buttons (button_conf )
211+ self .onoff_zwift_click_v2 (False )
212+ #self.onoff_fake_trainer(False)
213+
214+ #if self.config.logger.sensor.sensor_ble.enabled():
215+ # self.buttons["Zwift Click V2"].disable()
216+
217+ if self .config .ble_uart is None :
218+ self .buttons ["Fake Trainer for Zwift" ].disable ()
219+
220+ def onoff_zwift_click_v2 (self , change = True ):
221+ if change :
222+ self .config .G_ZWIFT_CLICK_V2 ["STATUS" ] = not self .config .G_ZWIFT_CLICK_V2 ["STATUS" ]
223+ sensor_ble = self .config .logger .sensor .sensor_ble
224+ if self .config .G_ZWIFT_CLICK_V2 ["STATUS" ]:
225+ if not sensor_ble .connect_zwift_click_v2 ():
226+ app_logger .warning ("Zwift Click V2 toggle skipped: BLE not enabled" )
227+ else :
228+ sensor_ble .disconnect_zwift_click_v2 ()
229+ self .buttons ["Zwift Click V2" ].change_toggle (self .config .G_ZWIFT_CLICK_V2 ["STATUS" ])
230+
231+ def onoff_fake_trainer (self , change = True ):
232+ if change :
233+ if self ._fake_trainer_is_running ():
234+ self ._stop_fake_trainer_process ()
235+ else :
236+ started = self ._start_fake_trainer_process ()
237+ if not started :
238+ self ._stop_fake_trainer_process ()
239+ self .buttons ["Fake Trainer for Zwift" ].change_toggle (self ._fake_trainer_is_running ())
240+
241+ def on_back_menu (self ):
242+ self ._stop_fake_trainer_process ()
243+ self .buttons ["Fake Trainer for Zwift" ].change_toggle (False )
244+
245+ def _fake_trainer_is_running (self ):
246+ proc = self .fake_trainer_proc
247+ return proc is not None and proc .poll () is None
248+
249+ def _start_fake_trainer_process (self ):
250+ if self ._fake_trainer_is_running ():
251+ return True
252+
253+ fake_trainer_path = os .path .abspath (
254+ os .path .join (
255+ os .path .dirname (__file__ ),
256+ ".." ,
257+ ".." ,
258+ "sensor" ,
259+ "ble" ,
260+ "fake_trainer.py" ,
261+ )
262+ )
263+ if not os .path .isfile (fake_trainer_path ):
264+ app_logger .warning (f"Fake trainer not found: { fake_trainer_path } " )
265+ self .buttons ["Fake Trainer for Zwift" ].change_toggle (False )
266+ return False
267+
268+ try :
269+ self .fake_trainer_proc = start_process (
270+ [sys .executable , fake_trainer_path ],
271+ cmd_print = False ,
272+ )
273+ except Exception as exc :
274+ app_logger .warning (f"Fake trainer start failed: { exc } " )
275+ self .fake_trainer_proc = None
276+ self .buttons ["Fake Trainer for Zwift" ].change_toggle (False )
277+ return False
278+
279+ if self .fake_trainer_proc .poll () is not None :
280+ app_logger .warning ("Fake trainer start failed: process exited early" )
281+ self .fake_trainer_proc = None
282+ self .buttons ["Fake Trainer for Zwift" ].change_toggle (False )
283+ return False
284+
285+ QtCore .QTimer .singleShot (200 , self ._check_fake_trainer_process )
286+ app_logger .info ("Fake trainer started" )
287+ return True
288+
289+ def _check_fake_trainer_process (self ):
290+ if self ._fake_trainer_is_running ():
291+ return
292+ self .fake_trainer_proc = None
293+ self .buttons ["Fake Trainer for Zwift" ].change_toggle (False )
294+ app_logger .warning ("Fake trainer stopped unexpectedly" )
295+
296+ def _stop_fake_trainer_process (self ):
297+ proc = self .fake_trainer_proc
298+ if proc is None :
299+ return
300+ if proc .poll () is not None :
301+ self .fake_trainer_proc = None
302+ return
303+
304+ try :
305+ proc .send_signal (signal .SIGINT )
306+ proc .wait (timeout = 1.0 )
307+ except Exception :
308+ pass
309+
310+ if proc .poll () is None :
311+ try :
312+ proc .terminate ()
313+ proc .wait (timeout = 1.0 )
314+ except Exception :
315+ pass
316+
317+ if proc .poll () is None :
318+ try :
319+ proc .kill ()
320+ proc .wait (timeout = 1.0 )
321+ except Exception :
322+ pass
323+
324+ self .fake_trainer_proc = None
325+ app_logger .info ("Fake trainer stopped" )
0 commit comments