Add support for the doorbell interface
This commit is contained in:
parent
31b05c81a6
commit
8d8b72790b
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -120,6 +120,8 @@ signals:
|
||||
void fetchingDataChanged();
|
||||
void notificationReceived(const QString &deviceId, const QString &eventTypeId, const QVariantList ¶ms);
|
||||
|
||||
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
|
||||
|
||||
@ -28,7 +28,7 @@ class JsonHandler : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
JsonHandler(QObject *parent = 0);
|
||||
JsonHandler(QObject *parent = nullptr);
|
||||
virtual QString nameSpace() const = 0;
|
||||
};
|
||||
|
||||
|
||||
@ -90,6 +90,7 @@ signals:
|
||||
void paramsChanged();
|
||||
void settingsChanged();
|
||||
void statesChanged();
|
||||
void eventTriggered(const QString &eventTypeId, const QVariantMap ¶ms);
|
||||
|
||||
};
|
||||
|
||||
|
||||
@ -115,6 +115,16 @@ RuleActionParam *RuleActionParams::get(int index) const
|
||||
return m_list.at(index);
|
||||
}
|
||||
|
||||
bool RuleActionParams::hasRuleActionParam(const QString ¶mTypeId) 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()) {
|
||||
|
||||
@ -33,6 +33,8 @@ public:
|
||||
|
||||
Q_INVOKABLE RuleActionParam* get(int index) const;
|
||||
|
||||
Q_INVOKABLE bool hasRuleActionParam(const QString ¶mTypeId) const;
|
||||
|
||||
bool operator==(RuleActionParams *other) const;
|
||||
|
||||
signals:
|
||||
|
||||
@ -132,6 +132,7 @@ target.path = /usr/bin
|
||||
INSTALLS += target
|
||||
|
||||
DISTFILES += \
|
||||
ruletemplates/doorbellruletemplates.json \
|
||||
ruletemplates/smartmetertemplates.json \
|
||||
ruletemplates/presencesensortemplates.json \
|
||||
ruletemplates/daylightsensor.json \
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
24
nymea-app/ruletemplates/doorbellruletemplates.json
Normal file
24
nymea-app/ruletemplates/doorbellruletemplates.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -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";
|
||||
}
|
||||
|
||||
@ -91,7 +91,6 @@ Item {
|
||||
|
||||
Label {
|
||||
text: infoPane.text
|
||||
visible: infoPane.alertState
|
||||
font.pixelSize: app.smallFont
|
||||
color: "white"
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
111
nymea-app/ui/devicepages/DoorbellDevicePage.qml
Normal file
111
nymea-app/ui/devicepages/DoorbellDevicePage.qml
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -43,6 +43,9 @@ Page {
|
||||
engine: _engine
|
||||
groupByInterface: true
|
||||
nameFilter: filterInput.shown ? filterInput.text : ""
|
||||
Component.onCompleted: {
|
||||
print("showing devices for interfaces", devicesProxy.shownInterfaces)
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
|
||||
Reference in New Issue
Block a user