zklib 1.0.0
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 +7 -0
- data/.gitignore +15 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +41 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/zklib.rb +124 -0
- data/lib/zklib/attendance_management.rb +119 -0
- data/lib/zklib/connection_management.rb +23 -0
- data/lib/zklib/data_management.rb +37 -0
- data/lib/zklib/device_management.rb +91 -0
- data/lib/zklib/face_management.rb +40 -0
- data/lib/zklib/helper.rb +181 -0
- data/lib/zklib/pin_management.rb +22 -0
- data/lib/zklib/platform_management.rb +22 -0
- data/lib/zklib/serial_management.rb +22 -0
- data/lib/zklib/ssr_management.rb +22 -0
- data/lib/zklib/time_management.rb +47 -0
- data/lib/zklib/user_management.rb +199 -0
- data/lib/zklib/version.rb +3 -0
- data/lib/zklib/version_management.rb +58 -0
- data/lib/zklib/work_code_management.rb +22 -0
- data/zklib.gemspec +27 -0
- metadata +139 -0
@@ -0,0 +1,37 @@
|
|
1
|
+
class Zklib
|
2
|
+
module DataManagement
|
3
|
+
# Free data for transmission
|
4
|
+
def free_data
|
5
|
+
execute_cmd(
|
6
|
+
command: CMD_FREE_DATA,
|
7
|
+
command_string: ''
|
8
|
+
) do |opts|
|
9
|
+
return puts "ERROR: #{options[:error]}" unless opts[:valid]
|
10
|
+
|
11
|
+
data = opts[:data]
|
12
|
+
if data.length > 7
|
13
|
+
data.split("\u0000").pop
|
14
|
+
else
|
15
|
+
puts 'ERROR: Invalid free data response'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Refresh data for transmission
|
21
|
+
def refresh_data
|
22
|
+
execute_cmd(
|
23
|
+
command: CMD_REFRESHDATA,
|
24
|
+
command_string: ''
|
25
|
+
) do |opts|
|
26
|
+
return puts "ERROR: #{options[:error]}" unless opts[:valid]
|
27
|
+
|
28
|
+
data = opts[:data]
|
29
|
+
if data.length > 7
|
30
|
+
data.split("\u0000").pop
|
31
|
+
else
|
32
|
+
puts 'ERROR: Invalid refresh data response'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
class Zklib
|
2
|
+
module DeviceManagement
|
3
|
+
DEVICE_NAME_KEYWORD = '~DeviceName'
|
4
|
+
DISABLE_DEVICE_KEYWORD = "\u0000\u0000"
|
5
|
+
|
6
|
+
# Disable attendance machine
|
7
|
+
def disable_device
|
8
|
+
execute_cmd(
|
9
|
+
command: CMD_DISABLEDEVICE,
|
10
|
+
command_string: DISABLE_DEVICE_KEYWORD
|
11
|
+
) do |opts|
|
12
|
+
return puts "ERROR: #{options[:error]}" unless opts[:valid]
|
13
|
+
|
14
|
+
data = opts[:data]
|
15
|
+
if data.length > 7
|
16
|
+
data.split("\u0000").pop
|
17
|
+
else
|
18
|
+
puts 'ERROR: Invalid disable device response'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Enable attendance machine
|
24
|
+
def enable_device
|
25
|
+
execute_cmd(
|
26
|
+
command: CMD_ENABLEDEVICE,
|
27
|
+
command_string: ''
|
28
|
+
) do |opts|
|
29
|
+
return puts "ERROR: #{options[:error]}" unless opts[:valid]
|
30
|
+
|
31
|
+
data = opts[:data]
|
32
|
+
if data.length > 7
|
33
|
+
data.split("\u0000").pop
|
34
|
+
else
|
35
|
+
puts 'ERROR: Invalid enable device response'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Get device name
|
41
|
+
def get_device_name
|
42
|
+
execute_cmd(
|
43
|
+
command: CMD_DEVICE,
|
44
|
+
command_string: DEVICE_NAME_KEYWORD
|
45
|
+
) do |opts|
|
46
|
+
return puts "ERROR: #{options[:error]}" unless opts[:valid]
|
47
|
+
|
48
|
+
data = opts[:data]
|
49
|
+
if data.length > 8
|
50
|
+
data.split("\u0000").pop.tr("#{DEVICE_NAME_KEYWORD}=", '')
|
51
|
+
else
|
52
|
+
puts 'ERROR: Invalid device name response'
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Turn off attendance machine
|
58
|
+
def power_off_device
|
59
|
+
execute_cmd(
|
60
|
+
command: CMD_POWEROFF,
|
61
|
+
command_string: ''
|
62
|
+
) do |opts|
|
63
|
+
return puts "ERROR: #{options[:error]}" unless opts[:valid]
|
64
|
+
|
65
|
+
data = opts[:data]
|
66
|
+
if data.length > 7
|
67
|
+
data.split("\u0000").pop
|
68
|
+
else
|
69
|
+
puts 'ERROR: Invalid power off device response'
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Restart attendance machine
|
75
|
+
def restart_device
|
76
|
+
execute_cmd(
|
77
|
+
command: CMD_RESTART,
|
78
|
+
command_string: ''
|
79
|
+
) do |opts|
|
80
|
+
return puts "ERROR: #{options[:error]}" unless opts[:valid]
|
81
|
+
|
82
|
+
data = opts[:data]
|
83
|
+
if data.length > 7
|
84
|
+
data.split("\u0000").pop
|
85
|
+
else
|
86
|
+
puts 'ERROR: Invalid restart device response'
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
class Zklib
|
2
|
+
module FaceManagement
|
3
|
+
TURN_FACE_OFF_KEYWORD = 'FaceFunOff'
|
4
|
+
TURN_FACE_ON_KEYWORD = 'FaceFunOn'
|
5
|
+
|
6
|
+
# Turn face off
|
7
|
+
def turn_face_off
|
8
|
+
execute_cmd(
|
9
|
+
command: CMD_DEVICE,
|
10
|
+
command_string: TURN_FACE_OFF_KEYWORD
|
11
|
+
) do |opts|
|
12
|
+
return puts "ERROR: #{options[:error]}" unless opts[:valid]
|
13
|
+
|
14
|
+
data = opts[:data]
|
15
|
+
if data.length > 7
|
16
|
+
data.split("\u0000").pop
|
17
|
+
else
|
18
|
+
puts 'ERROR: Invalid turn face off response'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Turn face on
|
24
|
+
def turn_face_on
|
25
|
+
execute_cmd(
|
26
|
+
command: CMD_DEVICE,
|
27
|
+
command_string: TURN_FACE_ON_KEYWORD
|
28
|
+
) do |opts|
|
29
|
+
return puts "ERROR: #{options[:error]}" unless opts[:valid]
|
30
|
+
|
31
|
+
data = opts[:data]
|
32
|
+
if data.length > 7
|
33
|
+
data.split("\u0000").pop
|
34
|
+
else
|
35
|
+
puts 'ERROR: Invalid turn face on response'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/zklib/helper.rb
ADDED
@@ -0,0 +1,181 @@
|
|
1
|
+
class Zklib
|
2
|
+
module Helper
|
3
|
+
# Check validity of response
|
4
|
+
#
|
5
|
+
# param options
|
6
|
+
# |_ data Data to check validity
|
7
|
+
def check_valid(options)
|
8
|
+
BinData::Uint16le.read(options[:data][0..-1]).snapshot == CMD_ACK_OK
|
9
|
+
end
|
10
|
+
|
11
|
+
# Create checksum for execution
|
12
|
+
#
|
13
|
+
# param options
|
14
|
+
# |_ data Data to create checksum
|
15
|
+
def create_checksum(options)
|
16
|
+
data = options[:data]
|
17
|
+
checksum = 0
|
18
|
+
|
19
|
+
(0...data.length).step(2) do |i|
|
20
|
+
checksum += (i == data.length - 1) ?
|
21
|
+
BinData::Uint8le.read(data[i]).snapshot :
|
22
|
+
BinData::Uint16le.read(data[i..-1]).snapshot
|
23
|
+
checksum %= USHRT_MAX
|
24
|
+
end
|
25
|
+
|
26
|
+
chksum = USHRT_MAX - checksum - 1
|
27
|
+
chksum
|
28
|
+
end
|
29
|
+
|
30
|
+
# Create header for execution
|
31
|
+
#
|
32
|
+
# param options
|
33
|
+
# |_ command Command value
|
34
|
+
# |_ checksum Checksum
|
35
|
+
# |_ session_id Session ID
|
36
|
+
# |_ reply_id Reply ID
|
37
|
+
# |_ command_string Command string
|
38
|
+
def create_header(options)
|
39
|
+
header_buffer = StringIO.new
|
40
|
+
binary_writer = BinData::Uint16le.new
|
41
|
+
|
42
|
+
# Write command header
|
43
|
+
binary_writer.value = options[:command]
|
44
|
+
header_buffer.pos = 0
|
45
|
+
binary_writer.write(header_buffer)
|
46
|
+
|
47
|
+
# Write checksum header
|
48
|
+
binary_writer.value = options[:checksum]
|
49
|
+
header_buffer.pos = 2
|
50
|
+
binary_writer.write(header_buffer)
|
51
|
+
|
52
|
+
# Write session ID header
|
53
|
+
binary_writer.value = options[:session_id]
|
54
|
+
header_buffer.pos = 4
|
55
|
+
binary_writer.write(header_buffer)
|
56
|
+
|
57
|
+
# Write reply ID header
|
58
|
+
binary_writer.value = options[:reply_id]
|
59
|
+
header_buffer.pos = 6
|
60
|
+
binary_writer.write(header_buffer)
|
61
|
+
|
62
|
+
# Write command string header
|
63
|
+
header_buffer.pos = 8
|
64
|
+
header_buffer.write(options[:command_string])
|
65
|
+
|
66
|
+
# Rewrite checksum header
|
67
|
+
binary_writer.value = create_checksum(data: header_buffer.string)
|
68
|
+
header_buffer.pos = 2
|
69
|
+
binary_writer.write(header_buffer)
|
70
|
+
|
71
|
+
# Rewrite reply ID header
|
72
|
+
binary_writer.value = (options[:reply_id] + 1) % USHRT_MAX
|
73
|
+
header_buffer.pos = 6
|
74
|
+
binary_writer.write(header_buffer)
|
75
|
+
|
76
|
+
header_buffer.string
|
77
|
+
end
|
78
|
+
|
79
|
+
# Convert number of seconds to time
|
80
|
+
#
|
81
|
+
# param options
|
82
|
+
# |_ seconds Time in seconds to decode
|
83
|
+
def decode_time(options)
|
84
|
+
time = options[:seconds]
|
85
|
+
|
86
|
+
# Calculate second value
|
87
|
+
second = time % 60
|
88
|
+
time = (time - second) / 60
|
89
|
+
|
90
|
+
# Calculate minute value
|
91
|
+
minute = time % 60
|
92
|
+
time = (time - minute) / 60
|
93
|
+
|
94
|
+
# Calculate hour value
|
95
|
+
hour = time % 24
|
96
|
+
time = (time - hour) / 24
|
97
|
+
|
98
|
+
# Calculate day value
|
99
|
+
day = time % 31 + 1
|
100
|
+
time = (time - day + 1) / 31
|
101
|
+
|
102
|
+
# Calculate month value
|
103
|
+
month = time % 12 + 1
|
104
|
+
time = (time - month + 1) / 12
|
105
|
+
|
106
|
+
# Calculate year value
|
107
|
+
year = time + 2000
|
108
|
+
|
109
|
+
Time.new(year, month, day, hour, minute, second)
|
110
|
+
end
|
111
|
+
|
112
|
+
def decode_user_data(options)
|
113
|
+
data = options[:data]
|
114
|
+
|
115
|
+
{
|
116
|
+
uid: BinData::Uint16be.read(data[0..1]).snapshot,
|
117
|
+
role: BinData::Uint16le.read(data[2..3]).snapshot,
|
118
|
+
password: nil,
|
119
|
+
name: nil,
|
120
|
+
cardno: nil,
|
121
|
+
userid: nil
|
122
|
+
}
|
123
|
+
end
|
124
|
+
|
125
|
+
# Convert time to number of seconds
|
126
|
+
#
|
127
|
+
# param options
|
128
|
+
# |_ time Time object to encode
|
129
|
+
def encode_time(options)
|
130
|
+
time = options[:time]
|
131
|
+
|
132
|
+
((time.year % 100) * 12 * 31 + ((time.mon - 1) * 31) + time.day - 1) * (24 * 60 * 60) + (time.hour * 60 + time.min) * 60 + time.sec
|
133
|
+
end
|
134
|
+
|
135
|
+
# Execute command on attendance machine
|
136
|
+
#
|
137
|
+
# param options
|
138
|
+
# |_ command Command to execute
|
139
|
+
# |_ command_string Command string
|
140
|
+
def execute_cmd(options)
|
141
|
+
command = options[:command]
|
142
|
+
self.reply_id = USHRT_MAX - 1 if command == CMD_CONNECT
|
143
|
+
header = create_header(
|
144
|
+
command: command,
|
145
|
+
checksum: 0,
|
146
|
+
session_id: session_id,
|
147
|
+
reply_id: reply_id,
|
148
|
+
command_string: options[:command_string]
|
149
|
+
)
|
150
|
+
|
151
|
+
# Send command
|
152
|
+
socket = UDPSocket.new
|
153
|
+
socket.bind('0.0.0.0', inport)
|
154
|
+
socket.send(header, 0, ip, port)
|
155
|
+
self.data_recv = socket.recvfrom(USHRT_MAX)[0] if IO.select([socket], nil, nil, 10)
|
156
|
+
socket.close
|
157
|
+
|
158
|
+
# Callback
|
159
|
+
if data_recv && data_recv.length > 0
|
160
|
+
self.session_id = BinData::Uint16le.read(data_recv[4..-1]).snapshot
|
161
|
+
self.reply_id = BinData::Uint16le.read(data_recv[6..-1]).snapshot
|
162
|
+
|
163
|
+
yield(valid: true, data: data_recv) if block_given?
|
164
|
+
else
|
165
|
+
yield(valid: false, error: 'Empty response') if block_given?
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# Receive data from non-blocking socket
|
170
|
+
#
|
171
|
+
# param options
|
172
|
+
# |_ socket Socket to receive data from
|
173
|
+
def receive_nonblock(options)
|
174
|
+
return options[:socket].recvfrom_nonblock(USHRT_MAX)
|
175
|
+
rescue IO::WaitReadable
|
176
|
+
IO.select([options[:socket]])
|
177
|
+
|
178
|
+
retry
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class Zklib
|
2
|
+
module PINManagement
|
3
|
+
PIN_WIDTH_KEYWORD = '~PIN2Width'
|
4
|
+
|
5
|
+
# Get PIN width
|
6
|
+
def get_pin_width
|
7
|
+
execute_cmd(
|
8
|
+
command: CMD_DEVICE,
|
9
|
+
command_string: PIN_WIDTH_KEYWORD
|
10
|
+
) do |opts|
|
11
|
+
return puts "ERROR: #{options[:error]}" unless opts[:valid]
|
12
|
+
|
13
|
+
data = opts[:data]
|
14
|
+
if data.length > 8
|
15
|
+
data.split("\u0000").pop.tr("#{PIN_WIDTH_KEYWORD}=", '')
|
16
|
+
else
|
17
|
+
puts 'ERROR: Invalid PIN width response'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class Zklib
|
2
|
+
module PlatformManagement
|
3
|
+
PLATFORM_KEYWORD = '~Platform'
|
4
|
+
|
5
|
+
# Get platform
|
6
|
+
def get_platform
|
7
|
+
execute_cmd(
|
8
|
+
command: CMD_DEVICE,
|
9
|
+
command_string: PLATFORM_KEYWORD
|
10
|
+
) do |opts|
|
11
|
+
return puts "ERROR: #{options[:error]}" unless opts[:valid]
|
12
|
+
|
13
|
+
data = opts[:data]
|
14
|
+
if data.length > 8
|
15
|
+
data.split("\u0000").pop.tr("#{PLATFORM_KEYWORD}=", '')
|
16
|
+
else
|
17
|
+
puts 'ERROR: Invalid platform response'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class Zklib
|
2
|
+
module SerialManagement
|
3
|
+
SERIAL_KEYWORD = '~SerialNumber'
|
4
|
+
|
5
|
+
# Get serial number of attendance machine
|
6
|
+
def get_serial_number
|
7
|
+
execute_cmd(
|
8
|
+
command: CMD_DEVICE,
|
9
|
+
command_string: SERIAL_KEYWORD
|
10
|
+
) do |opts|
|
11
|
+
return puts "ERROR: #{options[:error]}" unless opts[:valid]
|
12
|
+
|
13
|
+
data = opts[:data]
|
14
|
+
if data.length > 8
|
15
|
+
data.split("\u0000").pop.tr("#{SERIAL_KEYWORD}=", '')
|
16
|
+
else
|
17
|
+
puts 'ERROR: Invalid serial number response'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class Zklib
|
2
|
+
module SSRManagement
|
3
|
+
SSR_KEYWORD = '~SSR'
|
4
|
+
|
5
|
+
# Get SSR
|
6
|
+
def get_ssr
|
7
|
+
execute_cmd(
|
8
|
+
command: CMD_DEVICE,
|
9
|
+
command_string: SSR_KEYWORD
|
10
|
+
) do |opts|
|
11
|
+
return puts "ERROR: #{options[:error]}" unless opts[:valid]
|
12
|
+
|
13
|
+
data = opts[:data]
|
14
|
+
if data.length > 8
|
15
|
+
data.split("\u0000").pop.tr("#{SSR_KEYWORD}=", '')
|
16
|
+
else
|
17
|
+
puts 'ERROR: Invalid SSR response'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class Zklib
|
2
|
+
module TimeManagement
|
3
|
+
# Get current time of attendance machine
|
4
|
+
def get_time
|
5
|
+
execute_cmd(
|
6
|
+
command: CMD_GET_TIME,
|
7
|
+
command_string: ''
|
8
|
+
) do |opts|
|
9
|
+
return puts "ERROR: #{options[:error]}" unless opts[:valid]
|
10
|
+
|
11
|
+
data = opts[:data]
|
12
|
+
if data.length > 8
|
13
|
+
decode_time(seconds: BinData::Uint32le.read(data[8..-1]).snapshot)
|
14
|
+
else
|
15
|
+
puts 'ERROR: Invalid time response'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Set current time for attendance machine
|
21
|
+
def set_time(time = Time.now)
|
22
|
+
seconds = encode_time(time: time)
|
23
|
+
command_buffer = StringIO.new
|
24
|
+
binary_writer = BinData::Uint32le.new
|
25
|
+
|
26
|
+
# Write command string
|
27
|
+
binary_writer.value = seconds
|
28
|
+
command_buffer.pos = 0
|
29
|
+
binary_writer.write(command_buffer)
|
30
|
+
command_string = command_buffer.string
|
31
|
+
|
32
|
+
execute_cmd(
|
33
|
+
command: CMD_SET_TIME,
|
34
|
+
command_string: command_string
|
35
|
+
) do |opts|
|
36
|
+
return puts "ERROR: #{options[:error]}" unless opts[:valid]
|
37
|
+
|
38
|
+
data = opts[:data]
|
39
|
+
if data.length > 7
|
40
|
+
data.split("\u0000").pop
|
41
|
+
else
|
42
|
+
puts 'ERROR: Invalid time response'
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|