rbzk 0.1.2 → 0.1.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c4e93719b6428a029bf767e708352ee0ac29e26735050288b5848eb9aea2c7b7
4
- data.tar.gz: b40b96e9c5b4f123f93314a4f51730286a7b5a1d59b5f6e16322b6a583e9f766
3
+ metadata.gz: 99e2a6812f00009e79f937e5b54639f676635c9a7f9b75702005a1582e796b44
4
+ data.tar.gz: 9cf5c9f5014e23a1795dd6d9d50698481ed380d446169cf13506316133da3db3
5
5
  SHA512:
6
- metadata.gz: be9a441a5718f1733bfd03181461767f4f2c3f76951f643e35d8c17efbfceef7b3db420fbf58ba757ab146a0b2756fcdfc4ad791dfea67536ed11b26019cdc29
7
- data.tar.gz: 73687d3eb95d16bb53e6e2e1abb33e94e14afd8f235610933d3923284f213b4b73a61fbec2d8d20d8459bc33e03d8311bdff5e4cdd612907b45412b912535016
6
+ metadata.gz: 2e571f268a2d311934bb19009b936c6154a88796d2d985eea483f076659e47e4fc2dd5f64de018fa01ce490e6d72d4285787dfae34a6e77262e3b93b2ec8289f
7
+ data.tar.gz: 134a4623eb8fa507a453fe8373ac3b7222a3e52cede1a7473e19f54f22cd062afe9fc1f009475e142ee48dbe0f8300c13343061e48ebe69da0c733abc06ebcc6
@@ -75,6 +75,19 @@ module RBZK
75
75
  end
76
76
  end
77
77
 
78
+ desc "refresh [IP]", "Refresh device data"
79
+
80
+ def refresh(ip = nil)
81
+ # Use IP from options if not provided as argument
82
+ ip ||= options[:ip] || @config['ip']
83
+
84
+ with_connection(ip, options) do |conn|
85
+ puts "Refreshing device data..."
86
+ result = conn.refresh_data
87
+ puts "✓ Device data refreshed successfully!" if result
88
+ end
89
+ end
90
+
78
91
  desc "users [IP]", "Get users from the device"
79
92
 
80
93
  def users(ip = nil)
@@ -327,6 +340,236 @@ module RBZK
327
340
  end
328
341
  end
329
342
 
