#include "leddisplaybackend.h" LedDisplayBackend::LedDisplayBackend(QObject *parent) : QObject(parent) { this->bleClient = new QBluetoothLeUartClient(); this->bleClient->setUUIDs("92fecb20-1406-426a-afa5-cd5c1f306462", "92fecb21-1406-426a-afa5-cd5c1f306462", "92fecb22-1406-426a-afa5-cd5c1f306462"); this->displayTextModel = new LedDisplayTextModel(this); this->textSetsBuffer.clear(); this->displayBrightness = {{"displayBrightness", 0}, {"automaticBrightnessAdjustment", false}}; this->runningCommands = 0; this->waitingCommands = 0; this->settings = new QSettings(); this->keepAliveTimer = new QTimer(this); this->keepAliveTimer->setInterval(5000); this->keepAliveTimer->setSingleShot(false); connect(this->keepAliveTimer, &QTimer::timeout, this, &LedDisplayBackend::sendBluetoothKeepAlive); connect(this->bleClient, &QBluetoothLeUartClient::stateChanged, this, &LedDisplayBackend::handleBluetoothStateChange); connect(this->bleClient, &QBluetoothLeUartClient::foundNewDevice, this, &LedDisplayBackend::handleFoundNewDevice); connect(this->bleClient, &QBluetoothLeUartClient::dataReceived, this, &LedDisplayBackend::handleBluetoothDataReceived); connect(this->bleClient, &QBluetoothLeUartClient::connectedToDevice, this, &LedDisplayBackend::handleBluetoothDeviceConected); connect(this->displayTextModel, &LedDisplayTextModel::dataChanged, this, &LedDisplayBackend::handleDisplayTextModelDataChanged); connect(this->displayTextModel, &LedDisplayTextModel::rowsInserted, this, &LedDisplayBackend::handleDisplayTextModelRowsInserted); connect(this->displayTextModel, &LedDisplayTextModel::rowsRemoved, this, &LedDisplayBackend::handleDisplayTextModelRowsRemoved); this->setState(Idle); this->bleClient->startScanningForDevices(); } void LedDisplayBackend::startScanning() { this->bleClient->startScanningForDevices(); } void LedDisplayBackend::authenticate(QString code) { // tell display to send over existing model data this->setState(Authenticating); if(code.length() == 64) this->lastDisplaySecret = code; else if(code.length() == 4) { QString combinedCode = code; this->lastDisplaySecret = QCryptographicHash::hash(combinedCode.toUtf8(), QCryptographicHash::Sha256).toHex(); } this->sendBluetoothCommand(AuthenticateCommand, QVariantMap{{"secret", this->lastDisplaySecret}}); } void LedDisplayBackend::handleBluetoothScanningError(QBluetoothLeUartClient::BluetoothScanError error) { Q_UNUSED(error) } void LedDisplayBackend::handleBluetoothStateChange(QBluetoothLeUartClient::BluetoothLeUartClientState state){ switch(state){ case QBluetoothLeUartClient::Idle: { this->setState(Idle); break; } case QBluetoothLeUartClient::AdapterTurnedOff: { this->setState(BluetoothOff); break; } case QBluetoothLeUartClient::LocationPermissionDenied: { this->setState(LocationPermissionDenied); break; } case QBluetoothLeUartClient::Scanning: { this->setState(Scanning); break; } case QBluetoothLeUartClient::ScanFinished: { this->setState(ReadyToConnect); break; } case QBluetoothLeUartClient::Connecting: { this->setState(Connecting); break; } case QBluetoothLeUartClient::ScanningForService: { this->setState(Connecting); break; } case QBluetoothLeUartClient::ServiceFound: { this->setState(Connecting); break; } case QBluetoothLeUartClient::Connected: { break; } } if(state == QBluetoothLeUartClient::Connected) this->keepAliveTimer->start(); else if(this->keepAliveTimer->isActive()) this->keepAliveTimer->stop(); } void LedDisplayBackend::handleBluetoothDeviceConected() { if(this->settings->contains(this->bleClient->getCurrentDevice()->getAddress())) this->authenticate(this->settings->value(this->bleClient->getCurrentDevice()->getAddress()).toString()); else this->setState(AuthenticationRequired); } void LedDisplayBackend::handleFoundNewDevice(QBluetoothLeUartDevice* device) { qDebug() << "Found a device: name: " << device->getName() << " address: " << device->getAddress(); } void LedDisplayBackend::handleDisplayTextModelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) { if(this->state == Initing) return; qDebug() << "Data changed: topLeft: " << topLeft << " bottomRight: " << bottomRight << " roles: " << roles; for(int role : roles) this->updateDisplayTextSetParameter(topLeft.row(), role); } void LedDisplayBackend::handleDisplayTextModelRowsInserted(const QModelIndex &parent, int first, int last) { qDebug() << "Rows inserted: parent: " << parent << " first: " << first << " last " << last; for(int i = 0; i < LedDisplayTextModel::LedDisplayTextModelRoleCount; i++) { this->updateDisplayTextSetParameter(first, i); } } void LedDisplayBackend::handleDisplayTextModelRowsRemoved(const QModelIndex &parent, int first, int last) { qDebug() << "Rows removed: parent: " << parent << " first: " << first << " last " << last; // Setting Text to "" will delete the item this->updateDisplayTextSetParameter(first, LedDisplayTextModel::TextRole, ""); } void LedDisplayBackend::sendBluetoothCommand(OmobiDisplayCommand command, QVariant data) { QVariantMap commandMap = { {"header", command}, {"data", data} }; QJsonDocument doc = QJsonDocument::fromVariant(commandMap); qDebug() << "Sending command: \n" << qPrintable(doc.toJson(QJsonDocument::Compact)); if((this->state != Initing && this->runningCommands > 0) || (this->state == Initing && this->runningCommands > 1)) { this->waitingCommands++; QEventLoop loop; loop.connect(this, &LedDisplayBackend::commandFinished, &loop, &QEventLoop::quit); loop.exec(); this->waitingCommands--; if(this->state == Idle) return; } this->runningCommands ++; if(this->state == Connected) this->setState(Loading); this->bleClient->sendData(doc.toJson(QJsonDocument::Compact)); } void LedDisplayBackend::sendBluetoothKeepAlive() { QJsonDocument doc = QJsonDocument::fromVariant(QVariantMap{{"header", KeepAliveCommand}}); //qDebug() << "Sending keep alive: \n" << qPrintable(doc.toJson(QJsonDocument::Indented)); this->bleClient->sendData(doc.toJson(QJsonDocument::Compact)); } void LedDisplayBackend::handleBluetoothDataReceived(QString s){ qDebug() << "New data: \n" << qPrintable(s); QJsonParseError parseError; QJsonDocument doc = QJsonDocument::fromJson(s.toUtf8(), &parseError); if(parseError.error != QJsonParseError::NoError) return; OmobiDisplayCommand header = OmobiDisplayCommand(doc.toVariant().toMap()["header"].toInt()); QVariantMap data = doc.toVariant().toMap()["data"].toMap(); OmobiDisplayStatusCode status = OmobiDisplayStatusCode(doc.toVariant().toMap()["status"].toInt()); qDebug() << "Header is:" << header; switch (header) { case AuthenticateCommand: { if(status != Success) { this->setState(AuthenticationRequired); this->lastDisplaySecret = ""; return; } this->settings->setValue(this->bleClient->getCurrentDevice()->getAddress(), this->lastDisplaySecret); this->runningCommands = 0; emit this->commandFinished(); this->setLoadingProgress(0); this->setState(Initing); this->sendBluetoothCommand(GetAllTextSetsCommand); break; } case KeepAliveCommand: { break; } case GetAllTextSetsCommand: { // indicates that all existing text sets have been sent over after GetAllTextSetsCommand was called if(status != Success) // TODO: handle error break; this->displayTextModel->maximumTextLength = data["maximumTextLength"].toInt(); this->displayTextModel->maximumTextSets = data["maximumTextSets"].toInt(); this->displayTextModel->setTexts({}); this->textSetsBuffer.clear(); for(int i = 0; i < this->displayTextModel->maximumTextSets; i++) { for(int param = 0; param < DisplayTextSetParameterCount; param++) { this->setLoadingProgress(float((i*DisplayTextSetParameterCount) + param +1) / float(this->displayTextModel->maximumTextSets * DisplayTextSetParameterCount)); this->sendBluetoothCommand(GetTextSetParameterCommand, QVariantMap({{"index", i}, {"parameter", param}})); } } this->sendBluetoothCommand(GetDisplayBrightnessCommand); this->refreshLoadingState(); break; } case GetTextSetParameterCommand: { int index = data["index"].toInt(); if(index < 0) return; while(this->textSetsBuffer.length() <= index) this->textSetsBuffer.append(QMap()); int parameter = data["parameter"].toInt(); if(!this->textSetsBuffer[index].contains(parameter)) this->textSetsBuffer[index].insert(parameter, QVariant()); this->textSetsBuffer[index][parameter] = data["value"].toString(); qDebug() << "Got value: " << this->textSetsBuffer[index][parameter]; this->displayTextModel->setTexts(this->textSetsBuffer); this->refreshLoadingState(); break; } case GetDisplayBrightnessCommand: { this->setDisplayBrightness(data); this->refreshLoadingState(); break; } case SetTextSetParameterCommand: case SetDisplayBrightnessCommand: case SetDisplayCodeCommand: case SetDisplayNameCommand: // TODO: Error handling this->refreshLoadingState(); break; } } void LedDisplayBackend::updateDisplayTextSetParameter(int index, int parameter) { this->updateDisplayTextSetParameter(index, parameter, this->displayTextModel->data(index, parameter).toString()); } void LedDisplayBackend::updateDisplayTextSetParameter(int index, int parameter, QString value) { if(this->state == Initing) return; qDebug() << "Updating data at index: " << index << " parameter: " << parameter << " and value: " << value; QVariantMap dataMap = { {"index", index}, {"parameter", parameter}, {"value", value} }; this->sendBluetoothCommand(SetTextSetParameterCommand, dataMap); } QBluetoothLeUartClient* LedDisplayBackend::getBleClient() { return this->bleClient; } LedDisplayTextModel* LedDisplayBackend::getDisplayTextModel() { return this->displayTextModel; } LedDisplayBackend::OmobiDisplayAppState LedDisplayBackend::getState() { return this->state; } void LedDisplayBackend::refreshLoadingState() { if(this->state != Initing && this->state != Loading) return; qDebug() << "Refreshing loading state! Waiting: " << this->runningCommands; emit this->commandFinished(); if(this->runningCommands <= 1 && this->waitingCommands == 0) { this->runningCommands = 0; this->setState(Connected); this->setLoadingProgress(0); } else this->runningCommands--; } void LedDisplayBackend::setState(OmobiDisplayAppState state) { if(state == this->state) return; this->state = state; emit this->stateChanged(); qDebug() << "Now in " << state << " state"; if(this->state == Idle) { this->runningCommands = 0; emit this->commandFinished(); this->bleClient->startScanningForDevices(); } } QVariantMap LedDisplayBackend::getDisplayBrightness() { return this->displayBrightness; } void LedDisplayBackend::setDisplayBrightness(QVariantMap brightness) { if(brightness == this->displayBrightness) return; this->displayBrightness = brightness; this->sendBluetoothCommand(SetDisplayBrightnessCommand, this->displayBrightness); emit this->displayBrightnessChanged(); } void LedDisplayBackend::setDisplayCode(QString code) { this->sendBluetoothCommand(SetDisplayCodeCommand, QVariantMap{{"displayCode",code}}); } void LedDisplayBackend::setDisplayName(QString name) { // This will restart the display!! this->sendBluetoothCommand(SetDisplayNameCommand, QVariantMap{{"displayName", name}}); } void LedDisplayBackend::setLoadingProgress(float progress) { this->loadingProgress = progress; emit this->loadingProgressChanged(); } float LedDisplayBackend::getLoadingProgress() { return this->loadingProgress; }