Merge PR #240: New integration plug-in: BluOS

This commit is contained in:
Jenkins nymea 2020-06-09 18:19:22 +02:00
commit e556fec5fd
14 changed files with 2367 additions and 0 deletions

24
bluos/README.md Normal file
View File

@ -0,0 +1,24 @@
# BluOS
This integration allows to control audio devices based on BluOS. BluOS is an operating system that can be found in products from Blusound, NAD Electronics, DALI Loudspeakers and others.
## Supported Things
* BluOS Player
* Multimedia control
* Volume
* Browsing Presets
* Browsing various music services
* Grouping speakers
* No internet or cloud connection required
## Requirements
* The BluOS device must be in the same local area network as nymea.
* TCP sockets on port 80 must not be blocked by the router.
* Blusound App to setup the speaker.
* The package "nymea-plugin-bluos" must be installed
## More
https://www.bluesound.com/

701
bluos/bluos.cpp Normal file
View File

@ -0,0 +1,701 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2020, 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 <https://www.gnu.org/licenses/>.
*
* 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 "bluos.h"
#include "extern-plugininfo.h"
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QUrlQuery>
#include <QXmlStreamReader>
BluOS::BluOS(NetworkAccessManager *networkmanager, QHostAddress hostAddress, int port, QObject *parent) :
QObject(parent),
m_hostAddress(hostAddress),
m_port(port),
m_networkManager(networkmanager)
{
}
int BluOS::port()
{
return m_port;
}
QHostAddress BluOS::hostAddress()
{
return m_hostAddress;
}
void BluOS::getStatus()
{
QUrl url;
url.setScheme("http");
url.setHost(m_hostAddress.toString());
url.setPort(m_port);
url.setPath("/Status");
QNetworkReply *reply = m_networkManager->get(QNetworkRequest(url));
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, this, [reply, this] {
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
// Check HTTP status code
if (status != 200 || reply->error() != QNetworkReply::NoError) {
if (reply->error() == QNetworkReply::HostNotFoundError) {
emit connectionChanged(false);
}
qCWarning(dcBluOS()) << "Request error:" << status << reply->errorString();
return;
}
emit connectionChanged(true);
QByteArray data = reply->readAll();
//qCDebug(dcBluOS()) << "Get Status:" << data;
parseState(data);
});
return;
}
QUuid BluOS::setVolume(uint volume)
{
QUuid requestId = QUuid::createUuid();
QUrlQuery query;
query.addQueryItem("level", QString::number(volume));
query.addQueryItem("tell_slaves", "off");
QUrl url;
url.setScheme("http");
url.setHost(m_hostAddress.toString());
url.setPort(m_port);
url.setPath("/Volume");
url.setQuery(query);
QNetworkReply *reply = m_networkManager->get(QNetworkRequest(url));
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, this, [requestId, reply, this] {
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
// Check HTTP status code
if (status != 200 || reply->error() != QNetworkReply::NoError) {
if (reply->error() == QNetworkReply::HostNotFoundError) {
emit connectionChanged(false);
}
emit actionExecuted(requestId, false);
qCWarning(dcBluOS()) << "Request error:" << status << reply->errorString();
return;
}
emit connectionChanged(true);
QXmlStreamReader xml;
xml.addData(reply->readAll());
if (xml.hasError()) {
qCDebug(dcBluOS()) << "XML Error:" << xml.errorString();
}
int volume = 0;
bool mute = false;
if (xml.readNextStartElement()) {
if (xml.name() == "volume") {
if(xml.attributes().hasAttribute("mute")) {
mute = xml.attributes().value("mute").toInt();
}
volume = xml.readElementText().toInt();
}
}
emit volumeReceived(volume, mute);
emit actionExecuted(requestId, true);
});
return requestId;
}
QUuid BluOS::setMute(bool mute)
{
QUuid requestId = QUuid::createUuid();
QUrlQuery query;
query.addQueryItem("mute", QString::number(mute));
query.addQueryItem("tell_slaves", "off");
QUrl url;
url.setScheme("http");
url.setHost(m_hostAddress.toString());
url.setPort(m_port);
url.setPath("/Volume");
url.setQuery(query);
QNetworkReply *reply = m_networkManager->get(QNetworkRequest(url));
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, this, [requestId, reply, this] {
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
// Check HTTP status code
if (status != 200 || reply->error() != QNetworkReply::NoError) {
if (reply->error() == QNetworkReply::HostNotFoundError) {
emit connectionChanged(false);
}
emit actionExecuted(requestId, false);
qCWarning(dcBluOS()) << "Request error:" << status << reply->errorString();
return;
}
emit connectionChanged(true);
emit actionExecuted(requestId, true);
});
return requestId;
}
QUuid BluOS::play()
{
return playBackControl(PlaybackCommand::Play);
}
QUuid BluOS::pause()
{
return playBackControl(PlaybackCommand::Pause);
}
QUuid BluOS::stop()
{
return playBackControl(PlaybackCommand::Stop);
}
QUuid BluOS::back()
{
return playBackControl(PlaybackCommand::Back);
}
QUuid BluOS::setShuffle(bool shuffle)
{
Q_UNUSED(shuffle)
QUuid requestId = QUuid::createUuid();
QUrl url;
url.setScheme("http");
url.setHost(m_hostAddress.toString());
url.setPort(m_port);
url.setPath("/Shuffle");
QUrlQuery query;
query.addQueryItem("state", QString::number(shuffle));
url.setQuery(query);
QNetworkReply *reply = m_networkManager->get(QNetworkRequest(url));
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, this, [requestId, reply, this] {
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
// Check HTTP status code
if (status != 200 || reply->error() != QNetworkReply::NoError) {
if (reply->error() == QNetworkReply::HostNotFoundError) {
emit connectionChanged(false);
}
emit actionExecuted(requestId, false);
qCWarning(dcBluOS()) << "Request error:" << status << reply->errorString();
return;
}
emit connectionChanged(true);
emit actionExecuted(requestId, true);
QXmlStreamReader xml;
xml.addData(reply->readAll());
if (xml.hasError()) {
qCDebug(dcBluOS()) << "XML Error:" << xml.errorString();
return;
}
if (xml.readNextStartElement()) {
if (xml.attributes().hasAttribute("shuffle")) {
bool shuffle = RepeatMode(xml.attributes().value("shuffle").toInt());
emit shuffleStateReceived(shuffle);
}
}
});
return requestId;
}
QUuid BluOS::setRepeat(RepeatMode repeatMode)
{
Q_UNUSED(repeatMode)
QUuid requestId = QUuid::createUuid();
QUrl url;
url.setScheme("http");
url.setHost(m_hostAddress.toString());
url.setPort(m_port);
url.setPath("/Repeat");
QUrlQuery query;
query.addQueryItem("state", QString::number(repeatMode));
url.setQuery(query);
QNetworkReply *reply = m_networkManager->get(QNetworkRequest(url));
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, this, [requestId, reply, this] {
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
// Check HTTP status code
if (status != 200 || reply->error() != QNetworkReply::NoError) {
if (reply->error() == QNetworkReply::HostNotFoundError) {
emit connectionChanged(false);
}
emit actionExecuted(requestId, false);
qCWarning(dcBluOS()) << "Request error:" << status << reply->errorString();
return;
}
emit connectionChanged(true);
emit actionExecuted(requestId, true);
QXmlStreamReader xml;
xml.addData(reply->readAll());
if (xml.hasError()) {
qCDebug(dcBluOS()) << "XML Error:" << xml.errorString();
return;
}
if (xml.readNextStartElement()) {
if (xml.name() == "playlist") {
if (xml.attributes().hasAttribute("repeat")) {
RepeatMode mode = RepeatMode(xml.attributes().value("repeat").toInt());
emit repeatModeReceived(mode);
}
}
}
});
return requestId;
}
QUuid BluOS::listPresets()
{
QUuid requestId = QUuid::createUuid();
QUrl url;
url.setScheme("http");
url.setHost(m_hostAddress.toString());
url.setPort(m_port);
url.setPath("/Presets");
QNetworkReply *reply = m_networkManager->get(QNetworkRequest(url));
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, this, [requestId, reply, this] {
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
// Check HTTP status code
if (status != 200 || reply->error() != QNetworkReply::NoError) {
if (reply->error() == QNetworkReply::HostNotFoundError) {
emit connectionChanged(false);
}
qCWarning(dcBluOS()) << "Request error:" << status << reply->errorString();
return;
}
emit connectionChanged(true);
QByteArray data = reply->readAll();
QXmlStreamReader xml;
xml.addData(data);
if (xml.hasError()) {
qCDebug(dcBluOS()) << "XML Error:" << xml.errorString();
return;
}
QList<Preset> presetList;
if (xml.readNextStartElement()) {
if (xml.name() == "presets") {
while(xml.readNextStartElement()){
if(xml.name() == "preset"){
Preset preset;
if (xml.attributes().hasAttribute("id")) {
preset.Id = xml.attributes().value("id").toInt();
}
if (xml.attributes().hasAttribute("name")) {
preset.Name = xml.attributes().value("name").toString();
}
if (xml.attributes().hasAttribute("url")) {
preset.Url = xml.attributes().value("url").toString();
}
qCDebug(dcBluOS()) << "Preset text" << xml.readElementText(); //apparently the text must be read so the xml parser recognises the next element
presetList.append(preset);
} else {
xml.skipCurrentElement();
}
}
}
}
emit presetsReceived(requestId, presetList);
});
return requestId;
}
QUuid BluOS::loadPreset(int preset)
{
QUuid requestId = QUuid::createUuid();
QUrl url;
url.setScheme("http");
url.setHost(m_hostAddress.toString());
url.setPort(m_port);
url.setPath("/Preset");
QUrlQuery query;
query.addQueryItem("id", QString::number(preset));
url.setQuery(query);
qCDebug(dcBluOS()) << "Loading preset" << url.toString();
QNetworkReply *reply = m_networkManager->get(QNetworkRequest(url));
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, this, [requestId, reply, this] {
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
// Check HTTP status code
if (status != 200 || reply->error() != QNetworkReply::NoError) {
if (reply->error() == QNetworkReply::HostNotFoundError) {
emit connectionChanged(false);
}
emit actionExecuted(requestId, false);
qCWarning(dcBluOS()) << "Request error:" << status << reply->errorString();
return;
}
emit connectionChanged(true);
emit actionExecuted(requestId, true);
});
return requestId;
}
QUuid BluOS::getSources()
{
QUuid requestId = QUuid::createUuid();
QUrl url;
url.setScheme("http");
url.setHost(m_hostAddress.toString());
url.setPort(m_port);
url.setPath("/Browse");
QNetworkReply *reply = m_networkManager->get(QNetworkRequest(url));
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, this, [requestId, reply, this] {
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
// Check HTTP status code
if (status != 200 || reply->error() != QNetworkReply::NoError) {
if (reply->error() == QNetworkReply::HostNotFoundError) {
emit connectionChanged(false);
}
qCWarning(dcBluOS()) << "Request error:" << status << reply->errorString();
return;
}
emit connectionChanged(true);
QByteArray data = reply->readAll();
qCDebug(dcBluOS()) << "Sources: " << data;
QXmlStreamReader xml;
xml.addData(data);
if (xml.hasError()) {
qCDebug(dcBluOS()) << "XML Error:" << xml.errorString();
return;
}
QList<Source> sourceList;
if (xml.readNextStartElement()) {
if (xml.name() == "browse") {
while(xml.readNextStartElement()){
if(xml.name() == "item"){
Source source;
if (xml.attributes().hasAttribute("text")) {
source.Text = xml.attributes().value("text").toString();
}
if (xml.attributes().hasAttribute("type")) {
source.Type = xml.attributes().value("type").toString();
}
if (xml.attributes().hasAttribute("browseKey")) {
source.BrowseKey = xml.attributes().value("browseKey").toString();
}
if (xml.attributes().hasAttribute("image")) {
source.Image = xml.attributes().value("image").toString();
}
qCDebug(dcBluOS()) << "Source text" << xml.readElementText();
sourceList.append(source);
} else {
xml.skipCurrentElement();
}
}
}
}
emit sourcesReceived(requestId, sourceList);
});
return requestId;
}
QUuid BluOS::browseSource(const QString &key)
{
QUuid requestId = QUuid::createUuid();
QUrl url;
url.setScheme("http");
url.setHost(m_hostAddress.toString());
url.setPort(m_port);
url.setPath("/Browse");
QUrlQuery query;
query.addQueryItem("key", key);
url.setQuery(query);
QNetworkReply *reply = m_networkManager->get(QNetworkRequest(url));
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, this, [requestId, reply, this] {
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
// Check HTTP status code
if (status != 200 || reply->error() != QNetworkReply::NoError) {
if (reply->error() == QNetworkReply::HostNotFoundError) {
emit connectionChanged(false);
}
qCWarning(dcBluOS()) << "Request error:" << status << reply->errorString();
return;
}
emit connectionChanged(true);
QByteArray data = reply->readAll();
qCDebug(dcBluOS()) << "Browse result: " << data;
QXmlStreamReader xml;
xml.addData(data);
if (xml.hasError()) {
qCDebug(dcBluOS()) << "XML Error:" << xml.errorString();
return;
}
QList<Source> sourceList;
if (xml.readNextStartElement()) {
if (xml.name() == "browse") {
while(xml.readNextStartElement()){
if(xml.name() == "item"){
Source source;
if (xml.attributes().hasAttribute("text")) {
source.Text = xml.attributes().value("text").toString();
}
if (xml.attributes().hasAttribute("type")) {
source.Type = xml.attributes().value("type").toString();
}
if (xml.attributes().hasAttribute("browseKey")) {
source.BrowseKey = xml.attributes().value("browseKey").toString();
}
if (xml.attributes().hasAttribute("image")) {
source.Image = xml.attributes().value("image").toString();
}
qCDebug(dcBluOS()) << "Source text" << xml.readElementText();
sourceList.append(source);
} else {
xml.skipCurrentElement();
}
}
}
}
emit sourcesReceived(requestId, sourceList);
});
return requestId;
}
QUuid BluOS::addGroupPlayer(QHostAddress address, int port)
{
Q_UNUSED(address)
Q_UNUSED(port)
QUuid requestId = QUuid::createUuid();
QUrl url;
url.setScheme("http");
url.setHost(m_hostAddress.toString());
url.setPort(m_port);
url.setPath("/AddSlave");
QUrlQuery query;
query.addQueryItem("slave", address.toString());
query.addQueryItem("port", QString::number(port));
url.setQuery(query);
QNetworkReply *reply = m_networkManager->get(QNetworkRequest(url));
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, this, [reply, this] {
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
// Check HTTP status code
if (status != 200 || reply->error() != QNetworkReply::NoError) {
if (reply->error() == QNetworkReply::HostNotFoundError) {
emit connectionChanged(false);
}
qCWarning(dcBluOS()) << "Request error:" << status << reply->errorString();
return;
}
emit connectionChanged(true);
});
return requestId;
}
QUuid BluOS::removeGroupPlayer(QHostAddress address, int port)
{
Q_UNUSED(address)
Q_UNUSED(port)
QUuid requestId = QUuid::createUuid();
QUrl url;
url.setScheme("http");
url.setHost(m_hostAddress.toString());
url.setPort(m_port);
url.setPath("/RemoveSlave");
QUrlQuery query;
query.addQueryItem("slave", address.toString());
query.addQueryItem("port", QString::number(port));
url.setQuery(query);
QNetworkReply *reply = m_networkManager->get(QNetworkRequest(url));
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, this, [reply, this] {
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
// Check HTTP status code
if (status != 200 || reply->error() != QNetworkReply::NoError) {
if (reply->error() == QNetworkReply::HostNotFoundError) {
emit connectionChanged(false);
}
qCWarning(dcBluOS()) << "Request error:" << status << reply->errorString();
return;
}
emit connectionChanged(true);
});
return requestId;
}
QUuid BluOS::skip()
{
return playBackControl(PlaybackCommand::Skip);
}
QUuid BluOS::playBackControl(BluOS::PlaybackCommand command)
{
QUuid requestId = QUuid::createUuid();
QUrl url;
url.setScheme("http");
url.setHost(m_hostAddress.toString());
url.setPort(m_port);
switch (command) {
case PlaybackCommand::Play:
url.setPath("/Play");
break;
case PlaybackCommand::Pause:
url.setPath("/Pause");
break;
case PlaybackCommand::Stop:
url.setPath("/Stop");
break;
case PlaybackCommand::Back:
url.setPath("/Back");
break;
case PlaybackCommand::Skip:
url.setPath("/Skip");
break;
}
QNetworkRequest request;
request.setUrl(url);
QNetworkReply *reply = m_networkManager->get(request);
qCDebug(dcBluOS()) << "Sending request" << request.url();
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, this, [requestId, reply, this] {
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
// Check HTTP status code
if (status != 200 || reply->error() != QNetworkReply::NoError) {
if (reply->error() == QNetworkReply::HostNotFoundError) {
emit connectionChanged(false);
}
emit actionExecuted(requestId, false);
qCWarning(dcBluOS()) << "Request error:" << status << reply->errorString();
return;
}
emit connectionChanged(true);
QByteArray data = reply->readAll();
parseState(data);
});
return requestId;
}
bool BluOS::parseState(const QByteArray &state)
{
QXmlStreamReader xml;
xml.addData(state);
if (xml.hasError()) {
qCDebug(dcBluOS()) << "XML Error:" << xml.errorString();
return false;
}
StatusResponse statusResponse;
if (xml.readNextStartElement()) {
if (xml.name() == "status") {
while(xml.readNextStartElement()){
if(xml.name() == "artist"){
statusResponse.Artist = xml.readElementText();
} else if(xml.name() == "album"){
statusResponse.Album = xml.readElementText();
} else if(xml.name() == "name"){
statusResponse.Name = xml.readElementText();
} else if(xml.name() == "service"){
statusResponse.Service = xml.readElementText();
} else if(xml.name() == "serviceIcon"){
statusResponse.ServiceIcon = xml.readElementText();
} else if(xml.name() == "shuffle"){
statusResponse.Shuffle = xml.readElementText().toInt();
} else if(xml.name() == "repeat"){
statusResponse.Shuffle = xml.readElementText().toInt();
} else if(xml.name() == "state"){
QString playback = xml.readElementText();
if (playback == "play") {
statusResponse.State = PlaybackState::Playing;
} else if (playback == "pause") {
statusResponse.State = PlaybackState::Paused;
} else if (playback == "stop") {
statusResponse.State = PlaybackState::Stopped;
} else if (playback == "connecting") {
statusResponse.State = PlaybackState::Connecting;
} else if (playback == "stream") {
statusResponse.State = PlaybackState::Streaming;
} else {
statusResponse.State = PlaybackState::Stopped;
qCWarning(dcBluOS()) << "State response, unhandled playback mode" << playback;
}
} else if(xml.name() == "volume"){
statusResponse.Volume = xml.readElementText().toInt();
} else if(xml.name() == "mute"){
statusResponse.Mute = xml.readElementText().toInt();
} else if(xml.name() == "image") {
statusResponse.Image = xml.readElementText();
} else if(xml.name() == "title1") {
statusResponse.Title = xml.readElementText();
} else if(xml.name() == "group") {
statusResponse.Group = xml.readElementText();
} else {
xml.skipCurrentElement();
}
}
}
}
emit statusReceived(statusResponse);
return true;
}