343
+ desc "unlock [IP]", "Unlock the door"
344
+ method_option :time, type: :numeric, default: 3, desc: "Unlock time in seconds (default: 3)"
345
+ map "unlock" => :unlock_door
346
+
347
+ def unlock_door(ip = nil)
348
+ # Use IP from options if not provided as argument
349
+ ip ||= options[:ip] || @config['ip']
350
+
351
+ # Get the unlock time
352
+ time = options[:time] || 3
353
+
354
+ with_connection(ip, options) do |conn|
355
+ puts "Unlocking door for #{time} seconds..."
356
+ result = conn.unlock(time)
357
+ puts "✓ Door unlocked successfully!" if result
358
+ end
359
+ end
360
+
361
+ desc "door-state [IP]", "Get the door lock state"
362
+ map "door-state" => :door_state
363
+
364
+ def door_state(ip = nil)
365
+ # Use IP from options if not provided as argument
366
+ ip ||= options[:ip] || @config['ip']
367
+
368
+ with_connection(ip, options) do |conn|
369
+ state = conn.get_lock_state
370
+ puts "Door state: #{state ? 'Open' : 'Closed'}"
371
+ end
372
+ end
373
+
374
+ desc "write-lcd [IP] LINE_NUMBER TEXT", "Write text to LCD display"
375
+ map "write-lcd" => :write_lcd
376
+
377
+ def write_lcd(line_number, text, ip = nil)
378
+ # Use IP from options if not provided as argument
379
+ ip ||= options[:ip] || @config['ip']
380
+
381
+ # Convert line_number to integer
382
+ line_number = line_number.to_i
383
+
384
+ with_connection(ip, options) do |conn|
385
+ puts "Writing text to LCD line #{line_number}..."
386
+ result = conn.write_lcd(line_number, text)
387
+ puts "✓ Text written to LCD successfully!" if result
388
+ end
389
+ end
390
+
391
+ desc "clear-lcd [IP]", "Clear the LCD display"
392
+ map "clear-lcd" => :clear_lcd
393
+
394
+ def clear_lcd(ip = nil)
395
+ # Use IP from options if not provided as argument
396
+ ip ||= options[:ip] || @config['ip']
397
+
398
+ with_connection(ip, options) do |conn|
399
+ puts "Clearing LCD display..."
400
+ result = conn.clear_lcd
401
+ puts "✓ LCD cleared successfully!" if result
402
+ end
403
+ end
404
+
405
+ desc "add-user [IP]", "Add or update a user"
406
+ method_option :uid, type: :numeric, desc: "User ID (generated by device if not provided)"
407
+ method_option :name, type: :string, default: "", desc: "User name"
408
+ method_option :privilege, type: :numeric, default: 0, desc: "User privilege (0=User, 14=Admin)"
409
+ method_option :password, type: :string, default: "", desc: "User password"
410
+ method_option :group_id, type: :string, default: "", desc: "Group ID"
411
+ method_option :user_id, type: :string, default: "", desc: "Custom user ID"
412
+ method_option :card, type: :numeric, default: 0, desc: "Card number"
413
+ map "add-user" => :add_user
414
+
415
+ def add_user(ip = nil)
416
+ # Use IP from options if not provided as argument
417
+ ip ||= options[:ip] || @config['ip']
418
+
419
+ with_connection(ip, options) do |conn|
420
+ puts "Adding/updating user..."
421
+
422
+ result = conn.set_user(
423
+ uid: options[:uid],
424
+ name: options[:name] || "",
425
+ privilege: options[:privilege] || 0,
426
+ password: options[:password] || "",
427
+ group_id: options[:group_id] || "",
428
+ user_id: options[:user_id] || "",
429
+ card: options[:card] || 0
430
+ )
431
+
432
+ puts "✓ User added/updated successfully!" if result
433
+ end
434
+ end
435
+
436
+ desc "delete-user [IP]", "Delete a user"
437
+ method_option :uid, type: :numeric, desc: "User ID (generated by device)"
438
+ method_option :user_id, type: :string, desc: "Custom user ID"
439
+ map "delete-user" => :delete_user
440
+
441
+ def delete_user(ip = nil)
442
+ # Use IP from options if not provided as argument
443
+ ip ||= options[:ip] || @config['ip']
444
+
445
+ # Ensure at least one of uid or user_id is provided
446
+ if options[:uid].nil? && (options[:user_id].nil? || options[:user_id].empty?)
447
+ puts "Error: You must provide either --uid"
448
+ return
449
+ end
450
+
451
+ with_connection(ip, options) do |conn|
452
+ puts "Deleting user..."
453
+
454
+ # Use the User object with delete_user
455
+ result = conn.delete_user(uid: options[:uid])
456
+
457
+ if result
458
+ puts "✓ User deleted successfully!"
459
+ else
460
+ puts "✗ User not found or could not be deleted."
461
+ end
462
+ end
463
+ end
464
+
465
+ desc "get-templates [IP]", "Get all fingerprint templates"
466
+ map "get-templates" => :get_templates
467
+
468
+ def get_templates(ip = nil)
469
+ # Use IP from options if not provided as argument
470
+ ip ||= options[:ip] || @config['ip']
471
+
472
+ with_connection(ip, options) do |conn|
473
+ puts "Getting fingerprint templates..."
474
+ templates = conn.get_templates
475
+
476
+ if templates && !templates.empty?
477
+ puts "✓ Found #{templates.size} fingerprint templates:"
478
+
479
+ # Use Terminal::Table for pretty output
480
+ table = ::Terminal::Table.new do |t|
481
+ t.title = "Fingerprint Templates"
482
+ t.headings = [ 'UID', 'Finger ID', 'Valid', 'Size' ]
483
+
484
+ templates.each do |template|
485
+ t << [
486
+ template.uid,
487
+ template.fid,
488
+ template.valid == 1 ? 'Yes' : 'No',
489
+ template.size
490
+ ]
491
+ end
492
+ end
493
+
494
+ puts table
495
+ else
496
+ puts "✓ No fingerprint templates found"
497
+ end
498
+ end
499
+ end
500
+
501
+ desc "get-user-template [IP]", "Get a specific user's fingerprint template"
502
+ method_option :uid, type: :numeric, desc: "User ID (generated by device)"
503
+ method_option :user_id, type: :string, desc: "Custom user ID"
504
+ method_option :finger_id, type: :numeric, default: 0, desc: "Finger ID (0-9)"
505
+ map "get-user-template" => :get_user_template
506
+
507
+ def get_user_template(ip = nil)
508
+ # Use IP from options if not provided as argument
509
+ ip ||= options[:ip] || @config['ip']
510
+
511
+ # Ensure at least one of uid or user_id is provided
512
+ if options[:uid].nil? && (options[:user_id].nil? || options[:user_id].empty?)
513
+ puts "Error: You must provide either --uid or --user-id"
514
+ return
515
+ end
516
+
517
+ with_connection(ip, options) do |conn|
518
+ puts "Getting user fingerprint template..."
519
+
520
+ # Extract parameters from options
521
+ uid = options[:uid] || 0
522
+ user_id = options[:user_id] || ""
523
+ finger_id = options[:finger_id] || 0
524
+
525
+ template = conn.get_user_template(uid: uid, temp_id: finger_id, user_id: user_id)
526
+
527
+ if template
528
+ puts "✓ Found fingerprint template:"
529
+ puts " User ID: #{template.uid}"
530
+ puts " Finger ID: #{template.fid}"
531
+ puts " Valid: #{template.valid == 1 ? 'Yes' : 'No'}"
532
+ puts " Size: #{template.size} bytes"
533
+ else
534
+ puts "✗ Fingerprint template not found"
535
+ end
536
+ end
537
+ end
538
+
539
+ desc "delete-template [IP]", "Delete a specific fingerprint template"
540
+ method_option :uid, type: :numeric, desc: "User ID (generated by device)"
541
+ method_option :user_id, type: :string, desc: "Custom user ID"
542
+ method_option :finger_id, type: :numeric, default: 0, desc: "Finger ID (0-9)"
543
+ map "delete-template" => :delete_template
544
+
545
+ def delete_template(ip = nil)
546
+ # Use IP from options if not provided as argument
547
+ ip ||= options[:ip] || @config['ip']
548
+
549
+ # Ensure at least one of uid or user_id is provided
550
+ if options[:uid].nil? && (options[:user_id].nil? || options[:user_id].empty?)
551
+ puts "Error: You must provide either --uid or --user-id"
552
+ return
553
+ end
554
+
555
+ with_connection(ip, options) do |conn|
556
+ puts "Deleting fingerprint template..."
557
+
558
+ # Extract parameters from options
559
+ uid = options[:uid] || 0
560
+ user_id = options[:user_id] || ""
561
+ finger_id = options[:finger_id] || 0
562
+
563
+ result = conn.delete_user_template(uid: uid, temp_id: finger_id, user_id: user_id)
564
+
565
+ if result
566
+ puts "✓ Fingerprint template deleted successfully!"
567
+ else
568
+ puts "✗ Fingerprint template not found or could not be deleted"
569
+ end
570
+ end
571
+ end
572
+
330
573
  desc "test-voice [IP]", "Test the device voice"
