/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Copyright 2013 - 2025, nymea GmbH * Contact: contact@nymea.io * * This file is part of nymea. * This project including source code and documentation is protected by * copyright law, and remains the property of nymea GmbH. All rights, including * reproduction, publication, editing and translation, are reserved. The use of * this project is subject to the terms of a license agreement to be concluded * with nymea GmbH in accordance with the terms of use of nymea GmbH, available * under https://nymea.io/license * * GNU Lesser General Public License Usage * Alternatively, this project may be redistributed and/or modified under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation; version 3. This project 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 project. If not, see . * * For any further details and any questions please contact us under * contact@nymea.io or see our FAQ/Licensing Information on * https://nymea.io/license/faq * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "flowercare.h" #include "extern-plugininfo.h" #include FlowerCare::FlowerCare(BluetoothLowEnergyDevice *thing, QObject *parent): QObject(parent), m_bluetoothDevice(thing) { connect(m_bluetoothDevice, &BluetoothLowEnergyDevice::connectedChanged, this, &FlowerCare::onConnectedChanged); connect(m_bluetoothDevice, &BluetoothLowEnergyDevice::servicesDiscoveryFinished, this, &FlowerCare::onServiceDiscoveryFinished); } void FlowerCare::refreshData() { qCDebug(dcFlowerCare()) << "Connecting to device"; m_bluetoothDevice->connectDevice(); } BluetoothLowEnergyDevice *FlowerCare::btDevice() const { return m_bluetoothDevice; } void FlowerCare::onConnectedChanged(bool connected) { qCDebug(dcFlowerCare()) << "Connection changed:" << connected; if (!connected) { m_sensorService->deleteLater(); m_sensorService = nullptr; } } void FlowerCare::onServiceDiscoveryFinished() { BluetoothLowEnergyDevice *btDev = static_cast(sender()); qCDebug(dcFlowerCare()) << "have service uuids" << btDev->serviceUuids(); m_sensorService = btDev->controller()->createServiceObject(sensorServiceUuid, this); connect(m_sensorService, &QLowEnergyService::stateChanged, this, &FlowerCare::onSensorServiceStateChanged); connect(m_sensorService, &QLowEnergyService::characteristicRead, this, &FlowerCare::onSensorServiceCharacteristicRead); connect(m_sensorService, &QLowEnergyService::characteristicChanged, this, &FlowerCare::onSensorServiceCharacteristicChanged); m_sensorService->discoverDetails(); } void FlowerCare::onSensorServiceStateChanged(const QLowEnergyService::ServiceState &state) { if (state != QLowEnergyService::ServiceDiscovered) { return; } // printServiceDetails(m_sensorService); QLowEnergyCharacteristic batteryFirmwareCharacteristic = m_sensorService->characteristic(batteryFirmwareCharacteristicUuid); if (!batteryFirmwareCharacteristic.isValid()) { qCWarning(dcFlowerCare()) << "Invalid battery/firmware characteristic."; emit failed(); return; } QByteArray value = batteryFirmwareCharacteristic.value(); #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) QDataStream stream(&value, QDataStream::ReadOnly); #else QDataStream stream(&value, QIODevice::ReadOnly); #endif stream.setByteOrder(QDataStream::LittleEndian); stream >> m_batteryLevel; QString firmwareVersionString = value.right(5); qCDebug(dcFlowerCare()) << "Battery level:" << m_batteryLevel; qCDebug(dcFlowerCare()) << "Firmware version:" << firmwareVersionString; if (firmwareVersionString >= "2.6.6") { QLowEnergyCharacteristic sensorControlCharacteristic = m_sensorService->characteristic(sensorControlCharacteristicUuid); m_sensorService->writeCharacteristic(sensorControlCharacteristic, QByteArray::fromHex("A01F")); qCDebug(dcFlowerCare()) << "Wrote to handle 0x0033: A01F"; } m_sensorDataCharacteristic = m_sensorService->characteristic(sensorDataCharacteristicUuid); if (!m_sensorDataCharacteristic.isValid()) { qCWarning(dcFlowerCare()) << "Invalid sensor data characteristic."; } // Enable notifications #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) QLowEnergyDescriptor notificationDescriptor = m_sensorDataCharacteristic.descriptor(QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration); #else QLowEnergyDescriptor notificationDescriptor = m_sensorDataCharacteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration); #endif m_sensorService->writeDescriptor(notificationDescriptor, QByteArray::fromHex("0100")); // Read the data manually // Sometimes if we read the light intensty right now we might get wrong values // because the LED flashes upon connect. Let's not read it manually but instead wait for // the values to come in with the notification // m_sensorService->readCharacteristic(m_sensorDataCharacteristic); } void FlowerCare::onSensorServiceCharacteristicRead(const QLowEnergyCharacteristic &characteristic, const QByteArray &value) { #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) qCDebug(dcFlowerCare()) << "Characteristic read" << characteristic.uuid().toString() << value.toHex(); #else qCDebug(dcFlowerCare()) << "Characteristic read" << QString::number(characteristic.handle(), 16) << value.toHex(); #endif if (characteristic != m_sensorDataCharacteristic) { return; } processSensorData(value); } void FlowerCare::onSensorServiceCharacteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &value) { #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) qCDebug(dcFlowerCare()) << "Notification received" << characteristic.uuid().toString() << value.toHex(); #else qCDebug(dcFlowerCare()) << "Notification received" << QString::number(characteristic.handle(), 16) << value.toHex(); #endif if (characteristic != m_sensorDataCharacteristic) { return; } processSensorData(value); } void FlowerCare::printServiceDetails(QLowEnergyService *service) const { foreach (const QLowEnergyCharacteristic &characteristic, service->characteristics()) { #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) qCDebug(dcFlowerCare()).nospace() << "C: --> " << characteristic.uuid().toString() << " (" << " Name: " << characteristic.name() << "): " << characteristic.value() << ", " << characteristic.value().toHex(); #else qCDebug(dcFlowerCare()).nospace() << "C: --> " << characteristic.uuid().toString() << " (" << characteristic.handle() << " Name: " << characteristic.name() << "): " << characteristic.value() << ", " << characteristic.value().toHex(); #endif foreach (const QLowEnergyDescriptor &descriptor, characteristic.descriptors()) { #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) qCDebug(dcFlowerCare()).nospace() << "D: --> " << descriptor.uuid().toString() << " (" << " Name: " << descriptor.name() << "): " << descriptor.value() << ", " << descriptor.value().toHex(); #else qCDebug(dcFlowerCare()).nospace() << "D: --> " << descriptor.uuid().toString() << " (" << descriptor.handle() << " Name: " << descriptor.name() << "): " << descriptor.value() << ", " << descriptor.value().toHex(); #endif } } } void FlowerCare::processSensorData(const QByteArray &data) { QByteArray copy = data; #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) QDataStream stream(©, QDataStream::ReadOnly); #else QDataStream stream(©, QIODevice::ReadOnly); #endif stream.setByteOrder(QDataStream::LittleEndian); qint16 temp; stream >> temp; qint8 skip; stream >> skip; quint32 lux; stream >> lux; qint8 moisture; stream >> moisture; qint16 fertility; stream >> fertility; qCDebug(dcFlowerCare()) << "Temperature:" << temp << "Lux:" << lux << "moisture:" << moisture << "fertility" << fertility; m_bluetoothDevice->disconnectDevice(); emit finished(m_batteryLevel, 1.0 * temp / 10, lux, moisture, fertility); }