nuki: Add Qt6 support

master
Simon Stürz 2025-08-08 15:32:03 +02:00
parent d991034b61
commit 35abae7683
9 changed files with 147 additions and 33 deletions

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2020, nymea GmbH
* Copyright 2013 - 2025, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
@ -29,9 +29,11 @@
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "integrationpluginnuki.h"
#include "integrations/thing.h"
#include "plugininfo.h"
#include "hardware/bluetoothlowenergy/bluetoothlowenergymanager.h"
#include <integrations/thing.h>
#include <hardware/bluetoothlowenergy/bluetoothlowenergymanager.h>
extern "C"{
#include "sodium.h"

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2020, nymea GmbH
* Copyright 2013 - 2025, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
@ -31,9 +31,9 @@
#ifndef INTEGRATIONPLUGINNUKI_H
#define INTEGRATIONPLUGINNUKI_H
#include "plugintimer.h"
#include "integrations/integrationplugin.h"
#include "bluez/bluetoothmanager.h"
#include <integrations/integrationplugin.h>
#include <bluez/bluetoothmanager.h>
#include <plugintimer.h>
#include "nuki.h"
@ -41,7 +41,7 @@ class IntegrationPluginNuki : public IntegrationPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "guru.guh.DevicePlugin" FILE "integrationpluginnuki.json")
Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginnuki.json")
Q_INTERFACES(IntegrationPlugin)
public:

View File

