Aqara AQ1 DJT11LM vibration/tilt sensor for window opening

Hi,
I’ve used node red on several projects over the years and recently started with home assistant because I felt I could rely on some node red experience to start with some automations.

I bought an Aqara AQ1 vibration sensor to help report on the Velux roof window opening. I paired it with ZHA. Using NodeRed I can trap the current_orientation event and then with some borrowed binary to decimal and trig functions I can get the tilt angle of the sensor. :slight_smile:

[{"id":"dcf521ad5833f797","type":"server-events","z":"fb781577ee0a6ffe","name":"","server":"2be748d3.da7088","version":2,"eventType":"","exposeToHomeAssistant":false,"eventData":"{ \"device_ieee\": \"00:15:8d:00:08:74:7e:61\" }","haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"waitForRunning":true,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"$outputData(\"eventData\").event_type","valueType":"jsonata"}],"event_type":"","x":140,"y":120,"wires":[["02143a259fe8e393"]]},{"id":"96452fde42340884","type":"function","z":"fb781577ee0a6ffe","name":"Handle current_orientation","func":"\nvar rawX = msg.payload.event.args.rawValueX\nvar rawY = msg.payload.event.args.rawValueY\nvar rawZ = msg.payload.event.args.rawValueZ\n\nvar newX = twosComplementToInteger(rawX);\nvar newY = twosComplementToInteger(rawY);\nvar newZ = twosComplementToInteger(rawZ);\n\nmsg.payload = calculateAngle(newX,newY,newZ);\n\nreturn msg;\n\nfunction twosComplementToInteger(num) {\n    // If the MSB is 1, it means the number is negative\n    if (num & 0x8000) {\n        // Convert the negative two's complement representation to a negative integer\n        // by taking the two's complement and negating the result\n        return -((~num + 1) & 0x7FFF);\n    } else {\n        // If the MSB is 0, the number is positive\n        // The number itself is the positive integer\n        return num;\n    }\n}\n\nfunction calculateAngle(valueX, valueY, valueZ) {\n    const angleX = Math.round((Math.atan(valueX / Math.sqrt(valueZ * valueZ + valueY * valueY)) * 1800) / Math.PI / 10);\n    const angleY = Math.round((Math.atan(valueY / Math.sqrt(valueX * valueX + valueZ * valueZ)) * 1800) / Math.PI / 10);\n    const angleZ = Math.round((Math.atan(valueZ / Math.sqrt(valueX * valueX + valueY * valueY)) * 1800) / Math.PI / 10);\n\n    return { angleX, angleY, angleZ };\n}\n\n// Test the function\n//const twosComplementNum = 0b1111111111110001; // This represents -15 in two's complement\n//const result = twosComplementToInteger(twosComplementNum);\n//console.log(result); // Output: -15\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":380,"y":180,"wires":[["5d036dabcf96c1a0"]]},{"id":"02143a259fe8e393","type":"switch","z":"fb781577ee0a6ffe","name":"","property":"payload.event.command","propertyType":"msg","rules":[{"t":"eq","v":"current_orientation","vt":"str"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":150,"y":180,"wires":[["96452fde42340884"],["2f59491d565c4894"]]},{"id":"5d036dabcf96c1a0","type":"switch","z":"fb781577ee0a6ffe","name":"","property":"payload.angleY","propertyType":"msg","rules":[{"t":"btwn","v":"55","vt":"num","v2":"70","v2t":"num"},{"t":"btwn","v":"10","vt":"num","v2":"25","v2t":"num"},{"t":"btwn","v":"-15","vt":"num","v2":"-30","v2t":"num"},{"t":"btwn","v":"-31","vt":"num","v2":"-80","v2t":"num"}],"checkall":"true","repair":false,"outputs":4,"x":390,"y":380,"wires":[["9038c9cd9d50607e"],["95bc2bb1d8dfe6a3"],["c5b881fa67747992"],["dbb2b5845f881b47"]]},{"id":"9038c9cd9d50607e","type":"debug","z":"fb781577ee0a6ffe","name":"Locked","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"\"locked\"","targetType":"jsonata","statusVal":"","statusType":"auto","x":580,"y":320,"wires":[]},{"id":"95bc2bb1d8dfe6a3","type":"debug","z":"fb781577ee0a6ffe","name":"Vented","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"\"vented\"","targetType":"jsonata","statusVal":"","statusType":"auto","x":570,"y":360,"wires":[]},{"id":"c5b881fa67747992","type":"debug","z":"fb781577ee0a6ffe","name":"Open","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"\"open\"","targetType":"jsonata","statusVal":"","statusType":"auto","x":570,"y":400,"wires":[]},{"id":"dbb2b5845f881b47","type":"debug","z":"fb781577ee0a6ffe","name":"Wide Open","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"\"wide open\"","targetType":"jsonata","statusVal":"","statusType":"auto","x":590,"y":440,"wires":[]},{"id":"2f59491d565c4894","type":"debug","z":"fb781577ee0a6ffe","name":"Other events","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":330,"y":220,"wires":[]},{"id":"2be748d3.da7088","type":"server","name":"Home Assistant","addon":true}]

I’m trying to work out how to put the information back into HomeAssistant. I created a Text Helper that I thought I could update from this flow, but did not see how to do this.

What I think I want is to create a virtual window with states closed/locked or open+angle with the optional state of vented/not vented. I would then have a graph showing when the window was open and ultimately create alerts when leaving the house or when adverse weather is detected.

Where do I start getting node red into dashboard or other automations?

regards
Steve

1 Like

Take a look at the Node-RED companion custom component. It allows you to create entities in HA that can be updated from NR, probably a sensor in your use case.

https://github.com/zachowj/hass-node-red

You would use a call-service node to update a text helper. https://www.home-assistant.io/integrations/text/#service-textset_value

@Kermit thank you for that. I now have a simple indication of how open my Velux window is:

First, I created two helpers:
input_text:steves_office_velux_state
input_nubmber:steves_office_velux_angle

Then in NodeRed I created this flow to update the helpers:

[{"id":"dcf521ad5833f797","type":"server-events","z":"fb781577ee0a6ffe","name":"","server":"2be748d3.da7088","version":2,"eventType":"","exposeToHomeAssistant":false,"eventData":"{ \"device_ieee\": \"00:15:8d:00:08:74:7e:61\" }","haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"waitForRunning":true,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"$outputData(\"eventData\").event_type","valueType":"jsonata"}],"event_type":"","x":140,"y":120,"wires":[["02143a259fe8e393"]]},{"id":"96452fde42340884","type":"function","z":"fb781577ee0a6ffe","name":"Handle current_orientation event","func":"\nvar rawX = msg.payload.event.args.rawValueX\nvar rawY = msg.payload.event.args.rawValueY\nvar rawZ = msg.payload.event.args.rawValueZ\n\nvar newX = twosComplementToInteger(rawX);\nvar newY = twosComplementToInteger(rawY);\nvar newZ = twosComplementToInteger(rawZ);\n\nmsg.payload = calculateAngle(newX,newY,newZ);\n\n// 63 is not a magic number. It depends on the pitch of the roof\n// and the attachment of the sensor to the opening lever.\n// It is equal to the resting angle in locked position\n\nmsg.payload.angle = 63 - msg.payload.angleY;\n\nreturn msg;\n\nfunction twosComplementToInteger(num) {\n    // If the MSB is 1, it means the number is negative\n    if (num & 0x8000) {\n        // Convert the negative two's complement representation to a negative integer\n        // by taking the two's complement and negating the result\n        return -((~num + 1) & 0x7FFF);\n    } else {\n        // If the MSB is 0, the number is positive\n        // The number itself is the positive integer\n        return num;\n    }\n}\n\nfunction calculateAngle( valueX, valueY, valueZ) {\n    const angleX = Math.round((Math.atan(valueX / Math.sqrt(valueZ * valueZ + valueY * valueY)) * 1800) / Math.PI / 10);\n    const angleY = Math.round((Math.atan(valueY / Math.sqrt(valueX * valueX + valueZ * valueZ)) * 1800) / Math.PI / 10);\n    const angleZ = Math.round((Math.atan(valueZ / Math.sqrt(valueX * valueX + valueY * valueY)) * 1800) / Math.PI / 10);\n \n    return { angleX, angleY, angleZ };\n}\n\n// Test the function\n//const twosComplementNum = 0b1111111111110001; // This represents -15 in two's complement\n//const result = twosComplementToInteger(twosComplementNum);\n//console.log(result); // Output: -15\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":220,"y":280,"wires":[["62e92c917e8b7557"]]},{"id":"02143a259fe8e393","type":"switch","z":"fb781577ee0a6ffe","name":"","property":"payload.event.command","propertyType":"msg","rules":[{"t":"eq","v":"current_orientation","vt":"str"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":150,"y":180,"wires":[["96452fde42340884"],["2f59491d565c4894"]]},{"id":"5d036dabcf96c1a0","type":"switch","z":"fb781577ee0a6ffe","name":"","property":"payload.angle","propertyType":"msg","rules":[{"t":"btwn","v":"0","vt":"num","v2":"10","v2t":"num"},{"t":"btwn","v":"11","vt":"num","v2":"50","v2t":"num"},{"t":"btwn","v":"51","vt":"num","v2":"90","v2t":"num"},{"t":"btwn","v":"91","vt":"num","v2":"359","v2t":"num"}],"checkall":"true","repair":false,"outputs":4,"x":390,"y":420,"wires":[["eb8af86ae0dc857a"],["ac818b52a7c294c9"],["3055bcb26cef5799"],["ee02816ef60b7589"]]},{"id":"9038c9cd9d50607e","type":"debug","z":"fb781577ee0a6ffe","name":"Locked","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"\"locked\"","targetType":"jsonata","statusVal":"","statusType":"auto","x":860,"y":280,"wires":[]},{"id":"95bc2bb1d8dfe6a3","type":"debug","z":"fb781577ee0a6ffe","name":"Vented","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"\"vented\"","targetType":"jsonata","statusVal":"","statusType":"auto","x":850,"y":340,"wires":[]},{"id":"c5b881fa67747992","type":"debug","z":"fb781577ee0a6ffe","name":"Open","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"\"open\"","targetType":"jsonata","statusVal":"","statusType":"auto","x":850,"y":400,"wires":[]},{"id":"dbb2b5845f881b47","type":"debug","z":"fb781577ee0a6ffe","name":"Wide Open","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"\"wide open\"","targetType":"jsonata","statusVal":"","statusType":"auto","x":870,"y":460,"wires":[]},{"id":"2f59491d565c4894","type":"debug","z":"fb781577ee0a6ffe","name":"Other events","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":330,"y":220,"wires":[]},{"id":"eb8af86ae0dc857a","type":"api-call-service","z":"fb781577ee0a6ffe","name":"","server":"2be748d3.da7088","version":5,"debugenabled":false,"domain":"input_text","service":"set_value","areaId":["steves_office"],"deviceId":[],"entityId":["input_text.steves_office_velux_state"],"data":"{\"value\":\"Locked\"}","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"last","x":640,"y":280,"wires":[["9038c9cd9d50607e"]]},{"id":"3055bcb26cef5799","type":"api-call-service","z":"fb781577ee0a6ffe","name":"","server":"2be748d3.da7088","version":5,"debugenabled":false,"domain":"input_text","service":"set_value","areaId":["steves_office"],"deviceId":[],"entityId":["input_text.steves_office_velux_state"],"data":"{\"value\":\"Open\"}","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":640,"y":400,"wires":[["c5b881fa67747992"]]},{"id":"ee02816ef60b7589","type":"api-call-service","z":"fb781577ee0a6ffe","name":"","server":"2be748d3.da7088","version":5,"debugenabled":false,"domain":"input_text","service":"set_value","areaId":["steves_office"],"deviceId":[],"entityId":["input_text.steves_office_velux_state"],"data":"{\"value\":\"Wide Open\"}","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":640,"y":460,"wires":[["dbb2b5845f881b47"]]},{"id":"ac818b52a7c294c9","type":"api-call-service","z":"fb781577ee0a6ffe","name":"","server":"2be748d3.da7088","version":5,"debugenabled":false,"domain":"input_text","service":"set_value","areaId":["steves_office"],"deviceId":[],"entityId":["input_text.steves_office_velux_state"],"data":"{\"value\":\"Vented\"}","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"last","x":640,"y":340,"wires":[["95bc2bb1d8dfe6a3"]]},{"id":"62e92c917e8b7557","type":"api-call-service","z":"fb781577ee0a6ffe","name":"","server":"2be748d3.da7088","version":5,"debugenabled":false,"domain":"input_number","service":"set_value","areaId":["steves_office"],"deviceId":[],"entityId":["input_number.steves_office_velux_angle"],"data":"{\"value\": msg.payload.angle}","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"last","x":310,"y":340,"wires":[["5d036dabcf96c1a0","73665e4a77f44008"]]},{"id":"73665e4a77f44008","type":"debug","z":"fb781577ee0a6ffe","name":"debug 75","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":620,"y":220,"wires":[]},{"id":"2be748d3.da7088","type":"server","name":"Home Assistant","addon":true}]

Converting this to a blueprint is well beyond my current abilities but hopefully someone might find this example of getting angles out of the AQ1 with ZHA useful.

regards
Steve