331
574
  method_option :index, type: :numeric, desc: "Sound index to play (0-35, default: 0)"
332
575
  map "test-voice" => :test_voice
@@ -376,6 +619,42 @@ module RBZK
376
619
  end
377
620
  end
378
621
 
622
+ desc "restart [IP]", "Restart the device"
623
+
624
+ def restart(ip = nil)
625
+ # Use IP from options if not provided as argument
626
+ ip ||= options[:ip] || @config['ip']
627
+
628
+ if yes?("Are you sure you want to restart the device? (y/N)")
629
+ with_connection(ip, options.merge(skip_disconnect_after_yield: true)) do |conn|
630
+ puts "Restarting device..."
631
+ result = conn.restart
632
+ puts "✓ Device restart command sent successfully!" if result
633
+ puts "The device will restart now. You may need to wait a few moments before reconnecting."
634
+ end
635
+ else
636
+ puts "Operation cancelled."
637
+ end
638
+ end
639
+
640
+ desc "poweroff [IP]", "Power off the device"
641
+
642
+ def poweroff(ip = nil)
643
+ # Use IP from options if not provided as argument
644
+ ip ||= options[:ip] || @config['ip']
645
+
646
+ if yes?("Are you sure you want to power off the device? (y/N)")
647
+ with_connection(ip, options.merge(skip_disconnect_after_yield: true)) do |conn|
648
+ puts "Powering off device..."
649
+ result = conn.poweroff
650
+ puts "✓ Device poweroff command sent successfully!" if result
651
+ puts "The device will power off now. You will need to manually power it back on."
652
+ end
653
+ else
654
+ puts "Operation cancelled."
655
+ end
656
+ end
657
+
379
658
  desc "config", "Show current configuration"