153
bluos/bluos.h Normal file
View File

@ -0,0 +1,153 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2020, 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 <https://www.gnu.org/licenses/>.
*
* 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 BLUOS_H
#define BLUOS_H
#include <QObject>
#include <QTimer>
#include <QHostAddress>
#include <QUuid>
#include "network/networkaccessmanager.h"
#include "integrations/thing.h"
class BluOS : public QObject
{
Q_OBJECT
public:
enum PlaybackCommand {
Play,
Pause,
Stop,
Skip,
Back
};
enum RepeatMode {
All,
One,
None
};
enum PlaybackState {
Playing,
Paused,
Stopped,
Connecting,
Streaming
};
struct StatusResponse {
QString Album;
QString Artist;
QString Name;
QString Title;
QString Service;
QUrl ServiceIcon;
PlaybackState State;
QUrl StationUrl;
int Volume;
bool Mute;
RepeatMode Repeat;
bool Shuffle;
QUrl Image;
QString Group;
};
struct Preset {
QString Name;
int Id;
QString Url;
};
struct Source {
//<item image="/images/ci_myplaylists.png" browseKey="playlists" text="Playlists" type="link"/>
QString Image;
QString BrowseKey;
QString Text;
QString Type;
};
explicit BluOS(NetworkAccessManager *networkManager, QHostAddress hostAddress, int port, QObject *parent = nullptr);
int port();
QHostAddress hostAddress();
// Status Queries
void getStatus();
// Volume Control
QUuid setVolume(uint volume);
QUuid setMute(bool mute);
// Playback Control
QUuid play();
QUuid pause();
QUuid stop();
QUuid skip();
QUuid back();
QUuid setShuffle(bool shuffle);
QUuid setRepeat(RepeatMode repeatMode);
// Presets
QUuid listPresets();
QUuid loadPreset(int preset); //1 for next preset, -1 for previous preset
// Content Browsing
QUuid getSources();
QUuid browseSource(const QString &key);
// Player Grouping
QUuid addGroupPlayer(QHostAddress address, int port); //adds player as slave
QUuid removeGroupPlayer(QHostAddress address, int port);
private:
QHostAddress m_hostAddress;
int m_port;
NetworkAccessManager *m_networkManager = nullptr;
QUuid playBackControl(PlaybackCommand command);
bool parseState(const QByteArray &state);
signals:
void connectionChanged(bool connected);
void actionExecuted(QUuid actionId, bool success);
void statusReceived(const StatusResponse &status);
void volumeReceived(int volume, bool mute);
void shuffleStateReceived(bool state);
void repeatModeReceived(RepeatMode mode);
void presetsReceived(QUuid requestId, const QList<Preset> &presets);
void sourcesReceived(QUuid requestId, const QList<Source> &sources);
void browseResultReceived(QUuid requestId, const QList<Source> &sources);
};
#endif // BLUOS_H

