Add nuki plugin
Revert copyright changes
This commit is contained in:
parent
af1570e372
commit
e838e0ddd2
17
debian/control
vendored
17
debian/control
vendored
@ -14,6 +14,7 @@ Build-depends: libboblight-dev,
|
|||||||
qtbase5-dev,
|
qtbase5-dev,
|
||||||
qtconnectivity5-dev,
|
qtconnectivity5-dev,
|
||||||
libow-dev,
|
libow-dev,
|
||||||
|
libsodium-dev,
|
||||||
Standards-Version: 3.9.3
|
Standards-Version: 3.9.3
|
||||||
|
|
||||||
|
|
||||||
@ -453,6 +454,21 @@ Description: nymea.io plugin for networkdetector
|
|||||||
This package will install the nymea.io plugin for networkdetector
|
This package will install the nymea.io plugin for networkdetector
|
||||||
|
|
||||||
|
|
||||||
|
Package: nymea-plugin-nuki
|
||||||
|
Architecture: any
|
||||||
|
Depends: ${shlibs:Depends},
|
||||||
|
${misc:Depends},
|
||||||
|
libsodium23,
|
||||||
|
nymea-plugins-translations,
|
||||||
|
Description: nymea.io plugin for the nuki smart lock
|
||||||
|
The nymea daemon is a plugin based IoT (Internet of Things) server. The
|
||||||
|
server works like a translator for devices, things and services and
|
||||||
|
allows them to interact.
|
||||||
|
With the powerful rule engine you are able to connect any device available
|
||||||
|
in the system and create individual scenes and behaviors for your environment.
|
||||||
|
.
|
||||||
|
This package will install the nymea.io plugin for nuki devices
|
||||||
|
|
||||||
|
|
||||||
Package: nymea-plugin-onewire
|
Package: nymea-plugin-onewire
|
||||||
Architecture: any
|
Architecture: any
|
||||||
@ -877,6 +893,7 @@ Depends: nymea-plugin-anel,
|
|||||||
nymea-plugin-texasinstruments,
|
nymea-plugin-texasinstruments,
|
||||||
nymea-plugin-netatmo,
|
nymea-plugin-netatmo,
|
||||||
nymea-plugin-networkdetector,
|
nymea-plugin-networkdetector,
|
||||||
|
nymea-plugin-nuki,
|
||||||
nymea-plugin-openuv,
|
nymea-plugin-openuv,
|
||||||
nymea-plugin-openweathermap,
|
nymea-plugin-openweathermap,
|
||||||
nymea-plugin-philipshue,
|
nymea-plugin-philipshue,
|
||||||
|
|||||||
1
debian/copyright
vendored
1
debian/copyright
vendored
@ -54,6 +54,7 @@ Files: avahimonitor/*
|
|||||||
mailnotification/*
|
mailnotification/*
|
||||||
netatmo/*
|
netatmo/*
|
||||||
networkdetector/*
|
networkdetector/*
|
||||||
|
nuki/*
|
||||||
osdomotics/*
|
osdomotics/*
|
||||||
senic/*
|
senic/*
|
||||||
udpcommander/*
|
udpcommander/*
|
||||||
|
|||||||
1
debian/nymea-plugin-nuki.install.in
vendored
Normal file
1
debian/nymea-plugin-nuki.install.in
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_devicepluginnuki.so
|
||||||
3
nuki/README.md
Normal file
3
nuki/README.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# nymea-plugin-nuki
|
||||||
|
|
||||||
|
Device plugin for the [Nuki Smartlock](https://nuki.io/en/) Bluetooth API.
|
||||||
416
nuki/bluez/bluetoothadapter.cpp
Normal file
416
nuki/bluez/bluetoothadapter.cpp
Normal file
@ -0,0 +1,416 @@
|
|||||||
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
* *
|
||||||
|
* Copyright (C) 2016-2018 Simon Stürz <simon.stuerz@guh.io> *
|
||||||
|
* *
|
||||||
|
* This file is part of guh. *
|
||||||
|
* *
|
||||||
|
* This library is free software; you can redistribute it and/or *
|
||||||
|
* modify it under the terms of the GNU Lesser General Public *
|
||||||
|
* License as published by the Free Software Foundation; either *
|
||||||
|
* version 2.1 of the License, or (at your option) any later version. *
|
||||||
|
* *
|
||||||
|
* This library is distributed in the hope that it will be useful, *
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
||||||
|
* Lesser General Public License for more details. *
|
||||||
|
* *
|
||||||
|
* You should have received a copy of the GNU Lesser General Public *
|
||||||
|
* License along with this library; If not, see *
|
||||||
|
* <http://www.gnu.org/licenses/>. *
|
||||||
|
* *
|
||||||
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
|
#include "bluetoothadapter.h"
|
||||||
|
#include "blueztypes.h"
|
||||||
|
|
||||||
|
#include <QDBusPendingReply>
|
||||||
|
|
||||||
|
QString BluetoothAdapter::name() const
|
||||||
|
{
|
||||||
|
return m_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString BluetoothAdapter::alias() const
|
||||||
|
{
|
||||||
|
return m_alias;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BluetoothAdapter::setAlias(const QString &alias)
|
||||||
|
{
|
||||||
|
if (!m_adapterInterface->isValid())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return m_adapterInterface->setProperty("Alias", QVariant(alias));
|
||||||
|
}
|
||||||
|
|
||||||
|
QString BluetoothAdapter::address() const
|
||||||
|
{
|
||||||
|
return m_address;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString BluetoothAdapter::modalias() const
|
||||||
|
{
|
||||||
|
return m_modalias;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BluetoothAdapter::discovering() const
|
||||||
|
{
|
||||||
|
return m_discovering;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BluetoothAdapter::discoverable() const
|
||||||
|
{
|
||||||
|
return m_discoverable;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BluetoothAdapter::setDiscoverable(const bool &discoverable)
|
||||||
|
{
|
||||||
|
if (!m_adapterInterface->isValid())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return m_adapterInterface->setProperty("Discoverable", QVariant(discoverable));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint BluetoothAdapter::discoverableTimeout() const
|
||||||
|
{
|
||||||
|
return m_discoverableTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BluetoothAdapter::setDiscoverableTimeout(const uint &seconds)
|
||||||
|
{
|
||||||
|
if (!m_adapterInterface->isValid())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return m_adapterInterface->setProperty("DiscoverableTimeout", QVariant(seconds));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BluetoothAdapter::pairable() const
|
||||||
|
{
|
||||||
|
return m_pairable;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BluetoothAdapter::setPairable(const bool &pairable)
|
||||||
|
{
|
||||||
|
if (!m_adapterInterface->isValid())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return m_adapterInterface->setProperty("Pairable", QVariant(pairable));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint BluetoothAdapter::pairableTimeout() const
|
||||||
|
{
|
||||||
|
return m_pairableTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BluetoothAdapter::setPairableTimeout(const uint &seconds)
|
||||||
|
{
|
||||||
|
if (!m_adapterInterface->isValid())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return m_adapterInterface->setProperty("PairableTimeout", QVariant(seconds));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint BluetoothAdapter::adapterClass() const
|
||||||
|
{
|
||||||
|
return m_adapterClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString BluetoothAdapter::adapterClassString() const
|
||||||
|
{
|
||||||
|
QString adapterClassString;
|
||||||
|
switch (m_adapterClass) {
|
||||||
|
// TODO: get device type from class
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return adapterClassString;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BluetoothAdapter::powered() const
|
||||||
|
{
|
||||||
|
return m_powered;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BluetoothAdapter::setPower(const bool &power)
|
||||||
|
{
|
||||||
|
if (!m_adapterInterface->isValid())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return m_adapterInterface->setProperty("Powered", QVariant(power));
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList BluetoothAdapter::uuids() const
|
||||||
|
{
|
||||||
|
return m_uuids;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<BluetoothDevice *> BluetoothAdapter::devices() const
|
||||||
|
{
|
||||||
|
return m_devices;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BluetoothAdapter::hasDevice(const QBluetoothAddress &address)
|
||||||
|
{
|
||||||
|
foreach (BluetoothDevice *device, m_devices) {
|
||||||
|
if (device->address() == address) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BluetoothDevice *BluetoothAdapter::getDevice(const QBluetoothAddress &address)
|
||||||
|
{
|
||||||
|
foreach (BluetoothDevice *device, m_devices) {
|
||||||
|
if (device->address() == address) {
|
||||||
|
return device;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BluetoothAdapter::removeDevice(const QBluetoothAddress &address)
|
||||||
|
{
|
||||||
|
foreach (BluetoothDevice *device, m_devices) {
|
||||||
|
if (device->address() == address) {
|
||||||
|
return removeDevice(device->m_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BluetoothAdapter::isValid() const
|
||||||
|
{
|
||||||
|
return m_adapterInterface->isValid();
|
||||||
|
}
|
||||||
|
|
||||||
|
BluetoothAdapter::BluetoothAdapter(const QDBusObjectPath &path, const QVariantMap &properties, QObject *parent) :
|
||||||
|
QObject(parent),
|
||||||
|
m_path(path),
|
||||||
|
m_discovering(false),
|
||||||
|
m_discoverable(false),
|
||||||
|
m_discoverableTimeout(0),
|
||||||
|
m_pairable(false),
|
||||||
|
m_pairableTimeout(0),
|
||||||
|
m_adapterClass(0),
|
||||||
|
m_powered(false)
|
||||||
|
{
|
||||||
|
// Check DBus connection
|
||||||
|
if (!QDBusConnection::systemBus().isConnected()) {
|
||||||
|
qCWarning(dcBluez()) << "System DBus not connected.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_adapterInterface = new QDBusInterface(orgBluez, m_path.path(), orgBluezAdapter1, QDBusConnection::systemBus(), this);
|
||||||
|
if (!m_adapterInterface->isValid()) {
|
||||||
|
qCWarning(dcBluez()) << "Invalid DBus adapter interface for" << m_path.path();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDBusConnection::systemBus().connect(orgBluez, m_path.path(), "org.freedesktop.DBus.Properties", "PropertiesChanged", this, SLOT(onPropertiesChanged(QString,QVariantMap,QStringList)));
|
||||||
|
|
||||||
|
processProperties(properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
BluetoothAdapter::~BluetoothAdapter()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothAdapter::processProperties(const QVariantMap &properties)
|
||||||
|
{
|
||||||
|
foreach (const QString &propertyName, properties.keys()) {
|
||||||
|
if (propertyName == "Name") {
|
||||||
|
m_name = properties.value(propertyName).toString();
|
||||||
|
} else if (propertyName == "Alias") {
|
||||||
|
setAliasInternally(properties.value(propertyName).toString());
|
||||||
|
} else if (propertyName == "Address") {
|
||||||
|
m_address = properties.value(propertyName).toString();
|
||||||
|
} else if (propertyName == "Modalias") {
|
||||||
|
m_modalias = properties.value(propertyName).toString();
|
||||||
|
} else if (propertyName == "Discovering") {
|
||||||
|
setDiscoveringInternally(properties.value(propertyName).toBool());
|
||||||
|
} else if (propertyName == "Discoverable") {
|
||||||
|
setDiscoverableInernally(properties.value(propertyName).toBool());
|
||||||
|
} else if (propertyName == "DiscoverableTimeout") {
|
||||||
|
setDiscoverableTimeoutInternally(properties.value(propertyName).toUInt());
|
||||||
|
} else if (propertyName == "Pairable") {
|
||||||
|
setPairableInternally(properties.value(propertyName).toBool());
|
||||||
|
} else if (propertyName == "PairableTimeout") {
|
||||||
|
setPairableTimeoutInternally(properties.value(propertyName).toUInt());
|
||||||
|
} else if (propertyName == "Class") {
|
||||||
|
m_adapterClass = properties.value(propertyName).toUInt();
|
||||||
|
} else if (propertyName == "Powered") {
|
||||||
|
setPoweredInternally(properties.value(propertyName).toBool());
|
||||||
|
} else if (propertyName == "UUIDs") {
|
||||||
|
m_uuids = properties.value(propertyName).toStringList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothAdapter::addDeviceInternally(const QDBusObjectPath &path, const QVariantMap &properties)
|
||||||
|
{
|
||||||
|
// Check if device already added
|
||||||
|
if (hasDevice(path))
|
||||||
|
return;
|
||||||
|
|
||||||
|
BluetoothDevice *device = new BluetoothDevice(path, properties, this);
|
||||||
|
m_devices.append(device);
|
||||||
|
|
||||||
|
qCDebug(dcBluez()) << "[+]" << device;
|
||||||
|
|
||||||
|
emit deviceAdded(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothAdapter::removeDeviceInternally(const QDBusObjectPath &path)
|
||||||
|
{
|
||||||
|
foreach (BluetoothDevice *device, m_devices) {
|
||||||
|
if (device->m_path == path) {
|
||||||
|
m_devices.removeOne(device);
|
||||||
|
emit deviceRemoved(device);
|
||||||
|
device->deleteLater();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BluetoothAdapter::hasDevice(const QDBusObjectPath &path)
|
||||||
|
{
|
||||||
|
foreach (BluetoothDevice *device, m_devices) {
|
||||||
|
if (device->m_path == path)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BluetoothAdapter::removeDevice(const QDBusObjectPath &path)
|
||||||
|
{
|
||||||
|
if (!m_adapterInterface->isValid()) {
|
||||||
|
qCWarning(dcBluez()) << "Invalid DBus adapter interface for" << m_path.path();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
qCDebug(dcBluez()) << "Remove and unpair device" << path.path();
|
||||||
|
QDBusPendingCall removeCall = m_adapterInterface->asyncCall("RemoveDevice", QVariant::fromValue(path));
|
||||||
|
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(removeCall, this);
|
||||||
|
connect(watcher, &QDBusPendingCallWatcher::finished, this, &BluetoothAdapter::onRemoveDeviceFinished);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothAdapter::setAliasInternally(const QString &alias)
|
||||||
|
{
|
||||||
|
if (m_alias != alias) {
|
||||||
|
m_alias = alias;
|
||||||
|
emit aliasChanged(m_alias);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothAdapter::setDiscoveringInternally(const bool &discovering)
|
||||||
|
{
|
||||||
|
if (m_discovering != discovering) {
|
||||||
|
m_discovering = discovering;
|
||||||
|
emit discoveringChanged(m_discovering);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothAdapter::setDiscoverableInernally(const bool &discoverable)
|
||||||
|
{
|
||||||
|
if (m_discoverable != discoverable) {
|
||||||
|
m_discoverable = discoverable;
|
||||||
|
emit discoverableChanged(m_discoverable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothAdapter::setDiscoverableTimeoutInternally(const uint &discoverableTimeout)
|
||||||
|
{
|
||||||
|
if (m_discoverableTimeout != discoverableTimeout) {
|
||||||
|
m_discoverableTimeout = discoverableTimeout;
|
||||||
|
emit discoverableTimeoutChanged(m_discoverableTimeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothAdapter::setPairableInternally(const bool &pairable)
|
||||||
|
{
|
||||||
|
if (m_pairable != pairable) {
|
||||||
|
m_pairable = pairable;
|
||||||
|
emit pairableChanged(m_pairable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothAdapter::setPairableTimeoutInternally(const uint &pairableTimeout)
|
||||||
|
{
|
||||||
|
if (m_pairableTimeout != pairableTimeout) {
|
||||||
|
m_pairableTimeout = pairableTimeout;
|
||||||
|
emit pairableTimeoutChanged(m_pairableTimeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothAdapter::setPoweredInternally(const bool &powered)
|
||||||
|
{
|
||||||
|
if (m_powered != powered) {
|
||||||
|
m_powered = powered;
|
||||||
|
emit poweredChanged(m_powered);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothAdapter::onPropertiesChanged(const QString &interface, const QVariantMap &changedProperties, const QStringList &invalidatedProperties)
|
||||||
|
{
|
||||||
|
if (interface != orgBluezAdapter1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
qCDebug(dcBluez()) << "BluetoothAdapter:" << m_name << m_address << "properties changed" << interface << changedProperties << invalidatedProperties;
|
||||||
|
processProperties(changedProperties);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothAdapter::onRemoveDeviceFinished(QDBusPendingCallWatcher *call)
|
||||||
|
{
|
||||||
|
QDBusPendingReply<void> reply = *call;
|
||||||
|
if (reply.isError())
|
||||||
|
qCWarning(dcBluez()) << "Could not remove device" << m_address << reply.error().name() << reply.error().message();
|
||||||
|
|
||||||
|
call->deleteLater();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothAdapter::startDiscovering()
|
||||||
|
{
|
||||||
|
if (!m_adapterInterface->isValid()) {
|
||||||
|
qCWarning(dcBluez()) << "Invalid DBus adapter interface for" << m_path.path();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (discovering())
|
||||||
|
return;
|
||||||
|
|
||||||
|
QDBusMessage query = m_adapterInterface->call("StartDiscovery");
|
||||||
|
if(query.type() != QDBusMessage::ReplyMessage) {
|
||||||
|
qCWarning(dcBluez()) << "Could not start discovery" << m_name << ":" << query.errorName() << query.errorMessage();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothAdapter::stopDiscovering()
|
||||||
|
{
|
||||||
|
if (!m_adapterInterface->isValid()) {
|
||||||
|
qCWarning(dcBluez()) << "Invalid DBus adapter interface for" << m_path.path();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDBusMessage query = m_adapterInterface->call("StopDiscovery");
|
||||||
|
if(query.type() != QDBusMessage::ReplyMessage) {
|
||||||
|
qCWarning(dcBluez()) << "Could not start discovery" << m_name << ":" << query.errorName() << query.errorMessage();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QDebug operator<<(QDebug debug, BluetoothAdapter *adapter)
|
||||||
|
{
|
||||||
|
debug.noquote().nospace() << "BluetoothAdapter(" << adapter->name() << ", " << adapter->address();
|
||||||
|
debug.noquote().nospace() << ", powered: " << adapter->powered();
|
||||||
|
debug.noquote().nospace() << ", pairable: " << adapter->pairable();
|
||||||
|
debug.noquote().nospace() << ", visible: " << adapter->discoverable();
|
||||||
|
debug.noquote().nospace() << ") ";
|
||||||
|
return debug;
|
||||||
|
}
|
||||||
152
nuki/bluez/bluetoothadapter.h
Normal file
152
nuki/bluez/bluetoothadapter.h
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
* *
|
||||||
|
* Copyright (C) 2016-2018 Simon Stürz <simon.stuerz@guh.io> *
|
||||||
|
* *
|
||||||
|
* This file is part of guh. *
|
||||||
|
* *
|
||||||
|
* This library is free software; you can redistribute it and/or *
|
||||||
|
* modify it under the terms of the GNU Lesser General Public *
|
||||||
|
* License as published by the Free Software Foundation; either *
|
||||||
|
* version 2.1 of the License, or (at your option) any later version. *
|
||||||
|
* *
|
||||||
|
* This library is distributed in the hope that it will be useful, *
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
||||||
|
* Lesser General Public License for more details. *
|
||||||
|
* *
|
||||||
|
* You should have received a copy of the GNU Lesser General Public *
|
||||||
|
* License along with this library; If not, see *
|
||||||
|
* <http://www.gnu.org/licenses/>. *
|
||||||
|
* *
|
||||||
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
|
#ifndef BLUETOOTHADAPTER_H
|
||||||
|
#define BLUETOOTHADAPTER_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QDBusInterface>
|
||||||
|
#include <QDBusConnection>
|
||||||
|
#include <QDBusObjectPath>
|
||||||
|
|
||||||
|
#include "bluetoothdevice.h"
|
||||||
|
|
||||||
|
// Note: DBus documentation https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/adapter-api.txt
|
||||||
|
|
||||||
|
class BluetoothManager;
|
||||||
|
|
||||||
|
class BluetoothAdapter : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
friend class BluetoothManager;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Properties
|
||||||
|
QString name() const;
|
||||||
|
|
||||||
|
QString alias() const;
|
||||||
|
bool setAlias(const QString &alias);
|
||||||
|
|
||||||
|
QString address() const;
|
||||||
|
QString modalias() const;
|
||||||
|
bool discovering() const;
|
||||||
|
|
||||||
|
bool discoverable() const;
|
||||||
|
bool setDiscoverable(const bool &discoverable);
|
||||||
|
|
||||||
|
uint discoverableTimeout() const;
|
||||||
|
bool setDiscoverableTimeout(const uint &seconds);
|
||||||
|
|
||||||
|
bool pairable() const;
|
||||||
|
bool setPairable(const bool &pairable);
|
||||||
|
|
||||||
|
uint pairableTimeout() const;
|
||||||
|
bool setPairableTimeout(const uint &seconds);
|
||||||
|
|
||||||
|
uint adapterClass() const;
|
||||||
|
QString adapterClassString() const;
|
||||||
|
|
||||||
|
bool powered() const;
|
||||||
|
bool setPower(const bool &power);
|
||||||
|
|
||||||
|
QStringList uuids() const;
|
||||||
|
|
||||||
|
QList<BluetoothDevice *> devices() const;
|
||||||
|
bool hasDevice(const QBluetoothAddress &address);
|
||||||
|
BluetoothDevice *getDevice(const QBluetoothAddress &address);
|
||||||
|
bool removeDevice(const QBluetoothAddress &address);
|
||||||
|
|
||||||
|
bool isValid() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Note: only BluetoothManager can create adapter objects
|
||||||
|
explicit BluetoothAdapter(const QDBusObjectPath &path, const QVariantMap &properties, QObject *parent = 0);
|
||||||
|
~BluetoothAdapter();
|
||||||
|
|
||||||
|
QDBusObjectPath m_path;
|
||||||
|
QDBusInterface *m_adapterInterface;
|
||||||
|
|
||||||
|
QString m_name;
|
||||||
|
QString m_address;
|
||||||
|
|
||||||
|
QString m_alias;
|
||||||
|
QString m_modalias;
|
||||||
|
bool m_discovering;
|
||||||
|
bool m_discoverable;
|
||||||
|
uint m_discoverableTimeout;
|
||||||
|
bool m_pairable;
|
||||||
|
uint m_pairableTimeout;
|
||||||
|
uint m_adapterClass;
|
||||||
|
bool m_powered;
|
||||||
|
QStringList m_uuids;
|
||||||
|
|
||||||
|
QList<BluetoothDevice *> m_devices;
|
||||||
|
|
||||||
|
void processProperties(const QVariantMap &properties);
|
||||||
|
|
||||||
|
// Methods called from BluetoothManager
|
||||||
|
void addDeviceInternally(const QDBusObjectPath &path, const QVariantMap &properties);
|
||||||
|
void removeDeviceInternally(const QDBusObjectPath &path);
|
||||||
|
|
||||||
|
// Verification methods
|
||||||
|
bool hasDevice(const QDBusObjectPath &path);
|
||||||
|
|
||||||
|
// DBus methods
|
||||||
|
bool removeDevice(const QDBusObjectPath &path);
|
||||||
|
|
||||||
|
|
||||||
|
void setAliasInternally(const QString &alias);
|
||||||
|
void setDiscoveringInternally(const bool &discovering);
|
||||||
|
void setDiscoverableInernally(const bool &discoverable);
|
||||||
|
void setDiscoverableTimeoutInternally(const uint &discoverableTimeout);
|
||||||
|
void setPairableInternally(const bool &pairable);
|
||||||
|
void setPairableTimeoutInternally(const uint &pairableTimeout);
|
||||||
|
void setPoweredInternally(const bool &powered);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onPropertiesChanged(const QString &interface, const QVariantMap &changedProperties, const QStringList &invalidatedProperties);
|
||||||
|
void onRemoveDeviceFinished(QDBusPendingCallWatcher *call);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void aliasChanged(const QString &alias);
|
||||||
|
void discoveringChanged(const bool &discovering);
|
||||||
|
void discoverableChanged(const bool &discoverable);
|
||||||
|
void discoverableTimeoutChanged(const uint &discoverableTimeout);
|
||||||
|
void pairableChanged(const bool &pairable);
|
||||||
|
void pairableTimeoutChanged(const uint &pairableTimeout);
|
||||||
|
void poweredChanged(const bool &powered);
|
||||||
|
|
||||||
|
void deviceAdded(BluetoothDevice *device);
|
||||||
|
void deviceRemoved(BluetoothDevice *device);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void startDiscovering();
|
||||||
|
void stopDiscovering();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
QDebug operator<<(QDebug debug, BluetoothAdapter *adapter);
|
||||||
|
|
||||||
|
|
||||||
|
#endif // BLUETOOTHADAPTER_H
|
||||||
546
nuki/bluez/bluetoothdevice.cpp
Normal file
546
nuki/bluez/bluetoothdevice.cpp
Normal file
@ -0,0 +1,546 @@
|
|||||||
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
* *
|
||||||
|
* Copyright (C) 2016-2018 Simon Stürz <simon.stuerz@guh.io> *
|
||||||
|
* *
|
||||||
|
* This file is part of guh. *
|
||||||
|
* *
|
||||||
|
* This library is free software; you can redistribute it and/or *
|
||||||
|
* modify it under the terms of the GNU Lesser General Public *
|
||||||
|
* License as published by the Free Software Foundation; either *
|
||||||
|
* version 2.1 of the License, or (at your option) any later version. *
|
||||||
|
* *
|
||||||
|
* This library is distributed in the hope that it will be useful, *
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
||||||
|
* Lesser General Public License for more details. *
|
||||||
|
* *
|
||||||
|
* You should have received a copy of the GNU Lesser General Public *
|
||||||
|
* License along with this library; If not, see *
|
||||||
|
* <http://www.gnu.org/licenses/>. *
|
||||||
|
* *
|
||||||
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
|
#include "bluetoothdevice.h"
|
||||||
|
|
||||||
|
#include <QDBusPendingReply>
|
||||||
|
|
||||||
|
BluetoothDevice::State BluetoothDevice::state() const
|
||||||
|
{
|
||||||
|
return m_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString BluetoothDevice::name() const
|
||||||
|
{
|
||||||
|
return m_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
QBluetoothAddress BluetoothDevice::address() const
|
||||||
|
{
|
||||||
|
return m_address;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString BluetoothDevice::iconName() const
|
||||||
|
{
|
||||||
|
return m_iconName;
|
||||||
|
}
|
||||||
|
|
||||||
|
QBluetoothHostInfo BluetoothDevice::hostInfo() const
|
||||||
|
{
|
||||||
|
return m_hostInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString BluetoothDevice::alias() const
|
||||||
|
{
|
||||||
|
return m_alias;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BluetoothDevice::setAlias(const QString &alias)
|
||||||
|
{
|
||||||
|
if (!m_deviceInterface->isValid())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return m_deviceInterface->setProperty("Alias", QVariant(alias));
|
||||||
|
}
|
||||||
|
|
||||||
|
QString BluetoothDevice::modalias() const
|
||||||
|
{
|
||||||
|
return m_modalias;
|
||||||
|
}
|
||||||
|
|
||||||
|
quint32 BluetoothDevice::deviceClass() const
|
||||||
|
{
|
||||||
|
return m_deviceClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
quint16 BluetoothDevice::appearance() const
|
||||||
|
{
|
||||||
|
return m_appearance;
|
||||||
|
}
|
||||||
|
|
||||||
|
qint16 BluetoothDevice::rssi() const
|
||||||
|
{
|
||||||
|
return m_rssi;
|
||||||
|
}
|
||||||
|
|
||||||
|
qint16 BluetoothDevice::txPower() const
|
||||||
|
{
|
||||||
|
return m_txPower;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<QBluetoothUuid> BluetoothDevice::uuids() const
|
||||||
|
{
|
||||||
|
return m_uuids;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BluetoothDevice::paired() const
|
||||||
|
{
|
||||||
|
return m_paired;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BluetoothDevice::connected() const
|
||||||
|
{
|
||||||
|
return m_connected;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BluetoothDevice::trusted() const
|
||||||
|
{
|
||||||
|
return m_trusted;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BluetoothDevice::setTrusted(const bool &trusted)
|
||||||
|
{
|
||||||
|
if (!m_deviceInterface->isValid())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return m_deviceInterface->setProperty("Trusted", QVariant(trusted));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BluetoothDevice::blocked() const
|
||||||
|
{
|
||||||
|
return m_blocked;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BluetoothDevice::setBlocked(const bool &blocked)
|
||||||
|
{
|
||||||
|
if (!m_deviceInterface->isValid())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return m_deviceInterface->setProperty("Blocked", QVariant(blocked));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BluetoothDevice::legacyPairing() const
|
||||||
|
{
|
||||||
|
return m_legacyPairing;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BluetoothDevice::servicesResolved() const
|
||||||
|
{
|
||||||
|
return m_servicesResolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<BluetoothGattService *> BluetoothDevice::services() const
|
||||||
|
{
|
||||||
|
return m_services;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BluetoothDevice::hasService(const QBluetoothUuid &serviceUuid)
|
||||||
|
{
|
||||||
|
foreach (BluetoothGattService *service, m_services) {
|
||||||
|
if (service->uuid() == serviceUuid) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BluetoothGattService *BluetoothDevice::getService(const QBluetoothUuid &serviceUuid)
|
||||||
|
{
|
||||||
|
foreach (BluetoothGattService *service, m_services) {
|
||||||
|
if (service->uuid() == serviceUuid) {
|
||||||
|
return service;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
BluetoothDevice::BluetoothDevice(const QDBusObjectPath &path, const QVariantMap &properties, QObject *parent) :
|
||||||
|
QObject(parent),
|
||||||
|
m_path(path),
|
||||||
|
m_state(Disconnected),
|
||||||
|
m_deviceClass(0),
|
||||||
|
m_appearance(0),
|
||||||
|
m_rssi(0),
|
||||||
|
m_txPower(0),
|
||||||
|
m_paired(false),
|
||||||
|
m_connected(false),
|
||||||
|
m_trusted(false),
|
||||||
|
m_blocked(false),
|
||||||
|
m_legacyPairing(false),
|
||||||
|
m_servicesResolved(false),
|
||||||
|
m_connectWatcher(nullptr),
|
||||||
|
m_disconnectWatcher(nullptr),
|
||||||
|
m_pairingWatcher(nullptr)
|
||||||
|
{
|
||||||
|
// Check DBus connection
|
||||||
|
if (!QDBusConnection::systemBus().isConnected()) {
|
||||||
|
qCWarning(dcBluez()) << "System DBus not connected.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_deviceInterface = new QDBusInterface(orgBluez, m_path.path(), orgBluezDevice1, QDBusConnection::systemBus(), this);
|
||||||
|
if (!m_deviceInterface->isValid()) {
|
||||||
|
qCWarning(dcBluez()) << "Invalid DBus device interface for" << m_path.path();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDBusConnection::systemBus().connect(orgBluez, m_path.path(), "org.freedesktop.DBus.Properties", "PropertiesChanged", this, SLOT(onPropertiesChanged(QString,QVariantMap,QStringList)));
|
||||||
|
|
||||||
|
processProperties(properties);
|
||||||
|
|
||||||
|
// Set initial state
|
||||||
|
evaluateCurrentState();
|
||||||
|
}
|
||||||
|
|
||||||
|
BluetoothDevice::~BluetoothDevice()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothDevice::processProperties(const QVariantMap &properties)
|
||||||
|
{
|
||||||
|
foreach (const QString &propertyName, properties.keys()) {
|
||||||
|
if (propertyName == "Name") {
|
||||||
|
m_name = properties.value(propertyName).toString();
|
||||||
|
m_hostInfo.setName(m_name);
|
||||||
|
} else if (propertyName == "Address") {
|
||||||
|
m_address = QBluetoothAddress(properties.value(propertyName).toString());
|
||||||
|
m_hostInfo.setAddress(m_address);
|
||||||
|
} else if (propertyName == "Icon") {
|
||||||
|
m_iconName = properties.value(propertyName).toString();
|
||||||
|
} else if (propertyName == "Alias") {
|
||||||
|
setAliasInternally(properties.value(propertyName).toString());
|
||||||
|
} else if (propertyName == "Modalias") {
|
||||||
|
m_modalias = properties.value(propertyName).toString();
|
||||||
|
} else if (propertyName == "Class") {
|
||||||
|
m_deviceClass = properties.value(propertyName).toUInt();
|
||||||
|
} else if (propertyName == "Appearance") {
|
||||||
|
m_appearance = properties.value(propertyName).toUInt();
|
||||||
|
} else if (propertyName == "RSSI") {
|
||||||
|
setRssiInternally(properties.value(propertyName).toInt());
|
||||||
|
} else if (propertyName == "TxPower") {
|
||||||
|
setTxPowerInternally(properties.value(propertyName).toInt());
|
||||||
|
} else if (propertyName == "UUIDs") {
|
||||||
|
QStringList uuidStrings = properties.value(propertyName).toStringList();
|
||||||
|
m_uuids.clear();
|
||||||
|
foreach (const QString &uuidString, uuidStrings) {
|
||||||
|
QBluetoothUuid uuid = QBluetoothUuid(QUuid(uuidString));
|
||||||
|
m_uuids.append(uuid);
|
||||||
|
}
|
||||||
|
} else if (propertyName == "Paired") {
|
||||||
|
setPairedInternally(properties.value(propertyName).toBool());
|
||||||
|
} else if (propertyName == "Connected") {
|
||||||
|
setConnectedInternally(properties.value(propertyName).toBool());
|
||||||
|
} else if (propertyName == "Trusted") {
|
||||||
|
setTrustedInternally(properties.value(propertyName).toBool());
|
||||||
|
} else if (propertyName == "Blocked") {
|
||||||
|
setBlockedInternally(properties.value(propertyName).toBool());
|
||||||
|
} else if (propertyName == "LegacyPairing") {
|
||||||
|
m_legacyPairing = properties.value(propertyName).toBool();
|
||||||
|
} else if (propertyName == "ServicesResolved") {
|
||||||
|
setServicesResolvedInternally(properties.value(propertyName).toBool());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothDevice::evaluateCurrentState()
|
||||||
|
{
|
||||||
|
if (!connected()) {
|
||||||
|
setStateInternally(Disconnected);
|
||||||
|
} else if (connected() && servicesResolved()) {
|
||||||
|
setStateInternally(Discovered);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothDevice::addServiceInternally(const QDBusObjectPath &path, const QVariantMap &properties)
|
||||||
|
{
|
||||||
|
if (hasService(path))
|
||||||
|
return;
|
||||||
|
|
||||||
|
BluetoothGattService *service = new BluetoothGattService(path, properties, this);
|
||||||
|
m_services.append(service);
|
||||||
|
qCDebug(dcBluez()) << "[+]" << service;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BluetoothDevice::hasService(const QDBusObjectPath &path)
|
||||||
|
{
|
||||||
|
foreach (BluetoothGattService *service, m_services) {
|
||||||
|
if (service->m_path == path) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BluetoothGattService *BluetoothDevice::getService(const QDBusObjectPath &path)
|
||||||
|
{
|
||||||
|
foreach (BluetoothGattService *service, m_services) {
|
||||||
|
if (service->m_path == path) {
|
||||||
|
return service;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothDevice::setStateInternally(const BluetoothDevice::State &state)
|
||||||
|
{
|
||||||
|
if (m_state != state) {
|
||||||
|
m_state = state;
|
||||||
|
emit stateChanged(m_state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothDevice::setAliasInternally(const QString &alias)
|
||||||
|
{
|
||||||
|
if (m_alias != alias) {
|
||||||
|
m_alias = alias;
|
||||||
|
emit aliasChanged(m_alias);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothDevice::setRssiInternally(const qint16 &rssi)
|
||||||
|
{
|
||||||
|
if (m_rssi != rssi) {
|
||||||
|
m_rssi = rssi;
|
||||||
|
emit rssiChanged(m_rssi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothDevice::setTxPowerInternally(const qint16 &txPower)
|
||||||
|
{
|
||||||
|
if (m_txPower != txPower) {
|
||||||
|
m_txPower = txPower;
|
||||||
|
emit txPowerChanged(m_txPower);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothDevice::setPairedInternally(const bool &paired)
|
||||||
|
{
|
||||||
|
if (m_paired != paired) {
|
||||||
|
m_paired = paired;
|
||||||
|
emit pairedChanged(m_paired);
|
||||||
|
|
||||||
|
// Paired, if services not resolved
|
||||||
|
if (!m_servicesResolved) {
|
||||||
|
setStateInternally(Discovering);
|
||||||
|
} else {
|
||||||
|
setStateInternally(Discovered);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothDevice::setConnectedInternally(const bool &connected)
|
||||||
|
{
|
||||||
|
if (m_connected != connected) {
|
||||||
|
m_connected = connected;
|
||||||
|
emit connectedChanged(m_connected);
|
||||||
|
|
||||||
|
if (m_connected) {
|
||||||
|
setStateInternally(Connected);
|
||||||
|
|
||||||
|
if (!m_servicesResolved)
|
||||||
|
setStateInternally(Discovering);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
setStateInternally(Disconnected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothDevice::setTrustedInternally(const bool &trusted)
|
||||||
|
{
|
||||||
|
if (m_trusted != trusted) {
|
||||||
|
m_trusted = trusted;
|
||||||
|
emit trustedChanged(m_trusted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothDevice::setBlockedInternally(const bool &blocked)
|
||||||
|
{
|
||||||
|
if (m_blocked != blocked) {
|
||||||
|
m_blocked = blocked;
|
||||||
|
emit blockedChanged(m_blocked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothDevice::setServicesResolvedInternally(const bool &servicesResolved)
|
||||||
|
{
|
||||||
|
if (m_servicesResolved != servicesResolved) {
|
||||||
|
m_servicesResolved = servicesResolved;
|
||||||
|
emit servicesResolvedChanged(m_servicesResolved);
|
||||||
|
|
||||||
|
if (m_servicesResolved)
|
||||||
|
setStateInternally(Discovered);
|
||||||
|
|
||||||
|
// Note: if unresolved, the device is about to disconnect
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothDevice::onPropertiesChanged(const QString &interface, const QVariantMap &changedProperties, const QStringList &invalidatedProperties)
|
||||||
|
{
|
||||||
|
if (interface != orgBluezDevice1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
qCDebug(dcBluez()) << "BluetoothDevice:" << m_name << m_address << "properties changed" << interface << changedProperties << invalidatedProperties;
|
||||||
|
processProperties(changedProperties);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothDevice::onConnectDeviceFinished(QDBusPendingCallWatcher *call)
|
||||||
|
{
|
||||||
|
QDBusPendingReply<void> reply = *call;
|
||||||
|
if (reply.isError()) {
|
||||||
|
setStateInternally(Disconnected);
|
||||||
|
qCWarning(dcBluez()) << "Could not connect device" << m_address.toString() << reply.error().name() << reply.error().message();
|
||||||
|
}
|
||||||
|
|
||||||
|
call->deleteLater();
|
||||||
|
m_connectWatcher = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothDevice::onDisconnectDeviceFinished(QDBusPendingCallWatcher *call)
|
||||||
|
{
|
||||||
|
QDBusPendingReply<void> reply = *call;
|
||||||
|
if (reply.isError())
|
||||||
|
qCWarning(dcBluez()) << "Could not disconnect device" << m_address.toString() << reply.error().name() << reply.error().message();
|
||||||
|
|
||||||
|
evaluateCurrentState();
|
||||||
|
|
||||||
|
call->deleteLater();
|
||||||
|
m_disconnectWatcher = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothDevice::onPairingFinished(QDBusPendingCallWatcher *call)
|
||||||
|
{
|
||||||
|
QDBusPendingReply<void> reply = *call;
|
||||||
|
if (reply.isError())
|
||||||
|
qCWarning(dcBluez()) << "Could not pair device" << m_address.toString() << reply.error().name() << reply.error().message();
|
||||||
|
|
||||||
|
evaluateCurrentState();
|
||||||
|
|
||||||
|
call->deleteLater();
|
||||||
|
m_pairingWatcher = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothDevice::onCancelPairingFinished(QDBusPendingCallWatcher *call)
|
||||||
|
{
|
||||||
|
QDBusPendingReply<void> reply = *call;
|
||||||
|
if (reply.isError())
|
||||||
|
qCWarning(dcBluez()) << "Could not cancel pairing" << m_address.toString() << reply.error().name() << reply.error().message();
|
||||||
|
|
||||||
|
evaluateCurrentState();
|
||||||
|
|
||||||
|
call->deleteLater();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BluetoothDevice::connectDevice()
|
||||||
|
{
|
||||||
|
if (!m_deviceInterface->isValid()) {
|
||||||
|
qCWarning(dcBluez()) << "Invalid DBus device interface for" << m_path.path();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connected() || state() == Connecting || m_connectWatcher)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
setStateInternally(Connecting);
|
||||||
|
|
||||||
|
QDBusPendingCall connectingCall = m_deviceInterface->asyncCall("Connect");
|
||||||
|
m_connectWatcher = new QDBusPendingCallWatcher(connectingCall, this);
|
||||||
|
connect(m_connectWatcher, &QDBusPendingCallWatcher::finished, this, &BluetoothDevice::onConnectDeviceFinished);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BluetoothDevice::disconnectDevice()
|
||||||
|
{
|
||||||
|
if (!m_deviceInterface->isValid()) {
|
||||||
|
qCWarning(dcBluez()) << "Invalid DBus device interface for" << m_path.path();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!connected() || state() == Disconnecting || m_disconnectWatcher)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
setStateInternally(Disconnecting);
|
||||||
|
|
||||||
|
QDBusPendingCall disconnectingCall = m_deviceInterface->asyncCall("Disconnect");
|
||||||
|
m_disconnectWatcher = new QDBusPendingCallWatcher(disconnectingCall, this);
|
||||||
|
connect(m_disconnectWatcher, &QDBusPendingCallWatcher::finished, this, &BluetoothDevice::onDisconnectDeviceFinished);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BluetoothDevice::disconnectDeviceBlocking()
|
||||||
|
{
|
||||||
|
if (!m_deviceInterface->isValid()) {
|
||||||
|
qCWarning(dcBluez()) << "Invalid DBus device interface for" << m_path.path();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!connected() || state() == Disconnecting)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
qCWarning(dcBluez()) << "Disconnecting blocking" << this;
|
||||||
|
QDBusPendingReply<void> reply = m_deviceInterface->call("Disconnect");
|
||||||
|
reply.waitForFinished();
|
||||||
|
if(reply.isError()) {
|
||||||
|
qCWarning(dcBluez()) << reply.error();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BluetoothDevice::requestPairing()
|
||||||
|
{
|
||||||
|
if (!m_deviceInterface->isValid()) {
|
||||||
|
qCWarning(dcBluez()) << "Invalid DBus device interface for" << m_path.path();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (paired() || state() == Pairing || m_pairingWatcher)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
setStateInternally(Pairing);
|
||||||
|
|
||||||
|
QDBusPendingCall pairCall = m_deviceInterface->asyncCall("Pair");
|
||||||
|
m_pairingWatcher = new QDBusPendingCallWatcher(pairCall, this);
|
||||||
|
connect(m_pairingWatcher, &QDBusPendingCallWatcher::finished, this, &BluetoothDevice::onPairingFinished);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BluetoothDevice::cancelPairingRequest()
|
||||||
|
{
|
||||||
|
if (!m_deviceInterface->isValid()) {
|
||||||
|
qCWarning(dcBluez()) << "Invalid DBus device interface for" << m_path.path();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!paired() || state() == Unpairing)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
setStateInternally(Unpairing);
|
||||||
|
|
||||||
|
QDBusPendingCall cancelPairingCall = m_deviceInterface->asyncCall("CancelPairing");
|
||||||
|
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(cancelPairingCall, this);
|
||||||
|
connect(watcher, &QDBusPendingCallWatcher::finished, this, &BluetoothDevice::onPairingFinished);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDebug operator<<(QDebug debug, BluetoothDevice *device)
|
||||||
|
{
|
||||||
|
debug.noquote().nospace() << "BluetoothDevice(" << device->name() << ", " << device->address() << ") ";
|
||||||
|
return debug;
|
||||||
|
}
|
||||||
185
nuki/bluez/bluetoothdevice.h
Normal file
185
nuki/bluez/bluetoothdevice.h
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
* *
|
||||||
|
* Copyright (C) 2016-2018 Simon Stürz <simon.stuerz@guh.io> *
|
||||||
|
* *
|
||||||
|
* This file is part of guh. *
|
||||||
|
* *
|
||||||
|
* This library is free software; you can redistribute it and/or *
|
||||||
|
* modify it under the terms of the GNU Lesser General Public *
|
||||||
|
* License as published by the Free Software Foundation; either *
|
||||||
|
* version 2.1 of the License, or (at your option) any later version. *
|
||||||
|
* *
|
||||||
|
* This library is distributed in the hope that it will be useful, *
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
||||||
|
* Lesser General Public License for more details. *
|
||||||
|
* *
|
||||||
|
* You should have received a copy of the GNU Lesser General Public *
|
||||||
|
* License along with this library; If not, see *
|
||||||
|
* <http://www.gnu.org/licenses/>. *
|
||||||
|
* *
|
||||||
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
|
#ifndef BLUETOOTHDEVICE_H
|
||||||
|
#define BLUETOOTHDEVICE_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QDBusInterface>
|
||||||
|
#include <QDBusPendingCall>
|
||||||
|
#include <QBluetoothAddress>
|
||||||
|
#include <QBluetoothHostInfo>
|
||||||
|
#include <QDBusPendingCallWatcher>
|
||||||
|
|
||||||
|
#include "blueztypes.h"
|
||||||
|
#include "bluetoothgattservice.h"
|
||||||
|
|
||||||
|
// Note: DBus documentation https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/device-api.txt
|
||||||
|
|
||||||
|
class BluetoothManager;
|
||||||
|
class BluetoothAdapter;
|
||||||
|
|
||||||
|
class BluetoothDevice : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
friend class BluetoothManager;
|
||||||
|
friend class BluetoothAdapter;
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum State {
|
||||||
|
Connecting,
|
||||||
|
Connected,
|
||||||
|
Pairing,
|
||||||
|
Unpairing,
|
||||||
|
Discovering,
|
||||||
|
Discovered,
|
||||||
|
Disconnecting,
|
||||||
|
Disconnected
|
||||||
|
};
|
||||||
|
Q_ENUM(State)
|
||||||
|
|
||||||
|
State state() const;
|
||||||
|
|
||||||
|
QString name() const;
|
||||||
|
QBluetoothAddress address() const;
|
||||||
|
QString iconName() const;
|
||||||
|
|
||||||
|
QBluetoothHostInfo hostInfo() const;
|
||||||
|
|
||||||
|
QString alias() const;
|
||||||
|
bool setAlias(const QString &alias);
|
||||||
|
|
||||||
|
QString modalias() const;
|
||||||
|
quint32 deviceClass() const;
|
||||||
|
quint16 appearance() const;
|
||||||
|
qint16 rssi() const;
|
||||||
|
qint16 txPower() const;
|
||||||
|
QList<QBluetoothUuid> uuids() const;
|
||||||
|
bool paired() const;
|
||||||
|
bool connected() const;
|
||||||
|
|
||||||
|
bool trusted() const;
|
||||||
|
bool setTrusted(const bool &trusted);
|
||||||
|
|
||||||
|
bool blocked() const;
|
||||||
|
bool setBlocked(const bool &blocked);
|
||||||
|
|
||||||
|
bool legacyPairing() const;
|
||||||
|
bool servicesResolved() const;
|
||||||
|
|
||||||
|
// Service methods
|
||||||
|
QList<BluetoothGattService *> services() const;
|
||||||
|
bool hasService(const QBluetoothUuid &serviceUuid);
|
||||||
|
BluetoothGattService *getService(const QBluetoothUuid &serviceUuid);
|
||||||
|
|
||||||
|
private:
|
||||||
|
explicit BluetoothDevice(const QDBusObjectPath &path, const QVariantMap &properties, QObject *parent = 0);
|
||||||
|
~BluetoothDevice();
|
||||||
|
|
||||||
|
QDBusObjectPath m_path;
|
||||||
|
QDBusInterface *m_deviceInterface;
|
||||||
|
|
||||||
|
QList<BluetoothGattService *> m_services;
|
||||||
|
|
||||||
|
State m_state;
|
||||||
|
|
||||||
|
QString m_name;
|
||||||
|
QBluetoothAddress m_address;
|
||||||
|
QBluetoothHostInfo m_hostInfo;
|
||||||
|
QString m_iconName;
|
||||||
|
QString m_alias;
|
||||||
|
QString m_modalias;
|
||||||
|
quint32 m_deviceClass;
|
||||||
|
quint16 m_appearance;
|
||||||
|
qint16 m_rssi;
|
||||||
|
qint16 m_txPower;
|
||||||
|
|
||||||
|
QList<QBluetoothUuid> m_uuids;
|
||||||
|
bool m_paired;
|
||||||
|
bool m_connected;
|
||||||
|
bool m_trusted;
|
||||||
|
bool m_blocked;
|
||||||
|
bool m_legacyPairing;
|
||||||
|
bool m_servicesResolved;
|
||||||
|
|
||||||
|
QDBusObjectPath m_adapterObjectPath;
|
||||||
|
|
||||||
|
QDBusPendingCallWatcher *m_connectWatcher;
|
||||||
|
QDBusPendingCallWatcher *m_disconnectWatcher;
|
||||||
|
QDBusPendingCallWatcher *m_pairingWatcher;
|
||||||
|
|
||||||
|
// TODO: org.bluez.Device1.ManufacturerData
|
||||||
|
// TODO: org.bluez.Device1.ServiceData
|
||||||
|
|
||||||
|
void processProperties(const QVariantMap &properties);
|
||||||
|
|
||||||
|
void evaluateCurrentState();
|
||||||
|
|
||||||
|
// Methods called from BluetoothManager
|
||||||
|
void addServiceInternally(const QDBusObjectPath &path, const QVariantMap &properties);
|
||||||
|
bool hasService(const QDBusObjectPath &path);
|
||||||
|
BluetoothGattService *getService(const QDBusObjectPath &path);
|
||||||
|
|
||||||
|
void setStateInternally(const State &state);
|
||||||
|
|
||||||
|
void setAliasInternally(const QString &alias);
|
||||||
|
void setRssiInternally(const qint16 &rssi);
|
||||||
|
void setTxPowerInternally(const qint16 &txPower);
|
||||||
|
void setPairedInternally(const bool &paired);
|
||||||
|
void setConnectedInternally(const bool &connected);
|
||||||
|
void setTrustedInternally(const bool &trusted);
|
||||||
|
void setBlockedInternally(const bool &blocked);
|
||||||
|
void setServicesResolvedInternally(const bool &servicesResolved);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void stateChanged(const State &state);
|
||||||
|
|
||||||
|
void aliasChanged(const QString &alias);
|
||||||
|
void rssiChanged(const qint16 &rssi);
|
||||||
|
void txPowerChanged(const qint16 &txPower);
|
||||||
|
void pairedChanged(const bool &paired);
|
||||||
|
void connectedChanged(const bool &connected);
|
||||||
|
void trustedChanged(const bool &trusted);
|
||||||
|
void blockedChanged(const bool &blocked);
|
||||||
|
void servicesResolvedChanged(const bool &servicesResolved);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onPropertiesChanged(const QString &interface, const QVariantMap &changedProperties, const QStringList &invalidatedProperties);
|
||||||
|
|
||||||
|
void onConnectDeviceFinished(QDBusPendingCallWatcher *call);
|
||||||
|
void onDisconnectDeviceFinished(QDBusPendingCallWatcher *call);
|
||||||
|
void onPairingFinished(QDBusPendingCallWatcher *call);
|
||||||
|
void onCancelPairingFinished(QDBusPendingCallWatcher *call);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
bool connectDevice();
|
||||||
|
bool disconnectDevice();
|
||||||
|
bool disconnectDeviceBlocking();
|
||||||
|
bool requestPairing();
|
||||||
|
bool cancelPairingRequest();
|
||||||
|
};
|
||||||
|
|
||||||
|
QDebug operator<<(QDebug debug, BluetoothDevice *device);
|
||||||
|
|
||||||
|
|
||||||
|
#endif // BLUETOOTHDEVICE_H
|
||||||
368
nuki/bluez/bluetoothgattcharacteristic.cpp
Normal file
368
nuki/bluez/bluetoothgattcharacteristic.cpp
Normal file
@ -0,0 +1,368 @@
|
|||||||
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
* *
|
||||||
|
* Copyright (C) 2016-2018 Simon Stürz <simon.stuerz@guh.io> *
|
||||||
|
* *
|
||||||
|
* This file is part of guh. *
|
||||||
|
* *
|
||||||
|
* This library is free software; you can redistribute it and/or *
|
||||||
|
* modify it under the terms of the GNU Lesser General Public *
|
||||||
|
* License as published by the Free Software Foundation; either *
|
||||||
|
* version 2.1 of the License, or (at your option) any later version. *
|
||||||
|
* *
|
||||||
|
* This library is distributed in the hope that it will be useful, *
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
||||||
|
* Lesser General Public License for more details. *
|
||||||
|
* *
|
||||||
|
* You should have received a copy of the GNU Lesser General Public *
|
||||||
|
* License along with this library; If not, see *
|
||||||
|
* <http://www.gnu.org/licenses/>. *
|
||||||
|
* *
|
||||||
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
|
#include "bluetoothgattcharacteristic.h"
|
||||||
|
|
||||||
|
#include <QDBusReply>
|
||||||
|
|
||||||
|
QString BluetoothGattCharacteristic::chararcteristicName() const
|
||||||
|
{
|
||||||
|
bool ok = false;
|
||||||
|
quint16 typeId = m_uuid.toUInt16(&ok);
|
||||||
|
|
||||||
|
if (ok) {
|
||||||
|
QBluetoothUuid::CharacteristicType uuid = static_cast<QBluetoothUuid::CharacteristicType>(typeId);
|
||||||
|
const QString name = QBluetoothUuid::characteristicToString(uuid);
|
||||||
|
if (!name.isEmpty())
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return QString("Unknown Characteristic");
|
||||||
|
}
|
||||||
|
|
||||||
|
QBluetoothUuid BluetoothGattCharacteristic::uuid() const
|
||||||
|
{
|
||||||
|
return m_uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BluetoothGattCharacteristic::notifying() const
|
||||||
|
{
|
||||||
|
return m_notifying;
|
||||||
|
}
|
||||||
|
|
||||||
|
BluetoothGattCharacteristic::Properties BluetoothGattCharacteristic::properties() const
|
||||||
|
{
|
||||||
|
return m_properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray BluetoothGattCharacteristic::value() const
|
||||||
|
{
|
||||||
|
return m_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<BluetoothGattDescriptor *> BluetoothGattCharacteristic::descriptors() const
|
||||||
|
{
|
||||||
|
return m_descriptors;
|
||||||
|
}
|
||||||
|
|
||||||
|
BluetoothGattCharacteristic::BluetoothGattCharacteristic(const QDBusObjectPath &path, const QVariantMap &properties, QObject *parent) :
|
||||||
|
QObject(parent),
|
||||||
|
m_path(path),
|
||||||
|
m_notifying(false)
|
||||||
|
{
|
||||||
|
m_characteristicInterface = new QDBusInterface(orgBluez, m_path.path(), orgBluezGattCharacteristic1, QDBusConnection::systemBus(), this);
|
||||||
|
if (!m_characteristicInterface->isValid()) {
|
||||||
|
qCWarning(dcBluez()) << "Invalid DBus characteristic interface for" << m_path.path();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDBusConnection::systemBus().connect(orgBluez, m_path.path(), "org.freedesktop.DBus.Properties", "PropertiesChanged", this, SLOT(onPropertiesChanged(QString,QVariantMap,QStringList)));
|
||||||
|
|
||||||
|
processProperties(properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothGattCharacteristic::processProperties(const QVariantMap &properties)
|
||||||
|
{
|
||||||
|
foreach (const QString &propertyName, properties.keys()) {
|
||||||
|
if (propertyName == "UUID") {
|
||||||
|
m_uuid = QBluetoothUuid(properties.value(propertyName).toString());
|
||||||
|
} else if (propertyName == "Notifying") {
|
||||||
|
m_notifying = properties.value(propertyName).toBool();
|
||||||
|
emit notifyingChanged(m_notifying);
|
||||||
|
} else if (propertyName == "Flags") {
|
||||||
|
m_properties = parsePropertyFlags(properties.value(propertyName).toStringList());
|
||||||
|
} else if (propertyName == "Value") {
|
||||||
|
m_value = properties.value(propertyName).toByteArray();
|
||||||
|
emit valueChanged(m_value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothGattCharacteristic::addDescriptorInternally(const QDBusObjectPath &path, const QVariantMap &properties)
|
||||||
|
{
|
||||||
|
if (hasDescriptor(path))
|
||||||
|
return;
|
||||||
|
|
||||||
|
BluetoothGattDescriptor *descriptor = new BluetoothGattDescriptor(path, properties, this);
|
||||||
|
m_descriptors.append(descriptor);
|
||||||
|
|
||||||
|
qCDebug(dcBluez()) << "[+]" << descriptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BluetoothGattCharacteristic::hasDescriptor(const QDBusObjectPath &path)
|
||||||
|
{
|
||||||
|
foreach (BluetoothGattDescriptor *descriptor, m_descriptors) {
|
||||||
|
if (descriptor->m_path == path) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BluetoothGattDescriptor *BluetoothGattCharacteristic::getDescriptor(const QDBusObjectPath &path)
|
||||||
|
{
|
||||||
|
foreach (BluetoothGattDescriptor *descriptor, m_descriptors) {
|
||||||
|
if (descriptor->m_path == path) {
|
||||||
|
return descriptor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothGattCharacteristic::setValueInternally(const QByteArray &value)
|
||||||
|
{
|
||||||
|
if (m_value != value) {
|
||||||
|
m_value = value;
|
||||||
|
emit valueChanged(m_value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothGattCharacteristic::setNotifyingInternally(const bool ¬ifying)
|
||||||
|
{
|
||||||
|
if (m_notifying != notifying) {
|
||||||
|
m_notifying = notifying;
|
||||||
|
emit notifyingChanged(m_notifying);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BluetoothGattCharacteristic::Properties BluetoothGattCharacteristic::parsePropertyFlags(const QStringList &characteristicProperties)
|
||||||
|
{
|
||||||
|
Properties properties;
|
||||||
|
|
||||||
|
foreach (const QString &propertyString, characteristicProperties) {
|
||||||
|
if (propertyString == "broadcast") {
|
||||||
|
properties |= Broadcasting;
|
||||||
|
} else if (propertyString == "read") {
|
||||||
|
properties |= Read;
|
||||||
|
} else if (propertyString == "write-without-response") {
|
||||||
|
properties |= WriteNoResponse;
|
||||||
|
} else if (propertyString == "write") {
|
||||||
|
properties |= Write;
|
||||||
|
} else if (propertyString == "notify") {
|
||||||
|
properties |= Notify;
|
||||||
|
} else if (propertyString == "indicate") {
|
||||||
|
properties |= Indicate;
|
||||||
|
} else if (propertyString == "authenticated-signed-writes") {
|
||||||
|
properties |= WriteAuthenticatedSigned;
|
||||||
|
} else if (propertyString == "reliable-write") {
|
||||||
|
properties |= ReliableWrite;
|
||||||
|
} else if (propertyString == "writable-auxiliaries") {
|
||||||
|
properties |= WritableAuxiliaries;
|
||||||
|
} else if (propertyString == "encrypt-read") {
|
||||||
|
properties |= EncryptRead;
|
||||||
|
} else if (propertyString == "encrypt-write") {
|
||||||
|
properties |= EncryptWrite;
|
||||||
|
} else if (propertyString == "encrypt-authenticated-read") {
|
||||||
|
properties |= EncryptAuthenticatedRead;
|
||||||
|
} else if (propertyString == "encrypt-authenticated-write") {
|
||||||
|
properties |= EncryptAuthenticatedWrite;
|
||||||
|
} else if (propertyString == "secure-read") {
|
||||||
|
properties |= SecureRead;
|
||||||
|
} else if (propertyString == "secure-write") {
|
||||||
|
properties |= Unknown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothGattCharacteristic::onPropertiesChanged(const QString &interface, const QVariantMap &changedProperties, const QStringList &invalidatedProperties)
|
||||||
|
{
|
||||||
|
if (interface != orgBluezGattCharacteristic1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
qCDebug(dcBluez()) << "BluetoothCharacteristic:" << m_uuid.toString() << "properties changed" << interface << changedProperties << invalidatedProperties;
|
||||||
|
processProperties(changedProperties);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothGattCharacteristic::onReadingFinished(QDBusPendingCallWatcher *call)
|
||||||
|
{
|
||||||
|
QDBusPendingReply<QByteArray> reply = *call;
|
||||||
|
if (reply.isError()) {
|
||||||
|
qCWarning(dcBluez()) << "Could not read characteristic" << m_uuid.toString() << reply.error().name() << reply.error().message();
|
||||||
|
} else {
|
||||||
|
QByteArray value = reply.argumentAt<0>();
|
||||||
|
qCDebug(dcBluez()) << "Async reading finished for" << m_uuid.toString() << value;
|
||||||
|
setValueInternally(value);
|
||||||
|
emit readingFinished(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
call->deleteLater();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothGattCharacteristic::onWritingFinished(QDBusPendingCallWatcher *call)
|
||||||
|
{
|
||||||
|
QDBusPendingReply<void> reply = *call;
|
||||||
|
if (reply.isError()) {
|
||||||
|
qCWarning(dcBluez()) << "Could not write characteristic" << m_uuid.toString() << reply.error().name() << reply.error().message();
|
||||||
|
} else {
|
||||||
|
QByteArray value = m_asyncWrites.take(call);
|
||||||
|
qCDebug(dcBluez()) << "Async characteristic writing finished for" << m_uuid.toString() << value;
|
||||||
|
emit writingFinished(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
call->deleteLater();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothGattCharacteristic::onStartNotificationFinished(QDBusPendingCallWatcher *call)
|
||||||
|
{
|
||||||
|
QDBusPendingReply<void> reply = *call;
|
||||||
|
if (reply.isError())
|
||||||
|
qCWarning(dcBluez()) << "Could not start notifications on characteristic" << m_uuid.toString() << reply.error().name() << reply.error().message();
|
||||||
|
|
||||||
|
call->deleteLater();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothGattCharacteristic::onStopNotificationFinished(QDBusPendingCallWatcher *call)
|
||||||
|
{
|
||||||
|
QDBusPendingReply<void> reply = *call;
|
||||||
|
if (reply.isError())
|
||||||
|
qCWarning(dcBluez()) << "Could not stop notifications on characteristic" << m_uuid.toString() << reply.error().name() << reply.error().message();
|
||||||
|
|
||||||
|
call->deleteLater();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BluetoothGattCharacteristic::readCharacteristic()
|
||||||
|
{
|
||||||
|
if (!m_characteristicInterface->isValid()) {
|
||||||
|
qCWarning(dcBluez()) << "Invalid DBus characteristic interface for" << m_path.path();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDBusPendingCall readingCall = m_characteristicInterface->asyncCall("ReadValue", QVariantMap());
|
||||||
|
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(readingCall, this);
|
||||||
|
connect(watcher, &QDBusPendingCallWatcher::finished, this, &BluetoothGattCharacteristic::onReadingFinished);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BluetoothGattCharacteristic::writeCharacteristic(const QByteArray &value)
|
||||||
|
{
|
||||||
|
if (!m_characteristicInterface->isValid()) {
|
||||||
|
qCWarning(dcBluez()) << "Invalid DBus characteristic interface for" << m_path.path();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDBusPendingCall writingCall = m_characteristicInterface->asyncCall("WriteValue", value, QVariantMap());
|
||||||
|
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(writingCall, this);
|
||||||
|
m_asyncWrites.insert(watcher, value);
|
||||||
|
connect(watcher, &QDBusPendingCallWatcher::finished, this, &BluetoothGattCharacteristic::onWritingFinished);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BluetoothGattCharacteristic::startNotifications()
|
||||||
|
{
|
||||||
|
if (!m_characteristicInterface->isValid()) {
|
||||||
|
qCWarning(dcBluez()) << "Invalid DBus characteristic interface for" << m_path.path();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If already notifying
|
||||||
|
if (notifying())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
QDBusPendingCall startNotifyCall = m_characteristicInterface->asyncCall("StartNotify");
|
||||||
|
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(startNotifyCall, this);
|
||||||
|
connect(watcher, &QDBusPendingCallWatcher::finished, this, &BluetoothGattCharacteristic::onStartNotificationFinished);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BluetoothGattCharacteristic::stopNotifications()
|
||||||
|
{
|
||||||
|
if (!m_characteristicInterface->isValid()) {
|
||||||
|
qCWarning(dcBluez()) << "Invalid DBus characteristic interface for" << m_path.path();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If already stopped
|
||||||
|
if (!notifying())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
QDBusPendingCall stopNotifyCall = m_characteristicInterface->asyncCall("StopNotify");
|
||||||
|
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(stopNotifyCall, this);
|
||||||
|
connect(watcher, &QDBusPendingCallWatcher::finished, this, &BluetoothGattCharacteristic::onStopNotificationFinished);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDebug operator<<(QDebug debug, BluetoothGattCharacteristic *characteristic)
|
||||||
|
{
|
||||||
|
debug.noquote().nospace() << "GattCharacteristic(" << characteristic->chararcteristicName();
|
||||||
|
debug.noquote().nospace() << ", " << characteristic->uuid().toString();
|
||||||
|
debug.noquote().nospace() << ", Properties: " << characteristic->properties();
|
||||||
|
|
||||||
|
if (characteristic->properties().testFlag(BluetoothGattCharacteristic::Unknown))
|
||||||
|
debug.noquote().nospace() << " Unknown";
|
||||||
|
|
||||||
|
if (characteristic->properties().testFlag(BluetoothGattCharacteristic::Broadcasting))
|
||||||
|
debug.noquote().nospace() << " B";
|
||||||
|
|
||||||
|
if (characteristic->properties().testFlag(BluetoothGattCharacteristic::Read))
|
||||||
|
debug.noquote().nospace() << " R ";
|
||||||
|
|
||||||
|
if (characteristic->properties().testFlag(BluetoothGattCharacteristic::WriteNoResponse))
|
||||||
|
debug.noquote().nospace() << " WNR";
|
||||||
|
|
||||||
|
if (characteristic->properties().testFlag(BluetoothGattCharacteristic::Write))
|
||||||
|
debug.noquote().nospace() << " W";
|
||||||
|
|
||||||
|
if (characteristic->properties().testFlag(BluetoothGattCharacteristic::Notify))
|
||||||
|
debug.noquote().nospace() << " N";
|
||||||
|
|
||||||
|
if (characteristic->properties().testFlag(BluetoothGattCharacteristic::Indicate))
|
||||||
|
debug.noquote().nospace() << " I";
|
||||||
|
|
||||||
|
if (characteristic->properties().testFlag(BluetoothGattCharacteristic::WriteAuthenticatedSigned))
|
||||||
|
debug.noquote().nospace() << " WAS";
|
||||||
|
|
||||||
|
if (characteristic->properties().testFlag(BluetoothGattCharacteristic::ReliableWrite))
|
||||||
|
debug.noquote().nospace() << " RW";
|
||||||
|
|
||||||
|
if (characteristic->properties().testFlag(BluetoothGattCharacteristic::WritableAuxiliaries))
|
||||||
|
debug.noquote().nospace() << " WA";
|
||||||
|
|
||||||
|
if (characteristic->properties().testFlag(BluetoothGattCharacteristic::EncryptWrite))
|
||||||
|
debug.noquote().nospace() << " EW";
|
||||||
|
|
||||||
|
if (characteristic->properties().testFlag(BluetoothGattCharacteristic::EncryptRead))
|
||||||
|
debug.noquote().nospace() << " ER";
|
||||||
|
|
||||||
|
if (characteristic->properties().testFlag(BluetoothGattCharacteristic::EncryptAuthenticatedRead))
|
||||||
|
debug.noquote().nospace() << " EAR";
|
||||||
|
|
||||||
|
if (characteristic->properties().testFlag(BluetoothGattCharacteristic::EncryptAuthenticatedWrite))
|
||||||
|
debug.noquote().nospace() << " EAW";
|
||||||
|
|
||||||
|
if (characteristic->properties().testFlag(BluetoothGattCharacteristic::SecureRead))
|
||||||
|
debug.noquote().nospace() << " SR";
|
||||||
|
|
||||||
|
if (characteristic->properties().testFlag(BluetoothGattCharacteristic::SecureWrite))
|
||||||
|
debug.noquote().nospace() << " SW";
|
||||||
|
|
||||||
|
// If this is a notifying characteristic, inform if notifications are enabled
|
||||||
|
if (characteristic->properties().testFlag(BluetoothGattCharacteristic::Notify))
|
||||||
|
debug.noquote().nospace() << ", Notify: " << (characteristic->notifying() ? "ON" : "OFF");
|
||||||
|
|
||||||
|
debug.noquote().nospace() << ", value:" << characteristic->value();
|
||||||
|
debug.noquote().nospace() << ") ";
|
||||||
|
return debug;
|
||||||
|
}
|
||||||
132
nuki/bluez/bluetoothgattcharacteristic.h
Normal file
132
nuki/bluez/bluetoothgattcharacteristic.h
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
* *
|
||||||
|
* Copyright (C) 2016-2018 Simon Stürz <simon.stuerz@guh.io> *
|
||||||
|
* *
|
||||||
|
* This file is part of guh. *
|
||||||
|
* *
|
||||||
|
* This library is free software; you can redistribute it and/or *
|
||||||
|
* modify it under the terms of the GNU Lesser General Public *
|
||||||
|
* License as published by the Free Software Foundation; either *
|
||||||
|
* version 2.1 of the License, or (at your option) any later version. *
|
||||||
|
* *
|
||||||
|
* This library is distributed in the hope that it will be useful, *
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
||||||
|
* Lesser General Public License for more details. *
|
||||||
|
* *
|
||||||
|
* You should have received a copy of the GNU Lesser General Public *
|
||||||
|
* License along with this library; If not, see *
|
||||||
|
* <http://www.gnu.org/licenses/>. *
|
||||||
|
* *
|
||||||
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
|
#ifndef BLUETOOTHGATTCHARACTERISTIC_H
|
||||||
|
#define BLUETOOTHGATTCHARACTERISTIC_H
|
||||||
|
|
||||||
|
#include <QHash>
|
||||||
|
#include <QFlag>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QBluetoothUuid>
|
||||||
|
#include <QDBusInterface>
|
||||||
|
#include <QDBusPendingCall>
|
||||||
|
#include <QDBusPendingCallWatcher>
|
||||||
|
|
||||||
|
#include "blueztypes.h"
|
||||||
|
#include "bluetoothgattdescriptor.h"
|
||||||
|
|
||||||
|
// Note: DBus documentation https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/gatt-api.txt
|
||||||
|
|
||||||
|
class BluetoothManager;
|
||||||
|
class BluetoothGattService;
|
||||||
|
|
||||||
|
class BluetoothGattCharacteristic : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_FLAGS(Properties)
|
||||||
|
|
||||||
|
friend class BluetoothManager;
|
||||||
|
friend class BluetoothGattService;
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum Property {
|
||||||
|
Unknown = 0x00,
|
||||||
|
Broadcasting = 0x01,
|
||||||
|
Read = 0x02,
|
||||||
|
WriteNoResponse = 0x04,
|
||||||
|
Write = 0x08,
|
||||||
|
Notify = 0x10,
|
||||||
|
Indicate = 0x20,
|
||||||
|
WriteAuthenticatedSigned = 0x40,
|
||||||
|
ReliableWrite = 0x80,
|
||||||
|
WritableAuxiliaries = 0x100,
|
||||||
|
EncryptRead = 0x200,
|
||||||
|
EncryptWrite = 0x400,
|
||||||
|
EncryptAuthenticatedRead = 0x800,
|
||||||
|
EncryptAuthenticatedWrite = 0x1000,
|
||||||
|
SecureRead = 0x2000, // Server only
|
||||||
|
SecureWrite = 0x4000, // Server only
|
||||||
|
};
|
||||||
|
Q_DECLARE_FLAGS(Properties, Property)
|
||||||
|
|
||||||
|
QString chararcteristicName() const;
|
||||||
|
QBluetoothUuid uuid() const;
|
||||||
|
bool notifying() const;
|
||||||
|
Properties properties() const;
|
||||||
|
QByteArray value() const;
|
||||||
|
QList<BluetoothGattDescriptor *> descriptors() const;
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
explicit BluetoothGattCharacteristic(const QDBusObjectPath &path, const QVariantMap &properties, QObject *parent = 0);
|
||||||
|
|
||||||
|
QDBusObjectPath m_path;
|
||||||
|
QDBusInterface *m_characteristicInterface;
|
||||||
|
|
||||||
|
QString m_characteristicName;
|
||||||
|
QBluetoothUuid m_uuid;
|
||||||
|
bool m_notifying;
|
||||||
|
Properties m_properties;
|
||||||
|
QByteArray m_value;
|
||||||
|
QList<BluetoothGattDescriptor *> m_descriptors;
|
||||||
|
|
||||||
|
QHash<QDBusPendingCallWatcher *, QByteArray> m_asyncWrites;
|
||||||
|
|
||||||
|
void processProperties(const QVariantMap &properties);
|
||||||
|
|
||||||
|
// Methods called from BluetoothManager
|
||||||
|
void addDescriptorInternally(const QDBusObjectPath &path, const QVariantMap &properties);
|
||||||
|
bool hasDescriptor(const QDBusObjectPath &path);
|
||||||
|
BluetoothGattDescriptor *getDescriptor(const QDBusObjectPath &path);
|
||||||
|
|
||||||
|
void setValueInternally(const QByteArray &value);
|
||||||
|
void setNotifyingInternally(const bool ¬ifying);
|
||||||
|
|
||||||
|
Properties parsePropertyFlags(const QStringList &characteristicProperties);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void notifyingChanged(const bool ¬ifying);
|
||||||
|
void valueChanged(const QByteArray &value);
|
||||||
|
void readingFinished(const QByteArray &value);
|
||||||
|
void writingFinished(const QByteArray &value);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onPropertiesChanged(const QString &interface, const QVariantMap &changedProperties, const QStringList &invalidatedProperties);
|
||||||
|
|
||||||
|
void onReadingFinished(QDBusPendingCallWatcher *call);
|
||||||
|
void onWritingFinished(QDBusPendingCallWatcher *call);
|
||||||
|
void onStartNotificationFinished(QDBusPendingCallWatcher *call);
|
||||||
|
void onStopNotificationFinished(QDBusPendingCallWatcher *call);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
bool readCharacteristic();
|
||||||
|
bool writeCharacteristic(const QByteArray &value);
|
||||||
|
bool startNotifications();
|
||||||
|
bool stopNotifications();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
Q_DECLARE_OPERATORS_FOR_FLAGS(BluetoothGattCharacteristic::Properties)
|
||||||
|
|
||||||
|
QDebug operator<<(QDebug debug, BluetoothGattCharacteristic *characteristic);
|
||||||
|
|
||||||
|
#endif // BLUETOOTHGATTCHARACTERISTIC_H
|
||||||
219
nuki/bluez/bluetoothgattdescriptor.cpp
Normal file
219
nuki/bluez/bluetoothgattdescriptor.cpp
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
* *
|
||||||
|
* Copyright (C) 2016-2018 Simon Stürz <simon.stuerz@guh.io> *
|
||||||
|
* *
|
||||||
|
* This file is part of guh. *
|
||||||
|
* *
|
||||||
|
* This library is free software; you can redistribute it and/or *
|
||||||
|
* modify it under the terms of the GNU Lesser General Public *
|
||||||
|
* License as published by the Free Software Foundation; either *
|
||||||
|
* version 2.1 of the License, or (at your option) any later version. *
|
||||||
|
* *
|
||||||
|
* This library is distributed in the hope that it will be useful, *
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
||||||
|
* Lesser General Public License for more details. *
|
||||||
|
* *
|
||||||
|
* You should have received a copy of the GNU Lesser General Public *
|
||||||
|
* License along with this library; If not, see *
|
||||||
|
* <http://www.gnu.org/licenses/>. *
|
||||||
|
* *
|
||||||
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
|
#include "bluetoothgattdescriptor.h"
|
||||||
|
|
||||||
|
#include <QDBusReply>
|
||||||
|
|
||||||
|
QString BluetoothGattDescriptor::name() const
|
||||||
|
{
|
||||||
|
bool ok = false;
|
||||||
|
quint16 typeId = m_uuid.toUInt16(&ok);
|
||||||
|
|
||||||
|
if (ok) {
|
||||||
|
QBluetoothUuid::DescriptorType type = static_cast<QBluetoothUuid::DescriptorType>(typeId);
|
||||||
|
const QString name = QBluetoothUuid::descriptorToString(type);
|
||||||
|
if (!name.isEmpty())
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return QString("Unknown Descriptor");
|
||||||
|
}
|
||||||
|
|
||||||
|
QBluetoothUuid BluetoothGattDescriptor::uuid() const
|
||||||
|
{
|
||||||
|
return m_uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray BluetoothGattDescriptor::value() const
|
||||||
|
{
|
||||||
|
return m_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
BluetoothGattDescriptor::Properties BluetoothGattDescriptor::properties() const
|
||||||
|
{
|
||||||
|
return m_properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
BluetoothGattDescriptor::BluetoothGattDescriptor(const QDBusObjectPath &path, const QVariantMap &properties, QObject *parent) :
|
||||||
|
QObject(parent),
|
||||||
|
m_path(path)
|
||||||
|
{
|
||||||
|
m_descriptorInterface = new QDBusInterface(orgBluez, m_path.path(), orgBluezGattDescriptor1, QDBusConnection::systemBus(), this);
|
||||||
|
if (!m_descriptorInterface->isValid()) {
|
||||||
|
qCWarning(dcBluez()) << "Invalid DBus descriptor interface for" << m_path.path();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDBusConnection::systemBus().connect(orgBluez, m_path.path(), "org.freedesktop.DBus.Properties", "PropertiesChanged", this, SLOT(onPropertiesChanged(QString,QVariantMap,QStringList)));
|
||||||
|
|
||||||
|
processProperties(properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothGattDescriptor::processProperties(const QVariantMap &properties)
|
||||||
|
{
|
||||||
|
foreach (const QString &propertyName, properties.keys()) {
|
||||||
|
if (propertyName == "UUID") {
|
||||||
|
m_uuid = QBluetoothUuid(properties.value(propertyName).toString());
|
||||||
|
} else if (propertyName == "Value") {
|
||||||
|
setValueInternally(properties.value(propertyName).toByteArray());
|
||||||
|
} else if (propertyName == "Flags") {
|
||||||
|
m_properties = parsePropertyFlags(properties.value(propertyName).toStringList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothGattDescriptor::setValueInternally(const QByteArray &value)
|
||||||
|
{
|
||||||
|
if (m_value != value) {
|
||||||
|
m_value = value;
|
||||||
|
emit valueChanged(m_value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BluetoothGattDescriptor::Properties BluetoothGattDescriptor::parsePropertyFlags(const QStringList &descriptorProperties)
|
||||||
|
{
|
||||||
|
Properties properties;
|
||||||
|
|
||||||
|
foreach (const QString &propertyString, descriptorProperties) {
|
||||||
|
if (propertyString == "read") {
|
||||||
|
properties |= Read;
|
||||||
|
} else if (propertyString == "write") {
|
||||||
|
properties |= Write;
|
||||||
|
} else if (propertyString == "encrypt-read") {
|
||||||
|
properties |= EncryptRead;
|
||||||
|
} else if (propertyString == "encrypt-write") {
|
||||||
|
properties |= EncryptWrite;
|
||||||
|
} else if (propertyString == "encrypt-authenticated-read") {
|
||||||
|
properties |= EncryptAuthenticatedRead;
|
||||||
|
} else if (propertyString == "encrypt-authenticated-write") {
|
||||||
|
properties |= EncryptAuthenticatedWrite;
|
||||||
|
} else if (propertyString == "secure-read") {
|
||||||
|
properties |= SecureRead;
|
||||||
|
} else if (propertyString == "secure-write") {
|
||||||
|
properties |= SecureWrite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothGattDescriptor::onPropertiesChanged(const QString &interface, const QVariantMap &changedProperties, const QStringList &invalidatedProperties)
|
||||||
|
{
|
||||||
|
if (interface != orgBluezGattDescriptor1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
qCDebug(dcBluez()) << "BluetoothDescriptor:" << m_uuid << "properties changed" << interface << changedProperties << invalidatedProperties;
|
||||||
|
processProperties(changedProperties);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothGattDescriptor::onReadingFinished(QDBusPendingCallWatcher *call)
|
||||||
|
{
|
||||||
|
QDBusPendingReply<QByteArray> reply = *call;
|
||||||
|
if (reply.isError()) {
|
||||||
|
qCWarning(dcBluez()) << "Could not read descriptor" << m_uuid.toString() << reply.error().name() << reply.error().message();
|
||||||
|
} else {
|
||||||
|
QByteArray value = reply.argumentAt<0>();
|
||||||
|
qCDebug(dcBluez()) << "Async descriptor reading finished for" << m_uuid.toString() << value;
|
||||||
|
setValueInternally(value);
|
||||||
|
emit readingFinished(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
call->deleteLater();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothGattDescriptor::onWritingFinished(QDBusPendingCallWatcher *call)
|
||||||
|
{
|
||||||
|
QDBusPendingReply<void> reply = *call;
|
||||||
|
if (reply.isError()) {
|
||||||
|
qCWarning(dcBluez()) << "Could not write descriptor" << m_uuid.toString() << reply.error().name() << reply.error().message();
|
||||||
|
} else {
|
||||||
|
QByteArray value = m_asyncWrites.take(call);
|
||||||
|
qCDebug(dcBluez()) << "Async descriptor writing finished for" << m_uuid.toString() << value;
|
||||||
|
emit writingFinished(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
call->deleteLater();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BluetoothGattDescriptor::readValue()
|
||||||
|
{
|
||||||
|
if (!m_descriptorInterface->isValid()) {
|
||||||
|
qCWarning(dcBluez()) << "Invalid DBus characteristic interface for" << m_path.path();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDBusPendingCall readingCall = m_descriptorInterface->asyncCall("ReadValue", QVariantMap());
|
||||||
|
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(readingCall, this);
|
||||||
|
connect(watcher, &QDBusPendingCallWatcher::finished, this, &BluetoothGattDescriptor::onReadingFinished);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BluetoothGattDescriptor::writeValue(const QByteArray &value)
|
||||||
|
{
|
||||||
|
if (!m_descriptorInterface->isValid()) {
|
||||||
|
qCWarning(dcBluez()) << "Invalid DBus characteristic interface for" << m_path.path();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDBusPendingCall writingCall = m_descriptorInterface->asyncCall("WriteValue", value, QVariantMap());
|
||||||
|
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(writingCall, this);
|
||||||
|
m_asyncWrites.insert(watcher, value);
|
||||||
|
connect(watcher, &QDBusPendingCallWatcher::finished, this, &BluetoothGattDescriptor::onWritingFinished);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDebug operator<<(QDebug debug, BluetoothGattDescriptor *descriptor)
|
||||||
|
{
|
||||||
|
debug.noquote().nospace() << "GattDescriptor(" << descriptor->name();
|
||||||
|
debug.noquote().nospace() << ", " << descriptor->uuid().toString();
|
||||||
|
debug.noquote().nospace() << ", Properties: ";
|
||||||
|
|
||||||
|
if (descriptor->properties().testFlag(BluetoothGattDescriptor::Read))
|
||||||
|
debug.noquote().nospace() << " R ";
|
||||||
|
|
||||||
|
if (descriptor->properties().testFlag(BluetoothGattDescriptor::Write))
|
||||||
|
debug.noquote().nospace() << " W";
|
||||||
|
|
||||||
|
if (descriptor->properties().testFlag(BluetoothGattDescriptor::EncryptRead))
|
||||||
|
debug.noquote().nospace() << " ER";
|
||||||
|
|
||||||
|
if (descriptor->properties().testFlag(BluetoothGattDescriptor::EncryptWrite))
|
||||||
|
debug.noquote().nospace() << " EW";
|
||||||
|
|
||||||
|
if (descriptor->properties().testFlag(BluetoothGattDescriptor::EncryptAuthenticatedRead))
|
||||||
|
debug.noquote().nospace() << " EAR";
|
||||||
|
|
||||||
|
if (descriptor->properties().testFlag(BluetoothGattDescriptor::EncryptAuthenticatedWrite))
|
||||||
|
debug.noquote().nospace() << " EAW";
|
||||||
|
|
||||||
|
|
||||||
|
if (descriptor->properties().testFlag(BluetoothGattDescriptor::SecureRead))
|
||||||
|
debug.noquote().nospace() << " SR";
|
||||||
|
|
||||||
|
if (descriptor->properties().testFlag(BluetoothGattDescriptor::SecureWrite))
|
||||||
|
debug.noquote().nospace() << " SW";
|
||||||
|
|
||||||
|
debug.noquote().nospace() << ", value: " << descriptor->value();
|
||||||
|
debug.noquote().nospace() << ") ";
|
||||||
|
return debug;
|
||||||
|
}
|
||||||
104
nuki/bluez/bluetoothgattdescriptor.h
Normal file
104
nuki/bluez/bluetoothgattdescriptor.h
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
* *
|
||||||
|
* Copyright (C) 2016-2018 Simon Stürz <simon.stuerz@guh.io> *
|
||||||
|
* *
|
||||||
|
* This file is part of guh. *
|
||||||
|
* *
|
||||||
|
* This library is free software; you can redistribute it and/or *
|
||||||
|
* modify it under the terms of the GNU Lesser General Public *
|
||||||
|
* License as published by the Free Software Foundation; either *
|
||||||
|
* version 2.1 of the License, or (at your option) any later version. *
|
||||||
|
* *
|
||||||
|
* This library is distributed in the hope that it will be useful, *
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
||||||
|
* Lesser General Public License for more details. *
|
||||||
|
* *
|
||||||
|
* You should have received a copy of the GNU Lesser General Public *
|
||||||
|
* License along with this library; If not, see *
|
||||||
|
* <http://www.gnu.org/licenses/>. *
|
||||||
|
* *
|
||||||
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
|
#ifndef BLUETOOTHGATTDESCRIPTOR_H
|
||||||
|
#define BLUETOOTHGATTDESCRIPTOR_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QBluetoothUuid>
|
||||||
|
#include <QDBusInterface>
|
||||||
|
#include <QDBusPendingCall>
|
||||||
|
#include <QDBusPendingCallWatcher>
|
||||||
|
|
||||||
|
#include "blueztypes.h"
|
||||||
|
|
||||||
|
// Note: DBus documentation https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/gatt-api.txt
|
||||||
|
|
||||||
|
class BluetoothManager;
|
||||||
|
class BluetoothGattCharacteristic;
|
||||||
|
|
||||||
|
class BluetoothGattDescriptor : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_FLAGS(Properties)
|
||||||
|
|
||||||
|
friend class BluetoothManager;
|
||||||
|
friend class BluetoothGattCharacteristic;
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum Property {
|
||||||
|
Unknown = 0x00,
|
||||||
|
Read = 0x01,
|
||||||
|
Write = 0x02,
|
||||||
|
EncryptRead = 0x04,
|
||||||
|
EncryptWrite = 0x08,
|
||||||
|
EncryptAuthenticatedRead = 0x10,
|
||||||
|
EncryptAuthenticatedWrite = 0x20,
|
||||||
|
SecureRead = 0x40, // Server only
|
||||||
|
SecureWrite = 0x80 // Server only
|
||||||
|
};
|
||||||
|
Q_DECLARE_FLAGS(Properties, Property)
|
||||||
|
|
||||||
|
QString name() const;
|
||||||
|
QBluetoothUuid uuid() const;
|
||||||
|
QByteArray value() const;
|
||||||
|
Properties properties() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
explicit BluetoothGattDescriptor(const QDBusObjectPath &path, const QVariantMap &properties, QObject *parent = 0);
|
||||||
|
|
||||||
|
QDBusObjectPath m_path;
|
||||||
|
QDBusInterface *m_descriptorInterface;
|
||||||
|
|
||||||
|
QBluetoothUuid m_uuid;
|
||||||
|
QByteArray m_value;
|
||||||
|
Properties m_properties;
|
||||||
|
QHash<QDBusPendingCallWatcher *, QByteArray> m_asyncWrites;
|
||||||
|
|
||||||
|
void processProperties(const QVariantMap &properties);
|
||||||
|
|
||||||
|
void setValueInternally(const QByteArray &value);
|
||||||
|
|
||||||
|
Properties parsePropertyFlags(const QStringList &descriptorProperties);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void valueChanged(const QByteArray &value);
|
||||||
|
void readingFinished(const QByteArray &value);
|
||||||
|
void writingFinished(const QByteArray &value);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onPropertiesChanged(const QString &interface, const QVariantMap &changedProperties, const QStringList &invalidatedProperties);
|
||||||
|
|
||||||
|
void onReadingFinished(QDBusPendingCallWatcher *call);
|
||||||
|
void onWritingFinished(QDBusPendingCallWatcher *call);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
bool readValue();
|
||||||
|
bool writeValue(const QByteArray &value);
|
||||||
|
};
|
||||||
|
|
||||||
|
Q_DECLARE_OPERATORS_FOR_FLAGS(BluetoothGattDescriptor::Properties)
|
||||||
|
|
||||||
|
QDebug operator<<(QDebug debug, BluetoothGattDescriptor *descriptor);
|
||||||
|
|
||||||
|
|
||||||
|
#endif // BLUETOOTHGATTDESCRIPTOR_H
|
||||||
168
nuki/bluez/bluetoothgattservice.cpp
Normal file
168
nuki/bluez/bluetoothgattservice.cpp
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
* *
|
||||||
|
* Copyright (C) 2016-2018 Simon Stürz <simon.stuerz@guh.io> *
|
||||||
|
* *
|
||||||
|
* This file is part of guh. *
|
||||||
|
* *
|
||||||
|
* This library is free software; you can redistribute it and/or *
|
||||||
|
* modify it under the terms of the GNU Lesser General Public *
|
||||||
|
* License as published by the Free Software Foundation; either *
|
||||||
|
* version 2.1 of the License, or (at your option) any later version. *
|
||||||
|
* *
|
||||||
|
* This library is distributed in the hope that it will be useful, *
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
||||||
|
* Lesser General Public License for more details. *
|
||||||
|
* *
|
||||||
|
* You should have received a copy of the GNU Lesser General Public *
|
||||||
|
* License along with this library; If not, see *
|
||||||
|
* <http://www.gnu.org/licenses/>. *
|
||||||
|
* *
|
||||||
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
|
#include "bluetoothgattservice.h"
|
||||||
|
|
||||||
|
QString BluetoothGattService::serviceName() const
|
||||||
|
{
|
||||||
|
bool ok = false;
|
||||||
|
quint16 typeId = m_uuid.toUInt16(&ok);
|
||||||
|
|
||||||
|
if (ok) {
|
||||||
|
QBluetoothUuid::ServiceClassUuid uuid = static_cast<QBluetoothUuid::ServiceClassUuid>(typeId);
|
||||||
|
const QString name = QBluetoothUuid::serviceClassToString(uuid);
|
||||||
|
if (!name.isEmpty())
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return QString("Unknown Service");
|
||||||
|
}
|
||||||
|
|
||||||
|
BluetoothGattService::Type BluetoothGattService::type() const
|
||||||
|
{
|
||||||
|
return m_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
QBluetoothUuid BluetoothGattService::uuid() const
|
||||||
|
{
|
||||||
|
return m_uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<BluetoothGattCharacteristic *> BluetoothGattService::characteristics() const
|
||||||
|
{
|
||||||
|
return m_characteristics;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BluetoothGattService::hasCharacteristic(const QBluetoothUuid &characteristicUuid)
|
||||||
|
{
|
||||||
|
foreach (BluetoothGattCharacteristic *characteristic, m_characteristics) {
|
||||||
|
if (characteristic->uuid() == characteristicUuid) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BluetoothGattCharacteristic *BluetoothGattService::getCharacteristic(const QBluetoothUuid &characteristicUuid)
|
||||||
|
{
|
||||||
|
foreach (BluetoothGattCharacteristic *characteristic, m_characteristics) {
|
||||||
|
if (characteristic->uuid() == characteristicUuid) {
|
||||||
|
return characteristic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
BluetoothGattService::BluetoothGattService(const QDBusObjectPath &path, const QVariantMap &properties, QObject *parent) :
|
||||||
|
QObject(parent),
|
||||||
|
m_path(path),
|
||||||
|
m_type(Primary),
|
||||||
|
m_discovered(false)
|
||||||
|
{
|
||||||
|
processProperties(properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothGattService::processProperties(const QVariantMap &properties)
|
||||||
|
{
|
||||||
|
foreach (const QString &propertyName, properties.keys()) {
|
||||||
|
if (propertyName == "Primary") {
|
||||||
|
m_type = (properties.value(propertyName).toBool() ? Primary : Secondary);
|
||||||
|
} else if (propertyName == "UUID") {
|
||||||
|
m_uuid = QBluetoothUuid(properties.value(propertyName).toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothGattService::addCharacteristicInternally(const QDBusObjectPath &path, const QVariantMap &properties)
|
||||||
|
{
|
||||||
|
if (hasCharacteristic(path))
|
||||||
|
return;
|
||||||
|
|
||||||
|
BluetoothGattCharacteristic *characteristic = new BluetoothGattCharacteristic(path, properties, this);
|
||||||
|
m_characteristics.append(characteristic);
|
||||||
|
|
||||||
|
connect(characteristic, &BluetoothGattCharacteristic::readingFinished, this, &BluetoothGattService::onCharacteristicReadFinished);
|
||||||
|
connect(characteristic, &BluetoothGattCharacteristic::writingFinished, this, &BluetoothGattService::onCharacteristicReadFinished);
|
||||||
|
connect(characteristic, &BluetoothGattCharacteristic::valueChanged, this, &BluetoothGattService::onCharacteristicValueChanged);
|
||||||
|
|
||||||
|
qCDebug(dcBluez()) << "[+]" << characteristic;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BluetoothGattService::hasCharacteristic(const QDBusObjectPath &path)
|
||||||
|
{
|
||||||
|
foreach (BluetoothGattCharacteristic *characteristic, m_characteristics) {
|
||||||
|
if (characteristic->m_path == path) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BluetoothGattCharacteristic *BluetoothGattService::getCharacteristic(const QDBusObjectPath &path)
|
||||||
|
{
|
||||||
|
foreach (BluetoothGattCharacteristic *characteristic, m_characteristics) {
|
||||||
|
if (characteristic->m_path == path) {
|
||||||
|
return characteristic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothGattService::onCharacteristicReadFinished(const QByteArray &value)
|
||||||
|
{
|
||||||
|
BluetoothGattCharacteristic *characteristic = static_cast<BluetoothGattCharacteristic *>(sender());
|
||||||
|
emit characteristicReadFinished(characteristic, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothGattService::onCharacteristicWriteFinished(const QByteArray &value)
|
||||||
|
{
|
||||||
|
BluetoothGattCharacteristic *characteristic = static_cast<BluetoothGattCharacteristic *>(sender());
|
||||||
|
emit characteristicWriteFinished(characteristic, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothGattService::onCharacteristicValueChanged(const QByteArray &newValue)
|
||||||
|
{
|
||||||
|
BluetoothGattCharacteristic *characteristic = static_cast<BluetoothGattCharacteristic *>(sender());
|
||||||
|
emit characteristicChanged(characteristic, newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BluetoothGattService::readCharacteristic(const QBluetoothUuid &characteristicUuid)
|
||||||
|
{
|
||||||
|
if (!hasCharacteristic(characteristicUuid))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return getCharacteristic(characteristicUuid)->readCharacteristic();
|
||||||
|
}
|
||||||
|
|
||||||
|
QDebug operator<<(QDebug debug, BluetoothGattService *service)
|
||||||
|
{
|
||||||
|
debug.noquote().nospace() << "GattService(" << (service->type() == BluetoothGattService::Primary ? "Primary" : "Secondary");
|
||||||
|
debug.noquote().nospace() << ", " << service->serviceName();
|
||||||
|
debug.noquote().nospace() << ", " << service->uuid().toString();
|
||||||
|
debug.noquote().nospace() << ") ";
|
||||||
|
|
||||||
|
return debug;
|
||||||
|
}
|
||||||
96
nuki/bluez/bluetoothgattservice.h
Normal file
96
nuki/bluez/bluetoothgattservice.h
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
* *
|
||||||
|
* Copyright (C) 2016-2018 Simon Stürz <simon.stuerz@guh.io> *
|
||||||
|
* *
|
||||||
|
* This file is part of guh. *
|
||||||
|
* *
|
||||||
|
* This library is free software; you can redistribute it and/or *
|
||||||
|
* modify it under the terms of the GNU Lesser General Public *
|
||||||
|
* License as published by the Free Software Foundation; either *
|
||||||
|
* version 2.1 of the License, or (at your option) any later version. *
|
||||||
|
* *
|
||||||
|
* This library is distributed in the hope that it will be useful, *
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
||||||
|
* Lesser General Public License for more details. *
|
||||||
|
* *
|
||||||
|
* You should have received a copy of the GNU Lesser General Public *
|
||||||
|
* License along with this library; If not, see *
|
||||||
|
* <http://www.gnu.org/licenses/>. *
|
||||||
|
* *
|
||||||
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
|
#ifndef BLUETOOTHGATTSERVICE_H
|
||||||
|
#define BLUETOOTHGATTSERVICE_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QBluetoothUuid>
|
||||||
|
|
||||||
|
#include "blueztypes.h"
|
||||||
|
#include "bluetoothgattcharacteristic.h"
|
||||||
|
|
||||||
|
// Note: DBus documentation https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/gatt-api.txt
|
||||||
|
|
||||||
|
class BluetoothManager;
|
||||||
|
class BluetoothDevice;
|
||||||
|
|
||||||
|
class BluetoothGattService : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
friend class BluetoothManager;
|
||||||
|
friend class BluetoothDevice;
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum Type {
|
||||||
|
Primary,
|
||||||
|
Secondary
|
||||||
|
};
|
||||||
|
Q_ENUM(Type)
|
||||||
|
|
||||||
|
QString serviceName() const;
|
||||||
|
Type type() const;
|
||||||
|
QBluetoothUuid uuid() const;
|
||||||
|
|
||||||
|
// Characteristic methods
|
||||||
|
QList<BluetoothGattCharacteristic *> characteristics() const;
|
||||||
|
bool hasCharacteristic(const QBluetoothUuid &characteristicUuid);
|
||||||
|
BluetoothGattCharacteristic *getCharacteristic(const QBluetoothUuid &characteristicUuid);
|
||||||
|
|
||||||
|
private:
|
||||||
|
explicit BluetoothGattService(const QDBusObjectPath &path, const QVariantMap &properties, QObject *parent = 0);
|
||||||
|
|
||||||
|
QDBusObjectPath m_path;
|
||||||
|
|
||||||
|
Type m_type;
|
||||||
|
QBluetoothUuid m_uuid;
|
||||||
|
QList<BluetoothGattCharacteristic *> m_characteristics;
|
||||||
|
|
||||||
|
bool m_discovered;
|
||||||
|
|
||||||
|
void processProperties(const QVariantMap &properties);
|
||||||
|
|
||||||
|
// Methods called from BluetoothManager
|
||||||
|
void addCharacteristicInternally(const QDBusObjectPath &path, const QVariantMap &properties);
|
||||||
|
bool hasCharacteristic(const QDBusObjectPath &path);
|
||||||
|
BluetoothGattCharacteristic *getCharacteristic(const QDBusObjectPath &path);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onCharacteristicReadFinished(const QByteArray &value);
|
||||||
|
void onCharacteristicWriteFinished(const QByteArray &value);
|
||||||
|
void onCharacteristicValueChanged(const QByteArray &newValue);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void characteristicReadFinished(BluetoothGattCharacteristic *characteristic, const QByteArray &value);
|
||||||
|
void characteristicWriteFinished(BluetoothGattCharacteristic *characteristic, const QByteArray &value);
|
||||||
|
void characteristicChanged(BluetoothGattCharacteristic *characteristic, const QByteArray &newValue);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
bool readCharacteristic(const QBluetoothUuid &characteristicUuid);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
QDebug operator<<(QDebug debug, BluetoothGattService *service);
|
||||||
|
|
||||||
|
|
||||||
|
#endif // BLUETOOTHGATTSERVICE_H
|
||||||
306
nuki/bluez/bluetoothmanager.cpp
Normal file
306
nuki/bluez/bluetoothmanager.cpp
Normal file
@ -0,0 +1,306 @@
|
|||||||
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
* *
|
||||||
|
* Copyright (C) 2016-2018 Simon Stürz <simon.stuerz@guh.io> *
|
||||||
|
* *
|
||||||
|
* This file is part of guh. *
|
||||||
|
* *
|
||||||
|
* This library is free software; you can redistribute it and/or *
|
||||||
|
* modify it under the terms of the GNU Lesser General Public *
|
||||||
|
* License as published by the Free Software Foundation; either *
|
||||||
|
* version 2.1 of the License, or (at your option) any later version. *
|
||||||
|
* *
|
||||||
|
* This library is distributed in the hope that it will be useful, *
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
||||||
|
* Lesser General Public License for more details. *
|
||||||
|
* *
|
||||||
|
* You should have received a copy of the GNU Lesser General Public *
|
||||||
|
* License along with this library; If not, see *
|
||||||
|
* <http://www.gnu.org/licenses/>. *
|
||||||
|
* *
|
||||||
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
|
#include "bluetoothmanager.h"
|
||||||
|
|
||||||
|
#include <QDBusObjectPath>
|
||||||
|
#include <QDBusArgument>
|
||||||
|
#include <QDBusMetaType>
|
||||||
|
|
||||||
|
BluetoothManager::BluetoothManager(QObject *parent) :
|
||||||
|
QObject(parent),
|
||||||
|
m_available(false)
|
||||||
|
{
|
||||||
|
qDBusRegisterMetaType<InterfaceList>();
|
||||||
|
qDBusRegisterMetaType<ManagedObjectList>();
|
||||||
|
|
||||||
|
// Check DBus connection
|
||||||
|
if (!QDBusConnection::systemBus().isConnected()) {
|
||||||
|
qCWarning(dcBluez()) << "System DBus not connected.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get notification when bluez appears/disappears on DBus
|
||||||
|
m_serviceWatcher = new QDBusServiceWatcher(orgBluez, QDBusConnection::systemBus(), QDBusServiceWatcher::WatchForRegistration | QDBusServiceWatcher::WatchForUnregistration, this);
|
||||||
|
connect(m_serviceWatcher, &QDBusServiceWatcher::serviceRegistered, this, &BluetoothManager::serviceRegistered);
|
||||||
|
connect(m_serviceWatcher, &QDBusServiceWatcher::serviceUnregistered, this, &BluetoothManager::serviceUnregistered);
|
||||||
|
|
||||||
|
m_objectManagerInterface = new QDBusInterface(orgBluez, "/", orgFreedesktopDBusObjectManager, QDBusConnection::systemBus(), this);
|
||||||
|
if (!m_objectManagerInterface->isValid()) {
|
||||||
|
qCWarning(dcBluez()) << "Invalid DBus ObjectManager interface.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDBusConnection::systemBus().connect(orgBluez, "/", orgFreedesktopDBusObjectManager, "InterfacesAdded", this, SLOT(onInterfaceAdded(QDBusObjectPath,InterfaceList)));
|
||||||
|
QDBusConnection::systemBus().connect(orgBluez, "/", orgFreedesktopDBusObjectManager, "InterfacesRemoved", this, SLOT(onInterfaceRemoved(QDBusObjectPath,QStringList)));
|
||||||
|
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<BluetoothAdapter *> BluetoothManager::adapters() const
|
||||||
|
{
|
||||||
|
return m_adapters;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BluetoothManager::isAvailable() const
|
||||||
|
{
|
||||||
|
return m_available;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothManager::init()
|
||||||
|
{
|
||||||
|
// Get current object from org.bluez
|
||||||
|
QDBusMessage query = m_objectManagerInterface->call("GetManagedObjects");
|
||||||
|
if(query.type() != QDBusMessage::ReplyMessage) {
|
||||||
|
qCWarning(dcBluez()) << "Could not initialize BluetoothManager:" << query.errorName() << query.errorMessage();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QDBusArgument &argument = query.arguments().at(0).value<QDBusArgument>();
|
||||||
|
ManagedObjectList objectList = qdbus_cast<ManagedObjectList>(argument);
|
||||||
|
processObjectList(objectList);
|
||||||
|
|
||||||
|
if (!m_adapters.isEmpty())
|
||||||
|
setAvailable(true);
|
||||||
|
|
||||||
|
qCDebug(dcBluez()) << "BluetoothManager initialized successfully.";
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothManager::clean()
|
||||||
|
{
|
||||||
|
// Delete all adapter objects
|
||||||
|
// Note: devices, services and characteristic objects will be removed throug parent relation
|
||||||
|
foreach (BluetoothAdapter *adapter, m_adapters) {
|
||||||
|
m_adapters.removeOne(adapter);
|
||||||
|
emit adapterRemoved(adapter);
|
||||||
|
adapter->deleteLater();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_adapters.clear();
|
||||||
|
|
||||||
|
setAvailable(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothManager::setAvailable(const bool &available)
|
||||||
|
{
|
||||||
|
if (m_available != available) {
|
||||||
|
m_available = available;
|
||||||
|
emit availableChanged(m_available);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothManager::processObjectList(const ManagedObjectList &objectList)
|
||||||
|
{
|
||||||
|
foreach (const QDBusObjectPath &objectPath, objectList.keys()) {
|
||||||
|
InterfaceList interfaceList = objectList.value(objectPath);
|
||||||
|
processInterfaceList(objectPath, interfaceList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothManager::processInterfaceList(const QDBusObjectPath &objectPath, const InterfaceList &interfaceList)
|
||||||
|
{
|
||||||
|
// Note: object hierarchy: first add adapters, than devices, services, characteristics and finally descriptors
|
||||||
|
|
||||||
|
// Adapter interface
|
||||||
|
foreach (const QString &interface, interfaceList.keys()) {
|
||||||
|
if (interface == orgBluezAdapter1) {
|
||||||
|
QVariantMap properties = interfaceList.value(interface);
|
||||||
|
// Check if this adapter already added
|
||||||
|
if (!adapterAlreadyAdded(objectPath)) {
|
||||||
|
BluetoothAdapter *adapter = new BluetoothAdapter(objectPath, properties, this);
|
||||||
|
m_adapters.append(adapter);
|
||||||
|
emit adapterAdded(adapter);
|
||||||
|
qCDebug(dcBluez()) << "[+]" << adapter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Device interface
|
||||||
|
foreach (const QString &interface, interfaceList.keys()) {
|
||||||
|
if (interface == orgBluezDevice1) {
|
||||||
|
QVariantMap properties = interfaceList.value(interface);
|
||||||
|
// Find adapter for this device and add the device internally
|
||||||
|
if (properties.contains("Adapter")) {
|
||||||
|
QDBusObjectPath adapterObjectPath = qvariant_cast<QDBusObjectPath>(properties.value("Adapter"));
|
||||||
|
BluetoothAdapter *adapter = findAdapter(adapterObjectPath);
|
||||||
|
if (adapter)
|
||||||
|
adapter->addDeviceInternally(objectPath, properties);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GATT Service interface
|
||||||
|
foreach (const QString &interface, interfaceList.keys()) {
|
||||||
|
if (interface == orgBluezGattService1) {
|
||||||
|
QVariantMap properties = interfaceList.value(interface);
|
||||||
|
// Find device for this service and add the service internally
|
||||||
|
if (properties.contains("Device")) {
|
||||||
|
QDBusObjectPath deviceObjectPath = qvariant_cast<QDBusObjectPath>(properties.value("Device"));
|
||||||
|
BluetoothDevice *device = findDevice(deviceObjectPath);
|
||||||
|
if (device)
|
||||||
|
device->addServiceInternally(objectPath, properties);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GATT Characteristic interface
|
||||||
|
foreach (const QString &interface, interfaceList.keys()) {
|
||||||
|
if (interface == orgBluezGattCharacteristic1) {
|
||||||
|
QVariantMap properties = interfaceList.value(interface);
|
||||||
|
// Find service for this characteristic
|
||||||
|
if (properties.contains("Service")) {
|
||||||
|
QDBusObjectPath serviceObjectPath = qvariant_cast<QDBusObjectPath>(properties.value("Service"));
|
||||||
|
BluetoothGattService *service = findService(serviceObjectPath);
|
||||||
|
if (service)
|
||||||
|
service->addCharacteristicInternally(objectPath, properties);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GATT Descriptor interface
|
||||||
|
foreach (const QString &interface, interfaceList.keys()) {
|
||||||
|
if (interface == orgBluezGattDescriptor1) {
|
||||||
|
QVariantMap properties = interfaceList.value(interface);
|
||||||
|
// Find characteristic for this desciptor
|
||||||
|
if (properties.contains("Characteristic")) {
|
||||||
|
QDBusObjectPath characterisitcObjectPath = qvariant_cast<QDBusObjectPath>(properties.value("Characteristic"));
|
||||||
|
BluetoothGattCharacteristic *characteristic = findCharacteristic(characterisitcObjectPath);
|
||||||
|
if (characteristic)
|
||||||
|
characteristic->addDescriptorInternally(objectPath, properties);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BluetoothManager::adapterAlreadyAdded(const QDBusObjectPath &objectPath)
|
||||||
|
{
|
||||||
|
foreach (BluetoothAdapter *existingAdapter, m_adapters) {
|
||||||
|
if (existingAdapter->m_path == objectPath) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BluetoothAdapter *BluetoothManager::findAdapter(const QDBusObjectPath &objectPath)
|
||||||
|
{
|
||||||
|
foreach (BluetoothAdapter *adapter, m_adapters) {
|
||||||
|
if (adapter->m_path == objectPath) {
|
||||||
|
return adapter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
BluetoothDevice *BluetoothManager::findDevice(const QDBusObjectPath &objectPath)
|
||||||
|
{
|
||||||
|
foreach (BluetoothAdapter *adapter, m_adapters) {
|
||||||
|
foreach (BluetoothDevice *device, adapter->devices()) {
|
||||||
|
if (device->m_path == objectPath) {
|
||||||
|
return device;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
BluetoothGattService *BluetoothManager::findService(const QDBusObjectPath &objectPath)
|
||||||
|
{
|
||||||
|
foreach (BluetoothAdapter *adapter, m_adapters) {
|
||||||
|
foreach (BluetoothDevice *device, adapter->devices()) {
|
||||||
|
if (device->hasService(objectPath)) {
|
||||||
|
return device->getService(objectPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
BluetoothGattCharacteristic *BluetoothManager::findCharacteristic(const QDBusObjectPath &objectPath)
|
||||||
|
{
|
||||||
|
foreach (BluetoothAdapter *adapter, m_adapters) {
|
||||||
|
foreach (BluetoothDevice *device, adapter->devices()) {
|
||||||
|
foreach (BluetoothGattService *service, device->services()) {
|
||||||
|
if (service->hasCharacteristic(objectPath)) {
|
||||||
|
return service->getCharacteristic(objectPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothManager::serviceRegistered(const QString &serviceName)
|
||||||
|
{
|
||||||
|
qCDebug(dcBluez()) << "BluetoothManager: service registered" << serviceName;
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothManager::serviceUnregistered(const QString &serviceName)
|
||||||
|
{
|
||||||
|
qCDebug(dcBluez()) << "BluetoothManager: service unregistered" << serviceName;
|
||||||
|
if (serviceName == orgBluez)
|
||||||
|
clean();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothManager::onInterfaceAdded(const QDBusObjectPath &objectPath, const InterfaceList &interfaceList)
|
||||||
|
{
|
||||||
|
//qCDebug(dcBluez()) << "Interface added" << objectPath.path();
|
||||||
|
processInterfaceList(objectPath, interfaceList);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothManager::onInterfaceRemoved(const QDBusObjectPath &objectPath, const QStringList &interfaces)
|
||||||
|
{
|
||||||
|
//qCDebug(dcBluez()) << "Interface removed" << objectPath.path() << interfaces;
|
||||||
|
|
||||||
|
// Adapter removed
|
||||||
|
if (interfaces.contains(orgBluezAdapter1)) {
|
||||||
|
BluetoothAdapter *adapter = findAdapter(objectPath);
|
||||||
|
qCDebug(dcBluez()) << "[-]" << adapter;
|
||||||
|
if (adapter) {
|
||||||
|
m_adapters.removeOne(adapter);
|
||||||
|
emit adapterRemoved(adapter);
|
||||||
|
adapter->deleteLater();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Device removed
|
||||||
|
if (interfaces.contains(orgBluezDevice1)) {
|
||||||
|
// Find adapter for this device
|
||||||
|
foreach (BluetoothAdapter *adapter, m_adapters) {
|
||||||
|
if (adapter->hasDevice(objectPath)) {
|
||||||
|
adapter->removeDeviceInternally(objectPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
85
nuki/bluez/bluetoothmanager.h
Normal file
85
nuki/bluez/bluetoothmanager.h
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
* *
|
||||||
|
* Copyright (C) 2016-2018 Simon Stürz <simon.stuerz@guh.io> *
|
||||||
|
* *
|
||||||
|
* This file is part of guh. *
|
||||||
|
* *
|
||||||
|
* This library is free software; you can redistribute it and/or *
|
||||||
|
* modify it under the terms of the GNU Lesser General Public *
|
||||||
|
* License as published by the Free Software Foundation; either *
|
||||||
|
* version 2.1 of the License, or (at your option) any later version. *
|
||||||
|
* *
|
||||||
|
* This library is distributed in the hope that it will be useful, *
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
||||||
|
* Lesser General Public License for more details. *
|
||||||
|
* *
|
||||||
|
* You should have received a copy of the GNU Lesser General Public *
|
||||||
|
* License along with this library; If not, see *
|
||||||
|
* <http://www.gnu.org/licenses/>. *
|
||||||
|
* *
|
||||||
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
|
#ifndef BLUETOOTHMANAGER_H
|
||||||
|
#define BLUETOOTHMANAGER_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QDBusConnection>
|
||||||
|
#include <QDBusInterface>
|
||||||
|
#include <QDBusServiceWatcher>
|
||||||
|
|
||||||
|
#include "blueztypes.h"
|
||||||
|
#include "bluetoothadapter.h"
|
||||||
|
|
||||||
|
class BluetoothManager : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit BluetoothManager(QObject *parent = 0);
|
||||||
|
|
||||||
|
QList<BluetoothAdapter *> adapters() const;
|
||||||
|
|
||||||
|
bool isAvailable() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QDBusInterface *m_objectManagerInterface;
|
||||||
|
QDBusServiceWatcher *m_serviceWatcher;
|
||||||
|
|
||||||
|
QList<BluetoothAdapter *> m_adapters;
|
||||||
|
|
||||||
|
bool m_available;
|
||||||
|
|
||||||
|
void init();
|
||||||
|
void clean();
|
||||||
|
|
||||||
|
void setAvailable(const bool &available);
|
||||||
|
|
||||||
|
// DBus object helpers
|
||||||
|
void processObjectList(const ManagedObjectList &objectList);
|
||||||
|
void processInterfaceList(const QDBusObjectPath &objectPath, const InterfaceList &interfaceList);
|
||||||
|
|
||||||
|
bool adapterAlreadyAdded(const QDBusObjectPath &objectPath);
|
||||||
|
|
||||||
|
BluetoothAdapter *findAdapter(const QDBusObjectPath &objectPath);
|
||||||
|
BluetoothDevice *findDevice(const QDBusObjectPath &objectPath);
|
||||||
|
BluetoothGattService *findService(const QDBusObjectPath &objectPath);
|
||||||
|
BluetoothGattCharacteristic *findCharacteristic(const QDBusObjectPath &objectPath);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void availableChanged(const bool &available);
|
||||||
|
|
||||||
|
void adapterAdded(BluetoothAdapter *adapter);
|
||||||
|
void adapterRemoved(BluetoothAdapter *adapter);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void serviceRegistered(const QString &serviceName);
|
||||||
|
void serviceUnregistered(const QString &serviceName);
|
||||||
|
|
||||||
|
void onInterfaceAdded(const QDBusObjectPath &objectPath, const InterfaceList &interfaceList);
|
||||||
|
void onInterfaceRemoved(const QDBusObjectPath &objectPath, const QStringList &interfaces);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // BLUETOOTHMANAGER_H
|
||||||
32
nuki/bluez/blueztypes.cpp
Normal file
32
nuki/bluez/blueztypes.cpp
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
* *
|
||||||
|
* Copyright (C) 2016-2018 Simon Stürz <simon.stuerz@guh.io> *
|
||||||
|
* *
|
||||||
|
* This file is part of guh. *
|
||||||
|
* *
|
||||||
|
* This library is free software; you can redistribute it and/or *
|
||||||
|
* modify it under the terms of the GNU Lesser General Public *
|
||||||
|
* License as published by the Free Software Foundation; either *
|
||||||
|
* version 2.1 of the License, or (at your option) any later version. *
|
||||||
|
* *
|
||||||
|
* This library is distributed in the hope that it will be useful, *
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
||||||
|
* Lesser General Public License for more details. *
|
||||||
|
* *
|
||||||
|
* You should have received a copy of the GNU Lesser General Public *
|
||||||
|
* License along with this library; If not, see *
|
||||||
|
* <http://www.gnu.org/licenses/>. *
|
||||||
|
* *
|
||||||
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
|
#include "blueztypes.h"
|
||||||
|
|
||||||
|
Q_LOGGING_CATEGORY(dcBluez, "Bluez")
|
||||||
|
|
||||||
|
Bluez::Bluez()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
103
nuki/bluez/blueztypes.h
Normal file
103
nuki/bluez/blueztypes.h
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
* *
|
||||||
|
* Copyright (C) 2016-2018 Simon Stürz <simon.stuerz@guh.io> *
|
||||||
|
* *
|
||||||
|
* This file is part of guh. *
|
||||||
|
* *
|
||||||
|
* This library is free software; you can redistribute it and/or *
|
||||||
|
* modify it under the terms of the GNU Lesser General Public *
|
||||||
|
* License as published by the Free Software Foundation; either *
|
||||||
|
* version 2.1 of the License, or (at your option) any later version. *
|
||||||
|
* *
|
||||||
|
* This library is distributed in the hope that it will be useful, *
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
||||||
|
* Lesser General Public License for more details. *
|
||||||
|
* *
|
||||||
|
* You should have received a copy of the GNU Lesser General Public *
|
||||||
|
* License along with this library; If not, see *
|
||||||
|
* <http://www.gnu.org/licenses/>. *
|
||||||
|
* *
|
||||||
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
|
#ifndef BLUEZTYPES_H
|
||||||
|
#define BLUEZTYPES_H
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QString>
|
||||||
|
#include <QDBusArgument>
|
||||||
|
#include <QDBusObjectPath>
|
||||||
|
#include <QLoggingCategory>
|
||||||
|
|
||||||
|
// Interfaces DBus
|
||||||
|
static const QString orgFreedesktopDBus = QStringLiteral("org.freedesktop.DBus");
|
||||||
|
static const QString orgFreedesktopDBusObjectManager = QStringLiteral("org.freedesktop.DBus.ObjectManager");
|
||||||
|
|
||||||
|
// Interfaces Bluez
|
||||||
|
static const QString orgBluez = QStringLiteral("org.bluez");
|
||||||
|
static const QString orgBluezAdapter1 = QStringLiteral("org.bluez.Adapter1");
|
||||||
|
static const QString orgBluezDevice1 = QStringLiteral("org.bluez.Device1");
|
||||||
|
static const QString orgBluezGattService1 = QStringLiteral("org.bluez.GattService1");
|
||||||
|
static const QString orgBluezGattCharacteristic1 = QStringLiteral("org.bluez.GattCharacteristic1");
|
||||||
|
static const QString orgBluezGattDescriptor1 = QStringLiteral("org.bluez.GattDescriptor1");
|
||||||
|
|
||||||
|
// DBus Object and interface types
|
||||||
|
typedef QMap<QString, QVariantMap> InterfaceList;
|
||||||
|
Q_DECLARE_METATYPE(InterfaceList)
|
||||||
|
|
||||||
|
typedef QMap<QDBusObjectPath, InterfaceList> ManagedObjectList;
|
||||||
|
Q_DECLARE_METATYPE(ManagedObjectList)
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
quint16 manufacturerId;
|
||||||
|
QByteArray data;
|
||||||
|
} ManufacturerData;
|
||||||
|
|
||||||
|
Q_DECLARE_METATYPE(ManufacturerData)
|
||||||
|
|
||||||
|
typedef QList<ManufacturerData> ManufacturerDataList;
|
||||||
|
Q_DECLARE_METATYPE(ManufacturerDataList)
|
||||||
|
|
||||||
|
// TODO: get naufacturer data
|
||||||
|
//QDBusArgument &operator<<(QDBusArgument &argument, const ManufacturerDataList &manufacturerDataList);
|
||||||
|
|
||||||
|
// Logging cathegory
|
||||||
|
Q_DECLARE_LOGGING_CATEGORY(dcBluez)
|
||||||
|
|
||||||
|
// Gadget class fot handling types
|
||||||
|
class Bluez {
|
||||||
|
|
||||||
|
Q_GADGET
|
||||||
|
Q_ENUMS(Error)
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum Error {
|
||||||
|
NoError,
|
||||||
|
NotReady,
|
||||||
|
Failed,
|
||||||
|
Rejected,
|
||||||
|
Canceled,
|
||||||
|
InvalidArguments,
|
||||||
|
AlreadyExists,
|
||||||
|
DoesNotExist,
|
||||||
|
InProgress,
|
||||||
|
NotInProgress,
|
||||||
|
AlreadyConnected,
|
||||||
|
ConnectFailed,
|
||||||
|
NotConnected,
|
||||||
|
NotSupported,
|
||||||
|
NotAuthorized,
|
||||||
|
AuthenticationCanceled,
|
||||||
|
AuthenticationFailed,
|
||||||
|
AuthenticationRejected,
|
||||||
|
AuthenticationTimeout,
|
||||||
|
ConnectionAttemptFailed,
|
||||||
|
DBusError,
|
||||||
|
UnknownError
|
||||||
|
};
|
||||||
|
Q_ENUM(Error)
|
||||||
|
|
||||||
|
Bluez();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // BLUEZTYPES_H
|
||||||
325
nuki/devicepluginnuki.cpp
Normal file
325
nuki/devicepluginnuki.cpp
Normal file
@ -0,0 +1,325 @@
|
|||||||
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
* *
|
||||||
|
* Copyright (C) 2018 Bernhard Trinnes <bernhard.trinnes@guh.io> *
|
||||||
|
* Copyright (C) 2018 Simon Stürz <simon.stuerz@guh.io> *
|
||||||
|
* *
|
||||||
|
* This file is part of guh. *
|
||||||
|
* *
|
||||||
|
* This library is free software; you can redistribute it and/or *
|
||||||
|
* modify it under the terms of the GNU Lesser General Public *
|
||||||
|
* License as published by the Free Software Foundation; either *
|
||||||
|
* version 2.1 of the License, or (at your option) any later version. *
|
||||||
|
* *
|
||||||
|
* This library is distributed in the hope that it will be useful, *
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
||||||
|
* Lesser General Public License for more details. *
|
||||||
|
* *
|
||||||
|
* You should have received a copy of the GNU Lesser General Public *
|
||||||
|
* License along with this library; If not, see *
|
||||||
|
* <http://www.gnu.org/licenses/>. *
|
||||||
|
* *
|
||||||
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
|
#include "devicepluginnuki.h"
|
||||||
|
#include "devices/device.h"
|
||||||
|
#include "plugininfo.h"
|
||||||
|
#include "hardware/bluetoothlowenergy/bluetoothlowenergymanager.h"
|
||||||
|
|
||||||
|
extern "C"{
|
||||||
|
#include "sodium.h"
|
||||||
|
}
|
||||||
|
|
||||||
|
DevicePluginNuki::DevicePluginNuki()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
DevicePluginNuki::~DevicePluginNuki()
|
||||||
|
{
|
||||||
|
hardwareManager()->pluginTimerManager()->unregisterTimer(m_refreshTimer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DevicePluginNuki::init()
|
||||||
|
{
|
||||||
|
// Read every hour the state of the lock
|
||||||
|
m_refreshTimer = hardwareManager()->pluginTimerManager()->registerTimer(3600);
|
||||||
|
connect(m_refreshTimer, &PluginTimer::timeout, this, &DevicePluginNuki::onRefreshTimeout);
|
||||||
|
|
||||||
|
// Bluetooth manager for BTLE bluez handling
|
||||||
|
m_bluetoothManager = new BluetoothManager(this);
|
||||||
|
if (!m_bluetoothManager->isAvailable()) {
|
||||||
|
qCWarning(dcNuki()) << "Bluetooth not available";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_bluetoothManager->adapters().isEmpty()) {
|
||||||
|
qCWarning(dcNuki()) << "No bluetooth adapter found.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_bluetoothAdapter = m_bluetoothManager->adapters().first();
|
||||||
|
m_bluetoothAdapter->setPower(true);
|
||||||
|
m_bluetoothAdapter->setDiscoverable(true);
|
||||||
|
m_bluetoothAdapter->setPairable(true);
|
||||||
|
|
||||||
|
qCDebug(dcNuki()) << "Using bluetooth adapter" << m_bluetoothAdapter;
|
||||||
|
|
||||||
|
if (sodium_init() < 0) {
|
||||||
|
qCCritical(dcNuki()) << "Could not initialize encryption library sodium";
|
||||||
|
m_encrytionLibraryInitialized = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_encrytionLibraryInitialized = true;
|
||||||
|
qCDebug(dcNuki()) << "Encryption library initialized successfully: libsodium" << sodium_version_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DevicePluginNuki::setupDevice(DeviceSetupInfo *info)
|
||||||
|
{
|
||||||
|
Device *device = info->device();
|
||||||
|
qCDebug(dcNuki()) << "Setup device" << device->name() << device->params();
|
||||||
|
|
||||||
|
QBluetoothAddress address = QBluetoothAddress(device->params().paramValue(nukiDeviceMacParamTypeId).toString());
|
||||||
|
if (bluetoothDeviceAlreadyAdded(address)) {
|
||||||
|
qCWarning(dcNuki()) << "Device already added.";
|
||||||
|
return info->finish(Device::DeviceErrorDeviceInUse, QT_TR_NOOP("Device is already in use."));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_bluetoothAdapter){
|
||||||
|
qCWarning(dcNuki()) << "No bluetooth adapter available";
|
||||||
|
return info->finish(Device::DeviceErrorHardwareNotAvailable, QT_TR_NOOP("Bluetooth is not available on this system."));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_bluetoothAdapter->hasDevice(address)) {
|
||||||
|
Nuki *nuki = new Nuki(device, m_bluetoothAdapter->getDevice(address), this);
|
||||||
|
m_nukiDevices.insert(nuki, device);
|
||||||
|
} else {
|
||||||
|
qCWarning(dcNuki()) << "Could not find bluetooth device for setup" << address;
|
||||||
|
return info->finish(Device::DeviceErrorHardwareNotAvailable, QT_TR_NOOP("Bluetooth device not found."));
|
||||||
|
}
|
||||||
|
|
||||||
|
info->finish(Device::DeviceErrorNoError);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DevicePluginNuki::discoverDevices(DeviceDiscoveryInfo *info)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (info->deviceClassId() != nukiDeviceClassId)
|
||||||
|
return info->finish(Device::DeviceErrorDeviceClassNotFound);
|
||||||
|
|
||||||
|
if (!hardwareManager()->bluetoothLowEnergyManager()->enabled())
|
||||||
|
return info->finish(Device::DeviceErrorHardwareNotAvailable, QT_TR_NOOP("Bluetooth is not available on this system."));
|
||||||
|
|
||||||
|
if (!m_bluetoothAdapter)
|
||||||
|
return info->finish(Device::DeviceErrorHardwareNotAvailable, QT_TR_NOOP("Bluetooth is not available on this system."));
|
||||||
|
|
||||||
|
m_bluetoothAdapter->setDiscoverable(true);
|
||||||
|
m_bluetoothAdapter->setPairable(true);
|
||||||
|
|
||||||
|
qCDebug(dcNuki()) << "Start bluetooth discovery...";
|
||||||
|
if (!m_bluetoothAdapter->discovering())
|
||||||
|
m_bluetoothAdapter->startDiscovering();
|
||||||
|
|
||||||
|
QTimer::singleShot(5000, info, [this, info]() { onBluetoothDiscoveryFinished(info); });
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void DevicePluginNuki::startPairing(DevicePairingInfo *info)
|
||||||
|
{
|
||||||
|
info->finish(Device::DeviceErrorNoError, QT_TR_NOOP("Please press the Nuki button for 5 seconds in order to activate the pairing mode before you continue."));
|
||||||
|
}
|
||||||
|
|
||||||
|
void DevicePluginNuki::confirmPairing(DevicePairingInfo *info, const QString &username, const QString &secret)
|
||||||
|
{
|
||||||
|
Q_UNUSED(username)
|
||||||
|
Q_UNUSED(secret)
|
||||||
|
|
||||||
|
qCDebug(dcNuki()) << "Pairing confirmed, assuming the pairing mode is active. Start authentication process";
|
||||||
|
if (info->deviceClassId() != nukiDeviceClassId) {
|
||||||
|
qCWarning(dcNuki()) << "Invalid device class id";
|
||||||
|
return info->finish(Device::DeviceErrorDeviceClassNotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_asyncSetupNuki) {
|
||||||
|
qCWarning(dcNuki()) << "There is already an async setup for a nuki running.";
|
||||||
|
return info->finish(Device::DeviceErrorDeviceInUse);
|
||||||
|
}
|
||||||
|
|
||||||
|
QBluetoothAddress address = QBluetoothAddress(info->params().paramValue(nukiDeviceMacParamTypeId).toString());
|
||||||
|
if (!m_bluetoothAdapter->hasDevice(address)) {
|
||||||
|
qCWarning(dcNuki()) << "Could not find bluetooth device for" << address.toString();
|
||||||
|
return info->finish(Device::DeviceErrorDeviceNotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
BluetoothDevice *bluetoothDevice = m_bluetoothAdapter->getDevice(address);
|
||||||
|
|
||||||
|
m_asyncSetupNuki = new Nuki(nullptr, bluetoothDevice, this);
|
||||||
|
connect(m_asyncSetupNuki, &Nuki::authenticationProcessFinished, this, &DevicePluginNuki::onNukiAuthenticationProcessFinished);
|
||||||
|
connect(m_asyncSetupNuki, &Nuki::availableChanged, this, &DevicePluginNuki::onAsyncSetupNukiAvailableChanged);
|
||||||
|
|
||||||
|
m_asyncSetupNuki->startAuthenticationProcess(info->transactionId());
|
||||||
|
m_pairingInfo = info;
|
||||||
|
connect(info, &DevicePairingInfo::destroyed, this, [this] { m_pairingInfo = nullptr; });
|
||||||
|
}
|
||||||
|
|
||||||
|
void DevicePluginNuki::postSetupDevice(Device *device)
|
||||||
|
{
|
||||||
|
Nuki *nuki = m_nukiDevices.key(device);
|
||||||
|
nuki->refreshStates();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DevicePluginNuki::executeAction(DeviceActionInfo *info)
|
||||||
|
{
|
||||||
|
Device *device = info->device();
|
||||||
|
Action action = info->action();
|
||||||
|
|
||||||
|
QPointer<Nuki> nuki = m_nukiDevices.key(device);
|
||||||
|
if (nuki.isNull()) {
|
||||||
|
qCWarning(dcNuki()) << "Could not execute action. There is no nuki object for this device";
|
||||||
|
return info->finish(Device::DeviceErrorHardwareFailure);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hardwareManager()->bluetoothLowEnergyManager()->enabled()) {
|
||||||
|
qCWarning(dcNuki()) << "Could not execute action. There bluetooth hardware resource is disabled.";
|
||||||
|
return info->finish(Device::DeviceErrorHardwareNotAvailable);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action.actionTypeId() == nukiCloseActionTypeId) {
|
||||||
|
if (!nuki->executeDeviceAction(Nuki::NukiActionLock, info)) {
|
||||||
|
return info->finish(Device::DeviceErrorDeviceInUse);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} else if (action.actionTypeId() == nukiOpenActionTypeId) {
|
||||||
|
if (!nuki->executeDeviceAction(Nuki::NukiActionUnlock, info)) {
|
||||||
|
return info->finish(Device::DeviceErrorDeviceInUse);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} else if (action.actionTypeId() == nukiUnlatchActionTypeId) {
|
||||||
|
if (!nuki->executeDeviceAction(Nuki::NukiActionUnlatch, info)) {
|
||||||
|
return info->finish(Device::DeviceErrorDeviceInUse);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} else if (action.actionTypeId() == nukiRefreshActionTypeId) {
|
||||||
|
if (!nuki->executeDeviceAction(Nuki::NukiActionRefresh, info)) {
|
||||||
|
return info->finish(Device::DeviceErrorDeviceInUse);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
info->finish(Device::DeviceErrorActionTypeNotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DevicePluginNuki::deviceRemoved(Device *device)
|
||||||
|
{
|
||||||
|
if (!m_nukiDevices.values().contains(device))
|
||||||
|
return;
|
||||||
|
|
||||||
|
Nuki *nuki = m_nukiDevices.key(device);
|
||||||
|
nuki->clearSettings();
|
||||||
|
|
||||||
|
// FIXME: deauthenticate nymea from nuki device
|
||||||
|
|
||||||
|
qCDebug(dcNuki()) << "Delete pairing information from bluez" << nuki->bluetoothDevice();
|
||||||
|
m_bluetoothAdapter->removeDevice(nuki->bluetoothDevice()->address());
|
||||||
|
m_nukiDevices.remove(nuki);
|
||||||
|
nuki->deleteLater();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DevicePluginNuki::bluetoothDeviceAlreadyAdded(const QBluetoothAddress &address)
|
||||||
|
{
|
||||||
|
foreach (Device *device, m_nukiDevices.values()) {
|
||||||
|
if (device->deviceClassId() == nukiDeviceClassId && device->paramValue(nukiDeviceMacParamTypeId).toString() == address.toString()) {
|
||||||
|
qCDebug(dcNuki()) << "Nuki with address" << address.toString() << "already added.";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DevicePluginNuki::onRefreshTimeout()
|
||||||
|
{
|
||||||
|
// Only reconnect if the hardware resource is enabled
|
||||||
|
if (hardwareManager()->bluetoothLowEnergyManager()->enabled()) {
|
||||||
|
foreach (Nuki *nuki, m_nukiDevices.keys()) {
|
||||||
|
nuki->refreshStates();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DevicePluginNuki::onBluetoothEnabledChanged(const bool &enabled)
|
||||||
|
{
|
||||||
|
qCDebug(dcNuki()) << "Bluetooth hardware resource" << (enabled ? "enabled" : "disabled");
|
||||||
|
|
||||||
|
// Disconnect all devices, autoconnect will not trigger until the resource is enabled again
|
||||||
|
foreach (Nuki *nuki, m_nukiDevices.keys()) {
|
||||||
|
if (!enabled) {
|
||||||
|
nuki->disconnectDevice();
|
||||||
|
} else {
|
||||||
|
nuki->connectDevice();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DevicePluginNuki::onBluetoothDiscoveryFinished(DeviceDiscoveryInfo *info)
|
||||||
|
{
|
||||||
|
qCDebug(dcNuki()) << "Bluetooth discovery for nuki devices finished";
|
||||||
|
m_bluetoothAdapter->stopDiscovering();
|
||||||
|
|
||||||
|
foreach (BluetoothDevice *device, m_bluetoothAdapter->devices()) {
|
||||||
|
if (!bluetoothDeviceAlreadyAdded(device->address()) && device->name().contains("Nuki")) {
|
||||||
|
DeviceDescriptor descriptor(nukiDeviceClassId, "Nuki", device->address().toString());
|
||||||
|
|
||||||
|
// Get serial number from name
|
||||||
|
QString serialNumber;
|
||||||
|
QStringList tokens = device->name().split("_");
|
||||||
|
if (tokens.count() == 2) {
|
||||||
|
serialNumber = tokens.at(1);
|
||||||
|
} else {
|
||||||
|
qCWarning(dcNuki()) << "Could not read serial number from bluetooth device name" << device->name();
|
||||||
|
}
|
||||||
|
|
||||||
|
ParamList params;
|
||||||
|
params.append(Param(nukiDeviceNameParamTypeId, device->name()));
|
||||||
|
params.append(Param(nukiDeviceMacParamTypeId, device->address().toString()));
|
||||||
|
params.append(Param(nukiDeviceSerialNumberParamTypeId, serialNumber));
|
||||||
|
descriptor.setParams(params);
|
||||||
|
info->addDeviceDescriptor(descriptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info->finish(Device::DeviceErrorNoError);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DevicePluginNuki::onAsyncSetupNukiAvailableChanged(bool available)
|
||||||
|
{
|
||||||
|
// Remove possibly running Nuki setup devices on disconnected
|
||||||
|
if (!available && m_asyncSetupNuki) {
|
||||||
|
qCDebug(dcNuki()) << "Delete the temporary pairing device";
|
||||||
|
m_asyncSetupNuki->deleteLater();
|
||||||
|
m_asyncSetupNuki = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DevicePluginNuki::onNukiAuthenticationProcessFinished(const PairingTransactionId &pairingTransactionId, bool success)
|
||||||
|
{
|
||||||
|
if (m_asyncSetupNuki) {
|
||||||
|
qCDebug(dcNuki()) << "Delete the temporary pairing device";
|
||||||
|
m_asyncSetupNuki->deleteLater();
|
||||||
|
m_asyncSetupNuki = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_pairingInfo) {
|
||||||
|
qCWarning(dcNuki()) << "Authentication process finished, but have not valid pairing translaction id";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_pairingInfo->transactionId() != pairingTransactionId) {
|
||||||
|
qCWarning(dcNuki()) << "Authentication process finished, but have not valid pairing translaction id";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pairingInfo->finish(success ? Device::DeviceErrorNoError : Device::DeviceErrorHardwareFailure);
|
||||||
|
m_pairingInfo = nullptr;
|
||||||
|
}
|
||||||
78
nuki/devicepluginnuki.h
Normal file
78
nuki/devicepluginnuki.h
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
* *
|
||||||
|
* Copyright (C) 2018 Bernhard Trinnes <benhard.trinnes@guh.io> *
|
||||||
|
* Copyright (C) 2018 Simon Stürz <simon.stuerz@guh.io> *
|
||||||
|
* *
|
||||||
|
* This file is part of guh. *
|
||||||
|
* *
|
||||||
|
* This library is free software; you can redistribute it and/or *
|
||||||
|
* modify it under the terms of the GNU Lesser General Public *
|
||||||
|
* License as published by the Free Software Foundation; either *
|
||||||
|
* version 2.1 of the License, or (at your option) any later version. *
|
||||||
|
* *
|
||||||
|
* This library is distributed in the hope that it will be useful, *
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
||||||
|
* Lesser General Public License for more details. *
|
||||||
|
* *
|
||||||
|
* You should have received a copy of the GNU Lesser General Public *
|
||||||
|
* License along with this library; If not, see *
|
||||||
|
* <http://www.gnu.org/licenses/>. *
|
||||||
|
* *
|
||||||
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
|
#ifndef DEVICEPLUGINNUKI_H
|
||||||
|
#define DEVICEPLUGINNUKI_H
|
||||||
|
|
||||||
|
#include "plugintimer.h"
|
||||||
|
#include "devices/deviceplugin.h"
|
||||||
|
#include "bluez/bluetoothmanager.h"
|
||||||
|
|
||||||
|
#include "nuki.h"
|
||||||
|
|
||||||
|
class DevicePluginNuki : public DevicePlugin
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
Q_PLUGIN_METADATA(IID "guru.guh.DevicePlugin" FILE "devicepluginnuki.json")
|
||||||
|
Q_INTERFACES(DevicePlugin)
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit DevicePluginNuki();
|
||||||
|
~DevicePluginNuki();
|
||||||
|
|
||||||
|
void init() override;
|
||||||
|
void setupDevice(DeviceSetupInfo *info) override;
|
||||||
|
void discoverDevices(DeviceDiscoveryInfo *info) override;
|
||||||
|
void startPairing(DevicePairingInfo *info) override;
|
||||||
|
void confirmPairing(DevicePairingInfo *info, const QString &username, const QString &secret) override;
|
||||||
|
void postSetupDevice(Device *device) override;
|
||||||
|
void executeAction(DeviceActionInfo *info) override;
|
||||||
|
void deviceRemoved(Device *device) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QHash<Nuki *, Device *> m_nukiDevices;
|
||||||
|
PluginTimer *m_refreshTimer = nullptr;
|
||||||
|
|
||||||
|
BluetoothManager *m_bluetoothManager = nullptr;
|
||||||
|
BluetoothAdapter *m_bluetoothAdapter = nullptr;
|
||||||
|
|
||||||
|
Nuki *m_asyncSetupNuki = nullptr;
|
||||||
|
DevicePairingInfo *m_pairingInfo = nullptr;
|
||||||
|
|
||||||
|
bool m_encrytionLibraryInitialized = false;
|
||||||
|
|
||||||
|
bool bluetoothDeviceAlreadyAdded(const QBluetoothAddress &address);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onRefreshTimeout();
|
||||||
|
void onBluetoothEnabledChanged(const bool &enabled);
|
||||||
|
void onBluetoothDiscoveryFinished(DeviceDiscoveryInfo *info);
|
||||||
|
|
||||||
|
void onAsyncSetupNukiAvailableChanged(bool available);
|
||||||
|
|
||||||
|
void onNukiAuthenticationProcessFinished(const PairingTransactionId &pairingTransactionId, bool success);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // DEVICEPLUGINNUKI_H
|
||||||
157
nuki/devicepluginnuki.json
Normal file
157
nuki/devicepluginnuki.json
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
{
|
||||||
|
"name": "Nuki",
|
||||||
|
"displayName": "Nuki",
|
||||||
|
"id": "e5806d75-a40e-4766-a272-5a3a8d3ed625",
|
||||||
|
"vendors": [
|
||||||
|
{
|
||||||
|
"name": "nuki",
|
||||||
|
"displayName": "Nuki",
|
||||||
|
"id": "bf313b83-2ac5-4d22-bf0e-c13d3b4caf52",
|
||||||
|
"deviceClasses": [
|
||||||
|
{
|
||||||
|
"id": "4a1cc5d9-9b44-4632-8db0-66d64efd4767",
|
||||||
|
"name": "nuki",
|
||||||
|
"displayName": "Smartlock",
|
||||||
|
"interfaces": [ "connectable", "battery", "smartlock" ],
|
||||||
|
"createMethods": [ "discovery" ],
|
||||||
|
"setupMethod": "pushButton",
|
||||||
|
"paramTypes": [
|
||||||
|
{
|
||||||
|
"id": "2477abba-874b-4c48-b543-7b911ff215b3",
|
||||||
|
"name": "name",
|
||||||
|
"displayName": "Name",
|
||||||
|
"type": "QString",
|
||||||
|
"inputType": "TextLine"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "30976794-6066-4f72-8135-6d50499247a5",
|
||||||
|
"name": "mac",
|
||||||
|
"displayName": "MAC address",
|
||||||
|
"type": "QString",
|
||||||
|
"inputType": "MacAddress"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ea51d911-f94a-4d2d-97fd-9f1d4c6519bf",
|
||||||
|
"name": "serialNumber",
|
||||||
|
"displayName": "Serial number",
|
||||||
|
"type": "QString",
|
||||||
|
"inputType": "TextLine"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stateTypes": [
|
||||||
|
{
|
||||||
|
"id": "d5fbd774-4e87-4c7c-8c92-f094352897f6",
|
||||||
|
"name": "connected",
|
||||||
|
"displayName": "Connected",
|
||||||
|
"displayNameEvent": "Connected changed",
|
||||||
|
"type": "bool",
|
||||||
|
"defaultValue": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e9ffe14f-b71d-44cd-96c7-eb90fe243e13",
|
||||||
|
"name": "batteryCritical",
|
||||||
|
"displayName": "Battery critical",
|
||||||
|
"displayNameEvent": "Battery critical changed",
|
||||||
|
"type": "bool",
|
||||||
|
"defaultValue": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "a47dec8b-4df2-4acd-895b-bfeacbcb6f2e",
|
||||||
|
"name": "status",
|
||||||
|
"displayName": "Status",
|
||||||
|
"displayNameEvent": "Status changed",
|
||||||
|
"type": "QString",
|
||||||
|
"possibleValues": [
|
||||||
|
"Ok",
|
||||||
|
"Uncalibrated",
|
||||||
|
"Motor blocked",
|
||||||
|
"Undefined"
|
||||||
|
],
|
||||||
|
"defaultValue": "Undefined"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "07a09bd8-b342-4e33-92cd-0af21f8689fa",
|
||||||
|
"name": "state",
|
||||||
|
"displayName": "State",
|
||||||
|
"displayNameEvent": "State changed",
|
||||||
|
"type": "QString",
|
||||||
|
"possibleValues": [
|
||||||
|
"locked",
|
||||||
|
"locking",
|
||||||
|
"unlocked",
|
||||||
|
"unlocking",
|
||||||
|
"unlatched",
|
||||||
|
"unlatching"
|
||||||
|
],
|
||||||
|
"defaultValue": "locked"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "4e291e6a-2b90-4e6c-bad4-5400e6db4f05",
|
||||||
|
"name": "mode",
|
||||||
|
"displayName": "Mode",
|
||||||
|
"displayNameEvent": "Mode changed",
|
||||||
|
"type": "QString",
|
||||||
|
"possibleValues": [
|
||||||
|
"Uninitialized",
|
||||||
|
"Pairing",
|
||||||
|
"Door"
|
||||||
|
],
|
||||||
|
"defaultValue": "Uninitialized"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "b67bc7a6-2d6b-46e8-8e59-e4eab2e5292a",
|
||||||
|
"name": "trigger",
|
||||||
|
"displayName": "Trigger",
|
||||||
|
"displayNameEvent": "Trigger changed",
|
||||||
|
"type": "QString",
|
||||||
|
"possibleValues": [
|
||||||
|
"Bluetooth",
|
||||||
|
"Manual",
|
||||||
|
"Button"
|
||||||
|
],
|
||||||
|
"defaultValue": "Bluetooth"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "b1b0eafb-e5f2-47d3-bd18-74a21129f9b5",
|
||||||
|
"name": "hardwareRevision",
|
||||||
|
"displayName": "Hardware revision",
|
||||||
|
"displayNameEvent": "Hardware revision changed",
|
||||||
|
"type": "QString",
|
||||||
|
"defaultValue": "-"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "9dc0115b-cdb4-472c-961b-b182f77a576f",
|
||||||
|
"name": "firmwareRevision",
|
||||||
|
"displayName": "Firmware revision",
|
||||||
|
"displayNameEvent": "Firmware revision changed",
|
||||||
|
"type": "QString",
|
||||||
|
"defaultValue": "-"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"actionTypes": [
|
||||||
|
{
|
||||||
|
"id": "55d25891-89b9-4a9f-a2b3-774eccf30183",
|
||||||
|
"name": "close",
|
||||||
|
"displayName": "Lock"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "76e96738-5336-4b9a-87f7-1822307b5a39",
|
||||||
|
"name": "open",
|
||||||
|
"displayName": "Unlock"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "45be8d24-17c3-422b-b264-381e673bb3c8",
|
||||||
|
"name": "unlatch",
|
||||||
|
"displayName": "Open door"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "7c9d5c5d-d8c1-424b-8620-daa3a757576b",
|
||||||
|
"name": "refresh",
|
||||||
|
"displayName": "Refresh"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
nuki/docs/20170619-Bridge-API-v1.6.pdf
Normal file
BIN
nuki/docs/20170619-Bridge-API-v1.6.pdf
Normal file
Binary file not shown.
BIN
nuki/docs/20171120.Smartlock.API_.v1.10.pdf
Executable file
BIN
nuki/docs/20171120.Smartlock.API_.v1.10.pdf
Executable file
Binary file not shown.
556
nuki/nuki.cpp
Normal file
556
nuki/nuki.cpp
Normal file
@ -0,0 +1,556 @@
|
|||||||
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
* *
|
||||||
|
* Copyright (C) 2018 Bernhard Trinnes <bernhard.trinnes@guh.io> *
|
||||||
|
* Copyright (C) 2018 Simon Stürz <simon.stuerz@guh.io> *
|
||||||
|
* *
|
||||||
|
* This file is part of guh. *
|
||||||
|
* *
|
||||||
|
* This library is free software; you can redistribute it and/or *
|
||||||
|
* modify it under the terms of the GNU Lesser General Public *
|
||||||
|
* License as published by the Free Software Foundation; either *
|
||||||
|
* version 2.1 of the License, or (at your option) any later version. *
|
||||||
|
* *
|
||||||
|
* This library is distributed in the hope that it will be useful, *
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
||||||
|
* Lesser General Public License for more details. *
|
||||||
|
* *
|
||||||
|
* You should have received a copy of the GNU Lesser General Public *
|
||||||
|
* License along with this library; If not, see *
|
||||||
|
* <http://www.gnu.org/licenses/>. *
|
||||||
|
* *
|
||||||
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
|
#include "nuki.h"
|
||||||
|
#include "extern-plugininfo.h"
|
||||||
|
|
||||||
|
#include <QBitArray>
|
||||||
|
#include <QtEndian>
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QDataStream>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
static QBluetoothUuid initializationServiceUuid = QBluetoothUuid(QUuid("a92ee000-5501-11e4-916c-0800200c9a66"));
|
||||||
|
static QBluetoothUuid pairingServiceUuid = QBluetoothUuid(QUuid("a92ee100-5501-11e4-916c-0800200c9a66"));
|
||||||
|
static QBluetoothUuid pairingDataCharacteristicUuid = QBluetoothUuid(QUuid("a92ee101-5501-11e4-916c-0800200c9a66"));
|
||||||
|
static QBluetoothUuid keyturnerServiceUuid = QBluetoothUuid(QUuid("a92ee200-5501-11e4-916c-0800200c9a66"));
|
||||||
|
static QBluetoothUuid keyturnerDataCharacteristicUuid = QBluetoothUuid(QUuid("a92ee201-5501-11e4-916c-0800200c9a66"));
|
||||||
|
static QBluetoothUuid keyturnerUserDataCharacteristicUuid = QBluetoothUuid(QUuid("a92ee202-5501-11e4-916c-0800200c9a66"));
|
||||||
|
|
||||||
|
Nuki::Nuki(Device *device, BluetoothDevice *bluetoothDevice, QObject *parent) :
|
||||||
|
QObject(parent),
|
||||||
|
m_device(device),
|
||||||
|
m_bluetoothDevice(bluetoothDevice)
|
||||||
|
{
|
||||||
|
connect(m_bluetoothDevice, &BluetoothDevice::stateChanged, this, &Nuki::onBluetoothDeviceStateChanged);
|
||||||
|
onBluetoothDeviceStateChanged(m_bluetoothDevice->state());
|
||||||
|
}
|
||||||
|
|
||||||
|
Device *Nuki::device()
|
||||||
|
{
|
||||||
|
return m_device;
|
||||||
|
}
|
||||||
|
|
||||||
|
BluetoothDevice *Nuki::bluetoothDevice()
|
||||||
|
{
|
||||||
|
return m_bluetoothDevice;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Nuki::startAuthenticationProcess(const PairingTransactionId &pairingTransactionId)
|
||||||
|
{
|
||||||
|
if (m_nukiAction != NukiActionNone) {
|
||||||
|
qCWarning(dcNuki()) << "Cannot start authentication process. Nuki is busy and already processing an action. Please retry again." << m_nukiAction;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_nukiAction = NukiActionAuthenticate;
|
||||||
|
m_pairingId = pairingTransactionId;
|
||||||
|
|
||||||
|
if (m_available) {
|
||||||
|
executeCurrentAction();
|
||||||
|
} else {
|
||||||
|
m_bluetoothDevice->connectDevice();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Nuki::refreshStates()
|
||||||
|
{
|
||||||
|
return executeNukiAction(NukiActionRefresh);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Nuki::executeNukiAction(Nuki::NukiAction action)
|
||||||
|
{
|
||||||
|
if (m_nukiAction != NukiActionNone) {
|
||||||
|
qCWarning(dcNuki()) << "Cannot execute Nuki action. Nuki is busy and already processing an action." << m_nukiAction;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_nukiAction = action;
|
||||||
|
|
||||||
|
if (m_available) {
|
||||||
|
executeCurrentAction();
|
||||||
|
} else {
|
||||||
|
m_bluetoothDevice->connectDevice();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Nuki::executeDeviceAction(Nuki::NukiAction action, DeviceActionInfo *actionInfo)
|
||||||
|
{
|
||||||
|
if (m_nukiAction != NukiActionNone || !m_actionInfo.isNull()) {
|
||||||
|
qCWarning(dcNuki()) << "Nuki is busy and already processing an action. Please retry again." << m_nukiAction;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_actionInfo = QPointer<DeviceActionInfo>(actionInfo);
|
||||||
|
m_nukiAction = action;
|
||||||
|
|
||||||
|
if (m_available) {
|
||||||
|
executeCurrentAction();
|
||||||
|
} else {
|
||||||
|
m_bluetoothDevice->connectDevice();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Nuki::connectDevice()
|
||||||
|
{
|
||||||
|
if (!m_bluetoothDevice)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_bluetoothDevice->connectDevice();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Nuki::disconnectDevice()
|
||||||
|
{
|
||||||
|
if (!m_bluetoothDevice)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_bluetoothDevice->disconnectDevice();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Nuki::clearSettings()
|
||||||
|
{
|
||||||
|
if (m_nukiAuthenticator) {
|
||||||
|
m_nukiAuthenticator->clearSettings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Nuki::printServices()
|
||||||
|
{
|
||||||
|
foreach (BluetoothGattService *service, m_bluetoothDevice->services()) {
|
||||||
|
qCDebug(dcNuki()) << service;
|
||||||
|
foreach (BluetoothGattCharacteristic *characteristic, service->characteristics()) {
|
||||||
|
qCDebug(dcNuki()) << " " << characteristic->chararcteristicName() << characteristic->uuid().toString();
|
||||||
|
foreach (BluetoothGattDescriptor *descriptor, characteristic->descriptors()) {
|
||||||
|
qCDebug(dcNuki()) << " " << descriptor->name() << descriptor->uuid().toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Nuki::readDeviceInformationCharacteristics()
|
||||||
|
{
|
||||||
|
m_initUuidsToRead.append(QBluetoothUuid::SerialNumberString);
|
||||||
|
m_initUuidsToRead.append(QBluetoothUuid::HardwareRevisionString);
|
||||||
|
m_initUuidsToRead.append(QBluetoothUuid::FirmwareRevisionString);
|
||||||
|
|
||||||
|
m_deviceInformationService->readCharacteristic(QBluetoothUuid::SerialNumberString);
|
||||||
|
m_deviceInformationService->readCharacteristic(QBluetoothUuid::HardwareRevisionString);
|
||||||
|
m_deviceInformationService->readCharacteristic(QBluetoothUuid::FirmwareRevisionString);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Nuki::executeCurrentAction()
|
||||||
|
{
|
||||||
|
qCDebug(dcNuki()) << "Executing" << m_nukiAction;
|
||||||
|
|
||||||
|
switch (m_nukiAction) {
|
||||||
|
case NukiActionAuthenticate:
|
||||||
|
m_nukiAuthenticator->startAuthenticationProcess();
|
||||||
|
break;
|
||||||
|
case NukiActionRefresh:
|
||||||
|
if (!m_nukiController->readLockState()) {
|
||||||
|
finishCurrentAction(false);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NukiActionLock:
|
||||||
|
if (!m_nukiController->lock()) {
|
||||||
|
finishCurrentAction(false);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NukiActionUnlock:
|
||||||
|
if (!m_nukiController->unlock()) {
|
||||||
|
finishCurrentAction(false);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NukiActionUnlatch:
|
||||||
|
if (!m_nukiController->unlatch()) {
|
||||||
|
finishCurrentAction(false);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Nuki::onBluetoothDeviceStateChanged(const BluetoothDevice::State &state)
|
||||||
|
{
|
||||||
|
qCDebug(dcNuki()) << m_bluetoothDevice << "state changed --> " << state;
|
||||||
|
switch (state) {
|
||||||
|
case BluetoothDevice::Connecting:
|
||||||
|
break;
|
||||||
|
case BluetoothDevice::Connected:
|
||||||
|
if (m_bluetoothDevice->servicesResolved()) {
|
||||||
|
// Services already discovered
|
||||||
|
if (!init()) {
|
||||||
|
qCWarning(dcNuki()) << "Could not initialze device" << m_bluetoothDevice;
|
||||||
|
m_bluetoothDevice->disconnectDevice();
|
||||||
|
} else {
|
||||||
|
readDeviceInformationCharacteristics();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case BluetoothDevice::Pairing:
|
||||||
|
break;
|
||||||
|
case BluetoothDevice::Discovering:
|
||||||
|
break;
|
||||||
|
case BluetoothDevice::Discovered:
|
||||||
|
printServices();
|
||||||
|
if (!init()) {
|
||||||
|
qCWarning(dcNuki()) << "Could not initialze device" << m_bluetoothDevice;
|
||||||
|
m_bluetoothDevice->disconnectDevice();
|
||||||
|
} else {
|
||||||
|
readDeviceInformationCharacteristics();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case BluetoothDevice::Disconnecting:
|
||||||
|
setAvailable(false);
|
||||||
|
clean();
|
||||||
|
break;
|
||||||
|
case BluetoothDevice::Disconnected:
|
||||||
|
setAvailable(false);
|
||||||
|
clean();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Nuki::onDeviceInfoCharacteristicReadFinished(BluetoothGattCharacteristic *characteristic, const QByteArray &value)
|
||||||
|
{
|
||||||
|
qCDebug(dcNuki()) << "Read device information characteristic finished" << characteristic->chararcteristicName() << qUtf8Printable(value);
|
||||||
|
if (characteristic->uuid() == QBluetoothUuid::SerialNumberString) {
|
||||||
|
m_serialNumber = QString::fromUtf8(value);
|
||||||
|
m_initUuidsToRead.removeOne(QBluetoothUuid::SerialNumberString);
|
||||||
|
} else if (characteristic->uuid() == QBluetoothUuid::HardwareRevisionString) {
|
||||||
|
m_hardwareRevision = QString::fromUtf8(value);
|
||||||
|
m_initUuidsToRead.removeOne(QBluetoothUuid::HardwareRevisionString);
|
||||||
|
} else if (characteristic->uuid() == QBluetoothUuid::FirmwareRevisionString) {
|
||||||
|
m_firmwareRevision = QString::fromUtf8(value);
|
||||||
|
m_initUuidsToRead.removeOne(QBluetoothUuid::FirmwareRevisionString);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_initUuidsToRead.isEmpty()) {
|
||||||
|
// Initial read done. Make device available
|
||||||
|
setAvailable(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Nuki::onAuthenticationError(NukiUtils::ErrorCode error)
|
||||||
|
{
|
||||||
|
qCWarning(dcNuki()) << "Authentication error occured" << error;
|
||||||
|
|
||||||
|
if (m_pairingId.isNull())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// If we have a pairing id
|
||||||
|
emit authenticationProcessFinished(m_pairingId, false);
|
||||||
|
m_pairingId = PairingTransactionId();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Nuki::onAuthenticationFinished(bool success)
|
||||||
|
{
|
||||||
|
qCDebug(dcNuki()) << "Authentication process finished" << (success ? "successfully." : "with error.");
|
||||||
|
|
||||||
|
if (m_pairingId.isNull())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// If we have a pairing id
|
||||||
|
emit authenticationProcessFinished(m_pairingId, success);
|
||||||
|
m_pairingId = PairingTransactionId();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Nuki::onNukiReadStatesFinished(bool success)
|
||||||
|
{
|
||||||
|
m_nukiAction = NukiActionNone;
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
// Update states
|
||||||
|
onNukiStatesChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this was an action call
|
||||||
|
if (m_actionInfo.isNull()) {
|
||||||
|
// Looks like this was a refresh call, lets disconnect to minimize the not reachable time for other apps
|
||||||
|
QTimer::singleShot(0, m_bluetoothDevice, &BluetoothDevice::disconnectDevice);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
finishCurrentAction(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Nuki::onNukiStatesChanged()
|
||||||
|
{
|
||||||
|
if (!m_device)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_device->setStateValue(nukiHardwareRevisionStateTypeId, m_hardwareRevision);
|
||||||
|
m_device->setStateValue(nukiFirmwareRevisionStateTypeId, m_firmwareRevision);
|
||||||
|
m_device->setStateValue(nukiBatteryCriticalStateTypeId, m_nukiController->batteryCritical());
|
||||||
|
|
||||||
|
switch (m_nukiController->nukiLockTrigger()) {
|
||||||
|
case NukiUtils::LockTriggerBluetooth:
|
||||||
|
m_device->setStateValue(nukiTriggerStateTypeId, "Bluetooth");
|
||||||
|
break;
|
||||||
|
case NukiUtils::LockTriggerButton:
|
||||||
|
m_device->setStateValue(nukiTriggerStateTypeId, "Button");
|
||||||
|
break;
|
||||||
|
case NukiUtils::LockTriggerManual:
|
||||||
|
m_device->setStateValue(nukiTriggerStateTypeId, "Manual");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (m_nukiController->nukiState()) {
|
||||||
|
case NukiUtils::NukiStateDoorMode:
|
||||||
|
m_device->setStateValue(nukiModeStateTypeId, "Door");
|
||||||
|
break;
|
||||||
|
case NukiUtils::NukiStatePairingMode:
|
||||||
|
m_device->setStateValue(nukiModeStateTypeId, "Pairing");
|
||||||
|
break;
|
||||||
|
case NukiUtils::NukiStateUninitialized:
|
||||||
|
m_device->setStateValue(nukiModeStateTypeId, "Uninitialized");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (m_nukiController->nukiLockState()) {
|
||||||
|
case NukiUtils::LockStateLocked:
|
||||||
|
m_device->setStateValue(nukiStateStateTypeId, "locked");
|
||||||
|
m_device->setStateValue(nukiStatusStateTypeId, "Ok");
|
||||||
|
break;
|
||||||
|
case NukiUtils::LockStateLocking:
|
||||||
|
m_device->setStateValue(nukiStateStateTypeId, "locking");
|
||||||
|
m_device->setStateValue(nukiStatusStateTypeId, "Ok");
|
||||||
|
break;
|
||||||
|
case NukiUtils::LockStateMotorBlocked:
|
||||||
|
m_device->setStateValue(nukiStatusStateTypeId, "Motor blocked");
|
||||||
|
break;
|
||||||
|
case NukiUtils::LockStateUncalibrated:
|
||||||
|
m_device->setStateValue(nukiStatusStateTypeId, "Uncalibrated");
|
||||||
|
break;
|
||||||
|
case NukiUtils::LockStateUndefined:
|
||||||
|
m_device->setStateValue(nukiStatusStateTypeId, "Undefined");
|
||||||
|
break;
|
||||||
|
case NukiUtils::LockStateUnlatched:
|
||||||
|
m_device->setStateValue(nukiStateStateTypeId, "unlatched");
|
||||||
|
m_device->setStateValue(nukiStatusStateTypeId, "Ok");
|
||||||
|
break;
|
||||||
|
case NukiUtils::LockStateUnlatching:
|
||||||
|
m_device->setStateValue(nukiStateStateTypeId, "unlatching");
|
||||||
|
m_device->setStateValue(nukiStatusStateTypeId, "Ok");
|
||||||
|
break;
|
||||||
|
case NukiUtils::LockStateUnlockedLocknGoActive:
|
||||||
|
m_device->setStateValue(nukiStatusStateTypeId, "unlocked");
|
||||||
|
break;
|
||||||
|
case NukiUtils::LockStateUnlocked:
|
||||||
|
m_device->setStateValue(nukiStateStateTypeId, "unlocked");
|
||||||
|
m_device->setStateValue(nukiStatusStateTypeId, "Ok");
|
||||||
|
break;
|
||||||
|
case NukiUtils::LockStateUnlocking:
|
||||||
|
m_device->setStateValue(nukiStateStateTypeId, "unlocking");
|
||||||
|
m_device->setStateValue(nukiStatusStateTypeId, "Ok");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Nuki::init()
|
||||||
|
{
|
||||||
|
if (!m_bluetoothDevice)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
qCDebug(dcNuki()) << "Init" << m_bluetoothDevice;
|
||||||
|
|
||||||
|
// If not connected, connect
|
||||||
|
if (!m_bluetoothDevice->connected()) {
|
||||||
|
qCWarning(dcNuki()) << "Device is not connected" << m_bluetoothDevice;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If services not resolved yet, wait
|
||||||
|
if (!m_bluetoothDevice->servicesResolved()) {
|
||||||
|
qCWarning(dcNuki()) << "Device services not resolved yet" << m_bluetoothDevice;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify services
|
||||||
|
if (!m_bluetoothDevice->hasService(QBluetoothUuid::DeviceInformation)) {
|
||||||
|
qCWarning(dcNuki()) << "Could not find device information service on device" << m_bluetoothDevice;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_bluetoothDevice->hasService(pairingServiceUuid)) {
|
||||||
|
qCWarning(dcNuki()) << "Could not find pairing service on device" << m_bluetoothDevice;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_bluetoothDevice->hasService(keyturnerServiceUuid)) {
|
||||||
|
qCWarning(dcNuki()) << "Could not find key turner service on device" << m_bluetoothDevice;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create service and characteristic objects
|
||||||
|
// Device information
|
||||||
|
m_deviceInformationService = m_bluetoothDevice->getService(QBluetoothUuid::DeviceInformation);
|
||||||
|
connect(m_deviceInformationService, &BluetoothGattService::characteristicReadFinished, this, &Nuki::onDeviceInfoCharacteristicReadFinished);
|
||||||
|
|
||||||
|
// Keyturner service
|
||||||
|
m_keyturnerService = m_bluetoothDevice->getService(keyturnerServiceUuid);
|
||||||
|
if (!m_keyturnerService->hasCharacteristic(keyturnerUserDataCharacteristicUuid)) {
|
||||||
|
qCWarning(dcNuki()) << "Could not find user data characteristc on device" << m_bluetoothDevice;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!m_keyturnerService->hasCharacteristic(keyturnerDataCharacteristicUuid)) {
|
||||||
|
qCWarning(dcNuki()) << "Could not find data characteristc on device" << m_bluetoothDevice;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_keyturnerUserDataCharacteristic = m_keyturnerService->getCharacteristic(keyturnerUserDataCharacteristicUuid);
|
||||||
|
if (!m_keyturnerUserDataCharacteristic->startNotifications()) {
|
||||||
|
qCWarning(dcNuki()) << "Could not enable notifications for user data characteristic.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_keyturnerDataCharacteristic = m_keyturnerService->getCharacteristic(keyturnerDataCharacteristicUuid);
|
||||||
|
if (!m_keyturnerDataCharacteristic->startNotifications()) {
|
||||||
|
qCWarning(dcNuki()) << "Could not enable notifications for data characteristic.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pairing service
|
||||||
|
m_pairingService = m_bluetoothDevice->getService(pairingServiceUuid);
|
||||||
|
if (!m_pairingService->hasCharacteristic(pairingDataCharacteristicUuid)) {
|
||||||
|
qCWarning(dcNuki()) << "Could not find pairing data characteristc on device" << m_bluetoothDevice;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_pairingDataCharacteristic = m_pairingService->getCharacteristic(pairingDataCharacteristicUuid);
|
||||||
|
if (!m_pairingDataCharacteristic->startNotifications()) {
|
||||||
|
qCWarning(dcNuki()) << "Could not enable notifications for pairing characteristic.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create authenticator
|
||||||
|
if (m_nukiAuthenticator) {
|
||||||
|
delete m_nukiAuthenticator;
|
||||||
|
m_nukiAuthenticator = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_nukiAuthenticator = new NukiAuthenticator(m_bluetoothDevice->hostInfo(), m_pairingDataCharacteristic, this);
|
||||||
|
connect(m_nukiAuthenticator, &NukiAuthenticator::errorOccured, this, &Nuki::onAuthenticationError);
|
||||||
|
connect(m_nukiAuthenticator, &NukiAuthenticator::authenticationProcessFinished, this, &Nuki::onAuthenticationFinished);
|
||||||
|
|
||||||
|
// Create nuki handler for encrypted communication
|
||||||
|
if (m_nukiController) {
|
||||||
|
delete m_nukiController;
|
||||||
|
m_nukiController = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_nukiController = new NukiController(m_nukiAuthenticator, m_keyturnerUserDataCharacteristic, this);
|
||||||
|
connect(m_nukiController, &NukiController::readNukiStatesFinished, this, &Nuki::onNukiReadStatesFinished);
|
||||||
|
connect(m_nukiController, &NukiController::lockFinished, this, &Nuki::finishCurrentAction);
|
||||||
|
connect(m_nukiController, &NukiController::unlockFinished, this, &Nuki::finishCurrentAction);
|
||||||
|
connect(m_nukiController, &NukiController::unlatchFinished, this, &Nuki::finishCurrentAction);
|
||||||
|
connect(m_nukiController, &NukiController::nukiStatesChanged, this, &Nuki::onNukiStatesChanged);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Nuki::clean()
|
||||||
|
{
|
||||||
|
// Reset properties
|
||||||
|
m_hardwareRevision = QString();
|
||||||
|
m_serialNumber = QString();
|
||||||
|
m_firmwareRevision = QString();
|
||||||
|
m_initUuidsToRead.clear();
|
||||||
|
|
||||||
|
finishCurrentAction(false);
|
||||||
|
|
||||||
|
// Forget all services and characteristics
|
||||||
|
if (m_deviceInformationService) {
|
||||||
|
disconnect(m_deviceInformationService, &BluetoothGattService::characteristicReadFinished, this, &Nuki::onDeviceInfoCharacteristicReadFinished);
|
||||||
|
m_deviceInformationService = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_keyturnerService = nullptr;
|
||||||
|
m_keyturnerDataCharacteristic = nullptr;
|
||||||
|
m_keyturnerUserDataCharacteristic = nullptr;
|
||||||
|
|
||||||
|
m_pairingService = nullptr;
|
||||||
|
m_pairingDataCharacteristic = nullptr;
|
||||||
|
|
||||||
|
// Delete handler
|
||||||
|
if (m_nukiController) {
|
||||||
|
delete m_nukiController;
|
||||||
|
m_nukiController = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: delete the authenticator after the handler
|
||||||
|
if (m_nukiAuthenticator) {
|
||||||
|
delete m_nukiAuthenticator;
|
||||||
|
m_nukiAuthenticator = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Nuki::finishCurrentAction(bool success)
|
||||||
|
{
|
||||||
|
m_nukiAction = NukiActionNone;
|
||||||
|
if (m_actionInfo.isNull())
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_actionInfo->finish(success ? Device::DeviceErrorNoError : Device::DeviceErrorHardwareFailure);
|
||||||
|
m_actionInfo.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Nuki::setAvailable(bool available)
|
||||||
|
{
|
||||||
|
if (m_available == available)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_available = available;
|
||||||
|
emit availableChanged(m_available);
|
||||||
|
|
||||||
|
qCDebug(dcNuki()) << "Bluetooth device" << m_bluetoothDevice->name() << "is now" << (m_available ? "available" : "unavailable");
|
||||||
|
|
||||||
|
if (m_available) {
|
||||||
|
executeCurrentAction();
|
||||||
|
} else {
|
||||||
|
// Finish any running actions
|
||||||
|
finishCurrentAction(false);
|
||||||
|
|
||||||
|
// Finish possible running pairing transations
|
||||||
|
if (!m_pairingId.isNull()) {
|
||||||
|
qCWarning(dcNuki()) << "Cancel authentication process because of disconnection.";
|
||||||
|
emit authenticationProcessFinished(m_pairingId, false);
|
||||||
|
m_pairingId = PairingTransactionId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_device)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_device->setStateValue(nukiConnectedStateTypeId, m_available);
|
||||||
|
}
|
||||||
136
nuki/nuki.h
Normal file
136
nuki/nuki.h
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
* *
|
||||||
|
* Copyright (C) 2018 Bernhard Trinnes <bernhard.trinnes@guh.io> *
|
||||||
|
* Copyright (C) 2018 Simon Stürz <simon.stuerz@guh.io> *
|
||||||
|
* *
|
||||||
|
* This file is part of guh. *
|
||||||
|
* *
|
||||||
|
* This library is free software; you can redistribute it and/or *
|
||||||
|
* modify it under the terms of the GNU Lesser General Public *
|
||||||
|
* License as published by the Free Software Foundation; either *
|
||||||
|
* version 2.1 of the License, or (at your option) any later version. *
|
||||||
|
* *
|
||||||
|
* This library is distributed in the hope that it will be useful, *
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
||||||
|
* Lesser General Public License for more details. *
|
||||||
|
* *
|
||||||
|
* You should have received a copy of the GNU Lesser General Public *
|
||||||
|
* License along with this library; If not, see *
|
||||||
|
* <http://www.gnu.org/licenses/>. *
|
||||||
|
* *
|
||||||
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
|
#ifndef NUKI_H
|
||||||
|
#define NUKI_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QBluetoothUuid>
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QUuid>
|
||||||
|
#include <QPointer>
|
||||||
|
|
||||||
|
#include "typeutils.h"
|
||||||
|
#include "devices/device.h"
|
||||||
|
#include "bluez/bluetoothdevice.h"
|
||||||
|
#include "devices/deviceactioninfo.h"
|
||||||
|
|
||||||
|
#include "nukiutils.h"
|
||||||
|
#include "nukicontroller.h"
|
||||||
|
#include "nukiauthenticator.h"
|
||||||
|
|
||||||
|
//#include "nacl-20110221/crypto_auth/"
|
||||||
|
|
||||||
|
class Nuki : public QObject
|
||||||
|
{
|
||||||
|
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum NukiAction {
|
||||||
|
NukiActionNone,
|
||||||
|
NukiActionAuthenticate,
|
||||||
|
NukiActionRefresh,
|
||||||
|
NukiActionLock,
|
||||||
|
NukiActionUnlock,
|
||||||
|
NukiActionUnlatch
|
||||||
|
};
|
||||||
|
Q_ENUM(NukiAction)
|
||||||
|
|
||||||
|
explicit Nuki(Device *device, BluetoothDevice *bluetoothDevice, QObject *parent = nullptr);
|
||||||
|
|
||||||
|
Device *device();
|
||||||
|
BluetoothDevice *bluetoothDevice();
|
||||||
|
|
||||||
|
bool startAuthenticationProcess(const PairingTransactionId &pairingTransactionId);
|
||||||
|
bool refreshStates();
|
||||||
|
bool executeDeviceAction(NukiAction action, DeviceActionInfo *actionInfo);
|
||||||
|
|
||||||
|
void connectDevice();
|
||||||
|
void disconnectDevice();
|
||||||
|
|
||||||
|
void clearSettings();
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
Device *m_device = nullptr;
|
||||||
|
BluetoothDevice *m_bluetoothDevice = nullptr;
|
||||||
|
NukiController *m_nukiController = nullptr;
|
||||||
|
NukiAuthenticator *m_nukiAuthenticator = nullptr;
|
||||||
|
|
||||||
|
BluetoothGattService *m_deviceInformationService = nullptr;
|
||||||
|
BluetoothGattService *m_initializationService = nullptr;
|
||||||
|
BluetoothGattService *m_pairingService = nullptr;
|
||||||
|
BluetoothGattService *m_keyturnerService = nullptr;
|
||||||
|
|
||||||
|
BluetoothGattCharacteristic *m_pairingDataCharacteristic = nullptr;
|
||||||
|
BluetoothGattCharacteristic *m_keyturnerDataCharacteristic = nullptr;
|
||||||
|
BluetoothGattCharacteristic *m_keyturnerUserDataCharacteristic = nullptr;
|
||||||
|
|
||||||
|
// Device information
|
||||||
|
QList<QBluetoothUuid> m_initUuidsToRead;
|
||||||
|
QString m_serialNumber;
|
||||||
|
QString m_hardwareRevision;
|
||||||
|
QString m_firmwareRevision;
|
||||||
|
|
||||||
|
bool m_available = false;
|
||||||
|
|
||||||
|
NukiAction m_nukiAction = NukiActionNone;
|
||||||
|
QPointer<DeviceActionInfo> m_actionInfo;
|
||||||
|
PairingTransactionId m_pairingId;
|
||||||
|
|
||||||
|
bool init();
|
||||||
|
void clean();
|
||||||
|
bool executeNukiAction(NukiAction action);
|
||||||
|
|
||||||
|
void setAvailable(bool available);
|
||||||
|
void printServices();
|
||||||
|
void readDeviceInformationCharacteristics();
|
||||||
|
|
||||||
|
void executeCurrentAction();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void availableChanged(bool available);
|
||||||
|
void authenticationProcessFinished(const PairingTransactionId &pairingId, bool success);
|
||||||
|
void stateChanged(NukiUtils::LockState lockState);
|
||||||
|
void actionFinished(const ActionId &action, bool success);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
// Bluetooth device
|
||||||
|
void onBluetoothDeviceStateChanged(const BluetoothDevice::State &state);
|
||||||
|
|
||||||
|
// DeviceInfo service
|
||||||
|
void onDeviceInfoCharacteristicReadFinished(BluetoothGattCharacteristic *characteristic, const QByteArray &value);
|
||||||
|
|
||||||
|
// Nuki Authenticator
|
||||||
|
void onAuthenticationError(NukiUtils::ErrorCode error);
|
||||||
|
void onAuthenticationFinished(bool success);
|
||||||
|
|
||||||
|
// Nuki controller
|
||||||
|
void onNukiReadStatesFinished(bool success);
|
||||||
|
void finishCurrentAction(bool success);
|
||||||
|
void onNukiStatesChanged();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // NUKI_H
|
||||||
36
nuki/nuki.pro
Normal file
36
nuki/nuki.pro
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
include(../plugins.pri)
|
||||||
|
|
||||||
|
TARGET = $$qtLibraryTarget(nymea_devicepluginnuki)
|
||||||
|
|
||||||
|
QT += bluetooth dbus
|
||||||
|
|
||||||
|
# apt install libsodium-dev
|
||||||
|
LIBS += -lsodium
|
||||||
|
|
||||||
|
HEADERS += \
|
||||||
|
devicepluginnuki.h \
|
||||||
|
nuki.h \
|
||||||
|
bluez/blueztypes.h \
|
||||||
|
bluez/bluetoothmanager.h \
|
||||||
|
bluez/bluetoothadapter.h \
|
||||||
|
bluez/bluetoothdevice.h \
|
||||||
|
bluez/bluetoothgattservice.h \
|
||||||
|
bluez/bluetoothgattcharacteristic.h \
|
||||||
|
bluez/bluetoothgattdescriptor.h \
|
||||||
|
nukiutils.h \
|
||||||
|
nukiauthenticator.h \
|
||||||
|
nukicontroller.h
|
||||||
|
|
||||||
|
SOURCES += \
|
||||||
|
devicepluginnuki.cpp \
|
||||||
|
nuki.cpp \
|
||||||
|
bluez/blueztypes.cpp \
|
||||||
|
bluez/bluetoothmanager.cpp \
|
||||||
|
bluez/bluetoothadapter.cpp \
|
||||||
|
bluez/bluetoothdevice.cpp \
|
||||||
|
bluez/bluetoothgattservice.cpp \
|
||||||
|
bluez/bluetoothgattcharacteristic.cpp \
|
||||||
|
bluez/bluetoothgattdescriptor.cpp \
|
||||||
|
nukiutils.cpp \
|
||||||
|
nukiauthenticator.cpp \
|
||||||
|
nukicontroller.cpp
|
||||||
602
nuki/nukiauthenticator.cpp
Normal file
602
nuki/nukiauthenticator.cpp
Normal file
@ -0,0 +1,602 @@
|
|||||||
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
* *
|
||||||
|
* Copyright (C) 2018 Simon Stürz <simon.stuerz@guh.io> *
|
||||||
|
* *
|
||||||
|
* This file is part of guh. *
|
||||||
|
* *
|
||||||
|
* This library is free software; you can redistribute it and/or *
|
||||||
|
* modify it under the terms of the GNU Lesser General Public *
|
||||||
|
* License as published by the Free Software Foundation; either *
|
||||||
|
* version 2.1 of the License, or (at your option) any later version. *
|
||||||
|
* *
|
||||||
|
* This library is distributed in the hope that it will be useful, *
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
||||||
|
* Lesser General Public License for more details. *
|
||||||
|
* *
|
||||||
|
* You should have received a copy of the GNU Lesser General Public *
|
||||||
|
* License along with this library; If not, see *
|
||||||
|
* <http://www.gnu.org/licenses/>. *
|
||||||
|
* *
|
||||||
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
|
#include "nukiauthenticator.h"
|
||||||
|
|
||||||
|
#include "nukiutils.h"
|
||||||
|
#include "nymeasettings.h"
|
||||||
|
#include "extern-plugininfo.h"
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include "sodium.h"
|
||||||
|
#include "sodium/crypto_box.h"
|
||||||
|
#include "sodium/crypto_secretbox.h"
|
||||||
|
}
|
||||||
|
|
||||||
|
#include <QtEndian>
|
||||||
|
#include <QSettings>
|
||||||
|
#include <QDataStream>
|
||||||
|
|
||||||
|
NukiAuthenticator::NukiAuthenticator(const QBluetoothHostInfo &hostInfo, BluetoothGattCharacteristic *pairingCharacteristic, QObject *parent) :
|
||||||
|
QObject(parent),
|
||||||
|
m_hostInfo(hostInfo),
|
||||||
|
m_pairingCharacteristic(pairingCharacteristic)
|
||||||
|
{
|
||||||
|
|
||||||
|
#ifdef QT_DEBUG
|
||||||
|
// Enable full debug messages containing sensible data for debug builds
|
||||||
|
m_debug = true;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Check if we have authentication data for this device and set initial state
|
||||||
|
loadData();
|
||||||
|
if (isValid()) {
|
||||||
|
setState(AuthenticationStateAuthenticated);
|
||||||
|
} else {
|
||||||
|
setState(AuthenticationStateUnauthenticated);
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(m_pairingCharacteristic, &BluetoothGattCharacteristic::valueChanged, this, &NukiAuthenticator::onPairingDataCharacteristicChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
NukiUtils::ErrorCode NukiAuthenticator::error() const
|
||||||
|
{
|
||||||
|
return m_error;
|
||||||
|
}
|
||||||
|
|
||||||
|
NukiAuthenticator::AuthenticationState NukiAuthenticator::state() const
|
||||||
|
{
|
||||||
|
return m_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NukiAuthenticator::isValid() const
|
||||||
|
{
|
||||||
|
return !m_privateKey.isEmpty() &&
|
||||||
|
!m_publicKey.isEmpty() &&
|
||||||
|
!m_publicKeyNuki.isEmpty() &&
|
||||||
|
!m_authorizationId == 0 &&
|
||||||
|
!m_authorizationIdRawData.isEmpty() &&
|
||||||
|
!m_uuid.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NukiAuthenticator::clearSettings()
|
||||||
|
{
|
||||||
|
QSettings setting(NymeaSettings::settingsPath() + "/plugin-nuki.conf", QSettings::IniFormat);
|
||||||
|
setting.beginGroup(m_hostInfo.address().toString());
|
||||||
|
setting.remove("");
|
||||||
|
setting.endGroup();
|
||||||
|
|
||||||
|
qCDebug(dcNuki()) << "Settings cleared for" << m_hostInfo.address().toString() << "in" << setting.fileName();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NukiAuthenticator::startAuthenticationProcess()
|
||||||
|
{
|
||||||
|
setState(AuthenticationStateRequestPublicKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
quint32 NukiAuthenticator::authorizationId() const
|
||||||
|
{
|
||||||
|
return m_authorizationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray NukiAuthenticator::authorizationIdRawData() const
|
||||||
|
{
|
||||||
|
return m_authorizationIdRawData;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray NukiAuthenticator::encryptData(const QByteArray &data, const QByteArray &nonce)
|
||||||
|
{
|
||||||
|
// Calculate shared key
|
||||||
|
qCDebug(dcNuki()) << "Authenticator: Encrypt data";
|
||||||
|
Q_ASSERT_X(nonce.length() == crypto_box_NONCEBYTES, "data length", "The nonce does not have the correct length.");
|
||||||
|
|
||||||
|
/* Note: https://download.libsodium.org/doc/public-key_cryptography/authenticated_encryption.html
|
||||||
|
* unsigned char *c The encrypted message (length of the data + crypto_box_MACBYTES)
|
||||||
|
* const unsigned char *m The message to encrypt
|
||||||
|
* unsigned long long mlen The length of the message to encrypt
|
||||||
|
* const unsigned char *n The nonce (must also sent unencrypted)
|
||||||
|
* const unsigned char *pk The public key of the Nuki
|
||||||
|
* const unsigned char *sk The private key of nymea for this Nuki
|
||||||
|
*/
|
||||||
|
|
||||||
|
unsigned char encrypted[crypto_box_MACBYTES + data.length()];
|
||||||
|
int result = crypto_box_easy(encrypted,
|
||||||
|
reinterpret_cast<const unsigned char *>(data.data()),
|
||||||
|
static_cast<unsigned long long>(data.length()),
|
||||||
|
reinterpret_cast<const unsigned char *>(nonce.data()),
|
||||||
|
reinterpret_cast<const unsigned char *>(m_publicKeyNuki.data()),
|
||||||
|
reinterpret_cast<const unsigned char *>(m_privateKey.data()));
|
||||||
|
|
||||||
|
if (result < 0) {
|
||||||
|
qCWarning(dcNuki()) << "Could not encrypt data. Something went wrong";
|
||||||
|
return QByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray encryptedData = QByteArray(reinterpret_cast<const char*>(encrypted), crypto_box_MACBYTES + data.length());
|
||||||
|
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << " Private key :" << NukiUtils::convertByteArrayToHexStringCompact(m_privateKey);
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << " Public key :" << NukiUtils::convertByteArrayToHexStringCompact(m_publicKey);
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << " Nuki public key :" << NukiUtils::convertByteArrayToHexStringCompact(m_publicKeyNuki);
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << " Unencrypted data:" << NukiUtils::convertByteArrayToHexStringCompact(data);
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << " Encrypted data :" << NukiUtils::convertByteArrayToHexStringCompact(encryptedData);
|
||||||
|
|
||||||
|
return encryptedData;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray NukiAuthenticator::decryptData(const QByteArray &data, const QByteArray &nonce)
|
||||||
|
{
|
||||||
|
qCDebug(dcNuki()) << "Authenticator: Decrypt data";
|
||||||
|
Q_ASSERT_X(nonce.length() == crypto_box_NONCEBYTES, "data length", "The nonce does not have the correct length.");
|
||||||
|
Q_ASSERT_X(static_cast<uint>(data.length()) >= crypto_box_MACBYTES, "data length", "The encrypted data is to short.");
|
||||||
|
|
||||||
|
/* Note: https://download.libsodium.org/doc/public-key_cryptography/authenticated_encryption.html
|
||||||
|
* unsigned char *m The decrypted message result
|
||||||
|
* const unsigned char *c The message to decrypt / cyphertext (length of the encrypted data + crypto_box_MACBYTES)
|
||||||
|
* unsigned long long clen The length of the message to decrypt
|
||||||
|
* const unsigned char *n The nonce used while encryption (received in the unencrypted ADATA)
|
||||||
|
* 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];
|
||||||
|
int result = crypto_box_open_easy(decrypted,
|
||||||
|
reinterpret_cast<const unsigned char *>(data.data()),
|
||||||
|
static_cast<unsigned long long>(data.length()),
|
||||||
|
reinterpret_cast<const unsigned char *>(nonce.data()),
|
||||||
|
reinterpret_cast<const unsigned char *>(m_publicKeyNuki.data()),
|
||||||
|
reinterpret_cast<const unsigned char *>(m_privateKey.data()));
|
||||||
|
|
||||||
|
if (result < 0) {
|
||||||
|
qCWarning(dcNuki()) << "Could not decrypt data. Something went wrong";
|
||||||
|
return QByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray decryptedData = QByteArray(reinterpret_cast<const char*>(decrypted), data.length() - crypto_box_MACBYTES);
|
||||||
|
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << " Private key :" << NukiUtils::convertByteArrayToHexStringCompact(m_privateKey);
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << " Public key :" << NukiUtils::convertByteArrayToHexStringCompact(m_publicKey);
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << " Nuki public key :" << NukiUtils::convertByteArrayToHexStringCompact(m_publicKeyNuki);
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << " Encrypted data :" << NukiUtils::convertByteArrayToHexStringCompact(data);
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << " Decrypted data :" << NukiUtils::convertByteArrayToHexStringCompact(decryptedData);
|
||||||
|
|
||||||
|
return decryptedData;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray NukiAuthenticator::generateNonce(const int &length) const
|
||||||
|
{
|
||||||
|
unsigned char nounce[length];
|
||||||
|
randombytes_buf(nounce, length);
|
||||||
|
return QByteArray(reinterpret_cast<const char *>(nounce), length);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NukiAuthenticator::setState(NukiAuthenticator::AuthenticationState state)
|
||||||
|
{
|
||||||
|
if (m_state == state)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_state = state;
|
||||||
|
emit stateChanged(m_state);
|
||||||
|
|
||||||
|
qCDebug(dcNuki()) << m_state;
|
||||||
|
|
||||||
|
switch (m_state) {
|
||||||
|
case AuthenticationStateUnauthenticated:
|
||||||
|
resetExpectedData();
|
||||||
|
break;
|
||||||
|
case AuthenticationStateAuthenticated:
|
||||||
|
qCDebug(dcNuki()) << "Device" << m_hostInfo.address().toString() << "authenticated.";
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << " Private key :" << NukiUtils::convertByteArrayToHexStringCompact(m_privateKey);
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << " Public key :" << NukiUtils::convertByteArrayToHexStringCompact(m_publicKey);
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << " Nuki public key :" << NukiUtils::convertByteArrayToHexStringCompact(m_publicKeyNuki);
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << " Authorization ID:" << NukiUtils::convertByteArrayToHexStringCompact(m_authorizationIdRawData) << m_authorizationId;
|
||||||
|
break;
|
||||||
|
case AuthenticationStateRequestPublicKey:
|
||||||
|
resetExpectedData(NukiUtils::CommandPublicKey, 2);
|
||||||
|
requestPublicKey();
|
||||||
|
break;
|
||||||
|
case AuthenticationStateGenerateKeyPair:
|
||||||
|
resetExpectedData();
|
||||||
|
generateKeyPair();
|
||||||
|
setState(AuthenticationStateSendPublicKey);
|
||||||
|
break;
|
||||||
|
case AuthenticationStateSendPublicKey:
|
||||||
|
resetExpectedData(NukiUtils::CommandChallenge, 2);
|
||||||
|
sendPublicKey();
|
||||||
|
setState(AuthenticationStateReadChallenge);
|
||||||
|
break;
|
||||||
|
case AuthenticationStateReadChallenge:
|
||||||
|
resetExpectedData(NukiUtils::CommandChallenge, 2);
|
||||||
|
break;
|
||||||
|
case AuthenticationStateAutorization:
|
||||||
|
sendAuthorizationAuthenticator();
|
||||||
|
setState(AuthenticationStateReadSecondChallenge);
|
||||||
|
break;
|
||||||
|
case AuthenticationStateReadSecondChallenge:
|
||||||
|
resetExpectedData(NukiUtils::CommandChallenge, 2);
|
||||||
|
break;
|
||||||
|
case AuthenticationStateAuthenticateData:
|
||||||
|
resetExpectedData();
|
||||||
|
sendAuthenticateData();
|
||||||
|
setState(AuthenticationStateAuthorizationId);
|
||||||
|
break;
|
||||||
|
case AuthenticationStateAuthorizationId:
|
||||||
|
resetExpectedData(NukiUtils::CommandAuthorizationId, 5);
|
||||||
|
break;
|
||||||
|
case AuthenticationStateAuthorizationIdConfirm:
|
||||||
|
resetExpectedData();
|
||||||
|
sendAuthoizationIdConfirm();
|
||||||
|
setState(AuthenticationStateStatus);
|
||||||
|
break;
|
||||||
|
case AuthenticationStateStatus:
|
||||||
|
resetExpectedData(NukiUtils::CommandStatus);
|
||||||
|
break;
|
||||||
|
case AuthenticationStateError:
|
||||||
|
resetExpectedData();
|
||||||
|
emit errorOccured(m_error);
|
||||||
|
emit authenticationProcessFinished(false);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
qCWarning(dcNuki()) << "Authenticator: Unknown state.";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NukiAuthenticator::resetExpectedData(NukiUtils::Command command, int expectedCount)
|
||||||
|
{
|
||||||
|
m_currentReceivingCommand = command;
|
||||||
|
m_currentReceivingCurrentCount = 0;
|
||||||
|
m_currentReceivingExpectedCount = expectedCount;
|
||||||
|
m_currentReceivingData.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NukiAuthenticator::createAuthenticator(const QByteArray content)
|
||||||
|
{
|
||||||
|
// Create shared key
|
||||||
|
qCDebug(dcNuki()) << "Authenticator: Calculate shared key";
|
||||||
|
unsigned char sharedKey[crypto_box_BEFORENMBYTES];
|
||||||
|
int result = crypto_box_beforenm(sharedKey, reinterpret_cast<const unsigned char *>(m_publicKeyNuki.data()), reinterpret_cast<const unsigned char *>(m_privateKey.data()));
|
||||||
|
if (result < 0) {
|
||||||
|
qCWarning(dcNuki()) << "Could not create shared key for autorization authenticator.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_sharedKey = QByteArray(reinterpret_cast<const char*>(sharedKey), crypto_box_BEFORENMBYTES);
|
||||||
|
Q_ASSERT_X(m_sharedKey.length() == 32, "data length", "The shared key does not have the correct length.");
|
||||||
|
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << "Authenticator: Calculate authenticator hash HMAC-SHA-256";
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << " Shared key :" << NukiUtils::convertByteArrayToHexStringCompact(m_sharedKey);
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << " Nuki nonce :" << NukiUtils::convertByteArrayToHexStringCompact(m_nonceNuki);
|
||||||
|
|
||||||
|
// Calculate authenticator hash input for HMAC-SHA-256
|
||||||
|
qCDebug(dcNuki()) << "Authenticator: Calculate authenticator data";
|
||||||
|
unsigned char authenticator[crypto_auth_hmacsha256_BYTES];
|
||||||
|
result = crypto_auth_hmacsha256(authenticator, reinterpret_cast<const unsigned char *>(content.data()), content.length(), reinterpret_cast<const unsigned char *>(m_sharedKey.data()));
|
||||||
|
if (result < 0) {
|
||||||
|
qCWarning(dcNuki()) << "Could not create authenticator hash for autorization authenticator.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_authenticator = QByteArray(reinterpret_cast<const char*>(authenticator), crypto_auth_hmacsha256_BYTES);
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << " Authenticator :" << NukiUtils::convertByteArrayToHexStringCompact(m_authenticator);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NukiAuthenticator::requestPublicKey()
|
||||||
|
{
|
||||||
|
qCDebug(dcNuki()) << "Authenticator: Request public key fom Nuki";
|
||||||
|
|
||||||
|
QByteArray payload;
|
||||||
|
QDataStream stream(&payload, QIODevice::WriteOnly);
|
||||||
|
stream.setByteOrder(QDataStream::LittleEndian);
|
||||||
|
stream << static_cast<quint16>(NukiUtils::CommandPublicKey);
|
||||||
|
QByteArray data = NukiUtils::createRequestMessageForUnencrypted(NukiUtils::CommandRequestData, payload);
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << "-->" << NukiUtils::convertByteArrayToHexStringCompact(data);
|
||||||
|
m_pairingCharacteristic->writeCharacteristic(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NukiAuthenticator::sendPublicKey()
|
||||||
|
{
|
||||||
|
qCDebug(dcNuki()) << "Authenticator: Send public key to Nuki";
|
||||||
|
QByteArray data = NukiUtils::createRequestMessageForUnencrypted(NukiUtils::CommandPublicKey, m_publicKey);
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << "-->" << NukiUtils::convertByteArrayToHexStringCompact(data);
|
||||||
|
m_pairingCharacteristic->writeCharacteristic(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NukiAuthenticator::generateKeyPair()
|
||||||
|
{
|
||||||
|
qCDebug(dcNuki()) << "Generate key pair";
|
||||||
|
unsigned char publicKey[crypto_box_PUBLICKEYBYTES];
|
||||||
|
unsigned char secretKey[crypto_box_SECRETKEYBYTES];
|
||||||
|
crypto_box_keypair(publicKey, secretKey);
|
||||||
|
m_publicKey = QByteArray(reinterpret_cast<const char *>(publicKey), crypto_box_PUBLICKEYBYTES);
|
||||||
|
m_privateKey = QByteArray(reinterpret_cast<const char *>(secretKey), crypto_box_SECRETKEYBYTES);
|
||||||
|
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << " Private key :" << NukiUtils::convertByteArrayToHexStringCompact(m_privateKey);
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << " Public key :" << NukiUtils::convertByteArrayToHexStringCompact(m_publicKey);
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << " Nuki public key :" << NukiUtils::convertByteArrayToHexStringCompact(m_publicKeyNuki);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NukiAuthenticator::sendAuthorizationAuthenticator()
|
||||||
|
{
|
||||||
|
QByteArray valueR;
|
||||||
|
valueR.append(m_publicKey);
|
||||||
|
valueR.append(m_publicKeyNuki);
|
||||||
|
valueR.append(m_nonceNuki);
|
||||||
|
|
||||||
|
// Create authenticator and store it into m_authenticator
|
||||||
|
if (!createAuthenticator(valueR)) {
|
||||||
|
qCWarning(dcNuki()) << "Could not create authenticator hash HMAC-SHA-256";
|
||||||
|
setState(AuthenticationStateError);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the authenticator
|
||||||
|
qCDebug(dcNuki()) << "Authenticator: Send authorization authenticator to Nuki";
|
||||||
|
QByteArray message = NukiUtils::createRequestMessageForUnencrypted(NukiUtils::CommandAuthorizationAuthenticator, m_authenticator);
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << "-->" << NukiUtils::convertByteArrayToHexStringCompact(message);
|
||||||
|
m_pairingCharacteristic->writeCharacteristic(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NukiAuthenticator::sendAuthenticateData()
|
||||||
|
{
|
||||||
|
// Calculate new nounce
|
||||||
|
m_nonce = generateNonce();
|
||||||
|
|
||||||
|
QByteArray content;
|
||||||
|
QDataStream stream(&content, QIODevice::WriteOnly);
|
||||||
|
// Note: 0x00 = App, 0x01 = Bridge, 0x02 = Fob
|
||||||
|
stream << static_cast<quint8>(0x01);
|
||||||
|
// Note: app id (42)
|
||||||
|
stream << static_cast<quint32>(0x002A);
|
||||||
|
|
||||||
|
// 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.");
|
||||||
|
|
||||||
|
QByteArray valueR = content;
|
||||||
|
valueR.append(name);
|
||||||
|
valueR.append(m_nonce);
|
||||||
|
valueR.append(m_nonceNuki);
|
||||||
|
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << " Name :" << qUtf8Printable(name) << NukiUtils::convertByteArrayToHexStringCompact(name);
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << " Nonce :" << NukiUtils::convertByteArrayToHexStringCompact(m_nonce);
|
||||||
|
|
||||||
|
// Create authenticator and store it into m_authenticator
|
||||||
|
if (!createAuthenticator(valueR)) {
|
||||||
|
qCWarning(dcNuki()) << "Could not create authenticator hash HMAC-SHA-256";
|
||||||
|
setState(AuthenticationStateError);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare message to send to Nuki
|
||||||
|
QByteArray data;
|
||||||
|
data.append(m_authenticator);
|
||||||
|
data.append(content);
|
||||||
|
data.append(name);
|
||||||
|
data.append(m_nonce);
|
||||||
|
|
||||||
|
qCDebug(dcNuki()) << "Authenticator: Send authentication data to Nuki";
|
||||||
|
QByteArray message = NukiUtils::createRequestMessageForUnencrypted(NukiUtils::CommandAuthorizationData, data);
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << "-->" << NukiUtils::convertByteArrayToHexStringCompact(message);
|
||||||
|
m_pairingCharacteristic->writeCharacteristic(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NukiAuthenticator::sendAuthoizationIdConfirm()
|
||||||
|
{
|
||||||
|
qCDebug(dcNuki()) << "Authenticator: Create data for authentication ID confirm";
|
||||||
|
QByteArray valueR;
|
||||||
|
valueR.append(m_authorizationIdRawData);
|
||||||
|
valueR.append(m_nonceNuki);
|
||||||
|
|
||||||
|
// Create authenticator and store it into m_authenticator
|
||||||
|
if (!createAuthenticator(valueR)) {
|
||||||
|
qCWarning(dcNuki()) << "Could not create authenticator hash HMAC-SHA-256";
|
||||||
|
setState(AuthenticationStateError);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate new nounce
|
||||||
|
m_nonce = generateNonce();
|
||||||
|
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << " Nonce :" << NukiUtils::convertByteArrayToHexStringCompact(m_nonce);
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << " Nuki Nonce :" << NukiUtils::convertByteArrayToHexStringCompact(m_nonceNuki);
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << " Authorization ID:" << NukiUtils::convertByteArrayToHexStringCompact(m_authorizationIdRawData) << m_authorizationId;
|
||||||
|
|
||||||
|
// Prepare message to send to Nuki
|
||||||
|
QByteArray data;
|
||||||
|
data.append(m_authenticator);
|
||||||
|
data.append(m_authorizationIdRawData);
|
||||||
|
|
||||||
|
qCDebug(dcNuki()) << "Authenticator: Send authentication ID confirm to Nuki";
|
||||||
|
QByteArray message = NukiUtils::createRequestMessageForUnencrypted(NukiUtils::CommandAuthorizationIdConfirmation, data);
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << "-->" << NukiUtils::convertByteArrayToHexStringCompact(message);
|
||||||
|
m_pairingCharacteristic->writeCharacteristic(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NukiAuthenticator::saveData()
|
||||||
|
{
|
||||||
|
QSettings setting(NymeaSettings::settingsPath() + "/plugin-nuki.conf", QSettings::IniFormat);
|
||||||
|
setting.beginGroup(m_hostInfo.address().toString());
|
||||||
|
setting.setValue("privateKey", m_privateKey);
|
||||||
|
setting.setValue("publicKey", m_publicKey);
|
||||||
|
setting.setValue("publicKeyNuki", m_publicKeyNuki);
|
||||||
|
setting.setValue("authenticationIdRawData", m_authorizationIdRawData);
|
||||||
|
setting.setValue("authenticationId", m_authorizationId);
|
||||||
|
setting.setValue("uuid", m_uuid);
|
||||||
|
setting.endGroup();
|
||||||
|
|
||||||
|
qCDebug(dcNuki()) << "Authenticator: Settings saved to" << setting.fileName();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NukiAuthenticator::loadData()
|
||||||
|
{
|
||||||
|
QSettings setting(NymeaSettings::settingsPath() + "/plugin-nuki.conf", QSettings::IniFormat);
|
||||||
|
setting.beginGroup(m_hostInfo.address().toString());
|
||||||
|
m_privateKey = setting.value("privateKey", QByteArray()).toByteArray();
|
||||||
|
m_publicKey = setting.value("publicKey", QByteArray()).toByteArray();
|
||||||
|
m_publicKeyNuki = setting.value("publicKeyNuki", QByteArray()).toByteArray();
|
||||||
|
m_authorizationIdRawData = setting.value("authenticationIdRawData", QByteArray()).toByteArray();
|
||||||
|
m_authorizationId = static_cast<quint32>(setting.value("authenticationId", 0).toInt());
|
||||||
|
m_uuid = setting.value("uuid", QByteArray()).toByteArray();
|
||||||
|
setting.endGroup();
|
||||||
|
|
||||||
|
qCDebug(dcNuki()) << "Authenticator: Settings loaded from" << setting.fileName();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NukiAuthenticator::onPairingDataCharacteristicChanged(const QByteArray &value)
|
||||||
|
{
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << "Authenticator data received: <--" << NukiUtils::convertByteArrayToHexStringCompact(value);
|
||||||
|
|
||||||
|
// Process pairing characteristic data
|
||||||
|
QByteArray data = QByteArray(value);
|
||||||
|
QDataStream stream(&data, QIODevice::ReadOnly);
|
||||||
|
stream.setByteOrder(QDataStream::LittleEndian);
|
||||||
|
quint16 command;
|
||||||
|
stream >> command;
|
||||||
|
|
||||||
|
// Check if we are collecting data for multi part notification
|
||||||
|
if (m_currentReceivingCurrentCount > 0) {
|
||||||
|
command = m_currentReceivingCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << static_cast<NukiUtils::Command>(command);
|
||||||
|
|
||||||
|
switch (command) {
|
||||||
|
case NukiUtils::CommandErrorReport:
|
||||||
|
quint8 error;
|
||||||
|
quint16 commandIdentifier;
|
||||||
|
quint16 crc;
|
||||||
|
stream >> error >> commandIdentifier >> crc;
|
||||||
|
if (!NukiUtils::validateMessageCrc(data)) {
|
||||||
|
qCWarning(dcNuki()) << "Invalid message";
|
||||||
|
// FIXME: check what to do if crc is invalid
|
||||||
|
}
|
||||||
|
m_error = static_cast<NukiUtils::ErrorCode>(error);
|
||||||
|
qCWarning(dcNuki()) << "Authenticator: Error for command" << static_cast<NukiUtils::Command>(commandIdentifier) << m_error;
|
||||||
|
setState(AuthenticationStateError);
|
||||||
|
break;
|
||||||
|
case NukiUtils::CommandPublicKey:
|
||||||
|
m_currentReceivingCurrentCount++;
|
||||||
|
m_currentReceivingData.append(value);
|
||||||
|
if (m_currentReceivingCurrentCount == m_currentReceivingExpectedCount) {
|
||||||
|
if (!NukiUtils::validateMessageCrc(m_currentReceivingData)) {
|
||||||
|
qCWarning(dcNuki()) << "Invalid CRC CCITT value for public key message.";
|
||||||
|
// FIXME: check what to do if crc is invalid
|
||||||
|
}
|
||||||
|
|
||||||
|
qCDebug(dcNuki()) << "Authenticator: Nuki public key message received" << (m_debug ? NukiUtils::convertByteArrayToHexStringCompact(m_currentReceivingData) : "");
|
||||||
|
m_publicKeyNuki = m_currentReceivingData.mid(2, 32);
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << "Authenticator: --> Nuki public key:" << NukiUtils::convertByteArrayToHexStringCompact(m_publicKeyNuki);
|
||||||
|
|
||||||
|
setState(AuthenticationStateGenerateKeyPair);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NukiUtils::CommandChallenge:
|
||||||
|
m_currentReceivingCurrentCount++;
|
||||||
|
m_currentReceivingData.append(value);
|
||||||
|
if (m_currentReceivingCurrentCount == m_currentReceivingExpectedCount) {
|
||||||
|
qCDebug(dcNuki()) << "Authenticator: Nuki challenge message received" << (m_debug ? NukiUtils::convertByteArrayToHexStringCompact(m_currentReceivingData) : "");
|
||||||
|
if (!NukiUtils::validateMessageCrc(m_currentReceivingData)) {
|
||||||
|
qCWarning(dcNuki()) << "Invalid CRC CCITT value for challenge message.";
|
||||||
|
// FIXME: check what to do if crc is invalid
|
||||||
|
}
|
||||||
|
|
||||||
|
m_nonceNuki = m_currentReceivingData.mid(2, 32);
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << "Authenticator: --> Nuki nonce:" << NukiUtils::convertByteArrayToHexStringCompact(m_nonceNuki);
|
||||||
|
// Check if this was from the first challenge read or the second
|
||||||
|
if (m_state == AuthenticationStateReadChallenge) {
|
||||||
|
setState(AuthenticationStateAutorization);
|
||||||
|
} else if (m_state == AuthenticationStateReadSecondChallenge) {
|
||||||
|
setState(AuthenticationStateAuthenticateData);
|
||||||
|
} else {
|
||||||
|
qCWarning(dcNuki()) << "Received a challenge without expecting one.";
|
||||||
|
setState(AuthenticationStateError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NukiUtils::CommandAuthorizationId:
|
||||||
|
m_currentReceivingCurrentCount++;
|
||||||
|
m_currentReceivingData.append(value);
|
||||||
|
if (m_currentReceivingCurrentCount == m_currentReceivingExpectedCount) {
|
||||||
|
qCDebug(dcNuki()) << "Authenticator: Nuki authorization ID message received" << (m_debug ? NukiUtils::convertByteArrayToHexStringCompact(m_currentReceivingData) : "");
|
||||||
|
|
||||||
|
if (!NukiUtils::validateMessageCrc(m_currentReceivingData)) {
|
||||||
|
qCWarning(dcNuki()) << "Invalid CRC CCITT value for challenge message.";
|
||||||
|
// FIXME: check what to do if crc is invalid
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse data
|
||||||
|
QByteArray message = m_currentReceivingData.mid(2, m_currentReceivingData.count() - 4);
|
||||||
|
QByteArray authenticator = message.left(32);
|
||||||
|
Q_ASSERT_X(authenticator.count() == 32, "data length", "Nuki nonce has not the correct length.");
|
||||||
|
|
||||||
|
// Read authorization ID
|
||||||
|
m_authorizationIdRawData = message.mid(32, 4);
|
||||||
|
QDataStream stream(&m_authorizationIdRawData, QIODevice::ReadOnly);
|
||||||
|
stream.setByteOrder(QDataStream::LittleEndian);
|
||||||
|
stream >> m_authorizationId;
|
||||||
|
|
||||||
|
m_uuid = message.mid(36, 16);
|
||||||
|
Q_ASSERT_X(m_uuid.count() == 16, "data length", "UUIS 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.");
|
||||||
|
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << " Full message :" << NukiUtils::convertByteArrayToHexStringCompact(message);
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << " Authenticator :" << NukiUtils::convertByteArrayToHexStringCompact(authenticator);
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << " Authorization ID:" << NukiUtils::convertByteArrayToHexStringCompact(m_authorizationIdRawData) << m_authorizationId;
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << " UUID data :" << NukiUtils::convertByteArrayToHexStringCompact(m_uuid);
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << " Nuki nonce :" << NukiUtils::convertByteArrayToHexStringCompact(m_nonceNuki);
|
||||||
|
|
||||||
|
setState(AuthenticationStateAuthorizationIdConfirm);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NukiUtils::CommandStatus: {
|
||||||
|
quint8 status;
|
||||||
|
stream >> status;
|
||||||
|
|
||||||
|
if (!NukiUtils::validateMessageCrc(data)) {
|
||||||
|
qCWarning(dcNuki()) << "Invalid message";
|
||||||
|
// FIXME: check what to do if crc is invalid
|
||||||
|
}
|
||||||
|
|
||||||
|
NukiUtils::StatusCode statusCode = static_cast<NukiUtils::StatusCode>(status);
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << statusCode;
|
||||||
|
switch (statusCode) {
|
||||||
|
case NukiUtils::StatusCodeAccepted:
|
||||||
|
qCWarning(dcNuki()) << "The command was accepted, but not completed.";
|
||||||
|
setState(AuthenticationStateError);
|
||||||
|
break;
|
||||||
|
case NukiUtils::StatusCodeCompeted:
|
||||||
|
qCDebug(dcNuki()) << "Nuki authentication process finished successfully!";
|
||||||
|
saveData();
|
||||||
|
setState(AuthenticationStateAuthenticated);
|
||||||
|
emit authenticationProcessFinished(true);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
qCWarning(dcNuki()) << "Authenticator: Unhandled command identifier for parining charateristic" << NukiUtils::convertUint16ToHexString(command);
|
||||||
|
resetExpectedData();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
132
nuki/nukiauthenticator.h
Normal file
132
nuki/nukiauthenticator.h
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
* *
|
||||||
|
* Copyright (C) 2018 Simon Stürz <simon.stuerz@guh.io> *
|
||||||
|
* *
|
||||||
|
* This file is part of guh. *
|
||||||
|
* *
|
||||||
|
* This library is free software; you can redistribute it and/or *
|
||||||
|
* modify it under the terms of the GNU Lesser General Public *
|
||||||
|
* License as published by the Free Software Foundation; either *
|
||||||
|
* version 2.1 of the License, or (at your option) any later version. *
|
||||||
|
* *
|
||||||
|
* This library is distributed in the hope that it will be useful, *
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
||||||
|
* Lesser General Public License for more details. *
|
||||||
|
* *
|
||||||
|
* You should have received a copy of the GNU Lesser General Public *
|
||||||
|
* License along with this library; If not, see *
|
||||||
|
* <http://www.gnu.org/licenses/>. *
|
||||||
|
* *
|
||||||
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
|
#ifndef NUKIAUTHENTICATOR_H
|
||||||
|
#define NUKIAUTHENTICATOR_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QBluetoothHostInfo>
|
||||||
|
|
||||||
|
#include "nukiutils.h"
|
||||||
|
#include "bluez/bluetoothgattcharacteristic.h"
|
||||||
|
|
||||||
|
class NukiAuthenticator : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
enum AuthenticationState {
|
||||||
|
AuthenticationStateUnauthenticated,
|
||||||
|
AuthenticationStateAuthenticated,
|
||||||
|
AuthenticationStateRequestPublicKey,
|
||||||
|
AuthenticationStateGenerateKeyPair,
|
||||||
|
AuthenticationStateSendPublicKey,
|
||||||
|
AuthenticationStateReadChallenge,
|
||||||
|
AuthenticationStateAutorization,
|
||||||
|
AuthenticationStateReadSecondChallenge,
|
||||||
|
AuthenticationStateAuthenticateData,
|
||||||
|
AuthenticationStateAuthorizationId,
|
||||||
|
AuthenticationStateAuthorizationIdConfirm,
|
||||||
|
AuthenticationStateStatus,
|
||||||
|
AuthenticationStateError
|
||||||
|
};
|
||||||
|
Q_ENUM(AuthenticationState)
|
||||||
|
|
||||||
|
explicit NukiAuthenticator(const QBluetoothHostInfo &hostInfo, BluetoothGattCharacteristic *pairingCharacteristic, QObject *parent = nullptr);
|
||||||
|
|
||||||
|
NukiUtils::ErrorCode error() const;
|
||||||
|
AuthenticationState state() const;
|
||||||
|
|
||||||
|
// Returns true if authentication end encryption data are available
|
||||||
|
bool isValid() const;
|
||||||
|
|
||||||
|
void clearSettings();
|
||||||
|
void startAuthenticationProcess();
|
||||||
|
|
||||||
|
quint32 authorizationId() const;
|
||||||
|
QByteArray authorizationIdRawData() const;
|
||||||
|
|
||||||
|
QByteArray encryptData(const QByteArray &data, const QByteArray &nonce);
|
||||||
|
QByteArray decryptData(const QByteArray &data, const QByteArray &nonce);
|
||||||
|
|
||||||
|
// Generate 32 byte nonce data
|
||||||
|
QByteArray generateNonce(const int &length = 32) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QBluetoothHostInfo m_hostInfo;
|
||||||
|
BluetoothGattCharacteristic *m_pairingCharacteristic = nullptr;
|
||||||
|
AuthenticationState m_state = AuthenticationStateUnauthenticated;
|
||||||
|
NukiUtils::ErrorCode m_error = NukiUtils::ErrorCodeNoError;
|
||||||
|
|
||||||
|
// For handling splited notifications
|
||||||
|
NukiUtils::Command m_currentReceivingCommand = NukiUtils::CommandRequestData;
|
||||||
|
QByteArray m_currentReceivingData;
|
||||||
|
int m_currentReceivingExpectedCount = 0;
|
||||||
|
int m_currentReceivingCurrentCount = 0;
|
||||||
|
|
||||||
|
// For development debugging
|
||||||
|
bool m_debug = false;
|
||||||
|
|
||||||
|
// Local data
|
||||||
|
QByteArray m_privateKey;
|
||||||
|
QByteArray m_publicKey;
|
||||||
|
QByteArray m_sharedKey;
|
||||||
|
QByteArray m_authenticator;
|
||||||
|
QByteArray m_nonce;
|
||||||
|
QByteArray m_uuid;
|
||||||
|
QByteArray m_authorizationIdRawData;
|
||||||
|
quint32 m_authorizationId = 0;
|
||||||
|
|
||||||
|
// Nuki data
|
||||||
|
QByteArray m_publicKeyNuki;
|
||||||
|
QByteArray m_nonceNuki;
|
||||||
|
|
||||||
|
// State machine
|
||||||
|
void setState(AuthenticationState state);
|
||||||
|
void resetExpectedData(NukiUtils::Command command = NukiUtils::CommandRequestData, int expectedCount = 1);
|
||||||
|
|
||||||
|
// Helper methods
|
||||||
|
bool createAuthenticator(const QByteArray content);
|
||||||
|
|
||||||
|
// State action methods
|
||||||
|
void requestPublicKey();
|
||||||
|
void sendPublicKey();
|
||||||
|
void generateKeyPair();
|
||||||
|
void sendAuthorizationAuthenticator();
|
||||||
|
void sendAuthenticateData();
|
||||||
|
void sendAuthoizationIdConfirm();
|
||||||
|
|
||||||
|
// Storage
|
||||||
|
void saveData();
|
||||||
|
void loadData();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void errorOccured(NukiUtils::ErrorCode error);
|
||||||
|
void stateChanged(AuthenticationState state);
|
||||||
|
void authenticationProcessFinished(bool success);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onPairingDataCharacteristicChanged(const QByteArray &value);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // NUKIAUTHENTICATOR_H
|
||||||
524
nuki/nukicontroller.cpp
Normal file
524
nuki/nukicontroller.cpp
Normal file
@ -0,0 +1,524 @@
|
|||||||
|
#include "nukicontroller.h"
|
||||||
|
#include "extern-plugininfo.h"
|
||||||
|
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QDataStream>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include "sodium.h"
|
||||||
|
}
|
||||||
|
|
||||||
|
NukiController::NukiController(NukiAuthenticator *nukiAuthenticator, BluetoothGattCharacteristic *userDataCharacteristic, QObject *parent) :
|
||||||
|
QObject(parent),
|
||||||
|
m_nukiAuthenticator(nukiAuthenticator),
|
||||||
|
m_userDataCharacteristic(userDataCharacteristic)
|
||||||
|
{
|
||||||
|
#ifdef QT_DEBUG
|
||||||
|
// Enable full debug messages containing sensible data for debug builds
|
||||||
|
m_debug = true;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
connect(m_userDataCharacteristic, &BluetoothGattCharacteristic::valueChanged, this, &NukiController::onUserDataCharacteristicChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
NukiUtils::NukiState NukiController::nukiState() const
|
||||||
|
{
|
||||||
|
return m_nukiState;
|
||||||
|
}
|
||||||
|
|
||||||
|
NukiUtils::LockState NukiController::nukiLockState() const
|
||||||
|
{
|
||||||
|
return m_nukiLockState;
|
||||||
|
}
|
||||||
|
|
||||||
|
NukiUtils::LockTrigger NukiController::nukiLockTrigger() const
|
||||||
|
{
|
||||||
|
return m_nukiLockTrigger;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NukiController::batteryCritical() const
|
||||||
|
{
|
||||||
|
return m_batteryCritical;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NukiController::readLockState()
|
||||||
|
{
|
||||||
|
if (m_state != NukiControllerStateIdle) {
|
||||||
|
// TODO: maybe queue commands
|
||||||
|
qCWarning(dcNuki()) << "Controller: Could not read lock state, Nuki is currenty busy";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_nukiAuthenticator->isValid()) {
|
||||||
|
qCWarning(dcNuki()) << "Invalid authenticator. Please authenticate the device first.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(NukiControllerStateReadingLockStates);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NukiController::lock()
|
||||||
|
{
|
||||||
|
if (m_state != NukiControllerStateIdle) {
|
||||||
|
// TODO: maybe queue commands
|
||||||
|
qCWarning(dcNuki()) << "Controller: Could not lock, Nuki is currenty busy";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_nukiAuthenticator->isValid()) {
|
||||||
|
qCWarning(dcNuki()) << "Invalid authenticator. Please authenticate the device first.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(NukiControllerStateLockActionRequestChallange);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NukiController::unlock()
|
||||||
|
{
|
||||||
|
if (m_state != NukiControllerStateIdle) {
|
||||||
|
// TODO: maybe queue commands
|
||||||
|
qCWarning(dcNuki()) << "Controller: Could not lock, Nuki is currenty busy";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_nukiAuthenticator->isValid()) {
|
||||||
|
qCWarning(dcNuki()) << "Invalid authenticator. Please authenticate the device first.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(NukiControllerStateUnlockActionRequestChallange);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NukiController::unlatch()
|
||||||
|
{
|
||||||
|
if (m_state != NukiControllerStateIdle) {
|
||||||
|
// TODO: maybe queue commands
|
||||||
|
qCWarning(dcNuki()) << "Controller: Could not unlatch, Nuki is currenty busy";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_nukiAuthenticator->isValid()) {
|
||||||
|
qCWarning(dcNuki()) << "Invalid authenticator. Please authenticate the device first.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(NukiControllerStateUnlatchActionRequestChallange);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NukiController::setState(NukiController::NukiControllerState state)
|
||||||
|
{
|
||||||
|
if (m_state == state)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_state = state;
|
||||||
|
|
||||||
|
qCDebug(dcNuki()) << m_state;
|
||||||
|
|
||||||
|
switch (m_state) {
|
||||||
|
case NukiControllerStateIdle:
|
||||||
|
break;
|
||||||
|
case NukiControllerStateReadingLockStates:
|
||||||
|
sendReadLockStateRequest();
|
||||||
|
break;
|
||||||
|
case NukiControllerStateLockActionRequestChallange:
|
||||||
|
sendRequestChallengeRequest();
|
||||||
|
break;
|
||||||
|
case NukiControllerStateLockActionExecute:
|
||||||
|
sendLockActionRequest(NukiUtils::LockActionLock);
|
||||||
|
setState(NukiControllerStateLockActionAccepted);
|
||||||
|
break;
|
||||||
|
case NukiControllerStateLockActionAccepted:
|
||||||
|
break;
|
||||||
|
case NukiControllerStateUnlockActionRequestChallange:
|
||||||
|
sendRequestChallengeRequest();
|
||||||
|
break;
|
||||||
|
case NukiControllerStateUnlockActionExecute:
|
||||||
|
sendLockActionRequest(NukiUtils::LockActionUnlock);
|
||||||
|
setState(NukiControllerStateUnlockActionAccepted);
|
||||||
|
break;
|
||||||
|
case NukiControllerStateUnlockActionAccepted:
|
||||||
|
break;
|
||||||
|
case NukiControllerStateUnlatchActionRequestChallange:
|
||||||
|
sendRequestChallengeRequest();
|
||||||
|
break;
|
||||||
|
case NukiControllerStateUnlatchActionExecute:
|
||||||
|
sendLockActionRequest(NukiUtils::LockActionUnlatch);
|
||||||
|
setState(NukiControllerStateUnlatchActionAccepted);
|
||||||
|
break;
|
||||||
|
case NukiControllerStateUnlatchActionAccepted:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit stateChanged(m_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NukiController::resetMessageBuffer()
|
||||||
|
{
|
||||||
|
m_messageBuffer.clear();
|
||||||
|
m_messageBufferNonce.clear();
|
||||||
|
m_messageBufferIdentifier = 0;
|
||||||
|
m_messageBufferLength = 0;
|
||||||
|
m_messageBufferCounter = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NukiController::processNukiStatesData(const QByteArray &data)
|
||||||
|
{
|
||||||
|
quint8 nukiState = 0;
|
||||||
|
quint8 nukiLockState = 0;
|
||||||
|
quint8 nukiLockTrigger = 0;
|
||||||
|
quint16 year = 1970;
|
||||||
|
quint8 month = 1;
|
||||||
|
quint8 day = 1;
|
||||||
|
quint8 hour = 0;
|
||||||
|
quint8 minute = 0;
|
||||||
|
quint8 second = 0;
|
||||||
|
qint16 utcOffset = 0;
|
||||||
|
quint8 batteryCritical = 0;
|
||||||
|
|
||||||
|
QByteArray payload = data;
|
||||||
|
QDataStream stream(&payload, QIODevice::ReadOnly);
|
||||||
|
stream.setByteOrder(QDataStream::LittleEndian);
|
||||||
|
stream >> nukiState >> nukiLockState >> nukiLockTrigger >> year >> month >> day >> hour >> minute >> second >> utcOffset >> batteryCritical;
|
||||||
|
|
||||||
|
m_nukiState = static_cast<NukiUtils::NukiState>(nukiState);
|
||||||
|
m_nukiLockState = static_cast<NukiUtils::LockState>(nukiLockState);
|
||||||
|
m_nukiLockTrigger = static_cast<NukiUtils::LockTrigger>(nukiLockTrigger);
|
||||||
|
m_nukiDateTime = QDateTime(QDate(year, month, day), QTime(hour, minute, second));
|
||||||
|
m_nukiUtcOffset = utcOffset;
|
||||||
|
m_batteryCritical = (batteryCritical == 0 ? false : true);
|
||||||
|
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << "--------------------:" << m_state;
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << " Nuki state :" << m_nukiState;
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << " Nuki lock state :" << m_nukiLockState;
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << " Lock trigger :" << m_nukiLockTrigger;
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << " Date time :" << m_nukiDateTime.toString("dd.MM.yyyy hh:mm:ss") << "UTC offset:" << m_nukiUtcOffset;
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << " Battery critical:" << m_batteryCritical;
|
||||||
|
|
||||||
|
qCDebug(dcNuki()) << "Nuki states refreshed.";
|
||||||
|
|
||||||
|
emit nukiStatesChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NukiController::processNukiErrorReport(const QByteArray &data)
|
||||||
|
{
|
||||||
|
qint8 errorCode;
|
||||||
|
quint16 nukiCommand;
|
||||||
|
|
||||||
|
QByteArray payload = data;
|
||||||
|
QDataStream stream(&payload, QIODevice::ReadOnly);
|
||||||
|
stream.setByteOrder(QDataStream::LittleEndian);
|
||||||
|
stream >> errorCode >> nukiCommand;
|
||||||
|
|
||||||
|
qCDebug(dcNuki()) << "Received error report" << static_cast<NukiUtils::ErrorCode>(errorCode) << static_cast<NukiUtils::Command>(nukiCommand);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NukiController::processUserDataNotification(const QByteArray nonce, quint32 authorizationIdentifier, const QByteArray &privateData)
|
||||||
|
{
|
||||||
|
QByteArray decryptedMessage = m_nukiAuthenticator->decryptData(privateData, nonce);
|
||||||
|
|
||||||
|
// Process decrypted data
|
||||||
|
if (!NukiUtils::validateMessageCrc(decryptedMessage)) {
|
||||||
|
qCWarning(dcNuki()) << "Controller: User notification data has invalid CRC CCITT value. Rejecting data.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have the unencrypted and valid PDATA, let's see what this is
|
||||||
|
quint32 decryptedAuthenticationId = NukiUtils::convertByteArrayToUint32BigEndian(decryptedMessage.left(4));
|
||||||
|
NukiUtils::Command command = static_cast<NukiUtils::Command>(NukiUtils::convertByteArrayToUint16BigEndian(decryptedMessage.mid(4, 2)));
|
||||||
|
QByteArray payload = decryptedMessage.mid(6, decryptedMessage.length() - 8);
|
||||||
|
|
||||||
|
qCDebug(dcNuki()) << "Controller: Processing notification" << command;
|
||||||
|
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << " Nonce :" << NukiUtils::convertByteArrayToHexStringCompact(nonce);
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << " Authorization ID:" << authorizationIdentifier;
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << " Encrypted data :" << NukiUtils::convertByteArrayToHexStringCompact(privateData) << privateData.length();
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << " Decrypted data :" << NukiUtils::convertByteArrayToHexStringCompact(decryptedMessage) << decryptedMessage.length();
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << " Command :" << command;
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << " Authorization ID:" << NukiUtils::convertByteArrayToHexStringCompact(decryptedMessage.left(4)) << decryptedAuthenticationId;
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << " Payload :" << NukiUtils::convertByteArrayToHexStringCompact(payload);
|
||||||
|
|
||||||
|
// Lets see if this was an expected
|
||||||
|
switch (m_state) {
|
||||||
|
case NukiControllerStateReadingLockStates:
|
||||||
|
// We are expecting the states notification
|
||||||
|
if (command == NukiUtils::CommandNukiStates) {
|
||||||
|
processNukiStatesData(payload);
|
||||||
|
emit readNukiStatesFinished(true);
|
||||||
|
setState(NukiControllerStateIdle);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NukiControllerStateLockActionRequestChallange:
|
||||||
|
// We are expecting callange message
|
||||||
|
if (command == NukiUtils::CommandChallenge) {
|
||||||
|
m_nukiNonce = payload;
|
||||||
|
setState(NukiControllerStateLockActionExecute);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NukiControllerStateLockActionAccepted:
|
||||||
|
// We are expecting the status
|
||||||
|
if (command == NukiUtils::CommandStatus) {
|
||||||
|
NukiUtils::StatusCode statusCode = static_cast<NukiUtils::StatusCode>((quint8)payload.at(0));
|
||||||
|
qCDebug(dcNuki()) << "Controller:" << statusCode;
|
||||||
|
|
||||||
|
switch (statusCode) {
|
||||||
|
case NukiUtils::StatusCodeAccepted:
|
||||||
|
// Lets wait for completed
|
||||||
|
break;
|
||||||
|
case NukiUtils::StatusCodeCompeted:
|
||||||
|
emit lockFinished(true);
|
||||||
|
setState(NukiControllerStateIdle);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NukiControllerStateUnlockActionRequestChallange:
|
||||||
|
// We are expecting callenge message
|
||||||
|
if (command == NukiUtils::CommandChallenge) {
|
||||||
|
m_nukiNonce = payload;
|
||||||
|
setState(NukiControllerStateUnlockActionExecute);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NukiControllerStateUnlockActionAccepted:
|
||||||
|
// We are expecting the status
|
||||||
|
if (command == NukiUtils::CommandStatus) {
|
||||||
|
NukiUtils::StatusCode statusCode = static_cast<NukiUtils::StatusCode>((quint8)payload.at(0));
|
||||||
|
qCDebug(dcNuki()) << "Controller:" << statusCode;
|
||||||
|
|
||||||
|
switch (statusCode) {
|
||||||
|
case NukiUtils::StatusCodeAccepted:
|
||||||
|
// Lets wait for completed
|
||||||
|
break;
|
||||||
|
case NukiUtils::StatusCodeCompeted:
|
||||||
|
emit unlockFinished(true);
|
||||||
|
setState(NukiControllerStateIdle);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NukiControllerStateUnlatchActionRequestChallange:
|
||||||
|
// We are expecting callenge message
|
||||||
|
if (command == NukiUtils::CommandChallenge) {
|
||||||
|
m_nukiNonce = payload;
|
||||||
|
setState(NukiControllerStateUnlatchActionExecute);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NukiControllerStateUnlatchActionAccepted:
|
||||||
|
// We are expecting the status
|
||||||
|
if (command == NukiUtils::CommandStatus) {
|
||||||
|
NukiUtils::StatusCode statusCode = static_cast<NukiUtils::StatusCode>((quint8)payload.at(0));
|
||||||
|
qCDebug(dcNuki()) << "Controller:" << statusCode;
|
||||||
|
|
||||||
|
switch (statusCode) {
|
||||||
|
case NukiUtils::StatusCodeAccepted:
|
||||||
|
// Lets wait for completed
|
||||||
|
break;
|
||||||
|
case NukiUtils::StatusCodeCompeted:
|
||||||
|
emit unlatchFinished(true);
|
||||||
|
setState(NukiControllerStateIdle);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Other notification
|
||||||
|
switch (command) {
|
||||||
|
case NukiUtils::CommandNukiStates:
|
||||||
|
processNukiStatesData(payload);
|
||||||
|
break;
|
||||||
|
case NukiUtils::CommandErrorReport:
|
||||||
|
processNukiErrorReport(payload);
|
||||||
|
break;
|
||||||
|
case NukiUtils::CommandStatus: {
|
||||||
|
NukiUtils::StatusCode statusCode = static_cast<NukiUtils::StatusCode>((quint8)payload.at(0));
|
||||||
|
qCDebug(dcNuki()) << "Controller:" << statusCode;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
qCWarning(dcNuki()) << "Controller: Received unhandled notification:" << command;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NukiController::sendReadLockStateRequest()
|
||||||
|
{
|
||||||
|
qCDebug(dcNuki()) << "Controller: Reading lock state";
|
||||||
|
|
||||||
|
// Create data for encryption
|
||||||
|
QByteArray payload;
|
||||||
|
QDataStream stream(&payload, QIODevice::WriteOnly);
|
||||||
|
stream.setByteOrder(QDataStream::LittleEndian);
|
||||||
|
stream << static_cast<quint16>(NukiUtils::CommandNukiStates);
|
||||||
|
|
||||||
|
// Create unencrypted PDATA
|
||||||
|
QByteArray unencryptedMessage = NukiUtils::createRequestMessageForUnencryptedForEncryption(m_nukiAuthenticator->authorizationId(), NukiUtils::CommandRequestData, payload);
|
||||||
|
|
||||||
|
// Encrypt PDATA
|
||||||
|
QByteArray nonce = m_nukiAuthenticator->generateNonce(crypto_box_NONCEBYTES);
|
||||||
|
QByteArray encryptedMessage = m_nukiAuthenticator->encryptData(unencryptedMessage, nonce);
|
||||||
|
|
||||||
|
// Create ADATA
|
||||||
|
QByteArray header;
|
||||||
|
header.append(nonce);
|
||||||
|
header.append(m_nukiAuthenticator->authorizationIdRawData());
|
||||||
|
header.append(NukiUtils::converUint16ToByteArrayLittleEndian(static_cast<quint16>(encryptedMessage.length())));
|
||||||
|
|
||||||
|
// Message ADATA + PDATA
|
||||||
|
QByteArray message;
|
||||||
|
message.append(header);
|
||||||
|
message.append(encryptedMessage);
|
||||||
|
|
||||||
|
// Send data
|
||||||
|
qCDebug(dcNuki()) << "Controller: Sending read lock states request";
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << " Nonce :" << NukiUtils::convertByteArrayToHexStringCompact(nonce);
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << " Header :" << NukiUtils::convertByteArrayToHexStringCompact(header);
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << "Controller: -->" << NukiUtils::convertByteArrayToHexStringCompact(message);
|
||||||
|
m_userDataCharacteristic->writeCharacteristic(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NukiController::sendRequestChallengeRequest()
|
||||||
|
{
|
||||||
|
qCDebug(dcNuki()) << "Controller: Request challenge";
|
||||||
|
|
||||||
|
// Create data for encryption
|
||||||
|
QByteArray payload;
|
||||||
|
QDataStream stream(&payload, QIODevice::WriteOnly);
|
||||||
|
stream.setByteOrder(QDataStream::LittleEndian);
|
||||||
|
stream << static_cast<quint16>(NukiUtils::CommandChallenge);
|
||||||
|
|
||||||
|
// Create unencrypted PDATA
|
||||||
|
QByteArray unencryptedMessage = NukiUtils::createRequestMessageForUnencryptedForEncryption(m_nukiAuthenticator->authorizationId(), NukiUtils::CommandRequestData, payload);
|
||||||
|
|
||||||
|
// Encrypt PDATA
|
||||||
|
QByteArray nonce = m_nukiAuthenticator->generateNonce(crypto_box_NONCEBYTES);
|
||||||
|
QByteArray encryptedMessage = m_nukiAuthenticator->encryptData(unencryptedMessage, nonce);
|
||||||
|
|
||||||
|
// Create ADATA
|
||||||
|
QByteArray header;
|
||||||
|
header.append(nonce);
|
||||||
|
header.append(m_nukiAuthenticator->authorizationIdRawData());
|
||||||
|
header.append(NukiUtils::converUint16ToByteArrayLittleEndian(static_cast<quint16>(encryptedMessage.length())));
|
||||||
|
|
||||||
|
// Message ADATA + PDATA
|
||||||
|
QByteArray message;
|
||||||
|
message.append(header);
|
||||||
|
message.append(encryptedMessage);
|
||||||
|
|
||||||
|
// Send data
|
||||||
|
qCDebug(dcNuki()) << "Controller: Sending challange request";
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << " Nonce :" << NukiUtils::convertByteArrayToHexStringCompact(nonce);
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << " Header :" << NukiUtils::convertByteArrayToHexStringCompact(header);
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << "Controller: -->" << NukiUtils::convertByteArrayToHexStringCompact(message);
|
||||||
|
m_userDataCharacteristic->writeCharacteristic(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NukiController::sendLockActionRequest(NukiUtils::LockAction lockAction, quint8 flag)
|
||||||
|
{
|
||||||
|
qCDebug(dcNuki()) << "Controller: Send lock request" << lockAction;
|
||||||
|
|
||||||
|
QByteArray nonce = m_nukiAuthenticator->generateNonce(crypto_box_NONCEBYTES);
|
||||||
|
|
||||||
|
// Create data for encryption
|
||||||
|
QByteArray payload;
|
||||||
|
QDataStream stream(&payload, QIODevice::WriteOnly);
|
||||||
|
stream.setByteOrder(QDataStream::LittleEndian);
|
||||||
|
stream << static_cast<quint8>(lockAction);
|
||||||
|
stream << static_cast<quint32>(m_nukiAuthenticator->authorizationId());
|
||||||
|
stream << flag;
|
||||||
|
|
||||||
|
for (int i = 0; i < m_nukiNonce.length(); i++) {
|
||||||
|
stream << static_cast<quint8>(m_nukiNonce.at(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create unencrypted PDATA
|
||||||
|
QByteArray unencryptedMessage = NukiUtils::createRequestMessageForUnencryptedForEncryption(m_nukiAuthenticator->authorizationId(), NukiUtils::CommandLockAction, payload);
|
||||||
|
|
||||||
|
// Encrypt PDATA
|
||||||
|
QByteArray encryptedMessage = m_nukiAuthenticator->encryptData(unencryptedMessage, nonce);
|
||||||
|
|
||||||
|
// Create ADATA
|
||||||
|
QByteArray header;
|
||||||
|
header.append(nonce);
|
||||||
|
header.append(m_nukiAuthenticator->authorizationIdRawData());
|
||||||
|
header.append(NukiUtils::converUint16ToByteArrayLittleEndian(static_cast<quint16>(encryptedMessage.length())));
|
||||||
|
|
||||||
|
// Message ADATA + PDATA
|
||||||
|
QByteArray message;
|
||||||
|
message.append(header);
|
||||||
|
message.append(encryptedMessage);
|
||||||
|
|
||||||
|
// Send data
|
||||||
|
qCDebug(dcNuki()) << "Controller: Sending lock request";
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << " Nonce :" << NukiUtils::convertByteArrayToHexStringCompact(nonce);
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << " Header :" << NukiUtils::convertByteArrayToHexStringCompact(header);
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << "Controller: -->" << NukiUtils::convertByteArrayToHexStringCompact(message);
|
||||||
|
|
||||||
|
m_userDataCharacteristic->writeCharacteristic(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NukiController::onUserDataCharacteristicChanged(const QByteArray &value)
|
||||||
|
{
|
||||||
|
if (m_debug) qCDebug(dcNuki()) << "Controller: Data received: <--" << NukiUtils::convertByteArrayToHexStringCompact(value);
|
||||||
|
|
||||||
|
if (m_messageBufferCounter <= 0) {
|
||||||
|
// New data arrived
|
||||||
|
m_messageBuffer.append(value);
|
||||||
|
m_messageBufferCounter++;
|
||||||
|
} else {
|
||||||
|
// We are currently collecting
|
||||||
|
m_messageBuffer.append(value);
|
||||||
|
m_messageBufferCounter++;
|
||||||
|
|
||||||
|
// In the second buffer message is the complete message length
|
||||||
|
if (m_messageBufferCounter == 2) {
|
||||||
|
if (m_messageBuffer.count() < 30) {
|
||||||
|
qCWarning(dcNuki()) << "Controller: Cannot understand message. Rejecting.";
|
||||||
|
resetMessageBuffer();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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_messageBufferNonce = m_messageBufferAData.left(24);
|
||||||
|
QByteArray messageInformation = m_messageBufferAData.right(6);
|
||||||
|
|
||||||
|
QDataStream stream(&messageInformation, QIODevice::ReadOnly);
|
||||||
|
stream.setByteOrder(QDataStream::LittleEndian);
|
||||||
|
stream >> m_messageBufferIdentifier >> m_messageBufferLength;
|
||||||
|
if (m_messageBufferPData.count() == m_messageBufferLength) {
|
||||||
|
processUserDataNotification(m_messageBufferNonce, m_messageBufferIdentifier, m_messageBufferPData);
|
||||||
|
resetMessageBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// We already know the message length and are still collecting p data
|
||||||
|
m_messageBufferPData.append(value);
|
||||||
|
if (m_messageBufferPData.count() == m_messageBufferLength) {
|
||||||
|
// Message finished
|
||||||
|
processUserDataNotification(m_messageBufferNonce, m_messageBufferIdentifier, m_messageBufferPData);
|
||||||
|
resetMessageBuffer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
113
nuki/nukicontroller.h
Normal file
113
nuki/nukicontroller.h
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
#ifndef NUKICONTROLLER_H
|
||||||
|
#define NUKICONTROLLER_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QBluetoothAddress>
|
||||||
|
|
||||||
|
#include "nukiutils.h"
|
||||||
|
#include "nukiauthenticator.h"
|
||||||
|
#include "bluez/bluetoothgattcharacteristic.h"
|
||||||
|
|
||||||
|
class NukiController : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
enum NukiControllerState {
|
||||||
|
NukiControllerStateIdle,
|
||||||
|
|
||||||
|
// Read state
|
||||||
|
NukiControllerStateReadingLockStates,
|
||||||
|
|
||||||
|
// Lock action
|
||||||
|
NukiControllerStateLockActionRequestChallange,
|
||||||
|
NukiControllerStateLockActionExecute,
|
||||||
|
NukiControllerStateLockActionAccepted,
|
||||||
|
|
||||||
|
// Unlock action
|
||||||
|
NukiControllerStateUnlockActionRequestChallange,
|
||||||
|
NukiControllerStateUnlockActionExecute,
|
||||||
|
NukiControllerStateUnlockActionAccepted,
|
||||||
|
|
||||||
|
// Unlatch action
|
||||||
|
NukiControllerStateUnlatchActionRequestChallange,
|
||||||
|
NukiControllerStateUnlatchActionExecute,
|
||||||
|
NukiControllerStateUnlatchActionAccepted,
|
||||||
|
|
||||||
|
NukiControllerStateError
|
||||||
|
};
|
||||||
|
Q_ENUM(NukiControllerState)
|
||||||
|
|
||||||
|
explicit NukiController(NukiAuthenticator *nukiAuthenticator, BluetoothGattCharacteristic *userDataCharacteristic, QObject *parent = nullptr);
|
||||||
|
|
||||||
|
// States
|
||||||
|
NukiUtils::NukiState nukiState() const;
|
||||||
|
NukiUtils::LockState nukiLockState() const;
|
||||||
|
NukiUtils::LockTrigger nukiLockTrigger() const;
|
||||||
|
QDateTime nukiDateTime() const;
|
||||||
|
int nukiUtcOffset() const;
|
||||||
|
bool batteryCritical() const;
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
bool readLockState();
|
||||||
|
bool lock();
|
||||||
|
bool unlock();
|
||||||
|
bool unlatch();
|
||||||
|
|
||||||
|
private:
|
||||||
|
NukiAuthenticator *m_nukiAuthenticator = nullptr;
|
||||||
|
BluetoothGattCharacteristic *m_userDataCharacteristic = nullptr;
|
||||||
|
|
||||||
|
NukiControllerState m_state = NukiControllerStateIdle;
|
||||||
|
|
||||||
|
// Notification parsing helper
|
||||||
|
QByteArray m_messageBuffer;
|
||||||
|
QByteArray m_messageBufferAData;
|
||||||
|
QByteArray m_messageBufferPData;
|
||||||
|
QByteArray m_messageBufferNonce;
|
||||||
|
quint32 m_messageBufferIdentifier = 0;
|
||||||
|
quint16 m_messageBufferLength = 0;
|
||||||
|
int m_messageBufferCounter = 0;
|
||||||
|
|
||||||
|
// For development debugging
|
||||||
|
bool m_debug = false;
|
||||||
|
|
||||||
|
// Properties
|
||||||
|
NukiUtils::NukiState m_nukiState = NukiUtils::NukiStateUninitialized;
|
||||||
|
NukiUtils::LockState m_nukiLockState = NukiUtils::LockStateUndefined;
|
||||||
|
NukiUtils::LockTrigger m_nukiLockTrigger = NukiUtils::LockTriggerBluetooth;
|
||||||
|
QDateTime m_nukiDateTime;
|
||||||
|
int m_nukiUtcOffset = 0;
|
||||||
|
bool m_batteryCritical = false;
|
||||||
|
|
||||||
|
QByteArray m_nukiNonce;
|
||||||
|
|
||||||
|
// State machine helpers
|
||||||
|
void setState(NukiControllerState state);
|
||||||
|
void resetExpectedData(int expectedCount = 1);
|
||||||
|
void resetMessageBuffer();
|
||||||
|
|
||||||
|
// Data processors
|
||||||
|
void processNukiStatesData(const QByteArray &data);
|
||||||
|
void processNukiErrorReport(const QByteArray &data);
|
||||||
|
void processUserDataNotification(const QByteArray nonce, quint32 authorizationIdentifier, const QByteArray &privateData);
|
||||||
|
|
||||||
|
// State action methods
|
||||||
|
void sendReadLockStateRequest();
|
||||||
|
void sendRequestChallengeRequest();
|
||||||
|
void sendLockActionRequest(NukiUtils::LockAction lockAction, quint8 flag = 0);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void stateChanged(NukiControllerState state);
|
||||||
|
void readNukiStatesFinished(bool success);
|
||||||
|
void lockFinished(bool success);
|
||||||
|
void unlockFinished(bool success);
|
||||||
|
void unlatchFinished(bool success);
|
||||||
|
void nukiStatesChanged();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onUserDataCharacteristicChanged(const QByteArray &value);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // NUKICONTROLLER_H
|
||||||
171
nuki/nukiutils.cpp
Normal file
171
nuki/nukiutils.cpp
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
#include "nukiutils.h"
|
||||||
|
#include "extern-plugininfo.h"
|
||||||
|
|
||||||
|
#include <QtEndian>
|
||||||
|
#include <QDataStream>
|
||||||
|
|
||||||
|
QString NukiUtils::convertByteToHexString(const quint8 &byte)
|
||||||
|
{
|
||||||
|
QString hexString(QStringLiteral("0x%1"));
|
||||||
|
hexString = hexString.arg(byte, 2, 16, QLatin1Char('0'));
|
||||||
|
return hexString.toStdString().data();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString NukiUtils::convertByteArrayToHexString(const QByteArray &byteArray)
|
||||||
|
{
|
||||||
|
QString hexString;
|
||||||
|
for (int i = 0; i < byteArray.count(); i++) {
|
||||||
|
hexString.append(convertByteToHexString(static_cast<quint8>(byteArray.at(i))));
|
||||||
|
if (i != byteArray.count() - 1) {
|
||||||
|
hexString.append(" ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hexString.toStdString().data();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString NukiUtils::convertByteArrayToHexStringCompact(const QByteArray &byteArray)
|
||||||
|
{
|
||||||
|
QString hexString;
|
||||||
|
for (int i = 0; i < byteArray.length(); i++) {
|
||||||
|
hexString.append(QString("%1").arg(static_cast<unsigned char>(byteArray.at(i)), 2, 16, QLatin1Char('0')));
|
||||||
|
}
|
||||||
|
|
||||||
|
return hexString;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString NukiUtils::convertUint16ToHexString(const quint16 &value)
|
||||||
|
{
|
||||||
|
QByteArray data;
|
||||||
|
QDataStream stream(&data, QIODevice::WriteOnly);
|
||||||
|
stream << value;
|
||||||
|
|
||||||
|
return QString("0x%1").arg(convertByteArrayToHexString(data).remove(" ").remove("0x"));
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray NukiUtils::converUint32ToByteArrayLittleEndian(const quint32 &value)
|
||||||
|
{
|
||||||
|
QByteArray data;
|
||||||
|
QDataStream stream(&data, QIODevice::WriteOnly);
|
||||||
|
stream.setByteOrder(QDataStream::LittleEndian);
|
||||||
|
stream << value;
|
||||||
|
Q_ASSERT_X(data.length() == 4, "data converting", "Could not convert quint32 value to byte array (little endian)");
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray NukiUtils::converUint16ToByteArrayLittleEndian(const quint16 &value)
|
||||||
|
{
|
||||||
|
QByteArray data;
|
||||||
|
QDataStream stream(&data, QIODevice::WriteOnly);
|
||||||
|
stream.setByteOrder(QDataStream::LittleEndian);
|
||||||
|
stream << value;
|
||||||
|
Q_ASSERT_X(data.length() == 2, "data converting", "Could not convert quint16 value to byte array (little endian)");
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
quint16 NukiUtils::convertByteArrayToUint16BigEndian(const QByteArray &littleEndianByteArray)
|
||||||
|
{
|
||||||
|
Q_ASSERT_X(littleEndianByteArray.length() == 2, "data converting", "Could not convert byte array (little endian) to quint16 value. Invalid size of byte array.");
|
||||||
|
|
||||||
|
quint16 value = 0;
|
||||||
|
QByteArray data(littleEndianByteArray);
|
||||||
|
QDataStream stream(&data, QIODevice::ReadOnly);
|
||||||
|
stream.setByteOrder(QDataStream::LittleEndian);
|
||||||
|
stream >> value;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
quint32 NukiUtils::convertByteArrayToUint32BigEndian(const QByteArray &littleEndianByteArray)
|
||||||
|
{
|
||||||
|
Q_ASSERT_X(littleEndianByteArray.length() == 4, "data converting", "Could not convert byte array (little endian) to quint32 value. Invalid size of byte array.");
|
||||||
|
|
||||||
|
quint32 value = 0;
|
||||||
|
QByteArray data(littleEndianByteArray);
|
||||||
|
QDataStream stream(&data, QIODevice::ReadOnly);
|
||||||
|
stream.setByteOrder(QDataStream::LittleEndian);
|
||||||
|
stream >> value;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
quint16 NukiUtils::calculateCrc(const QByteArray &data)
|
||||||
|
{
|
||||||
|
quint16 crcValue = 0xffff;
|
||||||
|
quint16 polynom = 0x1021;
|
||||||
|
|
||||||
|
for (int byte = 0; byte < data.length(); ++byte) {
|
||||||
|
crcValue ^= (static_cast<quint8>(data.at(byte)) << 8);
|
||||||
|
for (quint8 bit = 8; bit > 0; --bit) {
|
||||||
|
if (crcValue & 0x8000) {
|
||||||
|
crcValue = (crcValue << 1) ^ polynom;
|
||||||
|
} else {
|
||||||
|
crcValue = (crcValue << 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return crcValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NukiUtils::validateMessageCrc(const QByteArray &message)
|
||||||
|
{
|
||||||
|
quint16 crcValue = 0;
|
||||||
|
QByteArray crcValueRaw = message.right(2);
|
||||||
|
QDataStream stream(&crcValueRaw, QIODevice::ReadOnly);
|
||||||
|
stream.setByteOrder(QDataStream::LittleEndian);
|
||||||
|
stream >> crcValue;
|
||||||
|
|
||||||
|
QByteArray content = message.left(message.count() - 2);
|
||||||
|
|
||||||
|
quint16 calculatedCrcValue = calculateCrc(content);
|
||||||
|
if (crcValue != calculatedCrcValue) {
|
||||||
|
qCWarning(dcNuki()) << "CRC CCITT validation failed:" << crcValue << "!=" << calculatedCrcValue;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray NukiUtils::createRequestMessageForUnencrypted(NukiUtils::Command command, const QByteArray &payload)
|
||||||
|
{
|
||||||
|
/* Note: build a message for unencrypted communication with paring service
|
||||||
|
* 2 Bytes: command identifier (LittleEndian)
|
||||||
|
* n Bytes: pyload (raw bytes)
|
||||||
|
* 2 Bytes: crc (LittleEndian)
|
||||||
|
*/
|
||||||
|
QByteArray message;
|
||||||
|
QDataStream stream(&message, QIODevice::WriteOnly);
|
||||||
|
stream.setByteOrder(QDataStream::LittleEndian);
|
||||||
|
stream << static_cast<quint16>(command);
|
||||||
|
|
||||||
|
// Write payload
|
||||||
|
for (int i = 0; i < payload.length(); i++) {
|
||||||
|
stream << static_cast<quint8>(payload.at(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
stream << NukiUtils::calculateCrc(message);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray NukiUtils::createRequestMessageForUnencryptedForEncryption(quint32 authenticationId, NukiUtils::Command command, const QByteArray &payload)
|
||||||
|
{
|
||||||
|
/* Note: build a message for encrypted communication with key turner service. This represents the unencrypted PDATA
|
||||||
|
* 4 Bytes: authentication ID (LittleEndian)
|
||||||
|
* 2 Bytes: command identifier (LittleEndian)
|
||||||
|
* n Bytes: pyxload (raw bytes)
|
||||||
|
* 2 Bytes: crc (LittleEndian)
|
||||||
|
*/
|
||||||
|
|
||||||
|
QByteArray message;
|
||||||
|
QDataStream stream(&message, QIODevice::WriteOnly);
|
||||||
|
stream.setByteOrder(QDataStream::LittleEndian);
|
||||||
|
stream << authenticationId;
|
||||||
|
stream << static_cast<quint16>(command);
|
||||||
|
|
||||||
|
// Write payload
|
||||||
|
for (int i = 0; i < payload.length(); i++) {
|
||||||
|
stream << static_cast<quint8>(payload.at(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
stream << NukiUtils::calculateCrc(message);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
167
nuki/nukiutils.h
Normal file
167
nuki/nukiutils.h
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
#ifndef NUKIUTILS_H
|
||||||
|
#define NUKIUTILS_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QString>
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QBitArray>
|
||||||
|
|
||||||
|
class NukiUtils
|
||||||
|
{
|
||||||
|
Q_GADGET
|
||||||
|
public:
|
||||||
|
enum StatusCode {
|
||||||
|
StatusCodeCompeted = 0x00,
|
||||||
|
StatusCodeAccepted = 0x01
|
||||||
|
};
|
||||||
|
Q_ENUM(StatusCode)
|
||||||
|
|
||||||
|
|
||||||
|
enum ErrorCode {
|
||||||
|
ErrorCodeNoError = 0x00,
|
||||||
|
|
||||||
|
// Pairing service error codes
|
||||||
|
ErrorCodeNotInPairingMode = 0x10,
|
||||||
|
ErrorCodeBadAuthenticator = 0x11,
|
||||||
|
ErrorCodeBadPairingParameter = 0x12,
|
||||||
|
ErrorCodeMaxUsersReached = 0x13,
|
||||||
|
|
||||||
|
// Key turner service error codes
|
||||||
|
ErrorCodeNotAuthorized = 0x20,
|
||||||
|
ErrorCodeBadPin = 0x21,
|
||||||
|
ErrorCodeBadNonce = 0x22,
|
||||||
|
ErrorCodeBadParameter = 0x23,
|
||||||
|
ErrorCodeInvalidAuthId = 0x24,
|
||||||
|
ErrorCodeDisabled = 0x25,
|
||||||
|
ErrorCodeRemoteNotAllowed = 0x26,
|
||||||
|
ErrorCodeTimeNotAllowed = 0x27,
|
||||||
|
ErrorCodeTooManyPinAttempts = 0x28,
|
||||||
|
ErrorCodeAutoUnlockTooRecent = 0x40,
|
||||||
|
ErrorCodePositionUnknown = 0x41,
|
||||||
|
ErrorCodeMotorBlocked = 0x42,
|
||||||
|
ErrorCodeClutchFailure = 0x43,
|
||||||
|
ErrorCodeMotorTimeout = 0x44,
|
||||||
|
ErrorCodeBusy = 0x45,
|
||||||
|
|
||||||
|
// General error codes
|
||||||
|
ErrorCodeBadCrc = 0xFD,
|
||||||
|
ErrorCodeBadLength = 0xFE,
|
||||||
|
ErrorCodeUnknown = 0xFF
|
||||||
|
};
|
||||||
|
Q_ENUM(ErrorCode)
|
||||||
|
|
||||||
|
|
||||||
|
enum LockState {
|
||||||
|
LockStateUncalibrated = 0x00,
|
||||||
|
LockStateLocked = 0x01,
|
||||||
|
LockStateUnlocking = 0x02,
|
||||||
|
LockStateUnlocked = 0x03,
|
||||||
|
LockStateLocking = 0x04,
|
||||||
|
LockStateUnlatched = 0x05,
|
||||||
|
LockStateUnlockedLocknGoActive = 0x06,
|
||||||
|
LockStateUnlatching = 0x07,
|
||||||
|
LockStateMotorBlocked = 0xfe,
|
||||||
|
LockStateUndefined = 0xff
|
||||||
|
};
|
||||||
|
Q_ENUM(LockState)
|
||||||
|
|
||||||
|
|
||||||
|
enum LockAction {
|
||||||
|
LockActionUnlock = 0x01,
|
||||||
|
LockActionLock = 0x02,
|
||||||
|
LockActionUnlatch = 0x03,
|
||||||
|
LockActionLockNGo = 0x04,
|
||||||
|
LockActionLockNGoWithUnlatch = 0x05,
|
||||||
|
LockActionFobAction1 = 0x81,
|
||||||
|
LockActionFobAction2 = 0x82,
|
||||||
|
LockActionFobAction3 = 0x83
|
||||||
|
};
|
||||||
|
Q_ENUM(LockAction)
|
||||||
|
|
||||||
|
|
||||||
|
enum LockTrigger {
|
||||||
|
LockTriggerBluetooth = 0x00,
|
||||||
|
LockTriggerManual = 0x01,
|
||||||
|
LockTriggerButton = 0x02
|
||||||
|
};
|
||||||
|
Q_ENUM(LockTrigger)
|
||||||
|
|
||||||
|
|
||||||
|
enum NukiState {
|
||||||
|
NukiStateUninitialized = 0x00,
|
||||||
|
NukiStatePairingMode = 0x01,
|
||||||
|
NukiStateDoorMode = 0x02
|
||||||
|
};
|
||||||
|
Q_ENUM(NukiState)
|
||||||
|
|
||||||
|
|
||||||
|
enum Command {
|
||||||
|
CommandRequestData = 0x0001,
|
||||||
|
CommandPublicKey = 0x0003,
|
||||||
|
CommandChallenge = 0x0004,
|
||||||
|
CommandAuthorizationAuthenticator = 0x0005,
|
||||||
|
CommandAuthorizationData = 0x0006,
|
||||||
|
CommandAuthorizationId = 0x0007,
|
||||||
|
CommandRemoveUserAuthorization = 0x0008,
|
||||||
|
CommandRequestAuthorizationEntries = 0x0009,
|
||||||
|
CommandAuthorizationEntry = 0x000A,
|
||||||
|
CommandAuthorizationDataInvite = 0x000B,
|
||||||
|
CommandNukiStates = 0x000C,
|
||||||
|
CommandLockAction = 0x000D,
|
||||||
|
CommandStatus = 0x000E,
|
||||||
|
CommandMostRecentCommand = 0x000F,
|
||||||
|
CommandOpeningsClosingsSummary = 0x0010,
|
||||||
|
CommandBatteryReport = 0x0011,
|
||||||
|
CommandErrorReport = 0x0012,
|
||||||
|
CommandSetConG = 0x0013,
|
||||||
|
CommandRequestConG = 0x0014,
|
||||||
|
CommandConG = 0x0015,
|
||||||
|
CommandSetSecurityPIN = 0x0019,
|
||||||
|
CommandRequestCalibration = 0x001A,
|
||||||
|
CommandRequestReboot = 0x001D,
|
||||||
|
CommandAuthorizationIdConfirmation = 0x001E,
|
||||||
|
CommandAuthorizationIdInvite = 0x001F,
|
||||||
|
CommandVerifySecurityPIN = 0x0020,
|
||||||
|
CommandUpdateTime = 0x0021,
|
||||||
|
CommandUpdateUserAuthorization = 0x0025,
|
||||||
|
CommandAuthorizationEntryCount = 0x0027,
|
||||||
|
CommandRequestDisconnect = 0x0030,
|
||||||
|
CommandRequestLogEntries = 0x0031,
|
||||||
|
CommandLogEntry = 0x0032,
|
||||||
|
CommandLogEntryCount = 0x0033,
|
||||||
|
CommandEnableLogging = 0x0034,
|
||||||
|
CommandSetAdvancedConG = 0x0035,
|
||||||
|
CommandRequestAdvancedConG = 0x0036,
|
||||||
|
CommandAdvancedConG = 0x0037,
|
||||||
|
CommandAddTimeControlEntry = 0x0039,
|
||||||
|
CommandTimeControlEntryId = 0x003A,
|
||||||
|
CommandRemoveTimeControlEntry = 0x003B,
|
||||||
|
CommandRequestTimeControlEntries = 0x003C,
|
||||||
|
CommandTimeControlEntryCount = 0x003D,
|
||||||
|
CommandTimeControlEntry = 0x003E,
|
||||||
|
CommandUpdateTimeControlEntry = 0x003F
|
||||||
|
};
|
||||||
|
Q_ENUM(Command)
|
||||||
|
|
||||||
|
// Data helpers
|
||||||
|
static QString convertByteToHexString(const quint8 &byte);
|
||||||
|
static QString convertByteArrayToHexString(const QByteArray &byteArray);
|
||||||
|
static QString convertByteArrayToHexStringCompact(const QByteArray &byteArray);
|
||||||
|
static QString convertUint16ToHexString(const quint16 &value);
|
||||||
|
|
||||||
|
static QByteArray converUint32ToByteArrayLittleEndian(const quint32 &value);
|
||||||
|
static QByteArray converUint16ToByteArrayLittleEndian(const quint16 &value);
|
||||||
|
static quint16 convertByteArrayToUint16BigEndian(const QByteArray &littleEndianByteArray);
|
||||||
|
static quint32 convertByteArrayToUint32BigEndian(const QByteArray &littleEndianByteArray);
|
||||||
|
|
||||||
|
// Crc calculation
|
||||||
|
static quint16 calculateCrc(const QByteArray &data);
|
||||||
|
static bool validateMessageCrc(const QByteArray &message);
|
||||||
|
|
||||||
|
// Message helper
|
||||||
|
static QByteArray createRequestMessageForUnencrypted(NukiUtils::Command command, const QByteArray &payload);
|
||||||
|
static QByteArray createRequestMessageForUnencryptedForEncryption(quint32 authenticationId, NukiUtils::Command command, const QByteArray &payload);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // NUKIUTILS_H
|
||||||
63
nuki/plugins.pri
Normal file
63
nuki/plugins.pri
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
TEMPLATE = lib
|
||||||
|
CONFIG += plugin
|
||||||
|
|
||||||
|
QT += network bluetooth dbus
|
||||||
|
|
||||||
|
QMAKE_CXXFLAGS += -Werror -std=c++11 -g
|
||||||
|
QMAKE_LFLAGS += -std=c++11
|
||||||
|
|
||||||
|
INCLUDEPATH += /usr/include/nymea
|
||||||
|
LIBS += -lnymea
|
||||||
|
|
||||||
|
PLUGIN_PATH=/usr/lib/$$system('dpkg-architecture -q DEB_HOST_MULTIARCH')/nymea/plugins/
|
||||||
|
|
||||||
|
# Check if this is a snap build
|
||||||
|
snappy{
|
||||||
|
INCLUDEPATH+=$$(SNAPCRAFT_STAGE)/usr/include/nymea
|
||||||
|
}
|
||||||
|
|
||||||
|
# Make the device plugin json file visible in the Qt Creator
|
||||||
|
OTHER_FILES+=$$PWD/deviceplugin"$$TARGET".json
|
||||||
|
|
||||||
|
# NOTE: if the code includes "plugininfo.h", it would fail if we only give it a compiler for $$OUT_PWD/plugininfo.h
|
||||||
|
# Let's add a dummy target with the plugininfo.h file without any path to allow the developer to just include it like that.
|
||||||
|
|
||||||
|
# Create plugininfo file
|
||||||
|
plugininfo.target = $$OUT_PWD/plugininfo.h
|
||||||
|
plugininfo_dummy.target = plugininfo.h
|
||||||
|
plugininfo.depends = FORCE
|
||||||
|
plugininfo.commands = nymea-generateplugininfo --filetype i --jsonfile $$PWD/deviceplugin"$$TARGET".json --output plugininfo.h --builddir $$OUT_PWD
|
||||||
|
plugininfo_dummy.commands = $$plugininfo.commands
|
||||||
|
QMAKE_EXTRA_TARGETS += plugininfo plugininfo_dummy
|
||||||
|
|
||||||
|
# Create extern-plugininfo file
|
||||||
|
extern_plugininfo.target = $$OUT_PWD/extern-plugininfo.h
|
||||||
|
extern_plugininfo_dummy.target = extern-plugininfo.h
|
||||||
|
extern_plugininfo.depends = FORCE
|
||||||
|
extern_plugininfo.commands = nymea-generateplugininfo --filetype e --jsonfile $$PWD/deviceplugin"$$TARGET".json --output extern-plugininfo.h --builddir $$OUT_PWD
|
||||||
|
extern_plugininfo_dummy.commands = $$extern_plugininfo.commands
|
||||||
|
QMAKE_EXTRA_TARGETS += extern_plugininfo extern_plugininfo_dummy
|
||||||
|
|
||||||
|
# Install translation files
|
||||||
|
TRANSLATIONS *= $$files($${PWD}/translations/*ts, true)
|
||||||
|
lupdate.depends = FORCE
|
||||||
|
lupdate.depends += plugininfo
|
||||||
|
lupdate.commands = lupdate -recursive -no-obsolete -locations none $$PWD/"$$TARGET".pro;
|
||||||
|
QMAKE_EXTRA_TARGETS += lupdate
|
||||||
|
|
||||||
|
# make lrelease to build .qm from .ts
|
||||||
|
lrelease.depends = FORCE
|
||||||
|
lrelease.commands += lrelease $$files($$PWD/translations/*.ts, true);
|
||||||
|
lrelease.commands += rsync -a $$PWD/translations/*.qm $$OUT_PWD/translations/;
|
||||||
|
QMAKE_EXTRA_TARGETS += lrelease
|
||||||
|
|
||||||
|
translations.path = /usr/share/nymea/translations
|
||||||
|
translations.files = $$[QT_SOURCE_TREE]/translations/*.qm
|
||||||
|
|
||||||
|
HEADERS += $$OUT_PWD/plugininfo.h \
|
||||||
|
$$OUT_PWD/extern-plugininfo.h
|
||||||
|
DEPENDPATH += $$OUT_PWD
|
||||||
|
|
||||||
|
# Install plugin
|
||||||
|
target.path = $$PLUGIN_PATH
|
||||||
|
INSTALLS += target translations
|
||||||
169
nuki/translations/e5806d75-a40e-4766-a272-5a3a8d3ed625-cs.ts
Normal file
169
nuki/translations/e5806d75-a40e-4766-a272-5a3a8d3ed625-cs.ts
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!DOCTYPE TS>
|
||||||
|
<TS version="2.1" language="cs">
|
||||||
|
<context>
|
||||||
|
<name>DevicePluginNuki</name>
|
||||||
|
<message>
|
||||||
|
<source>Device is already in use.</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Bluetooth is not available on this system.</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Bluetooth device not found.</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Please press the Nuki button for 5 seconds in order to activate the pairing mode before you continue.</source>
|
||||||
|
<translation type="unfinished">Stiskněte tlačítko Nuki na 5 sekund k aktivování režimu párování dříve než budete pokračovat.</translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
<context>
|
||||||
|
<name>Nuki</name>
|
||||||
|
<message>
|
||||||
|
<source>Nuki</source>
|
||||||
|
<extracomment>The name of the vendor ({bf313b83-2ac5-4d22-bf0e-c13d3b4caf52})
|
||||||
|
----------
|
||||||
|
The name of the plugin Nuki ({e5806d75-a40e-4766-a272-5a3a8d3ed625})</extracomment>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Smartlock</source>
|
||||||
|
<extracomment>The name of the DeviceClass ({4a1cc5d9-9b44-4632-8db0-66d64efd4767})</extracomment>
|
||||||
|
<translation>Smartlock</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Name</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, Type: device, ID: {2477abba-874b-4c48-b543-7b911ff215b3})</extracomment>
|
||||||
|
<translation>Název</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>MAC address</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, Type: device, ID: {30976794-6066-4f72-8135-6d50499247a5})</extracomment>
|
||||||
|
<translation>MAC adresa</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Serial number</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, Type: device, ID: {ea51d911-f94a-4d2d-97fd-9f1d4c6519bf})</extracomment>
|
||||||
|
<translation>Sériové číslo</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Connected changed</source>
|
||||||
|
<extracomment>The name of the EventType ({d5fbd774-4e87-4c7c-8c92-f094352897f6}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Připojeno změněno</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Connected</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: connected, ID: {d5fbd774-4e87-4c7c-8c92-f094352897f6})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({d5fbd774-4e87-4c7c-8c92-f094352897f6}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Připojeno</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Battery critical changed</source>
|
||||||
|
<extracomment>The name of the EventType ({e9ffe14f-b71d-44cd-96c7-eb90fe243e13}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Baterie kritický stav změněno</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Battery critical</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: batteryCritical, ID: {e9ffe14f-b71d-44cd-96c7-eb90fe243e13})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({e9ffe14f-b71d-44cd-96c7-eb90fe243e13}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Baterie kritický stav</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Status changed</source>
|
||||||
|
<extracomment>The name of the EventType ({a47dec8b-4df2-4acd-895b-bfeacbcb6f2e}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Status změněn</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Status</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: status, ID: {a47dec8b-4df2-4acd-895b-bfeacbcb6f2e})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({a47dec8b-4df2-4acd-895b-bfeacbcb6f2e}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Status</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Mode changed</source>
|
||||||
|
<extracomment>The name of the EventType ({4e291e6a-2b90-4e6c-bad4-5400e6db4f05}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Režim změněn</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Mode</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: mode, ID: {4e291e6a-2b90-4e6c-bad4-5400e6db4f05})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({4e291e6a-2b90-4e6c-bad4-5400e6db4f05}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Režim</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Trigger changed</source>
|
||||||
|
<extracomment>The name of the EventType ({b67bc7a6-2d6b-46e8-8e59-e4eab2e5292a}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Spoušť změněna</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Trigger</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: trigger, ID: {b67bc7a6-2d6b-46e8-8e59-e4eab2e5292a})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({b67bc7a6-2d6b-46e8-8e59-e4eab2e5292a}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Spoušť</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Hardware revision changed</source>
|
||||||
|
<extracomment>The name of the EventType ({b1b0eafb-e5f2-47d3-bd18-74a21129f9b5}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Revize hardwaru změněna</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Hardware revision</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: hardwareRevision, ID: {b1b0eafb-e5f2-47d3-bd18-74a21129f9b5})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({b1b0eafb-e5f2-47d3-bd18-74a21129f9b5}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Revize hardwaru</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Firmware revision changed</source>
|
||||||
|
<extracomment>The name of the EventType ({9dc0115b-cdb4-472c-961b-b182f77a576f}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Revize firmwaru změněna</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Firmware revision</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: firmwareRevision, ID: {9dc0115b-cdb4-472c-961b-b182f77a576f})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({9dc0115b-cdb4-472c-961b-b182f77a576f}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Revize firmwaru</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Lock</source>
|
||||||
|
<extracomment>The name of the ActionType ({55d25891-89b9-4a9f-a2b3-774eccf30183}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Zamknout</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Unlock</source>
|
||||||
|
<extracomment>The name of the ActionType ({76e96738-5336-4b9a-87f7-1822307b5a39}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Odemknout</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Refresh</source>
|
||||||
|
<extracomment>The name of the ActionType ({7c9d5c5d-d8c1-424b-8620-daa3a757576b}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Obnovit</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>State changed</source>
|
||||||
|
<extracomment>The name of the EventType ({07a09bd8-b342-4e33-92cd-0af21f8689fa}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Stav změněn</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>State</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: state, ID: {07a09bd8-b342-4e33-92cd-0af21f8689fa})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({07a09bd8-b342-4e33-92cd-0af21f8689fa}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Stav</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Open door</source>
|
||||||
|
<extracomment>The name of the ActionType ({45be8d24-17c3-422b-b264-381e673bb3c8}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Otevřít dveře</translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
</TS>
|
||||||
169
nuki/translations/e5806d75-a40e-4766-a272-5a3a8d3ed625-da.ts
Normal file
169
nuki/translations/e5806d75-a40e-4766-a272-5a3a8d3ed625-da.ts
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!DOCTYPE TS>
|
||||||
|
<TS version="2.1" language="da">
|
||||||
|
<context>
|
||||||
|
<name>DevicePluginNuki</name>
|
||||||
|
<message>
|
||||||
|
<source>Device is already in use.</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Bluetooth is not available on this system.</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Bluetooth device not found.</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Please press the Nuki button for 5 seconds in order to activate the pairing mode before you continue.</source>
|
||||||
|
<translation type="unfinished">Tryk på Nuki-knappen i fem sekunder for at aktivere parringstilstanden, før du fortsætter.</translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
<context>
|
||||||
|
<name>Nuki</name>
|
||||||
|
<message>
|
||||||
|
<source>Nuki</source>
|
||||||
|
<extracomment>The name of the vendor ({bf313b83-2ac5-4d22-bf0e-c13d3b4caf52})
|
||||||
|
----------
|
||||||
|
The name of the plugin Nuki ({e5806d75-a40e-4766-a272-5a3a8d3ed625})</extracomment>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Smartlock</source>
|
||||||
|
<extracomment>The name of the DeviceClass ({4a1cc5d9-9b44-4632-8db0-66d64efd4767})</extracomment>
|
||||||
|
<translation>Smartlock</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Name</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, Type: device, ID: {2477abba-874b-4c48-b543-7b911ff215b3})</extracomment>
|
||||||
|
<translation>Navn</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>MAC address</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, Type: device, ID: {30976794-6066-4f72-8135-6d50499247a5})</extracomment>
|
||||||
|
<translation>mac-adresse</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Serial number</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, Type: device, ID: {ea51d911-f94a-4d2d-97fd-9f1d4c6519bf})</extracomment>
|
||||||
|
<translation>Serienummer</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Connected changed</source>
|
||||||
|
<extracomment>The name of the EventType ({d5fbd774-4e87-4c7c-8c92-f094352897f6}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Forbindelse ændret</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Connected</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: connected, ID: {d5fbd774-4e87-4c7c-8c92-f094352897f6})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({d5fbd774-4e87-4c7c-8c92-f094352897f6}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Forbundet</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Battery critical changed</source>
|
||||||
|
<extracomment>The name of the EventType ({e9ffe14f-b71d-44cd-96c7-eb90fe243e13}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Batteri kritisk ændret</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Battery critical</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: batteryCritical, ID: {e9ffe14f-b71d-44cd-96c7-eb90fe243e13})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({e9ffe14f-b71d-44cd-96c7-eb90fe243e13}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Batteri kritisk</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Status changed</source>
|
||||||
|
<extracomment>The name of the EventType ({a47dec8b-4df2-4acd-895b-bfeacbcb6f2e}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Status ændret</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Status</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: status, ID: {a47dec8b-4df2-4acd-895b-bfeacbcb6f2e})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({a47dec8b-4df2-4acd-895b-bfeacbcb6f2e}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Status</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Mode changed</source>
|
||||||
|
<extracomment>The name of the EventType ({4e291e6a-2b90-4e6c-bad4-5400e6db4f05}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Modus ændret</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Mode</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: mode, ID: {4e291e6a-2b90-4e6c-bad4-5400e6db4f05})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({4e291e6a-2b90-4e6c-bad4-5400e6db4f05}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Modus</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Trigger changed</source>
|
||||||
|
<extracomment>The name of the EventType ({b67bc7a6-2d6b-46e8-8e59-e4eab2e5292a}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Udløser ændret</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Trigger</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: trigger, ID: {b67bc7a6-2d6b-46e8-8e59-e4eab2e5292a})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({b67bc7a6-2d6b-46e8-8e59-e4eab2e5292a}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Udløser</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Hardware revision changed</source>
|
||||||
|
<extracomment>The name of the EventType ({b1b0eafb-e5f2-47d3-bd18-74a21129f9b5}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>hardwarerevision ændret</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Hardware revision</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: hardwareRevision, ID: {b1b0eafb-e5f2-47d3-bd18-74a21129f9b5})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({b1b0eafb-e5f2-47d3-bd18-74a21129f9b5}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Hardwarerevision</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Firmware revision changed</source>
|
||||||
|
<extracomment>The name of the EventType ({9dc0115b-cdb4-472c-961b-b182f77a576f}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Firmwarerevision ændret</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Firmware revision</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: firmwareRevision, ID: {9dc0115b-cdb4-472c-961b-b182f77a576f})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({9dc0115b-cdb4-472c-961b-b182f77a576f}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Firmwarerevision</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Lock</source>
|
||||||
|
<extracomment>The name of the ActionType ({55d25891-89b9-4a9f-a2b3-774eccf30183}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Lås</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Unlock</source>
|
||||||
|
<extracomment>The name of the ActionType ({76e96738-5336-4b9a-87f7-1822307b5a39}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Lås op</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Refresh</source>
|
||||||
|
<extracomment>The name of the ActionType ({7c9d5c5d-d8c1-424b-8620-daa3a757576b}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Opdatér</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>State changed</source>
|
||||||
|
<extracomment>The name of the EventType ({07a09bd8-b342-4e33-92cd-0af21f8689fa}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Tilstand ændret</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>State</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: state, ID: {07a09bd8-b342-4e33-92cd-0af21f8689fa})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({07a09bd8-b342-4e33-92cd-0af21f8689fa}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Tilstand</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Open door</source>
|
||||||
|
<extracomment>The name of the ActionType ({45be8d24-17c3-422b-b264-381e673bb3c8}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Åbn dør</translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
</TS>
|
||||||
169
nuki/translations/e5806d75-a40e-4766-a272-5a3a8d3ed625-de.ts
Normal file
169
nuki/translations/e5806d75-a40e-4766-a272-5a3a8d3ed625-de.ts
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!DOCTYPE TS>
|
||||||
|
<TS version="2.1" language="de">
|
||||||
|
<context>
|
||||||
|
<name>DevicePluginNuki</name>
|
||||||
|
<message>
|
||||||
|
<source>Device is already in use.</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Bluetooth is not available on this system.</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Bluetooth device not found.</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Please press the Nuki button for 5 seconds in order to activate the pairing mode before you continue.</source>
|
||||||
|
<translation type="unfinished">Bevor es weitergeht drücke bitte die Nuki-Taste für 5 Sekunden um den Pairing-Modus zu aktivieren.</translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
<context>
|
||||||
|
<name>Nuki</name>
|
||||||
|
<message>
|
||||||
|
<source>Nuki</source>
|
||||||
|
<extracomment>The name of the vendor ({bf313b83-2ac5-4d22-bf0e-c13d3b4caf52})
|
||||||
|
----------
|
||||||
|
The name of the plugin Nuki ({e5806d75-a40e-4766-a272-5a3a8d3ed625})</extracomment>
|
||||||
|
<translation>Nuki</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Smartlock</source>
|
||||||
|
<extracomment>The name of the DeviceClass ({4a1cc5d9-9b44-4632-8db0-66d64efd4767})</extracomment>
|
||||||
|
<translation>Smartlock</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Name</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, Type: device, ID: {2477abba-874b-4c48-b543-7b911ff215b3})</extracomment>
|
||||||
|
<translation>Name</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>MAC address</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, Type: device, ID: {30976794-6066-4f72-8135-6d50499247a5})</extracomment>
|
||||||
|
<translation>MAC Adresse</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Serial number</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, Type: device, ID: {ea51d911-f94a-4d2d-97fd-9f1d4c6519bf})</extracomment>
|
||||||
|
<translation>Seriennummer</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Connected changed</source>
|
||||||
|
<extracomment>The name of the EventType ({d5fbd774-4e87-4c7c-8c92-f094352897f6}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Verbunden geändert</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Connected</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: connected, ID: {d5fbd774-4e87-4c7c-8c92-f094352897f6})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({d5fbd774-4e87-4c7c-8c92-f094352897f6}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Verbunden</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Battery critical changed</source>
|
||||||
|
<extracomment>The name of the EventType ({e9ffe14f-b71d-44cd-96c7-eb90fe243e13}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Batterie Ladung kritisch geändert</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Battery critical</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: batteryCritical, ID: {e9ffe14f-b71d-44cd-96c7-eb90fe243e13})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({e9ffe14f-b71d-44cd-96c7-eb90fe243e13}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Batterie Ladung kritisch</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Status changed</source>
|
||||||
|
<extracomment>The name of the EventType ({a47dec8b-4df2-4acd-895b-bfeacbcb6f2e}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Status geändert</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Status</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: status, ID: {a47dec8b-4df2-4acd-895b-bfeacbcb6f2e})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({a47dec8b-4df2-4acd-895b-bfeacbcb6f2e}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Status</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Mode changed</source>
|
||||||
|
<extracomment>The name of the EventType ({4e291e6a-2b90-4e6c-bad4-5400e6db4f05}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Modus geändert</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Mode</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: mode, ID: {4e291e6a-2b90-4e6c-bad4-5400e6db4f05})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({4e291e6a-2b90-4e6c-bad4-5400e6db4f05}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Modus</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Trigger changed</source>
|
||||||
|
<extracomment>The name of the EventType ({b67bc7a6-2d6b-46e8-8e59-e4eab2e5292a}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Auslöser geändert</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Trigger</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: trigger, ID: {b67bc7a6-2d6b-46e8-8e59-e4eab2e5292a})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({b67bc7a6-2d6b-46e8-8e59-e4eab2e5292a}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Auslöser</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Hardware revision changed</source>
|
||||||
|
<extracomment>The name of the EventType ({b1b0eafb-e5f2-47d3-bd18-74a21129f9b5}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Hardware-Revision geändert</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Hardware revision</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: hardwareRevision, ID: {b1b0eafb-e5f2-47d3-bd18-74a21129f9b5})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({b1b0eafb-e5f2-47d3-bd18-74a21129f9b5}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Hardware-Revision</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Firmware revision changed</source>
|
||||||
|
<extracomment>The name of the EventType ({9dc0115b-cdb4-472c-961b-b182f77a576f}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Firmware-Version geändert</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Firmware revision</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: firmwareRevision, ID: {9dc0115b-cdb4-472c-961b-b182f77a576f})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({9dc0115b-cdb4-472c-961b-b182f77a576f}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Firmware-Version</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Lock</source>
|
||||||
|
<extracomment>The name of the ActionType ({55d25891-89b9-4a9f-a2b3-774eccf30183}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Zusperren</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Unlock</source>
|
||||||
|
<extracomment>The name of the ActionType ({76e96738-5336-4b9a-87f7-1822307b5a39}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Aufsperren</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Refresh</source>
|
||||||
|
<extracomment>The name of the ActionType ({7c9d5c5d-d8c1-424b-8620-daa3a757576b}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Aktualisieren</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>State changed</source>
|
||||||
|
<extracomment>The name of the EventType ({07a09bd8-b342-4e33-92cd-0af21f8689fa}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Status geändert</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>State</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: state, ID: {07a09bd8-b342-4e33-92cd-0af21f8689fa})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({07a09bd8-b342-4e33-92cd-0af21f8689fa}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Status</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Open door</source>
|
||||||
|
<extracomment>The name of the ActionType ({45be8d24-17c3-422b-b264-381e673bb3c8}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Tür öffnen</translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
</TS>
|
||||||
169
nuki/translations/e5806d75-a40e-4766-a272-5a3a8d3ed625-en_US.ts
Normal file
169
nuki/translations/e5806d75-a40e-4766-a272-5a3a8d3ed625-en_US.ts
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!DOCTYPE TS>
|
||||||
|
<TS version="2.1" language="en_US">
|
||||||
|
<context>
|
||||||
|
<name>DevicePluginNuki</name>
|
||||||
|
<message>
|
||||||
|
<source>Device is already in use.</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Bluetooth is not available on this system.</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Bluetooth device not found.</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Please press the Nuki button for 5 seconds in order to activate the pairing mode before you continue.</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
<context>
|
||||||
|
<name>Nuki</name>
|
||||||
|
<message>
|
||||||
|
<source>Nuki</source>
|
||||||
|
<extracomment>The name of the vendor ({bf313b83-2ac5-4d22-bf0e-c13d3b4caf52})
|
||||||
|
----------
|
||||||
|
The name of the plugin Nuki ({e5806d75-a40e-4766-a272-5a3a8d3ed625})</extracomment>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Smartlock</source>
|
||||||
|
<extracomment>The name of the DeviceClass ({4a1cc5d9-9b44-4632-8db0-66d64efd4767})</extracomment>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Name</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, Type: device, ID: {2477abba-874b-4c48-b543-7b911ff215b3})</extracomment>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>MAC address</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, Type: device, ID: {30976794-6066-4f72-8135-6d50499247a5})</extracomment>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Serial number</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, Type: device, ID: {ea51d911-f94a-4d2d-97fd-9f1d4c6519bf})</extracomment>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Connected changed</source>
|
||||||
|
<extracomment>The name of the EventType ({d5fbd774-4e87-4c7c-8c92-f094352897f6}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Connected</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: connected, ID: {d5fbd774-4e87-4c7c-8c92-f094352897f6})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({d5fbd774-4e87-4c7c-8c92-f094352897f6}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Battery critical changed</source>
|
||||||
|
<extracomment>The name of the EventType ({e9ffe14f-b71d-44cd-96c7-eb90fe243e13}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Battery critical</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: batteryCritical, ID: {e9ffe14f-b71d-44cd-96c7-eb90fe243e13})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({e9ffe14f-b71d-44cd-96c7-eb90fe243e13}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Status changed</source>
|
||||||
|
<extracomment>The name of the EventType ({a47dec8b-4df2-4acd-895b-bfeacbcb6f2e}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Status</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: status, ID: {a47dec8b-4df2-4acd-895b-bfeacbcb6f2e})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({a47dec8b-4df2-4acd-895b-bfeacbcb6f2e}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Mode changed</source>
|
||||||
|
<extracomment>The name of the EventType ({4e291e6a-2b90-4e6c-bad4-5400e6db4f05}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Mode</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: mode, ID: {4e291e6a-2b90-4e6c-bad4-5400e6db4f05})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({4e291e6a-2b90-4e6c-bad4-5400e6db4f05}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Trigger changed</source>
|
||||||
|
<extracomment>The name of the EventType ({b67bc7a6-2d6b-46e8-8e59-e4eab2e5292a}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Trigger</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: trigger, ID: {b67bc7a6-2d6b-46e8-8e59-e4eab2e5292a})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({b67bc7a6-2d6b-46e8-8e59-e4eab2e5292a}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Hardware revision changed</source>
|
||||||
|
<extracomment>The name of the EventType ({b1b0eafb-e5f2-47d3-bd18-74a21129f9b5}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Hardware revision</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: hardwareRevision, ID: {b1b0eafb-e5f2-47d3-bd18-74a21129f9b5})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({b1b0eafb-e5f2-47d3-bd18-74a21129f9b5}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Firmware revision changed</source>
|
||||||
|
<extracomment>The name of the EventType ({9dc0115b-cdb4-472c-961b-b182f77a576f}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Firmware revision</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: firmwareRevision, ID: {9dc0115b-cdb4-472c-961b-b182f77a576f})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({9dc0115b-cdb4-472c-961b-b182f77a576f}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Lock</source>
|
||||||
|
<extracomment>The name of the ActionType ({55d25891-89b9-4a9f-a2b3-774eccf30183}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Unlock</source>
|
||||||
|
<extracomment>The name of the ActionType ({76e96738-5336-4b9a-87f7-1822307b5a39}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Refresh</source>
|
||||||
|
<extracomment>The name of the ActionType ({7c9d5c5d-d8c1-424b-8620-daa3a757576b}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>State changed</source>
|
||||||
|
<extracomment>The name of the EventType ({07a09bd8-b342-4e33-92cd-0af21f8689fa}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>State</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: state, ID: {07a09bd8-b342-4e33-92cd-0af21f8689fa})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({07a09bd8-b342-4e33-92cd-0af21f8689fa}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Open door</source>
|
||||||
|
<extracomment>The name of the ActionType ({45be8d24-17c3-422b-b264-381e673bb3c8}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
</TS>
|
||||||
169
nuki/translations/e5806d75-a40e-4766-a272-5a3a8d3ed625-es.ts
Normal file
169
nuki/translations/e5806d75-a40e-4766-a272-5a3a8d3ed625-es.ts
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!DOCTYPE TS>
|
||||||
|
<TS version="2.1" language="es">
|
||||||
|
<context>
|
||||||
|
<name>DevicePluginNuki</name>
|
||||||
|
<message>
|
||||||
|
<source>Device is already in use.</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Bluetooth is not available on this system.</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Bluetooth device not found.</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Please press the Nuki button for 5 seconds in order to activate the pairing mode before you continue.</source>
|
||||||
|
<translation type="unfinished">Pulse el botón Nuki durante 5 segundos para activar el modo de enlazado antes de continuar.</translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
<context>
|
||||||
|
<name>Nuki</name>
|
||||||
|
<message>
|
||||||
|
<source>Nuki</source>
|
||||||
|
<extracomment>The name of the vendor ({bf313b83-2ac5-4d22-bf0e-c13d3b4caf52})
|
||||||
|
----------
|
||||||
|
The name of the plugin Nuki ({e5806d75-a40e-4766-a272-5a3a8d3ed625})</extracomment>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Smartlock</source>
|
||||||
|
<extracomment>The name of the DeviceClass ({4a1cc5d9-9b44-4632-8db0-66d64efd4767})</extracomment>
|
||||||
|
<translation>Smartlock</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Name</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, Type: device, ID: {2477abba-874b-4c48-b543-7b911ff215b3})</extracomment>
|
||||||
|
<translation>Nombre</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>MAC address</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, Type: device, ID: {30976794-6066-4f72-8135-6d50499247a5})</extracomment>
|
||||||
|
<translation>Dirección MAC</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Serial number</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, Type: device, ID: {ea51d911-f94a-4d2d-97fd-9f1d4c6519bf})</extracomment>
|
||||||
|
<translation>Número de serie</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Connected changed</source>
|
||||||
|
<extracomment>The name of the EventType ({d5fbd774-4e87-4c7c-8c92-f094352897f6}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Conexión modificada</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Connected</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: connected, ID: {d5fbd774-4e87-4c7c-8c92-f094352897f6})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({d5fbd774-4e87-4c7c-8c92-f094352897f6}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Conectado</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Battery critical changed</source>
|
||||||
|
<extracomment>The name of the EventType ({e9ffe14f-b71d-44cd-96c7-eb90fe243e13}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Batería en estado crítico modificada</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Battery critical</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: batteryCritical, ID: {e9ffe14f-b71d-44cd-96c7-eb90fe243e13})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({e9ffe14f-b71d-44cd-96c7-eb90fe243e13}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Batería en estado crítico</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Status changed</source>
|
||||||
|
<extracomment>The name of the EventType ({a47dec8b-4df2-4acd-895b-bfeacbcb6f2e}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Estado modificado</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Status</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: status, ID: {a47dec8b-4df2-4acd-895b-bfeacbcb6f2e})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({a47dec8b-4df2-4acd-895b-bfeacbcb6f2e}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Estado</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Mode changed</source>
|
||||||
|
<extracomment>The name of the EventType ({4e291e6a-2b90-4e6c-bad4-5400e6db4f05}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Modo modificado</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Mode</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: mode, ID: {4e291e6a-2b90-4e6c-bad4-5400e6db4f05})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({4e291e6a-2b90-4e6c-bad4-5400e6db4f05}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Modo</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Trigger changed</source>
|
||||||
|
<extracomment>The name of the EventType ({b67bc7a6-2d6b-46e8-8e59-e4eab2e5292a}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Disparador modificado</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Trigger</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: trigger, ID: {b67bc7a6-2d6b-46e8-8e59-e4eab2e5292a})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({b67bc7a6-2d6b-46e8-8e59-e4eab2e5292a}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Disparador</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Hardware revision changed</source>
|
||||||
|
<extracomment>The name of the EventType ({b1b0eafb-e5f2-47d3-bd18-74a21129f9b5}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Revisión de hardware modificada</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Hardware revision</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: hardwareRevision, ID: {b1b0eafb-e5f2-47d3-bd18-74a21129f9b5})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({b1b0eafb-e5f2-47d3-bd18-74a21129f9b5}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Revisión de hardware</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Firmware revision changed</source>
|
||||||
|
<extracomment>The name of the EventType ({9dc0115b-cdb4-472c-961b-b182f77a576f}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Revisión de firmware modificada</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Firmware revision</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: firmwareRevision, ID: {9dc0115b-cdb4-472c-961b-b182f77a576f})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({9dc0115b-cdb4-472c-961b-b182f77a576f}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Revisión de firmware</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Lock</source>
|
||||||
|
<extracomment>The name of the ActionType ({55d25891-89b9-4a9f-a2b3-774eccf30183}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Bloqueo</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Unlock</source>
|
||||||
|
<extracomment>The name of the ActionType ({76e96738-5336-4b9a-87f7-1822307b5a39}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Desbloqueo</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Refresh</source>
|
||||||
|
<extracomment>The name of the ActionType ({7c9d5c5d-d8c1-424b-8620-daa3a757576b}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Actualizar</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>State changed</source>
|
||||||
|
<extracomment>The name of the EventType ({07a09bd8-b342-4e33-92cd-0af21f8689fa}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Estado modificado</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>State</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: state, ID: {07a09bd8-b342-4e33-92cd-0af21f8689fa})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({07a09bd8-b342-4e33-92cd-0af21f8689fa}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Estado</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Open door</source>
|
||||||
|
<extracomment>The name of the ActionType ({45be8d24-17c3-422b-b264-381e673bb3c8}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Abrir puerta</translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
</TS>
|
||||||
169
nuki/translations/e5806d75-a40e-4766-a272-5a3a8d3ed625-fr.ts
Normal file
169
nuki/translations/e5806d75-a40e-4766-a272-5a3a8d3ed625-fr.ts
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!DOCTYPE TS>
|
||||||
|
<TS version="2.1" language="fr">
|
||||||
|
<context>
|
||||||
|
<name>DevicePluginNuki</name>
|
||||||
|
<message>
|
||||||
|
<source>Device is already in use.</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Bluetooth is not available on this system.</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Bluetooth device not found.</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Please press the Nuki button for 5 seconds in order to activate the pairing mode before you continue.</source>
|
||||||
|
<translation type="unfinished">Veuillez appuyer pendant 5 secondes sur le bouton Nuki afin d'activer le mode de couplage avant de continuer.</translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
<context>
|
||||||
|
<name>Nuki</name>
|
||||||
|
<message>
|
||||||
|
<source>Nuki</source>
|
||||||
|
<extracomment>The name of the vendor ({bf313b83-2ac5-4d22-bf0e-c13d3b4caf52})
|
||||||
|
----------
|
||||||
|
The name of the plugin Nuki ({e5806d75-a40e-4766-a272-5a3a8d3ed625})</extracomment>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Smartlock</source>
|
||||||
|
<extracomment>The name of the DeviceClass ({4a1cc5d9-9b44-4632-8db0-66d64efd4767})</extracomment>
|
||||||
|
<translation>Smartlock</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Name</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, Type: device, ID: {2477abba-874b-4c48-b543-7b911ff215b3})</extracomment>
|
||||||
|
<translation>Nom</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>MAC address</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, Type: device, ID: {30976794-6066-4f72-8135-6d50499247a5})</extracomment>
|
||||||
|
<translation>Adresse MAC</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Serial number</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, Type: device, ID: {ea51d911-f94a-4d2d-97fd-9f1d4c6519bf})</extracomment>
|
||||||
|
<translation>Numéro de serie</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Connected changed</source>
|
||||||
|
<extracomment>The name of the EventType ({d5fbd774-4e87-4c7c-8c92-f094352897f6}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Connexion modifiée</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Connected</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: connected, ID: {d5fbd774-4e87-4c7c-8c92-f094352897f6})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({d5fbd774-4e87-4c7c-8c92-f094352897f6}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Connecté</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Battery critical changed</source>
|
||||||
|
<extracomment>The name of the EventType ({e9ffe14f-b71d-44cd-96c7-eb90fe243e13}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Statut de batterie critique modifié</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Battery critical</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: batteryCritical, ID: {e9ffe14f-b71d-44cd-96c7-eb90fe243e13})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({e9ffe14f-b71d-44cd-96c7-eb90fe243e13}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Batterie critique</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Status changed</source>
|
||||||
|
<extracomment>The name of the EventType ({a47dec8b-4df2-4acd-895b-bfeacbcb6f2e}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Statut modifié</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Status</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: status, ID: {a47dec8b-4df2-4acd-895b-bfeacbcb6f2e})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({a47dec8b-4df2-4acd-895b-bfeacbcb6f2e}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Statut</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Mode changed</source>
|
||||||
|
<extracomment>The name of the EventType ({4e291e6a-2b90-4e6c-bad4-5400e6db4f05}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Mode modifié</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Mode</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: mode, ID: {4e291e6a-2b90-4e6c-bad4-5400e6db4f05})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({4e291e6a-2b90-4e6c-bad4-5400e6db4f05}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Mode</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Trigger changed</source>
|
||||||
|
<extracomment>The name of the EventType ({b67bc7a6-2d6b-46e8-8e59-e4eab2e5292a}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Déclencheur modifié</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Trigger</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: trigger, ID: {b67bc7a6-2d6b-46e8-8e59-e4eab2e5292a})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({b67bc7a6-2d6b-46e8-8e59-e4eab2e5292a}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Déclencheur</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Hardware revision changed</source>
|
||||||
|
<extracomment>The name of the EventType ({b1b0eafb-e5f2-47d3-bd18-74a21129f9b5}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Révision du matériel (hardware) modifiée</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Hardware revision</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: hardwareRevision, ID: {b1b0eafb-e5f2-47d3-bd18-74a21129f9b5})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({b1b0eafb-e5f2-47d3-bd18-74a21129f9b5}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Révision du matériel (hardware)</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Firmware revision changed</source>
|
||||||
|
<extracomment>The name of the EventType ({9dc0115b-cdb4-472c-961b-b182f77a576f}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Version du progiciel modifiée</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Firmware revision</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: firmwareRevision, ID: {9dc0115b-cdb4-472c-961b-b182f77a576f})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({9dc0115b-cdb4-472c-961b-b182f77a576f}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Version du progiciel</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Lock</source>
|
||||||
|
<extracomment>The name of the ActionType ({55d25891-89b9-4a9f-a2b3-774eccf30183}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Verrouiller</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Unlock</source>
|
||||||
|
<extracomment>The name of the ActionType ({76e96738-5336-4b9a-87f7-1822307b5a39}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Déverrouiller</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Refresh</source>
|
||||||
|
<extracomment>The name of the ActionType ({7c9d5c5d-d8c1-424b-8620-daa3a757576b}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Rafraîchir</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>State changed</source>
|
||||||
|
<extracomment>The name of the EventType ({07a09bd8-b342-4e33-92cd-0af21f8689fa}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Statut modifié</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>State</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: state, ID: {07a09bd8-b342-4e33-92cd-0af21f8689fa})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({07a09bd8-b342-4e33-92cd-0af21f8689fa}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Statut</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Open door</source>
|
||||||
|
<extracomment>The name of the ActionType ({45be8d24-17c3-422b-b264-381e673bb3c8}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Ouvrir la porte</translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
</TS>
|
||||||
169
nuki/translations/e5806d75-a40e-4766-a272-5a3a8d3ed625-it.ts
Normal file
169
nuki/translations/e5806d75-a40e-4766-a272-5a3a8d3ed625-it.ts
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!DOCTYPE TS>
|
||||||
|
<TS version="2.1" language="it">
|
||||||
|
<context>
|
||||||
|
<name>DevicePluginNuki</name>
|
||||||
|
<message>
|
||||||
|
<source>Device is already in use.</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Bluetooth is not available on this system.</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Bluetooth device not found.</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Please press the Nuki button for 5 seconds in order to activate the pairing mode before you continue.</source>
|
||||||
|
<translation type="unfinished">Premi il pulsante Nuki per 5 secondi per attivare la sincronizzazione prima di continuare.</translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
<context>
|
||||||
|
<name>Nuki</name>
|
||||||
|
<message>
|
||||||
|
<source>Nuki</source>
|
||||||
|
<extracomment>The name of the vendor ({bf313b83-2ac5-4d22-bf0e-c13d3b4caf52})
|
||||||
|
----------
|
||||||
|
The name of the plugin Nuki ({e5806d75-a40e-4766-a272-5a3a8d3ed625})</extracomment>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Smartlock</source>
|
||||||
|
<extracomment>The name of the DeviceClass ({4a1cc5d9-9b44-4632-8db0-66d64efd4767})</extracomment>
|
||||||
|
<translation>Smartlock</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Name</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, Type: device, ID: {2477abba-874b-4c48-b543-7b911ff215b3})</extracomment>
|
||||||
|
<translation>Nome</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>MAC address</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, Type: device, ID: {30976794-6066-4f72-8135-6d50499247a5})</extracomment>
|
||||||
|
<translation>Indirizzo MAC</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Serial number</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, Type: device, ID: {ea51d911-f94a-4d2d-97fd-9f1d4c6519bf})</extracomment>
|
||||||
|
<translation>Numero di serie</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Connected changed</source>
|
||||||
|
<extracomment>The name of the EventType ({d5fbd774-4e87-4c7c-8c92-f094352897f6}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Connesso modificato</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Connected</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: connected, ID: {d5fbd774-4e87-4c7c-8c92-f094352897f6})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({d5fbd774-4e87-4c7c-8c92-f094352897f6}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Connesso</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Battery critical changed</source>
|
||||||
|
<extracomment>The name of the EventType ({e9ffe14f-b71d-44cd-96c7-eb90fe243e13}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Batteria critica modificata</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Battery critical</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: batteryCritical, ID: {e9ffe14f-b71d-44cd-96c7-eb90fe243e13})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({e9ffe14f-b71d-44cd-96c7-eb90fe243e13}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Batteria critica</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Status changed</source>
|
||||||
|
<extracomment>The name of the EventType ({a47dec8b-4df2-4acd-895b-bfeacbcb6f2e}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Stato modificato</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Status</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: status, ID: {a47dec8b-4df2-4acd-895b-bfeacbcb6f2e})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({a47dec8b-4df2-4acd-895b-bfeacbcb6f2e}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Stato</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Mode changed</source>
|
||||||
|
<extracomment>The name of the EventType ({4e291e6a-2b90-4e6c-bad4-5400e6db4f05}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Modalità modificata</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Mode</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: mode, ID: {4e291e6a-2b90-4e6c-bad4-5400e6db4f05})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({4e291e6a-2b90-4e6c-bad4-5400e6db4f05}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Modalità</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Trigger changed</source>
|
||||||
|
<extracomment>The name of the EventType ({b67bc7a6-2d6b-46e8-8e59-e4eab2e5292a}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Trigger modificato</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Trigger</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: trigger, ID: {b67bc7a6-2d6b-46e8-8e59-e4eab2e5292a})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({b67bc7a6-2d6b-46e8-8e59-e4eab2e5292a}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Trigger</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Hardware revision changed</source>
|
||||||
|
<extracomment>The name of the EventType ({b1b0eafb-e5f2-47d3-bd18-74a21129f9b5}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Revisione hardware modificata</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Hardware revision</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: hardwareRevision, ID: {b1b0eafb-e5f2-47d3-bd18-74a21129f9b5})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({b1b0eafb-e5f2-47d3-bd18-74a21129f9b5}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Revisione hardware</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Firmware revision changed</source>
|
||||||
|
<extracomment>The name of the EventType ({9dc0115b-cdb4-472c-961b-b182f77a576f}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Revisione firmware modificata</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Firmware revision</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: firmwareRevision, ID: {9dc0115b-cdb4-472c-961b-b182f77a576f})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({9dc0115b-cdb4-472c-961b-b182f77a576f}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Revisione firmware</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Lock</source>
|
||||||
|
<extracomment>The name of the ActionType ({55d25891-89b9-4a9f-a2b3-774eccf30183}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Blocca</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Unlock</source>
|
||||||
|
<extracomment>The name of the ActionType ({76e96738-5336-4b9a-87f7-1822307b5a39}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Sblocca</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Refresh</source>
|
||||||
|
<extracomment>The name of the ActionType ({7c9d5c5d-d8c1-424b-8620-daa3a757576b}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Aggiorna</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>State changed</source>
|
||||||
|
<extracomment>The name of the EventType ({07a09bd8-b342-4e33-92cd-0af21f8689fa}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Stato modificato</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>State</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: state, ID: {07a09bd8-b342-4e33-92cd-0af21f8689fa})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({07a09bd8-b342-4e33-92cd-0af21f8689fa}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Stato</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Open door</source>
|
||||||
|
<extracomment>The name of the ActionType ({45be8d24-17c3-422b-b264-381e673bb3c8}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Apri porta</translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
</TS>
|
||||||
169
nuki/translations/e5806d75-a40e-4766-a272-5a3a8d3ed625-nl.ts
Normal file
169
nuki/translations/e5806d75-a40e-4766-a272-5a3a8d3ed625-nl.ts
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!DOCTYPE TS>
|
||||||
|
<TS version="2.1" language="nl">
|
||||||
|
<context>
|
||||||
|
<name>DevicePluginNuki</name>
|
||||||
|
<message>
|
||||||
|
<source>Device is already in use.</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Bluetooth is not available on this system.</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Bluetooth device not found.</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Please press the Nuki button for 5 seconds in order to activate the pairing mode before you continue.</source>
|
||||||
|
<translation type="unfinished">Houd, alvorens verder te gaan, de Nuki-knop 5 seconden ingedrukt om de koppelmodus te activeren.</translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
<context>
|
||||||
|
<name>Nuki</name>
|
||||||
|
<message>
|
||||||
|
<source>Nuki</source>
|
||||||
|
<extracomment>The name of the vendor ({bf313b83-2ac5-4d22-bf0e-c13d3b4caf52})
|
||||||
|
----------
|
||||||
|
The name of the plugin Nuki ({e5806d75-a40e-4766-a272-5a3a8d3ed625})</extracomment>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Smartlock</source>
|
||||||
|
<extracomment>The name of the DeviceClass ({4a1cc5d9-9b44-4632-8db0-66d64efd4767})</extracomment>
|
||||||
|
<translation>Smartlock</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Name</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, Type: device, ID: {2477abba-874b-4c48-b543-7b911ff215b3})</extracomment>
|
||||||
|
<translation>Naam</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>MAC address</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, Type: device, ID: {30976794-6066-4f72-8135-6d50499247a5})</extracomment>
|
||||||
|
<translation>MAC address</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Serial number</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, Type: device, ID: {ea51d911-f94a-4d2d-97fd-9f1d4c6519bf})</extracomment>
|
||||||
|
<translation>Serienummer</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Connected changed</source>
|
||||||
|
<extracomment>The name of the EventType ({d5fbd774-4e87-4c7c-8c92-f094352897f6}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Verbonden gewijzigd</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Connected</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: connected, ID: {d5fbd774-4e87-4c7c-8c92-f094352897f6})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({d5fbd774-4e87-4c7c-8c92-f094352897f6}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Verbonden</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Battery critical changed</source>
|
||||||
|
<extracomment>The name of the EventType ({e9ffe14f-b71d-44cd-96c7-eb90fe243e13}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Batterij kritiek gewijzigd</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Battery critical</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: batteryCritical, ID: {e9ffe14f-b71d-44cd-96c7-eb90fe243e13})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({e9ffe14f-b71d-44cd-96c7-eb90fe243e13}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Batterij kritiek</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Status changed</source>
|
||||||
|
<extracomment>The name of the EventType ({a47dec8b-4df2-4acd-895b-bfeacbcb6f2e}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Status gewijzigd</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Status</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: status, ID: {a47dec8b-4df2-4acd-895b-bfeacbcb6f2e})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({a47dec8b-4df2-4acd-895b-bfeacbcb6f2e}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Status</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Mode changed</source>
|
||||||
|
<extracomment>The name of the EventType ({4e291e6a-2b90-4e6c-bad4-5400e6db4f05}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Modus gewijzigd</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Mode</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: mode, ID: {4e291e6a-2b90-4e6c-bad4-5400e6db4f05})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({4e291e6a-2b90-4e6c-bad4-5400e6db4f05}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Modus</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Trigger changed</source>
|
||||||
|
<extracomment>The name of the EventType ({b67bc7a6-2d6b-46e8-8e59-e4eab2e5292a}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Trigger gewijzigd</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Trigger</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: trigger, ID: {b67bc7a6-2d6b-46e8-8e59-e4eab2e5292a})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({b67bc7a6-2d6b-46e8-8e59-e4eab2e5292a}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Trigger</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Hardware revision changed</source>
|
||||||
|
<extracomment>The name of the EventType ({b1b0eafb-e5f2-47d3-bd18-74a21129f9b5}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Hardware-revisie gewijzigd</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Hardware revision</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: hardwareRevision, ID: {b1b0eafb-e5f2-47d3-bd18-74a21129f9b5})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({b1b0eafb-e5f2-47d3-bd18-74a21129f9b5}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Hardware-revisie</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Firmware revision changed</source>
|
||||||
|
<extracomment>The name of the EventType ({9dc0115b-cdb4-472c-961b-b182f77a576f}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Firmware-revisie gewijzigd</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Firmware revision</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: firmwareRevision, ID: {9dc0115b-cdb4-472c-961b-b182f77a576f})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({9dc0115b-cdb4-472c-961b-b182f77a576f}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Firmware-revisie</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Lock</source>
|
||||||
|
<extracomment>The name of the ActionType ({55d25891-89b9-4a9f-a2b3-774eccf30183}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Vergrendelen</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Unlock</source>
|
||||||
|
<extracomment>The name of the ActionType ({76e96738-5336-4b9a-87f7-1822307b5a39}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Ontgrendelen</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Refresh</source>
|
||||||
|
<extracomment>The name of the ActionType ({7c9d5c5d-d8c1-424b-8620-daa3a757576b}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Vernieuwen</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>State changed</source>
|
||||||
|
<extracomment>The name of the EventType ({07a09bd8-b342-4e33-92cd-0af21f8689fa}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Status gewijzigd</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>State</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: state, ID: {07a09bd8-b342-4e33-92cd-0af21f8689fa})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({07a09bd8-b342-4e33-92cd-0af21f8689fa}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Status</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Open door</source>
|
||||||
|
<extracomment>The name of the ActionType ({45be8d24-17c3-422b-b264-381e673bb3c8}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Deur openen</translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
</TS>
|
||||||
169
nuki/translations/e5806d75-a40e-4766-a272-5a3a8d3ed625-pt.ts
Normal file
169
nuki/translations/e5806d75-a40e-4766-a272-5a3a8d3ed625-pt.ts
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!DOCTYPE TS>
|
||||||
|
<TS version="2.1" language="pt">
|
||||||
|
<context>
|
||||||
|
<name>DevicePluginNuki</name>
|
||||||
|
<message>
|
||||||
|
<source>Device is already in use.</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Bluetooth is not available on this system.</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Bluetooth device not found.</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Please press the Nuki button for 5 seconds in order to activate the pairing mode before you continue.</source>
|
||||||
|
<translation type="unfinished">Pressione o botão Nuki durante 5 segundos para ativar o modo de emparelhamento antes de continuar.</translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
<context>
|
||||||
|
<name>Nuki</name>
|
||||||
|
<message>
|
||||||
|
<source>Nuki</source>
|
||||||
|
<extracomment>The name of the vendor ({bf313b83-2ac5-4d22-bf0e-c13d3b4caf52})
|
||||||
|
----------
|
||||||
|
The name of the plugin Nuki ({e5806d75-a40e-4766-a272-5a3a8d3ed625})</extracomment>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Smartlock</source>
|
||||||
|
<extracomment>The name of the DeviceClass ({4a1cc5d9-9b44-4632-8db0-66d64efd4767})</extracomment>
|
||||||
|
<translation>Smartlock</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Name</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, Type: device, ID: {2477abba-874b-4c48-b543-7b911ff215b3})</extracomment>
|
||||||
|
<translation>Nome</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>MAC address</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, Type: device, ID: {30976794-6066-4f72-8135-6d50499247a5})</extracomment>
|
||||||
|
<translation>Endereço MAC</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Serial number</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, Type: device, ID: {ea51d911-f94a-4d2d-97fd-9f1d4c6519bf})</extracomment>
|
||||||
|
<translation>Número de série</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Connected changed</source>
|
||||||
|
<extracomment>The name of the EventType ({d5fbd774-4e87-4c7c-8c92-f094352897f6}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Conectado alterado</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Connected</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: connected, ID: {d5fbd774-4e87-4c7c-8c92-f094352897f6})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({d5fbd774-4e87-4c7c-8c92-f094352897f6}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Conectado</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Battery critical changed</source>
|
||||||
|
<extracomment>The name of the EventType ({e9ffe14f-b71d-44cd-96c7-eb90fe243e13}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Bateria crítica alterada</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Battery critical</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: batteryCritical, ID: {e9ffe14f-b71d-44cd-96c7-eb90fe243e13})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({e9ffe14f-b71d-44cd-96c7-eb90fe243e13}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Bateria crítica</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Status changed</source>
|
||||||
|
<extracomment>The name of the EventType ({a47dec8b-4df2-4acd-895b-bfeacbcb6f2e}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Estado alterado</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Status</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: status, ID: {a47dec8b-4df2-4acd-895b-bfeacbcb6f2e})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({a47dec8b-4df2-4acd-895b-bfeacbcb6f2e}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Estado</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Mode changed</source>
|
||||||
|
<extracomment>The name of the EventType ({4e291e6a-2b90-4e6c-bad4-5400e6db4f05}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Modo alterado</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Mode</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: mode, ID: {4e291e6a-2b90-4e6c-bad4-5400e6db4f05})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({4e291e6a-2b90-4e6c-bad4-5400e6db4f05}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Modo</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Trigger changed</source>
|
||||||
|
<extracomment>The name of the EventType ({b67bc7a6-2d6b-46e8-8e59-e4eab2e5292a}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Acionamento alterado</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Trigger</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: trigger, ID: {b67bc7a6-2d6b-46e8-8e59-e4eab2e5292a})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({b67bc7a6-2d6b-46e8-8e59-e4eab2e5292a}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Acionamento</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Hardware revision changed</source>
|
||||||
|
<extracomment>The name of the EventType ({b1b0eafb-e5f2-47d3-bd18-74a21129f9b5}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Revisão de hardware alterada</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Hardware revision</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: hardwareRevision, ID: {b1b0eafb-e5f2-47d3-bd18-74a21129f9b5})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({b1b0eafb-e5f2-47d3-bd18-74a21129f9b5}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Revisão de hardware</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Firmware revision changed</source>
|
||||||
|
<extracomment>The name of the EventType ({9dc0115b-cdb4-472c-961b-b182f77a576f}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Revisão de firmware alterada</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Firmware revision</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: firmwareRevision, ID: {9dc0115b-cdb4-472c-961b-b182f77a576f})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({9dc0115b-cdb4-472c-961b-b182f77a576f}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Revisão de firmware</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Lock</source>
|
||||||
|
<extracomment>The name of the ActionType ({55d25891-89b9-4a9f-a2b3-774eccf30183}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Bloquear</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Unlock</source>
|
||||||
|
<extracomment>The name of the ActionType ({76e96738-5336-4b9a-87f7-1822307b5a39}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Desbloquear</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Refresh</source>
|
||||||
|
<extracomment>The name of the ActionType ({7c9d5c5d-d8c1-424b-8620-daa3a757576b}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Atualizar</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>State changed</source>
|
||||||
|
<extracomment>The name of the EventType ({07a09bd8-b342-4e33-92cd-0af21f8689fa}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Estado alterado</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>State</source>
|
||||||
|
<extracomment>The name of the ParamType (DeviceClass: nuki, EventType: state, ID: {07a09bd8-b342-4e33-92cd-0af21f8689fa})
|
||||||
|
----------
|
||||||
|
The name of the StateType ({07a09bd8-b342-4e33-92cd-0af21f8689fa}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Estado</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Open door</source>
|
||||||
|
<extracomment>The name of the ActionType ({45be8d24-17c3-422b-b264-381e673bb3c8}) of DeviceClass nuki</extracomment>
|
||||||
|
<translation>Abrir porta</translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
</TS>
|
||||||
@ -30,6 +30,7 @@ PLUGIN_DIRS = \
|
|||||||
mqttclient \
|
mqttclient \
|
||||||
netatmo \
|
netatmo \
|
||||||
networkdetector \
|
networkdetector \
|
||||||
|
nuki \
|
||||||
onewire \
|
onewire \
|
||||||
openuv \
|
openuv \
|
||||||
openweathermap \
|
openweathermap \
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user