380
659
 
381
660
  def config
@@ -425,26 +704,30 @@ module RBZK
425
704
  end
426
705
 
427
706
  def with_connection(ip, options = {})
428
- puts "Connecting to ZKTeco device at #{ip}:#{options[:port] || @config['port'] || 4370}..."
707
+ local_opts = options.dup # Use a copy for local modifications and access
708
+ skip_disconnect = local_opts.delete(:skip_disconnect_after_yield) || false
709
+
710
+ puts "Connecting to ZKTeco device at #{ip}:#{local_opts[:port] || @config['port'] || 4370}..." # Use local_opts
429
711
  puts "Please ensure the device is powered on and connected to the network."
430
712
 
713
+ conn = nil # Initialize conn for safety in ensure block
431
714
  begin
432
715
  # Create ZK instance with options from config and command line
433
716
  zk_options = {
434
- port: options[:port] || @config['port'] || 4370,
435
- timeout: options[:timeout] || @config['timeout'] || 30,
436
- password: options[:password] || @config['password'] || 0,
437
- verbose: options[:verbose] || @config['verbose'] || false,
438
- force_udp: options[:force_udp] || @config['force_udp'] || false,
439
- omit_ping: options[:no_ping] || @config['no_ping'] || false,
440
- encoding: options[:encoding] || @config['encoding'] || 'UTF-8'
717
+ port: local_opts[:port] || @config['port'] || 4370, # Use local_opts
718
+ timeout: local_opts[:timeout] || @config['timeout'] || 30, # Use local_opts
719
+ password: local_opts[:password] || @config['password'] || 0, # Use local_opts
720
+ verbose: local_opts[:verbose] || @config['verbose'] || false, # Use local_opts
721
+ force_udp: local_opts[:force_udp] || @config['force_udp'] || false, # Use local_opts
722
+ omit_ping: local_opts[:no_ping] || @config['no_ping'] || false, # Use local_opts
723
+ encoding: local_opts[:encoding] || @config['encoding'] || 'UTF-8' # Use local_opts
441
724
  }
442
725
 
443
726
  zk = RBZK::ZK.new(ip, **zk_options)
444
727
  conn = zk.connect
445
728
 
446
729
  if conn.connected?
447
- puts "✓ Connected successfully!" unless options[:quiet]
730
+ puts "✓ Connected successfully!" unless local_opts[:quiet] # Use local_opts
448
731
  yield conn if block_given?
449
732
  else
450
733
  puts "✗ Failed to connect to device."
@@ -458,12 +741,16 @@ module RBZK
458
741
  rescue => e
459
742
  puts "✗ Unexpected Error: #{e.message}"
460
743
  puts "An unexpected error occurred while communicating with the device."
461
- puts e.backtrace.join("\n") if options[:verbose]
744
+ puts e.backtrace.join("\n") if local_opts[:verbose] # Use local_opts
462
745
  ensure
463
746
  if conn && conn.connected?
