Files
BionxControl/bcvaluemanager.cpp
2025-12-19 21:20:14 +01:00

437 lines
12 KiB
C++

/***************************************************************************
BionxControl
Copyright © 2025 christoph holzheuer
christoph.holzheuer@gmail.com
Using:
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
mhs_can_drv.c 3.00
© 2011 - 2015 by MHS-Elektronik GmbH & Co. KG, Germany
Demlehner Klaus, info@mhs-elektronik.de
@see www.mhs-elektronik.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 <QFileDialog>
#include <QTableView>
#include <QPushButton>
#include <QMessageBox>
#include <QStatusBar>
#include <QApplication>
#include <QElapsedTimer>
#include <bcvaluemanager.h>
using namespace Qt::StringLiterals;
void processEventsFor(int milliseconds)
{
QElapsedTimer timer;
timer.start();
while (timer.elapsed() < milliseconds) {
QApplication::processEvents(QEventLoop::AllEvents, 50);
}
}
BCValueManager::BCValueManager(QObject *parent)
: QObject(parent)
{
loadTypeData();
qRegisterMetaType<BCValue>("BCValue");
_transmitter.moveToThread(&_worker);
// 4. Verbindungen herstellen (Signal/Slot über Thread-Grenzen)
// A) Befehl senden (Manager -> Runner)
connect(this, &BCValueManager::valueCreated, &_transmitter, &BCTransmitter::enqueueValue);
// B) Ergebnisse empfangen (Runner -> Manager)
//connect(&_transmitter, &BCTransmitter::commandFinished, this, &BCValueManager::onCommandFinished);
//connect(&_transmitter, &BCTransmitter::messageLogged, this, &BCValueManager::onRunnerMessage);
// C) Aufräumen: Wenn Thread endet, lösche den Runner
connect(&_worker, &QThread::finished, &_transmitter, &QObject::deleteLater);
// 5. Thread starten
_worker.start();
}
BCValueManager::~BCValueManager()
{
// nothing to do here for now,
// our models are autokilled.
_worker.quit(); // Event Loop stoppen
_worker.wait(); // Warten bis Thread wirklich fertig ist
}
void BCValueManager::onCommandFinished(int id, bool success)
{
qDebug() << "[Manager] Command" << id << "finished. Success:" << success;
}
void BCValueManager::onRunnerMessage(const QString &msg)
{
qDebug() << "[Worker says]:" << msg;
}
void BCValueManager::onToggleConnectionState( bool connect )
{
if( connect )
{
if( BCCanDriver::sIdle == _canDriver.getState() )
_canDriver.onStartDriver();
int hwVersion = _canDriver.getValue( BCDevice::ID::Console, BC::ID::Cons_Rev_Hw);
if (hwVersion == 0)
{
qDebug() << "Console not responding";
}
else
{
/*
swVersion = getValue(CONSOLE, CONSOLE_REF_SW);
printf( "Console information:" _NL
" hardware version ........: %02d" _NL
" software version ........: %02d" _NL
" assistance level ........: %d" _NL,
hwVersion, swVersion,
getValue(CONSOLE, CONSOLE_ASSIST_INITLEVEL)
);
if (!gNoSerialNumbers)
printf( " part number .............: %05d" _NL
" item number .............: %05d" _NL _NL,
((getValue(CONSOLE, CONSOLE_SN_PN_HI) << 8) + getValue(CONSOLE, CONSOLE_SN_PN_LO)),
((getValue(CONSOLE, CONSOLE_SN_ITEM_HI) << 8) + getValue(CONSOLE, CONSOLE_SN_ITEM_LO))
);
*/
}
qDebug() << " ---HAIL to the kings: " << hwVersion;
}
}
void BCValueManager::onSyncFromDevice()
{
qDebug() << " ---Syncing";
if( _currentDeviceID != BCDevice::ID::Invalid)
{
BCValueList& currentList = _valueModels[_currentDeviceID]->getValueList();
BCValue& value = currentList[4];
for( const BCValue& value : currentList )
{
qDebug() << " --- value: " << value.label;
// statt '_transmitter.enqueueValue( value )' entkoppeln
// wir das eleganter über emit valueCreated()
//_transmitter.enqueueValue( value );
emit valueCreated(value);
emit valueTouched( value.rowInModel );
processEventsFor(500);
//QApplication::processEvents();
// Thread schlafen lassen (Simulation einer blockierenden Operation)
//QThread::msleep(500);
}
}
}
std::optional<BCValueModel*> BCValueManager::getModel(BCDevice::ID deviceID )
{
if( _valueModels.contains( deviceID) )
return _valueModels[deviceID];
return std::nullopt;
}
void BCValueManager::loadBikeData()
{
auto printAttrs = [](const QXmlStreamReader& xml)
{
QStringList parts;
for (const auto &attr : xml.attributes()) {
parts << attr.name().toString() + "=\"" + attr.value().toString() + "\"";
}
qDebug().noquote() << parts.join(" ");
};
/*
QString fileName = QFileDialog::getOpenFileName(this, "XML öffnen", "", "XML Files (*.xml)");
if (fileName.isEmpty())
return;
*/
QFile file(":/bikeinfo.xml");
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
{
// __fix throw
QMessageBox::warning(nullptr, "Fehler", "Datei konnte nicht geöffnet werden.");
return;
}
_xml.setDevice(&file);
if (_xml.readNextStartElement())
{
if (_xml.name() != "Bike"_L1 )
// fix throw
_xml.raiseError(QObject::tr("The file is not an 'Bike' file."));
}
// ??
Q_ASSERT(_xml.isStartElement() && _xml.name() == "Bike"_L1);
while (!_xml.atEnd() && !_xml.hasError())
{
QXmlStreamReader::TokenType token = _xml.readNext();
if (token == QXmlStreamReader::StartElement)
{
QString deviceType = _xml.attributes().value("Type"_L1).toString();
printAttrs (_xml);
const char* deviceKey = _xml.attributes().value("Type"_L1).toLatin1().constData();
auto deviceID = _bcDeviceEnum.keyToValue64(deviceKey);
//_currentDeviceID = BCDevice::ID( deviceID.value_or( BCDevice::ID::Invalid ) );
if(deviceID.has_value())
{
qDebug() << " --- Device: " << _xml.name() << ": " << deviceType << " : " << deviceID;
_currentDeviceID = BCDevice::ID( deviceID.value() );
BCValueList parsedValues;
loadDeviceData( parsedValues );
if( parsedValues.count() )
{
BCValueModel* valueModel = new BCValueModel( this );
valueModel->setValueList(parsedValues);
_valueModels.insert( _currentDeviceID, valueModel );
}
}
}
_currentDeviceID = BCDevice::ID::Console;
}
/*
if (xml.hasError())
{
QMessageBox::critical(nullptr, "Parsing Fehler", xml.errorString());
}
else
{
m_model->setDevices(parsedValues);
}
*/
// create & add new model to the model map
}
void BCValueManager::loadDeviceData( BCValueList& parsedValues )
{
auto printAttrs = [](const QXmlStreamReader& xml)
{
QStringList parts;
for (const auto &attr : xml.attributes()) {
parts << attr.name().toString() + "=\"" + attr.value().toString() + "\"";
}
qDebug().noquote() << parts.join(" ");
};
printAttrs (_xml);
Q_ASSERT(_xml.isStartElement() && _xml.name() == "Device"_L1);
qDebug() << " ---------------";
//while (!_xml.atEnd() && !_xml.hasError())
while( _xml.readNextStartElement() )
{
if( _xml.attributes().hasAttribute(BCTags::ID) )
{
//qDebug() << " --- found: " << _xml.name() << " : " << _xml.attributes().value(BCTags::ID);
QString id = _xml.attributes().value(BCTags::ID).toString();
BCValueParams params
{
.ID = id,
.Label = _xml.attributes().value(BCTags::Label).toString(),
.Default = _xml.attributes().value(BCTags::Default).toString(),
.Current = _xml.attributes().value(BCTags::Current).toString(),
.Enabled = _xml.attributes().value(BCTags::Enabled).toString(),
.UnitType = _xml.attributes().value(BCTags::UnitType).toString(),
.Min = _xml.attributes().value(BCTags::Min).toString(),
.Max = _xml.attributes().value(BCTags::Max).toString(),
.Factor = _xml.attributes().value(BCTags::Factor).toString()
};
// __fix! können ungültige werte erzeugt werden ?
//BCValue newValue = BCValue::makeValue( _currentDeviceID, params );
//if(newValue)
// parsedValues.push_back( newValue );
parsedValues.push_back( makeValue( _currentDeviceID, params ) );
}
//printAttrs (_xml);
_xml.skipCurrentElement();
}
}
void BCValueManager::loadTypeData()
{
/*
Invalid = 0x0,
"Text"
"Number"
"Float"
"Percent"
"KWh"
"Watt"
"Km"
"Kmh"
"Mm"
"Sec"
"SoC"
"Odo"
"Date"
*/
//_valueTypes.insert( { BCValueType::TypeID::Invalid, "Invalid" } );
_valueTypes.insert( "Invalid", { BCValueType::TypeID::Invalid, "Invalid" } );
_valueTypes.insert( "Text", { BCValueType::TypeID::Text } );
_valueTypes.insert( "Number", { BCValueType::TypeID::Number } );
_valueTypes.insert( "Float", { BCValueType::TypeID::Float, "", 1.5625} );
_valueTypes.insert( "Percent",{ BCValueType::TypeID::Percent, "%", 1.5625 } );
_valueTypes.insert( "KWh", { BCValueType::TypeID::KWh, "kwh", 1.5625 } );
_valueTypes.insert( "Watt", { BCValueType::TypeID::Watt, "w", 1.5625 } );
_valueTypes.insert( "Km", { BCValueType::TypeID::Km, "km", 1.5625 } );
_valueTypes.insert( "Kmh", { BCValueType::TypeID::Kmh, "km/h", 0.1 } );
_valueTypes.insert( "Mm", { BCValueType::TypeID::Mm, "mm", 1.5625 } );
_valueTypes.insert( "Sec", { BCValueType::TypeID::Sec, "s", 1.5625 } );
_valueTypes.insert( "SoC", { BCValueType::TypeID::SoC, "%", 1.5625 } );
_valueTypes.insert( "Odo", { BCValueType::TypeID::Odo, "km", 1.5625 } );
_valueTypes.insert( "Assist", { BCValueType::TypeID::Assist, "", 0 ,4 } );
_valueTypes.insert( "Assist", { BCValueType::TypeID::AssistFac, "%" } );
_valueTypes.insert( "Date", { BCValueType::TypeID::Date } );
}
// --- NEU: Speichern mit QXmlStreamWriter ---
void BCValueManager::saveBikeData()
{
/*
QString fileName = QFileDialog::getSaveFileName(this, "XML speichern", "", "XML Files (*.xml)");
if (fileName.isEmpty()) return;
QFile file(fileName);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
QMessageBox::warning(this, "Fehler", "Datei konnte nicht zum Schreiben geöffnet werden.");
return;
}
QXmlStreamWriter xml(&file);
xml.setAutoFormatting(true); // Sorgt für schöne Einrückungen (Tabs/Spaces)
xml.writeStartDocument();
xml.writeStartElement("devices");
// Daten vom Model holen
const QList<Device> &devices = m_model->getDevices();
for (const Device &d : devices) {
xml.writeStartElement("device");
xml.writeAttribute("name", d.name);
xml.writeAttribute("ip", d.ip);
xml.writeEndElement(); // </device>
}
xml.writeEndElement(); // </devices>
xml.writeEndDocument();
// Prüfen ob alles geschrieben wurde
if (file.error() != QFile::NoError) {
QMessageBox::critical(this, "Fehler", "Fehler beim Schreiben der Datei.");
} else {
statusBar()->showMessage("Datei erfolgreich gespeichert!", 3000);
}
*/
}
BCValue BCValueManager::makeValue( BCDevice::ID deviceID, const BCValueParams& params )
{
auto setIfExists = [&]( QStringView source, optDouble& target )
{
if( !source.isEmpty() )
{
bool ok;
double testVal = source.toDouble(&ok);
if (ok)
target = testVal;
}
};
static QMetaEnum s_bcValueEnum{QMetaEnum::fromType<BC::ID>()};
/*
Wir brauchen:
- eine gültige ID
*/
BCValue newValue{};
auto IDVal = s_bcValueEnum.keyToValue64( params.ID.toLatin1().constData() );
if( IDVal.has_value() )
{
newValue = BCValue( deviceID, BC::ID( IDVal.value() ) );
/*
setIfExists( params.Factor, newValue.factor );
setIfExists( params.Min, newValue.min );
setIfExists( params.Max, newValue.max );
*/
newValue.defaultValue.setValue( params.Label );
newValue.value.setValue( params.Current );
newValue.label = params.Label;
//qDebug() << " --- created: " << params.Label;
}
return newValue;
}