diff --git a/debian/control b/debian/control
index a83af2d5f..a119c5edd 100644
--- a/debian/control
+++ b/debian/control
@@ -656,6 +656,17 @@ Description: nymea.io plugin for osdomotics
This package will install the nymea.io plugin for osdomotics
+Package: nymea-plugin-owlet
+Architecture: any
+Depends: ${shlibs:Depends},
+ ${misc:Depends},
+ nymea-plugins-translations,
+Description: nymea.io plugin for nymea:owlet
+ nymea:owlet is a firmware for microcontrollers (Arduino/ESP8266/ESP32) which
+ exposes pins of the microcontroller to nymea and allows using them for
+ whatever purpose like moodlights, control relays, inputs etc.
+
+
Package: nymea-plugin-philipshue
Architecture: any
Depends: ${shlibs:Depends},
diff --git a/debian/nymea-plugin-owlet.install.in b/debian/nymea-plugin-owlet.install.in
new file mode 100644
index 000000000..2e5fe9706
--- /dev/null
+++ b/debian/nymea-plugin-owlet.install.in
@@ -0,0 +1 @@
+usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginowlet.so
diff --git a/nymea-plugins.pro b/nymea-plugins.pro
index 1118b516f..e5c8eb57b 100644
--- a/nymea-plugins.pro
+++ b/nymea-plugins.pro
@@ -45,6 +45,7 @@ PLUGIN_DIRS = \
openuv \
openweathermap \
osdomotics \
+ owlet \
philipshue \
pushbullet \
pushnotifications \
diff --git a/owlet/integrationpluginowlet.cpp b/owlet/integrationpluginowlet.cpp
new file mode 100644
index 000000000..db30ac4c2
--- /dev/null
+++ b/owlet/integrationpluginowlet.cpp
@@ -0,0 +1,248 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2021, 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 "integrationpluginowlet.h"
+#include "plugininfo.h"
+#include "owletclient.h"
+
+#include "hardwaremanager.h"
+#include "platform/platformzeroconfcontroller.h"
+#include "network/zeroconf/zeroconfservicebrowser.h"
+#include "network/zeroconf/zeroconfserviceentry.h"
+
+#include
+#include
+
+static QHash idParamTypeMap = {
+ { digitalOutputThingClassId, digitalOutputThingOwletIdParamTypeId },
+ { digitalInputThingClassId, digitalInputThingOwletIdParamTypeId },
+ { ws2812ThingClassId, ws2812ThingOwletIdParamTypeId }
+};
+
+IntegrationPluginOwlet::IntegrationPluginOwlet()
+{
+}
+
+void IntegrationPluginOwlet::init()
+{
+ m_zeroConfBrowser = hardwareManager()->zeroConfController()->createServiceBrowser("_nymea-owlet._tcp");
+}
+
+void IntegrationPluginOwlet::discoverThings(ThingDiscoveryInfo *info)
+{
+ foreach (const ZeroConfServiceEntry &entry, m_zeroConfBrowser->serviceEntries()) {
+ qCDebug(dcOwlet()) << "Found owlet:" << entry;
+ ThingDescriptor descriptor(info->thingClassId(), entry.name(), entry.txt("platform"));
+ descriptor.setParams(ParamList() << Param(idParamTypeMap.value(info->thingClassId()), entry.txt("id")));
+ foreach (Thing *existingThing, myThings().filterByParam(idParamTypeMap.value(info->thingClassId()), entry.txt("id"))) {
+ descriptor.setThingId(existingThing->id());
+ break;
+ }
+ info->addThingDescriptor(descriptor);
+ }
+ info->finish(Thing::ThingErrorNoError);
+}
+
+
+void IntegrationPluginOwlet::setupThing(ThingSetupInfo *info)
+{
+ Thing *thing = info->thing();
+
+ QHostAddress ip;
+ int port = 5555;
+ foreach (const ZeroConfServiceEntry &entry, m_zeroConfBrowser->serviceEntries()) {
+ if (entry.txt("id") == info->thing()->paramValue(idParamTypeMap.value(info->thing()->thingClassId()))) {
+ ip = entry.hostAddress();
+ port = entry.port();
+ break;
+ }
+ }
+ // Try cached ip
+ if (ip.isNull()) {
+ pluginStorage()->beginGroup(thing->id().toString());
+ ip = QHostAddress(pluginStorage()->value("cachedIP").toString());
+ pluginStorage()->endGroup();
+ }
+
+ if (ip.isNull()) {
+ qCWarning(dcOwlet()) << "Can't find owlet in the local network.";
+ info->finish(Thing::ThingErrorHardwareNotAvailable);
+ return;
+ }
+
+ OwletClient *client = new OwletClient(this);
+
+ connect(client, &OwletClient::connected, info, [=](){
+ qCDebug(dcOwlet()) << "Connected to owleet";
+ m_clients.insert(thing, client);
+
+ if (thing->thingClassId() == digitalOutputThingClassId) {
+ QVariantMap params;
+ params.insert("id", thing->paramValue(digitalOutputThingPinParamTypeId).toInt());
+ params.insert("mode", "GPIOOutput");
+ client->sendCommand("GPIO.ConfigurePin", params);
+ }
+ if (thing->thingClassId() == digitalInputThingClassId) {
+ QVariantMap params;
+ params.insert("id", thing->paramValue(digitalInputThingPinParamTypeId).toInt());
+ params.insert("mode", "GPIOInput");
+ client->sendCommand("GPIO.ConfigurePin", params);
+ }
+ if (thing->thingClassId() == ws2812ThingClassId) {
+ QVariantMap params;
+ params.insert("id", thing->paramValue(ws2812ThingPinParamTypeId).toInt());
+ params.insert("mode", "WS2812");
+ params.insert("ledCount", thing->paramValue(ws2812ThingLedCountParamTypeId).toUInt());
+ params.insert("ledMode", "WS2812Mode" + thing->paramValue(ws2812ThingLedModeParamTypeId).toString());
+ params.insert("ledClock", "WS2812Clock" + thing->paramValue(ws2812ThingLedClockParamTypeId).toString());
+ client->sendCommand("GPIO.ConfigurePin", params);
+ }
+
+ info->finish(Thing::ThingErrorNoError);
+ });
+ connect(client, &OwletClient::error, info, [=](){
+ info->finish(Thing::ThingErrorHardwareFailure);
+ });
+ connect(client, &OwletClient::connected, thing, [=](){
+ thing->setStateValue("connected", true);
+ pluginStorage()->beginGroup(thing->id().toString());
+ pluginStorage()->setValue("cachedIP", ip.toString());
+ pluginStorage()->endGroup();
+ });
+ connect(client, &OwletClient::disconnected, thing, [=](){
+ thing->setStateValue("connected", false);
+ });
+
+ connect(client, &OwletClient::notificationReceived, this, [=](const QString &name, const QVariantMap ¶ms){
+ qCDebug(dcOwlet()) << "***Notif" << name << params;
+ if (thing->thingClassId() == digitalInputThingClassId) {
+ if (params.value("id").toInt() == thing->paramValue(digitalInputThingPinParamTypeId)) {
+ thing->setStateValue(digitalInputPowerStateTypeId, params.value("power").toBool());
+ }
+ }
+ if (thing->thingClassId() == digitalOutputThingClassId) {
+ if (params.value("id").toInt() == thing->paramValue(digitalOutputThingPinParamTypeId)) {
+ thing->setStateValue(digitalOutputPowerStateTypeId, params.value("power").toBool());
+ }
+ }
+ if (thing->thingClassId() == ws2812ThingClassId) {
+ if (name == "GPIO.PinChanged") {
+ if (params.contains("power")) {
+ thing->setStateValue(ws2812PowerStateTypeId, params.value("power").toBool());
+ }
+ if (params.contains("brightness")) {
+ thing->setStateValue(ws2812BrightnessStateTypeId, params.value("brightness").toInt());
+ }
+ if (params.contains("color")) {
+ thing->setStateValue(ws2812ColorStateTypeId, params.value("color").value());
+ }
+ if (params.contains("effect")) {
+ thing->setStateValue(ws2812EffectStateTypeId, params.value("effect").toInt());
+ }
+ }
+ }
+ });
+
+ client->connectToHost(ip, port);
+}
+
+
+void IntegrationPluginOwlet::executeAction(ThingActionInfo *info)
+{
+ if (info->thing()->thingClassId() == digitalOutputThingClassId) {
+ OwletClient *client = m_clients.value(info->thing());
+ QVariantMap params;
+ params.insert("id", info->thing()->paramValue(digitalOutputThingPinParamTypeId).toInt());
+ params.insert("power", info->action().paramValue(digitalOutputPowerActionPowerParamTypeId).toBool());
+ qCDebug(dcOwlet()) << "Sending ControlPin" << params;
+ int id = client->sendCommand("GPIO.ControlPin", params);
+ connect(client, &OwletClient::replyReceived, info, [=](int commandId, const QVariantMap ¶ms){
+ if (id != commandId) {
+ return;
+ }
+ qCDebug(dcOwlet()) << "reply from owlet:" << params;
+ QString error = params.value("error").toString();
+ if (error == "GPIOErrorNoError") {
+ info->thing()->setStateValue(digitalOutputPowerStateTypeId, info->action().paramValue(digitalOutputPowerActionPowerParamTypeId).toBool());
+ info->finish(Thing::ThingErrorNoError);
+ } else {
+ info->finish(Thing::ThingErrorHardwareFailure);
+ }
+ });
+
+ return;
+ }
+
+ if (info->thing()->thingClassId() == ws2812ThingClassId) {
+ OwletClient *client = m_clients.value(info->thing());
+ QVariantMap params;
+ params.insert("id", info->thing()->paramValue(ws2812ThingPinParamTypeId).toUInt());
+
+ if (info->action().actionTypeId() == ws2812PowerActionTypeId) {
+ params.insert("power", info->action().paramValue(ws2812PowerActionPowerParamTypeId).toBool());
+ }
+ if (info->action().actionTypeId() == ws2812BrightnessActionTypeId) {
+ params.insert("brightness", info->action().paramValue(ws2812BrightnessActionBrightnessParamTypeId).toInt());
+ }
+ if (info->action().actionTypeId() == ws2812ColorActionTypeId) {
+ QColor color = info->action().paramValue(ws2812ColorActionColorParamTypeId).value();
+ params.insert("color", (color.rgb() & 0xFFFFFF));
+ }
+ if (info->action().actionTypeId() == ws2812EffectActionTypeId) {
+ int effect = info->action().paramValue(ws2812EffectActionEffectParamTypeId).toInt();
+ params.insert("effect", effect);
+ }
+
+ int id = client->sendCommand("GPIO.ControlPin", params);
+ connect(client, &OwletClient::replyReceived, info, [=](int commandId, const QVariantMap ¶ms){
+ if (id != commandId) {
+ return;
+ }
+ qCDebug(dcOwlet()) << "reply from owlet:" << params;
+ QString error = params.value("error").toString();
+ if (error == "GPIOErrorNoError") {
+ info->finish(Thing::ThingErrorNoError);
+ } else {
+ info->finish(Thing::ThingErrorHardwareFailure);
+ }
+ });
+ return;
+ }
+
+
+
+ Q_ASSERT_X(false, "IntegrationPluginOwlet", "Not implemented");
+ info->finish(Thing::ThingErrorUnsupportedFeature);
+}
+
+void IntegrationPluginOwlet::thingRemoved(Thing *thing)
+{
+ Q_UNUSED(thing)
+}
diff --git a/owlet/integrationpluginowlet.h b/owlet/integrationpluginowlet.h
new file mode 100644
index 000000000..9d6337ab9
--- /dev/null
+++ b/owlet/integrationpluginowlet.h
@@ -0,0 +1,63 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2021, 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
+*
+* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#ifndef INTEGRATIONPLUGINOWLET_H
+#define INTEGRATIONPLUGINOWLET_H
+
+#include "integrations/integrationplugin.h"
+#include "extern-plugininfo.h"
+
+class ZeroConfServiceBrowser;
+class OwletClient;
+
+class IntegrationPluginOwlet: public IntegrationPlugin
+{
+ Q_OBJECT
+
+ Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginowlet.json")
+ Q_INTERFACES(IntegrationPlugin)
+
+public:
+ explicit IntegrationPluginOwlet();
+
+ void init() override;
+ void discoverThings(ThingDiscoveryInfo *info) override;
+ void setupThing(ThingSetupInfo *info) override;
+ void executeAction(ThingActionInfo *info) override;
+ void thingRemoved(Thing *thing) override;
+
+private:
+ ZeroConfServiceBrowser *m_zeroConfBrowser = nullptr;
+
+ QHash m_clients;
+
+};
+
+#endif // INTEGRATIONPLUGINOWLET_H
diff --git a/owlet/integrationpluginowlet.json b/owlet/integrationpluginowlet.json
new file mode 100644
index 000000000..7fef3d74e
--- /dev/null
+++ b/owlet/integrationpluginowlet.json
@@ -0,0 +1,226 @@
+{
+ "displayName": "nymea owlet",
+ "name": "owlet",
+ "id": "699a5b6d-d90f-4554-a8de-9205768a4a98",
+ "vendors": [
+ {
+ "displayName": "nymea GmbH",
+ "name": "nymea",
+ "id": "2062d64d-3232-433c-88bc-0d33c0ba2ba6",
+ "thingClasses": [
+ {
+ "id": "5a079c4e-9309-4d98-9ff1-9beeda210958",
+ "displayName": "Digital GPIO output on owlet",
+ "name": "digitalOutput",
+ "createMethods": ["discovery"],
+ "interfaces": ["power"],
+ "paramTypes": [
+ {
+ "id": "de8cda8f-b8f1-425d-ae16-fd0f5a885ca4",
+ "name": "owletId",
+ "displayName": "Owlet ID",
+ "type": "QString",
+ "defaultValue": ""
+ },
+ {
+ "id": "31dbcdea-04f3-4a0c-b131-7eda8a92c602",
+ "name": "pin",
+ "displayName": "Pin number",
+ "type": "uint",
+ "defaultValue": 1
+ }
+ ],
+ "stateTypes": [
+ {
+ "id": "dd97a6b1-e98e-4a60-b16a-b27240b91439",
+ "name": "power",
+ "displayName": "Power",
+ "type": "bool",
+ "defaultValue": false,
+ "writable": true,
+ "displayNameEvent": "Power changed",
+ "displayNameAction": "Set power",
+ "ioType": "digitalOutput"
+ }
+ ]
+ },
+ {
+ "id": "673512a3-75d8-44a6-9930-198c9f1a1f03",
+ "displayName": "Digital GPIO Input on owlet",
+ "name": "digitalInput",
+ "createMethods": ["discovery"],
+ "paramTypes": [
+ {
+ "id": "dd7eca3f-13f6-4320-aaaa-b0be8fbfeebf",
+ "name": "owletId",
+ "displayName": "Owlet ID",
+ "type": "QString",
+ "defaultValue": ""
+ },
+ {
+ "id": "f6b60a4b-e7a2-4328-884d-818b0e2a361e",
+ "name": "pin",
+ "displayName": "Pin number",
+ "type": "uint",
+ "defaultValue": 1
+ }
+ ],
+ "stateTypes": [
+ {
+ "id": "df1fbd9f-10b1-4788-a00e-de3f3f411cc6",
+ "name": "power",
+ "displayName": "Powered",
+ "type": "bool",
+ "defaultValue": false,
+ "displayNameEvent": "Powered changed",
+ "ioType": "digitalInput"
+ }
+ ]
+ },
+ {
+ "id": "76f4ef8e-8e17-4528-a667-3d3f5afdd6a7",
+ "name": "ws2812",
+ "displayName": "WS2812 on owlet",
+ "createMethods": ["discovery"],
+ "interfaces": ["colorlight", "wirelessconnectable"],
+ "paramTypes": [
+ {
+ "id": "8c00f42b-5d34-4595-8ae9-6f48056a8be0",
+ "name": "owletId",
+ "displayName": "Owlet ID",
+ "type": "QString",
+ "defaultValue": ""
+ },
+ {
+ "id": "d674ee68-7f24-4dec-a75a-647a083d3580",
+ "name": "pin",
+ "displayName": "Pin number",
+ "type": "uint",
+ "defaultValue": 1
+ },
+ {
+ "id": "6c6df8eb-cdf1-424c-b29d-60f7dd19ae41",
+ "name": "ledCount",
+ "displayName": "LED count",
+ "type": "uint",
+ "defaultValue": 1
+ },
+ {
+ "id": "69c7f0e5-fdc4-4f9f-a117-90f165af3178",
+ "name": "ledMode",
+ "displayName": "LED color mode",
+ "type": "QString",
+ "allowedValues": [
+ "RGB",
+ "RBG",
+ "GRB",
+ "GBR",
+ "BRG",
+ "BGR",
+ "WRGB",
+ "WRBG",
+ "WGRB",
+ "WGBR",
+ "WBRG",
+ "WBRG",
+ "RWGB",
+ "RWBG",
+ "RGWB",
+ "RGBW",
+ "RBWG",
+ "RBGW",
+ "GWRB",
+ "GWBR",
+ "GRWB",
+ "GRBW",
+ "GBWR",
+ "GBRW",
+ "BWRG",
+ "BWGR",
+ "BRWG",
+ "BRGW",
+ "BGWR",
+ "BGRW"
+ ],
+ "defaultValue": "RGB"
+ },
+ {
+ "id": "c4d99f98-1b46-4b38-bdd4-4bd5559dbb6f",
+ "name": "ledClock",
+ "displayName": "LED clock speed",
+ "type": "QString",
+ "allowedValues": ["400kHz", "800kHz"],
+ "defaultValue": "800kHz"
+ }
+ ],
+ "stateTypes": [
+ {
+ "id": "0dbdd49b-578d-4404-87d2-b5a921df6aa6",
+ "name": "connected",
+ "displayName": "Connected",
+ "displayNameEvent": "Connected or disconnected",
+ "type": "bool",
+ "defaultValue": false,
+ "cached": false
+ },
+ {
+ "id": "58a8b3ca-720c-458e-b045-b99b5aadabd7",
+ "name": "power",
+ "displayName": "Power",
+ "displayNameEvent": "Power changed",
+ "displayNameAction": "Set power",
+ "type": "bool",
+ "defaultValue": false,
+ "writable": true
+ },
+ {
+ "id": "b56a9368-db2a-4ee6-99de-9ee8e1ffebd3",
+ "name": "brightness",
+ "displayName": "Brightness",
+ "displayNameEvent": "Brightness changed",
+ "displayNameAction": "Set brightness",
+ "type": "int",
+ "minValue": 0,
+ "maxValue": 100,
+ "unit": "Percentage",
+ "defaultValue": 100,
+ "writable": true
+ },
+ {
+ "id": "684c9118-20f3-41a0-928e-b7290d40166d",
+ "name": "color",
+ "displayName": "Color",
+ "displayNameEvent": "Color changed",
+ "displayNameAction": "Set color",
+ "type": "QColor",
+ "defaultValue": "white",
+ "writable": true
+ },
+ {
+ "id": "f92ea731-a86e-49b5-955b-9c245c7f874f",
+ "name": "colorTemperature",
+ "displayName": "Color temperature",
+ "displayNameEvent": "Color temperature changed",
+ "displayNameAction": "Set color temperature",
+ "type": "int",
+ "minValue": 0,
+ "maxValue": 100,
+ "defaultValue": 50,
+ "writable": true
+ },
+ {
+ "id": "cb90f7bf-bcb0-42e8-a03b-442d84c5871f",
+ "name": "effect",
+ "displayName": "Effect",
+ "displayNameEvent": "Effect changed",
+ "displayNameAction": "Set effect",
+ "type": "int",
+ "defaultValue": 0,
+ "writable": true
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/owlet/owlet.pro b/owlet/owlet.pro
new file mode 100644
index 000000000..572785b2d
--- /dev/null
+++ b/owlet/owlet.pro
@@ -0,0 +1,13 @@
+include(../plugins.pri)
+
+QT += network
+
+SOURCES += \
+ integrationpluginowlet.cpp \
+ owletclient.cpp
+
+HEADERS += \
+ integrationpluginowlet.h \
+ owletclient.h
+
+
diff --git a/owlet/owletclient.cpp b/owlet/owletclient.cpp
new file mode 100644
index 000000000..c9ed5e7c8
--- /dev/null
+++ b/owlet/owletclient.cpp
@@ -0,0 +1,91 @@
+#include "owletclient.h"
+
+#include "extern-plugininfo.h"
+
+#include
+#include
+
+OwletClient::OwletClient(QObject *parent) : QObject(parent)
+{
+
+}
+
+void OwletClient::connectToHost(const QHostAddress &address, int port)
+{
+ if (m_socket) {
+ m_socket->abort();
+ m_socket->deleteLater();
+ }
+
+ m_socket = new QTcpSocket(this);
+ connect(m_socket, &QTcpSocket::connected, this, [this](){
+ emit connected();
+ });
+ connect(m_socket, &QTcpSocket::disconnected, this, [this, address, port](){
+ qCDebug(dcOwlet()) << "Disconnected from owleet";
+ emit disconnected();
+ QTimer::singleShot(1000, this, [=]{
+ connectToHost(address, port);
+ });
+
+ });
+ typedef void (QTcpSocket:: *errorSignal)(QAbstractSocket::SocketError);
+ connect(m_socket, static_cast(&QTcpSocket::error), this, [this](){
+ qCDebug(dcOwlet()) << "Error in owlet communication";
+ emit error();
+ });
+
+ connect(m_socket, &QTcpSocket::readyRead, this, [this](){
+ dataReceived(m_socket->readAll());
+ });
+ m_socket->connectToHost(address, port);
+}
+
+int OwletClient::sendCommand(const QString &method, const QVariantMap ¶ms)
+{
+ if (!m_socket) {
+ qCWarning(dcOwlet()) << "Not connected to owlet. Not sending command.";
+ return -1;
+ }
+
+ int id = ++m_commandId;
+
+ QVariantMap packet;
+ packet.insert("id", id);
+ packet.insert("method", method);
+ packet.insert("params", params);
+ m_socket->write(QJsonDocument::fromVariant(packet).toJson(QJsonDocument::Compact));
+ return id;
+}
+
+void OwletClient::dataReceived(const QByteArray &data)
+{
+ m_receiveBuffer.append(data);
+
+ int splitIndex = m_receiveBuffer.indexOf("}\n{") + 1;
+ if (splitIndex <= 0) {
+ splitIndex = m_receiveBuffer.length();
+ }
+ QJsonParseError error;
+ QJsonDocument jsonDoc = QJsonDocument::fromJson(m_receiveBuffer.left(splitIndex), &error);
+ if (error.error != QJsonParseError::NoError) {
+ // qWarning() << "Could not parse json data from nymea" << m_receiveBuffer.left(splitIndex) << error.errorString();
+ return;
+ }
+ // qDebug() << "received response" << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Indented));
+ m_receiveBuffer = m_receiveBuffer.right(m_receiveBuffer.length() - splitIndex - 1);
+ if (!m_receiveBuffer.isEmpty()) {
+ staticMetaObject.invokeMethod(this, "dataReceived", Qt::QueuedConnection, Q_ARG(QByteArray, QByteArray()));
+ }
+
+ QVariantMap packet = jsonDoc.toVariant().toMap();
+
+ if (packet.contains("notification")) {
+ qCDebug(dcOwlet()) << "Notification received:" << packet;
+ emit notificationReceived(packet.value("notification").toString(), packet.value("params").toMap());
+ } else if (packet.contains("id")) {
+ qCDebug(dcOwlet()) << "reply received:" << packet;
+ int id = packet.value("id").toInt();
+ emit replyReceived(id, packet.value("params").toMap());
+ }
+}
diff --git a/owlet/owletclient.h b/owlet/owletclient.h
new file mode 100644
index 000000000..289374dea
--- /dev/null
+++ b/owlet/owletclient.h
@@ -0,0 +1,37 @@
+#ifndef OWLETCLIENT_H
+#define OWLETCLIENT_H
+
+#include
+#include
+#include
+
+class OwletClient : public QObject
+{
+ Q_OBJECT
+public:
+ explicit OwletClient(QObject *parent = nullptr);
+
+ void connectToHost(const QHostAddress &address, int port);
+
+ int sendCommand(const QString &method, const QVariantMap ¶ms);
+
+signals:
+ void connected();
+ void disconnected();
+ void error();
+
+ void replyReceived(int commandId, const QVariantMap ¶ms);
+ void notificationReceived(const QString &name, const QVariantMap ¶ms);
+
+private slots:
+ void dataReceived(const QByteArray &data);
+
+private:
+ QTcpSocket *m_socket = nullptr;
+ int m_commandId = 0;
+
+ QByteArray m_receiveBuffer;
+
+};
+
+#endif // OWLETCLIENT_H