da_funk 0.4.4
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 +11 -0
- data/.yardopts +12 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +21 -0
- data/README.md +108 -0
- data/README_GUIDE.md +52 -0
- data/RELEASE_NOTES.md +23 -0
- data/Rakefile +69 -0
- data/da_funk.gemspec +31 -0
- data/ext/da_funk/Makefile +5 -0
- data/ext/da_funk/extconf.rb +8 -0
- data/guides/sample_input_output.rb +14 -0
- data/guides/sample_message_iso8583.rb +14 -0
- data/guides/sample_network_gprs.rb +26 -0
- data/guides/sample_read_magnect_card.rb +8 -0
- data/guides/sample_socket.rb +29 -0
- data/guides/sample_transaction.rb +23 -0
- data/guides/sample_transaction_download_application.rb +9 -0
- data/guides/sample_transaction_download_file.rb +6 -0
- data/guides/sample_transaction_download_parameter_file.rb +18 -0
- data/guides/sample_transaction_iso8583.rb +222 -0
- data/imgs/daft-punk-da-funk.jpg +0 -0
- data/lib/da_funk/rake_task.rb +129 -0
- data/lib/da_funk/test.rb +87 -0
- data/lib/da_funk.rb +39 -0
- data/lib/device/application.rb +41 -0
- data/lib/device/audio.rb +17 -0
- data/lib/device/crypto.rb +52 -0
- data/lib/device/display.rb +47 -0
- data/lib/device/helper.rb +161 -0
- data/lib/device/io.rb +86 -0
- data/lib/device/magnetic.rb +79 -0
- data/lib/device/network.rb +192 -0
- data/lib/device/notification.rb +116 -0
- data/lib/device/notification_callback.rb +47 -0
- data/lib/device/notification_event.rb +29 -0
- data/lib/device/params_dat.rb +119 -0
- data/lib/device/printer.rb +35 -0
- data/lib/device/runtime.rb +22 -0
- data/lib/device/setting.rb +63 -0
- data/lib/device/support.rb +28 -0
- data/lib/device/system.rb +61 -0
- data/lib/device/transaction/download.rb +268 -0
- data/lib/device/transaction/emv.rb +45 -0
- data/lib/device/transaction/iso.rb +75 -0
- data/lib/device/version.rb +11 -0
- data/lib/device/walk.rb +8 -0
- data/lib/device.rb +28 -0
- data/lib/ext/kernel.rb +9 -0
- data/lib/file_db.rb +47 -0
- data/lib/iso8583/bitmap.rb +114 -0
- data/lib/iso8583/codec.rb +197 -0
- data/lib/iso8583/exception.rb +4 -0
- data/lib/iso8583/field.rb +90 -0
- data/lib/iso8583/fields.rb +171 -0
- data/lib/iso8583/message.rb +455 -0
- data/lib/iso8583/util.rb +91 -0
- data/lib/iso8583/version.rb +6 -0
- data/lib/serfx/commands.rb +191 -0
- data/lib/serfx/connection.rb +160 -0
- data/lib/serfx/exceptions.rb +5 -0
- data/lib/serfx/response.rb +28 -0
- data/lib/serfx.rb +27 -0
- data/lib/version.rb +5 -0
- data/lib/zip.rb +29 -0
- data/test/integration/getc_test.rb +6 -0
- data/test/integration/mrb_eval_test.rb +36 -0
- data/test/integration/notification_test.rb +20 -0
- data/test/integration/params_dat_test.rb +11 -0
- data/test/test_helper.rb +18 -0
- data/test/unit/device/display_test.rb +43 -0
- data/test/unit/device/helper_test.rb +61 -0
- data/test/unit/device/notification_callback_test.rb +6 -0
- data/test/unit/device/notification_event_test.rb +73 -0
- data/test/unit/device/notification_test.rb +21 -0
- data/utils/command_line_platform.rb +67 -0
- data/utils/test_run.rb +3 -0
- metadata +177 -0
@@ -0,0 +1,161 @@
|
|
1
|
+
class Device
|
2
|
+
module Helper
|
3
|
+
def self.included(base)
|
4
|
+
base.extend Device::Helper
|
5
|
+
end
|
6
|
+
|
7
|
+
def form(label, options = {})
|
8
|
+
Device::Display.clear
|
9
|
+
options = form_default(options)
|
10
|
+
default = options.delete(:default)
|
11
|
+
puts "#{label} (#{default}):"
|
12
|
+
string = get_format(options.delete(:min), options.delete(:max), options)
|
13
|
+
return default if string.nil? || string.empty?
|
14
|
+
string
|
15
|
+
end
|
16
|
+
|
17
|
+
def attach
|
18
|
+
Device::Display.clear
|
19
|
+
puts "Connecting..."
|
20
|
+
if Device::Network.connected? < 0
|
21
|
+
if (ret = Device::Network.attach) == 0
|
22
|
+
puts "Connected #{ret}"
|
23
|
+
else
|
24
|
+
puts "Attach fail #{ret}"
|
25
|
+
sleep 4
|
26
|
+
return false
|
27
|
+
end
|
28
|
+
else
|
29
|
+
puts "Already connected"
|
30
|
+
end
|
31
|
+
true
|
32
|
+
end
|
33
|
+
|
34
|
+
# TODO Add i18n or something
|
35
|
+
def check_download_error(ret)
|
36
|
+
value = true
|
37
|
+
case ret
|
38
|
+
when Device::Transaction::Download::SERIAL_NUMBER_NOT_FOUND
|
39
|
+
puts "Serial number not found."
|
40
|
+
value = false
|
41
|
+
when Device::Transaction::Download::FILE_NOT_FOUND
|
42
|
+
puts "File not found."
|
43
|
+
value = false
|
44
|
+
when Device::Transaction::Download::FILE_NOT_CHANGE
|
45
|
+
puts "File is the same."
|
46
|
+
when Device::Transaction::Download::SUCCESS
|
47
|
+
puts "Success."
|
48
|
+
when Device::Transaction::Download::COMMUNICATION_ERROR
|
49
|
+
puts "Communication failure."
|
50
|
+
value = false
|
51
|
+
when Device::Transaction::Download::MAPREDUCE_RESPONSE_ERROR
|
52
|
+
puts "Encoding error."
|
53
|
+
value = false
|
54
|
+
when Device::Transaction::Download::IO_ERROR
|
55
|
+
puts "IO Error."
|
56
|
+
value = false
|
57
|
+
else
|
58
|
+
puts "Communication fail."
|
59
|
+
value = false
|
60
|
+
end
|
61
|
+
|
62
|
+
value
|
63
|
+
end
|
64
|
+
|
65
|
+
# {
|
66
|
+
# :default => {:detail => 10}, # default value to return if enter
|
67
|
+
# :number => true, # Add number to label or not
|
68
|
+
# "option X" => {:detail => 10},
|
69
|
+
# "option Y" => {:detail => 11}
|
70
|
+
# }
|
71
|
+
def menu(title, selection, options = {})
|
72
|
+
options[:number] = true if options[:number].nil?
|
73
|
+
|
74
|
+
Device::Display.clear
|
75
|
+
print_title(title, options[:default])
|
76
|
+
values = Hash.new
|
77
|
+
selection.each_with_index do |value,i|
|
78
|
+
values[i.to_i] = value[1]
|
79
|
+
if options[:number]
|
80
|
+
Device::Display.print("#{i+1} - #{value[0]}", i+2, 0)
|
81
|
+
else
|
82
|
+
Device::Display.print(value[0], i+2, 0)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
key = getc
|
87
|
+
|
88
|
+
return options[:default] if key == IO::ENTER || key == IO::CANCEL
|
89
|
+
[values[key.to_i - 1]].flatten.first
|
90
|
+
end
|
91
|
+
|
92
|
+
def number_to_currency(value, options = {})
|
93
|
+
options[:delimiter] ||= ","
|
94
|
+
options[:precision] ||= 2
|
95
|
+
options[:separator] ||= "."
|
96
|
+
|
97
|
+
if value.is_a? Float
|
98
|
+
number, unit = value.to_s.split(".")
|
99
|
+
len = number.size + unit.size
|
100
|
+
else
|
101
|
+
len = value.to_s.size
|
102
|
+
unit = value.to_s[(len - options[:precision])..-1]
|
103
|
+
if len <= options[:precision]
|
104
|
+
number = ""
|
105
|
+
else
|
106
|
+
number = value.to_s[0..(len - (options[:precision] + 1)).abs]
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
text = ""
|
111
|
+
i = 0
|
112
|
+
number.reverse.each_char do |ch|
|
113
|
+
i += 1
|
114
|
+
text << ch
|
115
|
+
text << options[:delimiter] if (i % 3 == 0) && (len - unit.size) != i
|
116
|
+
end
|
117
|
+
currency = [rjust(text.reverse, 1, "0"),rjust(unit, options[:precision], "0")].join options[:separator]
|
118
|
+
if options[:label]
|
119
|
+
options[:label] + currency
|
120
|
+
else
|
121
|
+
currency
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def ljust(string, size, new_string)
|
126
|
+
string_plain = string.to_s
|
127
|
+
if size > string_plain.size
|
128
|
+
string_plain + (new_string * (size - string_plain.size))
|
129
|
+
else
|
130
|
+
string_plain
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def rjust(string, size, new_string)
|
135
|
+
string_plain = string.to_s
|
136
|
+
if size > string_plain.size
|
137
|
+
(new_string * (size - string_plain.size)) + string_plain
|
138
|
+
else
|
139
|
+
string_plain
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
private
|
144
|
+
def form_default(options = {})
|
145
|
+
options[:default] ||= ""
|
146
|
+
options[:mode] ||= Device::IO::IO_INPUT_LETTERS
|
147
|
+
options[:min] ||= 0
|
148
|
+
options[:max] ||= 20
|
149
|
+
options
|
150
|
+
end
|
151
|
+
|
152
|
+
def print_title(string, default)
|
153
|
+
if default
|
154
|
+
puts("#{string} (#{default}):")
|
155
|
+
else
|
156
|
+
puts("#{string}:")
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
data/lib/device/io.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
class Device
|
2
|
+
class IO < ::IO
|
3
|
+
F1 = "\001"
|
4
|
+
F2 = "\002"
|
5
|
+
F3 = "\003"
|
6
|
+
F4 = "\004"
|
7
|
+
FUNC = "\006"
|
8
|
+
UP = "\007"
|
9
|
+
DOWN = "\008"
|
10
|
+
MENU = "\009"
|
11
|
+
ENTER = 0x0D.chr
|
12
|
+
CLEAR = 0x0F.chr
|
13
|
+
ALPHA = 0x10.chr
|
14
|
+
SHARP = 0x11.chr
|
15
|
+
KEY_TIMEOUT = 0x12.chr
|
16
|
+
BACK = "\017"
|
17
|
+
CANCEL = 0x1B.chr
|
18
|
+
IO_INPUT_NUMBERS = :numbers
|
19
|
+
IO_INPUT_LETTERS = :letters
|
20
|
+
IO_INPUT_SECRET = :secret
|
21
|
+
IO_INPUT_DECIMAL = :decimal
|
22
|
+
IO_INPUT_MONEY = :money
|
23
|
+
|
24
|
+
NUMBERS = %w(1 2 3 4 5 6 7 8 9 0)
|
25
|
+
|
26
|
+
include Device::Helper
|
27
|
+
|
28
|
+
# Restricted to terminals, get strings and numbers.
|
29
|
+
# The switch method between uppercase, lowercase and number characters is to keep pressing a same button quickly. The timeout of this operation is 1 second.
|
30
|
+
#
|
31
|
+
# @param min [Fixnum] Minimum length of the input string.
|
32
|
+
# @param max [Fixnum] Maximum length of the input string (127 bytes maximum).
|
33
|
+
# @param options [Hash]
|
34
|
+
#
|
35
|
+
# :precision - Sets the level of precision (defaults to 2).
|
36
|
+
#
|
37
|
+
# :separator - Sets the separator between the units (defaults to “.”).
|
38
|
+
#
|
39
|
+
# :delimiter - Sets the thousands delimiter (defaults to “,”).
|
40
|
+
#
|
41
|
+
# :label - Sets the label display before currency, eg.: "U$:", "R$:"
|
42
|
+
#
|
43
|
+
# :mode - Define input modes:
|
44
|
+
#
|
45
|
+
# :numbers (IO_INPUT_NUMBERS) - Only number.
|
46
|
+
# :letters (IO_INPUT_LETTERS) - Letters and numbers.
|
47
|
+
# :secret (IO_INPUT_SECRET) - Secret *.
|
48
|
+
# :decimal (IO_INPUT_DECIMAL) - Decimal input, only number.
|
49
|
+
# :money (IO_INPUT_MONEY) - Money input, only number.
|
50
|
+
#
|
51
|
+
# @return [String] buffer read from keyboard
|
52
|
+
def self.get_format(min, max, options = {})
|
53
|
+
options[:mode] ||= IO_INPUT_LETTERS
|
54
|
+
if options[:mode] == IO_INPUT_MONEY || options[:mode] == IO_INPUT_DECIMAL
|
55
|
+
|
56
|
+
text = ""
|
57
|
+
key = ""
|
58
|
+
|
59
|
+
while key != CANCEL
|
60
|
+
Device::Display.clear 2
|
61
|
+
Device::Display.print_line number_to_currency(text, options), 2, 0
|
62
|
+
key = getc
|
63
|
+
if key == BACK
|
64
|
+
text = text[0..-2]
|
65
|
+
elsif text.size >= max
|
66
|
+
next
|
67
|
+
elsif NUMBERS.include? key
|
68
|
+
text << key
|
69
|
+
elsif key == ENTER
|
70
|
+
return text
|
71
|
+
end
|
72
|
+
end
|
73
|
+
else
|
74
|
+
get_string(min, max, options[:mode])
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Read 1 byte on keyboard, wait until be pressed
|
79
|
+
#
|
80
|
+
# @param timeout [Fixnum] Timeout in milliseconds to wait for key.
|
81
|
+
#
|
82
|
+
# @return [String] key read from keyboard
|
83
|
+
def self.getc(timeout = 0); super(timeout); end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
@@ -0,0 +1,79 @@
|
|
1
|
+
class Device
|
2
|
+
class Magnetic
|
3
|
+
def self.adapter
|
4
|
+
Device.adapter::Magnetic
|
5
|
+
end
|
6
|
+
|
7
|
+
def adapter
|
8
|
+
Device.adapter::Magnetic
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.read_card(timeout)
|
12
|
+
time = Time.now + (timeout.to_f / 1000.0)
|
13
|
+
magnetic = self.new
|
14
|
+
loop do
|
15
|
+
break if magnetic.swiped? || time <= Time.now
|
16
|
+
end
|
17
|
+
magnetic.tracks
|
18
|
+
ensure
|
19
|
+
magnetic.close if magnetic
|
20
|
+
end
|
21
|
+
|
22
|
+
HARDWARE_SUCCESSFUL_READ = 1
|
23
|
+
HARDWARE_NOT_READ = 0
|
24
|
+
STATUS_SUCCESSFUL_READ = :success
|
25
|
+
STATUS_READ_TRACKS = :read
|
26
|
+
STATUS_CLOSE = :close
|
27
|
+
STATUS_OPEN = :open
|
28
|
+
STATUS_OPEN_FAIL = :open_fail
|
29
|
+
|
30
|
+
attr_reader :tracks, :track1, :track2, :track3, :status
|
31
|
+
|
32
|
+
def initialize
|
33
|
+
@status = STATUS_CLOSE
|
34
|
+
self.start
|
35
|
+
end
|
36
|
+
|
37
|
+
def start
|
38
|
+
if self.adapter.open == 1
|
39
|
+
@status = STATUS_OPEN
|
40
|
+
true
|
41
|
+
else
|
42
|
+
@status = STATUS_OPEN_FAIL
|
43
|
+
false
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def swiped?
|
48
|
+
if self.read == HARDWARE_SUCCESSFUL_READ
|
49
|
+
@status = STATUS_SUCCESSFUL_READ
|
50
|
+
return true
|
51
|
+
end
|
52
|
+
false
|
53
|
+
end
|
54
|
+
|
55
|
+
def read
|
56
|
+
adapter.read
|
57
|
+
end
|
58
|
+
|
59
|
+
def close
|
60
|
+
adapter.close
|
61
|
+
end
|
62
|
+
|
63
|
+
def tracks
|
64
|
+
read_tracks unless @tracks
|
65
|
+
@tracks
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
def read_tracks
|
70
|
+
@tracks = adapter.tracks
|
71
|
+
@track1 = @tracks[:track1]
|
72
|
+
@track2 = @tracks[:track2]
|
73
|
+
@track3 = @tracks[:track3]
|
74
|
+
|
75
|
+
@status = STATUS_READ_TRACKS
|
76
|
+
@tracks
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,192 @@
|
|
1
|
+
|
2
|
+
class Device
|
3
|
+
class Network
|
4
|
+
|
5
|
+
MEDIA_GPRS = "gprs"
|
6
|
+
MEDIA_WIFI = "wifi"
|
7
|
+
|
8
|
+
AUTH_NONE_OPEN = "open"
|
9
|
+
AUTH_NONE_WEP = "wep"
|
10
|
+
AUTH_NONE_WEP_SHARED = "wepshared"
|
11
|
+
AUTH_IEEE8021X = "IEEE8021X"
|
12
|
+
AUTH_WPA_PSK = "wpapsk"
|
13
|
+
AUTH_WPA_WPA2_PSK = "wpawpa2psk"
|
14
|
+
AUTH_WPA2_PSK = "wpa2psk"
|
15
|
+
|
16
|
+
PARE_CIPHERS_NONE = "none"
|
17
|
+
PARE_CIPHERS_WEP64 = "wep64"
|
18
|
+
PARE_CIPHERS_WEP128 = "wep128"
|
19
|
+
PARE_CIPHERS_WEPX = "wepx"
|
20
|
+
PARE_CIPHERS_CCMP = "ccmp"
|
21
|
+
PARE_CIPHERS_TKIP = "tkip"
|
22
|
+
|
23
|
+
MODE_IBSS = "ibss"
|
24
|
+
MODE_STATION = "station"
|
25
|
+
|
26
|
+
TIMEOUT = -3320
|
27
|
+
NO_CONNECTION = -1012
|
28
|
+
SUCCESS = 0
|
29
|
+
PROCESSING = 1
|
30
|
+
|
31
|
+
# Not Supported
|
32
|
+
#AUTH_WPA_EAP = "wpa_eap"
|
33
|
+
#AUTH_WPA2_EAP = "wpa2_eap"
|
34
|
+
#AUTH_WPA_WPA2_EAP = "wpa_wpa2_eap"
|
35
|
+
|
36
|
+
class << self
|
37
|
+
attr_accessor :type, :apn, :user, :password, :socket, :socket_tcp, :socket_ssl, :ssl
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.adapter
|
41
|
+
Device.adapter::Network
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.init(type, options)
|
45
|
+
adapter.init(type, options)
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.power(command)
|
49
|
+
adapter.power(command)
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.connect
|
53
|
+
adapter.connect
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.connected?
|
57
|
+
return NO_CONNECTION unless self.adapter.started?
|
58
|
+
adapter.connected?
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.configured?
|
62
|
+
Device::Setting.network_configured == "1"
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.ping(host, port)
|
66
|
+
adapter.ping(host, port)
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.disconnect
|
70
|
+
adapter.disconnect
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.dhcp_client(timeout)
|
74
|
+
time = Time.now + (timeout.to_f / 1000.0)
|
75
|
+
ret = adapter.dhcp_client_start
|
76
|
+
if (ret == SUCCESS)
|
77
|
+
ret = PROCESSING
|
78
|
+
while(ret == PROCESSING) # 1 - In process to attach
|
79
|
+
ret = adapter.dhcp_client_check
|
80
|
+
break ret = TIMEOUT unless (time >= Time.now)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
ret
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.handshake_ssl!
|
87
|
+
@socket_ssl = self.handshake_ssl(@socket_tcp)
|
88
|
+
@ssl = true
|
89
|
+
@socket = @socket_ssl
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.handshake_ssl(tcp)
|
93
|
+
return if tcp.closed?
|
94
|
+
entropy = PolarSSL::Entropy.new
|
95
|
+
ctr_drbg = PolarSSL::CtrDrbg.new entropy
|
96
|
+
s_ssl = PolarSSL::SSL.new
|
97
|
+
s_ssl.set_endpoint PolarSSL::SSL::SSL_IS_CLIENT
|
98
|
+
s_ssl.set_rng ctr_drbg
|
99
|
+
s_ssl.set_socket tcp
|
100
|
+
s_ssl.handshake
|
101
|
+
s_ssl
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.handshake!
|
105
|
+
handshake = "#{Device::System.serial};#{Device::System.app};#{Device::Setting.logical_number};#{Device.version}"
|
106
|
+
socket.write("#{handshake.size.chr}#{handshake}")
|
107
|
+
|
108
|
+
company_name = socket_tcp.closed? ? nil : socket.read(3)
|
109
|
+
return false if company_name == "err" || company_name.nil?
|
110
|
+
|
111
|
+
Device::Setting.company_name = company_name
|
112
|
+
true
|
113
|
+
end
|
114
|
+
|
115
|
+
# Create Socket in Walk Switch
|
116
|
+
def self.walk_socket
|
117
|
+
if @socket
|
118
|
+
@socket
|
119
|
+
else
|
120
|
+
@socket_tcp = create_socket
|
121
|
+
if Device::Setting.ssl == "1"
|
122
|
+
handshake_ssl!
|
123
|
+
else
|
124
|
+
@socket = @socket_tcp
|
125
|
+
end
|
126
|
+
handshake!
|
127
|
+
@socket
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def self.create_socket
|
132
|
+
TCPSocket.new(Device::Setting.host, Device::Setting.host_port)
|
133
|
+
end
|
134
|
+
|
135
|
+
def self.close_socket
|
136
|
+
@socket.close
|
137
|
+
if @socket != @socket_tcp
|
138
|
+
@socket_tcp.close
|
139
|
+
end
|
140
|
+
@socket = nil
|
141
|
+
@socket_tcp = nil
|
142
|
+
@socket_ssl = nil
|
143
|
+
end
|
144
|
+
|
145
|
+
def self.attach
|
146
|
+
Device::Network.init(*self.config)
|
147
|
+
ret = Device::Network.connect
|
148
|
+
ret = Device::Network.connected? if ret != SUCCESS
|
149
|
+
while(ret == PROCESSING)
|
150
|
+
ret = Device::Network.connected?
|
151
|
+
end
|
152
|
+
if ret == SUCCESS && wifi?
|
153
|
+
Device::Network.dhcp_client(20000)
|
154
|
+
end
|
155
|
+
ret
|
156
|
+
end
|
157
|
+
|
158
|
+
def self.config
|
159
|
+
# TODO raise some error if media was not set
|
160
|
+
[Device::Setting.media, self.config_media]
|
161
|
+
end
|
162
|
+
|
163
|
+
# TODO should check if WIFI, ETHERNET and etc
|
164
|
+
def self.config_media
|
165
|
+
if gprs?
|
166
|
+
{
|
167
|
+
apn: Device::Setting.apn,
|
168
|
+
user: Device::Setting.user,
|
169
|
+
password: Device::Setting.password
|
170
|
+
}
|
171
|
+
elsif wifi?
|
172
|
+
{
|
173
|
+
authentication: Device::Setting.authentication,
|
174
|
+
password: Device::Setting.password,
|
175
|
+
essid: Device::Setting.essid,
|
176
|
+
channel: Device::Setting.channel,
|
177
|
+
cipher: Device::Setting.cipher,
|
178
|
+
mode: Device::Setting.mode
|
179
|
+
}
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def self.gprs?
|
184
|
+
Device::Setting.media == "gprs"
|
185
|
+
end
|
186
|
+
|
187
|
+
def self.wifi?
|
188
|
+
Device::Setting.media == "wifi"
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
@@ -0,0 +1,116 @@
|
|
1
|
+
class Device
|
2
|
+
class Notification
|
3
|
+
DEFAULT_TIMEOUT = 15
|
4
|
+
DEFAULT_INTERVAL = 10
|
5
|
+
DEFAULT_STREAM_TIMEOUT = 0
|
6
|
+
|
7
|
+
class << self
|
8
|
+
attr_accessor :callbacks, :current
|
9
|
+
end
|
10
|
+
|
11
|
+
self.callbacks = Hash.new
|
12
|
+
|
13
|
+
attr_reader :fiber, :timeout, :interval, :last_check, :stream_timeout
|
14
|
+
|
15
|
+
def self.check
|
16
|
+
self.current.check if self.current
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.execute(event)
|
20
|
+
calls = self.callbacks[event.callback]
|
21
|
+
return unless calls
|
22
|
+
[:before, :on, :after].each do |moment|
|
23
|
+
calls.each{|callback| callback.call(event, moment)}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.schedule(callback)
|
28
|
+
self.callbacks[callback.description] ||= []
|
29
|
+
self.callbacks[callback.description] << callback
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.config
|
33
|
+
notification_timeout = Device::Setting.notification_timeout.empty? ? DEFAULT_TIMEOUT : Device::Setting.notification_timeout.to_i
|
34
|
+
notification_interval = Device::Setting.notification_interval.empty? ? DEFAULT_INTERVAL : Device::Setting.notification_interval.to_i
|
35
|
+
notification_stream_timeout = Device::Setting.notification_stream_timeout.empty? ? DEFAULT_STREAM_TIMEOUT : Device::Setting.notification_stream_timeout.to_i
|
36
|
+
[notification_timeout, notification_interval, notification_stream_timeout]
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.start
|
40
|
+
unless Device::Setting.logical_number.empty? || Device::Setting.company_name.empty? || (! Device::Network.connected?)
|
41
|
+
unless Device::Notification.current && Device::Notification.current.closed?
|
42
|
+
self.new(*self.config)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.setup
|
48
|
+
NotificationCallback.new "APP_UPDATE", :on => Proc.new { Device::ParamsDat.update_apps(true) }
|
49
|
+
NotificationCallback.new "SETUP_DEVICE_CONFIG", :on => Proc.new { Device::ParamsDat.update_apps(true) }
|
50
|
+
NotificationCallback.new "RESET_DEVICE_CONFIG", :on => Proc.new { Device::ParamsDat.format! }
|
51
|
+
|
52
|
+
NotificationCallback.new "SYSTEM_UPDATE", :on => Proc.new { |file| }
|
53
|
+
NotificationCallback.new "CANCEL_SYSTEM_UPDATE", :on => Proc.new { }
|
54
|
+
end
|
55
|
+
|
56
|
+
def initialize(timeout = DEFAULT_TIMEOUT, interval = DEFAULT_INTERVAL, stream_timeout = DEFAULT_STREAM_TIMEOUT)
|
57
|
+
@timeout = timeout
|
58
|
+
@stream_timeout = stream_timeout
|
59
|
+
@interval = interval
|
60
|
+
Device::Notification.current = self
|
61
|
+
@fiber = create_fiber
|
62
|
+
end
|
63
|
+
|
64
|
+
# Check if there is any notification
|
65
|
+
def check
|
66
|
+
if @fiber.alive? && valid_interval? && Device::Network.connected?
|
67
|
+
if (notification = @fiber.resume)
|
68
|
+
Notification.execute(NotificationEvent.new(notification))
|
69
|
+
end
|
70
|
+
@last_check = Time.now
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Close socket and finish Fiber execution
|
75
|
+
def close
|
76
|
+
@fiber.resume "close"
|
77
|
+
end
|
78
|
+
|
79
|
+
def closed?
|
80
|
+
! @fiber.alive?
|
81
|
+
end
|
82
|
+
|
83
|
+
def valid_interval?
|
84
|
+
if @last_check
|
85
|
+
(@last_check + self.interval) < Time.now
|
86
|
+
else
|
87
|
+
true
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
def create_fiber
|
93
|
+
Fiber.new do
|
94
|
+
Serfx.connect(socket_block: socket_callback, timeout: timeout, stream_timeout: stream_timeout) do |conn|
|
95
|
+
conn.stream("user:#{Device::Setting.company_name};#{Device::Setting.logical_number}")
|
96
|
+
end
|
97
|
+
true
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def socket_callback
|
102
|
+
Proc.new do
|
103
|
+
socket_tcp = Device::Network.create_socket
|
104
|
+
socket_tcp.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)
|
105
|
+
|
106
|
+
if Device::Setting.ssl == "1"
|
107
|
+
socket = Device::Network.handshake_ssl(socket_tcp)
|
108
|
+
else
|
109
|
+
socket = socket_tcp
|
110
|
+
end
|
111
|
+
[socket, socket_tcp]
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class Device
|
2
|
+
class NotificationCallback
|
3
|
+
attr_reader :on, :before, :after, :description
|
4
|
+
attr_accessor :results
|
5
|
+
|
6
|
+
CallbackResult = Struct.new(:before_execute, :result, :after_execute)
|
7
|
+
|
8
|
+
def initialize(description, procs = {})
|
9
|
+
@description = description
|
10
|
+
@on = procs[:on]
|
11
|
+
@before = procs[:before]
|
12
|
+
@after = procs[:after]
|
13
|
+
@results = {:on => [], :before => [], :after => []}
|
14
|
+
schedule!
|
15
|
+
end
|
16
|
+
|
17
|
+
def schedule!
|
18
|
+
Notification.schedule(self)
|
19
|
+
end
|
20
|
+
|
21
|
+
def call(event, moment = :on)
|
22
|
+
if support?(moment)
|
23
|
+
results[moment] << CallbackResult.new(
|
24
|
+
Time.now,
|
25
|
+
perform(event, moment),
|
26
|
+
Time.now
|
27
|
+
)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
def perform(event, moment)
|
33
|
+
unless equal_arity?(event, moment)
|
34
|
+
return "Error Arity not Match: Event arity #{event.parameters.size} Proc arity #{send(moment).arity}"
|
35
|
+
end
|
36
|
+
self.send(moment).call(*event.parameters)
|
37
|
+
end
|
38
|
+
|
39
|
+
def support?(moment)
|
40
|
+
! send(moment).nil?
|
41
|
+
end
|
42
|
+
|
43
|
+
def equal_arity?(event, moment)
|
44
|
+
send(moment).arity == event.parameters.size
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class Device
|
2
|
+
class NotificationEvent
|
3
|
+
attr_reader :values, :coalesce, :ltime, :payload, :name, :event, :message, :value, :body, :callback, :parameters
|
4
|
+
|
5
|
+
def initialize(values)
|
6
|
+
@values = values
|
7
|
+
parse(values)
|
8
|
+
end
|
9
|
+
|
10
|
+
def type
|
11
|
+
@event
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
def parse(values)
|
16
|
+
@coalesce = values["Coalesce"]
|
17
|
+
@ltime = values["LTime"]
|
18
|
+
@payload = values["Payload"]
|
19
|
+
@name = values["Name"]
|
20
|
+
@event = values["Event"]
|
21
|
+
|
22
|
+
@message = payload.gsub('=>', ' : ')
|
23
|
+
@value = JSON.parse(message)
|
24
|
+
@body = value["Body"]
|
25
|
+
|
26
|
+
@callback, *@parameters = @body.to_s.split("|")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|