From c5d826bec3b52777cc6238dbc2c1a89bb9de011b Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Mon, 2 Jul 2018 23:49:45 +0200 Subject: [PATCH] add flowercare plugin --- flowercare/devicepluginflowercare.cpp | 197 ++++++++++++++++++ flowercare/devicepluginflowercare.h | 68 ++++++ flowercare/devicepluginflowercare.json | 102 +++++++++ flowercare/flowercare.cpp | 144 +++++++++++++ flowercare/flowercare.h | 58 ++++++ flowercare/flowercare.pro | 13 ++ ...2106a-3407-4e89-a27a-1c890d78bee7-en_US.ts | 4 + nymea-plugins.pro | 2 +- 8 files changed, 587 insertions(+), 1 deletion(-) create mode 100644 flowercare/devicepluginflowercare.cpp create mode 100644 flowercare/devicepluginflowercare.h create mode 100644 flowercare/devicepluginflowercare.json create mode 100644 flowercare/flowercare.cpp create mode 100644 flowercare/flowercare.h create mode 100644 flowercare/flowercare.pro create mode 100644 flowercare/translations/74e2106a-3407-4e89-a27a-1c890d78bee7-en_US.ts diff --git a/flowercare/devicepluginflowercare.cpp b/flowercare/devicepluginflowercare.cpp new file mode 100644 index 00000000..67ea02e5 --- /dev/null +++ b/flowercare/devicepluginflowercare.cpp @@ -0,0 +1,197 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2018 Michael Zanetti * + * * + * This file is part of nymea. * + * * + * 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 * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + +/* + * ServieUUIDs: + * {00001204-0000-1000-8000-00805f9b34fb} + * {00001206-0000-1000-8000-00805f9b34fb} + * {00001800-0000-1000-8000-00805f9b34fb} + * {00001801-0000-1000-8000-00805f9b34fb} + * {0000fe95-0000-1000-8000-00805f9b34fb} + * {0000fef5-0000-1000-8000-00805f9b34fb} + */ +#include "plugininfo.h" +#include "devicemanager.h" +#include "hardware/bluetoothlowenergy/bluetoothlowenergymanager.h" +#include "devicepluginflowercare.h" +#include "flowercare.h" + +DevicePluginFlowercare::DevicePluginFlowercare() +{ + +} + +DevicePluginFlowercare::~DevicePluginFlowercare() +{ + if (m_reconnectTimer) { + hardwareManager()->pluginTimerManager()->unregisterTimer(m_reconnectTimer); + } +} + +void DevicePluginFlowercare::init() +{ +} + +DeviceManager::DeviceError DevicePluginFlowercare::discoverDevices(const DeviceClassId &deviceClassId, const ParamList ¶ms) +{ + Q_UNUSED(params) + Q_UNUSED(deviceClassId) + + if (!hardwareManager()->bluetoothLowEnergyManager()->available()) + return DeviceManager::DeviceErrorHardwareNotAvailable; + + if (!hardwareManager()->bluetoothLowEnergyManager()->enabled()) + return DeviceManager::DeviceErrorHardwareNotAvailable; + + BluetoothDiscoveryReply *reply = hardwareManager()->bluetoothLowEnergyManager()->discoverDevices(); + connect(reply, &BluetoothDiscoveryReply::finished, this, &DevicePluginFlowercare::onBluetoothDiscoveryFinished); + return DeviceManager::DeviceErrorAsync; +} + +DeviceManager::DeviceSetupStatus DevicePluginFlowercare::setupDevice(Device *device) +{ + qCDebug(dcFlowerCare) << "Setting up Flower care" << device->name() << device->params(); + + if (device->deviceClassId() == flowerCareDeviceClassId) { + QBluetoothAddress address = QBluetoothAddress(device->paramValue(flowerCareMacParamTypeId).toString()); + QString name = device->paramValue(flowerCareNameParamTypeId).toString(); + QBluetoothDeviceInfo deviceInfo = QBluetoothDeviceInfo(address, name, 0); + + BluetoothLowEnergyDevice *bluetoothDevice = hardwareManager()->bluetoothLowEnergyManager()->registerDevice(deviceInfo, QLowEnergyController::PublicAddress); + FlowerCare *flowerCare = new FlowerCare(bluetoothDevice, this); + connect(flowerCare, &FlowerCare::finished, this, &DevicePluginFlowercare::onSensorDataReceived); + m_list.insert(device, flowerCare); + + m_refreshMinutes[flowerCare] = 0; + + if (!m_reconnectTimer) { + m_reconnectTimer = hardwareManager()->pluginTimerManager()->registerTimer(); + connect(m_reconnectTimer, &PluginTimer::timeout, this, &DevicePluginFlowercare::onPluginTimer); + } + return DeviceManager::DeviceSetupStatusSuccess; + } + return DeviceManager::DeviceSetupStatusFailure; +} + +void DevicePluginFlowercare::postSetupDevice(Device *device) +{ + FlowerCare *flowerCare = m_list.value(device); + flowerCare->refreshData(); +} + +void DevicePluginFlowercare::deviceRemoved(Device *device) +{ + FlowerCare *flowerCare = m_list.take(device); + if (!flowerCare) { + return; + } + + hardwareManager()->bluetoothLowEnergyManager()->unregisterDevice(flowerCare->btDevice()); + flowerCare->deleteLater(); + + if (m_list.isEmpty() && m_reconnectTimer) { + hardwareManager()->pluginTimerManager()->unregisterTimer(m_reconnectTimer); + m_reconnectTimer = nullptr; + } +} + +DeviceManager::DeviceError DevicePluginFlowercare::executeAction(Device *device, const Action &action) +{ + Q_UNUSED(device) + Q_UNUSED(action) + return DeviceManager::DeviceErrorActionTypeNotFound; +} + +bool DevicePluginFlowercare::verifyExistingDevices(const QBluetoothDeviceInfo &deviceInfo) +{ + foreach (Device *device, m_list.keys()) { + if (device->paramValue(flowerCareMacParamTypeId).toString() == deviceInfo.address().toString()) + return true; + } + + return false; +} + +void DevicePluginFlowercare::onPluginTimer() +{ + + foreach (FlowerCare *flowerCare, m_list) { + if (--m_refreshMinutes[flowerCare] <= 0) { + qCDebug(dcFlowerCare()) << "Refreshing" << flowerCare->btDevice()->address(); + flowerCare->refreshData(); + } else { + qCDebug(dcFlowerCare()) << "Not refreshing" << flowerCare->btDevice()->address() << " Next refresh in" << m_refreshMinutes[flowerCare] << "minutes"; + } + + // If we had 2 or more failed connection attempts, mark it as disconnected + if (m_refreshMinutes[flowerCare] < -2) { + qCDebug(dcFlowerCare()) << "Failed to refresh for"<< (m_refreshMinutes[flowerCare] * -1) << "minutes. Marking as unreachable"; + m_list.key(flowerCare)->setStateValue(flowerCareConnectedStateTypeId, false); + } + } +} + +void DevicePluginFlowercare::onBluetoothDiscoveryFinished() +{ + BluetoothDiscoveryReply *reply = static_cast(sender()); + if (reply->error() != BluetoothDiscoveryReply::BluetoothDiscoveryReplyErrorNoError) { + qCWarning(dcFlowerCare()) << "Bluetooth discovery error:" << reply->error(); + reply->deleteLater(); + emit devicesDiscovered(flowerCareDeviceClassId, QList()); + return; + } + + QList deviceDescriptors; + qCDebug(dcFlowerCare()) << "Discovery finished"; + foreach (const QBluetoothDeviceInfo &deviceInfo, reply->discoveredDevices()) { + qCDebug(dcFlowerCare()) << "Discovered device" << deviceInfo.name(); + if (deviceInfo.name().contains("Flower care")) { + if (!verifyExistingDevices(deviceInfo)) { + DeviceDescriptor descriptor(flowerCareDeviceClassId, "Flower Care", deviceInfo.address().toString()); + ParamList params; + params.append(Param(flowerCareNameParamTypeId, deviceInfo.name())); + params.append(Param(flowerCareMacParamTypeId, deviceInfo.address().toString())); + descriptor.setParams(params); + deviceDescriptors.append(descriptor); + } + } + } + + reply->deleteLater(); + emit devicesDiscovered(flowerCareDeviceClassId, deviceDescriptors); +} + +void DevicePluginFlowercare::onSensorDataReceived(quint8 batteryLevel, double degreeCelsius, double lux, double moisture, double fertility) +{ + FlowerCare *flowerCare = static_cast(sender()); + Device *device = m_list.key(flowerCare); + device->setStateValue(flowerCareConnectedStateTypeId, true); + device->setStateValue(flowerCareBatteryLevelStateTypeId, batteryLevel); + device->setStateValue(flowerCareBatteryCriticalStateTypeId, batteryLevel <= 10); + device->setStateValue(flowerCareTemperatureStateTypeId, degreeCelsius); + device->setStateValue(flowerCareLightIntensityStateTypeId, lux); + device->setStateValue(flowerCareMoistureStateTypeId, moisture); + device->setStateValue(flowerCareConductivityStateTypeId, fertility); + + m_refreshMinutes[flowerCare] = 20; +} diff --git a/flowercare/devicepluginflowercare.h b/flowercare/devicepluginflowercare.h new file mode 100644 index 00000000..00d6b411 --- /dev/null +++ b/flowercare/devicepluginflowercare.h @@ -0,0 +1,68 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2018 Michael Zanetti * + * * + * This file is part of nymea. * + * * + * 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 * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef DEVICEPLUGINFLOWERCARE_H +#define DEVICEPLUGINFLOWERCARE_H + + +#include +#include +#include "plugin/deviceplugin.h" +#include "devicemanager.h" +#include "plugintimer.h" +#include "hardware/bluetoothlowenergy/bluetoothlowenergydevice.h" + +class FlowerCare; + +class DevicePluginFlowercare : public DevicePlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "io.nymea.DevicePlugin" FILE "devicepluginflowercare.json") + Q_INTERFACES(DevicePlugin) + +public: + explicit DevicePluginFlowercare(); + ~DevicePluginFlowercare(); + + void init() override; + DeviceManager::DeviceError discoverDevices(const DeviceClassId &deviceClassId, const ParamList ¶ms) override; + DeviceManager::DeviceSetupStatus setupDevice(Device *device) override; + void postSetupDevice(Device *device) override; + void deviceRemoved(Device *device) override; + + DeviceManager::DeviceError executeAction(Device *device, const Action &action) override; + +private: + PluginTimer *m_reconnectTimer = nullptr; + QHash m_list; + QHash m_refreshMinutes; + + bool verifyExistingDevices(const QBluetoothDeviceInfo &deviceInfo); + +private slots: + void onPluginTimer(); + void onBluetoothDiscoveryFinished(); + void onSensorDataReceived(quint8 batteryLevel, double degreeCelsius, double lux, double moisture, double fertility); + +}; + +#endif // DEVICEPLUGINFLOWERCARE_H diff --git a/flowercare/devicepluginflowercare.json b/flowercare/devicepluginflowercare.json new file mode 100644 index 00000000..c7dc0763 --- /dev/null +++ b/flowercare/devicepluginflowercare.json @@ -0,0 +1,102 @@ +{ + "displayName": "Flower Care", + "name": "flowerCare", + "id": "74e2106a-3407-4e89-a27a-1c890d78bee7", + "vendors": [ + { + "id": "f037aa1a-f764-42f9-a613-338e683e4da5", + "name": "xiaomi", + "displayName": "Xiaomi", + "deviceClasses": [ + { + "id": "297e2efa-168c-4eb8-bf3f-f8694dc0a2b9", + "name": "flowerCare", + "displayName": "Flower Care", + "createMethods": ["discovery"], + "interfaces": ["temperaturesensor", "lightsensor", "moisturesensor", "conductivitysensor", "connectable", "batterylevel"], + "paramTypes": [ + { + "id": "65f95e3c-59fe-47d6-ba4a-2469e5f554e2", + "name": "name", + "displayName": "Name", + "type": "QString", + "inputType": "TextLine" + }, + { + "id": "fe2dba0f-2a47-463e-a645-bcd55cf09750", + "name": "mac", + "displayName": "MAC address", + "type": "QString", + "inputType": "MacAddress" + } + ], + "stateTypes": [ + { + "id": "6254103a-ff8b-4f62-afda-6b5be1c933ec", + "name": "connected", + "displayName": "Reachable", + "displayNameEvent": "Reachable changed", + "type": "bool", + "cached": false, + "defaultValue": false + }, + { + "id": "801a00ab-ce3b-4b10-9236-2f845c469a26", + "name": "batteryLevel", + "displayName": "Battery level", + "displayNameEvent": "Battery level changed", + "type": "int", + "minValue": 0, + "maxValue": 100, + "defaultValue": 0 + }, + { + "id": "700a83e6-9fbb-4330-b00e-e9d0874b4f66", + "name": "batteryCritical", + "displayName": "Battery level critical", + "displayNameEvent": "Battery level critical changed", + "type": "bool", + "defaultValue": false + }, + { + "id": "4577a032-bafd-49ca-8625-6dcf90819e88", + "name": "temperature", + "displayName": "Temperature", + "displayNameEvent": "Temperature changed", + "type": "double", + "unit": "DegreeCelsius", + "defaultValue": 0 + }, + { + "id": "5e3ac82a-1dda-4399-888e-529b391a814a", + "name": "moisture", + "displayName": "Soil moisture", + "displayNameEvent": "Soil moisture changed", + "type": "double", + "minValue": 0, + "maxValue": 100, + "unit": "Percentage", + "defaultValue": 0 + }, + { + "id": "7e0a3b3d-72f4-4547-bd1e-2711be61c1a7", + "name": "lightIntensity", + "displayName": "Light intensity", + "displayNameEvent": "Light intensity changed", + "type": "double", + "defaultValue": 0 + }, + { + "id": "f77608b3-8b5b-43a9-aab6-0dc35c694ec3", + "name": "conductivity", + "displayName": "Conductivity", + "displayNameEvent": "Conductivity changed", + "type": "double", + "defaultValue": 0 + } + ] + } + ] + } + ] +} diff --git a/flowercare/flowercare.cpp b/flowercare/flowercare.cpp new file mode 100644 index 00000000..330cb7f8 --- /dev/null +++ b/flowercare/flowercare.cpp @@ -0,0 +1,144 @@ +#include "flowercare.h" + +#include "extern-plugininfo.h" + +#include +#include + +FlowerCare::FlowerCare(BluetoothLowEnergyDevice *device, QObject *parent): + QObject(parent), + m_bluetoothDevice(device) +{ + 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(); + QDataStream stream(&value, QIODevice::ReadOnly); + stream.setByteOrder(QDataStream::LittleEndian); + stream >> m_batteryLevel; + + QString firmwareVersionString = value.right(5); + qCDebug(dcFlowerCare()) << "Battery level:" << m_batteryLevel; + + QVersionNumber firmwareVersion = QVersionNumber::fromString(firmwareVersionString); + qCDebug(dcFlowerCare()) << "Firmware version:" << firmwareVersion; + + if (firmwareVersion >= QVersionNumber::fromString("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 + QLowEnergyDescriptor notificationDescriptor = m_sensorDataCharacteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration); + 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) +{ + qCDebug(dcFlowerCare()) << "Characteristic read" << QString::number(characteristic.handle(), 16) << value.toHex(); + if (characteristic != m_sensorDataCharacteristic) { + return; + } + processSensorData(value); +} + +void FlowerCare::onSensorServiceCharacteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &value) +{ + qCDebug(dcFlowerCare()) << "Notification received" << QString::number(characteristic.handle(), 16) << value.toHex(); + if (characteristic != m_sensorDataCharacteristic) { + return; + } + processSensorData(value); +} + + +void FlowerCare::printServiceDetails(QLowEnergyService *service) const +{ + foreach (const QLowEnergyCharacteristic &characteristic, service->characteristics()) { + qCDebug(dcFlowerCare()).nospace() << "C: --> " << characteristic.uuid().toString() << " (" << characteristic.handle() << " Name: " << characteristic.name() << "): " << characteristic.value() << ", " << characteristic.value().toHex(); + foreach (const QLowEnergyDescriptor &descriptor, characteristic.descriptors()) { + qCDebug(dcFlowerCare()).nospace() << "D: --> " << descriptor.uuid().toString() << " (" << descriptor.handle() << " Name: " << descriptor.name() << "): " << descriptor.value() << ", " << descriptor.value().toHex(); + } + } +} + +void FlowerCare::processSensorData(const QByteArray &data) +{ + QByteArray copy = data; + QDataStream stream(©, QIODevice::ReadOnly); + stream.setByteOrder(QDataStream::LittleEndian); + quint16 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); +} diff --git a/flowercare/flowercare.h b/flowercare/flowercare.h new file mode 100644 index 00000000..8424da48 --- /dev/null +++ b/flowercare/flowercare.h @@ -0,0 +1,58 @@ +#ifndef FLOWERCARE_H +#define FLOWERCARE_H + +#include + +#include "hardware/bluetoothlowenergy/bluetoothlowenergydevice.h" + +static QBluetoothUuid sensorServiceUuid = QBluetoothUuid(QUuid("00001204-0000-1000-8000-00805f9b34fb")); + +// Contains Battery level and firmware version +static QBluetoothUuid batteryFirmwareCharacteristicUuid = QBluetoothUuid(QUuid("00001a02-0000-1000-8000-00805f9b34fb")); + +// Need to write 0xA01F to this +static QBluetoothUuid sensorControlCharacteristicUuid = QBluetoothUuid(QUuid("00001a00-0000-1000-8000-00805f9b34fb")); + +// contains sensor data +static QBluetoothUuid sensorDataCharacteristicUuid = QBluetoothUuid(QUuid("00001a01-0000-1000-8000-00805f9b34fb")); + + +class FlowerCare : public QObject +{ + Q_OBJECT +public: + explicit FlowerCare(BluetoothLowEnergyDevice* device, QObject *parent = nullptr); + + void refreshData(); + + BluetoothLowEnergyDevice* btDevice() const; + +signals: + void finished(quint8 batteryLevel, double degreeCelsius, double lux, double moisture, double fertility); + void failed(); + +private slots: + void onConnectedChanged(bool connected); + void onServiceDiscoveryFinished(); + + void onSensorServiceStateChanged(const QLowEnergyService::ServiceState &state); + void onSensorServiceCharacteristicRead(const QLowEnergyCharacteristic &characteristic, const QByteArray &value); + void onSensorServiceCharacteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &value); + +private: + void printServiceDetails(QLowEnergyService* service) const; + + void processSensorData(const QByteArray &data); + + BluetoothLowEnergyDevice *m_bluetoothDevice; + + // Services + QLowEnergyService *m_sensorService = nullptr; + QLowEnergyCharacteristic m_sensorDataCharacteristic; + + // cache + quint8 m_batteryLevel = 0; + +}; + +#endif // FLOWERCARE_H diff --git a/flowercare/flowercare.pro b/flowercare/flowercare.pro new file mode 100644 index 00000000..3b9962bd --- /dev/null +++ b/flowercare/flowercare.pro @@ -0,0 +1,13 @@ +include(../plugins.pri) + +QT += bluetooth + +TARGET = $$qtLibraryTarget(nymea_devicepluginflowercare) + +SOURCES += \ + devicepluginflowercare.cpp \ + flowercare.cpp + +HEADERS += \ + devicepluginflowercare.h \ + flowercare.h diff --git a/flowercare/translations/74e2106a-3407-4e89-a27a-1c890d78bee7-en_US.ts b/flowercare/translations/74e2106a-3407-4e89-a27a-1c890d78bee7-en_US.ts new file mode 100644 index 00000000..f7f66d85 --- /dev/null +++ b/flowercare/translations/74e2106a-3407-4e89-a27a-1c890d78bee7-en_US.ts @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/nymea-plugins.pro b/nymea-plugins.pro index 55792e9b..bd9fb34e 100644 --- a/nymea-plugins.pro +++ b/nymea-plugins.pro @@ -37,7 +37,7 @@ PLUGIN_DIRS = \ keba \ remotessh \ dweetio \ - + flowercare \ CONFIG+=all