464
- puts "Disconnecting from device..." unless options[:quiet]
465
- conn.disconnect
466
- puts "✓ Disconnected" unless options[:quiet]
747
+ if skip_disconnect
748
+ puts "Skipping disconnect as device is undergoing restart/poweroff." unless local_opts[:quiet] # Use local_opts
749
+ else
750
+ puts "Disconnecting from device..." unless local_opts[:quiet] # Use local_opts
751
+ conn.disconnect
752
+ puts "✓ Disconnected" unless local_opts[:quiet] # Use local_opts
753
+ end
467
754
  end
468
755
  end
469
756
  end
data/lib/rbzk/finger.rb CHANGED
@@ -2,13 +2,24 @@
2
2
 
3
3
  module RBZK
4
4
  class Finger
5
- attr_accessor :uid, :fid, :valid, :template
5
+ attr_accessor :uid, :fid, :valid, :template, :size
6
6
 
7
7
  def initialize(uid, fid, valid, template = "")
8
8
  @uid = uid
9
9
  @fid = fid
10
10
  @valid = valid
11
11
  @template = template
12
+ @size = template.length
13
+ end
14
+
15
+ # Pack the finger data into a binary string (full data)
16
+ def repack
17
+ [@size + 6, @uid, @fid, @valid].pack('S<S<CC') + @template
18
+ end
19
+
20
+ # Pack only the template data into a binary string
21
+ def repack_only
22
+ [@size].pack('S<') + @template
12
23
  end
13
24
 
14
25
  def to_s
data/lib/rbzk/user.rb CHANGED
@@ -27,6 +27,40 @@ module RBZK
27
27
  @card = card
28
28
  end
29
29
 
30
+ # Pack the user data into a binary string for ZK6 devices (size 29)
31
+ def repack29
32
+ [2, @uid, @privilege].pack('CS<C') +
33
+ @password.encode(@@encoding, invalid: :replace, undef: :replace).ljust(5, "\x00")[0...5] +
34
+ @name.encode(@@encoding, invalid: :replace, undef: :replace).ljust(8, "\x00")[0...8] +
35
+ [@card, 0, @group_id.to_i, 0, @user_id.to_i].pack('L<CS<S<L<')
36
+ end
37
+
38
+ # Pack the user data into a binary string for ZK8 devices (size 73)
39
+ def repack73
40
+ [2, @uid, @privilege].pack('CS<C') +
41
+ @password.encode(@@encoding, invalid: :replace, undef: :replace).ljust(8, "\x00")[0...8] +
42
+ @name.encode(@@encoding, invalid: :replace, undef: :replace).ljust(24, "\x00")[0...24] +
43
+ [@card, 1].pack('L<C') +
44
+ @group_id.to_s.encode(@@encoding, invalid: :replace, undef: :replace).ljust(7, "\x00")[0...7] +
45
+ "\x00" +
46
+ @user_id.to_s.encode(@@encoding, invalid: :replace, undef: :replace).ljust(24, "\x00")[0...24]
47
+ end
48
+
49
+ # Check if the user is disabled
50
+ def is_disabled?
51
+ (@privilege & 1) != 0
52
+ end
53
+
54
+ # Check if the user is enabled
55
+ def is_enabled?
56
+ !is_disabled?
57
+ end
58
+
59
+ # Get the user type
60
+ def usertype
61
+ @privilege & 0xE
62
+ end
63
+
30
64
  def to_s
31
65
  "#{@uid} #{@user_id} #{@name} #{@privilege} #{@password} #{@group_id} #{@card}"
32
66
  end
data/lib/rbzk/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RBZK
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.5"
5
5
  end
data/lib/rbzk/zk.rb CHANGED
@@ -15,8 +15,13 @@ module RBZK
15
15
 
16
16
  def test_ping
17
17
  begin
18
- system("ping -c 1 -W 5 #{@ip} > /dev/null 2>&1")
19
- return $?.success?
18
+ Timeout.timeout(5) do
19
+ s = TCPSocket.new(@ip, @port)
20
+ s.close
21
+ return true
22
+ end
23
+ rescue Timeout::Error, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, SocketError => e
24
+ return false
20
25
  rescue => e
21
26
  return false
22
27
  end
@@ -343,6 +348,166 @@ module RBZK
343
348
  end
344
349
  end
