rbzk 0.1.6 → 0.1.7
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 +1 -1
- data/bin/console +3 -3
- data/lib/rbzk/attendance.rb +19 -0
- data/lib/rbzk/cli/commands.rb +202 -207
- data/lib/rbzk/cli/config.rb +2 -2
- data/lib/rbzk/constants.rb +3 -3
- data/lib/rbzk/exceptions.rb +3 -3
- data/lib/rbzk/finger.rb +1 -1
- data/lib/rbzk/user.rb +11 -11
- data/lib/rbzk/version.rb +1 -1
- data/lib/rbzk/zk.rb +4 -2
- data/lib/rbzk.rb +7 -7
- metadata +8 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1290a1fd6cc12310c5471f045a47e0c8d5b598326d7e5db899bde02ec6bc96ee
|
|
4
|
+
data.tar.gz: 172c829144c8aa97e998c19803d5d19fd9d379f38c93170295f149f4a163c2c0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4ee4a5a83b9f129158ea253868b4033d6c0013360d67fe05ba7518a67c6baaf971eeaf764ef447638a0e029a927524ccc51e7871ef0c22ea89b98d3608579542
|
|
7
|
+
data.tar.gz: 75af5b6ac2c57fe3e0dce24be118ac1fc69cb941d8d78bb8d706da6913ddcc9cf36e669c12e2f114c366eded3be18da5e3bec956e071657250a0812acf57810d
|
data/README.md
CHANGED
|
@@ -229,7 +229,7 @@ else
|
|
|
229
229
|
# Status: Check-in, Check-out, etc. (Refer to ZK documentation for specific status codes)
|
|
230
230
|
puts " Status: #{log.status}"
|
|
231
231
|
# Punch: Fingerprint, Password, Card, etc. (Refer to ZK documentation)
|
|
232
|
-
puts " Punch Type: #{log.
|
|
232
|
+
puts " Punch Type: #{log.punch_name}"
|
|
233
233
|
puts " Timestamp: #{log.timestamp.strftime('%Y-%m-%d %H:%M:%S')}"
|
|
234
234
|
puts "---"
|
|
235
235
|
end
|
data/bin/console
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
3
|
+
require 'bundler/setup'
|
|
4
|
+
require 'rbzk'
|
|
5
5
|
|
|
6
6
|
# You can add fixtures and/or initialization code here to make experimenting
|
|
7
7
|
# with your gem easier. You can also use a different console, if you like.
|
|
@@ -10,5 +10,5 @@ require "rbzk"
|
|
|
10
10
|
# require "pry"
|
|
11
11
|
# Pry.start
|
|
12
12
|
|
|
13
|
-
require
|
|
13
|
+
require 'irb'
|
|
14
14
|
IRB.start(__FILE__)
|
data/lib/rbzk/attendance.rb
CHANGED
|
@@ -12,6 +12,25 @@ module RBZK
|
|
|
12
12
|
@uid = uid
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
+
# Helper predicate for check-in (punch==0)
|
|
16
|
+
def check_in?
|
|
17
|
+
@punch == 0
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Helper predicate for check-out (punch==1)
|
|
21
|
+
def check_out?
|
|
22
|
+
@punch == 1
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Human readable punch name (0=Check In, 1=Check Out)
|
|
26
|
+
def punch_name
|
|
27
|
+
case @punch
|
|
28
|
+
when 0 then 'Check In'
|
|
29
|
+
when 1 then 'Check Out'
|
|
30
|
+
else "Punch (#{@punch})"
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
15
34
|
def to_s
|
|
16
35
|
"#{@user_id} #{@timestamp} #{@status} #{@punch} #{@uid}"
|
|
17
36
|
end
|
data/lib/rbzk/cli/commands.rb
CHANGED
|
@@ -13,7 +13,6 @@ require 'terminal-table'
|
|
|
13
13
|
module RBZK
|
|
14
14
|
module CLI
|
|
15
15
|
class Commands < Thor
|
|
16
|
-
|
|
17
16
|
# Global options
|
|
18
17
|
class_option :ip, type: :string, desc: 'IP address of the device'
|
|
19
18
|
class_option :port, type: :numeric, desc: 'Port number (default: 4370)'
|
|
@@ -24,10 +23,13 @@ module RBZK
|
|
|
24
23
|
class_option :no_ping, type: :boolean, desc: 'Skip ping check'
|
|
25
24
|
class_option :encoding, type: :string, desc: 'Encoding for strings (default: UTF-8)'
|
|
26
25
|
|
|
27
|
-
|
|
26
|
+
def self.exit_on_failure?
|
|
27
|
+
true
|
|
28
|
+
end
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
desc 'info [IP]', 'Get device information'
|
|
30
31
|
|
|
32
|
+
def info(ip = nil)
|
|
31
33
|
# Use IP from options if not provided as argument
|
|
32
34
|
ip ||= options[:ip] || @config['ip']
|
|
33
35
|
|
|
@@ -59,16 +61,16 @@ module RBZK
|
|
|
59
61
|
if defined?(::Terminal) && defined?(::Terminal::Table)
|
|
60
62
|
# Pretty table output
|
|
61
63
|
table = ::Terminal::Table.new do |t|
|
|
62
|
-
t.title =
|
|
64
|
+
t.title = 'Device Information'
|
|
63
65
|
device_info.each do |key, value|
|
|
64
|
-
t << [
|
|
66
|
+
t << [key, value]
|
|
65
67
|
end
|
|
66
68
|
end
|
|
67
69
|
|
|
68
70
|
puts table
|
|
69
71
|
else
|
|
70
72
|
# Fallback plain text output
|
|
71
|
-
puts
|
|
73
|
+
puts 'Device Information:'
|
|
72
74
|
device_info.each do |key, value|
|
|
73
75
|
puts "#{key}: #{value}"
|
|
74
76
|
end
|
|
@@ -76,61 +78,61 @@ module RBZK
|
|
|
76
78
|
end
|
|
77
79
|
end
|
|
78
80
|
|
|
79
|
-
desc
|
|
81
|
+
desc 'refresh [IP]', 'Refresh device data'
|
|
80
82
|
|
|
81
83
|
def refresh(ip = nil)
|
|
82
84
|
# Use IP from options if not provided as argument
|
|
83
85
|
ip ||= options[:ip] || @config['ip']
|
|
84
86
|
|
|
85
87
|
with_connection(ip, options) do |conn|
|
|
86
|
-
puts
|
|
88
|
+
puts 'Refreshing device data...'
|
|
87
89
|
result = conn.refresh_data
|
|
88
|
-
puts
|
|
90
|
+
puts '✓ Device data refreshed successfully!' if result
|
|
89
91
|
end
|
|
90
92
|
end
|
|
91
93
|
|
|
92
|
-
desc
|
|
94
|
+
desc 'users [IP]', 'Get users from the device'
|
|
93
95
|
|
|
94
96
|
def users(ip = nil)
|
|
95
97
|
# Use IP from options if not provided as argument
|
|
96
98
|
ip ||= options[:ip] || @config['ip']
|
|
97
99
|
with_connection(ip, options) do |conn|
|
|
98
|
-
puts
|
|
100
|
+
puts 'Getting users...'
|
|
99
101
|
users = conn.get_users
|
|
100
102
|
display_users(users)
|
|
101
103
|
end
|
|
102
104
|
end
|
|
103
105
|
|
|
104
106
|
# Add aliases for common log commands
|
|
105
|
-
desc
|
|
106
|
-
map
|
|
107
|
+
desc 'logs-today [IP]', "Get today's attendance logs"
|
|
108
|
+
map 'logs-today' => 'logs'
|
|
107
109
|
|
|
108
110
|
def logs_today(ip = nil)
|
|
109
|
-
invoke :logs, [
|
|
111
|
+
invoke :logs, [ip], { today: true }.merge(options)
|
|
110
112
|
end
|
|
111
113
|
|
|
112
|
-
desc
|
|
113
|
-
map
|
|
114
|
+
desc 'logs-yesterday [IP]', "Get yesterday's attendance logs"
|
|
115
|
+
map 'logs-yesterday' => 'logs'
|
|
114
116
|
|
|
115
117
|
def logs_yesterday(ip = nil)
|
|
116
|
-
invoke :logs, [
|
|
118
|
+
invoke :logs, [ip], { yesterday: true }.merge(options)
|
|
117
119
|
end
|
|
118
120
|
|
|
119
|
-
desc
|
|
120
|
-
map
|
|
121
|
+
desc 'logs-week [IP]', "Get this week's attendance logs"
|
|
122
|
+
map 'logs-week' => 'logs'
|
|
121
123
|
|
|
122
124
|
def logs_week(ip = nil)
|
|
123
|
-
invoke :logs, [
|
|
125
|
+
invoke :logs, [ip], { week: true }.merge(options)
|
|
124
126
|
end
|
|
125
127
|
|
|
126
|
-
desc
|
|
127
|
-
map
|
|
128
|
+
desc 'logs-month [IP]', "Get this month's attendance logs"
|
|
129
|
+
map 'logs-month' => 'logs'
|
|
128
130
|
|
|
129
131
|
def logs_month(ip = nil)
|
|
130
|
-
invoke :logs, [
|
|
132
|
+
invoke :logs, [ip], { month: true }.merge(options)
|
|
131
133
|
end
|
|
132
134
|
|
|
133
|
-
desc
|
|
135
|
+
desc 'logs-all [IP]', 'Get all attendance logs without limit'
|
|
134
136
|
|
|
135
137
|
def logs_all(ip = nil)
|
|
136
138
|
# Use IP from options if not provided as argument
|
|
@@ -138,7 +140,7 @@ module RBZK
|
|
|
138
140
|
|
|
139
141
|
with_connection(ip, options) do |conn|
|
|
140
142
|
# Get attendance logs
|
|
141
|
-
puts
|
|
143
|
+
puts 'Getting all attendance logs (this may take a while)...'
|
|
142
144
|
logs = conn.get_attendance_logs
|
|
143
145
|
total_logs = logs.size
|
|
144
146
|
puts "Total logs: #{total_logs}" if options[:verbose]
|
|
@@ -150,15 +152,17 @@ module RBZK
|
|
|
150
152
|
if defined?(::Terminal) && defined?(::Terminal::Table)
|
|
151
153
|
# Pretty table output
|
|
152
154
|
table = ::Terminal::Table.new do |t|
|
|
153
|
-
t.title =
|
|
154
|
-
t.headings = [ 'User ID', 'Time', 'Status' ]
|
|
155
|
+
t.title = 'All Attendance Logs (Showing All Records)'
|
|
156
|
+
t.headings = ['UID', 'User ID', 'Time', 'Status', 'Punch Type']
|
|
155
157
|
|
|
156
158
|
# Show all logs in the table
|
|
157
159
|
logs.each do |log|
|
|
158
160
|
t << [
|
|
161
|
+
log.uid,
|
|
159
162
|
log.user_id,
|
|
160
163
|
log.timestamp.strftime('%Y-%m-%d %H:%M:%S'),
|
|
161
|
-
|
|
164
|
+
log.status,
|
|
165
|
+
log.punch_name
|
|
162
166
|
]
|
|
163
167
|
end
|
|
164
168
|
end
|
|
@@ -167,7 +171,7 @@ module RBZK
|
|
|
167
171
|
else
|
|
168
172
|
# Fallback plain text output
|
|
169
173
|
logs.each do |log|
|
|
170
|
-
puts " User ID: #{log.user_id}, Time: #{log.timestamp.strftime('%Y-%m-%d %H:%M:%S')}, Status: #{format_status(log.status)}"
|
|
174
|
+
puts " UID: #{log.uid}, User ID: #{log.user_id}, Time: #{log.timestamp.strftime('%Y-%m-%d %H:%M:%S')}, Status: #{format_status(log.status)}, Punch: #{log.punch_name}"
|
|
171
175
|
end
|
|
172
176
|
end
|
|
173
177
|
else
|
|
@@ -176,31 +180,32 @@ module RBZK
|
|
|
176
180
|
end
|
|
177
181
|
end
|
|
178
182
|
|
|
179
|
-
desc
|
|
183
|
+
desc 'logs-custom START_DATE END_DATE [IP]', 'Get logs for a custom date range (YYYY-MM-DD)'
|
|
180
184
|
|
|
181
185
|
def logs_custom(start_date, end_date, ip = nil)
|
|
182
186
|
# Use IP from options if not provided as argument
|
|
183
187
|
ip ||= options[:ip] || @config['ip']
|
|
184
|
-
invoke :logs, [
|
|
188
|
+
invoke :logs, [ip], { start_date: start_date, end_date: end_date }.merge(options)
|
|
185
189
|
end
|
|
186
190
|
|
|
187
|
-
desc
|
|
191
|
+
desc 'logs [IP]', 'Get attendance logs'
|
|
188
192
|
method_option :today, type: :boolean, desc: "Get only today's logs"
|
|
189
193
|
method_option :yesterday, type: :boolean, desc: "Get only yesterday's logs"
|
|
190
194
|
method_option :week, type: :boolean, desc: "Get this week's logs"
|
|
191
195
|
method_option :month, type: :boolean, desc: "Get this month's logs"
|
|
192
|
-
method_option :start_date, type: :string, desc:
|
|
193
|
-
method_option :end_date, type: :string, desc:
|
|
194
|
-
method_option :start_time, type: :string, desc:
|
|
195
|
-
method_option :end_time, type: :string, desc:
|
|
196
|
-
method_option :limit, type: :numeric, default: 25,
|
|
196
|
+
method_option :start_date, type: :string, desc: 'Start date for custom range (YYYY-MM-DD)'
|
|
197
|
+
method_option :end_date, type: :string, desc: 'End date for custom range (YYYY-MM-DD)'
|
|
198
|
+
method_option :start_time, type: :string, desc: 'Start time for custom range (HH:MM)'
|
|
199
|
+
method_option :end_time, type: :string, desc: 'End time for custom range (HH:MM)'
|
|
200
|
+
method_option :limit, type: :numeric, default: 25,
|
|
201
|
+
desc: 'Limit the number of logs displayed (default: 25, use 0 for all)'
|
|
197
202
|
|
|
198
203
|
def logs(ip = nil)
|
|
199
204
|
# Use IP from options if not provided as argument
|
|
200
205
|
ip ||= options[:ip] || @config['ip']
|
|
201
206
|
with_connection(ip, options) do |conn|
|
|
202
207
|
# Get attendance logs
|
|
203
|
-
puts
|
|
208
|
+
puts 'Getting attendance logs...'
|
|
204
209
|
logs = conn.get_attendance_logs
|
|
205
210
|
total_logs = logs.size
|
|
206
211
|
puts "Total logs: #{total_logs}" if options[:verbose]
|
|
@@ -222,7 +227,8 @@ module RBZK
|
|
|
222
227
|
elsif options[:month]
|
|
223
228
|
today = Date.today
|
|
224
229
|
start_of_month = Date.new(today.year, today.month, 1)
|
|
225
|
-
logs = filter_logs_by_datetime(logs, start_of_month, today, options[:start_time],
|
|
230
|
+
logs = filter_logs_by_datetime(logs, start_of_month, today, options[:start_time],
|
|
231
|
+
options[:end_time])
|
|
226
232
|
"This Month's Attendance Logs (#{start_of_month} to #{today})"
|
|
227
233
|
elsif options[:start_date] && options[:end_date]
|
|
228
234
|
begin
|
|
@@ -233,11 +239,12 @@ module RBZK
|
|
|
233
239
|
puts "Filtering logs from #{start_date} to #{end_date}..." if options[:verbose]
|
|
234
240
|
|
|
235
241
|
# Use the filter_logs_by_datetime method
|
|
236
|
-
logs = filter_logs_by_datetime(logs, start_date, end_date, options[:start_time],
|
|
242
|
+
logs = filter_logs_by_datetime(logs, start_date, end_date, options[:start_time],
|
|
243
|
+
options[:end_time])
|
|
237
244
|
|
|
238
245
|
"Attendance Logs (#{start_date} to #{end_date})"
|
|
239
246
|
rescue ArgumentError
|
|
240
|
-
puts
|
|
247
|
+
puts 'Error: Invalid date format. Please use YYYY-MM-DD format.'
|
|
241
248
|
return
|
|
242
249
|
end
|
|
243
250
|
elsif options[:start_date]
|
|
@@ -249,11 +256,12 @@ module RBZK
|
|
|
249
256
|
puts "Filtering logs from #{start_date} onwards..." if options[:verbose]
|
|
250
257
|
|
|
251
258
|
# Use the filter_logs_by_datetime method
|
|
252
|
-
logs = filter_logs_by_datetime(logs, start_date, end_date, options[:start_time],
|
|
259
|
+
logs = filter_logs_by_datetime(logs, start_date, end_date, options[:start_time],
|
|
260
|
+
options[:end_time])
|
|
253
261
|
|
|
254
262
|
"Attendance Logs (#{start_date} to #{end_date})"
|
|
255
263
|
rescue ArgumentError
|
|
256
|
-
puts
|
|
264
|
+
puts 'Error: Invalid date format. Please use YYYY-MM-DD format.'
|
|
257
265
|
return
|
|
258
266
|
end
|
|
259
267
|
elsif options[:end_date]
|
|
@@ -266,15 +274,16 @@ module RBZK
|
|
|
266
274
|
puts "Filtering logs from #{start_date} to #{end_date}..." if options[:verbose]
|
|
267
275
|
|
|
268
276
|
# Use the filter_logs_by_datetime method
|
|
269
|
-
logs = filter_logs_by_datetime(logs, start_date, end_date, options[:start_time],
|
|
277
|
+
logs = filter_logs_by_datetime(logs, start_date, end_date, options[:start_time],
|
|
278
|
+
options[:end_time])
|
|
270
279
|
|
|
271
280
|
"Attendance Logs (#{start_date} to #{end_date})"
|
|
272
281
|
rescue ArgumentError
|
|
273
|
-
puts
|
|
282
|
+
puts 'Error: Invalid date format. Please use YYYY-MM-DD format.'
|
|
274
283
|
return
|
|
275
284
|
end
|
|
276
285
|
else
|
|
277
|
-
|
|
286
|
+
'All Attendance Logs'
|
|
278
287
|
end
|
|
279
288
|
|
|
280
289
|
# Display logs
|
|
@@ -288,15 +297,17 @@ module RBZK
|
|
|
288
297
|
if defined?(::Terminal) && defined?(::Terminal::Table)
|
|
289
298
|
# Pretty table output
|
|
290
299
|
table = ::Terminal::Table.new do |t|
|
|
291
|
-
t.title = title ||
|
|
292
|
-
t.headings = [ 'User ID', 'Time', 'Status' ]
|
|
300
|
+
t.title = title || 'Attendance Logs'
|
|
301
|
+
t.headings = ['UID', 'User ID', 'Time', 'Status', 'Punch Type']
|
|
293
302
|
|
|
294
303
|
# Show logs in the table based on limit
|
|
295
304
|
display_logs.each do |log|
|
|
296
305
|
t << [
|
|
306
|
+
log.uid,
|
|
297
307
|
log.user_id,
|
|
298
308
|
log.timestamp.strftime('%Y-%m-%d %H:%M:%S'),
|
|
299
|
-
|
|
309
|
+
log.status.to_s,
|
|
310
|
+
log.punch_name
|
|
300
311
|
]
|
|
301
312
|
end
|
|
302
313
|
end
|
|
@@ -310,12 +321,10 @@ module RBZK
|
|
|
310
321
|
else
|
|
311
322
|
# Fallback plain text output
|
|
312
323
|
display_logs.each do |log|
|
|
313
|
-
puts " User ID: #{log.user_id}, Time: #{log.timestamp.strftime('%Y-%m-%d %H:%M:%S')}, Status: #{format_status(log.status)}"
|
|
324
|
+
puts " UID: #{log.uid}, User ID: #{log.user_id}, Time: #{log.timestamp.strftime('%Y-%m-%d %H:%M:%S')}, Status: #{format_status(log.status)}, Punch: #{log.punch_name}"
|
|
314
325
|
end
|
|
315
326
|
|
|
316
|
-
if logs.size > display_logs.size
|
|
317
|
-
puts " ... and #{logs.size - display_logs.size} more records"
|
|
318
|
-
end
|
|
327
|
+
puts " ... and #{logs.size - display_logs.size} more records" if logs.size > display_logs.size
|
|
319
328
|
end
|
|
320
329
|
else
|
|
321
330
|
puts "\nNo attendance records found"
|
|
@@ -323,26 +332,25 @@ module RBZK
|
|
|
323
332
|
end
|
|
324
333
|
end
|
|
325
334
|
|
|
326
|
-
desc
|
|
327
|
-
map
|
|
335
|
+
desc 'clear-logs [IP]', 'Clear attendance logs'
|
|
336
|
+
map 'clear-logs' => :clear_logs
|
|
328
337
|
|
|
329
338
|
def clear_logs(ip = nil)
|
|
330
|
-
|
|
331
339
|
# Use IP from options if not provided as argument
|
|
332
340
|
ip ||= options[:ip] || @config['ip']
|
|
333
341
|
with_connection(ip, options) do |conn|
|
|
334
|
-
puts
|
|
335
|
-
return unless yes?(
|
|
342
|
+
puts 'WARNING: This will delete all attendance logs from the device.'
|
|
343
|
+
return unless yes?('Are you sure you want to continue? (y/N)')
|
|
336
344
|
|
|
337
|
-
puts
|
|
345
|
+
puts 'Clearing attendance logs...'
|
|
338
346
|
result = conn.clear_attendance
|
|
339
|
-
puts
|
|
347
|
+
puts '✓ Attendance logs cleared successfully!' if result
|
|
340
348
|
end
|
|
341
349
|
end
|
|
342
350
|
|
|
343
|
-
desc
|
|
344
|
-
method_option :time, type: :numeric, default: 3, desc:
|
|
345
|
-
map
|
|
351
|
+
desc 'unlock [IP]', 'Unlock the door'
|
|
352
|
+
method_option :time, type: :numeric, default: 3, desc: 'Unlock time in seconds (default: 3)'
|
|
353
|
+
map 'unlock' => :unlock_door
|
|
346
354
|
|
|
347
355
|
def unlock_door(ip = nil)
|
|
348
356
|
# Use IP from options if not provided as argument
|
|
@@ -354,12 +362,12 @@ module RBZK
|
|
|
354
362
|
with_connection(ip, options) do |conn|
|
|
355
363
|
puts "Unlocking door for #{time} seconds..."
|
|
356
364
|
result = conn.unlock(time)
|
|
357
|
-
puts
|
|
365
|
+
puts '✓ Door unlocked successfully!' if result
|
|
358
366
|
end
|
|
359
367
|
end
|
|
360
368
|
|
|
361
|
-
desc
|
|
362
|
-
map
|
|
369
|
+
desc 'door-state [IP]', 'Get the door lock state'
|
|
370
|
+
map 'door-state' => :door_state
|
|
363
371
|
|
|
364
372
|
def door_state(ip = nil)
|
|
365
373
|
# Use IP from options if not provided as argument
|
|
@@ -371,8 +379,8 @@ module RBZK
|
|
|
371
379
|
end
|
|
372
380
|
end
|
|
373
381
|
|
|
374
|
-
desc
|
|
375
|
-
map
|
|
382
|
+
desc 'write-lcd [IP] LINE_NUMBER TEXT', 'Write text to LCD display'
|
|
383
|
+
map 'write-lcd' => :write_lcd
|
|
376
384
|
|
|
377
385
|
def write_lcd(line_number, text, ip = nil)
|
|
378
386
|
# Use IP from options if not provided as argument
|
|
@@ -384,59 +392,59 @@ module RBZK
|
|
|
384
392
|
with_connection(ip, options) do |conn|
|
|
385
393
|
puts "Writing text to LCD line #{line_number}..."
|
|
386
394
|
result = conn.write_lcd(line_number, text)
|
|
387
|
-
puts
|
|
395
|
+
puts '✓ Text written to LCD successfully!' if result
|
|
388
396
|
end
|
|
389
397
|
end
|
|
390
398
|
|
|
391
|
-
desc
|
|
392
|
-
map
|
|
399
|
+
desc 'clear-lcd [IP]', 'Clear the LCD display'
|
|
400
|
+
map 'clear-lcd' => :clear_lcd
|
|
393
401
|
|
|
394
402
|
def clear_lcd(ip = nil)
|
|
395
403
|
# Use IP from options if not provided as argument
|
|
396
404
|
ip ||= options[:ip] || @config['ip']
|
|
397
405
|
|
|
398
406
|
with_connection(ip, options) do |conn|
|
|
399
|
-
puts
|
|
407
|
+
puts 'Clearing LCD display...'
|
|
400
408
|
result = conn.clear_lcd
|
|
401
|
-
puts
|
|
409
|
+
puts '✓ LCD cleared successfully!' if result
|
|
402
410
|
end
|
|
403
411
|
end
|
|
404
412
|
|
|
405
|
-
desc
|
|
406
|
-
method_option :uid, type: :numeric, desc:
|
|
407
|
-
method_option :name, type: :string, default:
|
|
408
|
-
method_option :privilege, type: :numeric, default: 0, desc:
|
|
409
|
-
method_option :password, type: :string, default:
|
|
410
|
-
method_option :group_id, type: :string, default:
|
|
411
|
-
method_option :user_id, type: :string, default:
|
|
412
|
-
method_option :card, type: :numeric, default: 0, desc:
|
|
413
|
-
map
|
|
413
|
+
desc 'add-user [IP]', 'Add or update a user'
|
|
414
|
+
method_option :uid, type: :numeric, desc: 'User ID (generated by device if not provided)'
|
|
415
|
+
method_option :name, type: :string, default: '', desc: 'User name'
|
|
416
|
+
method_option :privilege, type: :numeric, default: 0, desc: 'User privilege (0=User, 14=Admin)'
|
|
417
|
+
method_option :password, type: :string, default: '', desc: 'User password'
|
|
418
|
+
method_option :group_id, type: :string, default: '', desc: 'Group ID'
|
|
419
|
+
method_option :user_id, type: :string, default: '', desc: 'Custom user ID'
|
|
420
|
+
method_option :card, type: :numeric, default: 0, desc: 'Card number'
|
|
421
|
+
map 'add-user' => :add_user
|
|
414
422
|
|
|
415
423
|
def add_user(ip = nil)
|
|
416
424
|
# Use IP from options if not provided as argument
|
|
417
425
|
ip ||= options[:ip] || @config['ip']
|
|
418
426
|
|
|
419
427
|
with_connection(ip, options) do |conn|
|
|
420
|
-
puts
|
|
428
|
+
puts 'Adding/updating user...'
|
|
421
429
|
|
|
422
430
|
result = conn.set_user(
|
|
423
431
|
uid: options[:uid],
|
|
424
|
-
name: options[:name] ||
|
|
432
|
+
name: options[:name] || '',
|
|
425
433
|
privilege: options[:privilege] || 0,
|
|
426
|
-
password: options[:password] ||
|
|
427
|
-
group_id: options[:group_id] ||
|
|
428
|
-
user_id: options[:user_id] ||
|
|
434
|
+
password: options[:password] || '',
|
|
435
|
+
group_id: options[:group_id] || '',
|
|
436
|
+
user_id: options[:user_id] || '',
|
|
429
437
|
card: options[:card] || 0
|
|
430
438
|
)
|
|
431
439
|
|
|
432
|
-
puts
|
|
440
|
+
puts '✓ User added/updated successfully!' if result
|
|
433
441
|
end
|
|
434
442
|
end
|
|
435
443
|
|
|
436
|
-
desc
|
|
437
|
-
method_option :uid, type: :numeric, desc:
|
|
438
|
-
method_option :user_id, type: :string, desc:
|
|
439
|
-
map
|
|
444
|
+
desc 'delete-user [IP]', 'Delete a user'
|
|
445
|
+
method_option :uid, type: :numeric, desc: 'User ID (generated by device)'
|
|
446
|
+
method_option :user_id, type: :string, desc: 'Custom user ID'
|
|
447
|
+
map 'delete-user' => :delete_user
|
|
440
448
|
|
|
441
449
|
def delete_user(ip = nil)
|
|
442
450
|
# Use IP from options if not provided as argument
|
|
@@ -444,33 +452,33 @@ module RBZK
|
|
|
444
452
|
|
|
445
453
|
# Ensure at least one of uid or user_id is provided
|
|
446
454
|
if options[:uid].nil? && (options[:user_id].nil? || options[:user_id].empty?)
|
|
447
|
-
puts
|
|
455
|
+
puts 'Error: You must provide either --uid'
|
|
448
456
|
return
|
|
449
457
|
end
|
|
450
458
|
|
|
451
459
|
with_connection(ip, options) do |conn|
|
|
452
|
-
puts
|
|
460
|
+
puts 'Deleting user...'
|
|
453
461
|
|
|
454
462
|
# Use the User object with delete_user
|
|
455
463
|
result = conn.delete_user(uid: options[:uid])
|
|
456
464
|
|
|
457
465
|
if result
|
|
458
|
-
puts
|
|
466
|
+
puts '✓ User deleted successfully!'
|
|
459
467
|
else
|
|
460
|
-
puts
|
|
468
|
+
puts '✗ User not found or could not be deleted.'
|
|
461
469
|
end
|
|
462
470
|
end
|
|
463
471
|
end
|
|
464
472
|
|
|
465
|
-
desc
|
|
466
|
-
map
|
|
473
|
+
desc 'get-templates [IP]', 'Get all fingerprint templates'
|
|
474
|
+
map 'get-templates' => :get_templates
|
|
467
475
|
|
|
468
476
|
def get_templates(ip = nil)
|
|
469
477
|
# Use IP from options if not provided as argument
|
|
470
478
|
ip ||= options[:ip] || @config['ip']
|
|
471
479
|
|
|
472
480
|
with_connection(ip, options) do |conn|
|
|
473
|
-
puts
|
|
481
|
+
puts 'Getting fingerprint templates...'
|
|
474
482
|
templates = conn.get_templates
|
|
475
483
|
|
|
476
484
|
if templates && !templates.empty?
|
|
@@ -478,8 +486,8 @@ module RBZK
|
|
|
478
486
|
|
|
479
487
|
# Use Terminal::Table for pretty output
|
|
480
488
|
table = ::Terminal::Table.new do |t|
|
|
481
|
-
t.title =
|
|
482
|
-
t.headings = [
|
|
489
|
+
t.title = 'Fingerprint Templates'
|
|
490
|
+
t.headings = ['UID', 'Finger ID', 'Valid', 'Size']
|
|
483
491
|
|
|
484
492
|
templates.each do |template|
|
|
485
493
|
t << [
|
|
@@ -493,16 +501,16 @@ module RBZK
|
|
|
493
501
|
|
|
494
502
|
puts table
|
|
495
503
|
else
|
|
496
|
-
puts
|
|
504
|
+
puts '✓ No fingerprint templates found'
|
|
497
505
|
end
|
|
498
506
|
end
|
|
499
507
|
end
|
|
500
508
|
|
|
501
|
-
desc
|
|
502
|
-
method_option :uid, type: :numeric, desc:
|
|
503
|
-
method_option :user_id, type: :string, desc:
|
|
504
|
-
method_option :finger_id, type: :numeric, default: 0, desc:
|
|
505
|
-
map
|
|
509
|
+
desc 'get-user-template [IP]', "Get a specific user's fingerprint template"
|
|
510
|
+
method_option :uid, type: :numeric, desc: 'User ID (generated by device)'
|
|
511
|
+
method_option :user_id, type: :string, desc: 'Custom user ID'
|
|
512
|
+
method_option :finger_id, type: :numeric, default: 0, desc: 'Finger ID (0-9)'
|
|
513
|
+
map 'get-user-template' => :get_user_template
|
|
506
514
|
|
|
507
515
|
def get_user_template(ip = nil)
|
|
508
516
|
# Use IP from options if not provided as argument
|
|
@@ -510,37 +518,37 @@ module RBZK
|
|
|
510
518
|
|
|
511
519
|
# Ensure at least one of uid or user_id is provided
|
|
512
520
|
if options[:uid].nil? && (options[:user_id].nil? || options[:user_id].empty?)
|
|
513
|
-
puts
|
|
521
|
+
puts 'Error: You must provide either --uid or --user-id'
|
|
514
522
|
return
|
|
515
523
|
end
|
|
516
524
|
|
|
517
525
|
with_connection(ip, options) do |conn|
|
|
518
|
-
puts
|
|
526
|
+
puts 'Getting user fingerprint template...'
|
|
519
527
|
|
|
520
528
|
# Extract parameters from options
|
|
521
529
|
uid = options[:uid] || 0
|
|
522
|
-
user_id = options[:user_id] ||
|
|
530
|
+
user_id = options[:user_id] || ''
|
|
523
531
|
finger_id = options[:finger_id] || 0
|
|
524
532
|
|
|
525
533
|
template = conn.get_user_template(uid: uid, temp_id: finger_id, user_id: user_id)
|
|
526
534
|
|
|
527
535
|
if template
|
|
528
|
-
puts
|
|
536
|
+
puts '✓ Found fingerprint template:'
|
|
529
537
|
puts " User ID: #{template.uid}"
|
|
530
538
|
puts " Finger ID: #{template.fid}"
|
|
531
539
|
puts " Valid: #{template.valid == 1 ? 'Yes' : 'No'}"
|
|
532
540
|
puts " Size: #{template.size} bytes"
|
|
533
541
|
else
|
|
534
|
-
puts
|
|
542
|
+
puts '✗ Fingerprint template not found'
|
|
535
543
|
end
|
|
536
544
|
end
|
|
537
545
|
end
|
|
538
546
|
|
|
539
|
-
desc
|
|
540
|
-
method_option :uid, type: :numeric, desc:
|
|
541
|
-
method_option :user_id, type: :string, desc:
|
|
542
|
-
method_option :finger_id, type: :numeric, default: 0, desc:
|
|
543
|
-
map
|
|
547
|
+
desc 'delete-template [IP]', 'Delete a specific fingerprint template'
|
|
548
|
+
method_option :uid, type: :numeric, desc: 'User ID (generated by device)'
|
|
549
|
+
method_option :user_id, type: :string, desc: 'Custom user ID'
|
|
550
|
+
method_option :finger_id, type: :numeric, default: 0, desc: 'Finger ID (0-9)'
|
|
551
|
+
map 'delete-template' => :delete_template
|
|
544
552
|
|
|
545
553
|
def delete_template(ip = nil)
|
|
546
554
|
# Use IP from options if not provided as argument
|
|
@@ -548,31 +556,31 @@ module RBZK
|
|
|
548
556
|
|
|
549
557
|
# Ensure at least one of uid or user_id is provided
|
|
550
558
|
if options[:uid].nil? && (options[:user_id].nil? || options[:user_id].empty?)
|
|
551
|
-
puts
|
|
559
|
+
puts 'Error: You must provide either --uid or --user-id'
|
|
552
560
|
return
|
|
553
561
|
end
|
|
554
562
|
|
|
555
563
|
with_connection(ip, options) do |conn|
|
|
556
|
-
puts
|
|
564
|
+
puts 'Deleting fingerprint template...'
|
|
557
565
|
|
|
558
566
|
# Extract parameters from options
|
|
559
567
|
uid = options[:uid] || 0
|
|
560
|
-
user_id = options[:user_id] ||
|
|
568
|
+
user_id = options[:user_id] || ''
|
|
561
569
|
finger_id = options[:finger_id] || 0
|
|
562
570
|
|
|
563
571
|
result = conn.delete_user_template(uid: uid, temp_id: finger_id, user_id: user_id)
|
|
564
572
|
|
|
565
573
|
if result
|
|
566
|
-
puts
|
|
574
|
+
puts '✓ Fingerprint template deleted successfully!'
|
|
567
575
|
else
|
|
568
|
-
puts
|
|
576
|
+
puts '✗ Fingerprint template not found or could not be deleted'
|
|
569
577
|
end
|
|
570
578
|
end
|
|
571
579
|
end
|
|
572
580
|
|
|
573
|
-
desc
|
|
574
|
-
method_option :index, type: :numeric, desc:
|
|
575
|
-
map
|
|
581
|
+
desc 'test-voice [IP]', 'Test the device voice'
|
|
582
|
+
method_option :index, type: :numeric, desc: 'Sound index to play (0-35, default: 0)'
|
|
583
|
+
map 'test-voice' => :test_voice
|
|
576
584
|
|
|
577
585
|
def test_voice(ip = nil)
|
|
578
586
|
# Use IP from options if not provided as argument
|
|
@@ -583,89 +591,89 @@ module RBZK
|
|
|
583
591
|
|
|
584
592
|
# Print available sound indices if verbose
|
|
585
593
|
if options[:verbose]
|
|
586
|
-
puts
|
|
587
|
-
puts
|
|
588
|
-
puts
|
|
589
|
-
puts
|
|
590
|
-
puts
|
|
591
|
-
puts
|
|
592
|
-
puts
|
|
593
|
-
puts
|
|
594
|
-
puts
|
|
595
|
-
puts
|
|
596
|
-
puts
|
|
597
|
-
puts
|
|
598
|
-
puts
|
|
599
|
-
puts
|
|
600
|
-
puts
|
|
601
|
-
puts
|
|
602
|
-
puts
|
|
603
|
-
puts
|
|
604
|
-
puts
|
|
605
|
-
puts
|
|
606
|
-
puts
|
|
607
|
-
puts
|
|
608
|
-
puts
|
|
609
|
-
puts
|
|
610
|
-
puts
|
|
611
|
-
puts
|
|
612
|
-
puts
|
|
594
|
+
puts 'Available sound indices:'
|
|
595
|
+
puts ' 0: Thank You'
|
|
596
|
+
puts ' 1: Incorrect Password'
|
|
597
|
+
puts ' 2: Access Denied'
|
|
598
|
+
puts ' 3: Invalid ID'
|
|
599
|
+
puts ' 4: Please try again'
|
|
600
|
+
puts ' 5: Duplicate ID'
|
|
601
|
+
puts ' 6: The clock is flow'
|
|
602
|
+
puts ' 7: The clock is full'
|
|
603
|
+
puts ' 8: Duplicate finger'
|
|
604
|
+
puts ' 9: Duplicated punch'
|
|
605
|
+
puts '10: Beep kuko'
|
|
606
|
+
puts '11: Beep siren'
|
|
607
|
+
puts '13: Beep bell'
|
|
608
|
+
puts '18: Windows(R) opening sound'
|
|
609
|
+
puts '20: Fingerprint not emolt'
|
|
610
|
+
puts '21: Password not emolt'
|
|
611
|
+
puts '22: Badges not emolt'
|
|
612
|
+
puts '23: Face not emolt'
|
|
613
|
+
puts '24: Beep standard'
|
|
614
|
+
puts '30: Invalid user'
|
|
615
|
+
puts '31: Invalid time period'
|
|
616
|
+
puts '32: Invalid combination'
|
|
617
|
+
puts '33: Illegal Access'
|
|
618
|
+
puts '34: Disk space full'
|
|
619
|
+
puts '35: Duplicate fingerprint'
|
|
620
|
+
puts '51: Focus eyes on the green box'
|
|
613
621
|
end
|
|
614
622
|
|
|
615
623
|
with_connection(ip, options) do |conn|
|
|
616
624
|
puts "Testing device voice with index #{index}..."
|
|
617
625
|
result = conn.test_voice(index)
|
|
618
|
-
puts
|
|
626
|
+
puts '✓ Voice test successful!' if result
|
|
619
627
|
end
|
|
620
628
|
end
|
|
621
629
|
|
|
622
|
-
desc
|
|
630
|
+
desc 'restart [IP]', 'Restart the device'
|
|
623
631
|
|
|
624
632
|
def restart(ip = nil)
|
|
625
633
|
# Use IP from options if not provided as argument
|
|
626
634
|
ip ||= options[:ip] || @config['ip']
|
|
627
635
|
|
|
628
|
-
if yes?(
|
|
636
|
+
if yes?('Are you sure you want to restart the device? (y/N)')
|
|
629
637
|
with_connection(ip, options.merge(skip_disconnect_after_yield: true)) do |conn|
|
|
630
|
-
puts
|
|
638
|
+
puts 'Restarting device...'
|
|
631
639
|
result = conn.restart
|
|
632
|
-
puts
|
|
633
|
-
puts
|
|
640
|
+
puts '✓ Device restart command sent successfully!' if result
|
|
641
|
+
puts 'The device will restart now. You may need to wait a few moments before reconnecting.'
|
|
634
642
|
end
|
|
635
643
|
else
|
|
636
|
-
puts
|
|
644
|
+
puts 'Operation cancelled.'
|
|
637
645
|
end
|
|
638
646
|
end
|
|
639
647
|
|
|
640
|
-
desc
|
|
648
|
+
desc 'poweroff [IP]', 'Power off the device'
|
|
641
649
|
|
|
642
650
|
def poweroff(ip = nil)
|
|
643
651
|
# Use IP from options if not provided as argument
|
|
644
652
|
ip ||= options[:ip] || @config['ip']
|
|
645
653
|
|
|
646
|
-
if yes?(
|
|
654
|
+
if yes?('Are you sure you want to power off the device? (y/N)')
|
|
647
655
|
with_connection(ip, options.merge(skip_disconnect_after_yield: true)) do |conn|
|
|
648
|
-
puts
|
|
656
|
+
puts 'Powering off device...'
|
|
649
657
|
result = conn.poweroff
|
|
650
|
-
puts
|
|
651
|
-
puts
|
|
658
|
+
puts '✓ Device poweroff command sent successfully!' if result
|
|
659
|
+
puts 'The device will power off now. You will need to manually power it back on.'
|
|
652
660
|
end
|
|
653
661
|
else
|
|
654
|
-
puts
|
|
662
|
+
puts 'Operation cancelled.'
|
|
655
663
|
end
|
|
656
664
|
end
|
|
657
665
|
|
|
658
|
-
desc
|
|
666
|
+
desc 'config', 'Show current configuration'
|
|
659
667
|
|
|
660
668
|
def config
|
|
661
|
-
puts
|
|
662
|
-
puts
|
|
669
|
+
puts 'RBZK Configuration'
|
|
670
|
+
puts '=================='
|
|
663
671
|
@config.to_h.each do |key, value|
|
|
664
672
|
puts "#{key}: #{value}"
|
|
665
673
|
end
|
|
666
674
|
end
|
|
667
675
|
|
|
668
|
-
desc
|
|
676
|
+
desc 'config-set KEY VALUE', 'Set a configuration value'
|
|
669
677
|
|
|
670
678
|
def config_set(key, value)
|
|
671
679
|
# Convert value to appropriate type
|
|
@@ -683,16 +691,16 @@ module RBZK
|
|
|
683
691
|
puts "Configuration updated: #{key} = #{typed_value}"
|
|
684
692
|
end
|
|
685
693
|
|
|
686
|
-
desc
|
|
694
|
+
desc 'config-reset', 'Reset configuration to defaults'
|
|
687
695
|
|
|
688
696
|
def config_reset
|
|
689
|
-
if yes?(
|
|
697
|
+
if yes?('Are you sure you want to reset all configuration to defaults? (y/N)')
|
|
690
698
|
FileUtils.rm_f(@config.config_file)
|
|
691
699
|
@config = RBZK::CLI::Config.new
|
|
692
|
-
puts
|
|
700
|
+
puts 'Configuration reset to defaults.'
|
|
693
701
|
invoke :config
|
|
694
702
|
else
|
|
695
|
-
puts
|
|
703
|
+
puts 'Operation cancelled.'
|
|
696
704
|
end
|
|
697
705
|
end
|
|
698
706
|
|
|
@@ -708,7 +716,7 @@ module RBZK
|
|
|
708
716
|
skip_disconnect = local_opts.delete(:skip_disconnect_after_yield) || false
|
|
709
717
|
|
|
710
718
|
puts "Connecting to ZKTeco device at #{ip}:#{local_opts[:port] || @config['port'] || 4370}..." # Use local_opts
|
|
711
|
-
puts
|
|
719
|
+
puts 'Please ensure the device is powered on and connected to the network.'
|
|
712
720
|
|
|
713
721
|
conn = nil # Initialize conn for safety in ensure block
|
|
714
722
|
begin
|
|
@@ -727,29 +735,30 @@ module RBZK
|
|
|
727
735
|
conn = zk.connect
|
|
728
736
|
|
|
729
737
|
if conn.connected?
|
|
730
|
-
puts
|
|
738
|
+
puts '✓ Connected successfully!' unless local_opts[:quiet] # Use local_opts
|
|
731
739
|
yield conn if block_given?
|
|
732
740
|
else
|
|
733
|
-
puts
|
|
741
|
+
puts '✗ Failed to connect to device.'
|
|
734
742
|
end
|
|
735
743
|
rescue RBZK::ZKNetworkError => e
|
|
736
744
|
puts "✗ Network Error: #{e.message}"
|
|
737
|
-
puts
|
|
745
|
+
puts 'Please check the IP address and ensure the device is reachable.'
|
|
738
746
|
rescue RBZK::ZKErrorResponse => e
|
|
739
747
|
puts "✗ Device Error: #{e.message}"
|
|
740
|
-
puts
|
|
741
|
-
rescue => e
|
|
748
|
+
puts 'The device returned an error response.'
|
|
749
|
+
rescue StandardError => e
|
|
742
750
|
puts "✗ Unexpected Error: #{e.message}"
|
|
743
|
-
puts
|
|
751
|
+
puts 'An unexpected error occurred while communicating with the device.'
|
|
744
752
|
puts e.backtrace.join("\n") if local_opts[:verbose] # Use local_opts
|
|
745
753
|
ensure
|
|
746
754
|
if conn && conn.connected?
|
|
747
755
|
if skip_disconnect
|
|
748
|
-
|
|
756
|
+
# Use local_opts
|
|
757
|
+
puts 'Skipping disconnect as device is undergoing restart/poweroff.' unless local_opts[:quiet]
|
|
749
758
|
else
|
|
750
|
-
puts
|
|
759
|
+
puts 'Disconnecting from device...' unless local_opts[:quiet] # Use local_opts
|
|
751
760
|
conn.disconnect
|
|
752
|
-
puts
|
|
761
|
+
puts '✓ Disconnected' unless local_opts[:quiet] # Use local_opts
|
|
753
762
|
end
|
|
754
763
|
end
|
|
755
764
|
end
|
|
@@ -761,8 +770,8 @@ module RBZK
|
|
|
761
770
|
|
|
762
771
|
# Use Terminal::Table for pretty output
|
|
763
772
|
table = ::Terminal::Table.new do |t|
|
|
764
|
-
t.title =
|
|
765
|
-
t.headings = [
|
|
773
|
+
t.title = 'Users'
|
|
774
|
+
t.headings = ['UID', 'User ID', 'Name', 'Privilege', 'Password', 'Group ID', 'Card']
|
|
766
775
|
|
|
767
776
|
users.each do |user|
|
|
768
777
|
t << [
|
|
@@ -770,7 +779,7 @@ module RBZK
|
|
|
770
779
|
user.user_id,
|
|
771
780
|
user.name,
|
|
772
781
|
format_privilege(user.privilege),
|
|
773
|
-
|
|
782
|
+
user.password.nil? || user.password.empty? ? '(none)' : '(set)',
|
|
774
783
|
user.group_id,
|
|
775
784
|
user.card
|
|
776
785
|
]
|
|
@@ -779,7 +788,7 @@ module RBZK
|
|
|
779
788
|
|
|
780
789
|
puts table
|
|
781
790
|
else
|
|
782
|
-
puts
|
|
791
|
+
puts '✓ No users found'
|
|
783
792
|
end
|
|
784
793
|
end
|
|
785
794
|
|
|
@@ -799,32 +808,18 @@ module RBZK
|
|
|
799
808
|
log.timestamp >= start_datetime && log.timestamp <= end_datetime
|
|
800
809
|
end
|
|
801
810
|
|
|
802
|
-
if options[:verbose]
|
|
803
|
-
puts "Filtered logs: #{filtered_logs.size} of #{logs.size}"
|
|
804
|
-
end
|
|
811
|
+
puts "Filtered logs: #{filtered_logs.size} of #{logs.size}" if options[:verbose]
|
|
805
812
|
|
|
806
813
|
filtered_logs
|
|
807
814
|
end
|
|
808
815
|
|
|
809
|
-
def format_status(status)
|
|
810
|
-
case status
|
|
811
|
-
when 0 then "Check In"
|
|
812
|
-
when 1 then "Check Out"
|
|
813
|
-
when 2 then "Break Out"
|
|
814
|
-
when 3 then "Break In"
|
|
815
|
-
when 4 then "Overtime In"
|
|
816
|
-
when 5 then "Overtime Out"
|
|
817
|
-
else "Unknown (#{status})"
|
|
818
|
-
end
|
|
819
|
-
end
|
|
820
|
-
|
|
821
816
|
def format_privilege(privilege)
|
|
822
817
|
case privilege
|
|
823
|
-
when 0 then
|
|
824
|
-
when 1 then
|
|
825
|
-
when 2 then
|
|
826
|
-
when 3 then
|
|
827
|
-
when 14 then
|
|
818
|
+
when 0 then 'User'
|
|
819
|
+
when 1 then 'Enroller'
|
|
820
|
+
when 2 then 'Manager'
|
|
821
|
+
when 3 then 'Administrator'
|
|
822
|
+
when 14 then 'Super Admin'
|
|
828
823
|
else "Unknown (#{privilege})"
|
|
829
824
|
end
|
|
830
825
|
end
|
data/lib/rbzk/cli/config.rb
CHANGED
|
@@ -42,7 +42,7 @@ module RBZK
|
|
|
42
42
|
def save
|
|
43
43
|
# Create the directory if it doesn't exist
|
|
44
44
|
FileUtils.mkdir_p(File.dirname(@config_file))
|
|
45
|
-
|
|
45
|
+
|
|
46
46
|
# Save the configuration
|
|
47
47
|
File.open(@config_file, 'w') do |f|
|
|
48
48
|
f.write(YAML.dump(@config))
|
|
@@ -68,7 +68,7 @@ module RBZK
|
|
|
68
68
|
begin
|
|
69
69
|
config = YAML.load_file(@config_file)
|
|
70
70
|
return DEFAULT_CONFIG.merge(config) if config.is_a?(Hash)
|
|
71
|
-
rescue => e
|
|
71
|
+
rescue StandardError => e
|
|
72
72
|
warn "Error loading configuration file: #{e.message}"
|
|
73
73
|
end
|
|
74
74
|
end
|
data/lib/rbzk/constants.rb
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module RBZK
|
|
4
4
|
module Constants
|
|
5
|
-
USHRT_MAX =
|
|
5
|
+
USHRT_MAX = 65_535
|
|
6
6
|
|
|
7
7
|
# Command codes
|
|
8
8
|
CMD_DB_RRQ = 7 # Read in some kind of data from the machine
|
|
@@ -121,8 +121,8 @@ module RBZK
|
|
|
121
121
|
# Machine constants
|
|
122
122
|
# These values are in little-endian format when packed
|
|
123
123
|
# 0x5050 = 'PP' in ASCII when packed as '<H'
|
|
124
|
-
MACHINE_PREPARE_DATA_1 =
|
|
124
|
+
MACHINE_PREPARE_DATA_1 = 20_560 # 0x5050 = 'P' + 'P'*256 in little-endian
|
|
125
125
|
# 0x7D82 = 0x827D in little-endian format
|
|
126
|
-
MACHINE_PREPARE_DATA_2 =
|
|
126
|
+
MACHINE_PREPARE_DATA_2 = 32_130 # 0x7D82
|
|
127
127
|
end
|
|
128
128
|
end
|
data/lib/rbzk/exceptions.rb
CHANGED
|
@@ -4,19 +4,19 @@ module RBZK
|
|
|
4
4
|
class ZKError < StandardError; end
|
|
5
5
|
|
|
6
6
|
class ZKNetworkError < ZKError
|
|
7
|
-
def initialize(msg =
|
|
7
|
+
def initialize(msg = 'Network error')
|
|
8
8
|
super
|
|
9
9
|
end
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
class ZKErrorConnection < ZKError
|
|
13
|
-
def initialize(msg =
|
|
13
|
+
def initialize(msg = 'Connection error')
|
|
14
14
|
super
|
|
15
15
|
end
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
class ZKErrorResponse < ZKError
|
|
19
|
-
def initialize(msg =
|
|
19
|
+
def initialize(msg = 'Invalid response')
|
|
20
20
|
super
|
|
21
21
|
end
|
|
22
22
|
end
|
data/lib/rbzk/finger.rb
CHANGED
data/lib/rbzk/user.rb
CHANGED
|
@@ -4,7 +4,7 @@ module RBZK
|
|
|
4
4
|
class User
|
|
5
5
|
attr_accessor :uid, :user_id, :name, :privilege, :password, :group_id, :card
|
|
6
6
|
|
|
7
|
-
@@encoding =
|
|
7
|
+
@@encoding = 'UTF-8'
|
|
8
8
|
|
|
9
9
|
def self.encoding=(encoding)
|
|
10
10
|
@@encoding = encoding
|
|
@@ -17,7 +17,7 @@ module RBZK
|
|
|
17
17
|
# Match Python's User constructor exactly
|
|
18
18
|
# In Python:
|
|
19
19
|
# def __init__(self, uid, name, privilege, password='', group_id='', user_id='', card=0):
|
|
20
|
-
def initialize(uid = 0, name =
|
|
20
|
+
def initialize(uid = 0, name = '', privilege = 0, password = '', group_id = '', user_id = '', card = 0)
|
|
21
21
|
@uid = uid
|
|
22
22
|
@name = name
|
|
23
23
|
@privilege = privilege
|
|
@@ -30,20 +30,20 @@ module RBZK
|
|
|
30
30
|
# Pack the user data into a binary string for ZK6 devices (size 29)
|
|
31
31
|
def repack29
|
|
32
32
|
[2, @uid, @privilege].pack('CS<C') +
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
36
|
end
|
|
37
37
|
|
|
38
38
|
# Pack the user data into a binary string for ZK8 devices (size 73)
|
|
39
39
|
def repack73
|
|
40
40
|
[2, @uid, @privilege].pack('CS<C') +
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
47
|
end
|
|
48
48
|
|
|
49
49
|
# Check if the user is disabled
|
data/lib/rbzk/version.rb
CHANGED
data/lib/rbzk/zk.rb
CHANGED
|
@@ -39,13 +39,15 @@ module RBZK
|
|
|
39
39
|
rescue Errno::EISCONN
|
|
40
40
|
result = 0 # Already connected
|
|
41
41
|
rescue => e
|
|
42
|
-
|
|
42
|
+
# Some exceptions (e.g., Socket::ResolutionError) don't provide errno
|
|
43
|
+
result = e.respond_to?(:errno) ? e.errno : 1 # Connection failed
|
|
43
44
|
end
|
|
44
45
|
|
|
45
46
|
client.close
|
|
46
47
|
return result
|
|
47
48
|
rescue => e
|
|
48
|
-
|
|
49
|
+
# Some exceptions (e.g., Socket::ResolutionError) don't provide errno
|
|
50
|
+
return e.respond_to?(:errno) ? e.errno : 1
|
|
49
51
|
end
|
|
50
52
|
end
|
|
51
53
|
end
|
data/lib/rbzk.rb
CHANGED
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
require 'bytes'
|
|
5
5
|
|
|
6
6
|
# Internal dependencies
|
|
7
|
-
require_relative
|
|
8
|
-
require_relative
|
|
9
|
-
require_relative
|
|
10
|
-
require_relative
|
|
11
|
-
require_relative
|
|
12
|
-
require_relative
|
|
13
|
-
require_relative
|
|
7
|
+
require_relative 'rbzk/version'
|
|
8
|
+
require_relative 'rbzk/constants'
|
|
9
|
+
require_relative 'rbzk/exceptions'
|
|
10
|
+
require_relative 'rbzk/user'
|
|
11
|
+
require_relative 'rbzk/attendance'
|
|
12
|
+
require_relative 'rbzk/finger'
|
|
13
|
+
require_relative 'rbzk/zk'
|
|
14
14
|
|
|
15
15
|
module RBZK
|
|
16
16
|
class Error < StandardError; end
|
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.
|
|
4
|
+
version: 0.1.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Khaled AbuShqear
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2025-
|
|
10
|
+
date: 2025-10-20 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: bytes
|
|
@@ -24,33 +24,33 @@ dependencies:
|
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
25
|
version: '0.1'
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
|
-
name:
|
|
27
|
+
name: terminal-table
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
29
29
|
requirements:
|
|
30
30
|
- - "~>"
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: '
|
|
32
|
+
version: '3.0'
|
|
33
33
|
type: :runtime
|
|
34
34
|
prerelease: false
|
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
36
|
requirements:
|
|
37
37
|
- - "~>"
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: '
|
|
39
|
+
version: '3.0'
|
|
40
40
|
- !ruby/object:Gem::Dependency
|
|
41
|
-
name:
|
|
41
|
+
name: thor
|
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
|
43
43
|
requirements:
|
|
44
44
|
- - "~>"
|
|
45
45
|
- !ruby/object:Gem::Version
|
|
46
|
-
version: '
|
|
46
|
+
version: '1.2'
|
|
47
47
|
type: :runtime
|
|
48
48
|
prerelease: false
|
|
49
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
50
50
|
requirements:
|
|
51
51
|
- - "~>"
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
|
-
version: '
|
|
53
|
+
version: '1.2'
|
|
54
54
|
- !ruby/object:Gem::Dependency
|
|
55
55
|
name: bundler
|
|
56
56
|
requirement: !ruby/object:Gem::Requirement
|