rbzk 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/rbzk/zk.rb CHANGED
@@ -5,7 +5,7 @@ require 'timeout'
5
5
  require 'date'
6
6
 
7
7
  module RBZK
8
- # Helper class for ZK (like Python's ZK_helper)
8
+ # Helper class for ZK
9
9
  class ZKHelper
10
10
  def initialize(ip, port = 4370)
11
11
  @ip = ip
@@ -14,7 +14,6 @@ module RBZK
14
14
  end
15
15
 
16
16
  def test_ping
17
- # Like Python's test_ping
18
17
  begin
19
18
  system("ping -c 1 -W 5 #{@ip} > /dev/null 2>&1")
20
19
  return $?.success?
@@ -24,34 +23,23 @@ module RBZK
24
23
  end
25
24
 
26
25
  def test_tcp
27
- # Match Python's test_tcp method exactly
28
26
  begin
29
- # Create socket like Python's socket(AF_INET, SOCK_STREAM)
30
27
  client = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM)
31
-
32
- # Set timeout like Python's settimeout(10)
33
28
  client.setsockopt(Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, [ 10, 0 ].pack('l_*'))
34
29
 
35
- # Use connect_ex like Python (returns error code instead of raising exception)
36
30
  sockaddr = Socket.pack_sockaddr_in(@port, @ip)
37
31
  begin
38
32
  client.connect(sockaddr)
39
- result = 0 # Success, like Python's connect_ex returns 0 on success
33
+ result = 0 # Success code
40
34
  rescue Errno::EISCONN
41
- # Already connected
42
- result = 0
35
+ result = 0 # Already connected
43
36
  rescue => e
44
- # Connection failed, return error code
45
- result = e.errno || 1
37
+ result = e.errno || 1 # Connection failed
46
38
  end
47
39
 
48
- # Close socket
49
40
  client.close
50
-
51
- # Return result code (0 = success, non-zero = error)
52
41
  return result
53
42
  rescue => e
54
- # Something went wrong with socket creation
55
43
  return e.errno || 1
56
44
  end
57
45
  end
@@ -61,7 +49,7 @@ module RBZK
61
49
  include RBZK::Constants
62
50
 
63
51
  def initialize(ip, port: 4370, timeout: 60, password: 0, force_udp: false, omit_ping: false, verbose: false, encoding: 'UTF-8')
64
- # Match Python's __init__ method
52
+ # Initialize the ZK device connection
65
53
  RBZK::User.encoding = encoding
66
54
  @address = [ ip, port ]
67
55
 
@@ -74,13 +62,10 @@ module RBZK
74
62
  @verbose = verbose
75
63
  @encoding = encoding
76
64
 
77
- # Set TCP mode based on force_udp (like Python's self.tcp = not force_udp)
78
65
  @tcp = !force_udp
79
-
80
- # Socket will be created during connect
81
66
  @socket = nil
82
67
 
83
- # Initialize session variables (like Python)
68
+ # Initialize session variables
84
69
  @session_id = 0
85
70
  @reply_id = USHRT_MAX - 1
86
71
  @data_recv = nil
@@ -94,7 +79,7 @@ module RBZK
94
79
  @fingers = {}
95
80
  @tcp_header_size = 8
96
81
 
97
- # Initialize device info variables (like Python)
82
+ # Initialize device info variables
98
83
  @users = 0
99
84
  @fingers = 0
100
85
  @records = 0
@@ -118,60 +103,44 @@ module RBZK
118
103
  end
119
104
  end
120
105
 
121
- # Remove this method as we now use the helper class
122
- # def test_tcp
123
- # end
124
-
125
106
  def connect
126
- # Match Python's connect method
127
107
  return self if @connected
128
108
 
129
- # Skip ping check if requested (like Python's ommit_ping)
109
+ # Skip ping check if requested
130
110
  if !@omit_ping && !@helper.test_ping
131
111
  raise RBZK::ZKNetworkError, "Can't reach device (ping #{@ip})"
132
112
  end
133
113
 
134
- # Test TCP connection (like Python's connect)
114
+ # Set user packet size if TCP connection is available
135
115
  if !@force_udp && @helper.test_tcp == 0
136
- # Default user packet size for ZK8
137
- @user_packet_size = 72
116
+ @user_packet_size = 72 # Default for ZK8
138
117
  end
139
118
 
140
- # Create socket (like Python's __create_socket)
141
119
  create_socket
142
120
 
143
- # Reset session variables (like Python's connect)
121
+ # Reset session variables
144
122
  @session_id = 0
145
123
  @reply_id = USHRT_MAX - 1
146
124
 
147
- # Send connect command (like Python's connect)
148
125
  if @verbose
149
126
  puts "Sending connect command to device"
150
127
  end
151
128
 
152
129
  begin
153
- # Send connect command (like Python's connect)
154
- # In Python: cmd_response = self.__send_command(const.CMD_CONNECT)
155
- # No command string is needed for the connect command
156
130
  cmd_response = send_command(CMD_CONNECT)
157
-
158
- # Update session ID from header (like Python's connect)
159
131
  @session_id = @header[2]
160
132
 
161
- # Authenticate if needed (like Python's connect)
133
+ # Authenticate if needed
162
134
  if cmd_response[:code] == CMD_ACK_UNAUTH
163
135
  if @verbose
164
136
  puts "try auth"
165
137
  end
166
138
 
167
- # Create auth command string (like Python's make_commkey)
168
139
  command_string = make_commkey(@password, @session_id)
169
-
170
- # Send auth command
171
140
  cmd_response = send_command(CMD_AUTH, command_string)
172
141
  end
173
142
 
174
- # Check response status (like Python's connect)
143
+ # Check response status
175
144
  if cmd_response[:status]
176
145
  @connected = true
177
146
  return self
@@ -221,63 +190,29 @@ module RBZK
221
190
  end
222
191
 
223
192
  def disable_device
224
- # Match Python's disable_device method exactly
225
- # In Python:
226
- # def disable_device(self):
227
- # cmd_response = self.__send_command(const.CMD_DISABLEDEVICE)
228
- # if cmd_response.get('status'):
229
- # self.is_enabled = False
230
- # return True
231
- # else:
232
- # raise ZKErrorResponse("Can't disable device")
233
-
234
193
  cmd_response = self.send_command(CMD_DISABLEDEVICE)
