Add support for the doorbell interface

This commit is contained in:
Michael Zanetti 2019-08-30 02:47:48 +02:00
parent 31b05c81a6
commit 8d8b72790b
20 changed files with 243 additions and 14 deletions

View File

@ -69,7 +69,7 @@ void ZeroconfDiscovery::serviceEntryAdded(const QZeroConfService &entry)
return;
}
qDebug() << "zeroconf service discovered" << entry->type() << entry->name() << " IP:" << entry->ip().toString() << entry->txt();
// qDebug() << "zeroconf service discovered" << entry->type() << entry->name() << " IP:" << entry->ip().toString() << entry->txt();
QString uuid;
bool sslEnabled = false;
@ -97,7 +97,7 @@ void ZeroconfDiscovery::serviceEntryAdded(const QZeroConfService &entry)
if (!host) {
host = new NymeaHost(m_nymeaHosts);
host->setUuid(uuid);
qDebug() << "ZeroConf: Adding new host:" << serverName << uuid;
// qDebug() << "ZeroConf: Adding new host:" << serverName << uuid;
m_nymeaHosts->addHost(host);
}
host->setName(serverName);
@ -113,14 +113,14 @@ void ZeroconfDiscovery::serviceEntryAdded(const QZeroConfService &entry)
url.setPort(entry->port());
Connection *connection = host->connections()->find(url);
if (!connection) {
qDebug() << "Zeroconf: Adding new connection to host:" << host->name() << url.toString();
// qDebug() << "Zeroconf: Adding new connection to host:" << host->name() << url.toString();
QString displayName = QString("%1:%2").arg(url.host()).arg(url.port());
Connection::BearerType bearerType = QHostAddress(url.host()).isLoopback() ? Connection::BearerTypeLoopback : Connection::BearerTypeLan;
connection = new Connection(url, bearerType, sslEnabled, displayName);
connection->setOnline(true);
host->connections()->addConnection(connection);
} else {
qDebug() << "Zeroconf: Setting connection online:" << host->name() << url.toString();
// qDebug() << "Zeroconf: Setting connection online:" << host->name() << url.toString();
connection->setOnline(true);
}
}

View File

@ -412,11 +412,11 @@ void NymeaConnection::updateActiveBearers()
}
if (m_availableBearerTypes != availableBearerTypes) {
qDebug() << "Available Bearer Types changed:" << availableBearerTypes;
// qDebug() << "Available Bearer Types changed:" << availableBearerTypes;
m_availableBearerTypes = availableBearerTypes;
emit availableBearerTypesChanged();
} else {
qDebug() << "Available Bearer Types:" << availableBearerTypes;
// qDebug() << "Available Bearer Types:" << availableBearerTypes;
}
if (!m_currentHost) {

View File

@ -34,6 +34,22 @@ DeviceManager::DeviceManager(JsonRpcClient* jsonclient, QObject *parent) :
m_jsonClient(jsonclient)
{
m_jsonClient->registerNotificationHandler(this, "notificationReceived");
EventHandler *eventHandler = new EventHandler(this);
m_jsonClient->registerNotificationHandler(eventHandler, "notificationReceived");
connect(eventHandler, &EventHandler::eventReceived, this, [this](const QVariantMap event) {
QUuid deviceId = event.value("deviceId").toUuid();
QUuid eventTypeId = event.value("eventTypeId").toUuid();
Device *dev = m_devices->getDevice(deviceId);
if (!dev) {
qWarning() << "received an event from a device we don't know..." << deviceId << event;
return;
}
qDebug() << "Event received" << deviceId.toString() << eventTypeId.toString();
dev->eventTriggered(eventTypeId.toString(), event.value("params").toMap());
emit eventTriggered(deviceId.toString(), eventTypeId.toString(), event.value("params").toMap());
});
}
void DeviceManager::clear()

View File

@ -120,6 +120,8 @@ signals:
void fetchingDataChanged();
void notificationReceived(const QString &deviceId, const QString &eventTypeId, const QVariantList &params);
void eventTriggered(const QString &deviceId, const QString &eventTypeId, const QVariantMap params);
private:
Vendors *m_vendors;
Plugins *m_plugins;
@ -135,6 +137,29 @@ private:
QHash<int, QPointer<BrowserItems> > m_browsingRequests;
QHash<int, QPointer<BrowserItem> > m_browserDetailsRequests;
};
// TODO: Kinda shitty that Device Events are not sent from the Devices namespace...
class EventHandler: public JsonHandler {
Q_OBJECT
public:
EventHandler(QObject *parent = nullptr): JsonHandler(parent) {}
QString nameSpace() const override {
return "Events";
}
signals:
void eventReceived(const QVariantMap &event);
private:
Q_INVOKABLE void notificationReceived(const QVariantMap &data) {
qDebug() << "event rece" << data;
emit eventReceived(data.value("params").toMap().value("event").toMap());
}
};
#endif // DEVICEMANAGER_H

