SIEGENIA Comfort App Automatic Door KFV

Hello Guys,

i have an new Door for my house with an full electrical lock.
It works with an Siegenia Fingerscanne which is in my Wifi Network.
All information like, door is open, door is closed and also the possibility to lock the door is via app possible.

It would be great to integrade the app, or the Information of the app to HA.

Is there a possibility to add the cloud to HA?

Thank you

BR
Jim

any news?

Greetings

Did somebody find already a solution?

I have as well a SIEGNIA electrical lock integrated in the door. Did anybody managed to integrate it to HA?

I guess no news here.
But please. If anybody find a solution please post it here.

Thank you!

Hi, I just finished working on this. It’s a bit unpolished and also tested only on my setup but it works.

https://github.com/jakubvf/siegenia_door

1 Like

Hey Jakub,
sounds great!

I will try it asap.

Do you have some screenshots of this integration?`
What is possible to do or see? Can you open/lock the door aswell, or only the staus is visible?

Iam very curious :slight_smile:

BR
Jim

Jim,

here is a screenshot.

It uses Home Assistent’s Lock entity. It’s got the ability to open, lock and unlock the door plus you can see the current status.

Locking/unlocking is handled by switching the Day and Night mode (that’s what they call it inside the app).

The itegreation uses WiFi to communicate with the door. So you need the door connected to your LAN and you need to know your door’s IP address.

Every action takes about 5 - 10 seconds to propagate and the open/closed sensor is also not instant. I bet there is some way to get around it but I haven’t bothered yet as I use it mainly to check if the door is open when leaving.

The code needs some cleaning up and I’d like to make it available through HACS, I just haven’t gotten around to doing it yet.
Some features like HA auto-discovery or user management are also doable, but I’m not sure if it’s worth the effort.

Also, it’s literally only tested on my two doors and I wouldn’t be surprised if it doesn’t work for some other models.

1 Like

I have updated the repository with installation instructions using HACS.

1 Like

Great news!

Just tryed to integrate the solution.
But after restart there js no integration visible :frowning:
And also under custom repositorys the entry is deleted after the reboot.

Any idea?


Okay finally i managed it :slight_smile:

If there is closed without looking is not visible right?
Only if the lock is open or closed.

What kind of automations you use?

To be honest, for me the staus door open or closed is even more important than the possibility to lock and unlock the lock :slight_smile: :grimacing:

Thanks for your work!

Hey, sorry I couldn’t help with your issue and I’m glad you managed to fix it. I didn’t get a notification :face_with_raised_eyebrow:.


So your question is whether there is an indicator that the door is closed, right?

Well, the exposed lock entity has three states: locked, closed and open.

Meaning if you change the state of the door (ie. open, close, change day/night mode in Siegenia app) the lock entity should reflect that.

So I guess the anwser to your question is that you can tell that the door is open.
(That is if everything is working as intended :smiley:)


Entity filter

My main use-case is showing the door status on a dashboard, so that I’m able if everything is ready to start the alarm when leaving.

Here’s a entity filter card that shows what windows/door are open. Currently there is a single window open.

Here’s the code for entity filter card that shows two doors, only if they are open:

type: entity-filter
entities:
  - entity: lock.sf_lock_status
    state_filter:
      - operator: '=='
        value: open
  - entity: lock.sf_lock_status_2
    state_filter:
      - operator: '=='
        value: open

Tile card

And I also use a tile card to control the door:
Here’s the code for that one:

type: tile
entity: lock.lock_status_2
hide_state: false

Automations

I use a simple automation that locks the door when the alarm is set.
And two more autoamtions to lock the door at night and unlock in the morning.


I hope I answered your questions, if not, I’m happy to provide further explenations. :wink:

PS: I wanted to give more pictures but the forum won’t let me :frowning_face:

@jakubvf thanks for the Integration. Works Fine!
Would it also possible to integrale the Logs from the finger print sensor? they could be used e.g. for automations when coming home? In the logs is written who has opened the door.

@Teschi2494 sure, sounds useful.

If you or anyone else has any more feature requests, I’m happy to look into it :grinning:.

But please don’t expect anything soon. I hope to find some time this summer.

1 Like

if you can share some knowledge regarding the logic and the commands or where you got the available commands from, I can also start on my own and share

@jakubvf I found the solution.
There is a repository in github, where the protocols are already extracted:

There is a command called “getProtocolData” with parameter, which asks for the protocol. From my test I found out, that the logged in user needs to be an admin user.
And in the “test.py” file the counter needs to be set initially to 2 for the login.

The received protocol is in a base64 string, which is decoded in decode2.py
To get the last person, that tried to open the door the lines 69-88 in this file are relevant. The others are other logs, like logins etc.

From this information a home assistant sensor would be good, which is updated e.g. by a service/action, which we could trigger externally. E.g. by a door contact.
Or the door itself, from your integration?

I may have some time this February to implement this.

Although I’m not sure what entity type should I use. I suppose an Event entity makes sense here - letting the user handle the event through an automation.
Or maybe a Sensor entity - just providing a string “Person x unlocked door y.”?

What do you think? And thank you for figuring this out, @Teschi2494!

I now did it the following way, which works for me quite good. I created a python script, which returns me the last successfull authorization from the fingerprint sensor with timestamp and name as a json.
looks like this:
image

I execute the script with a trigger frome Homeassistant in node red, because It was quicker for me, than create a new integration in home assistant :smiley:
then I use this response to fill my own sensor for home assistant which looks like this, with several attributes:

So for me the workflow makes sense as a custom action, which you can trigger on your own, e.g. when the door opens and check, if the opening was authorized or not.

this is my node red flow

[{"id":"23ce548110d95931","type":"exec","z":"97468294216ecab8","command":"python3 /home/SiegeniaTest.py","addpay":"","append":"","useSpawn":"false","timer":"","winHide":false,"oldrc":false,"name":"","x":650,"y":160,"wires":[["c7f418c748c51663","2d9498b6a66ebd21"],["c7f418c748c51663"],["c7f418c748c51663"]]},{"id":"edddd3782e211607","type":"inject","z":"97468294216ecab8","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":260,"y":140,"wires":[["23ce548110d95931"]]},{"id":"c7f418c748c51663","type":"debug","z":"97468294216ecab8","name":"debug 3","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1040,"y":140,"wires":[]},{"id":"2a22c6df87c6011d","type":"ha-sensor","z":"97468294216ecab8","name":"SiegeniaDoorLog","entityConfig":"6e58dfb6d8e68e89","version":0,"state":"payload.state","stateType":"msg","attributes":[{"property":"time","value":"payload.time","valueType":"msg"},{"property":"name","value":"payload.name","valueType":"msg"},{"property":"lastupdate","value":"","valueType":"date"}],"inputOverride":"allow","outputProperties":[],"x":990,"y":260,"wires":[[]]},{"id":"2d9498b6a66ebd21","type":"function","z":"97468294216ecab8","name":"function 1","func":"// Parse the input string (assuming msg.payload contains the input string)\nlet inputString = msg.payload;\n\n// Convert the string into an object\nlet data = JSON.parse(\n inputString.replace(/'/g, '\"') // Replace single quotes with double quotes for valid JSON\n);\n\n// Create the new payload\nmsg.payload = {\n state: inputString,\n time: data.time,\n name: data.message\n};\n\n// Return the modified message\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":760,"y":260,"wires":[["eef6df223ff940fd","2a22c6df87c6011d"]]},{"id":"eef6df223ff940fd","type":"debug","z":"97468294216ecab8","name":"debug 14","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":940,"y":320,"wires":[]},{"id":"c99097a1daf883ba","type":"server-state-changed","z":"97468294216ecab8","name":"","server":"e1cef92d.75ece8","version":6,"outputs":1,"exposeAsEntityConfig":"","entities":{"entity":["input_boolean.updatesiegenialog"],"substring":[],"regex":[]},"outputInitially":false,"stateType":"str","ifState":"","ifStateType":"str","ifStateOperator":"is","outputOnlyOnStateChange":true,"for":"0","forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[],"x":360,"y":420,"wires":[["23ce548110d95931"]]},{"id":"6e58dfb6d8e68e89","type":"ha-entity-config","server":"e1cef92d.75ece8","deviceConfig":"","name":"SiegeniaDoorLog","version":6,"entityType":"sensor","haConfig":[{"property":"name","value":"SiegeniaDoorLog"},{"property":"icon","value":"mdi:log"},{"property":"entity_picture","value":""},{"property":"entity_category","value":""},{"property":"device_class","value":""},{"property":"unit_of_measurement","value":""},{"property":"state_class","value":""}],"resend":false,"debugEnabled":false},{"id":"e1cef92d.75ece8","type":"server","name":"Home Assistant","version":5,"addon":false,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true,"heartbeat":false,"heartbeatInterval":30,"areaSelector":"friendlyName","deviceSelector":"friendlyName","entitySelector":"friendlyName","statusSeparator":"at: ","statusYear":"hidden","statusMonth":"short","statusDay":"numeric","statusHourCycle":"h23","statusTimeFormat":"h:m","enableGlobalContextStore":true}]

Thats the final python script:

import struct
import datetime
import base64


class Buffer:
    def __init__(self, buf: bytearray):
        self.buf = buf
        # self.buf.reverse()

    def getShort(self):
        tmp = self.buf[:2]
        self.buf = self.buf[2:]
        integer = struct.unpack('>H', tmp)
        return integer[0]

    def getInt(self):
        tmp = self.buf[:4]
        self.buf = self.buf[4:]
        integer = struct.unpack('>I', tmp)
        return integer[0]

    def getByte(self):
        tmp = self.buf[0]
        self.buf = self.buf[1:]
        return tmp

    def advance(self, n: int):
        self.buf = self.buf[n:]

    def isEmpty(self):
        return len(self.buf) == 0


class ProtocollDecode1:

    def __init__(self, userdict: dict):
        self.userdict = userdict

        self.userType = {0: "Other", 1: "User", 2: "Admin"}

    def getAcsState(self, b):
        if b == 1:
            return "abgelehnt"
        if b == 2:
            return "erfolgreich"
        if b == 3:
            return "Keyless wartet auf zweites Merkmal"

        return "kein Match zu prĂĽfen"

    def getUser(self, ID):
        if ID in self.userdict:
            return self.userdict[ID]
        else:
            return "Unkown User"

    def decode(self, coded_string):
        buffer = Buffer(bytearray(base64.b64decode(coded_string)))

        entries = []
        while not buffer.isEmpty():

            buffer.getShort()
            time = buffer.getInt()
            #time = datetime.datetime.fromtimestamp(time)

            # Convert to datetime
            original_time = datetime.datetime.fromtimestamp(time)

            # Subtract one hour
            adjusted_time = original_time - datetime.timedelta(hours=1)

            #print(time,end = ' ')
            v1 = buffer.getByte()
            v2 = buffer.getByte()

            if v1 == 1:
                user = self.getUser(buffer.getShort())
                short12 = buffer.getShort()
                value13 = buffer.getByte()
                value14 = buffer.getByte()
                b = value14 >> 4
                b2 = value14 & 0xF
                value15 = buffer.getByte()

                #print("value15: " + str(value15))

                acsState = self.getAcsState(value15)

                state = "Aus"
                if b2 == 1:
                    state = "An"

                format_string = "Zutrittsversuch durch Benutzer \"%s\" mit Merkmal-ID %d (Typ: %d). Matchergebnis: %s. Keyless %s, Sicherheitsstufe: %d." % (
                    user, short12, value13, acsState, state, b)
                #print(format_string)

                time_entry = adjusted_time.strftime('%Y-%m-%d %H:%M:%S')
                name = user

                # Append a dictionary to the list
                if name != "Unkown User":
                    entries.append({"time": time_entry, "message": name})




            elif v1 == 2:

                user = self.getUser(buffer.getShort())
                value11 = int(buffer.getByte())
                value12 = int(buffer.getByte())
                format_string = "Neuer Benutzer \"%s\" angelegt. Status: %s. Ergebnis %d." % (user, hex(value11), value12)
                # print(format_string)

            elif v1 == 3:

                user = self.getUser(buffer.getShort())
                value9 = buffer.getByte()
                result = buffer.getByte()
                format_string = "Benutzer \"%s\" geändert. Status: %s. Ergebnis %s." % (user, hex(value9), result)
                #print(format_string)

            elif v1 == 4:
                user = self.getUser(buffer.getShort())
                format_string = "Benutzer \"%s\" gelöscht." % user
                #print(format_string)

            elif v1 == 5:

                user = self.getUser(buffer.getShort())
                short7 = int(buffer.getShort())
                value8 = int(buffer.getByte())

                format_string = "Benutzer \"%s\" legt neues Zutrittsmerkmal mit Merkmal-ID %s (Typ: %s) an." % (user, short7, value8)
                #print(format_string)

            elif v1 == 6:

                ID = int(buffer.getShort())

                format_string = "Zutrittsmerkmal mit ID %s wurde gelöscht." % ID
                #print(format_string)

            elif v1 == 7:
                tmp = ""
                short4 = buffer.getShort()
                value6 = buffer.getByte()
                value7 = buffer.getByte()
                if value7 == 0:
                    tmp = "Other"

                elif value7 == 1:
                    tmp = "User"

                elif value7 == 2:
                    tmp = "Admin"
                else:
                    tmp = "unknown"

                format_string = "App-Login des Benutzers \"%s\". Status: %s. Level: %s." % (self.getUser(short4), hex(value6), tmp)

                #print(format_string)

            elif v1 == 8:

                value4 = int(buffer.getByte())
                value5 = int(buffer.getByte())

                format_string = "Ă„nderung der Keyless-Sicherheits-Stufe. Alt: %s. Neu: %s." % (value4, value5)
                #print(format_string)

            elif v1 == 9:

                user0 = self.getUser(buffer.getShort())

                user1 = self.getUser(buffer.getShort())

                result = str(buffer.getByte())

                format_string = "Benutzer \"%s\" ändert das Passwort von Benutzer \"%s\". Ergebnis: %s." % (

                    user0, user1, result)

                #print(format_string)

            elif v1 == 10:

                user = self.getUser(buffer.getShort())

                format_string = "App-Logout des Benutzers \"%s\"." % (user)

                #print(format_string)


            else:
                print("Error while parsing")

            buffer.advance(8 - v2)

        return entries


    def getLastEntries(self, coded_string):
        buffer = Buffer(bytearray(base64.b64decode(coded_string)))
        entries = []
        while not buffer.isEmpty():

            buffer.getShort()
            time = buffer.getInt()
            time = datetime.datetime.fromtimestamp(time)
            # print(time,end = ' ')
            v1 = buffer.getByte()
            v2 = buffer.getByte()

            if v1 == 1:
                user = self.getUser(buffer.getShort())
                short12 = buffer.getShort()
                value13 = buffer.getByte()
                value14 = buffer.getByte()
                b = value14 >> 4
                b2 = value14 & 0xF
                value15 = buffer.getByte()

                # print("value15: " + str(value15))

                acsState = self.getAcsState(value15)

                state = "Aus"
                if b2 == 1:
                    state = "An"

                format_string = "Zutrittsversuch durch Benutzer \"%s\" mit Merkmal-ID %d (Typ: %d). Matchergebnis: %s. Keyless %s, Sicherheitsstufe: %d." % (
                    user, short12, value13, acsState, state, b)
                # print(format_string)

                time_entry = time.strftime('%Y-%m-%d %H:%M:%S')
                name = user

                # Append a dictionary to the list
                entries.append({"time": time_entry, "message": name})



import json
import asyncio
import pathlib
import ssl
import websockets

ssl_context_c = ssl.SSLContext()
ssl_context_c.check_hostname = False
ssl_context_c.verify_mode = ssl.CERT_NONE


userdict = {}

async def getLastEntry():
    uri = "wss://yourDoorIpAddress:443/WebSocket"
    async with websockets.connect(
            uri, ssl=ssl_context_c
    ) as websocket:

        counter = 2
        login = {"command": "login",
                 "user": "xxxx",
                 "password": "xxxx",
                 "long_life": False,
                 "id": counter}

        counter+=1

        getUserIDs = {"command": "getUserIds",
                   "id": counter}

        counter+=1

        json_object = json.dumps(login)
        await websocket.send(json_object)
        answer = await websocket.recv()

        json_object = json.dumps(getUserIDs)
        await websocket.send(json_object)
        answer = await websocket.recv()
        userids = (json.loads(answer)["data"]["userids"])

        for id in userids :
            getUserID = {"command": "getUser", "params": {"userid": id}, "id": counter}
            counter += 1
            json_object = json.dumps(getUserID)
            await websocket.send(json_object)
            answer = await websocket.recv()
            data = json.loads(answer)["data"]
            name = data["userdetails"]["username"]
            userdict[id] = name

        getProtocol = {"command":"getProtocolData","params":{"deviceid":9,"protocoltype":1},"id":counter}
        counter += 1
        json_object = json.dumps(getProtocol)
        await websocket.send(json_object)
        answer = await websocket.recv()
        data = json.loads(answer)["data"]
        protocoldata = data["protocoldata"]

        decoder = ProtocollDecode1(userdict)
        entries = decoder.decode(protocoldata)
        #print(protocoldata)

        #print(entries)

        lastEntry = entries[0]

        print(lastEntry)
        return lastEntry





asyncio.get_event_loop().run_until_complete(getLastEntry())