235
194
  if cmd_response[:status]
236
195
  @is_enabled = false
237
- return true
196
+ true
238
197
  else
239
198
  raise RBZK::ZKErrorResponse, "Can't disable device"
240
199
  end
241
200
  end
242
201
 
243
202
  def get_firmware_version
244
- # Match Python's get_firmware_version method exactly
245
- # In Python:
246
- # def get_firmware_version(self):
247
- # cmd_response = self.__send_command(const.CMD_GET_VERSION,b'', 1024)
248
- # if cmd_response.get('status'):
249
- # firmware_version = self.__data.split(b'\x00')[0]
250
- # return firmware_version.decode()
251
- # else:
252
- # raise ZKErrorResponse("Can't read frimware version")
253
-
254
203
  command = CMD_GET_VERSION
255
204
  response_size = 1024
256
205
  response = self.send_command(command, "", response_size)
257
206
 
258
207
  if response && response[:status]
259
208
  firmware_version = @data.split("\x00")[0]
260
- return firmware_version.to_s
209
+ firmware_version.to_s
261
210
  else
262
211
  raise RBZK::ZKErrorResponse, "Can't read firmware version"
263
212
  end
264
213
  end
265
214
 
266
215
  def get_serialnumber
267
- # Match Python's get_serialnumber method exactly
268
- # In Python:
269
- # def get_serialnumber(self):
270
- # command = const.CMD_OPTIONS_RRQ
271
- # command_string = b'~SerialNumber\x00'
272
- # response_size = 1024
273
- # cmd_response = self.__send_command(command, command_string, response_size)
274
- # if cmd_response.get('status'):
275
- # serialnumber = self.__data.split(b'=', 1)[-1].split(b'\x00')[0]
276
- # serialnumber = serialnumber.replace(b'=', b'')
277
- # return serialnumber.decode() # string?
278
- # else:
279
- # raise ZKErrorResponse("Can't read serial number")
280
-
281
216
  command = CMD_OPTIONS_RRQ
282
217
  command_string = "~SerialNumber\x00".b
283
218
  response_size = 1024
@@ -287,26 +222,13 @@ module RBZK
287
222
  if response && response[:status]
288
223
  serialnumber = @data.split("=", 2)[1]&.split("\x00")[0] || ""
289
224
  serialnumber = serialnumber.gsub("=", "")
290
- return serialnumber.to_s
225
+ serialnumber.to_s
291
226
  else
292
227
  raise RBZK::ZKErrorResponse, "Can't read serial number"
293
228
  end
294
229
  end
295
230
 
296
231
  def get_mac
297
- # Match Python's get_mac method exactly
298
- # In Python:
299
- # def get_mac(self):
300
- # command = const.CMD_OPTIONS_RRQ
301
- # command_string = b'MAC\x00'
302
- # response_size = 1024
303
- # cmd_response = self.__send_command(command, command_string, response_size)
304
- # if cmd_response.get('status'):
305
- # mac = self.__data.split(b'=', 1)[-1].split(b'\x00')[0]
306
- # return mac.decode()
307
- # else:
308
- # raise ZKErrorResponse("can't read mac address")
309
-
310
232
  command = CMD_OPTIONS_RRQ
311
233
  command_string = "MAC\x00".b
312
234
  response_size = 1024
@@ -315,26 +237,13 @@ module RBZK
315
237
 
316
238
  if response && response[:status]
317
239
  mac = @data.split("=", 2)[1]&.split("\x00")[0] || ""
318
- return mac.to_s
240
+ mac.to_s
319
241
  else
320
242
  raise RBZK::ZKErrorResponse, "Can't read MAC address"
321
243
  end
322
244
  end
323
245
 
324
246
  def get_device_name
325
- # Match Python's get_device_name method exactly
326
- # In Python:
327
- # def get_device_name(self):
328
- # command = const.CMD_OPTIONS_RRQ
329
- # command_string = b'~DeviceName\x00'
330
- # response_size = 1024
331
- # cmd_response = self.__send_command(command, command_string, response_size)
332
- # if cmd_response.get('status'):
333
- # device = self.__data.split(b'=', 1)[-1].split(b'\x00')[0]
334
- # return device.decode()
335
- # else:
336
- # return ""
337
-
338
247
  command = CMD_OPTIONS_RRQ
339
248
  command_string = "~DeviceName\x00".b
340
249
  response_size = 1024
@@ -343,26 +252,13 @@ module RBZK
343
252
 
344
253
  if response && response[:status]
345
254
  device = @data.split("=", 2)[1]&.split("\x00")[0] || ""
346
- return device.to_s
255
+ device.to_s
347
256
  else
348
- return ""
257
+ ""
349
258
  end
350
259
  end
351
260
 
352
261
  def get_face_version
353
- # Match Python's get_face_version method exactly
354
- # In Python:
355
- # def get_face_version(self):
356
- # command = const.CMD_OPTIONS_RRQ
357
- # command_string = b'ZKFaceVersion\x00'
358
- # response_size = 1024
359
- # cmd_response = self.__send_command(command, command_string, response_size)
360
- # if cmd_response.get('status'):
361
- # response = self.__data.split(b'=', 1)[-1].split(b'\x00')[0]
362
- # return safe_cast(response, int, 0) if response else 0
363
- # else:
364
- # return None
365
-
366
262
  command = CMD_OPTIONS_RRQ
367
263
  command_string = "ZKFaceVersion\x00".b
368
264
  response_size = 1024
@@ -371,27 +267,13 @@ module RBZK
371
267
 
372
268
  if response && response[:status]
373
269
  version = @data.split("=", 2)[1]&.split("\x00")[0] || ""
374
- return version.to_i rescue 0
270
+ version.to_i rescue 0
375
271
  else
376
- return nil
272
+ nil
377
273
  end
378
274
  end
379
275
 
380
276
  def get_extend_fmt
