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.
- checksums.yaml +4 -4
- data/README.md +163 -2
- data/bin/console +14 -0
- data/bin/rbzk +8 -0
- data/bin/setup +6 -0
- data/lib/rbzk/cli/commands.rb +551 -0
- data/lib/rbzk/cli/config.rb +85 -0
- data/lib/rbzk/cli_thor.rb +14 -0
- data/lib/rbzk/constants.rb +0 -1
- data/lib/rbzk/zk.rb +56 -635
- metadata +35 -14
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
|
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
|
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
|
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
|
-
#
|
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
|
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
|
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
|
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
|
-
#
|
114
|
+
# Set user packet size if TCP connection is available
|
135
115
|
if !@force_udp && @helper.test_tcp == 0
|
136
|
-
|
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
|
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
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
255
|
+
device.to_s
|
347
256
|
else
|
348
|
-
|
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
|
-
|
270
|
+
version.to_i rescue 0
|
375
271
|
else
|
376
|
-
|
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
|
-
|
285
|
+
fmt.to_i rescue 0
|
404
286
|
else
|
405
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
340
|
+
true
|
500
341
|
else
|
501
|
-
|
342
|
+
false
|
502
343
|
end
|
503
344
|
end
|
504
345
|
|
505
|
-
# Helper method to read data 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
|
-
#
|
563
|
-
#
|
564
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
|
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
|
-
|
472
|
+
size
|
703
473
|
else
|
704
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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 "
|
1982
|
-
puts " MACHINE_PREPARE_DATA_1: 0x#{MACHINE_PREPARE_DATA_1.to_s(16)}
|
1983
|
-
puts " MACHINE_PREPARE_DATA_2: 0x#{MACHINE_PREPARE_DATA_2.to_s(16)}
|
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
|
-
|
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
|
-
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
2049
|
-
# In Python: checksum = unpack('H', self.__create_checksum(buf))[0]
|
1507
|
+
# Calculate checksum
|
2050
1508
|
checksum = calculate_checksum(buf)
|
2051
1509
|
|
2052
|
-
#
|
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
|
-
#
|
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
|
-
|
1614
|
+
{
|
2194
1615
|
status: true,
|
2195
1616
|
code: @response
|
2196
1617
|
}
|
2197
1618
|
else
|
2198
|
-
|
1619
|
+
{
|
2199
1620
|
status: false,
|
2200
1621
|
code: @response
|
2201
1622
|
}
|