11
bluos/bluos.pro Normal file
View File

@ -0,0 +1,11 @@
include(../plugins.pri)
QT += network
SOURCES += \
integrationpluginbluos.cpp \
bluos.cpp \
HEADERS += \
integrationpluginbluos.h \
bluos.h \

BIN
bluos/blusound.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -0,0 +1,568 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2020, 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 <https://www.gnu.org/licenses/>.
*
* 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 "integrationpluginbluos.h"
#include "plugininfo.h"
#include "integrations/thing.h"
#include "network/networkaccessmanager.h"
#include "types/mediabrowseritem.h"
#include "types/browseritem.h"
#include <QDebug>
#include <QStringList>
#include <QJsonDocument>
#include <QTimer>
IntegrationPluginBluOS::IntegrationPluginBluOS()
{
}
void IntegrationPluginBluOS::init()
{
m_serviceBrowser = hardwareManager()->zeroConfController()->createServiceBrowser("_musc._tcp");
}
void IntegrationPluginBluOS::discoverThings(ThingDiscoveryInfo *info)
{
QTimer::singleShot(5000, info, [this, info](){
foreach (const ZeroConfServiceEntry avahiEntry, m_serviceBrowser->serviceEntries()) {
qCDebug(dcBluOS()) << "Zeroconf entry:" << avahiEntry;
QString playerId = avahiEntry.hostName().split(".").first();
ThingDescriptor descriptor(bluosPlayerThingClassId, avahiEntry.name(), avahiEntry.hostAddress().toString());
ParamList params;
foreach (Thing *existingDevice, myThings().filterByThingClassId(bluosPlayerThingClassId)) {
if (existingDevice->paramValue(bluosPlayerThingSerialNumberParamTypeId).toString() == playerId) {
descriptor.setThingId(existingDevice->id());
break;
}
}
params << Param(bluosPlayerThingAddressParamTypeId, avahiEntry.hostAddress().toString());
params << Param(bluosPlayerThingPortParamTypeId, avahiEntry.port());
params << Param(bluosPlayerThingSerialNumberParamTypeId, playerId);
descriptor.setParams(params);
info->addThingDescriptor(descriptor);
}
info->finish(Thing::ThingErrorNoError);
});
}
void IntegrationPluginBluOS::setupThing(ThingSetupInfo *info)
{
Thing *thing = info->thing();
if (thing->thingClassId() == bluosPlayerThingClassId) {
qCDebug(dcBluOS()) << "Setup BluOS device" << thing->paramValue(bluosPlayerThingAddressParamTypeId).toString();
QHostAddress address(thing->paramValue(bluosPlayerThingAddressParamTypeId).toString());
int port = thing->paramValue(bluosPlayerThingPortParamTypeId).toInt();
BluOS *bluos = new BluOS(hardwareManager()->networkManager() , address, port, this);
connect(bluos, &BluOS::connectionChanged, this, &IntegrationPluginBluOS::onConnectionChanged);
connect(bluos, &BluOS::statusReceived, this, &IntegrationPluginBluOS::onStatusResponseReceived);
connect(bluos, &BluOS::actionExecuted, this, &IntegrationPluginBluOS::onActionExecuted);
connect(bluos, &BluOS::volumeReceived, this, &IntegrationPluginBluOS::onVolumeReceived);
connect(bluos, &BluOS::presetsReceived, this, &IntegrationPluginBluOS::onPresetsReceived);
connect(bluos, &BluOS::sourcesReceived, this, &IntegrationPluginBluOS::onSourcesReceived);
connect(bluos, &BluOS::shuffleStateReceived, this, &IntegrationPluginBluOS::onShuffleStateReceived);
connect(bluos, &BluOS::repeatModeReceived, this, &IntegrationPluginBluOS::onRepeatModeReceived);
m_asyncSetup.insert(bluos, info);
bluos->getStatus();
// In case the setup is cancelled before we finish it...
connect(info, &ThingSetupInfo::aborted, this, [this, bluos] {
m_asyncSetup.remove(bluos);
bluos->deleteLater();
});
return;
} else {
return info->finish(Thing::ThingErrorThingClassNotFound);
}
}
void IntegrationPluginBluOS::postSetupThing(Thing *thing)
{
Q_UNUSED(thing)
if (!m_pluginTimer) {
m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(10);
connect(m_pluginTimer, &PluginTimer::timeout, [this] {
foreach(BluOS *bluos, m_bluos) {
bluos->getStatus();
}
});
}
}
void IntegrationPluginBluOS::thingRemoved(Thing *thing)
{
if (thing->thingClassId() == bluosPlayerThingClassId) {
BluOS *bluos = m_bluos.take(thing->id());
bluos->deleteLater();
} else {
qCWarning(dcBluOS()) << "Things removed, unhandled thing class id";
}
}
void IntegrationPluginBluOS::executeAction(ThingActionInfo *info)
{
Thing *thing = info->thing();
Action action = info->action();
if (thing->thingClassId() == bluosPlayerThingClassId) {
BluOS *bluos = m_bluos.value(thing->id());
if (!bluos) {
return info->finish(Thing::ThingErrorHardwareFailure);
}
if (action.actionTypeId() == bluosPlayerPlaybackStatusActionTypeId) {
QString playbakStatus = action.param(bluosPlayerPlaybackStatusEventPlaybackStatusParamTypeId).value().toString();
QUuid requestId;
if (playbakStatus == "Playing") {
requestId = bluos->play();
} else if (playbakStatus == "Paused") {
requestId = bluos->pause();
} else if (playbakStatus == "Stopped") {
requestId = bluos->stop();
} else {
qCWarning(dcBluOS()) << "Unhandled Playback mode";
}
m_asyncActions.insert(requestId, info);
connect(info, &ThingActionInfo::aborted, [this, requestId] {m_asyncActions.remove(requestId);});
} else if (action.actionTypeId() == bluosPlayerPlayActionTypeId) {
QUuid requestId = bluos->play();
m_asyncActions.insert(requestId, info);
connect(info, &ThingActionInfo::aborted, [this, requestId] {m_asyncActions.remove(requestId);});
} else if (action.actionTypeId() == bluosPlayerPauseActionTypeId) {
QUuid requestId = bluos->pause();
m_asyncActions.insert(requestId, info);
connect(info, &ThingActionInfo::aborted, [this, requestId] {m_asyncActions.remove(requestId);});
} else if (action.actionTypeId() == bluosPlayerStopActionTypeId) {
QUuid requestId = bluos->stop();
m_asyncActions.insert(requestId, info);
connect(info, &ThingActionInfo::aborted, [this, requestId] {m_asyncActions.remove(requestId);});
} else if (action.actionTypeId() == bluosPlayerSkipNextActionTypeId) {
QUuid requestId = bluos->skip();
m_asyncActions.insert(requestId, info);
connect(info, &ThingActionInfo::aborted, [this, requestId] {m_asyncActions.remove(requestId);});
} else if (action.actionTypeId() == bluosPlayerSkipBackActionTypeId) {
QUuid requestId = bluos->back();
m_asyncActions.insert(requestId, info);
connect(info, &ThingActionInfo::aborted, [this, requestId] {m_asyncActions.remove(requestId);});
} else if (action.actionTypeId() == bluosPlayerVolumeActionTypeId) {
uint volume = action.param(bluosPlayerVolumeActionVolumeParamTypeId).value().toUInt();
QUuid requestId = bluos->setVolume(volume);
m_asyncActions.insert(requestId, info);
connect(info, &ThingActionInfo::aborted, [this, requestId] {m_asyncActions.remove(requestId);});
} else if (action.actionTypeId() == bluosPlayerMuteActionTypeId) {
bool mute = action.param(bluosPlayerMuteActionMuteParamTypeId).value().toBool();
QUuid requestId = bluos->setMute(mute);
m_asyncActions.insert(requestId, info);
connect(info, &ThingActionInfo::aborted, [this, requestId] {m_asyncActions.remove(requestId);});
} else if (action.actionTypeId() == bluosPlayerShuffleActionTypeId) {
bool shuffle = action.param(bluosPlayerShuffleActionShuffleParamTypeId).value().toBool();
QUuid requestId = bluos->setShuffle(shuffle);
m_asyncActions.insert(requestId, info);
connect(info, &ThingActionInfo::aborted, [this, requestId] {m_asyncActions.remove(requestId);});
} else if (action.actionTypeId() == bluosPlayerRepeatActionTypeId) {
QString repeat = action.param(bluosPlayerRepeatActionRepeatParamTypeId).value().toString();
QUuid requestId;
if (repeat == "One") {
requestId = bluos->setRepeat(BluOS::RepeatMode::One);
} else if (repeat == "All") {
requestId = bluos->setRepeat(BluOS::RepeatMode::All);
} else if (repeat == "None") {
requestId = bluos->setRepeat(BluOS::RepeatMode::None);
} else {
qCWarning(dcBluOS()) << "Unhandled Repeat Mode";
}
m_asyncActions.insert(requestId, info);
connect(info, &ThingActionInfo::aborted, [this, requestId] {m_asyncActions.remove(requestId);});
} else {
qCWarning(dcBluOS()) << "Execute Action, unhandled action type id" << action.actionTypeId();
return info->finish(Thing::ThingErrorThingClassNotFound);
}
} else {
qCWarning(dcBluOS()) << "Execute Action, unhandled thing class id" << thing->thingClassId();
return info->finish(Thing::ThingErrorThingClassNotFound);
}
}
void IntegrationPluginBluOS::browseThing(BrowseResult *result)
{
Thing *thing = result->thing();
if (thing->thingClassId() == bluosPlayerThingClassId) {
BluOS *bluos = m_bluos.value(thing->id());
if (!bluos) {
qCWarning(dcBluOS()) << "Could not find any BluOS object that belongs to" << thing->name();
result->finish(Thing::ThingErrorHardwareNotAvailable, "BluOS connection not properly initialized");
return;
}
if (result->itemId() == "presets") {
QUuid requestId = bluos->listPresets();
m_asyncBrowseResults.insert(requestId, result);
connect(result, &BrowseResult::aborted, this, [this, requestId]{m_asyncBrowseResults.remove(requestId);});
} else if (result->itemId() == "grouping") {
foreach (const ZeroConfServiceEntry avahiEntry, m_serviceBrowser->serviceEntries()) {
qCDebug(dcBluOS()) << "Zeroconf entry:" << avahiEntry;
QString playerId = avahiEntry.hostName().split(".").first();
if (thing->paramValue(bluosPlayerThingSerialNumberParamTypeId).toString() == playerId) {
continue;
}
MediaBrowserItem groupingItem("grouping&"+avahiEntry.hostAddress().toString()+"&"+avahiEntry.port(), avahiEntry.name(), true, false);
groupingItem.setDescription(avahiEntry.hostAddress().toString());
groupingItem.setMediaIcon(MediaBrowserItem::MediaBrowserIconNetwork);
groupingItem.setIcon(BrowserItem::BrowserIconMusic);
result->addItem(groupingItem);
}
} else if (result->itemId().isEmpty()) {
MediaBrowserItem presetItem("presets", "Presets", true, false);
presetItem.setIcon(BrowserItem::BrowserIcon::BrowserIconFavorites);
presetItem.setMediaIcon(MediaBrowserItem::MediaBrowserIconMusicLibrary);
result->addItem(presetItem);
// MediaBrowserItem groupingItem("grouping", "Grouping", true, false);
// groupingItem.setIcon(BrowserItem::BrowserIcon::BrowserIconApplication);
// groupingItem.setMediaIcon(MediaBrowserItem::MediaBrowserIconNetwork);
// result->addItem(groupingItem);
QUuid requestId = bluos->getSources();
m_asyncBrowseResults.insert(requestId, result);
connect(result, &BrowseResult::aborted, this, [this, requestId]{m_asyncBrowseResults.remove(requestId);});
} else {
QUuid requestId = bluos->browseSource(result->itemId());
m_asyncBrowseResults.insert(requestId, result);
connect(result, &BrowseResult::aborted, this, [this, requestId]{m_asyncBrowseResults.remove(requestId);});
}
}
}
void IntegrationPluginBluOS::browserItem(BrowserItemResult *result)
{
Thing *thing = result->thing();
if (thing->thingClassId() == bluosPlayerThingClassId) {
BluOS *bluos = m_bluos.value(thing->id());
if (!bluos) {
qCWarning(dcBluOS()) << "Could not find any BluOS object that belongs to" << thing->name();
return;
}
if (result->itemId() == "presets") {
QUuid requestId = bluos->listPresets();
m_asyncBrowseItemResults.insert(requestId, result);
connect(result, &BrowserItemResult::aborted, this, [this, requestId]{m_asyncBrowseItemResults.remove(requestId);});
} else {
BrowserItem presetItem("presets", "Presets", true, false);
presetItem.setIcon(BrowserItem::BrowserIcon::BrowserIconFavorites);
QUuid requestId = bluos->getSources();
m_asyncBrowseItemResults.insert(requestId, result);
connect(result, &BrowserItemResult::aborted, this, [this, requestId]{m_asyncBrowseItemResults.remove(requestId);});
}
}
}
void IntegrationPluginBluOS::executeBrowserItem(BrowserActionInfo *info)
{
Thing *thing = info->thing();
if (thing->thingClassId() == bluosPlayerThingClassId) {
BluOS *bluos = m_bluos.value(thing->id());
if (!bluos) {
qCWarning(dcBluOS()) << "Could not find any BluOS object that belongs to" << thing->name();
return;
}
if (info->browserAction().itemId().startsWith("presets")) {
QUuid requestId;
int presetId = info->browserAction().itemId().split("&").last().toInt();
requestId = bluos->loadPreset(presetId);
m_asyncExecuteBrowseItems.insert(requestId, info);
connect(info, &BrowserActionInfo::aborted, this, [this, requestId]{m_asyncExecuteBrowseItems.remove(requestId);});
} else if (info->browserAction().itemId().startsWith("grouping")) {
//TODO Grouping
//Test devices are required
} else {
//TODO Sources
//Test services are required
}
}
}
void IntegrationPluginBluOS::onConnectionChanged(bool connected)
{
BluOS *bluos = static_cast<BluOS*>(sender());
if (m_asyncSetup.contains(bluos)) {
ThingSetupInfo *info = m_asyncSetup.take(bluos);
if (connected) {
m_bluos.insert(info->thing()->id(), bluos);
info->thing()->setStateValue(bluosPlayerConnectedStateTypeId, true);
info->finish(Thing::ThingErrorNoError);
} else {
bluos->deleteLater();
info->finish(Thing::ThingErrorSetupFailed);
}
} else {
Thing *thing = myThings().findById(m_bluos.key(bluos));
if (!thing) {
qCWarning(dcBluOS()) << "Could not find any Thing that belongs to the BluOS object";
return;
}
thing->setStateValue(bluosPlayerConnectedStateTypeId, connected);
}
}
void IntegrationPluginBluOS::onStatusResponseReceived(const BluOS::StatusResponse &status)
{
BluOS *bluos = static_cast<BluOS*>(sender());
Thing *thing = myThings().findById(m_bluos.key(bluos));
if (!thing){
qCWarning(dcBluOS()) << "Could not find any Thing that belongs to this BluOS object";
return;
}
thing->setStateValue(bluosPlayerArtistStateTypeId, status.Artist);
thing->setStateValue(bluosPlayerCollectionStateTypeId, status.Album);
thing->setStateValue(bluosPlayerTitleStateTypeId, status.Title);
thing->setStateValue(bluosPlayerSourceStateTypeId, status.Service);
thing->setStateValue(bluosPlayerArtworkStateTypeId, status.Image);
switch (status.State) {
case BluOS::PlaybackState::Playing:
case BluOS::PlaybackState::Streaming:
thing->setStateValue(bluosPlayerPlaybackStatusStateTypeId, "Playing");
break;
case BluOS::PlaybackState::Paused:
thing->setStateValue(bluosPlayerPlaybackStatusStateTypeId, "Paused");
break;
case BluOS::PlaybackState::Stopped:
thing->setStateValue(bluosPlayerPlaybackStatusStateTypeId, "Stopped");
break;
default:
thing->setStateValue(bluosPlayerPlaybackStatusStateTypeId, "Stopped");
break;
}
thing->setStateValue(bluosPlayerMuteStateTypeId, status.Mute);
thing->setStateValue(bluosPlayerVolumeStateTypeId, status.Volume);
thing->setStateValue(bluosPlayerShuffleStateTypeId, status.Shuffle);
switch (status.Repeat) {
case BluOS::RepeatMode::All:
thing->setStateValue(bluosPlayerRepeatStateTypeId, "All");
break;
case BluOS::RepeatMode::One:
thing->setStateValue(bluosPlayerRepeatStateTypeId, "One");
break;
case BluOS::RepeatMode::None:
thing->setStateValue(bluosPlayerRepeatStateTypeId, "None");
break;
}
thing->setStateValue(bluosPlayerGroupStateTypeId, status.Group);
}
void IntegrationPluginBluOS::onActionExecuted(QUuid requestId, bool success)
{
if (m_asyncActions.contains(requestId)) {
ThingActionInfo *info = m_asyncActions.take(requestId);
if (success) {
info->finish(Thing::ThingErrorNoError);
} else {
info->finish(Thing::ThingErrorHardwareNotAvailable);
}
}
if (m_asyncExecuteBrowseItems.contains(requestId)) {
BrowserActionInfo *info = m_asyncExecuteBrowseItems.take(requestId);
if (success) {
info->finish(Thing::ThingErrorNoError);
} else {
info->finish(Thing::ThingErrorHardwareFailure);
}
m_pluginTimer->timeout(); // get a status update
}
}
void IntegrationPluginBluOS::onVolumeReceived(int volume, bool mute)
{
BluOS *bluos = static_cast<BluOS*>(sender());
Thing *thing = myThings().findById(m_bluos.key(bluos));
if (!thing){
qCWarning(dcBluOS()) << "Could not find any Thing that belongs to this BluOS object";
return;
}
thing->setStateValue(bluosPlayerMuteStateTypeId, mute);
thing->setStateValue(bluosPlayerVolumeStateTypeId, volume);
}
void IntegrationPluginBluOS::onShuffleStateReceived(bool state)
{
BluOS *bluos = static_cast<BluOS*>(sender());
Thing *thing = myThings().findById(m_bluos.key(bluos));
if (!thing)
return;
thing->setStateValue(bluosPlayerShuffleStateTypeId, state);
}
void IntegrationPluginBluOS::onRepeatModeReceived(BluOS::RepeatMode mode)
{
BluOS *bluos = static_cast<BluOS*>(sender());
Thing *thing = myThings().findById(m_bluos.key(bluos));
if (!thing){
qCWarning(dcBluOS()) << "Could not find any Thing that belongs to this BluOS object";
return;
}
switch (mode) {
case BluOS::RepeatMode::All:
thing->setStateValue(bluosPlayerRepeatStateTypeId, "All");
break;
case BluOS::RepeatMode::One:
thing->setStateValue(bluosPlayerRepeatStateTypeId, "One");
break;
case BluOS::RepeatMode::None:
thing->setStateValue(bluosPlayerRepeatStateTypeId, "None");
break;
}
}
void IntegrationPluginBluOS::onPresetsReceived(QUuid requestId, const QList<BluOS::Preset> &presets)
{
BluOS *bluos = static_cast<BluOS*>(sender());
Thing *thing = myThings().findById(m_bluos.key(bluos));
if (m_asyncBrowseResults.contains(requestId)) {
BrowseResult *result = m_asyncBrowseResults.take(requestId);
if (!thing) {
qCWarning(dcBluOS()) << "Could not find any Thing that belongs to this browse result";
result->finish(Thing::ThingErrorHardwareNotAvailable);
return;
}
foreach(BluOS::Preset preset, presets) {
qCDebug(dcBluOS()) << "Preset added" << preset.Name << preset.Id << preset.Url;
BrowserItem item("presets&"+QString::number(preset.Id), preset.Name, false, true);
item.setIcon(BrowserItem::BrowserIcon::BrowserIconFavorites);
result->addItem(item);
}
result->finish(Thing::ThingErrorNoError);
}
if (m_asyncBrowseItemResults.contains(requestId)) {
BrowserItemResult *result = m_asyncBrowseItemResults.take(requestId);
result->finish(Thing::ThingErrorItemNotFound);
//For future browsing features
}
}
void IntegrationPluginBluOS::onSourcesReceived(QUuid requestId, const QList<BluOS::Source> &sources)
{
BluOS *bluos = static_cast<BluOS*>(sender());
Thing *thing = myThings().findById(m_bluos.key(bluos));
if (m_asyncBrowseResults.contains(requestId)) {
BrowseResult *result = m_asyncBrowseResults.take(requestId);
if (!thing) {
qCWarning(dcBluOS()) << "Could not find any Thing that belongs to this browse result";
result->finish(Thing::ThingErrorHardwareNotAvailable);
return;
}
foreach(BluOS::Source source, sources) {
qCDebug(dcBluOS()) << "Source added" << source.Text << source.BrowseKey << source.Type;
MediaBrowserItem item;
item.setDisplayName(source.Text);
if (source.BrowseKey.isEmpty()) {
item.setBrowsable(false);
item.setExecutable(true);
item.setId(source.Text);
} else {
item.setBrowsable(true);
item.setExecutable(false);
item.setId(source.BrowseKey);
}
item.setIcon(BrowserItem::BrowserIconMusic);
if (source.Text == "Bluetooth") {
item.setMediaIcon(MediaBrowserItem::MediaBrowserIconBluetooth);
//result->addItem(item);
} else if (source.Text == "Spotify") {
item.setExecutable(false);
item.setBrowsable(false);
item.setMediaIcon(MediaBrowserItem::MediaBrowserIconSpotify);
item.setDescription("Open the Spotify App for browsing");
result->addItem(item);
} else if (source.Text == "TuneIn") {
item.setMediaIcon(MediaBrowserItem::MediaBrowserIconTuneIn);
result->addItem(item);
} else if (source.Text.contains("Aux")) {
item.setMediaIcon(MediaBrowserItem::MediaBrowserIconAux);
result->addItem(item);
} else if (source.Text == "Radio Paradise") {
//item.setMediaIcon(MediaBrowserItem::MediaBrowserIconRadioParadise);
//result->addItem(item);
//Needs testing before continuing
}
}
result->finish(Thing::ThingErrorNoError);
}
if (m_asyncBrowseItemResults.contains(requestId)) {
BrowserItemResult *result = m_asyncBrowseItemResults.take(requestId);
result->finish(Thing::ThingErrorItemNotFound);
//For future browsing features
}
}
void IntegrationPluginBluOS::onBrowseResultReceived(QUuid requestId, const QList<BluOS::Source> &sources)
{
BluOS *bluos = static_cast<BluOS*>(sender());
Thing *thing = myThings().findById(m_bluos.key(bluos));
if (m_asyncBrowseResults.contains(requestId)) {
BrowseResult *result = m_asyncBrowseResults.take(requestId);
if (!thing) {
qCWarning(dcBluOS()) << "Could not find any Thing that belongs to this browse result";
result->finish(Thing::ThingErrorHardwareNotAvailable);
return;
}
foreach(BluOS::Source source, sources) {
qCDebug(dcBluOS()) << "Source added" << source.Text << source.BrowseKey << source.Type;
MediaBrowserItem item;
item.setDisplayName(source.Text);
if (source.BrowseKey.isEmpty()) {
item.setBrowsable(false);
item.setExecutable(true);
item.setId(source.Text);
} else {
item.setBrowsable(true);
item.setExecutable(false);
item.setId(source.BrowseKey);
}
item.setIcon(BrowserItem::BrowserIconMusic);
result->addItem(item);
}
result->finish(Thing::ThingErrorNoError);
}
}