381
- # Match Python's get_extend_fmt method exactly
382
- # In Python:
383
- # def get_extend_fmt(self):
384
- # command = const.CMD_OPTIONS_RRQ
385
- # command_string = b'~ExtendFmt\x00'
386
- # response_size = 1024
387
- # cmd_response = self.__send_command(command, command_string, response_size)
388
- # if cmd_response.get('status'):
389
- # fmt = (self.__data.split(b'=', 1)[-1].split(b'\x00')[0])
390
- # return safe_cast(fmt, int, 0) if fmt else 0
391
- # else:
392
- # self._clear_error(command_string)
393
- # return None
394
-
395
277
  command = CMD_OPTIONS_RRQ
396
278
  command_string = "~ExtendFmt\x00".b
397
279
  response_size = 1024
@@ -400,29 +282,13 @@ module RBZK
400
282
 
401
283
  if response && response[:status]
402
284
  fmt = @data.split("=", 2)[1]&.split("\x00")[0] || ""
403
- return fmt.to_i rescue 0
285
+ fmt.to_i rescue 0
404
286
  else
405
- # In Python, this would call self._clear_error(command_string)
406
- # We don't have that method, so we'll just return nil
407
- return nil
287
+ nil
408
288
  end
409
289
  end
410
290
 
411
291
  def get_platform
412
- # Match Python's get_platform method exactly
413
- # In Python:
414
- # def get_platform(self):
415
- # command = const.CMD_OPTIONS_RRQ
416
- # command_string = b'~Platform\x00'
417
- # response_size = 1024
418
- # cmd_response = self.__send_command(command, command_string, response_size)
419
- # if cmd_response.get('status'):
420
- # platform = self.__data.split(b'=', 1)[-1].split(b'\x00')[0]
421
- # platform = platform.replace(b'=', b'')
422
- # return platform.decode()
423
- # else:
424
- # raise ZKErrorResponse("Can't read platform name")
425
-
426
292
  command = CMD_OPTIONS_RRQ
427
293
  command_string = "~Platform\x00".b
428
294
  response_size = 1024
@@ -432,27 +298,13 @@ module RBZK
432
298
  if response && response[:status]
433
299
  platform = @data.split("=", 2)[1]&.split("\x00")[0] || ""
434
300
  platform = platform.gsub("=", "")
435
- return platform.to_s
301
+ platform.to_s
436
302
  else
437
303
  raise RBZK::ZKErrorResponse, "Can't read platform name"
438
304
  end
439
305
  end
440
306
 
441
307
  def get_fp_version
442
- # Match Python's get_fp_version method exactly
443
- # In Python:
444
- # def get_fp_version(self):
445
- # command = const.CMD_OPTIONS_RRQ
446
- # command_string = b'~ZKFPVersion\x00'
447
- # response_size = 1024
448
- # cmd_response = self.__send_command(command, command_string, response_size)
449
- # if cmd_response.get('status'):
450
- # response = self.__data.split(b'=', 1)[-1].split(b'\x00')[0]
451
- # response = response.replace(b'=', b'')
452
- # return safe_cast(response, int, 0) if response else 0
453
- # else:
454
- # raise ZKErrorResponse("can't read fingerprint version")
455
-
456
308
  command = CMD_OPTIONS_RRQ
457
309
  command_string = "~ZKFPVersion\x00".b
458
310
  response_size = 1024
@@ -462,7 +314,7 @@ module RBZK
462
314
  if response && response[:status]
463
315
  version = @data.split("=", 2)[1]&.split("\x00")[0] || ""
464
316
  version = version.gsub("=", "")
465
- return version.to_i rescue 0
317
+ version.to_i rescue 0
466
318
  else
467
319
  raise RBZK::ZKErrorResponse, "Can't read fingerprint version"
468
320
  end
@@ -481,76 +333,18 @@ module RBZK
481
333
  end
482
334
 
483
335
  def test_voice(index = 0)
484
- # Match Python's test_voice method exactly
485
- # In Python:
486
- # def test_voice(self, index=0):
487
- # command = const.CMD_TESTVOICE
488
- # command_string = pack("I", index)
489
- # cmd_response = self.__send_command(command, command_string)
490
- # if cmd_response.get('status'):
491
- # return True
492
- # else:
493
- # return False
494
-
495
336
  command_string = [ index ].pack('L<')
496
337
  response = self.send_command(CMD_TESTVOICE, command_string)
497
338
 
498
339
  if response && response[:status]
499
- return true
340
+ true
500
341
  else
501
- return false
342
+ false
502
343
  end
503
344
  end
504
345
 
505
- # Helper method to read data with buffer, similar to Python's read_with_buffer
346
+ # Helper method to read data with buffer (ZK6: 1503)
506
347
  def read_with_buffer(command, fct = 0, ext = 0)
507
- # Match Python's read_with_buffer method exactly
508
- # In Python:
509
- # def read_with_buffer(self, command, fct=0 ,ext=0):
510
- # """
511
- # Test read info with buffered command (ZK6: 1503)
512
- # """
513
- # if self.tcp:
514
- # MAX_CHUNK = 0xFFc0
515
- # else:
516
- # MAX_CHUNK = 16 * 1024
517
- # command_string = pack('<bhii', 1, command, fct, ext)
518
- # if self.verbose: print ("rwb cs", command_string)
519
- # response_size = 1024
520
- # data = []
521
- # start = 0
522
- # cmd_response = self.__send_command(const._CMD_PREPARE_BUFFER, command_string, response_size)
523
- # if not cmd_response.get('status'):
524
- # raise ZKErrorResponse("RWB Not supported")
525
- # if cmd_response['code'] == const.CMD_DATA:
526
- # if self.tcp:
527
- # if self.verbose: print ("DATA! is {} bytes, tcp length is {}".format(len(self.__data), self.__tcp_length))
528
- # if len(self.__data) < (self.__tcp_length - 8):
529
- # need = (self.__tcp_length - 8) - len(self.__data)
530
- # if self.verbose: print ("need more data: {}".format(need))
531
- # more_data = self.__recieve_raw_data(need)
532
- # return b''.join([self.__data, more_data]), len(self.__data) + len(more_data)
533
- # else:
534
- # if self.verbose: print ("Enough data")
535
- # size = len(self.__data)
536
- # return self.__data, size
537
- # else:
538
- # size = len(self.__data)
539
- # return self.__data, size
540
- # size = unpack('I', self.__data[1:5])[0]
541
- # if self.verbose: print ("size fill be %i" % size)
542
- # remain = size % MAX_CHUNK
543
- # packets = (size-remain) // MAX_CHUNK # should be size /16k
544
- # if self.verbose: print ("rwb: #{} packets of max {} bytes, and extra {} bytes remain".format(packets, MAX_CHUNK, remain))
545
- # for _wlk in range(packets):
546
- # data.append(self.__read_chunk(start,MAX_CHUNK))
547
- # start += MAX_CHUNK
548
- # if remain:
549
- # data.append(self.__read_chunk(start, remain))
550
- # start += remain
551
- # self.free_data()
552
- # if self.verbose: print ("_read w/chunk %i bytes" % start)
553
- # return b''.join(data), start
554
348
 