@ -154,6 +154,15 @@ void Nuki::printServices()
void Nuki::readDeviceInformationCharacteristics()
{
qCDebug(dcNuki()) << "Start reading device information";
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
m_initUuidsToRead.append(QBluetoothUuid::CharacteristicType::SerialNumberString);
m_initUuidsToRead.append(QBluetoothUuid::CharacteristicType::HardwareRevisionString);
m_initUuidsToRead.append(QBluetoothUuid::CharacteristicType::FirmwareRevisionString);
m_deviceInformationService->readCharacteristic(QBluetoothUuid::CharacteristicType::SerialNumberString);
m_deviceInformationService->readCharacteristic(QBluetoothUuid::CharacteristicType::HardwareRevisionString);
m_deviceInformationService->readCharacteristic(QBluetoothUuid::CharacteristicType::FirmwareRevisionString);
#else
m_initUuidsToRead.append(QBluetoothUuid::SerialNumberString);
m_initUuidsToRead.append(QBluetoothUuid::HardwareRevisionString);
m_initUuidsToRead.append(QBluetoothUuid::FirmwareRevisionString);
@ -161,6 +170,8 @@ void Nuki::readDeviceInformationCharacteristics()
m_deviceInformationService->readCharacteristic(QBluetoothUuid::SerialNumberString);
m_deviceInformationService->readCharacteristic(QBluetoothUuid::HardwareRevisionString);
m_deviceInformationService->readCharacteristic(QBluetoothUuid::FirmwareRevisionString);
#endif
}
void Nuki::executeCurrentAction()
@ -253,6 +264,18 @@ void Nuki::onBluetoothDeviceStateChanged(const BluetoothDevice::State &state)
void Nuki::onDeviceInfoCharacteristicReadFinished(BluetoothGattCharacteristic *characteristic, const QByteArray &value)
{
qCDebug(dcNuki()) << "Read thing information characteristic finished" << characteristic->chararcteristicName() << qUtf8Printable(value);
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
if (characteristic->uuid() == QBluetoothUuid::CharacteristicType::SerialNumberString) {
m_serialNumber = QString::fromUtf8(value);
m_initUuidsToRead.removeOne(QBluetoothUuid::CharacteristicType::SerialNumberString);
} else if (characteristic->uuid() == QBluetoothUuid::CharacteristicType::HardwareRevisionString) {
m_hardwareRevision = QString::fromUtf8(value);
m_initUuidsToRead.removeOne(QBluetoothUuid::CharacteristicType::HardwareRevisionString);
} else if (characteristic->uuid() == QBluetoothUuid::CharacteristicType::FirmwareRevisionString) {
m_firmwareRevision = QString::fromUtf8(value);
m_initUuidsToRead.removeOne(QBluetoothUuid::CharacteristicType::FirmwareRevisionString);
}
#else
if (characteristic->uuid() == QBluetoothUuid::SerialNumberString) {
m_serialNumber = QString::fromUtf8(value);
m_initUuidsToRead.removeOne(QBluetoothUuid::SerialNumberString);
@ -263,6 +286,7 @@ void Nuki::onDeviceInfoCharacteristicReadFinished(BluetoothGattCharacteristic *c
m_firmwareRevision = QString::fromUtf8(value);
m_initUuidsToRead.removeOne(QBluetoothUuid::FirmwareRevisionString);
}
#endif
if (m_initUuidsToRead.isEmpty()) {
// Initial read done. Make thing available
@ -412,7 +436,11 @@ bool Nuki::init()
}
// Verify services
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
if (!m_bluetoothDevice->hasService(QBluetoothUuid::ServiceClassUuid::DeviceInformation)) {
#else
if (!m_bluetoothDevice->hasService(QBluetoothUuid::DeviceInformation)) {
#endif
qCWarning(dcNuki()) << "Could not find device information service on device" << m_bluetoothDevice;
return false;
}
@ -429,7 +457,11 @@ bool Nuki::init()
// Create service and characteristic objects
// Device information
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
m_deviceInformationService = m_bluetoothDevice->getService(QBluetoothUuid::ServiceClassUuid::DeviceInformation);
#else
m_deviceInformationService = m_bluetoothDevice->getService(QBluetoothUuid::DeviceInformation);
#endif
connect(m_deviceInformationService, &BluetoothGattService::characteristicReadFinished, this, &Nuki::onDeviceInfoCharacteristicReadFinished);
// Keyturner service

View File

@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2020, nymea GmbH
* Copyright 2013 - 2025, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
@ -37,15 +37,16 @@
#include <QUuid>
#include <QPointer>
#include "typeutils.h"
#include "integrations/thing.h"
#include "bluez/bluetoothdevice.h"
#include "integrations/thingactioninfo.h"
#include <typeutils.h>
#include <integrations/thing.h>
#include <integrations/thingactioninfo.h>
#include "nukiutils.h"
#include "nukicontroller.h"
#include "nukiauthenticator.h"
#include "bluez/bluetoothdevice.h"
//#include "nacl-20110221/crypto_auth/"
class Nuki : public QObject

View File

@ -1,8 +1,6 @@
include(../plugins.pri)
TARGET = $$qtLibraryTarget(nymea_integrationpluginnuki)
QT += bluetooth dbus
QT *= bluetooth dbus
# apt install libsodium-dev
LIBS += -lsodium

View File

@ -127,7 +127,9 @@ QByteArray NukiAuthenticator::encryptData(const QByteArray &data, const QByteArr
* const unsigned char *sk The private key of nymea for this Nuki
*/
unsigned char encrypted[crypto_box_MACBYTES + data.length()];
unsigned char *encrypted = NULL;
std::vector<unsigned char> realBuffer(crypto_box_MACBYTES + data.length());
encrypted = &realBuffer[0];
int result = crypto_box_easy(encrypted,
reinterpret_cast<const unsigned char *>(data.data()),
static_cast<unsigned long long>(data.length()),
@ -165,8 +167,9 @@ QByteArray NukiAuthenticator::decryptData(const QByteArray &data, const QByteArr
* const unsigned char *pk The public key of the Nuki
* const unsigned char *sk The private key of nymea for this Nuki
*/
unsigned char decrypted[data.length() - crypto_box_MACBYTES];
unsigned char *decrypted = NULL;
std::vector<unsigned char> realBuffer(data.length() - crypto_box_MACBYTES);
decrypted = &realBuffer[0];
int result = crypto_box_open_easy(decrypted,
reinterpret_cast<const unsigned char *>(data.data()),
static_cast<unsigned long long>(data.length()),
@ -190,9 +193,11 @@ QByteArray NukiAuthenticator::decryptData(const QByteArray &data, const QByteArr
return decryptedData;
}
QByteArray NukiAuthenticator::generateNonce(const int &length) const
QByteArray NukiAuthenticator::generateNonce(int length) const
{
unsigned char nounce[length];
unsigned char *nounce = NULL;
std::vector<unsigned char> realBuffer(length);
nounce = &realBuffer[0];
randombytes_buf(nounce, length);
return QByteArray(reinterpret_cast<const char *>(nounce), length);
}
@ -295,7 +300,11 @@ void NukiAuthenticator::requestPublicKey()
qCDebug(dcNuki()) << "Authenticator: Request public key fom Nuki";
QByteArray payload;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QDataStream stream(&payload, QDataStream::WriteOnly);
#else
QDataStream stream(&payload, QIODevice::WriteOnly);
#endif
stream.setByteOrder(QDataStream::LittleEndian);
stream << static_cast<quint16>(NukiUtils::CommandPublicKey);
QByteArray data = NukiUtils::createRequestMessageForUnencrypted(NukiUtils::CommandRequestData, payload);
@ -351,7 +360,11 @@ void NukiAuthenticator::sendAuthenticateData()
m_nonce = generateNonce();
QByteArray content;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QDataStream stream(&content, QDataStream::WriteOnly);
#else
QDataStream stream(&content, QIODevice::WriteOnly);
#endif
// Note: 0x00 = App, 0x01 = Bridge, 0x02 = Fob
stream << static_cast<quint8>(0x01);
// Note: app id (42)
@ -359,7 +372,7 @@ void NukiAuthenticator::sendAuthenticateData()
// Note: the name of the bridge in 32 bytes [ 0 0 0 ... n y m e a ]
QByteArray name = QByteArray(27, '\0').append(QByteArray("nymea"));
Q_ASSERT_X(name.count() == 32, "data length", "Name has not the correct length.");
Q_ASSERT_X(name.length() == 32, "data length", "Name has not the correct length.");
QByteArray valueR = content;
valueR.append(name);
@ -454,7 +467,11 @@ void NukiAuthenticator::onPairingDataCharacteristicChanged(const QByteArray &val
// Process pairing characteristic data
QByteArray data = QByteArray(value);
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QDataStream stream(&data, QDataStream::ReadOnly);
#else
QDataStream stream(&data, QIODevice::ReadOnly);
#endif
stream.setByteOrder(QDataStream::LittleEndian);
quint16 command;
stream >> command;
@ -517,21 +534,25 @@ void NukiAuthenticator::onPairingDataCharacteristicChanged(const QByteArray &val
}
// Parse data
QByteArray message = m_currentReceivingData.mid(2, m_currentReceivingData.count() - 4);
QByteArray message = m_currentReceivingData.mid(2, m_currentReceivingData.length() - 4);
QByteArray authenticator = message.left(32);
Q_ASSERT_X(authenticator.count() == 32, "data length", "Nuki nonce has not the correct length.");
Q_ASSERT_X(authenticator.length() == 32, "data length", "Nuki nonce has not the correct length.");
// Read authorization ID
m_authorizationIdRawData = message.mid(32, 4);
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QDataStream stream(&m_authorizationIdRawData, QDataStream::ReadOnly);
#else
QDataStream stream(&m_authorizationIdRawData, QIODevice::ReadOnly);
#endif
stream.setByteOrder(QDataStream::LittleEndian);
stream >> m_authorizationId;
m_uuid = message.mid(36, 16);
Q_ASSERT_X(m_uuid.count() == 16, "data length", "UUID has not the correct length.");
Q_ASSERT_X(m_uuid.length() == 16, "data length", "UUID has not the correct length.");
m_nonceNuki = message.mid(52, 32);
Q_ASSERT_X(m_nonceNuki.count() == 32, "data length", "Nuki nonce has not the correct length.");
Q_ASSERT_X(m_nonceNuki.length() == 32, "data length", "Nuki nonce has not the correct length.");
if (m_debug) qCDebug(dcNuki()) << " Full message :" << NukiUtils::convertByteArrayToHexStringCompact(message);
if (m_debug) qCDebug(dcNuki()) << " Authenticator :" << NukiUtils::convertByteArrayToHexStringCompact(authenticator);

View File

@ -77,7 +77,7 @@ public:
QByteArray decryptData(const QByteArray &data, const QByteArray &nonce);
// Generate 32 byte nonce data
QByteArray generateNonce(const int &length = 32) const;
QByteArray generateNonce(int length = 32) const;
private:
QBluetoothHostInfo m_hostInfo;

View File

@ -238,7 +238,11 @@ void NukiController::processNukiStatesData(const QByteArray &data)
quint8 batteryCritical = 0;
QByteArray payload = data;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QDataStream stream(&payload, QDataStream::ReadOnly);
#else
QDataStream stream(&payload, QIODevice::ReadOnly);
#endif
stream.setByteOrder(QDataStream::LittleEndian);
stream >> nukiState >> nukiLockState >> nukiLockTrigger >> year >> month >> day >> hour >> minute >> second >> utcOffset >> batteryCritical;
@ -273,7 +277,11 @@ void NukiController::processNukiErrorReport(const QByteArray &data)
quint16 nukiCommand;
QByteArray payload = data;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QDataStream stream(&payload, QDataStream::ReadOnly);
#else
QDataStream stream(&payload, QIODevice::ReadOnly);
#endif
stream.setByteOrder(QDataStream::LittleEndian);
stream >> errorCode >> nukiCommand;
@ -441,7 +449,11 @@ void NukiController::sendReadLockStateRequest()
// Create data for encryption
QByteArray payload;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QDataStream stream(&payload, QDataStream::WriteOnly);
#else
QDataStream stream(&payload, QIODevice::WriteOnly);
#endif
stream.setByteOrder(QDataStream::LittleEndian);
stream << static_cast<quint16>(NukiUtils::CommandNukiStates);
@ -477,7 +489,11 @@ void NukiController::sendReadConfigurationRequest()
// Create data for encryption
QByteArray payload;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QDataStream stream(&payload, QDataStream::WriteOnly);
#else
QDataStream stream(&payload, QIODevice::WriteOnly);
#endif
stream.setByteOrder(QDataStream::LittleEndian);
stream << static_cast<quint16>(NukiUtils::CommandRequestConfig);
for (int i = 0; i < m_nukiNonce.length(); i++) {
@ -516,7 +532,11 @@ void NukiController::sendRequestChallengeRequest()
// Create data for encryption
QByteArray payload;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QDataStream stream(&payload, QDataStream::WriteOnly);
#else
QDataStream stream(&payload, QIODevice::WriteOnly);
#endif
stream.setByteOrder(QDataStream::LittleEndian);
stream << static_cast<quint16>(NukiUtils::CommandChallenge);
@ -554,7 +574,11 @@ void NukiController::sendLockActionRequest(NukiUtils::LockAction lockAction, qui
// Create data for encryption
QByteArray payload;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QDataStream stream(&payload, QDataStream::WriteOnly);
#else
QDataStream stream(&payload, QIODevice::WriteOnly);
#endif
stream.setByteOrder(QDataStream::LittleEndian);
stream << static_cast<quint8>(lockAction);
stream << static_cast<quint32>(m_nukiAuthenticator->authorizationId());
@ -596,7 +620,7 @@ void NukiController::onUserDataCharacteristicChanged(const QByteArray &value)
m_messageBuffer = value;
if (m_messageBuffer.count() < 30) {
if (m_messageBuffer.length() < 30) {
qCWarning(dcNuki()) << "Controller: Cannot understand message. Rejecting.";
resetMessageBuffer();
return;
@ -605,14 +629,18 @@ void NukiController::onUserDataCharacteristicChanged(const QByteArray &value)
// Parse message length
// ADATA: 24 byte nonce, 4 byte autorization, 2 byte encrypted message length
m_messageBufferAData = m_messageBuffer.left(30);
m_messageBufferPData = m_messageBuffer.right(m_messageBuffer.count() - 30);
m_messageBufferPData = m_messageBuffer.right(m_messageBuffer.length() - 30);
m_messageBufferNonce = m_messageBufferAData.left(24);
QByteArray messageInformation = m_messageBufferAData.right(6);
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QDataStream stream(&messageInformation, QDataStream::ReadOnly);
#else
QDataStream stream(&messageInformation, QIODevice::ReadOnly);
#endif
stream.setByteOrder(QDataStream::LittleEndian);
stream >> m_messageBufferIdentifier >> m_messageBufferLength;
if (m_messageBufferPData.count() == m_messageBufferLength) {
if (m_messageBufferPData.length() == m_messageBufferLength) {
processUserDataNotification(m_messageBufferNonce, m_messageBufferIdentifier, m_messageBufferPData);
resetMessageBuffer();
}

View File

@ -44,9 +44,9 @@ QString NukiUtils::convertByteToHexString(const quint8 &byte)
QString NukiUtils::convertByteArrayToHexString(const QByteArray &byteArray)
{
QString hexString;
for (int i = 0; i < byteArray.count(); i++) {
for (int i = 0; i < byteArray.length(); i++) {
hexString.append(convertByteToHexString(static_cast<quint8>(byteArray.at(i))));
if (i != byteArray.count() - 1) {
if (i != byteArray.length() - 1) {
hexString.append(" ");
}
}
@ -67,7 +67,11 @@ QString NukiUtils::convertByteArrayToHexStringCompact(const QByteArray &byteArra
QString NukiUtils::convertUint16ToHexString(const quint16 &value)
{
QByteArray data;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QDataStream stream(&data, QDataStream::WriteOnly);
#else
QDataStream stream(&data, QIODevice::WriteOnly);
#endif
stream << value;
return QString("0x%1").arg(convertByteArrayToHexString(data).remove(" ").remove("0x"));
@ -76,7 +80,11 @@ QString NukiUtils::convertUint16ToHexString(const quint16 &value)
QByteArray NukiUtils::converUint32ToByteArrayLittleEndian(const quint32 &value)
{
QByteArray data;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QDataStream stream(&data, QDataStream::WriteOnly);
#else
QDataStream stream(&data, QIODevice::WriteOnly);
#endif
stream.setByteOrder(QDataStream::LittleEndian);
stream << value;
Q_ASSERT_X(data.length() == 4, "data converting", "Could not convert quint32 value to byte array (little endian)");
@ -86,7 +94,11 @@ QByteArray NukiUtils::converUint32ToByteArrayLittleEndian(const quint32 &value)
QByteArray NukiUtils::converUint16ToByteArrayLittleEndian(const quint16 &value)
{
QByteArray data;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QDataStream stream(&data, QDataStream::WriteOnly);
#else
QDataStream stream(&data, QIODevice::WriteOnly);
#endif
stream.setByteOrder(QDataStream::LittleEndian);
stream << value;
Q_ASSERT_X(data.length() == 2, "data converting", "Could not convert quint16 value to byte array (little endian)");
@ -99,7 +111,11 @@ quint16 NukiUtils::convertByteArrayToUint16BigEndian(const QByteArray &littleEnd
quint16 value = 0;
QByteArray data(littleEndianByteArray);
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QDataStream stream(&data, QDataStream::ReadOnly);
#else
QDataStream stream(&data, QIODevice::ReadOnly);
#endif
stream.setByteOrder(QDataStream::LittleEndian);
stream >> value;
return value;
@ -111,7 +127,11 @@ quint32 NukiUtils::convertByteArrayToUint32BigEndian(const QByteArray &littleEnd
quint32 value = 0;
QByteArray data(littleEndianByteArray);
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QDataStream stream(&data, QDataStream::ReadOnly);
#else
QDataStream stream(&data, QIODevice::ReadOnly);
#endif
stream.setByteOrder(QDataStream::LittleEndian);
stream >> value;
return value;
@ -140,11 +160,15 @@ bool NukiUtils::validateMessageCrc(const QByteArray &message)
{
quint16 crcValue = 0;
QByteArray crcValueRaw = message.right(2);
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QDataStream stream(&crcValueRaw, QDataStream::ReadOnly);
#else
QDataStream stream(&crcValueRaw, QIODevice::ReadOnly);
#endif
stream.setByteOrder(QDataStream::LittleEndian);
stream >> crcValue;
QByteArray content = message.left(message.count() - 2);
QByteArray content = message.left(message.length() - 2);
quint16 calculatedCrcValue = calculateCrc(content);
if (crcValue != calculatedCrcValue) {
@ -163,7 +187,11 @@ QByteArray NukiUtils::createRequestMessageForUnencrypted(NukiUtils::Command comm
* 2 Bytes: crc (LittleEndian)
*/
QByteArray message;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QDataStream stream(&message, QDataStream::WriteOnly);
#else
QDataStream stream(&message, QIODevice::WriteOnly);
#endif
stream.setByteOrder(QDataStream::LittleEndian);
stream << static_cast<quint16>(command);
@ -186,7 +214,11 @@ QByteArray NukiUtils::createRequestMessageForUnencryptedForEncryption(quint32 au
*/
QByteArray message;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QDataStream stream(&message, QDataStream::WriteOnly);
#else
QDataStream stream(&message, QIODevice::WriteOnly);
#endif
stream.setByteOrder(QDataStream::LittleEndian);
stream << authenticationId;
stream << static_cast<quint16>(command);