View File

@ -0,0 +1,92 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2020, 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 <https://www.gnu.org/licenses/>.
*
* 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 INTEGRATIONPLUGINBLUOS_H
#define INTEGRATIONPLUGINBLUOS_H
#include "bluos.h"
#include "integrations/integrationplugin.h"
#include "platform/platformzeroconfcontroller.h"
#include "network/zeroconf/zeroconfservicebrowser.h"
#include "plugintimer.h"
#include <QUdpSocket>
#include <QNetworkAccessManager>
class PluginTimer;
class IntegrationPluginBluOS: public IntegrationPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginbluos.json")
Q_INTERFACES(IntegrationPlugin)
public:
explicit IntegrationPluginBluOS();
void init() override;
void discoverThings(ThingDiscoveryInfo *info) override;
void setupThing(ThingSetupInfo *info) override;
void postSetupThing(Thing *thing) override;
void thingRemoved(Thing *thing) override;
void executeAction(ThingActionInfo *info) override;
void browseThing(BrowseResult *result) override;
void browserItem(BrowserItemResult *result) override;
void executeBrowserItem(BrowserActionInfo *info) override;
private:
PluginTimer *m_pluginTimer = nullptr;
ZeroConfServiceBrowser *m_serviceBrowser = nullptr;
QHash<ThingId, BluOS *> m_bluos;
QHash<BluOS *, ThingSetupInfo *> m_asyncSetup;
QHash<QUuid, ThingActionInfo *> m_asyncActions;
QHash<QUuid, BrowseResult *> m_asyncBrowseResults;
QHash<QUuid, BrowserActionInfo *> m_asyncExecuteBrowseItems;
QHash<QUuid, BrowserItemResult *> m_asyncBrowseItemResults;
private slots:
void onPluginTimer();
void onConnectionChanged(bool connected);
void onStatusResponseReceived(const BluOS::StatusResponse &status);
void onActionExecuted(QUuid actionId, bool success);
void onVolumeReceived(int volume, bool mute);
void onShuffleStateReceived(bool state);
void onRepeatModeReceived(BluOS::RepeatMode mode);
void onPresetsReceived(QUuid requestId, const QList<BluOS::Preset> &presets);
void onSourcesReceived(QUuid requestId, const QList<BluOS::Source> &sources);
void onBrowseResultReceived(QUuid requestId, const QList<BluOS::Source> &sources);
};
#endif // INTEGRATIONPLUGINBLUOS_H