555
349
  if @verbose
556
350
  puts "Reading data with buffer: command=#{command}, fct=#{fct}, ext=#{ext}"
@@ -559,27 +353,16 @@ module RBZK
559
353
  # Set max chunk size based on connection type
560
354
  max_chunk = @tcp ? 0xFFc0 : 16 * 1024
561
355
 
562
- # In Python: command_string = pack('<bhii', 1, command, fct, ext)
563
- # Note: In Python, the format '<bhii' means:
564
- # < - little endian
565
- # b - signed char (1 byte)
566
- # h - short (2 bytes)
567
- # i - int (4 bytes)
568
- # i - int (4 bytes)
569
- # In Ruby, we need to use:
570
- # c - signed char (1 byte) to match Python's 'b'
571
- # s - short (2 bytes)
572
- # l - long (4 bytes)
573
- # l - long (4 bytes)
574
- # with < for little-endian
356
+ # Pack the command parameters into a binary string
357
+ # Format: 1 byte signed char, 2 byte short, 4 byte long, 4 byte long
358
+ # All in little-endian format
575
359
  command_string = [ 1, command, fct, ext ].pack('cs<l<l<')
576
360
 
577
361
  if @verbose
578
362
  puts "Command string: #{python_format(command_string)}"
579
363
  end
580
364
 
581
- # In Python: cmd_response = self.__send_command(const._CMD_PREPARE_BUFFER, command_string, response_size)
582
- # Note: In Python, const._CMD_PREPARE_BUFFER is 1503
365
+ # Send the command to prepare the buffer
583
366
  response_size = 1024
584
367
  data = []
585
368
  start = 0
@@ -609,7 +392,7 @@ module RBZK
609
392
  puts "need more data: #{need}"
610
393
  end
611
394
 
612
- # In Python: more_data = self.__recieve_raw_data(need)
395
+ # Receive more data to complete the buffer
613
396
  more_data = receive_raw_data(need)
614
397
 
615
398
  if @verbose
@@ -632,9 +415,7 @@ module RBZK
632
415
  end
633
416
  end
634
417
 
635
- # Get the size from the first 4 bytes
636
- # In Python: size = unpack('I', self.__data[1:5])[0]
637
- # In Ruby, 'L<' is an unsigned long (4 bytes) in little-endian format, which matches Python's 'I'
418
+ # Get the size from the first 4 bytes (unsigned long, little-endian)
638
419
  size = data[1..4].unpack('L<')[0]
639
420
 
640
421
  if @verbose
@@ -643,8 +424,7 @@ module RBZK
643
424
 
644
425
  # Calculate chunks
645
426
  remain = size % max_chunk
646
- # In Python: packets = (size-remain) // MAX_CHUNK # should be size /16k
647
- # In Ruby, we need to use integer division to match Python's // operator
427
+ # Calculate number of full-sized packets (integer division)
648
428
  packets = (size - remain).div(max_chunk)
649
429
 
650
430
  if @verbose
@@ -682,72 +462,19 @@ module RBZK
682
462
 
683
463
  # In Python: return b''.join(data), start
684
464
  result = result_data.join
685
- return result, start
465
+ [ result, start ]
686
466
  end
687
467
 
688
468
  # Helper method to get data size from the current data
689
469
  def get_data_size
690
- # Match Python's __get_data_size method exactly
691
- # In Python:
692
- # def __get_data_size(self):
693
- # """internal function to get data size from the packet"""
694
- # if len(self.__data) >= 4:
695
- # size = unpack('I', self.__data[:4])[0]
696
- # return size
697
- # else:
698
- # return 0
699
-
700
470
  if @data && @data.size >= 4
701
471
  size = @data[0...4].unpack('L<')[0]
702
- return size
472
+ size
703
473
  else
704
- return 0
474
+ 0
705
475
  end
706
476
  end
707
477
 
708
- # Helper method to test TCP header
709
- def test_tcp_top(data)
710
- # Match Python's __test_tcp_top method exactly
711
- # In Python:
712
- # def __test_tcp_top(self, packet):
713
- # """test a TCP packet header"""
714
- # if not packet:
715
- # return False
716
- # if len(packet) < 8:
717
- # self.__tcp_length = 0
718
- # self.__response = const.CMD_TCP_STILL_ALIVE
719
- # return True
720
- #
721
- # if len(packet) < 8:
722
- # self.__tcp_length = 0
723
- # self.__response = const.CMD_TCP_STILL_ALIVE
724
- # return True
725
- # top, self.__session_id, self.__reply_id, self.__tcp_length = unpack('<HHHI', packet[:8])
726
- # self.__response = top
727
- # if self.verbose: print ("tcp top is {}, session id is {}, reply id is {}, tcp length is {}".format(
728
- # self.__response, self.__session_id, self.__reply_id, self.__tcp_length))
729
- # return True
730
-
731
- if !data || data.empty?
732
- return false
733
- end
734
-
735
- if data.size < 8
736
- @tcp_length = 0
737
- @response = CMD_TCP_STILL_ALIVE
738
- return true
739
- end
740
-
741
- top, @session_id, @reply_id, @tcp_length = data[0...8].unpack('S<S<S<L<')
742
- @response = top
743
-
744
- if @verbose
745
- puts "tcp top is #{@response}, session id is #{@session_id}, reply id is #{@reply_id}, tcp length is #{@tcp_length}"
746
- end
747
-
748
- return true
749
- end
750
-
751
478
  # Helper method to receive TCP data
