Files
BionxControl/bcmainwindow.cpp

356 lines
10 KiB
C++
Raw Permalink Normal View History

2025-12-15 20:57:09 +01:00
/***************************************************************************
2025-12-26 14:07:55 +01:00
BionxControl
2026-01-03 23:51:14 +01:00
© 2025 -2026 christoph holzheuer
2025-12-26 14:07:55 +01:00
christoph.holzheuer@gmail.com
2025-12-15 20:57:09 +01:00
2025-12-26 14:07:55 +01:00
Using:
2025-12-15 20:57:09 +01:00
2025-12-26 14:07:55 +01:00
mhs_can_drv.c
© 2011 - 2023 by MHS-Elektronik GmbH & Co. KG, Germany
Klaus Demlehner, klaus@mhs-elektronik.de
@see www.mhs-elektronik.de
2025-12-15 20:57:09 +01:00
2025-12-26 14:07:55 +01:00
Based on Bionx data type descriptions from:
2025-12-15 20:57:09 +01:00
2025-12-26 14:07:55 +01:00
BigXionFlasher USB V 0.2.4 rev. 97
© 2011-2013 by Thomas Koenig <info@bigxionflasher.org>
@see www.bigxionflasher.org
2025-12-15 20:57:09 +01:00
2025-12-26 14:07:55 +01:00
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
***************************************************************************/
2025-12-15 20:57:09 +01:00
2026-01-05 23:39:45 +01:00
#include <QFile>
2026-01-01 02:40:12 +01:00
#include <QTimer>
2026-01-08 14:55:47 +01:00
#include <QMessageBox>
2025-12-29 15:44:06 +01:00
2026-04-03 22:28:07 +02:00
#include <bcthemebutton.h>
#include <bcdriverstatewidget.h>
2025-12-15 20:57:09 +01:00
#include <bcmainwindow.h>
2026-01-10 22:18:54 +01:00
#include <bcvaluedelegate.h>
2025-12-15 20:57:09 +01:00
#include <ui_bcmainwindow.h>
2025-12-19 21:20:14 +01:00
2025-12-26 14:07:55 +01:00
/**
* @brief Das Mainwindow erzeugen
2025-12-29 15:44:06 +01:00
* @param parent Das Elternwidget
2025-12-26 14:07:55 +01:00
*/
2025-12-19 21:20:14 +01:00
2025-12-15 20:57:09 +01:00
BCMainWindow::BCMainWindow(QWidget *parent)
: QMainWindow(parent)
{
2026-01-01 13:28:17 +01:00
// __fix! in der Form nötig?
2025-12-28 22:48:18 +01:00
qRegisterMetaType<BCValue>("BCValue");
qRegisterMetaType<QList<BCValue>>("BCValueList");
2025-12-27 18:43:15 +01:00
2026-01-07 22:20:39 +01:00
#if defined(Q_OS_LINUX)
// Für Touch screen: Window FRam weglassen
//setWindowFlags(windowFlags() | Qt::FramelessWindowHint);
#endif
2025-12-26 23:09:53 +01:00
2026-01-07 22:20:39 +01:00
setupUi(this);
2026-01-03 00:09:26 +01:00
2026-01-01 13:28:17 +01:00
// 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]()
{
2026-01-01 02:40:12 +01:00
initMainWindow();
});
2025-12-26 23:09:53 +01:00
}
2025-12-29 15:44:06 +01:00
/**
* @brief Destruktor. Räumt den Workthread auf.
*/
2025-12-26 23:09:53 +01:00
BCMainWindow::~BCMainWindow()
{
_worker.quit(); // Event Loop stoppen
_worker.wait(); // Warten bis Thread wirklich fertig ist
2025-12-26 23:09:53 +01:00
}
2025-12-29 15:44:06 +01:00
/**
* @brief Initialisiert alle Komponenten des MainWindows.
*/
void BCMainWindow::initMainWindow()
2025-12-26 23:09:53 +01:00
{
2026-01-01 13:28:17 +01:00
// Lambda um die buttons mit ihren Actions zu verbinden
2025-12-31 13:38:46 +01:00
auto configureAction = [&]( QToolButton* button, QAction* action, BCDevice::ID deviceID )
2025-12-26 23:09:53 +01:00
{
2025-12-26 23:37:15 +01:00
// Action an den Button binden
2025-12-26 23:09:53 +01:00
button->setDefaultAction( action);
2025-12-26 23:37:15 +01:00
// new way: die DeviceID muss aber explizit vom Lambda eingefanden werden.
connect( action, &QAction::triggered, this, [this,deviceID]()
2026-01-07 22:20:39 +01:00
{
onShowDevicePanel( deviceID );
});
2025-12-27 18:43:15 +01:00
2025-12-26 23:37:15 +01:00
if( _devicePanels.contains(deviceID) )
2026-01-10 16:38:52 +01:00
{
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() ) );
}
2025-12-26 23:09:53 +01:00
};
// Wir wollen die Devices den Views zuordnen können
_devicePanels[BCDevice::ID::Console] = _consolePanel;
_devicePanels[BCDevice::ID::Battery] = _batteryPanel;
_devicePanels[BCDevice::ID::Motor] = _motorPanel;
2025-12-22 00:14:42 +01:00
2026-01-07 22:20:39 +01:00
// Die actions an die Buttons binden
2025-12-31 13:38:46 +01:00
configureAction(_motorButton, _motorAction, BCDevice::ID::Motor );
configureAction(_consoleButton, _consoleAction, BCDevice::ID::Console );
configureAction(_batteryButton, _batteryAction, BCDevice::ID::Battery );
2026-01-05 23:39:45 +01:00
2026-01-03 23:51:14 +01:00
initStatusBar();
2026-01-06 22:39:41 +01:00
_connectButton->setDefaultAction( _connectAction);
_syncButton->setDefaultAction( _syncAction);
connect( _connectAction, &QAction::triggered, &_transmitter, &BCTransmitter::onToggleDriverConnection );
connect( _syncAction, &QAction::triggered, this, &BCMainWindow::onSyncDeviceView );
2026-01-06 13:05:19 +01:00
connect( _exitButton, &QToolButton::clicked, qApp, &QCoreApplication::quit );
2025-12-29 00:04:15 +01:00
2026-01-06 13:05:19 +01:00
connect( &_transmitter, &BCTransmitter::valueUpdated, this, &BCMainWindow::onValueUpdated );
connect( this, &BCMainWindow::requestValueUpdate, &_transmitter, &BCTransmitter::onUpdateValue);
connect( &_worker, &QThread::finished, &_transmitter, &QObject::deleteLater);
2026-01-03 00:09:26 +01:00
connect( &_transmitter, &BCTransmitter::driverStateChanged, this, &BCMainWindow::onDriverStateChanged );
2026-01-08 19:05:07 +01:00
connect( &_transmitter, &BCTransmitter::endOfProcessing, this, &BCMainWindow::onEndOfProcessing );
connect( this, &BCMainWindow::endOfTransmission, &_transmitter, &BCTransmitter::onEndOfTransmission );
2026-01-01 00:40:27 +01:00
2026-01-03 00:09:26 +01:00
// transmitter starten
2026-01-03 23:51:14 +01:00
_transmitter.moveToThread(&_worker);
_worker.start();
2025-12-28 14:42:12 +01:00
2026-01-08 14:55:47 +01:00
try
{
// die Daten des eBikes laden
_dataManager.loadXmlBikeData(":/bikeinfo.xml"_L1);
}
catch( BCException& exception )
{
QMessageBox::critical( this, "Ladefehler", exception.what() );
}
// Konsolendaten als erstes anzeigen
2026-01-06 16:21:59 +01:00
_consoleAction->trigger();
//_batteryAction->trigger();
2026-01-01 03:08:34 +01:00
2026-01-08 14:55:47 +01:00
/*
2026-01-06 16:21:59 +01:00
// Dummy sync beim starten
QTimer::singleShot(1000, this, [this]()
{
onSyncDeviceView();
});
2026-01-08 14:55:47 +01:00
*/
2026-01-07 22:20:39 +01:00
2026-01-10 16:38:52 +01:00
// not least
_delightWidget = new BCDelightPMWidget(this);
2026-01-07 22:20:39 +01:00
}
2026-01-02 22:15:50 +01:00
2026-01-05 23:39:45 +01:00
/*
// 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);
*/
2026-01-03 00:09:26 +01:00
2026-01-02 22:15:50 +01:00
void BCMainWindow::initStatusBar()
{
2026-01-01 03:08:34 +01:00
2026-01-05 23:39:45 +01:00
BCDriverStateWidget* conState = new BCDriverStateWidget(this);
connect( &_transmitter, &BCTransmitter::driverStateChanged, conState, &BCDriverStateWidget::onDriverStateChanged );
connect( conState, &BCDriverStateWidget::clicked, _connectAction, &QAction::trigger );
2026-01-01 03:08:34 +01:00
2026-01-09 10:47:29 +01:00
_statusBar->addPermanentWidget(conState);
2026-01-05 23:39:45 +01:00
conState->installEventFilter(this);
2026-04-03 22:28:07 +02:00
BCThemeButton* themeBtn = new BCThemeButton(this);
2026-01-09 10:47:29 +01:00
_statusBar->addPermanentWidget(themeBtn);
2026-04-03 22:28:07 +02:00
connect(themeBtn, &BCThemeButton::themeChanged, this, [this](bool isDark)
2026-01-03 23:51:14 +01:00
{
2026-01-23 16:33:05 +01:00
QString message = isDark ? "using DarkMode." : "using LightMode.";
onShowMessage( message );
setApplicationStyleSheet( isDark ? cDarkModeStyle : cLightModeStyle );
2026-01-20 23:02:00 +01:00
2026-01-01 03:08:34 +01:00
});
2026-01-05 23:39:45 +01:00
// Wir starten im light mode
//themeBtn->setDarkMode( false );
2026-01-23 16:33:05 +01:00
onShowMessage("Ready. (Using dummy driver)");
2026-01-06 13:05:19 +01:00
2026-01-11 20:01:33 +01:00
setApplicationStyleSheet(cLightModeStyle);
2026-01-06 16:21:59 +01:00
2026-01-05 23:39:45 +01:00
}
/*
bool BCMainWindow::eventFilter(QObject *obj, QEvent *event)
{
if (obj == myWidget && event->type() == QEvent::MouseButtonRelease) {
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(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();
2026-01-20 23:02:00 +01:00
//qApp->setStyleSheet(" ");
2026-01-05 23:39:45 +01:00
return true;
2025-12-15 20:57:09 +01:00
}
2025-12-16 22:42:35 +01:00
2026-01-03 00:09:26 +01:00
/**
* @brief Setzt den Headerlabel ( == die Device-Bezeichnung )
* @param headerLabel Der headerLabel
*/
void BCMainWindow::setHeaderLabel( const QString& headerText)
{
_headerLabel->setText( " BionxControl: " + headerText );
}
2026-01-09 10:47:29 +01:00
2026-01-05 23:39:45 +01:00
void BCMainWindow::onShowMessage( const QString& message, int timeOut )
{
2026-01-09 10:47:29 +01:00
_statusBar->showMessage( message, timeOut );
2026-01-05 23:39:45 +01:00
}
2026-01-03 00:09:26 +01:00
2026-01-02 22:15:50 +01:00
2026-01-10 16:38:52 +01:00
void BCMainWindow::onStartAnimation()
{
_delightWidget->onStartChaos();
}
2026-01-02 22:15:50 +01:00
void BCMainWindow::onDriverStateChanged( BCDriver::DriverState state, const QString& message )
{
2026-01-09 10:47:29 +01:00
Q_UNUSED(state)
2026-01-23 16:33:05 +01:00
onShowMessage( message, 8000 );
2026-01-02 22:15:50 +01:00
}
2025-12-26 23:09:53 +01:00
void BCMainWindow::onShowDevicePanel( BCDevice::ID deviceID )
2025-12-26 14:07:55 +01:00
{
2025-12-28 22:48:18 +01:00
if( _devicePanels.contains( deviceID ) )
2025-12-26 14:07:55 +01:00
{
2025-12-29 23:29:56 +01:00
BCDeviceView* nxtPanel = _devicePanels[deviceID];
2025-12-26 23:37:15 +01:00
if( nxtPanel != _currentPanel )
{
_currentPanel = nxtPanel;
2026-01-06 22:39:41 +01:00
setHeaderLabel( _currentPanel->property( cBCKeyHeaderLabel ).toString() );
2026-01-08 14:55:47 +01:00
_stackedWidget->setCurrentWidget( _currentPanel );
if( _currentPanel->firstExpose() )
{
// Dummy sync beim starten
QTimer::singleShot(1000, this, [this]()
{
onSyncDeviceView();
});
}
2025-12-26 23:37:15 +01:00
// knopf auch abschalten?
}
2025-12-26 23:09:53 +01:00
}
}
2025-12-26 14:07:55 +01:00
2026-01-09 10:47:29 +01:00
/**
* @brief SLOT, wird aufgerufen, wenn der Treiber eine frischen Wert abgeholt hat.
*/
2026-01-08 20:47:05 +01:00
void BCMainWindow::onValueUpdated(BCDevice::ID deviceID, int index, BCValue::Flags newState, uint32_t rawValue )
{
2025-12-28 22:48:18 +01:00
if( _devicePanels.contains( deviceID ) )
{
2025-12-29 23:29:56 +01:00
BCDeviceView& panel = *_devicePanels[deviceID];
2026-01-08 20:47:05 +01:00
panel.updateValue( index, newState, rawValue );
2025-12-28 22:48:18 +01:00
}
}
2026-01-09 10:47:29 +01:00
/**
* @brief SLOT, wird aufgerufen, wenn der Treiber die Datenübertrgeung beendet hat.
*/
2026-01-08 19:05:07 +01:00
void BCMainWindow::onEndOfProcessing()
2026-01-06 19:52:55 +01:00
{
_syncButton->setEnabled( true );
2026-01-23 16:33:05 +01:00
onShowMessage( "Synchronization complete.");
2026-01-06 19:52:55 +01:00
}
2025-12-31 16:30:03 +01:00
/**
* @brief SLOT, der aufgerufen wird, um das akutelle Device (Battery, Motor, ... )
* zu synchronisieren, d.h. die aktuellen Werte über den CAN-Bus abzufragen.
*/
2026-01-02 16:25:21 +01:00
void BCMainWindow::onSyncDeviceView()
{
2026-01-02 16:25:21 +01:00
Q_ASSERT_X(_currentPanel, "onSyncDeviceView()", "_currentpanel ist null!");
2026-01-09 10:47:29 +01:00
const BCValueList& currentList =_currentPanel->getValueList();
2026-01-09 10:47:29 +01:00
// wir schalten den Sync-Button hier ab,
2026-01-06 19:52:55 +01:00
// wenn der Autrag bearbeitet wurde, wird der
// Button wieder eingeschaltet.
2026-01-09 10:47:29 +01:00
_syncButton->setEnabled( false );
QString devName = _currentPanel->property( cBCKeyHeaderLabel ).toString();
2026-01-23 16:33:05 +01:00
onShowMessage( "Reading: " + devName );
2026-01-02 16:25:21 +01:00
for( const BCValuePtr& value : currentList )
2025-12-28 14:42:12 +01:00
{
2026-01-02 16:25:21 +01:00
// wir setzen auf 'lesen'
2026-01-13 16:29:02 +01:00
value->setFlag( BCValue::Flag::ReadMe );
2026-01-06 02:07:42 +01:00
2026-01-06 10:53:15 +01:00
// statt '_transmitter.onUpdateValue( value )' müssen wir hier
2026-01-06 02:07:42 +01:00
// über emit requestValueUpdate() zur Thread sysnchronisation
// entkoppeln,
2026-01-02 01:52:48 +01:00
emit requestValueUpdate( value);
}
2026-01-06 18:47:08 +01:00
2026-01-09 10:47:29 +01:00
emit endOfTransmission();
}