View File

@ -0,0 +1,198 @@
{
"displayName": "BluOS",
"name": "BluOS",
"id": "71dd25b3-37ef-4b27-abca-24989fa38c61",
"vendors": [
{
"id": "39a492e9-e497-4b43-94d4-970eb9913b96",
"displayName": "BluOS",
"name": "bluos",
"thingClasses": [
{
"id": "406adcbc-1e7d-4e41-ae2a-f87b6bafd13d",
"name": "bluosPlayer",
"displayName": "BluOS player",
"createMethods": ["discovery"],
"interfaces": ["mediaplayer", "extendedvolumecontroller", "mediametadataprovider", "shufflerepeat", "connectable"],
"browsable": true,
"paramTypes":[
{
"id": "833f99cc-fc3f-48ef-a705-a69ae2c8e9ec",
"name": "address",
"displayName": "IP Address",
"type" : "QString"
},
{
"id": "4628c040-6bbb-43c9-b25f-ce3b22300e3b",
"name": "port",
"displayName": "Port",
"type" : "int"
},
{
"id": "8fd2a7d5-bb26-4f18-a488-5ab0779733f8",
"name": "serialNumber",
"displayName": "Serial number",
"type" : "QString"
}
],
"stateTypes": [
{
"id": "8092f387-5099-43e8-a7c4-7f0dd7b70fce",
"name": "connected",
"displayName": "Connected",
"displayNameEvent": "Connected changed",
"type": "bool",
"defaultValue": false,
"cached": false
},
{
"id": "2f650ae6-f3a5-4851-8449-1ba02c4864e5",
"name": "mute",
"displayName": "Mute",
"displayNameEvent": "Mute changed",
"displayNameAction": "Set mute",
"type": "bool",
"defaultValue": false,
"cached": false,
"writable": true
},
{
"id": "a444bb7c-7266-4c7e-874a-eb04bb91b9cf",
"name": "volume",
"displayName": "Volume",
"displayNameEvent": "Volume changed",
"displayNameAction": "Set volume",
"type": "int",
"defaultValue": 50,
"minValue": 0,
"maxValue": 100,
"writable": true
},
{
"id": "43f552c7-7dfe-4358-bebd-ab558191cdfc",
"name": "playbackStatus",
"displayName": "Playback status",
"displayNameEvent": "Playback status changed",
"displayNameAction": "Set playback status",
"type": "QString",
"defaultValue": "Stopped",
"possibleValues": ["Playing", "Paused", "Stopped"],
"cached": false,
"writable": true
},
{
"id": "5636c21a-f14b-472c-8486-177543f6adfb",
"name": "shuffle",
"displayName": "Shuffle",
"displayNameEvent": "Shuffle changed",
"displayNameAction": "Set shuffle",
"type": "bool",
"defaultValue": false,
"cached": false,
"writable": true
},
{
"id": "b4a572b1-6120-43e8-9a6c-18d099c8b162",
"name": "repeat",
"displayName": "Repeat mode",
"displayNameEvent": "Repeat mode changed",
"displayNameAction": "Set repeat mode",
"type": "QString",
"defaultValue": "None",
"possibleValues": ["None", "One", "All"],
"cached": false,
"writable": true
},
{
"id": "604e4995-ee1a-44fc-bfcb-ca0e861710bd",
"name": "source",
"displayName": "Source",
"displayNameEvent": "Source changed",
"type": "QString",
"defaultValue": ""
},
{
"id": "8c5372f1-3dde-4984-9955-9a436a29e8e3",
"name": "artist",
"displayName": "Artist",
"displayNameEvent": "Artist changed",
"type": "QString",
"defaultValue": ""
},
{
"id": "d2a2db24-5855-40cd-a043-6f67e43acc61",
"name": "collection",
"displayName": "Album",
"displayNameEvent": "Album changed",
"type": "QString",
"defaultValue": ""
},
{
"id": "6204ec9d-6aab-4fd3-8911-fdaa462c19a8",
"name": "title",
"displayName": "Title",
"displayNameEvent": "Title changed",
"type": "QString",
"defaultValue": ""
},
{
"id": "918bfeed-bf96-4034-85d6-9f4cff35fd00",
"name": "artwork",
"displayName": "Artwork",
"displayNameEvent": "Artwork changed",
"type": "QString",
"defaultValue": ""
},
{
"id": "69757ef3-173f-499c-9304-4252de3588c6",
"name": "group",
"displayName": "Group",
"displayNameEvent": "Group changed",
"type": "QString",
"defaultValue": ""
},
{
"id": "e5222e93-fa14-49de-950f-2f605ce8927b",
"name": "playerType",
"displayName": "Player type",
"displayNameEvent": "Player type changed",
"possibleValues": [
"audio",
"video"
],
"type": "QString",
"defaultValue": "audio"
}
],
"actionTypes": [
{
"id": "864a5bcc-71e1-4d7f-8f2e-3c4222d5d988",
"name": "skipBack",
"displayName": "Skip back"
},
{
"id": "2526ec6d-1c21-4f73-97f1-973a5f05b626",
"name": "stop",
"displayName": "Stop"
},
{
"id": "253eb62f-d50d-4667-8213-8632de178aa3",
"name": "play",
"displayName": "Play"
},
{
"id": "92446566-8c32-4ee2-9498-d9dd9333a75d",
"name": "pause",
"displayName": "Pause"
},
{
"id": "30930095-6f97-48ef-8bbf-597d734f0751",
"name": "skipNext",
"displayName": "Skip next"
}
]
}
]
}
]
}