752
479
  def receive_tcp_data(data_recv, size)
753
480
  data = []
@@ -940,28 +667,12 @@ module RBZK
940
667
  if @verbose
941
668
  puts "invalid response #{@response}"
942
669
  end
943
- return nil
670
+ nil
944
671
  end
945
672
  end
946
673
 
947
674
  # Helper method to receive raw data (like Python's __recieve_raw_data)
948
675
  def receive_raw_data(size)
949
- # Match Python's __recieve_raw_data method exactly
950
- # In Python:
951
- # def __recieve_raw_data(self, size):
952
- # """ partial data ? """
953
- # data = []
954
- # if self.verbose: print ("expecting {} bytes raw data".format(size))
955
- # while size > 0:
956
- # data_recv = self.__sock.recv(size)
957
- # recieved = len(data_recv)
958
- # if self.verbose: print ("partial recv {}".format(recieved))
959
- # if recieved < 100 and self.verbose: print (" recv {}".format(codecs.encode(data_recv, 'hex')))
960
- # data.append(data_recv)
961
- # size -= recieved
962
- # if self.verbose: print ("still need {}".format(size))
963
- # return b''.join(data)
964
-
965
676
  data = []
966
677
  if @verbose
967
678
  puts "expecting #{size} bytes raw data"
@@ -991,26 +702,11 @@ module RBZK
991
702
 
992
703
  # Helper method to clear buffer (like Python's free_data)
993
704
  def free_data
994
- # Match Python's free_data method exactly
995
- # In Python:
996
- # def free_data(self):
997
- # """
998
- # clear buffer
999
- #
1000
- # :return: bool
1001
- # """
1002
- # command = const.CMD_FREE_DATA
1003
- # cmd_response = self.__send_command(command)
1004
- # if cmd_response.get('status'):
1005
- # return True
1006
- # else:
1007
- # raise ZKErrorResponse("can't free data")
1008
-
1009
705
  command = CMD_FREE_DATA
1010
706
  response = self.send_command(command)
1011
707
 
1012
708
  if response && response[:status]
1013
- return true
709
+ true
1014
710
  else
1015
711
  raise RBZK::ZKErrorResponse, "Can't free data"
1016
712
  end
@@ -1018,26 +714,6 @@ module RBZK
1018
714
 
1019
715
  # Helper method to read a chunk of data
1020
716
  def read_chunk(start, size)
1021
- # Match Python's __read_chunk method exactly
1022
- # In Python:
1023
- # def __read_chunk(self, start, size):
1024
- # """
1025
- # read a chunk from buffer
1026
- # """
1027
- # for _retries in range(3):
1028
- # command = const._CMD_READ_BUFFER
1029
- # command_string = pack('<ii', start, size)
1030
- # if self.tcp:
1031
- # response_size = size + 32
1032
- # else:
1033
- # response_size = 1024 + 8
1034
- # cmd_response = self.__send_command(command, command_string, response_size)
1035
- # data = self.__recieve_chunk()
1036
- # if data is not None:
1037
- # return data
1038
- # else:
1039
- # raise ZKErrorResponse("can't read chunk %i:[%i]" % (start, size))
1040
-
1041
717
  if @verbose
1042
718
  puts "Reading chunk: start=#{start}, size=#{size}"
1043
719
  end
@@ -1157,68 +833,6 @@ module RBZK
1157
833
  end
1158
834
 
1159
835
  def get_attendance_logs
1160
- # Match Python's get_attendance method exactly
1161
- # In Python:
1162
- # def get_attendance(self):
1163
- # self.read_sizes()
1164
- # if self.records == 0:
1165
- # return []
1166
- # users = self.get_users()
1167
- # if self.verbose: print (users)
1168
- # attendances = []
1169
- # attendance_data, size = self.read_with_buffer(const.CMD_ATTLOG_RRQ)
1170
- # if size < 4:
1171
- # if self.verbose: print ("WRN: no attendance data")
1172
- # return []
1173
- # total_size = unpack("I", attendance_data[:4])[0]
1174
- # record_size = total_size // self.records
1175
- # if self.verbose: print ("record_size is ", record_size)
1176
- # attendance_data = attendance_data[4:]
1177
- # if record_size == 8:
1178
- # while len(attendance_data) >= 8:
1179
- # uid, status, timestamp, punch = unpack('HB4sB', attendance_data.ljust(8, b'\x00')[:8])
1180
- # if self.verbose: print (codecs.encode(attendance_data[:8], 'hex'))
1181
- # attendance_data = attendance_data[8:]
1182
- # tuser = list(filter(lambda x: x.uid == uid, users))
1183
- # if not tuser:
1184
- # user_id = str(uid)
1185
- # else:
1186
- # user_id = tuser[0].user_id
1187
- # timestamp = self.__decode_time(timestamp)
1188
- # attendance = Attendance(user_id, timestamp, status, punch, uid)
1189
- # attendances.append(attendance)
1190
- # elif record_size == 16:
1191
- # while len(attendance_data) >= 16:
1192
- # user_id, timestamp, status, punch, reserved, workcode = unpack('<I4sBB2sI', attendance_data.ljust(16, b'\x00')[:16])
1193
- # user_id = str(user_id)
1194
- # if self.verbose: print(codecs.encode(attendance_data[:16], 'hex'))
1195
- # attendance_data = attendance_data[16:]
1196
- # tuser = list(filter(lambda x: x.user_id == user_id, users))
1197
- # if not tuser:
1198
- # if self.verbose: print("no uid {}", user_id)
1199
- # uid = str(user_id)
1200
- # tuser = list(filter(lambda x: x.uid == user_id, users))
1201
- # if not tuser:
1202
- # uid = str(user_id)
1203
- # else:
1204
- # uid = tuser[0].uid
1205
- # user_id = tuser[0].user_id
1206
- # else:
1207
- # uid = tuser[0].uid
1208
- # timestamp = self.__decode_time(timestamp)
1209
- # attendance = Attendance(user_id, timestamp, status, punch, uid)
1210
- # attendances.append(attendance)
1211
- # else:
1212
- # while len(attendance_data) >= 40:
1213
- # uid, user_id, status, timestamp, punch, space = unpack('<H24sB4sB8s', attendance_data.ljust(40, b'\x00')[:40])
1214
- # if self.verbose: print (codecs.encode(attendance_data[:40], 'hex'))
1215
- # user_id = (user_id.split(b'\x00')[0]).decode(errors='ignore')
1216
- # timestamp = self.__decode_time(timestamp)
1217
- # attendance = Attendance(user_id, timestamp, status, punch, uid)
1218
- # attendances.append(attendance)
1219
- # attendance_data = attendance_data[record_size:]
1220
- # return attendances
1221
-
1222
836
  # First, read device sizes to get record count
