uhppoted / uhppoted-python

PyPI package for the UHPPOTED Python API
MIT License
5 stars 0 forks source link

how to get data (id card) when swipe card #6

Closed NguyenDucQuan12 closed 5 months ago

NguyenDucQuan12 commented 5 months ago

hello, I'm currently using controller and I want to get card id every time I swipe the card using python, I wandered around, until I came across this project, it's amazing. There is almost no documentation on this issue, can you give me some instructions so that I can get the id every time I swipe the card through this controller. Thank you

uhppoted commented 5 months ago

Hi,

Hmm, I see the listen function isn't documented - I'll make a note to fix that. There is a sample in the example program but actually it's fairly straightforward:

e.g.:

import ipaddress

from uhppoted import uhppote
from pprint import pprint

def main():
    controller = 405419896
    host_addr = ipaddress.IPv4Address('192.168.1.100')
    host_port = 60001

    bind_addr = '0.0.0.0'
    broadcast_addr = '255.255.255.255:60000'
    listen_addr = '0.0.0.0:60001'
    debug = True

    try:
        u = uhppote.Uhppote(bind_addr, broadcast_addr, listen_addr, debug)

        set_listener(u,controller, host_addr, host_port)
        listen(u)

    except Exception as x:
        print()
        print(f'*** ERROR  {x}')
        print()

def set_listener(u, controller, address, port):
    print('-- set-listener')

    response = u.set_listener(controller, address, port)

    print('  ', response)
    print()

def listen(u):
    print('-- listening for events')
    u.listen(onEvent)

def onEvent(event):
    if event != None:
        pprint(event.__dict__, indent=2, width=1)

if __name__ == '__main__':
    main()

If you edit that to set host_addr to the IP address of your machine, run it and swipe a card you should get something like:

python3 main.py

-- set-listener
   00000000  17 90 00 00 78 37 2a 18  c0 a8 01 64 61 ea 00 00
   00000010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
   00000020  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
   00000030  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00

   00000000  17 90 00 00 78 37 2a 18  01 00 00 00 00 00 00 00
   00000010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
   00000020  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
   00000030  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00

   SetListenerResponse(controller=405419896, ok=True)

-- listening for events
   00000000  17 20 00 00 78 37 2a 18  46 00 00 00 01 00 03 01
   00000010  a0 7a 99 00 20 24 04 02  19 14 07 12 00 00 00 00
   00000020  00 00 00 00 00 19 14 07  00 00 00 00 00 00 00 00
   00000030  00 00 00 24 04 02 00 00  00 00 00 00 00 00 00 00

{ 'controller': 405419896,
  'door_1_button': False,
  'door_1_open': False,
  'door_2_button': False,
  'door_2_open': False,
  'door_3_button': False,
  'door_3_open': False,
  'door_4_button': False,
  'door_4_open': False,
  'event_access_granted': False,
  'event_card': 10058400,
  'event_direction': 1,
  'event_door': 3,
  'event_index': 70,
  'event_reason': 18,
  'event_timestamp': datetime.datetime(2024, 4, 2, 19, 14, 7),
  'event_type': 1,
  'inputs': 0,
  'relays': 0,
  'sequence_no': 0,
  'special_info': 0,
  'system_date': datetime.date(2024, 4, 2),
  'system_error': 0,
  'system_time': datetime.time(19, 14, 7)}
NguyenDucQuan12 commented 5 months ago

@uhppoted when I change controller and host_addr to my address, I swipe the card but it doesn't return any numbers. When I tried swiping the card with access control software, I still got the result (so I can eliminate the line connection problem). I don't know if I did any wrong steps. image image

NguyenDucQuan12 commented 5 months ago

I want to add more information to avoid unwanted situations: controller: acb-004 (s4a) card reader: idteck ip10 (connect wiegand 26) python: 3.11.8 I connect the controller to the switch and connect from the switch to the computer

uhppoted commented 5 months ago

Hmm - the fact that you got a valid reply back for the set-listener command says your network setup is ok and the card reader is clearly working.

Are you by any chance running the access control software and the Python script at the same time? And do you possibly have an active breakpoint set on line 37?

