Qolsys IQ Panel 2 and 3rd party integration

I try again and wait a little bit and see this error.

pi:~ $ openssl s_client -host 192.168.0.45 -port 12345
CONNECTED(00000003)
write:errno=104
---
no peer certificate available
---
No client certificate CA names sent
---
SSL handshake has read 0 bytes and written 283 bytes
Verification: OK
---
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 0 (ok)
---

Interesting. If you press enter there, do you get an ACK response?

Anyone had a chance to see if their panel will go from arm away to arm stay at the end of the countdown and a possible way to get it to work right?

I will say that since I changed over to using the OpenSSL command, I havenā€™t had the first time Iā€™ve needed to reboot my panel. Now granted Iā€™m using it more for homebridge now but I am also sending that over to home assistant too.

I am also working on a flow for SmartThings for just the alarm information, not the sensors.

Wow I havenā€™t been able to pay attention to this topic for a while. Iā€™m really looking forward to trying out the commands and openssl implementation @Smwoodward! And Iā€™m gonna take a look at the python script - it could replace node-red @mzac!

One thing I spent many hours doing is making my sensor creation much more dynamic. Iā€™m now using MQTT and taking advantage of HAā€™s autodiscovery. After defining my zone id and mapping them to names, I get sensors in HA immediately. So Iā€™ve moved my automation into YAML in HA now.

I defined my zones (all I have are a few doors) in the ā€œInitialize zone ids and namesā€ Setup step. Itā€™s in javascript but super simple - just a list you can update for your zones.

I use the add-on MQTT broker so my MQTT setup is really simple.