1223
837
  self.read_sizes
1224
838
 
@@ -1353,31 +967,6 @@ module RBZK
1353
967
  logs
1354
968
  end
1355
969
 
1356
- # Decode a timestamp retrieved from the timeclock
1357
- # Match Python's __decode_time method exactly
1358
- # In Python:
1359
- # def __decode_time(self, t):
1360
- # t = unpack("<I", t)[0]
1361
- # second = t % 60
1362
- # t = t // 60
1363
- #
1364
- # minute = t % 60
1365
- # t = t // 60
1366
- #
1367
- # hour = t % 24
1368
- # t = t // 24
1369
- #
1370
- # day = t % 31 + 1
1371
- # t = t // 31
1372
- #
1373
- # month = t % 12 + 1
1374
- # t = t // 12
1375
- #
1376
- # year = t + 2000
1377
- #
1378
- # d = datetime(year, month, day, hour, minute, second)
1379
- #
1380
- # return d
1381
970
  def decode_time(t)
1382
971
  # Convert binary timestamp to integer
1383
972
  t = t.unpack("L<")[0]
@@ -1406,12 +995,6 @@ module RBZK
1406
995
 
1407
996
  # Decode a timestamp in hex format (6 bytes)
1408
997
  # Match Python's __decode_timehex method
1409
- # In Python:
1410
- # def __decode_timehex(self, timehex):
1411
- # year, month, day, hour, minute, second = unpack("6B", timehex)
1412
- # year += 2000
1413
- # d = datetime(year, month, day, hour, minute, second)
1414
- # return d
1415
998
  def decode_timehex(timehex)
1416
999
  # Extract time components
1417
1000
  year, month, day, hour, minute, second = timehex.unpack("C6")
@@ -1421,15 +1004,6 @@ module RBZK
1421
1004
  Time.new(year, month, day, hour, minute, second)
1422
1005
  end
1423
1006
 
1424
- # Encode a timestamp for the device
1425
- # Match Python's __encode_time method
1426
- # In Python:
1427
- # def __encode_time(self, t):
1428
- # d = (
1429
- # ((t.year % 100) * 12 * 31 + ((t.month - 1) * 31) + t.day - 1) *
1430
- # (24 * 60 * 60) + (t.hour * 60 + t.minute) * 60 + t.second
1431
- # )
1432
- # return d
1433
1007
  def encode_time(t)
1434
1008
  # Calculate encoded timestamp