13
bluos/meta.json Normal file
View File

@ -0,0 +1,13 @@
{
"title": "BluOS",
"tagline": "Control audio devices based on BluOS.",
"icon": "blusound.png",
"stability": "consumer",
"offline": true,
"technologies": [
"network"
],
"categories": [
"multimedia"
]
}

View File

@ -0,0 +1,295 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="de">
<context>
<name>BluOS</name>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="77"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="80"/>
<source>Album</source>
<extracomment>The name of the ParamType (ThingClass: bluosPlayer, EventType: collection, ID: {d2a2db24-5855-40cd-a043-6f67e43acc61})
----------
The name of the StateType ({d2a2db24-5855-40cd-a043-6f67e43acc61}) of ThingClass bluosPlayer</extracomment>
<translation>Album</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="83"/>
<source>Album changed</source>
<extracomment>The name of the EventType ({d2a2db24-5855-40cd-a043-6f67e43acc61}) of ThingClass bluosPlayer</extracomment>
<translation>Album geändert</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="86"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="89"/>
<source>Artist</source>
<extracomment>The name of the ParamType (ThingClass: bluosPlayer, EventType: artist, ID: {8c5372f1-3dde-4984-9955-9a436a29e8e3})
----------
The name of the StateType ({8c5372f1-3dde-4984-9955-9a436a29e8e3}) of ThingClass bluosPlayer</extracomment>
<translation>Künstler</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="92"/>
<source>Artist changed</source>
<extracomment>The name of the EventType ({8c5372f1-3dde-4984-9955-9a436a29e8e3}) of ThingClass bluosPlayer</extracomment>
<translation>Künstler geändert</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="95"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="98"/>
<source>Artwork</source>
<extracomment>The name of the ParamType (ThingClass: bluosPlayer, EventType: artwork, ID: {918bfeed-bf96-4034-85d6-9f4cff35fd00})
----------
The name of the StateType ({918bfeed-bf96-4034-85d6-9f4cff35fd00}) of ThingClass bluosPlayer</extracomment>
<translation>Artwork</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="101"/>
<source>Artwork changed</source>
<extracomment>The name of the EventType ({918bfeed-bf96-4034-85d6-9f4cff35fd00}) of ThingClass bluosPlayer</extracomment>
<translation>Artwork geändert</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="104"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="107"/>
<source>BluOS</source>
<extracomment>The name of the vendor ({39a492e9-e497-4b43-94d4-970eb9913b96})
----------
The name of the plugin BluOS ({71dd25b3-37ef-4b27-abca-24989fa38c61})</extracomment>
<translation>BluOS</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="110"/>
<source>BluOS player</source>
<extracomment>The name of the ThingClass ({406adcbc-1e7d-4e41-ae2a-f87b6bafd13d})</extracomment>
<translation>BluOS Player</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="113"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="116"/>
<source>Connected</source>
<extracomment>The name of the ParamType (ThingClass: bluosPlayer, EventType: connected, ID: {8092f387-5099-43e8-a7c4-7f0dd7b70fce})
----------
The name of the StateType ({8092f387-5099-43e8-a7c4-7f0dd7b70fce}) of ThingClass bluosPlayer</extracomment>
<translation>Verbunden</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="119"/>
<source>Connected changed</source>
<extracomment>The name of the EventType ({8092f387-5099-43e8-a7c4-7f0dd7b70fce}) of ThingClass bluosPlayer</extracomment>
<translation>Verbunden geändert</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="122"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="125"/>
<source>Group</source>
<extracomment>The name of the ParamType (ThingClass: bluosPlayer, EventType: group, ID: {69757ef3-173f-499c-9304-4252de3588c6})
----------
The name of the StateType ({69757ef3-173f-499c-9304-4252de3588c6}) of ThingClass bluosPlayer</extracomment>
<translation>Gruppe</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="128"/>
<source>Group changed</source>
<extracomment>The name of the EventType ({69757ef3-173f-499c-9304-4252de3588c6}) of ThingClass bluosPlayer</extracomment>
<translation>Gruppe geändert</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="131"/>
<source>IP Address</source>
<extracomment>The name of the ParamType (ThingClass: bluosPlayer, Type: thing, ID: {833f99cc-fc3f-48ef-a705-a69ae2c8e9ec})</extracomment>
<translation>IP-Adresse</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="134"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="137"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="140"/>
<source>Mute</source>
<extracomment>The name of the ParamType (ThingClass: bluosPlayer, ActionType: mute, ID: {2f650ae6-f3a5-4851-8449-1ba02c4864e5})
----------
The name of the ParamType (ThingClass: bluosPlayer, EventType: mute, ID: {2f650ae6-f3a5-4851-8449-1ba02c4864e5})
----------
The name of the StateType ({2f650ae6-f3a5-4851-8449-1ba02c4864e5}) of ThingClass bluosPlayer</extracomment>
<translation>Stumm</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="143"/>
<source>Mute changed</source>
<extracomment>The name of the EventType ({2f650ae6-f3a5-4851-8449-1ba02c4864e5}) of ThingClass bluosPlayer</extracomment>
<translation>Stumm geändert</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="146"/>
<source>Pause</source>
<extracomment>The name of the ActionType ({92446566-8c32-4ee2-9498-d9dd9333a75d}) of ThingClass bluosPlayer</extracomment>
<translation>Pause</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="149"/>
<source>Play</source>
<extracomment>The name of the ActionType ({253eb62f-d50d-4667-8213-8632de178aa3}) of ThingClass bluosPlayer</extracomment>
<translation>Play</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="152"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="155"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="158"/>
<source>Playback status</source>
<extracomment>The name of the ParamType (ThingClass: bluosPlayer, ActionType: playbackStatus, ID: {43f552c7-7dfe-4358-bebd-ab558191cdfc})
----------
The name of the ParamType (ThingClass: bluosPlayer, EventType: playbackStatus, ID: {43f552c7-7dfe-4358-bebd-ab558191cdfc})
----------
The name of the StateType ({43f552c7-7dfe-4358-bebd-ab558191cdfc}) of ThingClass bluosPlayer</extracomment>
<translation>Playbackstatus</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="161"/>
<source>Playback status changed</source>
<extracomment>The name of the EventType ({43f552c7-7dfe-4358-bebd-ab558191cdfc}) of ThingClass bluosPlayer</extracomment>
<translation>Playbackstatus geändert</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="164"/>
<source>Port</source>
<extracomment>The name of the ParamType (ThingClass: bluosPlayer, Type: thing, ID: {4628c040-6bbb-43c9-b25f-ce3b22300e3b})</extracomment>
<translation>Port</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="167"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="170"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="173"/>
<source>Repeat mode</source>
<extracomment>The name of the ParamType (ThingClass: bluosPlayer, ActionType: repeat, ID: {b4a572b1-6120-43e8-9a6c-18d099c8b162})
----------
The name of the ParamType (ThingClass: bluosPlayer, EventType: repeat, ID: {b4a572b1-6120-43e8-9a6c-18d099c8b162})
----------
The name of the StateType ({b4a572b1-6120-43e8-9a6c-18d099c8b162}) of ThingClass bluosPlayer</extracomment>
<translation>Wiederholungsmodus</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="176"/>
<source>Repeat mode changed</source>
<extracomment>The name of the EventType ({b4a572b1-6120-43e8-9a6c-18d099c8b162}) of ThingClass bluosPlayer</extracomment>
<translation>Wiederholungsmodus geändert</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="179"/>
<source>Serial number</source>
<extracomment>The name of the ParamType (ThingClass: bluosPlayer, Type: thing, ID: {8fd2a7d5-bb26-4f18-a488-5ab0779733f8})</extracomment>
<translation>Seriennummer</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="182"/>
<source>Set mute</source>
<extracomment>The name of the ActionType ({2f650ae6-f3a5-4851-8449-1ba02c4864e5}) of ThingClass bluosPlayer</extracomment>
<translation>Setze stumm</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="185"/>
<source>Set playback status</source>
<extracomment>The name of the ActionType ({43f552c7-7dfe-4358-bebd-ab558191cdfc}) of ThingClass bluosPlayer</extracomment>
<translation>Setze Playbackstatus</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="188"/>
<source>Set repeat mode</source>
<extracomment>The name of the ActionType ({b4a572b1-6120-43e8-9a6c-18d099c8b162}) of ThingClass bluosPlayer</extracomment>
<translation>Setze Wiederholungsmodus</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="191"/>
<source>Set shuffle</source>
<extracomment>The name of the ActionType ({5636c21a-f14b-472c-8486-177543f6adfb}) of ThingClass bluosPlayer</extracomment>
<translation>Setze Zufallswiedergabe</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="194"/>
<source>Set volume</source>
<extracomment>The name of the ActionType ({a444bb7c-7266-4c7e-874a-eb04bb91b9cf}) of ThingClass bluosPlayer</extracomment>
<translation>Setze Lautstärke</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="197"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="200"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="203"/>
<source>Shuffle</source>
<extracomment>The name of the ParamType (ThingClass: bluosPlayer, ActionType: shuffle, ID: {5636c21a-f14b-472c-8486-177543f6adfb})
----------
The name of the ParamType (ThingClass: bluosPlayer, EventType: shuffle, ID: {5636c21a-f14b-472c-8486-177543f6adfb})
----------
The name of the StateType ({5636c21a-f14b-472c-8486-177543f6adfb}) of ThingClass bluosPlayer</extracomment>
<translation>Zufallswiedergabe</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="206"/>
<source>Shuffle changed</source>
<extracomment>The name of the EventType ({5636c21a-f14b-472c-8486-177543f6adfb}) of ThingClass bluosPlayer</extracomment>
<translation>Zufallswiedergabe geändert</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="209"/>
<source>Skip back</source>
<extracomment>The name of the ActionType ({864a5bcc-71e1-4d7f-8f2e-3c4222d5d988}) of ThingClass bluosPlayer</extracomment>
<translation>Vorheriges</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="212"/>
<source>Skip next</source>
<extracomment>The name of the ActionType ({30930095-6f97-48ef-8bbf-597d734f0751}) of ThingClass bluosPlayer</extracomment>
<translation>Nächstes</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="215"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="218"/>
<source>Source</source>
<extracomment>The name of the ParamType (ThingClass: bluosPlayer, EventType: source, ID: {604e4995-ee1a-44fc-bfcb-ca0e861710bd})
----------
The name of the StateType ({604e4995-ee1a-44fc-bfcb-ca0e861710bd}) of ThingClass bluosPlayer</extracomment>
<translation>Quelle</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="221"/>
<source>Source changed</source>
<extracomment>The name of the EventType ({604e4995-ee1a-44fc-bfcb-ca0e861710bd}) of ThingClass bluosPlayer</extracomment>
<translation>Quelle geändert</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="224"/>
<source>Stop</source>
<extracomment>The name of the ActionType ({2526ec6d-1c21-4f73-97f1-973a5f05b626}) of ThingClass bluosPlayer</extracomment>
<translation>Stopp</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="227"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="230"/>
<source>Title</source>
<extracomment>The name of the ParamType (ThingClass: bluosPlayer, EventType: title, ID: {6204ec9d-6aab-4fd3-8911-fdaa462c19a8})
----------
The name of the StateType ({6204ec9d-6aab-4fd3-8911-fdaa462c19a8}) of ThingClass bluosPlayer</extracomment>
<translation>Titel</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="233"/>
<source>Title changed</source>
<extracomment>The name of the EventType ({6204ec9d-6aab-4fd3-8911-fdaa462c19a8}) of ThingClass bluosPlayer</extracomment>
<translation>Titel geändert</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="236"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="239"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="242"/>
<source>Volume</source>
<extracomment>The name of the ParamType (ThingClass: bluosPlayer, ActionType: volume, ID: {a444bb7c-7266-4c7e-874a-eb04bb91b9cf})
----------
The name of the ParamType (ThingClass: bluosPlayer, EventType: volume, ID: {a444bb7c-7266-4c7e-874a-eb04bb91b9cf})
----------
The name of the StateType ({a444bb7c-7266-4c7e-874a-eb04bb91b9cf}) of ThingClass bluosPlayer</extracomment>
<translation>Lautstärke</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="245"/>
<source>Volume changed</source>
<extracomment>The name of the EventType ({a444bb7c-7266-4c7e-874a-eb04bb91b9cf}) of ThingClass bluosPlayer</extracomment>
<translation>Lautstärke geändert</translation>
</message>
</context>
</TS>

