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