345
350
 
351
+ # Unlock the door
352
+ # @param time [Integer] define delay in seconds
353
+ # @return [Boolean] true if successful, raises exception otherwise
354
+ def unlock(time = 3)
355
+ command_string = [time * 10].pack('L<')
356
+ response = self.send_command(CMD_UNLOCK, command_string)
357
+
358
+ if response && response[:status]
359
+ true
360
+ else
361
+ raise RBZK::ZKErrorResponse, "Can't open door"
362
+ end
363
+ end
364
+
365
+ # Get the lock state
366
+ # @return [Boolean] true if door is open, false otherwise
367
+ def get_lock_state
368
+ response = self.send_command(CMD_DOORSTATE_RRQ)
369
+
370
+ if response && response[:status]
371
+ true
372
+ else
373
+ false
374
+ end
375
+ end
376
+
377
+ # Write text to LCD
378
+ # @param line_number [Integer] line number
379
+ # @param text [String] text to write
380
+ # @return [Boolean] true if successful, raises exception otherwise
381
+ def write_lcd(line_number, text)
382
+ command_string = [line_number, 0].pack('s<c') + ' ' + text.encode(@encoding, invalid: :replace, undef: :replace)
383
+ response = self.send_command(CMD_WRITE_LCD, command_string)
384
+
385
+ if response && response[:status]
386
+ true
387
+ else
388
+ raise RBZK::ZKErrorResponse, "Can't write lcd"
389
+ end
390
+ end
391
+
392
+ # Clear LCD
393
+ # @return [Boolean] true if successful, raises exception otherwise
394
+ def clear_lcd
395
+ response = self.send_command(CMD_CLEAR_LCD)
396
+
397
+ if response && response[:status]
398
+ true
399
+ else
400
+ raise RBZK::ZKErrorResponse, "Can't clear lcd"
401
+ end
402
+ end
403
+
404
+ # Refresh the device data
405
+ # @return [Boolean] true if successful, raises exception otherwise
406
+ def refresh_data
407
+ response = self.send_command(CMD_REFRESHDATA)
408
+
409
+ if response && response[:status]
410
+ true
411
+ else
412
+ raise RBZK::ZKErrorResponse, "Can't refresh data"
413
+ end
414
+ end
415
+
416
+ # Create or update user by uid
417
+ # @param uid [Integer] user ID that are generated from device
418
+ # @param name [String] name of the user
419
+ # @param privilege [Integer] user privilege level (default or admin)
420
+ # @param password [String] user password
421
+ # @param group_id [String] group ID
422
+ # @param user_id [String] your own user ID
423
+ # @param card [Integer] card number
424
+ # @return [Boolean] true if successful, raises exception otherwise
425
+ def set_user(uid: nil, name: '', privilege: 0, password: '', group_id: '', user_id: '', card: 0)
426
+ # If uid is not provided, use next_uid
427
+ if uid.nil?
428
+ uid = @next_uid
429
+ if user_id.empty?
430
+ user_id = @next_user_id
431
+ end
432
+ end
433
+
434
+ # If user_id is not provided, use uid as string
435
+ if user_id.empty?
436
+ user_id = uid.to_s # ZK6 needs uid2 == uid
437
+ end
438
+
439
+ # Validate privilege
440
+ if privilege != USER_DEFAULT && privilege != USER_ADMIN
441
+ privilege = USER_DEFAULT
442
+ end
443
+ privilege = privilege.to_i
444
+
445
+ # Create command string based on user_packet_size
446
+ if @user_packet_size == 28 # firmware == 6
447
+ group_id = 0 if group_id.empty?
448
+
449
+ begin
450
+ command_string = [uid, privilege].pack('S<C') +
451
+ password.encode(@encoding, invalid: :replace, undef: :replace).ljust(5, "\x00")[0...5] +
452
+ name.encode(@encoding, invalid: :replace, undef: :replace).ljust(8, "\x00")[0...8] +
453
+ [card.to_i, 0, group_id.to_i, 0, user_id.to_i].pack('L<CS<S<L<')
454
+ rescue => e
455
+ if @verbose
456
+ puts "Error packing user: #{e.message}"
457
+ end
458
+ raise RBZK::ZKErrorResponse, "Can't pack user"
459
+ end
460
+ else
461
+ # For other firmware versions
462
+ name_pad = name.encode(@encoding, invalid: :replace, undef: :replace).ljust(24, "\x00")[0...24]
463
+ card_str = [card.to_i].pack('L<')[0...4]
464
+ command_string = [uid, privilege].pack('S<C') +
465
+ password.encode(@encoding, invalid: :replace, undef: :replace).ljust(8, "\x00")[0...8] +
466
+ name_pad +
467
+ card_str + "\x00" +
468
+ group_id.encode(@encoding, invalid: :replace, undef: :replace).ljust(7, "\x00")[0...7] +
469
+ "\x00" +
470
+ user_id.encode(@encoding, invalid: :replace, undef: :replace).ljust(24, "\x00")[0...24]
471
+ end
472
+
473
+ # Send command
474
+ response = self.send_command(CMD_USER_WRQ, command_string, 1024)
475
+
476
+ if response && response[:status]
477
+ # Update next_uid and next_user_id if necessary
478
+ self.refresh_data
479
+ if @next_uid == uid
480
+ @next_uid += 1
481
+ end
482
+ if @next_user_id == user_id
483
+ @next_user_id = @next_uid.to_s
484
+ end
485
+ true
486
+ else
487
+ raise RBZK::ZKErrorResponse, "Can't set user"
488
+ end
489
+ end
490
+
491
+ # Delete user by uid
492
+ # @param uid [Integer] user ID that are generated from device
493
+ # @return [Boolean] true if successful, raises exception otherwise
494
+ def delete_user(uid: 0)
495
+ # Send command
496
+ command_string = [uid].pack('S<')
497
+ response = self.send_command(CMD_DELETE_USER, command_string)
498
+
499
+ if response && response[:status]
500
+ self.refresh_data
501
+ # Update next_uid if necessary
502
+ if uid == (@next_uid - 1)
503
+ @next_uid = uid
504
+ end
505
+ true
506
+ else
507
+ raise RBZK::ZKErrorResponse, "Can't delete user"
508
+ end
509
+ end
510
+
346
511
  # Helper method to read data with buffer (ZK6: 1503)
