More work on Shelly. Pretty complete already for the Shelly1
This commit is contained in:
parent
0d4d6cd4f6
commit
b744b3b245
@ -1,20 +1,29 @@
|
|||||||
# Tasmota
|
# Shelly
|
||||||
|
|
||||||
This plugin allows to make use of Sonoff-Tasmota devices via the nymea internal MQTT broker. There is no external MQTT broker needed.
|
The Shelly plugin adds support for Shelly devices (https://shelly.cloud).
|
||||||
|
|
||||||
Note that Sonoff devices must be flashed with the Tasmota sofware and connected to the WiFi network in order to work with this plugin.
|
Currently the Shelly1 and Shelly1PM are supported.
|
||||||
|
|
||||||
See the [Sonoff-Tasmota wiki](https://github.com/arendst/Sonoff-Tasmota/wiki) for a list of all supported devices and instructions on how to
|
## Requirements
|
||||||
install Tasmota on those.
|
Shelly devices communicate with via MQTT. This means, in order to add Shelly devices to nymea, the nymea instance is required
|
||||||
|
to have the MQTT broker enabled in the nymea settings and the Shelly device needs to be connected to the same WiFi as nymea is
|
||||||
|
in. New Shelly devices will open a WiFi named with their name as SSID. For instance, a Shelly 1 would appear as "shelly1-XXXXXX".
|
||||||
|
Connect to this WiFi and open the webpage that will pop up. From there, it can be configured it to connect to the same
|
||||||
|
network where the nymea system is located. No other options need to be set as they can be configured using nymea later on.
|
||||||
|
|
||||||
After flashing Tasmota to a Sonoff device and connecting it to WiFi, it can be added to nymea. The only required
|
|
||||||
thing is the IP address to the device. This plugin will create a new isoloated MQTT channel on the nymea internal
|
## Setting up devices
|
||||||
MQTT broker and provision login details to the Tasmota device via HTTP. Once that is successful, the Tasmota device
|
Once the Shelly is connected to the WiFi, a device discovery in nymea can be performed and will list the Shelly device.
|
||||||
will connect to the MQTT broker and appear as connected in nymea.
|
During setup, the connected device can be configured. If the Shelly is connected to e.g. a light bulb, choose "Light" here.
|
||||||
|
Optionally, a username and password can be set. If the Shelly device is already configured to require authentication,
|
||||||
|
the username and password here must match the ones set on the Shelly. NOTE: If the Shelly is not configured to require a
|
||||||
|
login yet, but credentials are entered during setup, the Shelly device will be configured to require authentication from
|
||||||
|
now on.
|
||||||
|
|
||||||
## Plugin properties
|
## Plugin properties
|
||||||
When adding a Tasmota device it will add a new Gateway type device representing the Tasmota device itself. In addition
|
When adding a Shelly device it will add a new Gateway type device representing the Shelly device itself. It will allow
|
||||||
to that a power switch device will appear which can be used to control the switches in the Tasmota device. Upon
|
basic monitoring (such as the connected state) and interaction (e.g. reboot the Shelly device). In addition to that, a
|
||||||
device setup, the user can optionally select the type of the connected hardware, (e.g. a light) which causes this
|
power switch device will appear which will reflect presses on the Shelly's SW input. This power switch device also
|
||||||
plugin to create a light device in the system which also controls the switches inside the Tasmota device and nicely
|
offers the possiblity to configure the used switch (e.g. toggle, momentary, edge or detached from the Shelly's output).
|
||||||
integrates with the nymea:ux for the given device type.
|
If a connected device has been selected during setup, an additional device, e.g. the light will appear in the system and
|
||||||
|
can be used to control the power output of the Shelly, e.g. turning on or off the connected light.
|
||||||
|
|||||||
@ -38,6 +38,18 @@
|
|||||||
|
|
||||||
DevicePluginShelly::DevicePluginShelly()
|
DevicePluginShelly::DevicePluginShelly()
|
||||||
{
|
{
|
||||||
|
m_connectedStateTypesMap[shellySwitchDeviceClassId] = shellySwitchConnectedStateTypeId;
|
||||||
|
m_connectedStateTypesMap[shellyGenericDeviceClassId] = shellyGenericConnectedStateTypeId;
|
||||||
|
m_connectedStateTypesMap[shellyLightDeviceClassId] = shellyLightConnectedStateTypeId;
|
||||||
|
|
||||||
|
m_powerActionTypesMap[shellyGenericPowerActionTypeId] = shellyGenericDeviceClassId;
|
||||||
|
m_powerActionTypesMap[shellyLightPowerActionTypeId] = shellyLightDeviceClassId;
|
||||||
|
|
||||||
|
m_powerActionParamTypesMap[shellyGenericPowerActionTypeId] = shellyGenericPowerActionPowerParamTypeId;
|
||||||
|
m_powerActionParamTypesMap[shellyLightPowerActionTypeId] = shellyLightPowerActionPowerParamTypeId;
|
||||||
|
|
||||||
|
m_powerStateTypeMap[shellyGenericDeviceClassId] = shellyGenericPowerStateTypeId;
|
||||||
|
m_powerStateTypeMap[shellyLightDeviceClassId] = shellyLightPowerStateTypeId;
|
||||||
}
|
}
|
||||||
|
|
||||||
DevicePluginShelly::~DevicePluginShelly()
|
DevicePluginShelly::~DevicePluginShelly()
|
||||||
@ -53,14 +65,17 @@ void DevicePluginShelly::discoverDevices(DeviceDiscoveryInfo *info)
|
|||||||
{
|
{
|
||||||
foreach (const ZeroConfServiceEntry &entry, m_zeroconfBrowser->serviceEntries()) {
|
foreach (const ZeroConfServiceEntry &entry, m_zeroconfBrowser->serviceEntries()) {
|
||||||
// qCDebug(dcShelly()) << "Have entry" << entry;
|
// qCDebug(dcShelly()) << "Have entry" << entry;
|
||||||
QRegExp namePattern("^shelly[1-2]-[0-9A-Z]+$");
|
QRegExp namePattern;
|
||||||
|
if (info->deviceClassId() == shelly1DeviceClassId) {
|
||||||
|
namePattern = QRegExp("^shelly(1|1pm|plug|plug-s)-[0-9A-Z]+$");
|
||||||
|
}
|
||||||
if (!entry.name().contains(namePattern)) {
|
if (!entry.name().contains(namePattern)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
DeviceDescriptor descriptor(shellyOneDeviceClassId, entry.name(), entry.hostAddress().toString());
|
DeviceDescriptor descriptor(shelly1DeviceClassId, entry.name(), entry.hostAddress().toString());
|
||||||
ParamList params;
|
ParamList params;
|
||||||
params << Param(shellyOneDeviceIdParamTypeId, entry.name());
|
params << Param(shelly1DeviceIdParamTypeId, entry.name());
|
||||||
descriptor.setParams(params);
|
descriptor.setParams(params);
|
||||||
|
|
||||||
Device *existingDevice = myDevices().findByParams(params);
|
Device *existingDevice = myDevices().findByParams(params);
|
||||||
@ -80,87 +95,12 @@ void DevicePluginShelly::setupDevice(DeviceSetupInfo *info)
|
|||||||
{
|
{
|
||||||
Device *device = info->device();
|
Device *device = info->device();
|
||||||
|
|
||||||
if (device->deviceClassId() == shellyOneDeviceClassId) {
|
if (device->deviceClassId() == shelly1DeviceClassId) {
|
||||||
QString shellyId = device->paramValue(shellyOneDeviceIdParamTypeId).toString();
|
setupShellyGateway(info);
|
||||||
ZeroConfServiceEntry zeroConfEntry;
|
|
||||||
foreach (const ZeroConfServiceEntry &entry, m_zeroconfBrowser->serviceEntries()) {
|
|
||||||
if (entry.name() == shellyId) {
|
|
||||||
zeroConfEntry = entry;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
QHostAddress address;
|
|
||||||
pluginStorage()->beginGroup(device->id().toString());
|
|
||||||
if (zeroConfEntry.isValid()) {
|
|
||||||
address = zeroConfEntry.hostAddress().toString();
|
|
||||||
pluginStorage()->setValue("cachedAddress", address.toString());
|
|
||||||
} else {
|
|
||||||
qCWarning(dcShelly()) << "Could not find Shelly device on zeroconf. Trying cached address.";
|
|
||||||
address = pluginStorage()->value("cachedAddress").toString();
|
|
||||||
}
|
|
||||||
pluginStorage()->endGroup();
|
|
||||||
|
|
||||||
if (address.isNull()) {
|
|
||||||
qCWarning(dcShelly()) << "Unable to determine Shelly's network address. Failed to set up device.";
|
|
||||||
info->finish(Device::DeviceErrorHardwareNotAvailable, QT_TR_NOOP("Unable to find the device in the network."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
MqttChannel *channel = hardwareManager()->mqttProvider()->createChannel(shellyId, QHostAddress(address), {"shellies"});
|
|
||||||
if (!channel) {
|
|
||||||
qCWarning(dcShelly()) << "Failed to create MQTT channel.";
|
|
||||||
return info->finish(Device::DeviceErrorHardwareFailure, QT_TR_NOOP("Error creating MQTT channel. Please check MQTT server settings."));
|
|
||||||
}
|
|
||||||
|
|
||||||
QUrl url;
|
|
||||||
url.setScheme("http");
|
|
||||||
url.setHost(address.toString());
|
|
||||||
url.setPort(80);
|
|
||||||
url.setPath("/settings");
|
|
||||||
|
|
||||||
QUrlQuery query;
|
|
||||||
query.addQueryItem("mqtt_server", channel->serverAddress().toString() + ":" + QString::number(channel->serverPort()));
|
|
||||||
query.addQueryItem("mqtt_user", channel->username());
|
|
||||||
query.addQueryItem("mqtt_pass", channel->password());
|
|
||||||
query.addQueryItem("mqtt_enable", "true");
|
|
||||||
|
|
||||||
url.setQuery(query);
|
|
||||||
|
|
||||||
QNetworkRequest request(url);
|
|
||||||
qCDebug(dcShelly()) << "Connecting to" << url.toString();
|
|
||||||
QNetworkReply *reply = hardwareManager()->networkManager()->get(request);
|
|
||||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
|
||||||
connect(info, &DeviceSetupInfo::aborted, channel, [this, channel](){
|
|
||||||
hardwareManager()->mqttProvider()->releaseChannel(channel);
|
|
||||||
});
|
|
||||||
connect(reply, &QNetworkReply::finished, info, [this, info, reply, channel](){
|
|
||||||
if (reply->error() != QNetworkReply::NoError) {
|
|
||||||
qCWarning(dcShelly()) << "Error fetching device settings" << reply->error() << reply->errorString();
|
|
||||||
info->finish(Device::DeviceErrorHardwareFailure, QT_TR_NOOP("Error connecting to Shelly device."));
|
|
||||||
hardwareManager()->mqttProvider()->releaseChannel(channel);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
QByteArray data = reply->readAll();
|
|
||||||
QJsonParseError error;
|
|
||||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
|
|
||||||
if (error.error != QJsonParseError::NoError) {
|
|
||||||
qCWarning(dcShelly()) << "Error parsing settings reply" << error.errorString() << "\n" << data;
|
|
||||||
info->finish(Device::DeviceErrorHardwareFailure, QT_TR_NOOP("Unexpected data received from Shelly device."));
|
|
||||||
hardwareManager()->mqttProvider()->releaseChannel(channel);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
qCDebug(dcShelly()) << "Settings data" << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Indented));
|
|
||||||
|
|
||||||
m_mqttChannels.insert(info->device(), channel);
|
|
||||||
connect(channel, &MqttChannel::clientConnected, this, &DevicePluginShelly::onClientConnected);
|
|
||||||
connect(channel, &MqttChannel::clientDisconnected, this, &DevicePluginShelly::onClientDisconnected);
|
|
||||||
connect(channel, &MqttChannel::publishReceived, this, &DevicePluginShelly::onPublishReceived);
|
|
||||||
|
|
||||||
info->finish(Device::DeviceErrorNoError);
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
qCWarning(dcShelly) << "Unhandled DeviceClass in setupDevice" << device->deviceClassId();
|
setupShellyChild(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DevicePluginShelly::deviceRemoved(Device *device)
|
void DevicePluginShelly::deviceRemoved(Device *device)
|
||||||
@ -176,10 +116,27 @@ void DevicePluginShelly::executeAction(DeviceActionInfo *info)
|
|||||||
Device *device = info->device();
|
Device *device = info->device();
|
||||||
Action action = info->action();
|
Action action = info->action();
|
||||||
|
|
||||||
if (action.actionTypeId() == shellyOnePowerActionTypeId) {
|
if (action.actionTypeId() == shelly1RebootActionTypeId) {
|
||||||
MqttChannel *channel = m_mqttChannels.value(device);
|
QUrl url;
|
||||||
QString shellyId = device->paramValue(shellyOneDeviceIdParamTypeId).toString();
|
url.setScheme("http");
|
||||||
bool on = action.param(shellyOnePowerActionPowerParamTypeId).value().toBool();
|
url.setHost(getIP(info->device()));
|
||||||
|
url.setPath("/reboot");
|
||||||
|
url.setUserName(device->paramValue(shelly1DeviceUsernameParamTypeId).toString());
|
||||||
|
url.setPassword(device->paramValue(shelly1DevicePasswordParamTypeId).toString());
|
||||||
|
QNetworkReply *reply = hardwareManager()->networkManager()->get(QNetworkRequest(url));
|
||||||
|
connect(reply, &QNetworkReply::finished, &QNetworkReply::deleteLater);
|
||||||
|
connect(reply, &QNetworkReply::finished, info, [info, reply](){
|
||||||
|
info->finish(reply->error() == QNetworkReply::NoError ? Device::DeviceErrorNoError : Device::DeviceErrorHardwareFailure);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_powerActionTypesMap.contains(action.actionTypeId())) {
|
||||||
|
Device *parentDevice = myDevices().findById(device->parentId());
|
||||||
|
MqttChannel *channel = m_mqttChannels.value(parentDevice);
|
||||||
|
QString shellyId = parentDevice->paramValue(shelly1DeviceIdParamTypeId).toString();
|
||||||
|
ParamTypeId powerParamTypeId = m_powerActionParamTypesMap.value(action.actionTypeId());
|
||||||
|
bool on = action.param(powerParamTypeId).value().toBool();
|
||||||
channel->publish("shellies/" + shellyId + "/relay/0/command", on ? "on" : "off");
|
channel->publish("shellies/" + shellyId + "/relay/0/command", on ? "on" : "off");
|
||||||
info->finish(Device::DeviceErrorNoError);
|
info->finish(Device::DeviceErrorNoError);
|
||||||
return;
|
return;
|
||||||
@ -195,7 +152,11 @@ void DevicePluginShelly::onClientConnected(MqttChannel *channel)
|
|||||||
qCWarning(dcShelly()) << "Received a client connect for a device we don't know!";
|
qCWarning(dcShelly()) << "Received a client connect for a device we don't know!";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
device->setStateValue(shellyOneConnectedStateTypeId, true);
|
device->setStateValue(shelly1ConnectedStateTypeId, true);
|
||||||
|
|
||||||
|
foreach (Device *child, myDevices().filterByParentDeviceId(device->id())) {
|
||||||
|
child->setStateValue(m_connectedStateTypesMap[child->deviceClassId()], true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DevicePluginShelly::onClientDisconnected(MqttChannel *channel)
|
void DevicePluginShelly::onClientDisconnected(MqttChannel *channel)
|
||||||
@ -205,7 +166,11 @@ void DevicePluginShelly::onClientDisconnected(MqttChannel *channel)
|
|||||||
qCWarning(dcShelly()) << "Received a client disconnect for a device we don't know!";
|
qCWarning(dcShelly()) << "Received a client disconnect for a device we don't know!";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
device->setStateValue(shellyOneConnectedStateTypeId, false);
|
device->setStateValue(shelly1ConnectedStateTypeId, false);
|
||||||
|
|
||||||
|
foreach (Device *child, myDevices().filterByParentDeviceId(device->id())) {
|
||||||
|
child->setStateValue(m_connectedStateTypesMap[child->deviceClassId()], false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DevicePluginShelly::onPublishReceived(MqttChannel *channel, const QString &topic, const QByteArray &payload)
|
void DevicePluginShelly::onPublishReceived(MqttChannel *channel, const QString &topic, const QByteArray &payload)
|
||||||
@ -216,16 +181,215 @@ void DevicePluginShelly::onPublishReceived(MqttChannel *channel, const QString &
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString shellyId = device->paramValue(shellyOneDeviceIdParamTypeId).toString();
|
QString shellyId = device->paramValue(shelly1DeviceIdParamTypeId).toString();
|
||||||
if (topic == "shellies/" + shellyId + "/input/0") {
|
if (topic == "shellies/" + shellyId + "/input/0") {
|
||||||
// "1" or "0"
|
// "1" or "0"
|
||||||
// Emit event button pressed
|
// Emit event button pressed
|
||||||
|
bool on = payload == "1";
|
||||||
|
foreach (Device *child, myDevices().filterByParentDeviceId(device->id())) {
|
||||||
|
if (child->deviceClassId() == shellySwitchDeviceClassId) {
|
||||||
|
if (child->stateValue(shellySwitchPowerStateTypeId).toBool() != on) {
|
||||||
|
child->setStateValue(shellySwitchPowerStateTypeId, on);
|
||||||
|
emit emitEvent(Event(shellySwitchPressedEventTypeId, child->id()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (topic == "shellies/" + shellyId + "/relay/0") {
|
if (topic == "shellies/" + shellyId + "/relay/0") {
|
||||||
bool on = payload == "on";
|
bool on = payload == "on";
|
||||||
device->setStateValue(shellyOnePowerStateTypeId, on);
|
|
||||||
|
foreach (Device *child, myDevices().filterByParentDeviceId(device->id())) {
|
||||||
|
if (m_powerStateTypeMap.contains(child->deviceClassId())) {
|
||||||
|
child->setStateValue(m_powerStateTypeMap.value(child->deviceClassId()), on);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
qCDebug(dcShelly()) << "Publish received from" << device->name() << topic << payload;
|
qCDebug(dcShelly()) << "Publish received from" << device->name() << topic << payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DevicePluginShelly::setupShellyGateway(DeviceSetupInfo *info)
|
||||||
|
{
|
||||||
|
QString shellyId = info->device()->paramValue(shelly1DeviceIdParamTypeId).toString();
|
||||||
|
ZeroConfServiceEntry zeroConfEntry;
|
||||||
|
foreach (const ZeroConfServiceEntry &entry, m_zeroconfBrowser->serviceEntries()) {
|
||||||
|
if (entry.name() == shellyId) {
|
||||||
|
zeroConfEntry = entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QHostAddress address;
|
||||||
|
pluginStorage()->beginGroup(info->device()->id().toString());
|
||||||
|
if (zeroConfEntry.isValid()) {
|
||||||
|
address = zeroConfEntry.hostAddress().toString();
|
||||||
|
pluginStorage()->setValue("cachedAddress", address.toString());
|
||||||
|
} else {
|
||||||
|
qCWarning(dcShelly()) << "Could not find Shelly device on zeroconf. Trying cached address.";
|
||||||
|
address = pluginStorage()->value("cachedAddress").toString();
|
||||||
|
}
|
||||||
|
pluginStorage()->endGroup();
|
||||||
|
|
||||||
|
if (address.isNull()) {
|
||||||
|
qCWarning(dcShelly()) << "Unable to determine Shelly's network address. Failed to set up device.";
|
||||||
|
info->finish(Device::DeviceErrorHardwareNotAvailable, QT_TR_NOOP("Unable to find the device in the network."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MqttChannel *channel = hardwareManager()->mqttProvider()->createChannel(shellyId, QHostAddress(address), {"shellies"});
|
||||||
|
if (!channel) {
|
||||||
|
qCWarning(dcShelly()) << "Failed to create MQTT channel.";
|
||||||
|
return info->finish(Device::DeviceErrorHardwareFailure, QT_TR_NOOP("Error creating MQTT channel. Please check MQTT server settings."));
|
||||||
|
}
|
||||||
|
|
||||||
|
QUrl url;
|
||||||
|
url.setScheme("http");
|
||||||
|
url.setHost(address.toString());
|
||||||
|
url.setPort(80);
|
||||||
|
url.setPath("/settings");
|
||||||
|
url.setUserName(info->device()->paramValue(shelly1DeviceUsernameParamTypeId).toString());
|
||||||
|
url.setPassword(info->device()->paramValue(shelly1DevicePasswordParamTypeId).toString());
|
||||||
|
|
||||||
|
QUrlQuery query;
|
||||||
|
query.addQueryItem("mqtt_server", channel->serverAddress().toString() + ":" + QString::number(channel->serverPort()));
|
||||||
|
query.addQueryItem("mqtt_user", channel->username());
|
||||||
|
query.addQueryItem("mqtt_pass", channel->password());
|
||||||
|
query.addQueryItem("mqtt_enable", "true");
|
||||||
|
|
||||||
|
url.setQuery(query);
|
||||||
|
QNetworkRequest request(url);
|
||||||
|
|
||||||
|
qCDebug(dcShelly()) << "Connecting to" << url.toString();
|
||||||
|
QNetworkReply *reply = hardwareManager()->networkManager()->get(request);
|
||||||
|
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||||
|
connect(info, &DeviceSetupInfo::aborted, channel, [this, channel](){
|
||||||
|
hardwareManager()->mqttProvider()->releaseChannel(channel);
|
||||||
|
});
|
||||||
|
connect(reply, &QNetworkReply::finished, info, [this, info, reply, channel, address](){
|
||||||
|
if (reply->error() != QNetworkReply::NoError) {
|
||||||
|
hardwareManager()->mqttProvider()->releaseChannel(channel);
|
||||||
|
qCWarning(dcShelly()) << "Error fetching device settings" << reply->error() << reply->errorString();
|
||||||
|
if (reply->error() == QNetworkReply::AuthenticationRequiredError) {
|
||||||
|
info->finish(Device::DeviceErrorAuthenticationFailure, QT_TR_NOOP("Username and password not set correctly."));
|
||||||
|
} else {
|
||||||
|
info->finish(Device::DeviceErrorHardwareFailure, QT_TR_NOOP("Error connecting to Shelly device."));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QByteArray data = reply->readAll();
|
||||||
|
QJsonParseError error;
|
||||||
|
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
|
||||||
|
if (error.error != QJsonParseError::NoError) {
|
||||||
|
qCWarning(dcShelly()) << "Error parsing settings reply" << error.errorString() << "\n" << data;
|
||||||
|
info->finish(Device::DeviceErrorHardwareFailure, QT_TR_NOOP("Unexpected data received from Shelly device."));
|
||||||
|
hardwareManager()->mqttProvider()->releaseChannel(channel);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
qCDebug(dcShelly()) << "Settings data" << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Indented));
|
||||||
|
|
||||||
|
m_mqttChannels.insert(info->device(), channel);
|
||||||
|
connect(channel, &MqttChannel::clientConnected, this, &DevicePluginShelly::onClientConnected);
|
||||||
|
connect(channel, &MqttChannel::clientDisconnected, this, &DevicePluginShelly::onClientDisconnected);
|
||||||
|
connect(channel, &MqttChannel::publishReceived, this, &DevicePluginShelly::onPublishReceived);
|
||||||
|
|
||||||
|
DeviceDescriptors autoChilds;
|
||||||
|
|
||||||
|
// Always create the switch device if we don't have one yet
|
||||||
|
if (myDevices().filterByParentDeviceId(info->device()->id()).filterByDeviceClassId(shellySwitchDeviceClassId).isEmpty()) {
|
||||||
|
DeviceDescriptor switchChild(shellySwitchDeviceClassId, "Shelly switch", QString(), info->device()->id());
|
||||||
|
autoChilds.append(switchChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add connected devices as configured in params
|
||||||
|
if (info->device()->paramValue(shelly1DeviceConnectedDeviceParamTypeId).toString() == "Generic") {
|
||||||
|
if (myDevices().filterByParentDeviceId(info->device()->id()).filterByDeviceClassId(shellyGenericDeviceClassId).isEmpty()) {
|
||||||
|
DeviceDescriptor genericChild(shellyGenericDeviceClassId, "Shelly connected device", QString(), info->device()->id());
|
||||||
|
autoChilds.append(genericChild);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (info->device()->paramValue(shelly1DeviceConnectedDeviceParamTypeId).toString() == "Light") {
|
||||||
|
if (myDevices().filterByParentDeviceId(info->device()->id()).filterByDeviceClassId(shellyLightDeviceClassId).isEmpty()) {
|
||||||
|
DeviceDescriptor genericChild(shellyLightDeviceClassId, "Shelly connected light", QString(), info->device()->id());
|
||||||
|
autoChilds.append(genericChild);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info->finish(Device::DeviceErrorNoError);
|
||||||
|
|
||||||
|
emit autoDevicesAppeared(autoChilds);
|
||||||
|
|
||||||
|
// Make sure authentication is enalbed if the user wants it
|
||||||
|
QString username = info->device()->paramValue(shelly1DeviceUsernameParamTypeId).toString();
|
||||||
|
QString password = info->device()->paramValue(shelly1DevicePasswordParamTypeId).toString();
|
||||||
|
if (!username.isEmpty()) {
|
||||||
|
QUrl url;
|
||||||
|
url.setScheme("http");
|
||||||
|
url.setHost(address.toString());
|
||||||
|
url.setPort(80);
|
||||||
|
url.setPath("/settings/login");
|
||||||
|
url.setUserName(username);
|
||||||
|
url.setPassword(password);
|
||||||
|
|
||||||
|
QUrlQuery query;
|
||||||
|
query.addQueryItem("username", username);
|
||||||
|
query.addQueryItem("password", password);
|
||||||
|
query.addQueryItem("enabled", "true");
|
||||||
|
|
||||||
|
url.setQuery(query);
|
||||||
|
|
||||||
|
QNetworkRequest request(url);
|
||||||
|
qCDebug(dcShelly()) << "Enabling auth" << username << password;
|
||||||
|
QNetworkReply *reply = hardwareManager()->networkManager()->get(request);
|
||||||
|
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void DevicePluginShelly::setupShellyChild(DeviceSetupInfo *info)
|
||||||
|
{
|
||||||
|
Device *device = info->device();
|
||||||
|
|
||||||
|
// Connect to settings changes to store them to the device
|
||||||
|
connect(info->device(), &Device::settingChanged, this, [this, device](const ParamTypeId ¶mTypeId, const QVariant &value){
|
||||||
|
Device *parentDevice = myDevices().findById(device->parentId());
|
||||||
|
pluginStorage()->beginGroup(parentDevice->id().toString());
|
||||||
|
QString address = pluginStorage()->value("cachedAddress").toString();
|
||||||
|
pluginStorage()->endGroup();
|
||||||
|
|
||||||
|
QUrl url;
|
||||||
|
url.setScheme("http");
|
||||||
|
url.setHost(address);
|
||||||
|
url.setPort(80);
|
||||||
|
url.setPath("/settings/relay/0");
|
||||||
|
url.setUserName(parentDevice->paramValue(shelly1DeviceUsernameParamTypeId).toString());
|
||||||
|
url.setPassword(parentDevice->paramValue(shelly1DevicePasswordParamTypeId).toString());
|
||||||
|
|
||||||
|
QUrlQuery query;
|
||||||
|
if (paramTypeId == shellySwitchSettingsButtonTypeParamTypeId) {
|
||||||
|
query.addQueryItem("btn_type", value.toString());
|
||||||
|
}
|
||||||
|
if (paramTypeId == shellySwitchSettingsInvertButtonParamTypeId) {
|
||||||
|
query.addQueryItem("btn_reverse", value.toBool() ? "1" : "0");
|
||||||
|
}
|
||||||
|
if (paramTypeId == shellyGenericSettingsDefaultStateParamTypeId || paramTypeId == shellyLightSettingsDefaultStateParamTypeId) {
|
||||||
|
query.addQueryItem("default_state", value.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
url.setQuery(query);
|
||||||
|
|
||||||
|
QNetworkReply *reply = hardwareManager()->networkManager()->get(QNetworkRequest(url));
|
||||||
|
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||||
|
});
|
||||||
|
|
||||||
|
info->finish(Device::DeviceErrorNoError);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString DevicePluginShelly::getIP(Device *device) const
|
||||||
|
{
|
||||||
|
Device *d = device;
|
||||||
|
if (!device->parentId().isNull()) {
|
||||||
|
d = myDevices().findById(device->parentId());
|
||||||
|
}
|
||||||
|
pluginStorage()->beginGroup(d->id().toString());
|
||||||
|
QString ip = pluginStorage()->value("cachedAddress").toString();
|
||||||
|
pluginStorage()->endGroup();
|
||||||
|
return ip;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -50,10 +50,21 @@ private slots:
|
|||||||
void onClientDisconnected(MqttChannel* channel);
|
void onClientDisconnected(MqttChannel* channel);
|
||||||
void onPublishReceived(MqttChannel* channel, const QString &topic, const QByteArray &payload);
|
void onPublishReceived(MqttChannel* channel, const QString &topic, const QByteArray &payload);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void setupShellyGateway(DeviceSetupInfo *info);
|
||||||
|
void setupShellyChild(DeviceSetupInfo *info);
|
||||||
|
|
||||||
|
QString getIP(Device *device) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ZeroConfServiceBrowser *m_zeroconfBrowser = nullptr;
|
ZeroConfServiceBrowser *m_zeroconfBrowser = nullptr;
|
||||||
|
|
||||||
QHash<Device*, MqttChannel*> m_mqttChannels;
|
QHash<Device*, MqttChannel*> m_mqttChannels;
|
||||||
|
|
||||||
|
QHash<DeviceClassId, StateTypeId> m_connectedStateTypesMap;
|
||||||
|
QHash<DeviceClassId, StateTypeId> m_powerStateTypeMap;
|
||||||
|
QHash<ActionTypeId, DeviceClassId> m_powerActionTypesMap;
|
||||||
|
QHash<ActionTypeId, ParamTypeId> m_powerActionParamTypesMap;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // DEVICEPLUGINSHELLY_H
|
#endif // DEVICEPLUGINSHELLY_H
|
||||||
|
|||||||
@ -10,15 +10,36 @@
|
|||||||
"deviceClasses": [
|
"deviceClasses": [
|
||||||
{
|
{
|
||||||
"id": "f810b66a-7177-4397-9771-4229abaabbb6",
|
"id": "f810b66a-7177-4397-9771-4229abaabbb6",
|
||||||
"name": "shellyOne",
|
"name": "shelly1",
|
||||||
"displayName": "Shelly One",
|
"displayName": "Shelly1 / Shelly1PM",
|
||||||
"createMethods": ["discovery"],
|
"createMethods": ["discovery"],
|
||||||
"interfaces": [ "powerswitch", "connectable" ],
|
"interfaces": [ "gateway" ],
|
||||||
"paramTypes": [
|
"paramTypes": [
|
||||||
{
|
{
|
||||||
"id": "1d301dc0-5e48-473f-a611-8e407289e545",
|
"id": "1d301dc0-5e48-473f-a611-8e407289e545",
|
||||||
"name":"id",
|
"name":"id",
|
||||||
"displayName": "ID",
|
"displayName": "Shelly ID",
|
||||||
|
"type": "QString",
|
||||||
|
"readOnly": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "d0e0499e-faa0-432a-a760-c295b0aefed0",
|
||||||
|
"name": "connectedDevice",
|
||||||
|
"displayName": "Connected device",
|
||||||
|
"type": "QString",
|
||||||
|
"allowedValues": ["None", "Generic", "Light"],
|
||||||
|
"defaultValue": "Generic"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "fa1aa0f6-93b2-410d-a2c5-7b2f45eae679",
|
||||||
|
"name": "username",
|
||||||
|
"displayName": "Username (optional)",
|
||||||
|
"type": "QString"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "d29b8399-bfa6-4146-921d-a1d43ca4e184",
|
||||||
|
"name": "password",
|
||||||
|
"displayName": "Password (optional)",
|
||||||
"type": "QString"
|
"type": "QString"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -31,9 +52,95 @@
|
|||||||
"type": "bool",
|
"type": "bool",
|
||||||
"defaultValue": false,
|
"defaultValue": false,
|
||||||
"cached": false
|
"cached": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"actionTypes": [
|
||||||
|
{
|
||||||
|
"id": "b4067d54-36c5-4d30-bbc3-c8c712d6fd32",
|
||||||
|
"name": "reboot",
|
||||||
|
"displayName": "Reboot"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6de35a17-0f54-4397-894d-4321b64c53d1",
|
||||||
|
"name": "shellySwitch",
|
||||||
|
"displayName": "Shelly switch",
|
||||||
|
"createMethods": ["auto"],
|
||||||
|
"interfaces": [ "powerswitch", "connectable"],
|
||||||
|
"settingsTypes": [
|
||||||
|
{
|
||||||
|
"id": "ce9f1650-5e12-40f4-97de-27af86afa40b",
|
||||||
|
"name": "buttonType",
|
||||||
|
"displayName": "Button type",
|
||||||
|
"allowedValues": ["momentary", "toggle", "edge", "detached"],
|
||||||
|
"type": "QString",
|
||||||
|
"defaultValue": "toggle"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "0f6df838-7fc4-4fc0-9247-b9b8fa4ec924",
|
"id": "f31eb52b-9aaf-409d-8bba-badda7c1a249",
|
||||||
|
"name": "invertButton",
|
||||||
|
"displayName": "Invert button",
|
||||||
|
"type": "bool",
|
||||||
|
"defaultValue": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stateTypes": [
|
||||||
|
{
|
||||||
|
"id": "0c233312-7b8f-4ca3-880d-523cab9b3ccb",
|
||||||
|
"name": "connected",
|
||||||
|
"displayName": "Connected",
|
||||||
|
"displayNameEvent": "Connected changed",
|
||||||
|
"type": "bool",
|
||||||
|
"defaultValue": false,
|
||||||
|
"cached": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "20f74d88-0683-4d3a-9513-6b29b5112b7b",
|
||||||
|
"name": "power",
|
||||||
|
"displayName": "On/Off",
|
||||||
|
"displayNameEvent": "On/Off toggled",
|
||||||
|
"type": "bool",
|
||||||
|
"defaultValue": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"eventTypes": [
|
||||||
|
{
|
||||||
|
"id": "41498655-1943-4b46-ac36-adea7bafab87",
|
||||||
|
"name": "pressed",
|
||||||
|
"displayName": "Pressed"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "512c3c7d-d6a6-4d2a-bccd-83147e5f9a25",
|
||||||
|
"name": "shellyGeneric",
|
||||||
|
"displayName": "Shelly connected device",
|
||||||
|
"createMethods": ["auto"],
|
||||||
|
"interfaces": ["power", "connectable"],
|
||||||
|
"settingsTypes": [
|
||||||
|
{
|
||||||
|
"id": "7d35aea3-1444-48c8-9732-a41bfc3b9d75",
|
||||||
|
"name": "defaultState",
|
||||||
|
"displayName": "Default state",
|
||||||
|
"allowedValues": ["on", "off", "last", "switch"],
|
||||||
|
"defaultValue": "off",
|
||||||
|
"type": "QString"
|
||||||
|
}
|
||||||
|
|
||||||
|
],
|
||||||
|
"stateTypes": [
|
||||||
|
{
|
||||||
|
"id": "4a141674-faa6-4953-8272-5b4a4da84d31",
|
||||||
|
"name": "connected",
|
||||||
|
"displayName": "Connected",
|
||||||
|
"displayNameEvent": "Connected changed",
|
||||||
|
"type": "bool",
|
||||||
|
"defaultValue": false,
|
||||||
|
"cached": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "72d7dbba-757c-4b03-a092-1d3f374fa961",
|
||||||
"name": "power",
|
"name": "power",
|
||||||
"displayName": "Power",
|
"displayName": "Power",
|
||||||
"displayNameEvent": "Power changed",
|
"displayNameEvent": "Power changed",
|
||||||
@ -42,12 +149,44 @@
|
|||||||
"defaultValue": false,
|
"defaultValue": false,
|
||||||
"writable": true
|
"writable": true
|
||||||
}
|
}
|
||||||
],
|
|
||||||
"eventTypes": [
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "62a2d6b8-d70d-45fc-ba8c-1c680282a399",
|
||||||
|
"name": "shellyLight",
|
||||||
|
"displayName": "Shelly connected light",
|
||||||
|
"createMethods": ["auto"],
|
||||||
|
"interfaces": ["light", "connectable"],
|
||||||
|
"settingsTypes": [
|
||||||
{
|
{
|
||||||
"id": "172e6aa3-13d3-4c71-8a4d-112605460863",
|
"id": "4fe9ae31-3657-41bf-bd40-a219d58465d3",
|
||||||
"name": "pressed",
|
"name": "defaultState",
|
||||||
"displayName": "Pressed"
|
"displayName": "Default state",
|
||||||
|
"allowedValues": ["on", "off", "last", "switch"],
|
||||||
|
"defaultValue": "off",
|
||||||
|
"type": "QString"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stateTypes": [
|
||||||
|
{
|
||||||
|
"id": "61b7d8ac-d229-4268-8143-6edb2eca978d",
|
||||||
|
"name": "connected",
|
||||||
|
"displayName": "Connected",
|
||||||
|
"displayNameEvent": "Connected changed",
|
||||||
|
"type": "bool",
|
||||||
|
"defaultValue": false,
|
||||||
|
"cached": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2ee5bfab-271e-4b95-9464-122a5208f1a5",
|
||||||
|
"name": "power",
|
||||||
|
"displayName": "Power",
|
||||||
|
"displayNameEvent": "Power changed",
|
||||||
|
"displayNameAction": "Set power",
|
||||||
|
"type": "bool",
|
||||||
|
"defaultValue": false,
|
||||||
|
"writable": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user