View File

@ -28,7 +28,7 @@ class JsonHandler : public QObject
{
Q_OBJECT
public:
JsonHandler(QObject *parent = 0);
JsonHandler(QObject *parent = nullptr);
virtual QString nameSpace() const = 0;
};

View File

@ -90,6 +90,7 @@ signals:
void paramsChanged();
void settingsChanged();
void statesChanged();
void eventTriggered(const QString &eventTypeId, const QVariantMap &params);
};

View File

@ -115,6 +115,16 @@ RuleActionParam *RuleActionParams::get(int index) const
return m_list.at(index);
}
bool RuleActionParams::hasRuleActionParam(const QString &paramTypeId) const
{
for (int i = 0; i < m_list.count(); i++) {
if (m_list.at(i)->paramTypeId() == paramTypeId) {
return true;
}
}
return false;
}
bool RuleActionParams::operator==(RuleActionParams *other) const
{
if (rowCount() != other->rowCount()) {

View File

@ -33,6 +33,8 @@ public:
Q_INVOKABLE RuleActionParam* get(int index) const;
Q_INVOKABLE bool hasRuleActionParam(const QString &paramTypeId) const;
bool operator==(RuleActionParams *other) const;
signals:

View File

@ -132,6 +132,7 @@ target.path = /usr/bin
INSTALLS += target
DISTFILES += \
ruletemplates/doorbellruletemplates.json \
ruletemplates/smartmetertemplates.json \
ruletemplates/presencesensortemplates.json \
ruletemplates/daylightsensor.json \

View File

@ -193,5 +193,6 @@
<file>ui/delegates/BrowserItemDelegate.qml</file>
<file>ui/components/NavigationPad.qml</file>
<file>ui/components/KeypadButton.qml</file>
<file>ui/devicepages/DoorbellDevicePage.qml</file>
</qresource>
</RCC>

View File

@ -7,5 +7,6 @@
<file>ruletemplates/presencesensortemplates.json</file>
<file>ruletemplates/daylightsensor.json</file>
<file>ruletemplates/lighttemplates.json</file>
<file>ruletemplates/doorbellruletemplates.json</file>
</qresource>
</RCC>

View File

@ -0,0 +1,24 @@
{
"templates": [
{
"interfaceName": "doorbell",
"description": "Alert on doorbell ring",
"ruleNameTemplate": "Alert %1 when someone is at %0",
"eventDescriptorTemplates": [
{
"interfaceName": "doorbell",
"interfaceEvent": "doorbellPressed",
"selectionId": 0
}
],
"ruleActionTemplates": [
{
"interfaceName": "alert",
"interfaceAction": "alert",
"selectionId": 1
}
]
}
]
}

View File

@ -65,7 +65,7 @@ ApplicationWindow {
}
property alias _discovery: discovery
property var supportedInterfaces: ["light", "weather", "media", "garagegate", "awning", "shutter", "blind", "powersocket", "heating", "sensor", "smartmeter", "evcharger", "accesscontrol", "button", "notifications", "inputtrigger", "outputtrigger", "gateway"]
property var supportedInterfaces: ["light", "weather", "media", "garagegate", "awning", "shutter", "blind", "powersocket", "heating", "doorbell", "sensor", "smartmeter", "evcharger", "accesscontrol", "button", "notifications", "inputtrigger", "outputtrigger", "gateway"]
function interfaceToString(name) {
switch(name) {
case "light":
@ -122,6 +122,8 @@ ApplicationWindow {
return qsTr("EV-chargers");
case "powersocket":
return qsTr("Power sockets")
case "doorbell":
return qsTr("Doorbells");
case "uncategorized":
return qsTr("Uncategorized")
default:
@ -226,6 +228,8 @@ ApplicationWindow {
case "evcharger":
case "extendedevcharger":
return Qt.resolvedUrl("images/ev-charger.svg")
case "doorbell":
return Qt.resolvedUrl("images/notification.svg")
case "connectable":
return Qt.resolvedUrl("images/stock_link.svg")
default:
@ -285,6 +289,10 @@ ApplicationWindow {
return qsTr("daylight sensor")
case "presencesensor":
return qsTr("presence sensor")
case "doorbell":
return qsTr("doorbell")
case "alert":
return qsTr("alert")
default:
console.warn("Unhandled interfaceToDisplayName:", name)
}
@ -322,6 +330,8 @@ ApplicationWindow {
page = "SmartMeterDevicePage.qml"
} else if (interfaceList.indexOf("powersocket") >= 0) {
page = "PowersocketDevicePage.qml";
} else if (interfaceList.indexOf("doorbell") >= 0) {
page = "DoorbellDevicePage.qml";
} else {
page = "GenericDevicePage.qml";
}

View File

@ -91,7 +91,6 @@ Item {
Label {
text: infoPane.text
visible: infoPane.alertState
font.pixelSize: app.smallFont
color: "white"
}

View File

@ -13,6 +13,7 @@ SwipeDelegate {
property bool wrapTexts: true
property bool prominentSubText: true
property int textAlignment: Text.AlignLeft
property string iconName
property string thumbnail
@ -81,6 +82,7 @@ SwipeDelegate {
maximumLineCount: root.wrapTexts ? 2 : 1
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
horizontalAlignment: root.textAlignment
}
Label {
Layout.fillWidth: true

View File

@ -0,0 +1,111 @@
import QtQuick 2.5
import QtQuick.Controls 2.1
import QtQuick.Layouts 1.1
import Nymea 1.0
import "../components"
import "../customviews"
DevicePageBase {
id: root
readonly property EventType doorbellPressedType: deviceClass.eventTypes.findByName("doorbellPressed")
GridLayout {
anchors.fill: parent
anchors.topMargin: app.margins
columns: app.landscape ? 2 : 1
columnSpacing: app.margins
rowSpacing: app.margins
Item {
Layout.fillWidth: true
Layout.fillHeight: true
ColorIcon {
id: doorbellIcon
anchors.centerIn: parent
height: Math.min(parent.width, parent.height)
width: height
name: "../images/notification.svg"
color: keyColor
SequentialAnimation {
id: ringAnimation
ColorAnimation { target: doorbellIcon; property: "color"; from: doorbellIcon.keyColor; to: app.accentColor; duration: 200 }
ColorAnimation { target: doorbellIcon; property: "color"; from: app.accentColor; to: doorbellIcon.keyColor; duration: 300 }
ColorAnimation { target: doorbellIcon; property: "color"; from: doorbellIcon.keyColor; to: app.accentColor; duration: 200 }
ColorAnimation { target: doorbellIcon; property: "color"; from: app.accentColor; to: doorbellIcon.keyColor; duration: 300 }
ColorAnimation { target: doorbellIcon; property: "color"; from: doorbellIcon.keyColor; to: app.accentColor; duration: 200 }
ColorAnimation { target: doorbellIcon; property: "color"; from: app.accentColor; to: doorbellIcon.keyColor; duration: 300 }
ColorAnimation { target: doorbellIcon; property: "color"; from: doorbellIcon.keyColor; to: app.accentColor; duration: 200 }
ColorAnimation { target: doorbellIcon; property: "color"; from: app.accentColor; to: doorbellIcon.keyColor; duration: 300 }
}
Connections {
target: root.device
onEventTriggered: {
print("evenEmitted", params)
if (eventTypeId == root.doorbellPressedType.id) {
ringAnimation.start();
}
}
}
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
ColumnLayout {
anchors.fill: parent
spacing: app.margins
ThinDivider {
visible: !app.landscape
}
RowLayout {
spacing: app.margins
Label {
Layout.fillWidth: true
}
ColorIcon {
Layout.preferredHeight: app.iconSize
Layout.preferredWidth: app.iconSize
name: "../images/alarm-clock.svg"
color: app.accentColor
}
Label {
text: qsTr("History")
}
Label {
Layout.fillWidth: true
}
}
ListView {
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
model: LogsModelNg {
engine: _engine
live: true
deviceId: root.device.id
typeIds: [root.doorbellPressedType.id]
}
delegate: NymeaListItemDelegate {
width: parent.width
text: Qt.formatDateTime(model.timestamp)
progressive: false
textAlignment: Text.AlignHCenter
}
}
}
}
}
}

View File

@ -78,7 +78,6 @@ Page {
Connections {
target: engine.ruleManager
onAddRuleReply: {
d.editRulePage.busy = false;
if (ruleError == "RuleErrorNoError") {
pageStack.pop(root);
} else {
@ -86,10 +85,10 @@ Page {
var popup = errorDialog.createObject(root, {errorCode: ruleError })
popup.open();
}
d.editRulePage.busy = false;
}
onEditRuleReply: {
d.editRulePage.busy = false;
if (ruleError == "RuleErrorNoError") {
pageStack.pop(root);
} else {
@ -97,6 +96,7 @@ Page {
var popup = errorDialog.createObject(root, {errorCode: ruleError })
popup.open();
}
d.editRulePage.busy = false;
}
}

View File

@ -300,12 +300,15 @@ Page {
function createRuleAction(rule, ruleTemplate, ruleActions, device, ruleActionTemplate) {
var ruleAction = ruleActions.createNewRuleAction();
var deviceClass = engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId);
ruleAction.actionTypeId = deviceClass.actionTypes.findByName(ruleActionTemplate.interfaceAction).id
ruleAction.deviceId = device.id;
print("Creating action", ruleActionTemplate.interfaceAction, "on device class", deviceClass.displayName)
ruleAction.actionTypeId = deviceClass.actionTypes.findByName(ruleActionTemplate.interfaceAction).id
var actionType = deviceClass.actionTypes.getActionType(ruleAction.actionTypeId);
for (var j = 0; j < ruleActionTemplate.ruleActionParamTemplates.count; j++) {
var ruleActionParamTemplate = ruleActionTemplate.ruleActionParamTemplates.get(j)
var actionType = deviceClass.actionTypes.getActionType(ruleAction.actionTypeId);
var paramType = actionType.paramTypes.findByName(ruleActionParamTemplate.paramName);
if (ruleActionParamTemplate.value !== undefined) {
ruleAction.ruleActionParams.setRuleActionParam(paramType.id, ruleActionParamTemplate.value)
@ -335,8 +338,26 @@ Page {
} else {
console.warn("Invalid RuleActionParamTemplate. Has neither value nor event spec")
}
}
// Check if the action has more paramTypes than there are defined in the ruleActionTemplate
for (var i = 0; i < actionType.paramTypes.count; i++) {
var paramType = actionType.paramTypes.get(i);
if (!ruleAction.ruleActionParams.hasRuleActionParam(paramType.id)) {
print("Missing param!", paramType.name)
var paramsPage = pageStack.push(Qt.resolvedUrl("SelectRuleActionParamsPage.qml"), {ruleAction: ruleAction, rule: rule})
paramsPage.onBackPressed.connect(function() {
ruleAction.destroy()
pageStack.pop();
});
paramsPage.onCompleted.connect(function() {
pageStack.pop();
ruleActions.addRuleAction(ruleAction);
fillRuleFromTemplate(rule, ruleTemplate);
})
return;
}
}
ruleActions.addRuleAction(ruleAction);
fillRuleFromTemplate(rule, ruleTemplate);
}

View File

@ -187,7 +187,9 @@ Page {
for (var i = 0; i < delegateRepeater.count; i++) {
var paramDelegate = delegateRepeater.itemAt(i);
if (paramDelegate.type === "static") {
print("Setting static value", paramDelegate.value)
if (root.device) {
print("setting", paramDelegate.paramType.id)
root.ruleAction.ruleActionParams.setRuleActionParam(paramDelegate.paramType.id, paramDelegate.value)
} else if (root.iface) {
root.ruleAction.ruleActionParams.setRuleActionParamByName(root.actionType.paramTypes.get(i).name, paramDelegate.value)

View File

@ -43,6 +43,9 @@ Page {
engine: _engine
groupByInterface: true
nameFilter: filterInput.shown ? filterInput.text : ""
Component.onCompleted: {
print("showing devices for interfaces", devicesProxy.shownInterfaces)
}
}
ColumnLayout {