[
    {
        "id": "cbfaf013.0e5e",
        "type": "tab",
        "label": "Monitor QolSys Panel",
        "disabled": false,
        "info": ""
    },
    {
        "id": "a92cf1.8c434b1",
        "type": "inject",
        "z": "cbfaf013.0e5e",
        "name": "go",
        "props": [],
        "repeat": "",
        "crontab": "",
        "once": true,
        "onceDelay": 0.1,
        "topic": "",
        "x": 90,
        "y": 100,
        "wires": [
            [
                "df1b46c2.dc0748",
                "7593a9f9.4b4dc8"
            ]
        ]
    },
    {
        "id": "df1b46c2.dc0748",
        "type": "exec",
        "z": "cbfaf013.0e5e",
        "command": "curl -kN --http0.9 https://192.168.10.34:12345",
        "addpay": true,
        "append": "",
        "useSpawn": "true",
        "timer": "",
        "oldrc": false,
        "name": "curl",
        "x": 210,
        "y": 300,
        "wires": [
            [
                "1f5ac981.6ccdde",
                "1edaf367.8f44ed"
            ],
            [
                "f17d89c8.de8bf"
            ],
            [
                "cf10d274.0242e",
                "5fe3eb4d.3d8c04"
            ]
        ]
    },
    {
        "id": "cf10d274.0242e",
        "type": "debug",
        "z": "cbfaf013.0e5e",
        "name": "return (exit) codes",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 1130,
        "y": 380,
        "wires": []
    },
    {
        "id": "1f5ac981.6ccdde",
        "type": "debug",
        "z": "cbfaf013.0e5e",
        "name": "activity events and updates",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 1060,
        "y": 300,
        "wires": []
    },
    {
        "id": "f17d89c8.de8bf",
        "type": "debug",
        "z": "cbfaf013.0e5e",
        "name": "Just outputs the curl progress, not the actual output",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 1060,
        "y": 340,
        "wires": []
    },
    {
        "id": "8cb8b681.b122f8",
        "type": "status",
        "z": "cbfaf013.0e5e",
        "name": "",
        "scope": [
            "df1b46c2.dc0748"
        ],
        "x": 160,
        "y": 560,
        "wires": [
            [
                "aff08d86.5d518",
                "938d4ef0.68605"
            ]
        ]
    },
    {
        "id": "aff08d86.5d518",
        "type": "debug",
        "z": "cbfaf013.0e5e",
        "name": "pid",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 310,
        "y": 580,
        "wires": []
    },
    {
        "id": "1edaf367.8f44ed",
        "type": "switch",
        "z": "cbfaf013.0e5e",
        "name": "Ignore ACK",
        "property": "payload",
        "propertyType": "msg",
        "rules": [
            {
                "t": "cont",
                "v": "ACK",
                "vt": "str"
            },
            {
                "t": "cont",
                "v": "ZONE_UPDATE",
                "vt": "str"
            },
            {
                "t": "cont",
                "v": "ZONE_ACTIVE",
                "vt": "str"
            },
            {
                "t": "else"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 4,
        "x": 190,
        "y": 400,
        "wires": [
            [],
            [
                "9a07870e.d047d8"
            ],
            [
                "9a07870e.d047d8"
            ],
            [
                "29d0c5a8.b1b4ea"
            ]
        ]
    },
    {
        "id": "9a07870e.d047d8",
        "type": "json",
        "z": "cbfaf013.0e5e",
        "name": "convert update to json",
        "property": "payload",
        "action": "",
        "pretty": true,
        "x": 400,
        "y": 400,
        "wires": [
            [
                "9c24717d.c5031",
                "59822226.0248cc"
            ]
        ]
    },
    {
        "id": "9c24717d.c5031",
        "type": "debug",
        "z": "cbfaf013.0e5e",
        "name": "converted json",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 1080,
        "y": 460,
        "wires": []
    },
    {
        "id": "938d4ef0.68605",
        "type": "change",
        "z": "cbfaf013.0e5e",
        "name": "get pid",
        "rules": [
            {
                "t": "set",
                "p": "pid",
                "pt": "flow",
                "to": "$split($lookup(status, \"text\"),\":\")[1]",
                "tot": "jsonata"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 300,
        "y": 520,
        "wires": [
            [
                "50203a30.d5c0a4"
            ]
        ]
    },
    {
        "id": "50203a30.d5c0a4",
        "type": "debug",
        "z": "cbfaf013.0e5e",
        "name": "",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": true,
        "complete": "$flowContext(\"pid\")\t",
        "targetType": "jsonata",
        "statusVal": "$flowContext(\"pid\")\t",
        "statusType": "auto",
        "x": 480,
        "y": 520,
        "wires": []
    },
    {
        "id": "5fe3eb4d.3d8c04",
        "type": "switch",
        "z": "cbfaf013.0e5e",
        "name": "Check and restart",
        "property": "payload",
        "propertyType": "msg",
        "rules": [
            {
                "t": "jsonata_exp",
                "v": "$lookup(payload, \"code\")=56",
                "vt": "jsonata"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 1,
        "x": 470,
        "y": 360,
        "wires": [
            [
                "bf874de4.ef135"
            ]
        ]
    },
    {
        "id": "bf874de4.ef135",
        "type": "delay",
        "z": "cbfaf013.0e5e",
        "name": "",
        "pauseType": "delay",
        "timeout": "10",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "x": 660,
        "y": 360,
        "wires": [
            [
                "df1b46c2.dc0748"
            ]
        ]
    },
    {
        "id": "20c47d1f.7e57d2",
        "type": "debug",
        "z": "cbfaf013.0e5e",
        "name": "mqtt out",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 720,
        "y": 460,
        "wires": []
    },
    {
        "id": "a272ddf7.56ba6",
        "type": "debug",
        "z": "cbfaf013.0e5e",
        "name": "Zone id to zone name mapping",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": true,
        "complete": "true",
        "targetType": "full",
        "statusVal": "$flowContext(\"zones\")",
        "statusType": "auto",
        "x": 1050,
        "y": 20,
        "wires": []
    },
    {
        "id": "7593a9f9.4b4dc8",
        "type": "function",
        "z": "cbfaf013.0e5e",
        "name": "initialize zone ids and names",
        "func": "var zones = flow.get(\"zones\")\nmsg.payload = zones\nreturn msg",
        "outputs": 1,
        "noerr": 0,
        "initialize": "// Code added here will be run once\n// whenever the node is deployed.\n\nclass door_window {\n    constructor(zoneid, entity_id=\"\", name, device_class=\"door\", state=\"Closed\") {\n    this.state = state\n    this.id = zoneid\n    this.name = name\n    this.device_class = device_class\n    this.entity_id = entity_id\n    this.payload_on = \"Open\"\n    this.payload_off = \"Closed\"\n    this.config_topic = \"homeassistant/binary_sensor/\" + entity_id + \"/config\"\n    this.state_topic = \"mqtt_states/binary_sensor/\" + entity_id + \"/state\" \n    }\n    \n}\n\nvar zones = []\nzones[1] = new door_window(1, \"front_door\", \"Front Door\")\nzones[2] = new door_window(2, \"trash_gate\", \"Trash Gate\")\nzones[3] = new door_window(3, \"neela_s_garage_entry\", \"Neela's Garage Entry\")\nzones[4] = new door_window(4, \"roopesh_s_garage_entry\", \"Roopesh's Garage Entry\")\nzones[6] = new door_window(6, \"great_room_slider\", \"Great Room Slider\")\nzones[7] = new door_window(7, \"garage_side_door\", \"Garage Side Door\")\n\nflow.set(\"zones\", zones)\n",
        "finalize": "",
        "x": 360,
        "y": 60,
        "wires": [
            [
                "a272ddf7.56ba6",
                "4e0e16a3.7cd0f8"
            ]
        ]
    },
    {
        "id": "29d0c5a8.b1b4ea",
        "type": "debug",
        "z": "cbfaf013.0e5e",
        "name": "Unknown events",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 170,
        "y": 460,
        "wires": []
    },
    {
        "id": "59822226.0248cc",
        "type": "function",
        "z": "cbfaf013.0e5e",
        "name": "Build MQTT Update Payload",
        "func": "var zid = msg.payload[\"zone\"][\"zone_id\"]\nvar zones = flow.get(\"zones\")\nvar state_topic = zones[zid].state_topic\nvar state = msg.payload[\"zone\"][\"status\"]\n\nmsg.payload = state\nmsg.topic = state_topic\n\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "x": 460,
        "y": 460,
        "wires": [
            [
                "7a8a7809.26cf68",
                "20c47d1f.7e57d2"
            ]
        ]
    },
    {
        "id": "4e0e16a3.7cd0f8",
        "type": "array-loop",
        "z": "cbfaf013.0e5e",
        "name": "loop zones",
        "key": "al4e0e16a37cd0f8",
        "keyType": "msg",
        "reset": true,
        "resetValue": "value-null",
        "array": "zones",
        "arrayType": "flow",
        "x": 650,
        "y": 80,
        "wires": [
            [
                "89ebb812.5ea3a8"
            ],
            [
                "a51867e3.622258"
            ]
        ]
    },
    {
        "id": "89ebb812.5ea3a8",
        "type": "debug",
        "z": "cbfaf013.0e5e",
        "name": "All HA Init'd",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 990,
        "y": 80,
        "wires": []
    },
    {
        "id": "a51867e3.622258",
        "type": "switch",
        "z": "cbfaf013.0e5e",
        "name": "",
        "property": "payload",
        "propertyType": "msg",
        "rules": [
            {
                "t": "nnull"
            },
            {
                "t": "else"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 2,
        "x": 430,
        "y": 200,
        "wires": [
            [
                "48744f25.dce7a"
            ],
            [
                "4e0e16a3.7cd0f8"
            ]
        ]
    },
    {
        "id": "3f8da6fc.a7238a",
        "type": "catch",
        "z": "cbfaf013.0e5e",
        "name": "",
        "scope": null,
        "uncaught": false,
        "x": 540,
        "y": 580,
        "wires": [
            [
                "cd788dfe.d978"
            ]
        ]
    },
    {
        "id": "cd788dfe.d978",
        "type": "debug",
        "z": "cbfaf013.0e5e",
        "name": "",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 710,
        "y": 580,
        "wires": []
    },
    {
        "id": "dfd2c176.68a0e",
        "type": "debug",
        "z": "cbfaf013.0e5e",
        "name": "Initialized in HA",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 980,
        "y": 160,
        "wires": []
    },
    {
        "id": "1534a5cd.807cea",
        "type": "mqtt out",
        "z": "cbfaf013.0e5e",
        "name": "",
        "topic": "",
        "qos": "",
        "retain": "",
        "broker": "b1167957.68aa08",
        "x": 930,
        "y": 220,
        "wires": []
    },
    {
        "id": "48744f25.dce7a",
        "type": "function",
        "z": "cbfaf013.0e5e",
        "name": "Build MQTT topic and payload",
        "func": "var topic = \"\"\nvar type = msg.payload[\"type\"]\nvar entity_id = msg.payload[\"entity_id\"]\nvar name = msg.payload[\"name\"]\nvar device_class = msg.payload[\"device_class\"]\nvar state_topic = msg.payload[\"state_topic\"]\nvar config_topic = msg.payload[\"config_topic\"]\nvar payload_on = msg.payload[\"payload_on\"]\nvar payload_off = msg.payload[\"payload_off\"]\nvar config_msg = {\n    \"name\": name,\n    \"device_class\": device_class,\n    \"state_topic\": state_topic,\n    \"payload_on\": payload_on,\n    \"payload_off\": payload_off\n}\n\nmsg.topic = config_topic\n\nmsg.payload = config_msg\nreturn msg",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "x": 690,
        "y": 200,
        "wires": [
            [
                "dfd2c176.68a0e",
                "4e0e16a3.7cd0f8",
                "1534a5cd.807cea"
            ]
        ]
    },
    {
        "id": "7a8a7809.26cf68",
        "type": "mqtt out",
        "z": "cbfaf013.0e5e",
        "name": "",
        "topic": "",
        "qos": "",
        "retain": "",
        "broker": "b1167957.68aa08",
        "x": 710,
        "y": 500,
        "wires": []
    },
    {
        "id": "b1167957.68aa08",
        "type": "mqtt-broker",
        "name": "HA MQTT",
        "broker": "127.0.0.1",
        "port": "1883",
        "clientid": "",
        "usetls": false,
        "compatmode": true,
        "keepalive": "60",
        "cleansession": true,
        "birthTopic": "",
        "birthQos": "0",
        "birthPayload": "",
        "closeTopic": "",
        "closeQos": "0",
        "closePayload": "",
        "willTopic": "",
        "willQos": "0",
        "willPayload": ""
    }
]

If it wasnā€™t nearly 1AM I would be implementing the openssl and/or python solutions :grinning:.

Also, FWIW, I nearly never have to reboot my panel. Every so often node-redā€™s curl would fail (dunno why) so I built in a restart if it fails.

The get status command to the panel will allow the panel to send you every sensor attached to the panel, so that would be one of the first steps in getting it to dynamically populate the sensors.

Yeah I realized that after I spent all that time :upside_down_face:. Thatā€™s OK, I needed it doneā€¦ still figuring out how to listen on the websocket and feed data back to HA. I really like the MQTT sensors, so Iā€™ll probably keep using that scheme.

Iā€™ve made some really great progress using your script as a starting point @mzac. I have the socket open in a thread and can have it output as events happen, as well as feed events in. Itā€™s kinda (very) poorly written right now but I should be able to share it soon. Iā€™ve forked the repo and you can see current progress: https://github.com/roopesh/qolsys_client/tree/separate_socket

I was thinking about incorporating the entire MQTT sensor approach into the python script. Then it could run entirely independent (no more node-red). I make no promises on approach and Iā€™m totally open to feedback. I am separating how data gets published from the panel as a callback function so this could be replaced with however you want to get the data. Hopefully I have something fully functioning and semi-solid by the weekend.

1 Like

This is great! I am really a beginner at Python so any improvements that can be made will be great. If it all works would you like to merge it back and Iā€™ll create another release on Pypi? (Or I could make you a developer?)

BTW, in your MQTT section I would probably add options for username/password to connect to MQTT, and possibly also certificate/encryption options. What do you think?

Hey I just wanted to check in and say that Iā€™ve had literally zero restart or communication problems by using Jasonā€™s original Node Red flow from January 2nd. I know thatā€™s anecdotal and not very helpful but I wonder what the differences are. All Iā€™ve really done is:

  • Update my panelā€™s firmware fully (via Wifi)
  • Enabled the external API access
  • Implemented Jasonā€™s flow according to the directions (which were a little thin but I got there :wink: ). It has been untouched and fully working since then. Even after a few reboots. If there are any communication problems happening Iā€™m not noticing at all.

Thanks again everyone for the awesome work here. Iā€™m trying to find some time to contribute.

So it seems like I might have figured out the arm away function where my panel would count down and then at 0, automatically arm stay.

I primarily use Home Bridge to pipe everything through to Apple home stuff, and when I would run the ā€œarm awayā€ function, I was already out of the house. This is key as I have the security system set to if a door isnā€™t opened after arming away, it will automatically change over to arm stay. To get around this, I added ā€œdelayā€:0 to the command so that it instantly arms away. This works fine for me because when I use that function through Apple Homekit, I am already out of the house, so a door will not open at that point. If someone doesnā€™t have that setting enabled in the alarm panel, then I donā€™t think my same issue would apply to everyone, however I donā€™t remember if that is a customizable feature in the alarm or not.

One issue that I need to work out, I noticed it when my house cleaner left the other day, she armed the house through the panel, and Homekit immediately locked the door. lol. So I will need to do an arm away and see what the message is passed from the panel when it starts the countdown and what it says when the countdown is complete and adjust my flow accordingly.

Good catch. That option is enabled by default. Itā€™s under Advanced Settings, Installation, Security and Arming, Auto Stay.

Ahh, that makes sense. Something to keep in mind if someone is trying to send an arm command to the panel. Any time that I would want to send an arm command to my panel from outside the Qolsys I donā€™t need the delay as Iā€™m already out of the house.

@mzac I havenā€™t worried about MQTT security yet. Maybe in the future.

@Smwoodward I canā€™t seem to disarm the alarm via the socket. Have you been able to do it? If so, can you post your json message?

This works for me:

{
    "version": 1,
    "source": "C4",
    "partition_id": 0,
    "action": "ARMING",
    "arming_type": "DISARM",
    "nonce": "",
    "usercode": "xxxx",
    "token": "xxxxxx"
}

Took a few tries to get it right, and if you donā€™t get it right the panel doesnā€™t do anything, probably for security reasons. No error message or anything.

Thanks @dcaton. The usercode was missing :slight_smile: .

I can have the thread running listening to events quite easily now. With so much running in Python now, though, Iā€™m feeling like I need to make this run in AppDaemon otherwise Iā€™m not sure how/where itā€™ll run.

I just committed a basically working version of the python app. However, thereā€™s no documentation yet.

Anyone here familiar enough with AppDaemon to run the python app in there?

Thanks for all your work on this so far, looking very promising. Is anybody woking on a installable plugin? Or are we just experimenting at this time?

Not sure what you mean by installable plugin-in. Since so much of the logic sits in python and I now have it fully functioning, I am going to try diving into AppDaemon to see if I can get it working there.

FYI there have been big changes to the python module on Pypi, we just pushed release 0.1.0 thanks to @crazeeeyez !!


3 Likes