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.
Files changed (79) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.yardopts +12 -0
  4. data/Gemfile +7 -0
  5. data/Gemfile.lock +21 -0
  6. data/README.md +108 -0
  7. data/README_GUIDE.md +52 -0
  8. data/RELEASE_NOTES.md +23 -0
  9. data/Rakefile +69 -0
  10. data/da_funk.gemspec +31 -0
  11. data/ext/da_funk/Makefile +5 -0
  12. data/ext/da_funk/extconf.rb +8 -0
  13. data/guides/sample_input_output.rb +14 -0
  14. data/guides/sample_message_iso8583.rb +14 -0
  15. data/guides/sample_network_gprs.rb +26 -0
  16. data/guides/sample_read_magnect_card.rb +8 -0
  17. data/guides/sample_socket.rb +29 -0
  18. data/guides/sample_transaction.rb +23 -0
  19. data/guides/sample_transaction_download_application.rb +9 -0
  20. data/guides/sample_transaction_download_file.rb +6 -0
  21. data/guides/sample_transaction_download_parameter_file.rb +18 -0
  22. data/guides/sample_transaction_iso8583.rb +222 -0
  23. data/imgs/daft-punk-da-funk.jpg +0 -0
  24. data/lib/da_funk/rake_task.rb +129 -0
  25. data/lib/da_funk/test.rb +87 -0
  26. data/lib/da_funk.rb +39 -0
  27. data/lib/device/application.rb +41 -0
  28. data/lib/device/audio.rb +17 -0
  29. data/lib/device/crypto.rb +52 -0
  30. data/lib/device/display.rb +47 -0
  31. data/lib/device/helper.rb +161 -0
  32. data/lib/device/io.rb +86 -0
  33. data/lib/device/magnetic.rb +79 -0
  34. data/lib/device/network.rb +192 -0
  35. data/lib/device/notification.rb +116 -0
  36. data/lib/device/notification_callback.rb +47 -0
  37. data/lib/device/notification_event.rb +29 -0
  38. data/lib/device/params_dat.rb +119 -0
  39. data/lib/device/printer.rb +35 -0
  40. data/lib/device/runtime.rb +22 -0
  41. data/lib/device/setting.rb +63 -0
  42. data/lib/device/support.rb +28 -0
  43. data/lib/device/system.rb +61 -0
  44. data/lib/device/transaction/download.rb +268 -0
  45. data/lib/device/transaction/emv.rb +45 -0
  46. data/lib/device/transaction/iso.rb +75 -0
  47. data/lib/device/version.rb +11 -0
  48. data/lib/device/walk.rb +8 -0
  49. data/lib/device.rb +28 -0
  50. data/lib/ext/kernel.rb +9 -0
  51. data/lib/file_db.rb +47 -0
  52. data/lib/iso8583/bitmap.rb +114 -0
  53. data/lib/iso8583/codec.rb +197 -0
  54. data/lib/iso8583/exception.rb +4 -0
  55. data/lib/iso8583/field.rb +90 -0
  56. data/lib/iso8583/fields.rb +171 -0
  57. data/lib/iso8583/message.rb +455 -0
  58. data/lib/iso8583/util.rb +91 -0
  59. data/lib/iso8583/version.rb +6 -0
  60. data/lib/serfx/commands.rb +191 -0
  61. data/lib/serfx/connection.rb +160 -0
  62. data/lib/serfx/exceptions.rb +5 -0
  63. data/lib/serfx/response.rb +28 -0
  64. data/lib/serfx.rb +27 -0
  65. data/lib/version.rb +5 -0
  66. data/lib/zip.rb +29 -0
  67. data/test/integration/getc_test.rb +6 -0
  68. data/test/integration/mrb_eval_test.rb +36 -0
  69. data/test/integration/notification_test.rb +20 -0
  70. data/test/integration/params_dat_test.rb +11 -0
  71. data/test/test_helper.rb +18 -0
  72. data/test/unit/device/display_test.rb +43 -0
  73. data/test/unit/device/helper_test.rb +61 -0
  74. data/test/unit/device/notification_callback_test.rb +6 -0
  75. data/test/unit/device/notification_event_test.rb +73 -0
  76. data/test/unit/device/notification_test.rb +21 -0
  77. data/utils/command_line_platform.rb +67 -0
  78. data/utils/test_run.rb +3 -0
  79. 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