NguyenDucQuan12 commented 5 months ago

I can set a breakpoint at line 37, I also turned off the access control software to try again, but still nothing appears when I swipe the card image this is code:

import ipaddress
from uhppoted import uhppote
from pprint import pprint

def main():
    controller = 423138650
    host_addr = ipaddress.IPv4Address('169.254.184.123')
    host_port = 60001

    bind_addr = '0.0.0.0'
    broadcast_addr = '255.255.255.255:60000'
    listen_addr = '0.0.0.0:60001'
    debug = True

    try:
        u = uhppote.Uhppote(bind_addr, broadcast_addr, listen_addr, debug)

        set_listener(u,controller, host_addr, host_port)
        listen(u)

    except Exception as x:
        print()
        print(f'*** ERROR  {x}')
        print()

def set_listener(u, controller, address, port):
    print('-- set-listener')

    response = u.set_listener(controller, address, port)

    print('  ', response)
    print()

def listen(u):
    print('-- listening for events')
    u.listen(onEvent)

def onEvent(event):
    if event != None:
        pprint(event.__dict__, indent=2, width=1)
    else:
        print("nodata")

if __name__ == '__main__':
    main()
uhppoted commented 5 months ago

Ok:

Can you perhaps post your network configuration? And just to confirm - you are running directly on the host machine with IP address 169.254.184.123 (and not e.g. in a VM or Docker container)?

Some other things you can try:

  1. Change the listen_addr to 169.254.184.123:60001 so that it is definitely binding to the correct IP.

  2. You could also try downloading the CLI and running the following commands:

    uhppote-cli --debug set-listener 423138650 169.254.184.123:60001
    uhppote-cli --debug get-listener 423138650
    uhppote-cli --debug listen
  3. If that doesn't work then the next step is get Wireshark traces on the UDP packets to and from the controller.

(BTW it's late this side of the planet so I'll only be able to follow up tomorrow)

NguyenDucQuan12 commented 5 months ago

@uhppoted sorry, the ip address I passed in was the controller's ip, I changed it to the computer's ip address and it worked. Really sorry for my confusion, have a good night's sleep

uhppoted commented 5 months ago

Ah, yes! I was a bit puzzled .. I need to make a drawing or something that explains it better than words because a couple of people have done that.

Great! Very glad you're up and running :-).

uhppoted commented 5 months ago

Hi,

I've added the missing listen API function to the README. The description is a bit brief but I've also added an event-listener example based on the code above which should hopefully make it clear for anybody else who runs into the same problem.

Going to close this one out - but feel free to reopen it if you need to :-).

NguyenDucQuan12 commented 3 months ago

@uhppoted Hello, I am dealing with a situation where after 2 minutes there is no card swipe event, then call the gc.collect function. However, it seems that the statement after else is never executed, I tested it. but no results were printed.

def onEvent(self,event):
        # Nếu có sự kiện quẹt thẻ thì event nhận giá trị
        if event:
            self.time_start = time.time()
            # 2 lệnh dưới sẽ in ra toàn bộ thông tin có trong thẻ
            # pprint(event.__dict__, indent=2, width=1)
            # print(event.event_card)

            # Đây là sử dụng luồng, nếu số lượng luồng mở quá nhiều thì chương trình tự đóng, vì vậy dùng semaphore để giới hạn luồng được mở
            # Không dùng join để đợi luồng này, bởi như vậy sẽ làm delay các quá trình còn lại, không giải quyết được vấn đề vượt CPU
            if semaphore.acquire(blocking=False):
                get_license_in_thread = threading.Thread(target=self.function, args=(event.event_card,))
                get_license_in_thread.start()
                # Chờ luồng kết thúc, sử dụng join()
                # get_license_in_thread.join()
                self.id_card = event.event_card

            # Đây là không sử dụng luồng, nếu CPU>20% gây ra hiện tượng lag ( quẹt thẻ với tần suất 2s cho 1 lần quẹt)
            # self.function(event.event_card)
            # self.id_card = event.event_card
        else:
            current_time = time.time()
            elapsed_time = current_time - self.time_start
            print(current_time)
            # Trong vòng 2 phút nếu không có sự kiện quẹt thẻ nào cả thì dọn các file rác và hiển thị quảng cáo
            if elapsed_time >= 120:
                print(current_time)
                print(self.time_start)
                gc.collect()
                print("đã dọn bộ nhớ")
                self.time_start = time.time()
