espuino: Add ESPuino plugin
See https://forum.espuino.de/ and https://github.com/biologist79/ESPuino/master
parent
68e9cd8c01
commit
effe59e9b9
|
|
@ -210,6 +210,15 @@ Description: nymea integration plugin for elgato
|
|||
This package contains the nymea integration plugin for devices from Elgato
|
||||
|
||||
|
||||
Package: nymea-plugin-espuino
|
||||
Architecture: any
|
||||
Depends: ${shlibs:Depends},
|
||||
${misc:Depends},
|
||||
Conflicts: nymea-plugins-translations (< 1.0.1)
|
||||
Description: nymea integration plugin for ESPuino
|
||||
This package contains the nymea integration plugin for ESPuino devices.
|
||||
|
||||
|
||||
Package: nymea-plugin-fastcom
|
||||
Architecture: any
|
||||
Depends: ${misc:Depends},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginespuino.so
|
||||
espuino/translations/*qm usr/share/nymea/translations/
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
# ESPuino
|
||||
|
||||
This plugin allows to integrate nymea with [ESPunio](https://github.com/biologist79/ESPuino),
|
||||
a software for Rfid-controlled music players running on ESP32 hardware.
|
||||
|
||||
ESPuino boxes can't be bought off the shelf, but there's a (mostly
|
||||
German speaking) community in the [ESPuino forum](https://forum.espuino.de/)
|
||||
that provides a lot of documentation and ideas for building your own custom
|
||||
ESPuino.
|
||||
|
||||
## Usage
|
||||
|
||||
As a prerequisite, ESPuino has to be compiled with WiFi and MQTT
|
||||
enabled and the ESPuino must have been set up to connect to your home
|
||||
WiFi. For the mDNS based auto discovery to work, the hostname must start with
|
||||
`espuino`.
|
||||
|
||||
Then the ESPuino can be added to nymea.
|
||||
|
||||
## Supported features
|
||||
|
||||
- Display current title and cover art
|
||||
- Control volume
|
||||
- Play/pause
|
||||
- Lock hardware controls
|
||||
- Browse SD-Card and start playback
|
||||
- Control LED brightness
|
||||
- Configure sleep or repeat modes
|
||||
- Show battery status
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
include(../plugins.pri)
|
||||
|
||||
QT += network \
|
||||
websockets
|
||||
|
||||
PKGCONFIG += nymea-mqtt
|
||||
|
||||
SOURCES += \
|
||||
integrationpluginespuino.cpp
|
||||
|
||||
HEADERS += \
|
||||
integrationpluginespuino.h
|
||||
|
|
@ -0,0 +1,556 @@
|
|||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2022, 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 "integrationpluginespuino.h"
|
||||
|
||||
#include "integrations/integrationplugin.h"
|
||||
|
||||
#include "plugininfo.h"
|
||||
|
||||
#include "network/networkaccessmanager.h"
|
||||
#include "network/mqtt/mqttprovider.h"
|
||||
#include "network/mqtt/mqttchannel.h"
|
||||
#include "network/zeroconf/zeroconfservicebrowser.h"
|
||||
#include "platform/platformzeroconfcontroller.h"
|
||||
|
||||
#include <mqttclient.h>
|
||||
|
||||
#include <QWebSocket>
|
||||
#include <QJsonDocument>
|
||||
#include <QNetworkReply>
|
||||
#include <QUrlQuery>
|
||||
|
||||
void IntegrationPluginEspuino::init()
|
||||
{
|
||||
m_zeroConfBrowser = hardwareManager()->zeroConfController()->createServiceBrowser("_http._tcp");
|
||||
}
|
||||
|
||||
void IntegrationPluginEspuino::discoverThings(ThingDiscoveryInfo *info)
|
||||
{
|
||||
foreach (const ZeroConfServiceEntry &entry, m_zeroConfBrowser->serviceEntries()) {
|
||||
QRegExp match("espuino.*");
|
||||
if (!match.exactMatch(entry.name())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
qCDebug(dcESPuino()) << "Found device:" << entry;
|
||||
ThingDescriptor descriptor(info->thingClassId(), entry.hostName(), entry.hostAddress().toString());
|
||||
ParamList params;
|
||||
params << Param(espuinoThingHostnameParamTypeId, entry.hostName());
|
||||
descriptor.setParams(params);
|
||||
|
||||
Things existingThings = myThings().filterByParam(espuinoThingHostnameParamTypeId, entry.hostName());
|
||||
if (existingThings.count() == 1) {
|
||||
qCDebug(dcESPuino()) << "This device already exists in the system!";
|
||||
descriptor.setThingId(existingThings.first()->id());
|
||||
}
|
||||
|
||||
info->addThingDescriptor(descriptor);
|
||||
}
|
||||
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
}
|
||||
|
||||
void IntegrationPluginEspuino::setupThing(ThingSetupInfo *info)
|
||||
{
|
||||
Thing *thing = info->thing();
|
||||
|
||||
MqttChannel *channel;
|
||||
if (myThings().findByParams(ParamList() << Param(espuinoThingHostnameParamTypeId, thing->paramValue(espuinoThingHostnameParamTypeId).toString())) == nullptr){
|
||||
qCDebug(dcESPuino) << "Creating MQTT channel for new device.";
|
||||
|
||||
channel = hardwareManager()->mqttProvider()->createChannel(QHostAddress(getHost(thing)), {"Cmnd/ESPuino", "State/ESPuino"});
|
||||
if (!channel) {
|
||||
qCWarning(dcESPuino) << "Failed to create MQTT channel.";
|
||||
info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("Error creating MQTT channel. Please check MQTT server settings."));
|
||||
return;
|
||||
}
|
||||
|
||||
qCInfo(dcESPuino) << "Reconfiguring MQTT settings via Websocket.";
|
||||
QWebSocket *ws = new QWebSocket(QString(), QWebSocketProtocol::VersionLatest, info);
|
||||
connect(ws, &QWebSocket::connected, info, [channel, ws](){
|
||||
QJsonDocument jsonRequest{QJsonObject
|
||||
{
|
||||
{"mqtt", QJsonObject{{"mqttEnable", "1"},
|
||||
{"mqttClientId", channel->clientId()},
|
||||
{"mqttServer", channel->serverAddress().toString()},
|
||||
{"mqttUser", channel->username()},
|
||||
{"mqttPwd", channel->password()},
|
||||
{"mqttPort", QString::number(channel->serverPort())}}}
|
||||
}};
|
||||
ws->sendTextMessage(jsonRequest.toJson(QJsonDocument::Compact));
|
||||
});
|
||||
connect(ws, &QWebSocket::textMessageReceived, info, [this, info, thing, channel, ws](const QString &message){
|
||||
ws->close();
|
||||
|
||||
QJsonParseError parseError;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(message.toUtf8(), &parseError);
|
||||
if (parseError.error != QJsonParseError::NoError) {
|
||||
qCWarning(dcESPuino()) << "Json parse error:" << parseError.error << parseError.errorString() << "Received:" << message;
|
||||
info->finish(Thing::ThingErrorSetupFailed, QT_TR_NOOP("Failed to configure MQTT via Websocket."));
|
||||
hardwareManager()->mqttProvider()->releaseChannel(channel);
|
||||
return;
|
||||
}
|
||||
if (jsonDoc.object().value("status").toString() != "ok") {
|
||||
qCWarning(dcESPuino()) << "Failed to configure MQTT via websocket. Received:" << message;
|
||||
info->finish(Thing::ThingErrorSetupFailed, QT_TR_NOOP("Failed to configure MQTT via Websocket."));
|
||||
hardwareManager()->mqttProvider()->releaseChannel(channel);
|
||||
return;
|
||||
}
|
||||
|
||||
pluginStorage()->beginGroup(thing->id().toString());
|
||||
pluginStorage()->setValue("clientId", channel->clientId());
|
||||
pluginStorage()->setValue("username", channel->username());
|
||||
pluginStorage()->setValue("password", channel->password());
|
||||
pluginStorage()->endGroup();
|
||||
|
||||
qCInfo(dcESPuino) << "Restarting box to apply new MQTT config.";
|
||||
QUrl url(QString("http://%1/restart").arg(getHost(thing)));
|
||||
QNetworkRequest request(url);
|
||||
QNetworkReply *reply = hardwareManager()->networkManager()->get(request);
|
||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
});
|
||||
QUrl url(QString("ws://%1/ws").arg(getHost(thing)));
|
||||
ws->open(url);
|
||||
} else {
|
||||
qCDebug(dcESPuino) << "Creating MQTT channel for existing device.";
|
||||
pluginStorage()->beginGroup(thing->id().toString());
|
||||
QString clientId = pluginStorage()->value("clientId").toString();
|
||||
QString username = pluginStorage()->value("username").toString();
|
||||
QString password = pluginStorage()->value("password").toString();
|
||||
pluginStorage()->endGroup();
|
||||
channel = hardwareManager()->mqttProvider()->createChannel(clientId, username, password, QHostAddress(getHost(thing)), {"Cmnd/ESPuino", "State/ESPuino"});
|
||||
if (!channel) {
|
||||
qCWarning(dcESPuino) << "Failed to create MQTT channel.";
|
||||
info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("Error creating MQTT channel. Please check MQTT server settings."));
|
||||
return;
|
||||
}
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
}
|
||||
|
||||
m_mqttChannels.insert(thing, channel);
|
||||
connect(channel, &MqttChannel::clientConnected, this, &IntegrationPluginEspuino::onClientConnected);
|
||||
connect(channel, &MqttChannel::clientDisconnected, this, &IntegrationPluginEspuino::onClientDisconnected);
|
||||
connect(channel, &MqttChannel::publishReceived, this, &IntegrationPluginEspuino::onPublishReceived);
|
||||
}
|
||||
|
||||
void IntegrationPluginEspuino::thingRemoved(Thing *thing)
|
||||
{
|
||||
qCDebug(dcESPuino) << "Device removed" << thing->name();
|
||||
if (m_mqttChannels.contains(thing)) {
|
||||
qCDebug(dcESPuino) << "Releasing MQTT channel";
|
||||
MqttChannel* channel = m_mqttChannels.take(thing);
|
||||
hardwareManager()->mqttProvider()->releaseChannel(channel);
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginEspuino::onClientConnected(MqttChannel *channel)
|
||||
{
|
||||
Thing *thing = m_mqttChannels.key(channel);
|
||||
qCDebug(dcESPuino) << "Thing connected" << thing->name();
|
||||
if (!thing) {
|
||||
qCWarning(dcESPuino) << "Received a MQTT connected message from a client but don't have a matching thing";
|
||||
return;
|
||||
}
|
||||
thing->setStateValue(espuinoConnectedStateTypeId, true);
|
||||
}
|
||||
|
||||
void IntegrationPluginEspuino::onClientDisconnected(MqttChannel *channel)
|
||||
{
|
||||
Thing *thing = m_mqttChannels.key(channel);
|
||||
qCDebug(dcESPuino) << "Thing disconnected" << thing->name();
|
||||
if (!thing) {
|
||||
qCWarning(dcESPuino) << "Received a MQTT disconnected message from a client but don't have a matching thing";
|
||||
return;
|
||||
}
|
||||
thing->setStateValue(espuinoConnectedStateTypeId, false);
|
||||
}
|
||||
|
||||
void IntegrationPluginEspuino::onPublishReceived(MqttChannel *channel, const QString &topic, const QByteArray &payload)
|
||||
{
|
||||
qCDebug(dcESPuino) << "Publish received" << topic << payload;
|
||||
|
||||
Thing *thing = m_mqttChannels.key(channel);
|
||||
if (!thing) {
|
||||
qCWarning(dcESPuino) << "Received a publish message from a client but don't have a matching thing";
|
||||
return;
|
||||
}
|
||||
|
||||
if (topic == "State/ESPuino/State") {
|
||||
thing->setStateValue(espuinoConnectedStateTypeId, payload == "Online");
|
||||
} else if (topic == "State/ESPuino/Playmode") {
|
||||
thing->setStateValue(espuinoPlaybackStatusStateTypeId, payload == "0" ? "Stopped" : "Playing");
|
||||
|
||||
QString playmode = "None";
|
||||
if (payload == "0") {
|
||||
playmode = "None";
|
||||
} else if (payload == "1") {
|
||||
playmode = "Single track";
|
||||
} else if (payload == "2") {
|
||||
playmode = "Single track (loop)";
|
||||
} else if (payload == "12") {
|
||||
playmode = "Single track of a directory (random). Followed by sleep.";
|
||||
} else if (payload == "3") {
|
||||
playmode = "Audiobook";
|
||||
} else if (payload == "4") {
|
||||
playmode = "Audiobook (loop)";
|
||||
} else if (payload == "5") {
|
||||
playmode = "All tracks of a directory (sorted alph.)";
|
||||
} else if (payload == "6") {
|
||||
playmode = "All tracks of a directory (random)";
|
||||
} else if (payload == "7") {
|
||||
playmode = "All tracks of a directory (sorted alph., loop)";
|
||||
} else if (payload == "9") {
|
||||
playmode = "All tracks of a directory (random, loop)";
|
||||
} else if (payload == "8") {
|
||||
playmode = "Webradio";
|
||||
} else if (payload == "11") {
|
||||
playmode = "List (files from SD and/or webstreams) from local .m3u-File";
|
||||
} else if (payload == "10") {
|
||||
playmode = "Busy";
|
||||
} else {
|
||||
qCWarning(dcESPuino) << "Unknown playmode received" << payload;
|
||||
}
|
||||
thing->setStateValue(espuinoPlaymodeStateTypeId, playmode);
|
||||
} else if (topic == "State/ESPuino/Loudness") {
|
||||
bool ok;
|
||||
int volume = payload.toInt(&ok);
|
||||
if (ok) {
|
||||
thing->setStateValue(espuinoVolumeStateTypeId, volume);
|
||||
} else {
|
||||
qCWarning(dcESPuino) << "Failed to read numeric volume value" << payload;
|
||||
thing->setStateValue(espuinoVolumeStateTypeId, 0);
|
||||
}
|
||||
} else if (topic == "State/ESPuino/Track") {
|
||||
thing->setStateValue(espuinoTitleStateTypeId, payload);
|
||||
} else if (topic == "State/ESPuino/CoverChanged") {
|
||||
thing->setStateValue(espuinoArtworkStateTypeId, QString("http://%1/cover?%2").arg(getHost(thing)).arg(QDateTime::currentMSecsSinceEpoch()));
|
||||
} else if (topic == "State/ESPuino/LedBrightness") {
|
||||
bool ok;
|
||||
int brightness = payload.toInt(&ok);
|
||||
if (ok) {
|
||||
thing->setStateValue(espuinoBrightnessStateTypeId, brightness);
|
||||
} else {
|
||||
qCWarning(dcESPuino) << "Failed to read numeric brightness value" << payload;
|
||||
thing->setStateValue(espuinoBrightnessStateTypeId, 0);
|
||||
}
|
||||
} else if (topic == "State/ESPuino/RepeatMode") {
|
||||
if (payload == "3") {
|
||||
thing->setStateValue(espuinoRepeatStateTypeId, "All");
|
||||
} if (payload == "1") {
|
||||
thing->setStateValue(espuinoRepeatStateTypeId, "One");
|
||||
} else {
|
||||
thing->setStateValue(espuinoRepeatStateTypeId, "None");
|
||||
}
|
||||
} else if (topic == "State/ESPuino/WifiRssi") {
|
||||
bool ok;
|
||||
int rssi = payload.toInt(&ok);
|
||||
if (ok) {
|
||||
thing->setStateValue(espuinoSignalStrengthStateTypeId, qMin(100, qMax(0, (rssi + 100) * 2)));
|
||||
} else {
|
||||
thing->setStateValue(espuinoSignalStrengthStateTypeId, 0);
|
||||
}
|
||||
} else if (topic == "State/ESPuino/LockControl") {
|
||||
thing->setStateValue(espuinoChildLockStateTypeId, payload == "ON");
|
||||
} else if (topic == "State/ESPuino/SleepTimer") {
|
||||
if (payload == "EOP") {
|
||||
thing->setStateValue(espuinoSleepmodeStateTypeId, "End of playlist");
|
||||
} else if (payload == "EOT") {
|
||||
thing->setStateValue(espuinoSleepmodeStateTypeId, "End of track");
|
||||
} else if (payload == "EO5T") {
|
||||
thing->setStateValue(espuinoSleepmodeStateTypeId, "End of five tracks");
|
||||
} else if (payload == "0") {
|
||||
thing->setStateValue(espuinoSleepmodeStateTypeId, "None");
|
||||
} else {
|
||||
bool ok;
|
||||
int timer = payload.toInt(&ok);
|
||||
if (ok) {
|
||||
thing->setStateValue(espuinoSleepmodeStateTypeId, "Timer");
|
||||
thing->setStateValue(espuinoSleeptimerStateTypeId, timer);
|
||||
} else {
|
||||
qCWarning(dcESPuino) << "Failed to read numeric sleep timer value" << payload;
|
||||
thing->setStateValue(espuinoSleepmodeStateTypeId, "None");
|
||||
}
|
||||
}
|
||||
} else if (topic == "State/ESPuino/Battery") {
|
||||
bool ok;
|
||||
float battery = payload.toFloat(&ok);
|
||||
if (ok) {
|
||||
thing->setStateValue(espuinoBatteryLevelStateTypeId, battery);
|
||||
thing->setStateValue(espuinoBatteryCriticalStateTypeId, battery < 5.0f);
|
||||
} else {
|
||||
qCWarning(dcESPuino) << "Failed to read numeric battery level value" << payload;
|
||||
thing->setStateValue(espuinoBatteryLevelStateTypeId, 0);
|
||||
thing->setStateValue(espuinoBatteryCriticalStateTypeId, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Finish pending action.
|
||||
QPointer<ThingActionInfo> thingActionInfo = m_pendingActions.take(topic);
|
||||
if (!thingActionInfo.isNull()) {
|
||||
thingActionInfo->finish(Thing::ThingErrorNoError);
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginEspuino::executeAction(ThingActionInfo *info)
|
||||
{
|
||||
Thing *thing = info->thing();
|
||||
Action action = info->action();
|
||||
|
||||
MqttChannel *channel = m_mqttChannels.value(thing);
|
||||
if (!channel) {
|
||||
qCWarning(dcESPuino) << "No valid MQTT channel for thing" << thing->name();
|
||||
return info->finish(Thing::ThingErrorThingNotFound);
|
||||
}
|
||||
|
||||
// See: https://github.com/biologist79/ESPuino#mqtt-topics-and-their-ranges
|
||||
QString topic;
|
||||
QByteArray payload;
|
||||
if (action.actionTypeId() == espuinoVolumeActionTypeId) {
|
||||
topic = "Cmnd/ESPuino/Loudness";
|
||||
payload = QByteArray::number(action.param(espuinoVolumeActionVolumeParamTypeId).value().toInt());
|
||||
m_pendingActions.insert("State/ESPuino/Loudness", info);
|
||||
} else if (action.actionTypeId() == espuinoIncreaseVolumeActionTypeId) {
|
||||
topic = "Cmnd/ESPuino/Loudness";
|
||||
payload = QByteArray::number(thing->stateValue(espuinoVolumeStateTypeId).toInt() + 1);
|
||||
m_pendingActions.insert("State/ESPuino/Loudness", info);
|
||||
} else if (action.actionTypeId() == espuinoDecreaseVolumeActionTypeId) {
|
||||
topic = "Cmnd/ESPuino/Loudness";
|
||||
payload = QByteArray::number(thing->stateValue(espuinoVolumeStateTypeId).toInt() - 1);
|
||||
m_pendingActions.insert("State/ESPuino/Loudness", info);
|
||||
} else if (action.actionTypeId() == espuinoStopActionTypeId) {
|
||||
topic = "Cmnd/ESPuino/TrackControl";
|
||||
payload = "1";
|
||||
m_pendingActions.insert("State/ESPuino/TrackControl", info);
|
||||
} else if (action.actionTypeId() == espuinoSkipNextActionTypeId) {
|
||||
topic = "Cmnd/ESPuino/TrackControl";
|
||||
payload = "4";
|
||||
m_pendingActions.insert("State/ESPuino/TrackControl", info);
|
||||
} else if (action.actionTypeId() == espuinoSkipBackActionTypeId) {
|
||||
topic = "Cmnd/ESPuino/TrackControl";
|
||||
payload = "5";
|
||||
m_pendingActions.insert("State/ESPuino/TrackControl", info);
|
||||
} else if (action.actionTypeId() == espuinoPlayActionTypeId) {
|
||||
topic = "Cmnd/ESPuino/TrackControl";
|
||||
payload = "3";
|
||||
m_pendingActions.insert("State/ESPuino/TrackControl", info);
|
||||
} else if (action.actionTypeId() == espuinoPauseActionTypeId) {
|
||||
topic = "Cmnd/ESPuino/TrackControl";
|
||||
payload = "3";
|
||||
m_pendingActions.insert("State/ESPuino/TrackControl", info);
|
||||
} else if (action.actionTypeId() == espuinoBrightnessActionTypeId) {
|
||||
topic = "Cmnd/ESPuino/LedBrightness";
|
||||
payload = QByteArray::number(action.param(espuinoBrightnessActionBrightnessParamTypeId).value().toInt());
|
||||
m_pendingActions.insert("State/ESPuino/LedBrightness", info);
|
||||
} else if (action.actionTypeId() == espuinoRepeatActionTypeId) {
|
||||
topic = "Cmnd/ESPuino/RepeatMode";
|
||||
QString repeat = action.param(espuinoRepeatActionRepeatParamTypeId).value().toString();
|
||||
if (repeat == "One") {
|
||||
payload = "1";
|
||||
} else if (repeat == "All") {
|
||||
payload = "3";
|
||||
} else {
|
||||
payload = "0";
|
||||
}
|
||||
m_pendingActions.insert("State/ESPuino/RepeatMode", info);
|
||||
} else if (action.actionTypeId() == espuinoChildLockActionTypeId) {
|
||||
topic = "Cmnd/ESPuino/LockControls";
|
||||
payload = action.param(espuinoChildLockActionChildLockParamTypeId).value().toBool() ? "ON" : "OFF";
|
||||
m_pendingActions.insert("State/ESPuino/LockControls", info);
|
||||
} else if (action.actionTypeId() == espuinoSleepmodeActionTypeId) {
|
||||
topic = "Cmnd/ESPuino/SleepTimer";
|
||||
QString sleepmode = action.param(espuinoSleepmodeActionSleepmodeParamTypeId).value().toString();
|
||||
if (sleepmode == "None") {
|
||||
payload = "0";
|
||||
} else if (sleepmode == "End of playlist") {
|
||||
payload = "EOP";
|
||||
} else if (sleepmode == "End of track") {
|
||||
payload = "EOT";
|
||||
} else if (sleepmode == "End of five tracks") {
|
||||
payload = "EO5T";
|
||||
} else {
|
||||
payload = QByteArray::number(thing->stateValue(espuinoSleeptimerStateTypeId).toInt());
|
||||
}
|
||||
m_pendingActions.insert("State/ESPuino/SleepTimer", info);
|
||||
} else if (action.actionTypeId() == espuinoSleeptimerActionTypeId) {
|
||||
thing->setStateValue(espuinoSleeptimerStateTypeId, action.param(espuinoSleeptimerActionSleeptimerParamTypeId).value().toUInt());
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
}
|
||||
|
||||
if (!topic.isEmpty()) {
|
||||
qCDebug(dcESPuino) << "Publishing:" << topic << payload;
|
||||
channel->publish(topic, payload);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
void IntegrationPluginEspuino::browseThing(BrowseResult *result)
|
||||
{
|
||||
QUrlQuery id(result->itemId());
|
||||
browseThing(result, id.queryItemValue("path"));
|
||||
}
|
||||
|
||||
void IntegrationPluginEspuino::browseThing(BrowseResult *result, const QString &path)
|
||||
{
|
||||
QUrl url(QString("http://%1/explorer?path=%2").arg(getHost(result->thing())).arg(path.isEmpty() ? "/" : path));
|
||||
QNetworkRequest request(url);
|
||||
QNetworkReply *reply = hardwareManager()->networkManager()->get(request);
|
||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||
connect(reply, &QNetworkReply::finished, result, [result, reply, path, this]() {
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
qCWarning(dcESPuino()) << "Error fetching paths";
|
||||
result->finish(Thing::ThingErrorHardwareNotAvailable);
|
||||
return;
|
||||
}
|
||||
QByteArray data = reply->readAll();
|
||||
QJsonParseError error;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
qCWarning(dcESPuino()) << "Error parsing json" << data;
|
||||
result->finish(Thing::ThingErrorHardwareFailure);
|
||||
return;
|
||||
}
|
||||
|
||||
//qCDebug(dcESPuino()) << "Reply:" << qUtf8Printable(jsonDoc.toJson());
|
||||
QVariantList variantList = jsonDoc.toVariant().toList();
|
||||
foreach (const QVariant &element, variantList) {
|
||||
QVariantMap variantMap = element.toMap();
|
||||
QUrlQuery id;
|
||||
id.addQueryItem("name", variantMap.value("name").toString());
|
||||
id.addQueryItem("path", path + "/" + variantMap.value("name").toString());
|
||||
if (variantMap.value("dir").toBool()) {
|
||||
id.addQueryItem("playmode", QString::number(5));
|
||||
id.addQueryItem("type", "dir");
|
||||
} else if (variantMap.value("name").toString().contains(QRegExp("\\.(:?mp3|ogg|wav|wma|acc|m4a|flac)$", Qt::CaseInsensitive))) {
|
||||
id.addQueryItem("playmode", QString::number(1));
|
||||
id.addQueryItem("type", "audiofile");
|
||||
} else if (variantMap.value("name").toString().endsWith(".m3u", Qt::CaseInsensitive)) {
|
||||
id.addQueryItem("playmode", QString::number(11));
|
||||
id.addQueryItem("type", "playlist");
|
||||
}
|
||||
result->addItem(browserItemFromQuery(id));
|
||||
}
|
||||
result->finish(Thing::ThingErrorNoError);
|
||||
});
|
||||
}
|
||||
|
||||
void IntegrationPluginEspuino::browserItem(BrowserItemResult *result)
|
||||
{
|
||||
QUrlQuery id(result->itemId());
|
||||
result->finish(browserItemFromQuery(id));
|
||||
}
|
||||
|
||||
BrowserItem IntegrationPluginEspuino::browserItemFromQuery(const QUrlQuery &id)
|
||||
{
|
||||
BrowserItem item;
|
||||
item.setDisplayName(id.queryItemValue("name"));
|
||||
if (id.queryItemValue("type") == "dir") {
|
||||
item.setId(id.toString());
|
||||
item.setIcon(BrowserItem::BrowserIconFolder);
|
||||
item.setBrowsable(true);
|
||||
item.setActionTypeIds({espuinoPlayAllBrowserItemActionTypeId});
|
||||
} else if (id.queryItemValue("type") == "audiofile") {
|
||||
item.setId(id.toString());
|
||||
item.setIcon(BrowserItem::BrowserIconMusic);
|
||||
item.setExecutable(true);
|
||||
} else if (id.queryItemValue("type") == "playlist") {
|
||||
item.setId(id.toString());
|
||||
item.setIcon(BrowserItem::BrowserIconDocument);
|
||||
item.setExecutable(true);
|
||||
} else {
|
||||
item.setId(id.toString());
|
||||
item.setIcon(BrowserItem::BrowserIconFile);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
void IntegrationPluginEspuino::IntegrationPluginEspuino::executeBrowserItem(BrowserActionInfo *info)
|
||||
{
|
||||
Thing *thing = info->thing();
|
||||
BrowserAction action = info->browserAction();
|
||||
|
||||
QUrl url(QString("http://%1/exploreraudio?%2").arg(getHost(thing)).arg(action.itemId()));
|
||||
qCInfo(dcESPuino) << "Starting playback" << url.toString();
|
||||
QNetworkRequest request(url);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
QNetworkReply *reply = hardwareManager()->networkManager()->post(request, QByteArray());
|
||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||
connect(reply, &QNetworkReply::finished, this, [reply]() {
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
qCWarning(dcESPuino()) << "Fail to execute play action";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void IntegrationPluginEspuino::executeBrowserItemAction(BrowserItemActionInfo *info)
|
||||
{
|
||||
Thing *thing = info->thing();
|
||||
BrowserItemAction action = info->browserItemAction();
|
||||
|
||||
if (action.actionTypeId() == espuinoPlayAllBrowserItemActionTypeId) {
|
||||
QUrl url(QString("http://%1/exploreraudio?%2").arg(getHost(thing)).arg(action.itemId()));
|
||||
qCInfo(dcESPuino) << "Starting playback" << url.toString();
|
||||
QNetworkRequest request(url);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
QNetworkReply *reply = hardwareManager()->networkManager()->post(request, QByteArray());
|
||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||
connect(reply, &QNetworkReply::finished, this, [reply]() {
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
qCWarning(dcESPuino()) << "Fail to execute play action";
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
QString IntegrationPluginEspuino::getHost(Thing *thing) const
|
||||
{
|
||||
QString hostName = thing->paramValue(espuinoThingHostnameParamTypeId).toString();
|
||||
ZeroConfServiceEntry zeroConfEntry;
|
||||
foreach (const ZeroConfServiceEntry &entry, m_zeroConfBrowser->serviceEntries()) {
|
||||
if (hostName == entry.hostName()) {
|
||||
zeroConfEntry = entry;
|
||||
}
|
||||
}
|
||||
QString host;
|
||||
pluginStorage()->beginGroup(thing->id().toString());
|
||||
if (zeroConfEntry.isValid()) {
|
||||
host = zeroConfEntry.hostAddress().toString();
|
||||
pluginStorage()->setValue("cachedAddress", host);
|
||||
} else if (pluginStorage()->contains("cachedAddress")){
|
||||
host = pluginStorage()->value("cachedAddress").toString();
|
||||
} else {
|
||||
qCWarning(dcESPuino()) << "Unable to determine IP address for:" << hostName;
|
||||
}
|
||||
pluginStorage()->endGroup();
|
||||
|
||||
return host;
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2022, 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 INTEGRATIONPLUGINESPUINO_H
|
||||
#define INTEGRATIONPLUGINESPUINO_H
|
||||
|
||||
#include "integrations/integrationplugin.h"
|
||||
#include "plugintimer.h"
|
||||
|
||||
#include <QHash>
|
||||
|
||||
#include "extern-plugininfo.h"
|
||||
|
||||
class MqttChannel;
|
||||
class ZeroConfServiceBrowser;
|
||||
|
||||
class IntegrationPluginEspuino : public IntegrationPlugin
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginespuino.json")
|
||||
Q_INTERFACES(IntegrationPlugin)
|
||||
|
||||
public:
|
||||
void init() override;
|
||||
void discoverThings(ThingDiscoveryInfo *info) override;
|
||||
void setupThing(ThingSetupInfo *info) override;
|
||||
void thingRemoved(Thing *thing) override;
|
||||
void executeAction(ThingActionInfo *info) override;
|
||||
void browseThing(BrowseResult *result) override;
|
||||
void browseThing(BrowseResult *result, const QString &path);
|
||||
void browserItem(BrowserItemResult *result) override;
|
||||
BrowserItem browserItemFromQuery(const QUrlQuery &query);
|
||||
void executeBrowserItem(BrowserActionInfo *info) override;
|
||||
void executeBrowserItemAction(BrowserItemActionInfo *info) override;
|
||||
|
||||
private slots:
|
||||
void onClientConnected(MqttChannel *channel);
|
||||
void onClientDisconnected(MqttChannel *channel);
|
||||
void onPublishReceived(MqttChannel* channel, const QString &topic, const QByteArray &payload);
|
||||
|
||||
private:
|
||||
QString getHost(Thing *thing) const;
|
||||
|
||||
ZeroConfServiceBrowser *m_zeroConfBrowser;
|
||||
QHash<Thing*, MqttChannel*> m_mqttChannels;
|
||||
QHash<ThingClassId, ParamTypeId> m_ipAddressParamTypeMap;
|
||||
QMap<QString, QPointer<ThingActionInfo>> m_pendingActions;
|
||||
};
|
||||
|
||||
#endif // INTEGRATIONPLUGINESPUINO_H
|
||||
|
|
@ -0,0 +1,261 @@
|
|||
{
|
||||
"name": "ESPuino",
|
||||
"displayName": "ESPuino",
|
||||
"id": "5f8ba72b-d3fb-4efe-952d-a927bed20cfe",
|
||||
"vendors": [
|
||||
{
|
||||
"name": "ESPuino",
|
||||
"displayName": "ESPuino",
|
||||
"id": "58c8eb30-98a4-44fd-aaac-cb2a7aae7e8a",
|
||||
"thingClasses": [
|
||||
{
|
||||
"id": "ee24ce2b-d34a-4c2c-85f3-9d895d17f414",
|
||||
"name": "espuino",
|
||||
"displayName": "ESPuino",
|
||||
"createMethods": ["discovery"],
|
||||
"interfaces": ["mediaplayer", "mediametadataprovider", "volumecontroller", "wirelessconnectable", "battery", "childlock"],
|
||||
"browsable": true,
|
||||
"paramTypes": [
|
||||
{
|
||||
"id": "2a9c9427-3e4e-4473-805e-c25242cfc621",
|
||||
"name": "hostname",
|
||||
"displayName": "Hostname",
|
||||
"type": "QString",
|
||||
"readOnly": true
|
||||
}
|
||||
],
|
||||
"stateTypes": [
|
||||
{
|
||||
"id": "edbff474-0cdc-488c-a9e9-970b25ce7548",
|
||||
"name": "connected",
|
||||
"displayName": "Connected",
|
||||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "57bd1bab-d872-4315-b53e-1157fe3889d4",
|
||||
"name": "signalStrength",
|
||||
"displayName": "Signal strength",
|
||||
"type": "uint",
|
||||
"unit": "Percentage",
|
||||
"minValue": 0,
|
||||
"maxValue": 100,
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "bee497e6-a320-458a-9006-ddfe4c7c37c2",
|
||||
"name": "batteryCritical",
|
||||
"displayName": "Battery critical",
|
||||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "9fd8f882-8240-492f-8c6b-b5477e26623e",
|
||||
"name": "batteryLevel",
|
||||
"displayName": "Battery level",
|
||||
"type": "int",
|
||||
"unit": "Percentage",
|
||||
"minValue": 0,
|
||||
"maxValue": 100,
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "dd1cfb1f-fec4-4035-9c02-562a6fba683d",
|
||||
"name": "playbackStatus",
|
||||
"displayName": "Playback status",
|
||||
"type": "QString",
|
||||
"possibleValues": ["Playing", "Paused", "Stopped"],
|
||||
"defaultValue": "Stopped",
|
||||
"writable": false,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "a274e048-9820-444a-b5de-a3a421c855a2",
|
||||
"name": "title",
|
||||
"displayName": "Title",
|
||||
"type": "QString",
|
||||
"defaultValue": "",
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "5acce950-cdac-44ea-963d-0635afcabdca",
|
||||
"name": "playmode",
|
||||
"displayName": "Playmode",
|
||||
"type": "QString",
|
||||
"possibleValues": [
|
||||
"None",
|
||||
"Single track",
|
||||
"Single track (loop)",
|
||||
"Single track of a directory (random). Followed by sleep.",
|
||||
"Audiobook",
|
||||
"Audiobook (loop)",
|
||||
"All tracks of a directory (sorted alph.)",
|
||||
"All tracks of a directory (random)",
|
||||
"All tracks of a directory (sorted alph., loop)",
|
||||
"All tracks of a directory (random, loop)",
|
||||
"Webradio",
|
||||
"List (files from SD and/or webstreams) from local .m3u-File",
|
||||
"Busy"],
|
||||
"defaultValue": "None",
|
||||
"writable": false,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "27b5ff3b-bd60-411f-b8e3-b1c8f6897bec",
|
||||
"name": "repeat",
|
||||
"displayName": "Repeat mode",
|
||||
"type": "QString",
|
||||
"possibleValues": [
|
||||
"None",
|
||||
"One",
|
||||
"All"
|
||||
],
|
||||
"displayNameAction": "Set repeat",
|
||||
"defaultValue": "None",
|
||||
"writable": true,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "93a5098a-a41a-46ee-8613-266d4f9ed69a",
|
||||
"displayName": "Volume",
|
||||
"name": "volume",
|
||||
"type": "int",
|
||||
"minValue": "0",
|
||||
"maxValue": "21",
|
||||
"displayNameAction": "Set volume",
|
||||
"defaultValue": 0,
|
||||
"writable": true,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "595908c1-57b1-4303-a0ca-4c64f3cb1907",
|
||||
"name": "brightness",
|
||||
"displayName": "LED brightness",
|
||||
"type": "int",
|
||||
"minValue": 0,
|
||||
"maxValue": 255,
|
||||
"displayNameAction": "Set LED brightness",
|
||||
"writable": true,
|
||||
"defaultValue": 0,
|
||||
"ioType": "analogOutput",
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "03e7a5e2-9434-47e8-91ad-03610601b925",
|
||||
"name": "childLock",
|
||||
"displayName": "Locl controls",
|
||||
"type": "bool",
|
||||
"displayNameAction": "Enable/disable control lock",
|
||||
"writable": true,
|
||||
"defaultValue": false,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "19bd1456-2e4f-444a-a586-75bf6cc9fb73",
|
||||
"name": "sleepmode",
|
||||
"displayName": "Sleepmode",
|
||||
"type": "QString",
|
||||
"possibleValues": ["None", "End of playlist", "End of track", "End of five tracks", "Timer"],
|
||||
"defaultValue": "None",
|
||||
"displayNameAction": "Set Sleepmode",
|
||||
"writable": true,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "4c7594e4-70e7-4f0c-aae4-02e3993ffa1d",
|
||||
"name": "sleeptimer",
|
||||
"displayName": "Sleeptimer",
|
||||
"type": "uint",
|
||||
"unit": "Minutes",
|
||||
"defaultValue": 10,
|
||||
"displayNameAction": "Set Sleeptimer",
|
||||
"writable": true,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "f84ccfc3-0698-40ff-b413-53f0064ce663",
|
||||
"name": "artwork",
|
||||
"displayName": "Artwork",
|
||||
"type": "QString",
|
||||
"defaultValue": "",
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "67a5b71e-ec88-4272-8d68-9562b7f786cf",
|
||||
"name": "artist",
|
||||
"displayName": "Artist",
|
||||
"type": "QString",
|
||||
"defaultValue": "",
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "c1af97a6-f061-4082-8bf5-595728b03ab1",
|
||||
"name": "collection",
|
||||
"displayName": "Collection",
|
||||
"type": "QString",
|
||||
"defaultValue": "",
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "c7814ee8-52b1-4cc9-b8f4-f3f91ad8f33e",
|
||||
"displayName": "Player Type",
|
||||
"name": "playerType",
|
||||
"type": "QString",
|
||||
"possibleValues": ["audio", "video"],
|
||||
"defaultValue": "audio",
|
||||
"cached": false
|
||||
}
|
||||
],
|
||||
"actionTypes": [
|
||||
{
|
||||
"id": "d045e491-c83b-4155-85ef-abc28a391402",
|
||||
"name": "increaseVolume",
|
||||
"displayName": "Increase volume"
|
||||
},
|
||||
{
|
||||
"id": "16ae2d6a-68cc-497f-9e5d-2fa1f5f7107a",
|
||||
"name": "decreaseVolume",
|
||||
"displayName": "Decrease volume"
|
||||
},
|
||||
{
|
||||
"id": "e04b74cc-cf74-482c-908d-8df294bd5ec8",
|
||||
"name": "skipBack",
|
||||
"displayName": "Prev"
|
||||
},
|
||||
{
|
||||
"id": "d46f0b61-d406-4302-adc3-6bbc00fc2a8f",
|
||||
"name": "stop",
|
||||
"displayName": "Stop"
|
||||
},
|
||||
{
|
||||
"id": "4e3b2f50-82dc-4f51-a9e5-69012985b491",
|
||||
"name": "play",
|
||||
"displayName": "Play"
|
||||
},
|
||||
{
|
||||
"id": "b7128827-b429-4583-bc34-1ef4e7987809",
|
||||
"name": "pause",
|
||||
"displayName": "Pause"
|
||||
},
|
||||
{
|
||||
"id": "25301c30-727c-43fd-bf3b-f7b3916947c7",
|
||||
"name": "skipNext",
|
||||
"displayName": "Next"
|
||||
}
|
||||
],
|
||||
"browserItemActionTypes": [
|
||||
{
|
||||
"id": "ccb210ac-5819-4614-897b-e5a0b130a38a",
|
||||
"name": "playAll",
|
||||
"displayName": "Play All"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"title": "ESPunio",
|
||||
"tagline": "Remote control ESPunio (Rfid-controller musicplayer) via MQTT.",
|
||||
"stability": "community",
|
||||
"offline": true,
|
||||
"technologies": [
|
||||
"mqtt",
|
||||
"network"
|
||||
],
|
||||
"categories": [
|
||||
"dyi",
|
||||
"multimedia",
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,244 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1">
|
||||
<context>
|
||||
<name>ESPuino</name>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/espuino/plugininfo.h" line="66"/>
|
||||
<source>Artist</source>
|
||||
<extracomment>The name of the StateType ({67a5b71e-ec88-4272-8d68-9562b7f786cf}) of ThingClass espuino</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/espuino/plugininfo.h" line="69"/>
|
||||
<source>Artwork</source>
|
||||
<extracomment>The name of the StateType ({f84ccfc3-0698-40ff-b413-53f0064ce663}) of ThingClass espuino</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/espuino/plugininfo.h" line="72"/>
|
||||
<source>Battery critical</source>
|
||||
<extracomment>The name of the StateType ({bee497e6-a320-458a-9006-ddfe4c7c37c2}) of ThingClass espuino</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/espuino/plugininfo.h" line="75"/>
|
||||
<source>Battery level</source>
|
||||
<extracomment>The name of the StateType ({9fd8f882-8240-492f-8c6b-b5477e26623e}) of ThingClass espuino</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/espuino/plugininfo.h" line="78"/>
|
||||
<source>Collection</source>
|
||||
<extracomment>The name of the StateType ({c1af97a6-f061-4082-8bf5-595728b03ab1}) of ThingClass espuino</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/espuino/plugininfo.h" line="81"/>
|
||||
<source>Connected</source>
|
||||
<extracomment>The name of the StateType ({edbff474-0cdc-488c-a9e9-970b25ce7548}) of ThingClass espuino</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/espuino/plugininfo.h" line="84"/>
|
||||
<source>Decrease volume</source>
|
||||
<extracomment>The name of the ActionType ({16ae2d6a-68cc-497f-9e5d-2fa1f5f7107a}) of ThingClass espuino</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/espuino/plugininfo.h" line="87"/>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/espuino/plugininfo.h" line="90"/>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/espuino/plugininfo.h" line="93"/>
|
||||
<source>ESPuino</source>
|
||||
<extracomment>The name of the ThingClass ({ee24ce2b-d34a-4c2c-85f3-9d895d17f414})
|
||||
----------
|
||||
The name of the vendor ({58c8eb30-98a4-44fd-aaac-cb2a7aae7e8a})
|
||||
----------
|
||||
The name of the plugin ESPuino ({5f8ba72b-d3fb-4efe-952d-a927bed20cfe})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/espuino/plugininfo.h" line="96"/>
|
||||
<source>Enable/disable control lock</source>
|
||||
<extracomment>The name of the ActionType ({03e7a5e2-9434-47e8-91ad-03610601b925}) of ThingClass espuino</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/espuino/plugininfo.h" line="99"/>
|
||||
<source>Hostname</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: espuino, Type: thing, ID: {2a9c9427-3e4e-4473-805e-c25242cfc621})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/espuino/plugininfo.h" line="102"/>
|
||||
<source>Increase volume</source>
|
||||
<extracomment>The name of the ActionType ({d045e491-c83b-4155-85ef-abc28a391402}) of ThingClass espuino</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/espuino/plugininfo.h" line="105"/>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/espuino/plugininfo.h" line="108"/>
|
||||
<source>LED brightness</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: espuino, ActionType: brightness, ID: {595908c1-57b1-4303-a0ca-4c64f3cb1907})
|
||||
----------
|
||||
The name of the StateType ({595908c1-57b1-4303-a0ca-4c64f3cb1907}) of ThingClass espuino</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/espuino/plugininfo.h" line="111"/>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/espuino/plugininfo.h" line="114"/>
|
||||
<source>Locl controls</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: espuino, ActionType: childLock, ID: {03e7a5e2-9434-47e8-91ad-03610601b925})
|
||||
----------
|
||||
The name of the StateType ({03e7a5e2-9434-47e8-91ad-03610601b925}) of ThingClass espuino</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/espuino/plugininfo.h" line="117"/>
|
||||
<source>Next</source>
|
||||
<extracomment>The name of the ActionType ({25301c30-727c-43fd-bf3b-f7b3916947c7}) of ThingClass espuino</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/espuino/plugininfo.h" line="120"/>
|
||||
<source>Pause</source>
|
||||
<extracomment>The name of the ActionType ({b7128827-b429-4583-bc34-1ef4e7987809}) of ThingClass espuino</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/espuino/plugininfo.h" line="123"/>
|
||||
<source>Play</source>
|
||||
<extracomment>The name of the ActionType ({4e3b2f50-82dc-4f51-a9e5-69012985b491}) of ThingClass espuino</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/espuino/plugininfo.h" line="126"/>
|
||||
<source>Play All</source>
|
||||
<extracomment>The name of the Browser Item ActionType ({ccb210ac-5819-4614-897b-e5a0b130a38a}) of ThingClass espuino</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/espuino/plugininfo.h" line="129"/>
|
||||
<source>Playback status</source>
|
||||
<extracomment>The name of the StateType ({dd1cfb1f-fec4-4035-9c02-562a6fba683d}) of ThingClass espuino</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/espuino/plugininfo.h" line="132"/>
|
||||
<source>Player Type</source>
|
||||
<extracomment>The name of the StateType ({c7814ee8-52b1-4cc9-b8f4-f3f91ad8f33e}) of ThingClass espuino</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/espuino/plugininfo.h" line="135"/>
|
||||
<source>Playmode</source>
|
||||
<extracomment>The name of the StateType ({5acce950-cdac-44ea-963d-0635afcabdca}) of ThingClass espuino</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/espuino/plugininfo.h" line="138"/>
|
||||
<source>Prev</source>
|
||||
<extracomment>The name of the ActionType ({e04b74cc-cf74-482c-908d-8df294bd5ec8}) of ThingClass espuino</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/espuino/plugininfo.h" line="141"/>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/espuino/plugininfo.h" line="144"/>
|
||||
<source>Repeat mode</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: espuino, ActionType: repeat, ID: {27b5ff3b-bd60-411f-b8e3-b1c8f6897bec})
|
||||
----------
|
||||
The name of the StateType ({27b5ff3b-bd60-411f-b8e3-b1c8f6897bec}) of ThingClass espuino</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/espuino/plugininfo.h" line="147"/>
|
||||
<source>Set LED brightness</source>
|
||||
<extracomment>The name of the ActionType ({595908c1-57b1-4303-a0ca-4c64f3cb1907}) of ThingClass espuino</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/espuino/plugininfo.h" line="150"/>
|
||||
<source>Set Sleepmode</source>
|
||||
<extracomment>The name of the ActionType ({19bd1456-2e4f-444a-a586-75bf6cc9fb73}) of ThingClass espuino</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/espuino/plugininfo.h" line="153"/>
|
||||
<source>Set Sleeptimer</source>
|
||||
<extracomment>The name of the ActionType ({4c7594e4-70e7-4f0c-aae4-02e3993ffa1d}) of ThingClass espuino</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/espuino/plugininfo.h" line="156"/>
|
||||
<source>Set repeat</source>
|
||||
<extracomment>The name of the ActionType ({27b5ff3b-bd60-411f-b8e3-b1c8f6897bec}) of ThingClass espuino</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/espuino/plugininfo.h" line="159"/>
|
||||
<source>Set volume</source>
|
||||
<extracomment>The name of the ActionType ({93a5098a-a41a-46ee-8613-266d4f9ed69a}) of ThingClass espuino</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/espuino/plugininfo.h" line="162"/>
|
||||
<source>Signal strength</source>
|
||||
<extracomment>The name of the StateType ({57bd1bab-d872-4315-b53e-1157fe3889d4}) of ThingClass espuino</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/espuino/plugininfo.h" line="165"/>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/espuino/plugininfo.h" line="168"/>
|
||||
<source>Sleepmode</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: espuino, ActionType: sleepmode, ID: {19bd1456-2e4f-444a-a586-75bf6cc9fb73})
|
||||
----------
|
||||
The name of the StateType ({19bd1456-2e4f-444a-a586-75bf6cc9fb73}) of ThingClass espuino</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/espuino/plugininfo.h" line="171"/>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/espuino/plugininfo.h" line="174"/>
|
||||
<source>Sleeptimer</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: espuino, ActionType: sleeptimer, ID: {4c7594e4-70e7-4f0c-aae4-02e3993ffa1d})
|
||||
----------
|
||||
The name of the StateType ({4c7594e4-70e7-4f0c-aae4-02e3993ffa1d}) of ThingClass espuino</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/espuino/plugininfo.h" line="177"/>
|
||||
<source>Stop</source>
|
||||
<extracomment>The name of the ActionType ({d46f0b61-d406-4302-adc3-6bbc00fc2a8f}) of ThingClass espuino</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/espuino/plugininfo.h" line="180"/>
|
||||
<source>Title</source>
|
||||
<extracomment>The name of the StateType ({a274e048-9820-444a-b5de-a3a421c855a2}) of ThingClass espuino</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/espuino/plugininfo.h" line="183"/>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/espuino/plugininfo.h" line="186"/>
|
||||
<source>Volume</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: espuino, ActionType: volume, ID: {93a5098a-a41a-46ee-8613-266d4f9ed69a})
|
||||
----------
|
||||
The name of the StateType ({93a5098a-a41a-46ee-8613-266d4f9ed69a}) of ThingClass espuino</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>IntegrationPluginEspuino</name>
|
||||
<message>
|
||||
<location filename="../integrationpluginespuino.cpp" line="92"/>
|
||||
<location filename="../integrationpluginespuino.cpp" line="154"/>
|
||||
<source>Error creating MQTT channel. Please check MQTT server settings.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../integrationpluginespuino.cpp" line="117"/>
|
||||
<location filename="../integrationpluginespuino.cpp" line="123"/>
|
||||
<source>Failed to configure MQTT via Websocket.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
|
|
@ -21,6 +21,7 @@ PLUGIN_DIRS = \
|
|||
dynatrace \
|
||||
elgato \
|
||||
eq-3 \
|
||||
espuino \
|
||||
fastcom \
|
||||
flowercare \
|
||||
fronius \
|
||||
|
|
|
|||
Loading…
Reference in New Issue