View File

@ -0,0 +1,295 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1">
<context>
<name>BluOS</name>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="77"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="80"/>
<source>Album</source>
<extracomment>The name of the ParamType (ThingClass: bluosPlayer, EventType: collection, ID: {d2a2db24-5855-40cd-a043-6f67e43acc61})
----------
The name of the StateType ({d2a2db24-5855-40cd-a043-6f67e43acc61}) of ThingClass bluosPlayer</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="83"/>
<source>Album changed</source>
<extracomment>The name of the EventType ({d2a2db24-5855-40cd-a043-6f67e43acc61}) of ThingClass bluosPlayer</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="86"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="89"/>
<source>Artist</source>
<extracomment>The name of the ParamType (ThingClass: bluosPlayer, EventType: artist, ID: {8c5372f1-3dde-4984-9955-9a436a29e8e3})
----------
The name of the StateType ({8c5372f1-3dde-4984-9955-9a436a29e8e3}) of ThingClass bluosPlayer</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="92"/>
<source>Artist changed</source>
<extracomment>The name of the EventType ({8c5372f1-3dde-4984-9955-9a436a29e8e3}) of ThingClass bluosPlayer</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="95"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="98"/>
<source>Artwork</source>
<extracomment>The name of the ParamType (ThingClass: bluosPlayer, EventType: artwork, ID: {918bfeed-bf96-4034-85d6-9f4cff35fd00})
----------
The name of the StateType ({918bfeed-bf96-4034-85d6-9f4cff35fd00}) of ThingClass bluosPlayer</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="101"/>
<source>Artwork changed</source>
<extracomment>The name of the EventType ({918bfeed-bf96-4034-85d6-9f4cff35fd00}) of ThingClass bluosPlayer</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="104"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="107"/>
<source>BluOS</source>
<extracomment>The name of the vendor ({39a492e9-e497-4b43-94d4-970eb9913b96})
----------
The name of the plugin BluOS ({71dd25b3-37ef-4b27-abca-24989fa38c61})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="110"/>
<source>BluOS player</source>
<extracomment>The name of the ThingClass ({406adcbc-1e7d-4e41-ae2a-f87b6bafd13d})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="113"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="116"/>
<source>Connected</source>
<extracomment>The name of the ParamType (ThingClass: bluosPlayer, EventType: connected, ID: {8092f387-5099-43e8-a7c4-7f0dd7b70fce})
----------
The name of the StateType ({8092f387-5099-43e8-a7c4-7f0dd7b70fce}) of ThingClass bluosPlayer</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="119"/>
<source>Connected changed</source>
<extracomment>The name of the EventType ({8092f387-5099-43e8-a7c4-7f0dd7b70fce}) of ThingClass bluosPlayer</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="122"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="125"/>
<source>Group</source>
<extracomment>The name of the ParamType (ThingClass: bluosPlayer, EventType: group, ID: {69757ef3-173f-499c-9304-4252de3588c6})
----------
The name of the StateType ({69757ef3-173f-499c-9304-4252de3588c6}) of ThingClass bluosPlayer</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="128"/>
<source>Group changed</source>
<extracomment>The name of the EventType ({69757ef3-173f-499c-9304-4252de3588c6}) of ThingClass bluosPlayer</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="131"/>
<source>IP Address</source>
<extracomment>The name of the ParamType (ThingClass: bluosPlayer, Type: thing, ID: {833f99cc-fc3f-48ef-a705-a69ae2c8e9ec})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="134"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="137"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="140"/>
<source>Mute</source>
<extracomment>The name of the ParamType (ThingClass: bluosPlayer, ActionType: mute, ID: {2f650ae6-f3a5-4851-8449-1ba02c4864e5})
----------
The name of the ParamType (ThingClass: bluosPlayer, EventType: mute, ID: {2f650ae6-f3a5-4851-8449-1ba02c4864e5})
----------
The name of the StateType ({2f650ae6-f3a5-4851-8449-1ba02c4864e5}) of ThingClass bluosPlayer</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="143"/>
<source>Mute changed</source>
<extracomment>The name of the EventType ({2f650ae6-f3a5-4851-8449-1ba02c4864e5}) of ThingClass bluosPlayer</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="146"/>
<source>Pause</source>
<extracomment>The name of the ActionType ({92446566-8c32-4ee2-9498-d9dd9333a75d}) of ThingClass bluosPlayer</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="149"/>
<source>Play</source>
<extracomment>The name of the ActionType ({253eb62f-d50d-4667-8213-8632de178aa3}) of ThingClass bluosPlayer</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="152"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="155"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="158"/>
<source>Playback status</source>
<extracomment>The name of the ParamType (ThingClass: bluosPlayer, ActionType: playbackStatus, ID: {43f552c7-7dfe-4358-bebd-ab558191cdfc})
----------
The name of the ParamType (ThingClass: bluosPlayer, EventType: playbackStatus, ID: {43f552c7-7dfe-4358-bebd-ab558191cdfc})
----------
The name of the StateType ({43f552c7-7dfe-4358-bebd-ab558191cdfc}) of ThingClass bluosPlayer</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="161"/>
<source>Playback status changed</source>
<extracomment>The name of the EventType ({43f552c7-7dfe-4358-bebd-ab558191cdfc}) of ThingClass bluosPlayer</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="164"/>
<source>Port</source>
<extracomment>The name of the ParamType (ThingClass: bluosPlayer, Type: thing, ID: {4628c040-6bbb-43c9-b25f-ce3b22300e3b})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="167"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="170"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="173"/>
<source>Repeat mode</source>
<extracomment>The name of the ParamType (ThingClass: bluosPlayer, ActionType: repeat, ID: {b4a572b1-6120-43e8-9a6c-18d099c8b162})
----------
The name of the ParamType (ThingClass: bluosPlayer, EventType: repeat, ID: {b4a572b1-6120-43e8-9a6c-18d099c8b162})
----------
The name of the StateType ({b4a572b1-6120-43e8-9a6c-18d099c8b162}) of ThingClass bluosPlayer</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="176"/>
<source>Repeat mode changed</source>
<extracomment>The name of the EventType ({b4a572b1-6120-43e8-9a6c-18d099c8b162}) of ThingClass bluosPlayer</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="179"/>
<source>Serial number</source>
<extracomment>The name of the ParamType (ThingClass: bluosPlayer, Type: thing, ID: {8fd2a7d5-bb26-4f18-a488-5ab0779733f8})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="182"/>
<source>Set mute</source>
<extracomment>The name of the ActionType ({2f650ae6-f3a5-4851-8449-1ba02c4864e5}) of ThingClass bluosPlayer</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="185"/>
<source>Set playback status</source>
<extracomment>The name of the ActionType ({43f552c7-7dfe-4358-bebd-ab558191cdfc}) of ThingClass bluosPlayer</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="188"/>
<source>Set repeat mode</source>
<extracomment>The name of the ActionType ({b4a572b1-6120-43e8-9a6c-18d099c8b162}) of ThingClass bluosPlayer</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="191"/>
<source>Set shuffle</source>
<extracomment>The name of the ActionType ({5636c21a-f14b-472c-8486-177543f6adfb}) of ThingClass bluosPlayer</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="194"/>
<source>Set volume</source>
<extracomment>The name of the ActionType ({a444bb7c-7266-4c7e-874a-eb04bb91b9cf}) of ThingClass bluosPlayer</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="197"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="200"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="203"/>
<source>Shuffle</source>
<extracomment>The name of the ParamType (ThingClass: bluosPlayer, ActionType: shuffle, ID: {5636c21a-f14b-472c-8486-177543f6adfb})
----------
The name of the ParamType (ThingClass: bluosPlayer, EventType: shuffle, ID: {5636c21a-f14b-472c-8486-177543f6adfb})
----------
The name of the StateType ({5636c21a-f14b-472c-8486-177543f6adfb}) of ThingClass bluosPlayer</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="206"/>
<source>Shuffle changed</source>
<extracomment>The name of the EventType ({5636c21a-f14b-472c-8486-177543f6adfb}) of ThingClass bluosPlayer</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="209"/>
<source>Skip back</source>
<extracomment>The name of the ActionType ({864a5bcc-71e1-4d7f-8f2e-3c4222d5d988}) of ThingClass bluosPlayer</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="212"/>
<source>Skip next</source>
<extracomment>The name of the ActionType ({30930095-6f97-48ef-8bbf-597d734f0751}) of ThingClass bluosPlayer</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="215"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="218"/>
<source>Source</source>
<extracomment>The name of the ParamType (ThingClass: bluosPlayer, EventType: source, ID: {604e4995-ee1a-44fc-bfcb-ca0e861710bd})
----------
The name of the StateType ({604e4995-ee1a-44fc-bfcb-ca0e861710bd}) of ThingClass bluosPlayer</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="221"/>
<source>Source changed</source>
<extracomment>The name of the EventType ({604e4995-ee1a-44fc-bfcb-ca0e861710bd}) of ThingClass bluosPlayer</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="224"/>
<source>Stop</source>
<extracomment>The name of the ActionType ({2526ec6d-1c21-4f73-97f1-973a5f05b626}) of ThingClass bluosPlayer</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="227"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="230"/>
<source>Title</source>
<extracomment>The name of the ParamType (ThingClass: bluosPlayer, EventType: title, ID: {6204ec9d-6aab-4fd3-8911-fdaa462c19a8})
----------
The name of the StateType ({6204ec9d-6aab-4fd3-8911-fdaa462c19a8}) of ThingClass bluosPlayer</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="233"/>
<source>Title changed</source>
<extracomment>The name of the EventType ({6204ec9d-6aab-4fd3-8911-fdaa462c19a8}) of ThingClass bluosPlayer</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="236"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="239"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="242"/>
<source>Volume</source>
<extracomment>The name of the ParamType (ThingClass: bluosPlayer, ActionType: volume, ID: {a444bb7c-7266-4c7e-874a-eb04bb91b9cf})
----------
The name of the ParamType (ThingClass: bluosPlayer, EventType: volume, ID: {a444bb7c-7266-4c7e-874a-eb04bb91b9cf})
----------
The name of the StateType ({a444bb7c-7266-4c7e-874a-eb04bb91b9cf}) of ThingClass bluosPlayer</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/bluos/plugininfo.h" line="245"/>
<source>Volume changed</source>
<extracomment>The name of the EventType ({a444bb7c-7266-4c7e-874a-eb04bb91b9cf}) of ThingClass bluosPlayer</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
</TS>

15
debian/control vendored
View File

@ -88,6 +88,21 @@ Description: nymea.io plugin for awattar
This package will install the nymea.io plugin for awattar
Package: nymea-plugin-bluos
Architecture: any
Depends: ${shlibs:Depends},
${misc:Depends},
nymea-plugins-translations,
Description: nymea.io plugin for bluos
The nymea daemon is a plugin based IoT (Internet of Things) server. The
server works like a translator for devices, things and services and
allows them to interact.
With the powerful rule engine you are able to connect any device available
in the system and create individual scenes and behaviors for your environment.
.
This package will install the nymea.io plugin for bluos
Package: nymea-plugin-boblight
Architecture: any
Depends: ${shlibs:Depends},

1
debian/nymea-plugin-bluos.install.in vendored Normal file
View File

@ -0,0 +1 @@
usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginbluos.so

View File

@ -5,6 +5,7 @@ PLUGIN_DIRS = \
aqi \
avahimonitor \
awattar \
bluos \
boblight \
bose \
coinmarketcap \