From 7d557133e2c21f0706448d6f0bbcfac62470c758 Mon Sep 17 00:00:00 2001 From: 0xFEEDC0DE64 Date: Tue, 20 Jul 2021 23:48:00 +0200 Subject: [PATCH] Implemented basic remote control --- devicehandler.cpp | 206 +++++++++++++++++++++++++++++------------- devicehandler.h | 36 +++++++- qml/RemoteControl.qml | 47 +++++++++- 3 files changed, 219 insertions(+), 70 deletions(-) diff --git a/devicehandler.cpp b/devicehandler.cpp index 3b3c266..8a2772a 100644 --- a/devicehandler.cpp +++ b/devicehandler.cpp @@ -6,6 +6,7 @@ #include #include #include +#include // local includes #include "deviceinfo.h" @@ -23,26 +24,6 @@ DeviceHandler::DeviceHandler(QObject *parent) : { } -void DeviceHandler::setAddressType(AddressType type) -{ - switch (type) { - case DeviceHandler::AddressType::PublicAddress: - m_addressType = QLowEnergyController::PublicAddress; - break; - case DeviceHandler::AddressType::RandomAddress: - m_addressType = QLowEnergyController::RandomAddress; - break; - } -} - -DeviceHandler::AddressType DeviceHandler::addressType() const -{ - if (m_addressType == QLowEnergyController::RandomAddress) - return DeviceHandler::AddressType::RandomAddress; - - return DeviceHandler::AddressType::PublicAddress; -} - void DeviceHandler::setDevice(DeviceInfo *device) { clearMessages(); @@ -89,6 +70,110 @@ void DeviceHandler::setDevice(DeviceInfo *device) } } +void DeviceHandler::setAddressType(AddressType type) +{ + switch (type) { + case DeviceHandler::AddressType::PublicAddress: + m_addressType = QLowEnergyController::PublicAddress; + break; + case DeviceHandler::AddressType::RandomAddress: + m_addressType = QLowEnergyController::RandomAddress; + break; + } +} + +DeviceHandler::AddressType DeviceHandler::addressType() const +{ + if (m_addressType == QLowEnergyController::RandomAddress) + return DeviceHandler::AddressType::RandomAddress; + + return DeviceHandler::AddressType::PublicAddress; +} + +bool DeviceHandler::alive() const +{ + if (m_service) + return m_service->state() == QLowEnergyService::ServiceDiscovered; + + return false; +} + +void DeviceHandler::setRemoteControlActive(bool remoteControlActive) +{ + if (!remoteControlActive && m_timerId != -1) + { + killTimer(m_timerId); + m_timerId = -1; + emit remoteControlActiveChanged(); + + if (m_service && m_remotecontrolCharacteristic.isValid()) + { + m_remoteControlFrontLeft = 0; + m_remoteControlFrontRight = 0; + m_remoteControlBackLeft = 0; + m_remoteControlBackRight = 0; + + sendRemoteControl(); + } + } + else if (remoteControlActive && m_timerId == -1 && m_service && m_remotecontrolCharacteristic.isValid()) + { + m_timerId = startTimer(100); + emit remoteControlActiveChanged(); + } +} + +void DeviceHandler::timerEvent(QTimerEvent *event) +{ + if (event->timerId() == m_timerId) + { + if (!m_service || !m_remotecontrolCharacteristic.isValid()) + { + killTimer(m_timerId); + m_timerId = -1; + emit remoteControlActiveChanged(); + return; + } + + if (m_waitingForWrite) + qWarning() << "still pending"; + else + sendRemoteControl(); + } + else + BluetoothBaseClass::timerEvent(event); +} + +void DeviceHandler::disconnectService() +{ + m_foundBobbycarService = false; + + //disable notifications + if (m_service) + { + if (m_notificationDescLivestats.isValid() && m_notificationDescLivestats.value() == QByteArray::fromHex("0100")) + m_service->writeDescriptor(m_notificationDescLivestats, QByteArray::fromHex("0000")); + + disconnectInternal(); + } +} + +void DeviceHandler::disconnectInternal() +{ + if (!m_notificationDescLivestats.isValid()) + { + //disabled notifications -> assume disconnect intent + if (m_control) + m_control->disconnectFromDevice(); + + if (m_service) + { + delete m_service; + m_service = nullptr; + } + } +} + void DeviceHandler::serviceDiscovered(const QBluetoothUuid &gatt) { if (gatt == bobbycarServiceUuid) @@ -118,6 +203,7 @@ void DeviceHandler::serviceScanDone() connect(m_service, &QLowEnergyService::stateChanged, this, &DeviceHandler::serviceStateChanged); connect(m_service, &QLowEnergyService::characteristicChanged, this, &DeviceHandler::updateBobbycarValue); connect(m_service, &QLowEnergyService::descriptorWritten, this, &DeviceHandler::confirmedDescriptorWrite); + connect(m_service, &QLowEnergyService::characteristicWritten, this, &DeviceHandler::confirmedCharacteristicWrite); m_service->discoverDetails(); } else @@ -126,23 +212,21 @@ void DeviceHandler::serviceScanDone() } } -namespace { -void logAddr(const QBluetoothUuid &uuid) -{ - if (uuid == bobbycarServiceUuid) - qDebug() << "bobbycarServiceUuid"; - else if (uuid == livestatsCharacUuid) - qDebug() << "livestatsCharacUuid"; - else if (uuid == remotecontrolCharacUuid) - qDebug() << "remotecontrolCharacUuid"; - else - qDebug() << "unknown uuid" << uuid; -} -} - void DeviceHandler::serviceStateChanged(QLowEnergyService::ServiceState s) { - switch (s) { + qDebug() << "serviceStateChanged()" << s; + + if (m_timerId != -1) + { + killTimer(m_timerId); + m_timerId = -1; + emit remoteControlActiveChanged(); + } + + m_waitingForWrite = false; + + switch (s) + { case QLowEnergyService::DiscoveringServices: setInfo(tr("Discovering services...")); break; @@ -162,6 +246,13 @@ void DeviceHandler::serviceStateChanged(QLowEnergyService::ServiceState s) break; } + m_remotecontrolCharacteristic = m_service->characteristic(remotecontrolCharacUuid); + if (!m_remotecontrolCharacteristic.isValid()) + { + setError("remotecontrolCharacUuid not found."); + break; + } + break; } default: @@ -187,6 +278,8 @@ void DeviceHandler::updateBobbycarValue(const QLowEnergyCharacteristic &c, const return; } + clearMessages(); + const QJsonObject &obj = doc.object(); { @@ -253,40 +346,23 @@ void DeviceHandler::confirmedDescriptorWrite(const QLowEnergyDescriptor &d, cons } } -void DeviceHandler::disconnectService() +void DeviceHandler::confirmedCharacteristicWrite(const QLowEnergyCharacteristic &info, const QByteArray &value) { - m_foundBobbycarService = false; + qDebug() << "confirmedCharacteristicWrite"; - //disable notifications - if (m_service) - { - if (m_notificationDescLivestats.isValid() && m_notificationDescLivestats.value() == QByteArray::fromHex("0100")) - m_service->writeDescriptor(m_notificationDescLivestats, QByteArray::fromHex("0000")); - - disconnectInternal(); - } + if (info == m_remotecontrolCharacteristic) + m_waitingForWrite = false; } -void DeviceHandler::disconnectInternal() +void DeviceHandler::sendRemoteControl() { - if (!m_notificationDescLivestats.isValid()) - { - //disabled notifications -> assume disconnect intent - if (m_control) - m_control->disconnectFromDevice(); + m_waitingForWrite = true; - if (m_service) - { - delete m_service; - m_service = nullptr; - } - } -} - -bool DeviceHandler::alive() const -{ - if (m_service) - return m_service->state() == QLowEnergyService::ServiceDiscovered; - - return false; + qDebug() << "writeCharacteristic()"; + m_service->writeCharacteristic(m_remotecontrolCharacteristic, QJsonDocument{QJsonObject { + {"fl", m_remoteControlFrontLeft}, + {"fr", m_remoteControlFrontRight}, + {"bl", m_remoteControlBackLeft}, + {"br", m_remoteControlBackRight} + }}.toJson(QJsonDocument::Compact)); } diff --git a/devicehandler.h b/devicehandler.h index d53bedb..40b7e08 100644 --- a/devicehandler.h +++ b/devicehandler.h @@ -34,6 +34,12 @@ class DeviceHandler : public BluetoothBaseClass Q_PROPERTY(float backLeftDcLink READ backLeftDcLink NOTIFY backLeftDcLinkChanged); Q_PROPERTY(float backRightDcLink READ backRightDcLink NOTIFY backRightDcLinkChanged); + Q_PROPERTY(bool remoteControlActive READ remoteControlActive WRITE setRemoteControlActive NOTIFY remoteControlActiveChanged); + Q_PROPERTY(int remoteControlFrontLeft WRITE setRemoteControlFrontLeft); + Q_PROPERTY(int remoteControlFrontRight WRITE setRemoteControlFrontRight); + Q_PROPERTY(int remoteControlBackLeft WRITE setRemoteControlBackLeft); + Q_PROPERTY(int remoteControlBackRight WRITE setRemoteControlBackRight); + public: enum class AddressType { PublicAddress, @@ -66,6 +72,13 @@ public: float backLeftDcLink() const { return m_backLeftDcLink; } float backRightDcLink() const { return m_backRightDcLink; } + bool remoteControlActive() const { return m_timerId != -1; } + void setRemoteControlActive(bool remoteControlActive); + void setRemoteControlFrontLeft(int remoteControlFrontLeft) { m_remoteControlFrontLeft = remoteControlFrontLeft; } + void setRemoteControlFrontRight(int remoteControlFrontRight) { m_remoteControlFrontRight = remoteControlFrontRight; } + void setRemoteControlBackLeft(int remoteControlBackLeft) { m_remoteControlBackLeft = remoteControlBackLeft; } + void setRemoteControlBackRight(int remoteControlBackRight) { m_remoteControlBackRight = remoteControlBackRight; } + signals: void aliveChanged(); @@ -86,6 +99,11 @@ signals: void backLeftDcLinkChanged(); void backRightDcLinkChanged(); + void remoteControlActiveChanged(); + +protected: + void timerEvent(QTimerEvent *event) override; + public slots: void disconnectService(); @@ -99,15 +117,20 @@ private: //QLowEnergyService void serviceStateChanged(QLowEnergyService::ServiceState s); void updateBobbycarValue(const QLowEnergyCharacteristic &c, - const QByteArray &value); + const QByteArray &value); void confirmedDescriptorWrite(const QLowEnergyDescriptor &d, - const QByteArray &value); + const QByteArray &value); + void confirmedCharacteristicWrite(const QLowEnergyCharacteristic &info, + const QByteArray &value); + + void sendRemoteControl(); private: QLowEnergyController::RemoteAddressType m_addressType = QLowEnergyController::PublicAddress; QLowEnergyController *m_control = nullptr; QLowEnergyService *m_service = nullptr; QLowEnergyDescriptor m_notificationDescLivestats; + QLowEnergyCharacteristic m_remotecontrolCharacteristic; DeviceInfo *m_currentDevice{}; bool m_foundBobbycarService{}; @@ -128,6 +151,15 @@ private: float m_frontRightDcLink{}; float m_backLeftDcLink{}; float m_backRightDcLink{}; + + int m_timerId{-1}; + + int m_remoteControlFrontLeft{}; + int m_remoteControlFrontRight{}; + int m_remoteControlBackLeft{}; + int m_remoteControlBackRight{}; + + bool m_waitingForWrite{}; }; #endif // DEVICEHANDLER_H diff --git a/qml/RemoteControl.qml b/qml/RemoteControl.qml index 7e1ff87..486561e 100644 --- a/qml/RemoteControl.qml +++ b/qml/RemoteControl.qml @@ -22,19 +22,60 @@ GamePage { radius: GameSettings.buttonRadius color: GameSettings.viewColor + property real maxSpeed: 100 + property real remoteControlFrontLeft: maxSpeed * handler.relativeY * (handler.relativeX < 0 ? 1 + handler.relativeX : 1) + property real remoteControlFrontRight: maxSpeed * handler.relativeY * (handler.relativeX > 0 ? 1 - handler.relativeX : 1) + property real remoteControlBackLeft: 0 + property real remoteControlBackRight: 0 + + onRemoteControlFrontLeftChanged: deviceHandler.remoteControlFrontLeft = remoteControlFrontLeft + onRemoteControlFrontRightChanged: deviceHandler.remoteControlFrontRight = remoteControlFrontRight + onRemoteControlBackLeftChanged: deviceHandler.remoteControlBackLeft = remoteControlBackLeft + onRemoteControlBackRightChanged: deviceHandler.remoteControlBackRight = remoteControlBackRight + + Rectangle { + parent: container + color: "white" + anchors.centerIn: parent + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + width: 20 + height: width + radius: width / 2 + } + + Text { + anchors.top: parent.top + minimumPixelSize: 10 + font.pixelSize: GameSettings.mediumFontSize + color: GameSettings.textColor + text: { + return "x:" + handler.relativeX.toFixed(1) + " y:" + handler.relativeY.toFixed(1) + "\n" + + "fl:" + Math.round(container.remoteControlFrontLeft) + " fr:" + Math.round(container.remoteControlFrontRight) + "\n" + + "bl:" + Math.round(container.remoteControlBackLeft) + " br:" + Math.round(container.remoteControlBackRight); + } + } + PointHandler { id: handler //acceptedDevices: PointerDevice.TouchScreen + property real clampedX: handler.active ? Math.min(Math.max(handler.point.position.x, 0), container.width) : (container.width / 2) + property real clampedY: handler.active ? Math.min(Math.max(handler.point.position.y, 0), container.height) : (container.height / 2) + property real relativeX: ((clampedX / container.width) - 0.5) * 2 + property real relativeY: ((clampedY / container.height) - 0.5) * -2 + target: Rectangle { parent: container color: "red" visible: handler.active - x: handler.point.position.x - width / 2 - y: handler.point.position.y - height / 2 - width: 20 + x: handler.clampedX - width / 2 + y: handler.clampedY - height / 2 + width: 50 height: width radius: width / 2 } + + onActiveChanged: deviceHandler.remoteControlActive = handler.active } } }