/*************************************************************************** BionxControl © 2025 -2026 christoph holzheuer christoph.holzheuer@gmail.com Using: mhs_can_drv.c © 2011 - 2023 by MHS-Elektronik GmbH & Co. KG, Germany Klaus Demlehner, klaus@mhs-elektronik.de @see www.mhs-elektronik.de Based on Bionx data type descriptions from: BigXionFlasher USB V 0.2.4 rev. 97 © 2011-2013 by Thomas Koenig @see www.bigxionflasher.org Bionx Bike Info © 2018 Thorsten Schmidt (tschmidt@ts-soft.de) @see www.ts-soft.de This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. @see https://github.com/bikemike/bionx-bikeinfo ***************************************************************************/ #include #include #include #include #include #include #include #include /** * @brief Das Mainwindow erzeugen * @param parent Das Elternwidget */ BCMainWindow::BCMainWindow(QWidget *parent) : QMainWindow(parent) { // __fix! in der Form nötig? qRegisterMetaType("BCValue"); qRegisterMetaType>("BCValueList"); #if defined(Q_OS_LINUX) // Für Touch screen: Window FRam weglassen //setWindowFlags(windowFlags() | Qt::FramelessWindowHint); #endif setupUi(this); // Wir schreiben den 'initMainWindow()' Aufruf mit Hilfe des // timers in die Event-Queue, damit er erst ausgeführt wird, // wenn das Fenster sichtbar ist. QTimer::singleShot(0, this, [this]() { initMainWindow(); }); } /** * @brief Destruktor. Räumt den Workthread auf. */ BCMainWindow::~BCMainWindow() { _worker.quit(); // Event Loop stoppen _worker.wait(); // Warten bis Thread wirklich fertig ist } /** * @brief Initialisiert alle Komponenten des MainWindows. */ void BCMainWindow::initMainWindow() { // Lambda um die buttons mit ihren Actions zu verbinden auto configureAction = [&]( QToolButton* button, QAction* action, BCDevice::ID deviceID ) { // Action an den Button binden button->setDefaultAction( action); // new way: die DeviceID muss aber explizit vom Lambda eingefanden werden. connect( action, &QAction::triggered, this, [this,deviceID]() { onShowDevicePanel( deviceID ); }); if( _devicePanels.contains(deviceID) ) { BCDeviceView* currentPanel = _devicePanels[deviceID]; // ... und ihre device ID currentPanel->setDeviceID( deviceID ); // Wenn ein Device (entspricht einem Datenmodel) fertig eingelesen wurde, // wird es weitergereicht. // Problem: alle Panels bekommen alle Datenmodelle angeboten. connect( &_dataManager, &BCXmlLoader::valueListReady, currentPanel, &BCDeviceView::onValueListReady ); connect( currentPanel->model(), SIGNAL(makeSimonHappy()), this, SLOT(onStartAnimation() ) ); } }; // Wir wollen die Devices den Views zuordnen können _devicePanels[BCDevice::ID::Console] = _consolePanel; _devicePanels[BCDevice::ID::Battery] = _batteryPanel; _devicePanels[BCDevice::ID::Motor] = _motorPanel; // Die actions an die Buttons binden configureAction(_motorButton, _motorAction, BCDevice::ID::Motor ); configureAction(_consoleButton, _consoleAction, BCDevice::ID::Console ); configureAction(_batteryButton, _batteryAction, BCDevice::ID::Battery ); initStatusBar(); _connectButton->setDefaultAction( _connectAction); _syncButton->setDefaultAction( _syncAction); connect( _connectAction, &QAction::triggered, &_transmitter, &BCTransmitter::onToggleDriverConnection ); connect( _syncAction, &QAction::triggered, this, &BCMainWindow::onSyncDeviceView ); connect( _exitButton, &QToolButton::clicked, qApp, &QCoreApplication::quit ); connect( &_transmitter, &BCTransmitter::valueUpdated, this, &BCMainWindow::onValueUpdated ); connect( this, &BCMainWindow::requestValueUpdate, &_transmitter, &BCTransmitter::onUpdateValue); connect( &_worker, &QThread::finished, &_transmitter, &QObject::deleteLater); connect( &_transmitter, &BCTransmitter::driverStateChanged, this, &BCMainWindow::onDriverStateChanged ); connect( &_transmitter, &BCTransmitter::endOfProcessing, this, &BCMainWindow::onEndOfProcessing ); connect( this, &BCMainWindow::endOfTransmission, &_transmitter, &BCTransmitter::onEndOfTransmission ); // transmitter starten _transmitter.moveToThread(&_worker); _worker.start(); try { // die Daten des eBikes laden _dataManager.loadXmlBikeData(":/bikeinfo.xml"_L1); } catch( BCException& exception ) { QMessageBox::critical( this, "Ladefehler", exception.what() ); } // Konsolendaten als erstes anzeigen _consoleAction->trigger(); //_batteryAction->trigger(); /* // Dummy sync beim starten QTimer::singleShot(1000, this, [this]() { onSyncDeviceView(); }); */ // not least _delightWidget = new BCDelightPMWidget(this); } /* // 2. Bild für den Zustand UNCHECKED (Off) hinzufügen // Das ist das Symbol, wenn NICHT verbunden ist (z.B. ein Stecker) connectIcon.addFile(":/icons/plug_disconnected.svg", QSize(), QIcon::Normal, QIcon::Off); // 3. Bild für den Zustand CHECKED (On) hinzufügen // Das ist das Symbol, wenn verbunden IST (z.B. Stecker in Dose) connectIcon.addFile(":/icons/plug_connected.svg", QSize(), QIcon::Normal, QIcon::On); */ void BCMainWindow::initStatusBar() { BCDriverStateWidget* conState = new BCDriverStateWidget(this); connect( &_transmitter, &BCTransmitter::driverStateChanged, conState, &BCDriverStateWidget::onDriverStateChanged ); connect( conState, &BCDriverStateWidget::clicked, _connectAction, &QAction::trigger ); _statusBar->addPermanentWidget(conState); conState->installEventFilter(this); BCThemeSwitchButton* themeBtn = new BCThemeSwitchButton(this); _statusBar->addPermanentWidget(themeBtn); connect(themeBtn, &BCThemeSwitchButton::themeChanged, this, [this](bool isDark) { QString message = isDark ? "using DarkMode." : "using LightMode."; onShowMessage( message ); setApplicationStyleSheet( isDark ? cDarkModeStyle : cLightModeStyle ); }); // Wir starten im light mode //themeBtn->setDarkMode( false ); onShowMessage("Ready. (Using dummy driver)"); setApplicationStyleSheet(cLightModeStyle); } /* bool BCMainWindow::eventFilter(QObject *obj, QEvent *event) { if (obj == myWidget && event->type() == QEvent::MouseButtonRelease) { QMouseEvent *mouseEvent = static_cast(event); if (mouseEvent->button() == Qt::LeftButton) { myAction->trigger(); return true; // Event wurde verarbeitet } } return QObject::eventFilter(obj, event); } */ /** * @brief Setzt das Stylesheet, hier: Dark- oder Lightmode * @param path Der Pfad zuum Stylesheet * @return */ bool BCMainWindow::setApplicationStyleSheet( QAnyStringView path ) { QFile styleFile( path.toString() ); if (styleFile.open(QIODevice::ReadOnly | QIODevice::Text)) { QString style = styleFile.readAll(); qApp->setStyleSheet(style); styleFile.close(); return false; } qWarning() << "Konnte Stylesheet nicht laden:" << styleFile.errorString(); //qApp->setStyleSheet(" "); return true; } /** * @brief Setzt den Headerlabel ( == die Device-Bezeichnung ) * @param headerLabel Der headerLabel */ void BCMainWindow::setHeaderLabel( const QString& headerText) { _headerLabel->setText( " BionxControl: " + headerText ); } void BCMainWindow::onShowMessage( const QString& message, int timeOut ) { _statusBar->showMessage( message, timeOut ); } void BCMainWindow::onStartAnimation() { _delightWidget->onStartChaos(); } void BCMainWindow::onDriverStateChanged( BCDriver::DriverState state, const QString& message ) { Q_UNUSED(state) onShowMessage( message, 8000 ); } void BCMainWindow::onShowDevicePanel( BCDevice::ID deviceID ) { if( _devicePanels.contains( deviceID ) ) { BCDeviceView* nxtPanel = _devicePanels[deviceID]; if( nxtPanel != _currentPanel ) { _currentPanel = nxtPanel; setHeaderLabel( _currentPanel->property( cBCKeyHeaderLabel ).toString() ); _stackedWidget->setCurrentWidget( _currentPanel ); if( _currentPanel->firstExpose() ) { // Dummy sync beim starten QTimer::singleShot(1000, this, [this]() { onSyncDeviceView(); }); } // knopf auch abschalten? } } } /** * @brief SLOT, wird aufgerufen, wenn der Treiber eine frischen Wert abgeholt hat. */ void BCMainWindow::onValueUpdated(BCDevice::ID deviceID, int index, BCValue::Flags newState, uint32_t rawValue ) { if( _devicePanels.contains( deviceID ) ) { BCDeviceView& panel = *_devicePanels[deviceID]; panel.updateValue( index, newState, rawValue ); } } /** * @brief SLOT, wird aufgerufen, wenn der Treiber die Datenübertrgeung beendet hat. */ void BCMainWindow::onEndOfProcessing() { _syncButton->setEnabled( true ); onShowMessage( "Synchronization complete."); } /** * @brief SLOT, der aufgerufen wird, um das akutelle Device (Battery, Motor, ... ) * zu synchronisieren, d.h. die aktuellen Werte über den CAN-Bus abzufragen. */ void BCMainWindow::onSyncDeviceView() { Q_ASSERT_X(_currentPanel, "onSyncDeviceView()", "_currentpanel ist null!"); const BCValueList& currentList =_currentPanel->getValueList(); // wir schalten den Sync-Button hier ab, // wenn der Autrag bearbeitet wurde, wird der // Button wieder eingeschaltet. _syncButton->setEnabled( false ); QString devName = _currentPanel->property( cBCKeyHeaderLabel ).toString(); onShowMessage( "Reading: " + devName ); for( const BCValuePtr& value : currentList ) { // wir setzen auf 'lesen' value->setFlag( BCValue::Flag::ReadMe ); // statt '_transmitter.onUpdateValue( value )' müssen wir hier // über emit requestValueUpdate() zur Thread sysnchronisation // entkoppeln, emit requestValueUpdate( value); } emit endOfTransmission(); }