diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 00000000..7581b25b
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "sonos/sonos/noson"]
+ path = sonos/sonos/noson
+ url = https://github.com/janbar/noson.git
diff --git a/modbuscommander/translations/7dda1b6d-c37e-4c9f-a696-1666f9de66e6-en_US.ts b/modbuscommander/translations/7dda1b6d-c37e-4c9f-a696-1666f9de66e6-en_US.ts
new file mode 100644
index 00000000..f7f66d85
--- /dev/null
+++ b/modbuscommander/translations/7dda1b6d-c37e-4c9f-a696-1666f9de66e6-en_US.ts
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/nymea-plugins.pro b/nymea-plugins.pro
index 9addd3f2..53c48709 100644
--- a/nymea-plugins.pro
+++ b/nymea-plugins.pro
@@ -40,6 +40,7 @@ PLUGIN_DIRS = \
serialportcommander \
simulation \
snapd \
+ sonos \
tasmota \
tcpcommander \
texasinstruments \
diff --git a/serialportcommander/translations/fe93a12e-36f4-4015-8019-26b659817773-en_US.ts b/serialportcommander/translations/fe93a12e-36f4-4015-8019-26b659817773-en_US.ts
new file mode 100644
index 00000000..f7f66d85
--- /dev/null
+++ b/serialportcommander/translations/fe93a12e-36f4-4015-8019-26b659817773-en_US.ts
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/sonos/devicepluginsonos.cpp b/sonos/devicepluginsonos.cpp
new file mode 100644
index 00000000..2499fc21
--- /dev/null
+++ b/sonos/devicepluginsonos.cpp
@@ -0,0 +1,279 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * *
+ * Copyright (C) 2019 Bernhard Trinnes . *
+ * *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#include "devicepluginsonos.h"
+#include "devices/device.h"
+#include "plugininfo.h"
+
+
+#include
+
+#include
+#include
+
+DevicePluginSonos::DevicePluginSonos()
+{
+
+}
+
+DevicePluginSonos::~DevicePluginSonos()
+{
+ hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer);
+}
+
+void DevicePluginSonos::init()
+{
+ qDebug(dcSonos) << "Nymea sonos plug-in using libnoson Copyright (C) 2018 Jean-Luc Barriere";
+ m_sonosSystem = new SONOS::System(nullptr, nullptr);
+}
+
+Device::DeviceSetupStatus DevicePluginSonos::setupDevice(Device *device)
+{
+ if (device->deviceClassId() == sonosDeviceClassId) {
+ if (!m_sonosSystem->Discover()) {
+ return Device::DeviceSetupStatusFailure;
+ }
+ QString zoneName = device->paramValue(sonosDeviceZoneNameParamTypeId).toString();
+
+ SONOS::ZoneList zones = m_sonosSystem->GetZoneList();
+
+ for(SONOS::ZoneList::const_iterator iz = zones.begin(); iz != zones.end(); ++iz) {
+
+ if (iz->second->GetZoneName().c_str() == zoneName) {
+ if (!m_sonosSystem->ConnectZone(iz->second, nullptr, nullptr)) {
+ qCDebug(dcSonos()) << "Failed connecting to zone" << zoneName;
+ return Device::DeviceSetupStatusFailure;
+ }
+ }
+ }
+ device->setStateValue(sonosConnectedStateTypeId, true);
+ }
+ return Device::DeviceSetupStatusSuccess;
+}
+
+void DevicePluginSonos::postSetupDevice(Device *device)
+{
+ if (device->deviceClassId() == sonosDeviceClassId) {
+ SONOS::ZonePtr pl = m_sonosSystem->GetConnectedZone();
+ uint8_t volume;
+ uint8_t mute;
+ for (SONOS::Zone::iterator ip = pl->begin(); ip != pl->end(); ++ip) {
+ if (!m_sonosSystem->GetPlayer()->GetVolume((*ip)->GetUUID(), &volume)) {
+ qWarning(dcSonos()) << "Could not get volume for" << (*ip)->GetHost().c_str();
+ } else {
+ device->setStateValue(sonosVolumeStateTypeId, volume);
+ }
+
+ if (!m_sonosSystem->GetPlayer()->GetMute((*ip)->GetUUID(), &mute)) {
+ qWarning(dcSonos()) << "Could not get mute state for" << (*ip)->GetHost().c_str();
+ } else {
+ device->setStateValue(sonosMuteStateTypeId, mute);
+ }
+ }
+
+ while(m_sonosSystem->GetPlayer()->TransportPropertyEmpty());
+
+ SONOS::AVTProperty properties = m_sonosSystem->GetPlayer()->GetTransportProperty();
+
+ qDebug(dcSonos()) << "Transport Status" << properties.TransportStatus.c_str();
+ qDebug(dcSonos()) << "Transport State" << properties.TransportState.c_str();
+ if (properties.TransportState.c_str() == "PLAYING") {
+ device->setStateValue(sonosPlaybackStatusStateTypeId, "Playing");
+ } else if (properties.TransportState.c_str() == "PAUSED") {
+ device->setStateValue(sonosPlaybackStatusStateTypeId, "Paused");
+ } else if (properties.TransportState.c_str() == "STOPPED") {
+ device->setStateValue(sonosPlaybackStatusStateTypeId, "Stopped");
+ }
+
+ qDebug(dcSonos()) << "AVTransport URI" << properties.AVTransportURI.c_str();
+ qDebug(dcSonos()) << "AVTransportTitle" << properties.AVTransportURIMetaData->GetValue("dc:title").c_str();
+ qDebug(dcSonos()) << "Current Track" << properties.CurrentTrack;
+ qDebug(dcSonos()) << "Current Track Duration" << properties.CurrentTrackDuration.c_str();
+ qDebug(dcSonos()) << "Current Track URI" << properties.CurrentTrackURI.c_str();
+ //device->setStateValue(sonosArtworkStateTypeId, properties.CurrentTrackURI.c_str());
+ qDebug(dcSonos()) << "Current Track Title" << properties.CurrentTrackMetaData->GetValue("dc:title").c_str();
+ device->setStateValue(sonosTitleStateTypeId, properties.CurrentTrackMetaData->GetValue("dc:title").c_str());
+ qDebug(dcSonos()) << "Current Track Album" << properties.CurrentTrackMetaData->GetValue("upnp:album").c_str();
+ qDebug(dcSonos()) << "Current Track Artist" << properties.CurrentTrackMetaData->GetValue("dc:creator").c_str();
+ device->setStateValue(sonosArtistStateTypeId, properties.CurrentTrackMetaData->GetValue("dc:creator").c_str());
+ qDebug(dcSonos()) << "Current Crossfade Mode" << properties.CurrentCrossfadeMode.c_str();
+ qDebug(dcSonos()) << "Current Play Mode" << properties.CurrentPlayMode.c_str();
+ qDebug(dcSonos()) << "Current TransportActions" << properties.CurrentTransportActions.c_str();
+ qDebug(dcSonos()) << "Number Of Tracks" << properties.NumberOfTracks;
+ qDebug(dcSonos()) << "Alarm Running" << properties.r_AlarmRunning.c_str();
+ qDebug(dcSonos()) << "Alarm ID Running" << properties.r_AlarmIDRunning.c_str();
+ qDebug(dcSonos()) << "Alarm Logged Start Time" << properties.r_AlarmLoggedStartTime.c_str();
+ qDebug(dcSonos()) << "AlarmState" << properties.r_AlarmState.c_str();
+ }
+}
+
+void DevicePluginSonos::deviceRemoved(Device *device)
+{
+ qCDebug(dcSonos) << "Delete " << device->name();
+ if (myDevices().empty()) {
+ delete m_sonosSystem;
+ }
+}
+
+Device::DeviceError DevicePluginSonos::discoverDevices(const DeviceClassId &deviceClassId, const ParamList ¶ms)
+{
+ Q_UNUSED(params)
+ Q_UNUSED(deviceClassId)
+
+
+ if (!m_sonosSystem->Discover()) {
+ return Device::DeviceErrorDeviceNotFound;
+ }
+
+ QList descriptors;
+ SONOS::ZoneList zones = m_sonosSystem->GetZoneList();
+
+ for (SONOS::ZoneList::const_iterator it = zones.begin(); it != zones.end(); ++it) {
+ qDebug(dcSonos()) << "Found zone" << it->second->GetZoneName().c_str();
+ DeviceDescriptor descriptor(sonosDeviceClassId, it->second->GetZoneName().c_str());
+ ParamList params;
+ params << Param(sonosDeviceZoneNameParamTypeId, it->second->GetZoneName().c_str());
+ descriptor.setParams(params);
+ descriptors << descriptor;
+ }
+ emit devicesDiscovered(sonosDeviceClassId, descriptors);
+ return Device::DeviceErrorAsync;
+}
+
+Device::DeviceError DevicePluginSonos::executeAction(Device *device, const Action &action)
+{
+ Q_UNUSED(action)
+ if (device->deviceClassId() == sonosDeviceClassId) {
+
+ if (action.actionTypeId() == sonosPlayActionTypeId) {
+ if (!m_sonosSystem->GetPlayer()->Play()) {
+ return Device::DeviceErrorHardwareFailure;
+ }
+ return Device::DeviceErrorNoError;
+ }
+
+ if (action.actionTypeId() == sonosShuffleActionTypeId) {
+ SONOS::PlayMode_t mode = SONOS::PlayMode_t::PlayMode_NORMAL;;
+ if (action.param(sonosShuffleActionShuffleParamTypeId).value().toBool()) {
+ mode = SONOS::PlayMode_t::PlayMode_NORMAL;
+ }
+ if (!m_sonosSystem->GetPlayer()->SetPlayMode(mode)) {
+ return Device::DeviceErrorHardwareFailure;
+ }
+ return Device::DeviceErrorNoError;
+ }
+
+ if (action.actionTypeId() == sonosRepeatActionTypeId) {
+ SONOS::PlayMode_t mode;
+ if (action.param(sonosRepeatActionRepeatParamTypeId).value().toString() == "None") {
+ mode = SONOS::PlayMode_t::PlayMode_NORMAL;
+ } else if (action.param(sonosShuffleActionShuffleParamTypeId).value().toString() == "One") {
+ mode = SONOS::PlayMode_t::PlayMode_REPEAT_ONE;
+ } else if (action.param(sonosShuffleActionShuffleParamTypeId).value().toString() == "All") {
+ mode = SONOS::PlayMode_t::PlayMode_REPEAT_ALL;
+ } else {
+ return Device::DeviceErrorHardwareFailure;
+ }
+
+ if (!m_sonosSystem->GetPlayer()->SetPlayMode(mode)) {
+ return Device::DeviceErrorHardwareFailure;
+ }
+ return Device::DeviceErrorNoError;
+ }
+
+ if (action.actionTypeId() == sonosPauseActionTypeId) {
+ if (!m_sonosSystem->GetPlayer()->Pause()) {
+ return Device::DeviceErrorHardwareFailure;
+ }
+ return Device::DeviceErrorNoError;
+ }
+
+ if (action.actionTypeId() == sonosStopActionTypeId) {
+ if (!m_sonosSystem->GetPlayer()->Stop()) {
+ return Device::DeviceErrorHardwareFailure;
+ }
+ return Device::DeviceErrorNoError;
+ }
+
+ if (action.actionTypeId() == sonosMuteActionTypeId) {
+ bool mute = action.param(sonosMuteActionMuteParamTypeId).value().toBool();
+
+ SONOS::ZonePtr pl = m_sonosSystem->GetConnectedZone();
+ for (SONOS::Zone::iterator ip = pl->begin(); ip != pl->end(); ++ip) {
+ if (!m_sonosSystem->GetPlayer()->SetMute((*ip)->GetUUID(), mute)) {
+ qWarning(dcSonos()) << "Could not set mute state for" << (*ip)->GetHost().c_str();
+ return Device::DeviceErrorHardwareFailure;
+ }
+ }
+ return Device::DeviceErrorNoError;
+ }
+
+ if (action.actionTypeId() == sonosSkipNextActionTypeId) {
+ if (!m_sonosSystem->GetPlayer()->Next()) {
+ return Device::DeviceErrorHardwareFailure;
+ }
+ return Device::DeviceErrorNoError;
+ }
+
+ if (action.actionTypeId() == sonosSkipBackActionTypeId) {
+ if(!m_sonosSystem->GetPlayer()->Previous()) {
+ return Device::DeviceErrorHardwareFailure;
+ }
+ return Device::DeviceErrorNoError;
+ }
+
+ if (action.actionTypeId() == sonosSkipBackActionTypeId) {
+ int volume = action.param(sonosVolumeActionVolumeParamTypeId).value().toInt();
+
+ SONOS::ZonePtr pl = m_sonosSystem->GetConnectedZone();
+ for (SONOS::Zone::iterator ip = pl->begin(); ip != pl->end(); ++ip) {
+ if (!m_sonosSystem->GetPlayer()->SetVolume((*ip)->GetUUID(), volume)) {
+ qWarning(dcSonos()) << "Could not set volume for" << (*ip)->GetHost().c_str();
+ return Device::DeviceErrorHardwareFailure;
+ }
+ }
+ return Device::DeviceErrorNoError;
+ }
+ return Device::DeviceErrorActionTypeNotFound;
+ }
+ return Device::DeviceErrorDeviceClassNotFound;
+}
+
+void DevicePluginSonos::onPluginTimer()
+{
+}
+
+void DevicePluginSonos::handleEventCB(void* handle)
+{
+ Q_UNUSED(handle);
+ /*unsigned char mask = m_sonosSystem->LastEvents();
+ if ((mask & SONOS::SVCEvent_TransportChanged))
+ qDebug(dcSonos()) << "Event Transport changed";
+ if ((mask & SONOS::SVCEvent_AlarmClockChanged))
+ qDebug(dcSonos()) << "Alarm clock changed";
+ if ((mask & SONOS::SVCEvent_ZGTopologyChanged))
+ qDebug(dcSonos()) << "ZG Topology changed";
+ if ((mask & SONOS::SVCEvent_ContentDirectoryChanged))
+ qDebug(dcSonos()) << "Content directory changed";
+ if ((mask & SONOS::SVCEvent_RenderingControlChanged))
+ qDebug(dcSonos()) << "Rendering control changed";*/
+}
diff --git a/sonos/devicepluginsonos.h b/sonos/devicepluginsonos.h
new file mode 100644
index 00000000..169cd1eb
--- /dev/null
+++ b/sonos/devicepluginsonos.h
@@ -0,0 +1,69 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * *
+ * Copyright (C) 2019 Bernhard Trinnes . *
+ * *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#ifndef DEVICEPLUGINSONOS_H
+#define DEVICEPLUGINSONOS_H
+
+#include "devices/deviceplugin.h"
+#include "plugintimer.h"
+
+#include
+
+#include
+#include
+
+
+class DevicePluginSonos : public DevicePlugin
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID "io.nymea.DevicePlugin" FILE "devicepluginsonos.json")
+ Q_INTERFACES(DevicePlugin)
+
+public:
+ explicit DevicePluginSonos();
+ ~DevicePluginSonos() override;
+
+ void init() override;
+ Device::DeviceSetupStatus setupDevice(Device *device) override;
+ void postSetupDevice(Device *device) override;
+ void deviceRemoved(Device *device) override;
+ Device::DeviceError discoverDevices(const DeviceClassId &deviceClassId, const ParamList ¶ms) override;
+ Device::DeviceError executeAction(Device *device, const Action &action) override;
+
+private:
+ SONOS::System *m_sonosSystem = nullptr;
+ PluginTimer *m_pluginTimer;
+
+ static void handleEventCB(void *handle);
+
+private slots:
+ void onPluginTimer();
+ /*void onConnectionChanged();
+ void onStateChanged();
+ void onActionExecuted(int actionId, bool success);
+ void versionDataReceived(const QVariantMap &data);
+ void onSetupFinished(const QVariantMap &data);
+
+ void onPlaybackStatusChanged(const QString &playbackStatus);*/
+};
+
+#endif // DEVICEPLUGINSONOS_H
diff --git a/sonos/devicepluginsonos.json b/sonos/devicepluginsonos.json
new file mode 100644
index 00000000..4e239fb7
--- /dev/null
+++ b/sonos/devicepluginsonos.json
@@ -0,0 +1,176 @@
+{
+ "id": "cdb07719-c445-4fa5-9c7a-564ee02a4412",
+ "name": "Sonos",
+ "displayName": "Sonos",
+ "vendors": [
+ {
+ "id": "30a60752-d06f-4ec9-a4e1-9810a5d22fa3",
+ "name": "sonos",
+ "displayName": "Sonos",
+ "deviceClasses": [
+ {
+ "id": "22df416d-7732-44f1-b6b9-e41296211178",
+ "name": "sonos",
+ "displayName": "Sonos",
+ "interfaces": ["extendedvolumecontroller", "mediametadataprovider", "shufflerepeat", "connectable"],
+ "createMethods": ["discovery"],
+ "paramTypes": [
+ {
+ "id": "defc44cd-2ffb-4af1-b348-d6a3474c7515",
+ "name": "zoneName",
+ "displayName": "Zone name",
+ "type" : "QString"
+ }
+ ],
+ "stateTypes": [
+ {
+ "id": "09dfbd40-c97c-4a20-9ecd-f80e389a4864",
+ "name": "connected",
+ "displayName": "connected",
+ "displayNameEvent": "connected changed",
+ "defaultValue": false,
+ "type": "bool"
+ },
+ {
+ "id": "bc98cdb0-4d0e-48ca-afc7-922e49bb7813",
+ "name": "mute",
+ "displayName": "mute",
+ "displayNameEvent": "mute changed",
+ "displayNameAction": "Set mute",
+ "type": "bool",
+ "defaultValue": true,
+ "writable": true
+ },
+ {
+ "id": "9dfe5d78-4c3f-497c-bab1-bb9fdf7e93a9",
+ "name": "volume",
+ "displayName": "volume",
+ "displayNameEvent": "volume changed",
+ "displayNameAction": "Set volume",
+ "unit": "Percentage",
+ "type": "int",
+ "minValue": 0,
+ "maxValue": 100,
+ "defaultValue": 50,
+ "writable": true
+ },
+ {
+ "id": "2dd512b7-40c2-488e-8d4f-6519edaa6f74",
+ "name": "playbackStatus",
+ "displayName": "playback status",
+ "type": "QString",
+ "possibleValues": ["Playing", "Paused", "Stopped"],
+ "defaultValue": "Stopped",
+ "displayNameEvent": "playback status changed",
+ "displayNameAction": "set playback status",
+ "writable": true
+ },
+ {
+ "id": "f2209fec-cceb-46ad-8189-4caf42166e6b",
+ "type": "QString",
+ "name": "title",
+ "displayName": "Title",
+ "displayNameEvent": "Title changed",
+ "defaultValue": ""
+ },
+ {
+ "id": "8cb920a3-3bf1-4231-92d4-8ac27e7b3d65",
+ "type": "QString",
+ "name": "artist",
+ "displayName": "Artist",
+ "displayNameEvent": "Artist changed",
+ "defaultValue": ""
+ },
+ {
+ "id": "ce399eec-9f6a-4903-9916-0e90e38b255e",
+ "type": "QString",
+ "name": "collection",
+ "displayName": "Collection",
+ "displayNameEvent": "Collection changed",
+ "defaultValue": ""
+ },
+ {
+ "id": "44304c82-c2f6-433b-b62b-815382617d0b",
+ "type": "QString",
+ "name": "artwork",
+ "displayName": "Artwork",
+ "displayNameEvent": "Artwork changed",
+ "defaultValue": ""
+ },
+ {
+ "id": "5913aa2a-629d-4de5-bf44-a4a1f130c118",
+ "type": "bool",
+ "name": "shuffle",
+ "displayName": "Shuffle",
+ "displayNameEvent": "Shuffle changed",
+ "displayNameAction": "Set shuffle",
+ "defaultValue": false,
+ "writable": true
+ },
+ {
+ "id": "bc02c28e-3f5d-4de4-b9b5-c0b1576c6e7e",
+ "type": "QString",
+ "name": "repeat",
+ "displayName": "Repeat",
+ "displayNameEvent": "Repeat changed",
+ "displayNameAction": "Set repeat",
+ "possibleValues": ["None", "One", "All"],
+ "defaultValue": "None",
+ "writable": true
+ }
+ ],
+ "eventTypes": [
+ {
+ "id": "2535a1eb-7643-4874-98f6-b027fdff6311",
+ "name": "onPlayerPlay",
+ "displayName": "player play"
+ },
+ {
+ "id": "99498b1c-e9c0-480a-9e91-662ee79ba976",
+ "name": "onPlayerPause",
+ "displayName": "player pause"
+ },
+ {
+ "id": "a02ce255-3abb-435d-a92e-7f99c952ecb2",
+ "name": "onPlayerStop",
+ "displayName": "player stop"
+ }
+ ],
+ "actionTypes": [
+ {
+ "id": "a180807d-1265-4831-9d86-a421767418dd",
+ "name": "skipBack",
+ "displayName": "skip back"
+ },
+ {
+ "id": "7e70b47b-7e79-4521-be34-04a3c427e5b1",
+ "name": "fastRewind",
+ "displayName": "rewind"
+ },
+ {
+ "id": "ae3cbe03-ee3e-410e-abbd-efabc2402198",
+ "name": "stop",
+ "displayName": "stop"
+ },
+ {
+ "id": "4d2ee668-a2e3-4795-8b96-0c800b703b46",
+ "name": "play",
+ "displayName": "play"
+ },
+ {
+ "id": "3cf341cb-fe63-40bc-a450-9678d18e91e3",
+ "name": "pause",
+ "displayName": "pause"
+ },
+ {
+ "id": "85d7126a-b123-4a28-aeb4-d84bcfb4d14f",
+ "name": "skipNext",
+ "displayName": "skipNext"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
+
diff --git a/sonos/noson b/sonos/noson
new file mode 160000
index 00000000..569f2b30
--- /dev/null
+++ b/sonos/noson
@@ -0,0 +1 @@
+Subproject commit 569f2b3087d2ba39bfe796bd6dd20b767fb68eb7
diff --git a/unipi/.crossbuilder/cache.conf b/unipi/.crossbuilder/cache.conf
new file mode 100644
index 00000000..5aa1fd54
--- /dev/null
+++ b/unipi/.crossbuilder/cache.conf
@@ -0,0 +1,2 @@
+TARGET_ARCH=armhf
+TARGET_UBUNTU=stretch