347
512
  def read_with_buffer(command, fct = 0, ext = 0)
348
513
 
@@ -712,6 +877,50 @@ module RBZK
712
877
  end
713
878
  end
714
879
 
880
+ # Send data with buffer
881
+ # @param buffer [String] data to send
882
+ # @return [Boolean] true if successful, raises exception otherwise
883
+ def send_with_buffer(buffer)
884
+ max_chunk = 1024
885
+ size = buffer.size
886
+ self.free_data
887
+
888
+ command = CMD_PREPARE_DATA
889
+ command_string = [size].pack('L<')
890
+ response = self.send_command(command, command_string)
891
+
892
+ if !response || !response[:status]
893
+ raise RBZK::ZKErrorResponse, "Can't prepare data"
894
+ end
895
+
896
+ remain = size % max_chunk
897
+ packets = (size - remain) / max_chunk
898
+ start = 0
899
+
900
+ packets.times do
901
+ send_chunk(buffer[start, max_chunk])
902
+ start += max_chunk
903
+ end
904
+
905
+ send_chunk(buffer[start, remain]) if remain > 0
906
+
907
+ true
908
+ end
909
+
910
+ # Send a chunk of data
911
+ # @param command_string [String] data to send
912
+ # @return [Boolean] true if successful, raises exception otherwise
913
+ def send_chunk(command_string)
914
+ command = CMD_DATA
915
+ response = self.send_command(command, command_string)
916
+
917
+ if response && response[:status]
918
+ true
919
+ else
920
+ raise RBZK::ZKErrorResponse, "Can't send chunk"
921
+ end
922
+ end
923
+
715
924
  # Helper method to read a chunk of data
716
925
  def read_chunk(start, size)
717
926
  if @verbose
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rbzk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Khaled AbuShqear
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-05-21 00:00:00.000000000 Z
10
+ date: 2025-05-28 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: bytes