356 lines
10 KiB
C++
356 lines
10 KiB
C++
/***************************************************************************
|
|
|
|
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 <info@bigxionflasher.org>
|
|
@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 <QFile>
|
|
#include <QTimer>
|
|
#include <QMessageBox>
|
|
|
|
#include <bcthemeswitchbutton.h>
|
|
#include <bcdriverstatewidget.h>
|
|
#include <bcmainwindow.h>
|
|
#include <bcvaluedelegate.h>
|
|
#include <ui_bcmainwindow.h>
|
|
|
|
/**
|
|
* @brief Das Mainwindow erzeugen
|
|
* @param parent Das Elternwidget
|
|
*/
|
|
|
|
BCMainWindow::BCMainWindow(QWidget *parent)
|
|
: QMainWindow(parent)
|
|
{
|
|
// __fix! in der Form nötig?
|
|
qRegisterMetaType<BCValue>("BCValue");
|
|
qRegisterMetaType<QList<BCValue>>("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 ? "DarkMode aktiviert" : "LightMode aktiviert";
|
|
_statusBar->showMessage( message, 3000);
|
|
setApplicationStyleSheet( isDark ? cDarkModeStyle : cLightModeStyle );
|
|
});
|
|
|
|
// Wir starten im light mode
|
|
//themeBtn->setDarkMode( false );
|
|
|
|
_statusBar->showMessage("Bereit. (Dummy-Treiber eingestellt)");
|
|
|
|
setApplicationStyleSheet(cLightModeStyle);
|
|
|
|
}
|
|
|
|
/*
|
|
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();
|
|
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()
|
|
{
|
|
qDebug() << " FEIN!";
|
|
_delightWidget->onStartChaos();
|
|
}
|
|
|
|
|
|
void BCMainWindow::onDriverStateChanged( BCDriver::DriverState state, const QString& message )
|
|
{
|
|
Q_UNUSED(state)
|
|
_statusBar->showMessage( 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 );
|
|
_statusBar->showMessage( "Synchronisation abgeschlossen.", 3000 );
|
|
}
|
|
|
|
/**
|
|
* @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();
|
|
_statusBar->showMessage( "Lese: " + devName, 5000 );
|
|
|
|
for( const BCValuePtr& value : currentList )
|
|
{
|
|
// wir setzen auf 'lesen'
|
|
value->getValueFlags().setFlag( BCValue::Flag::ReadMe );
|
|
|
|
// statt '_transmitter.onUpdateValue( value )' müssen wir hier
|
|
// über emit requestValueUpdate() zur Thread sysnchronisation
|
|
// entkoppeln,
|
|
emit requestValueUpdate( value);
|
|
|
|
}
|
|
|
|
emit endOfTransmission();
|
|
}
|