1435
1009
  d = (
@@ -1440,40 +1014,18 @@ module RBZK
1440
1014
  end
1441
1015
 
1442
1016
  def get_time
1443
- # Match Python's get_time method exactly
1444
- # In Python:
1445
- # def get_time(self):
1446
- # command = const.CMD_GET_TIME
1447
- # response_size = 1032
1448
- # cmd_response = self.__send_command(command, b'', response_size)
1449
- # if cmd_response.get('status'):
1450
- # return self.__decode_time(self.__data[:4])
1451
- # else:
1452
- # raise ZKErrorResponse("can't get time")
1453
-
1454
1017
  command = CMD_GET_TIME
1455
1018
  response_size = 1032
1456
1019
  response = self.send_command(command, "", response_size)
1457
1020
 
1458
1021
  if response && response[:status]
1459
- return decode_time(@data[0...4])
1022
+ decode_time(@data[0...4])
1460
1023
  else
1461
1024
  raise RBZK::ZKErrorResponse, "Can't get time"
1462
1025
  end
1463
1026
  end
1464
1027
 
1465
1028
  def set_time(timestamp = nil)
1466
- # Match Python's set_time method exactly
1467
- # In Python:
1468
- # def set_time(self, timestamp):
1469
- # command = const.CMD_SET_TIME
1470
- # command_string = pack(b'I', self.__encode_time(timestamp))
1471
- # cmd_response = self.__send_command(command, command_string)
1472
- # if cmd_response.get('status'):
1473
- # return True
1474
- # else:
1475
- # raise ZKErrorResponse("can't set time")
1476
-
1477
1029
  # Default to current time if not provided
1478
1030
  timestamp ||= Time.now
1479
1031
 
@@ -1482,7 +1034,7 @@ module RBZK
1482
1034
  response = self.send_command(command, command_string)
1483
1035
 
1484
1036
  if response && response[:status]
1485
- return true
1037
+ true
1486
1038
  else
1487
1039
  raise RBZK::ZKErrorResponse, "Can't set time"
1488
1040
  end
@@ -1608,30 +1160,6 @@ module RBZK
1608
1160
  end
1609
1161
 
1610
1162
  def read_sizes
1611
- # Match Python's read_sizes method exactly
1612
- # In Python:
1613
- # def read_sizes(self):
1614
- # command = const.CMD_GET_FREE_SIZES
1615
- # response_size = 1024
1616
- # cmd_response = self.__send_command(command,b'', response_size)
1617
- # if cmd_response.get('status'):
1618
- # if self.verbose: print(codecs.encode(self.__data,'hex'))
1619
- # size = len(self.__data)
1620
- # if len(self.__data) >= 80:
1621
- # fields = unpack('20i', self.__data[:80])
1622
- # self.users = fields[4]
1623
- # self.fingers = fields[6]
1624
- # self.records = fields[8]
1625
- # self.dummy = fields[10] #???
1626
- # self.cards = fields[12]
1627
- # self.fingers_cap = fields[14]
1628
- # self.users_cap = fields[15]
1629
- # self.rec_cap = fields[16]
1630
- # self.fingers_av = fields[17]
1631
- # self.users_av = fields[18]
1632
- # self.rec_av = fields[19]
1633
- # self.__data = self.__data[80:]
1634
-
1635
1163
  command = CMD_GET_FREE_SIZES
1636
1164
  response_size = 1024
1637
1165
  cmd_response = self.send_command(command, "", response_size)
@@ -1841,7 +1369,7 @@ module RBZK
1841
1369
  end
1842
1370
 
1843
1371
  # Default return 0
1844
- return 0
1372
+ 0
1845
1373
  end
1846
1374
 
1847
1375
  def ping
@@ -1894,26 +1422,6 @@ module RBZK
1894
1422
  end
1895
1423
 
1896
1424
  def calculate_checksum(buf)
1897
- # Match Python's __create_checksum method exactly
1898
- # In Python:
1899
- # def __create_checksum(self, p):
1900
- # l = len(p)
1901
- # checksum = 0
1902
- # while l > 1:
1903
- # checksum += unpack('H', pack('BB', p[0], p[1]))[0]
1904
- # p = p[2:]
1905
- # if checksum > const.USHRT_MAX:
1906
- # checksum -= const.USHRT_MAX
1907
- # l -= 2
1908
- # if l:
1909
- # checksum = checksum + p[-1]
1910
- # while checksum > const.USHRT_MAX:
1911
- # checksum -= const.USHRT_MAX
1912
- # checksum = ~checksum
1913
- # while checksum < 0:
1914
- # checksum += const.USHRT_MAX
1915
- # return pack('H', checksum)
1916
-
1917
1425
  # Get the length of the buffer
1918
1426
  l = buf.size
1919
1427
  checksum = 0
@@ -1970,94 +1478,42 @@ module RBZK
1970
1478
  end
1971
1479
 
1972
1480
  def create_tcp_top(packet)
1973
- # Match Python's __create_tcp_top method exactly
1974
1481
  puts "\n*** DEBUG: create_tcp_top called ***" if @verbose
1975
1482
  length = packet.size
1976
- # In Python: pack('<HHI', const.MACHINE_PREPARE_DATA_1, const.MACHINE_PREPARE_DATA_2, length)
1977
- # In Ruby: [MACHINE_PREPARE_DATA_1, MACHINE_PREPARE_DATA_2, length].pack('S<S<I<')
1978
1483
  top = [ MACHINE_PREPARE_DATA_1, MACHINE_PREPARE_DATA_2, length ].pack('S<S<I<')
1979
1484
 
1980
1485
  if @verbose
1981
- puts "\nTCP header components:"
1982
- puts " MACHINE_PREPARE_DATA_1: 0x#{MACHINE_PREPARE_DATA_1.to_s(16)} (#{MACHINE_PREPARE_DATA_1}) - should be 'PP' in ASCII"
1983
- puts " MACHINE_PREPARE_DATA_2: 0x#{MACHINE_PREPARE_DATA_2.to_s(16)} (#{MACHINE_PREPARE_DATA_2}) - should be '\\x82\\x7d' in hex"
1486
+ puts "TCP header components:"
1487
+ puts " MACHINE_PREPARE_DATA_1: 0x#{MACHINE_PREPARE_DATA_1.to_s(16)}"
1488
+ puts " MACHINE_PREPARE_DATA_2: 0x#{MACHINE_PREPARE_DATA_2.to_s(16)}"
1984
1489
  puts " packet length: #{length}"
1985
-
1986
- # Show the expected Python representation
1987
- expected_python_header = "PP\\x82\\x7d\\x#{(length & 0xFF).to_s(16).rjust(2, '0')}\\x#{((length >> 8) & 0xFF).to_s(16).rjust(2, '0')}\\x#{((length >> 16) & 0xFF).to_s(16).rjust(2, '0')}\\x#{((length >> 24) & 0xFF).to_s(16).rjust(2, '0')}"
1988
- puts " Expected Python header: #{expected_python_header}"
1989
-
1990
- # Show the actual bytes of the constants
1991
- puts " MACHINE_PREPARE_DATA_1 bytes: #{[ MACHINE_PREPARE_DATA_1 ].pack('S<').bytes.map { |b| "0x#{b.to_s(16).rjust(2, '0')}" }.join(' ')}"
1992
- puts " MACHINE_PREPARE_DATA_2 bytes: #{[ MACHINE_PREPARE_DATA_2 ].pack('S<').bytes.map { |b| "0x#{b.to_s(16).rjust(2, '0')}" }.join(' ')}"
1993
-
1994
- debug_binary("TCP header only", top) # This is just the 8-byte header
1995
- debug_binary("Full TCP packet (what Python calls 'top')", top + packet) # This is what we return
1996
- end
1997
-
1998
- # Print debug info right before returning
1999
- if @verbose
2000
- puts "\n*** FINAL TCP PACKET DEBUG ***"
2001
- puts "In both Python and Ruby, the variable 'top' is just the TCP header, but the method returns 'top + packet':"
2002
- puts "TCP header format: b'PP\\x82\\x7d\\x#{(length & 0xFF).to_s(16).rjust(2, '0')}\\x#{((length >> 8) & 0xFF).to_s(16).rjust(2, '0')}\\x#{((length >> 16) & 0xFF).to_s(16).rjust(2, '0')}\\x#{((length >> 24) & 0xFF).to_s(16).rjust(2, '0')}'"
2003
- puts "Return value format (top + packet): TCP header + command packet"
2004
- puts "Ruby 'top + packet' format: #{(top + packet).bytes.map { |b| "0x#{b.to_s(16).rjust(2, '0')}" }.join(' ')}"
2005
- puts "Hex format: #{(top + packet).bytes.map { |b| "\\x#{b.to_s(16).rjust(2, '0')}" }.join('')}"
1490
+ debug_binary("TCP header", top)
1491
+ debug_binary("Full TCP packet", top + packet)
2006
1492
  end
2007
1493
 
2008
- # Return top + packet (like Python's return top + packet)
2009
- result = top + packet
2010
-
2011
- if @verbose
2012
- puts "\n*** SUPER EXPLICIT DEBUG ***"
2013
- puts "top bytes: #{top.bytes.map { |b| "0x#{b.to_s(16).rjust(2, '0')}" }.join(' ')}"
2014
- puts "packet bytes: #{packet.bytes.map { |b| "0x#{b.to_s(16).rjust(2, '0')}" }.join(' ')}"
2015
- puts "result bytes: #{result.bytes.map { |b| "0x#{b.to_s(16).rjust(2, '0')}" }.join(' ')}"
2016
- puts "result size: #{result.size} bytes"
2017
- end
2018
-
2019
- result
1494
+ top + packet
2020
1495
  end
2021
1496
 
2022
1497
  def create_header(command, command_string = "".b, session_id = 0, reply_id = 0)
2023
- # Match Python's __create_header method exactly
2024
- # In Python:
2025
- # def __create_header(self, command, command_string, session_id, reply_id):
2026
- # buf = pack('<4H', command, 0, session_id, reply_id) + command_string
2027
- # buf = unpack('8B' + '%sB' % len(command_string), buf)
2028
- # checksum = unpack('H', self.__create_checksum(buf))[0]
2029
- # reply_id += 1
2030
- # if reply_id >= const.USHRT_MAX:
2031
- # reply_id -= const.USHRT_MAX
2032
- # buf = pack('<4H', command, checksum, session_id, reply_id)
2033
- # return buf + command_string
2034
-
2035
1498
  # Ensure command_string is a binary string
2036
1499
  command_string = command_string.to_s.b
2037
1500
 
2038
- # Step 1: Create initial header and combine with command_string
2039
- # In Python: buf = pack('<4H', command, 0, session_id, reply_id) + command_string
1501
+ # Create initial header and combine with command_string
2040
1502
  buf = [ command, 0, session_id, reply_id ].pack('v4') + command_string
2041
1503
 
2042
- # Step 2: Convert to bytes array for checksum calculation
2043
- # In Python: buf = unpack('8B' + '%sB' % len(command_string), buf)
2044
- # This unpacks the buffer into individual bytes
2045
- # In Ruby, we can use String#bytes to get an array of bytes
1504
+ # Convert to bytes array for checksum calculation
2046
1505
  buf = buf.unpack("C#{8 + command_string.length}")
2047
1506
 
2048
- # Step 3: Calculate checksum
2049
- # In Python: checksum = unpack('H', self.__create_checksum(buf))[0]
1507
+ # Calculate checksum
2050
1508
  checksum = calculate_checksum(buf)
2051
1509
 
2052
- # Step 4: Update reply_id
2053
- # In Python: reply_id += 1; if reply_id >= const.USHRT_MAX: reply_id -= const.USHRT_MAX
1510
+ # Update reply_id
2054
1511
  reply_id += 1
2055
1512
  if reply_id >= USHRT_MAX
2056
1513
  reply_id -= USHRT_MAX
2057
1514
  end
2058
1515
 
2059
- # Step 5: Create final header with updated values
2060
- # In Python: buf = pack('<4H', command, checksum, session_id, reply_id)
1516
+ # Create final header with updated values
2061
1517
  buf = [ command, checksum, session_id, reply_id ].pack('v4')
2062
1518
 
2063
1519
  if @verbose
@@ -2079,41 +1535,6 @@ module RBZK
2079
1535
  end
2080
1536
 
2081
1537
  def send_command(command, command_string = "".b, response_size = 8)
2082
- # Match Python's __send_command method exactly
2083
- # In Python:
2084
- # def __send_command(self, command, command_string=b'', response_size=8):
2085
- # if command not in [const.CMD_CONNECT, const.CMD_AUTH] and not self.is_connect:
2086
- # raise ZKErrorConnection("instance are not connected.")
2087
- # buf = self.__create_header(command, command_string, self.__session_id, self.__reply_id)
2088
- # try:
2089
- # if self.tcp:
2090
- # top = self.__create_tcp_top(buf)
2091
- # self.__sock.send(top)
2092
- # self.__tcp_data_recv = self.__sock.recv(response_size + 8)
2093
- # self.__tcp_length = self.__test_tcp_top(self.__tcp_data_recv)
2094
- # if self.__tcp_length == 0:
2095
- # raise ZKNetworkError("TCP packet invalid")
2096
- # self.__header = unpack('<4H', self.__tcp_data_recv[8:16])
2097
- # self.__data_recv = self.__tcp_data_recv[8:]
2098
- # else:
2099
- # self.__sock.sendto(buf, self.__address)
2100
- # self.__data_recv = self.__sock.recv(response_size)
2101
- # self.__header = unpack('<4H', self.__data_recv[:8])
2102
- # except Exception as e:
2103
- # raise ZKNetworkError(str(e))
2104
- # self.__response = self.__header[0]
2105
- # self.__reply_id = self.__header[3]
2106
- # self.__data = self.__data_recv[8:]
2107
- # if self.__response in [const.CMD_ACK_OK, const.CMD_PREPARE_DATA, const.CMD_DATA]:
2108
- # return {
2109
- # 'status': True,
2110
- # 'code': self.__response
2111
- # }
2112
- # return {
2113
- # 'status': False,
2114
- # 'code': self.__response
2115
- # }
2116
-
2117
1538
  # Check connection status (except for connect and auth commands)
2118
1539
  if command != CMD_CONNECT && command != CMD_AUTH && !@connected
2119
1540
  raise RBZK::ZKErrorConnection, "Instance are not connected."
@@ -2190,12 +1611,12 @@ module RBZK
2190
1611
 
2191
1612
  # Return response status (like Python's __send_command)
2192
1613
  if @response == CMD_ACK_OK || @response == CMD_PREPARE_DATA || @response == CMD_DATA
2193
- return {
1614
+ {
2194
1615
  status: true,
2195
1616
  code: @response
2196
1617
  }
2197
1618
  else
2198
- return {
1619
+ {
2199
1620
  status: false,
2200
1621
  code: @response
2201
1622
  }