uhppoted commented 3 months ago

Hi,

I'd be very surprised if event is ever None because the onEvent handler is only called when an event is received - which means the else block is never going to be executed.

In my code I check for event == None because it avoids a crash if (for example) there is a mistake in some other code - it is not an expected condition.

NguyenDucQuan12 commented 3 months ago

@uhppoted so is there any other way I can do the time calculation without the card swipe event? For example, how long has it been in the value of event = None? I'm trying to call gc.collect() every once in a while, because I'm multi-threaded so calling gc.collect() too often causes unexpected problems.

uhppoted commented 3 months ago

Personally I would just start a separate thread in the main function to do garbage collection every 2 minutes.

Although TBH I'm a bit puzzled as to why you are doing garbage collection at all?

NguyenDucQuan12 commented 3 months ago

@uhppoted I am integrating object detection and OCR every time there is a card swipe event, however after each object detection +OCR, those 2 models do not automatically return the used resources, every time. It increased to 50MB of RAM again, after 300 card swipes it increased to 250MB of RAM. So I need to clean up the trash after there are no swipe events at all. To make sure that there is no situation where someone is swiping the card, gc.collect() also collects garbage and causes the program to error. So I have to know when event = None will allow me to collect the garbage

uhppoted commented 3 months ago

Huh! Very interesting!

In that situation I would typically have a separate thread to do garbage collection and something like a shared counter - when onEvent sees a card swipe it e.g. increments the counter. The thread would periodically wake up and decrement the counter and if the counter was zero then do a gc. There are lots of other ways to do it - but I'm afraid doing it in the else block isn't going to work because event == None is only going to happen if there is a serious error in the controller (so basically never).

NguyenDucQuan12 commented 3 months ago

@uhppoted thank you so much

uhppoted commented 3 months ago

Pleasure! And good luck!

NguyenDucQuan12 commented 3 months ago

@uhppoted Can I limit the time to receive the id from the magnetic card, for example, after 2 seconds before I can swipe the card? Because if I intentionally swipe the card continuously (less than 1 second), it will cause an unexpected error, so I want to limit the time I can swipe the card to 2 seconds for each swipe.

uhppoted commented 3 months ago

What is the unexpected error you are getting?

NguyenDucQuan12 commented 3 months ago

@uhppoted Every time I swipe my card, I will open a new thread to perform tasks. However, if I swipe the card too quickly, many open threads will cause conflicts in the parameters in that thread. I don't have much experience. so I don't know how to handle it well

uhppoted commented 3 months ago

Oh .. ok, what you want to do is use a work queue:

  1. In the onEvent handler, when you receive a swipe event you add it to a FIFO queue.
  2. In a separate thread you have a loop that waits for the FIFO queue to be not empty - then while there is a swipe event in the queue, remove the event and process it and repeat until the queue is empty, at which point the thread goes back to sleep and waits for the queue to be not empty again.
  3. If the queue gets too large (e.g. 16 pending swipes) then you can either discard the event in the onEvent handler (and log a warning message) or discard it in the processing thread (and log a warning message).

To start with, the processing thread can just sleep in a loop and when it wakes up check the queue for a swipe event. Once you've got that working with just one thread you can get more sophisticated (e.g. wait for a signal/event) and/or use multiple processing threads. If you have e.g. 4 cores in your machine then you could run 4 processing threads.

You'll also need to use mutexes/semaphores to guard the queue but you can read up on that if you need to.

NguyenDucQuan12 commented 3 months ago

@uhppoted Thank you, I will try it immediately

uhppoted commented 3 months ago

Interestingly, if you use Python's queue.get() you can do the gc.collection on timeout very easily!

(it's also threadsafe so you don't need to have external mutexes/semaphores).