client_for_poslynx 0.9.0 → 1.0.0.pre

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,85 @@
1
+ require 'client_for_poslynx'
2
+
3
+ # Given that fake_pos_terminal is running on the local system and
4
+ # listening on port 3010, this example will execute the following
5
+ # workflow...
6
+ #
7
+ # 1. Display a message on the PIN pad with buttons.
8
+ # 2. After a button is selected on the PIN pad, display a message
9
+ # showing info about the button response for 5 seconds.
10
+ # 3. Reset the PIN pad.
11
+ #
12
+ # This example will also work using an actual POSLynx and PIN pad
13
+ # if the IP address, port number, encryption, and client MAC
14
+ # values are changed appropriately.
15
+
16
+ SERVER_IP = '127.0.0.1'
17
+ SERVER_PORT = 3010
18
+ # :none for no encryption. :use_ssl for SSL encryption.
19
+ ENCRYPTION = :none
20
+ CLIENT_MAC = 'whatever'
21
+
22
+
23
+ gem 'client_for_poslynx'
24
+ require 'client_for_poslynx'
25
+
26
+ include ClientForPoslynx::Net
27
+ include ClientForPoslynx::Data
28
+
29
+ connector = EM_Connector.new(SERVER_IP, SERVER_PORT, encryption: ENCRYPTION)
30
+
31
+ EventMachine.run do
32
+
33
+ EM_Session.execute connector do |s|
34
+ begin
35
+ request_data = Requests::PinPadDisplayMessage.new.tap{ |r|
36
+ r.client_mac = CLIENT_MAC
37
+ r.line_count = 2
38
+ r.text_lines = ["How done", "do you want it"]
39
+ r.button_labels = %w(Rare Medium Well-done)
40
+ }
41
+ response = s.request( request_data )
42
+ unless response.error_code == '0000'
43
+ puts "Got failure response from 1st request"
44
+ puts "Error code: #{response.error_code}. Result text: #(response.result}"
45
+ return
46
+ end
47
+
48
+ request_data = Requests::PinPadDisplayMessage.new.tap{ |r|
49
+ r.client_mac = CLIENT_MAC
50
+ r.line_count = 2
51
+ r.text_lines = [
52
+ "Got result: #{response.result}",
53
+ "Got button response: #{response.button_response}"
54
+ ]
55
+ }
56
+ s.request request_data
57
+ unless response.error_code == '0000'
58
+ puts "Got failure response from 2nd request"
59
+ puts "Error code: #{response.error_code}. Result text: #(response.result}"
60
+ return
61
+ end
62
+
63
+ s.sleep 4
64
+
65
+ request_data = Requests::PinPadReset.new.tap{ |r|
66
+ r.client_mac = CLIENT_MAC
67
+ }
68
+ s.request request_data
69
+ unless response.error_code == '0000'
70
+ puts "Got failure response from concluding reset request"
71
+ puts "Error code: #{response.error_code}. Result text: #(response.result}"
72
+ return
73
+ end
74
+
75
+ rescue EM_Session::RequestError => e
76
+ puts "An exception was encountered in the session"
77
+ puts e.message
78
+
79
+ ensure
80
+ EventMachine.stop_event_loop
81
+
82
+ end
83
+ end
84
+
85
+ end
@@ -0,0 +1,155 @@
1
+ require 'client_for_poslynx'
2
+
3
+ # This example will also work using an actual POSLynx and PIN pad
4
+ # if the IP address, port number, encryption, and client MAC
5
+ # values are changed appropriately.
6
+
7
+ SERVER_IP = '127.0.0.1'
8
+ SERVER_PORT = 3010
9
+ # :none for no encryption. :use_ssl for SSL encryption.
10
+ ENCRYPTION = :none
11
+ CLIENT_MAC = 'whatever'
12
+
13
+
14
+ gem 'client_for_poslynx'
15
+ require 'client_for_poslynx'
16
+
17
+ include ClientForPoslynx::Net
18
+ include ClientForPoslynx::Data
19
+
20
+ connector = EM_Connector.new(SERVER_IP, SERVER_PORT, encryption: ENCRYPTION)
21
+
22
+ session_a = ->(s) {
23
+ begin
24
+ request_data = Requests::PinPadDisplayMessage.new.tap{ |r|
25
+ r.client_mac = CLIENT_MAC
26
+ r.line_count = 2
27
+ r.text_lines = ["How done", "do you want it"]
28
+ r.button_labels = %w(Rare Medium Well-done)
29
+ }
30
+ response = s.request( request_data )
31
+ unless response.error_code == '0000'
32
+ puts "Got failure response in session_a from 1st request"
33
+ puts "Error code: #{response.error_code}. Result text: #(response.result}"
34
+ return
35
+ end
36
+
37
+ request_data = Requests::PinPadDisplayMessage.new.tap{ |r|
38
+ r.client_mac = CLIENT_MAC
39
+ r.line_count = 2
40
+ r.text_lines = [
41
+ "Got result: #{response.result}",
42
+ "Got button response: #{response.button_response}"
43
+ ]
44
+ }
45
+ s.request request_data
46
+ unless response.error_code == '0000'
47
+ puts "Got failure response in session_a from 2st request"
48
+ puts "Error code: #{response.error_code}. Result text: #(response.result}"
49
+ return
50
+ end
51
+
52
+ s.sleep 4
53
+
54
+ request_data = Requests::PinPadReset.new.tap{ |r|
55
+ r.client_mac = CLIENT_MAC
56
+ }
57
+ s.request request_data
58
+ unless response.error_code == '0000'
59
+ puts "Got failure response in session_b from concluding reset-request"
60
+ puts "Error code: #{response.error_code}. Result text: #(response.result}"
61
+ return
62
+ end
63
+
64
+ EventMachine.stop_event_loop
65
+
66
+ rescue EM_Session::RequestError => e
67
+ puts "An exception was encountered in session_a"
68
+ puts e.message
69
+
70
+ end
71
+ }
72
+
73
+ session_b = ->(s) {
74
+ begin
75
+ # Let session_a get underway before interrupting.
76
+ s.sleep 3
77
+
78
+ request_data = Requests::PinPadReset.new.tap{ |r|
79
+ r.client_mac = CLIENT_MAC
80
+ }
81
+ response = s.request( request_data )
82
+ unless response.error_code == '0000'
83
+ puts "Got failure response in session_b from 1st request"
84
+ puts "Error code: #{response.error_code}. Result text: #(response.result}"
85
+ return
86
+ end
87
+
88
+ request_data = Requests::PinPadDisplayMessage.new.tap{ |r|
89
+ r.client_mac = CLIENT_MAC
90
+ r.line_count = 1
91
+ r.text_lines = "Rudely interrupting..."
92
+ }
93
+ s.request request_data
94
+ unless response.error_code == '0000'
95
+ puts "Got failure response in session_b from 2nd request"
96
+ puts "Error code: #{response.error_code}. Result text: #(response.result}"
97
+ return
98
+ end
99
+
100
+ s.sleep 3
101
+
102
+ request_data = Requests::PinPadDisplayMessage.new.tap{ |r|
103
+ r.client_mac = CLIENT_MAC
104
+ r.line_count = 2
105
+ r.text_lines = ["How annoying was", "this interruption?"]
106
+ r.button_labels = %w(Very Not-much)
107
+ }
108
+ response = s.request( request_data )
109
+ unless response.error_code == '0000'
110
+ puts "Got failure response in session_b from 3rd request"
111
+ puts "Error code: #{response.error_code}. Result text: #(response.result}"
112
+ return
113
+ end
114
+
115
+ request_data = Requests::PinPadDisplayMessage.new.tap{ |r|
116
+ r.client_mac = CLIENT_MAC
117
+ r.line_count = 2
118
+ r.text_lines = [
119
+ "Got result: #{response.result}",
120
+ "Got button response: #{response.button_response}"
121
+ ]
122
+ }
123
+ s.request request_data
124
+ unless response.error_code == '0000'
125
+ puts "Got failure response in session_b from 4th request"
126
+ puts "Error code: #{response.error_code}. Result text: #(response.result}"
127
+ return
128
+ end
129
+
130
+ s.sleep 4
131
+
132
+ request_data = Requests::PinPadReset.new.tap{ |r|
133
+ r.client_mac = CLIENT_MAC
134
+ }
135
+ s.request request_data
136
+ unless response.error_code == '0000'
137
+ puts "Got failure response in session_b from concluding reset-request"
138
+ puts "Error code: #{response.error_code}. Result text: #(response.result}"
139
+ return
140
+ end
141
+
142
+ rescue EM_Session::RequestError => e
143
+ puts "An exception was encountered in session_b"
144
+ puts e.message
145
+
146
+ ensure
147
+ EventMachine.stop_event_loop
148
+
149
+ end
150
+ }
151
+
152
+ EventMachine.run do
153
+ EM_Session.execute connector, &session_a
154
+ EM_Session.execute connector, &session_b
155
+ end
@@ -29,6 +29,12 @@ module ClientForPoslynx
29
29
  visitor.public_send "visit_#{simple_class_name}", self
30
30
  end
31
31
 
32
+ # True is the given object is of the right type to be a
33
+ # response to a request made using this request data.
34
+ def potential_response?(candidate)
35
+ self.class.response_class === candidate
36
+ end
37
+
32
38
  end
33
39
 
34
40
  end
@@ -1,7 +1,8 @@
1
1
  # coding: utf-8
2
2
 
3
3
  require_relative 'net/em_protocol'
4
- require_relative 'net/structured_client'
4
+ require_relative 'net/em_connector'
5
+ require_relative 'net/em_session'
5
6
 
6
7
  module ClientForPoslynx
7
8
 
@@ -0,0 +1,344 @@
1
+ # coding: utf-8
2
+
3
+ module ClientForPoslynx
4
+ module Net
5
+ class EM_Connector ; end
6
+
7
+ # Convenient shorthand for EM_Connector for use within
8
+ # Net and its sub-nested classes and modules.
9
+ EMC = EM_Connector
10
+ end
11
+ end
12
+
13
+ require_relative 'em_connector/connector_state'
14
+ require_relative 'em_connector/handles_connection'
15
+ require_relative 'em_connector/connection_handler'
16
+ require_relative 'em_connector/event_dispatcher'
17
+ require_relative 'em_connector/request_call'
18
+ require_relative 'em_connector/callback_map'
19
+
20
+ module ClientForPoslynx
21
+ module Net
22
+
23
+ # A <tt>ClientForPoslynx::Net::EM_Connector</tt> object is
24
+ # associated with a specific POSLynx host (lane) and provides
25
+ # a convenient means of connecting or re-connecting to the
26
+ # host any number of times within an Event Manager run loop.
27
+ #
28
+ # An instance of <tt>ClientForPoslynx::Net::EM_Connector</tt>
29
+ # may be created either inside or outside of a run loop, but
30
+ # it must be used from within a run loop to make or interact
31
+ # with connections since that's the only context in which
32
+ # Event Manager connections are applicable.
33
+ class EM_Connector
34
+ extend Forwardable
35
+
36
+ # Creates a new
37
+ # <tt>ClientForPoslynx::Net::EM_Connector</tt>
38
+ # instance.
39
+ #
40
+ # The <tt>server</tt> and <tt>port</tt> arguments are
41
+ # passed to Event Manager's methods for connecting or
42
+ # reconnecting to the poslynx lane as needed.
43
+ #
44
+ # ==== Options
45
+ # * <tt>:encryption<tt> - <tt>:use_ssl</tt> if SSL
46
+ # if the connection should be encrypted using SSL.
47
+ # <tt>:none</tt> if the connection should not be
48
+ # encrypted. Defaults to <tt>:none</tt>.
49
+ # * <tt>:handler<tt> - The class given as the handler
50
+ # argument to the <tt>::connect</tt> call to the Event
51
+ # Machine system (normally <tt>::EM</tt>).
52
+ # Defaults to
53
+ # <tt>ClientForPoslynx::Net::EM_Connector::ConnectionHandler</tt>
54
+ # This should generally be a subclass of
55
+ # <tt>ClientForPoslynx::Net::EM_Connector::ConnectionHandler</tt>
56
+ # or be a class that includes
57
+ # <tt>ClientforPoslynx::Net::EM_Connector::HandlesConnection</tt>
58
+ # and is a subclass of <tt>EM::Connection</tt>.
59
+ # When using a substitute for <tt>::EM</tt> as the Event
60
+ # Machine system, this might not need to be a subclass of
61
+ # <tt>EM::Connection</tt>, though it will need to supply
62
+ # substitute implementations of several of the
63
+ # <tt>EM::Connection</tt> methods in that case.
64
+ # * <tt>:em_system</tt> - The event machine system that
65
+ # will be called on for making connections. Defaults
66
+ # to <tt>::EM</tt>. Must implement the <tt>::connect</tt>
67
+ # method.
68
+ # This is used for dependency injection in unit tests and
69
+ # may be useful for inserting instrumentation arround the
70
+ # <tt>::connect</tt> call within an application.
71
+ def initialize(server, port, opts={})
72
+ @server = server
73
+ @port = port
74
+ state.encryption = opts.fetch( :encryption, :none )
75
+ @handler_class = opts.fetch( :handler, EMC::ConnectionHandler )
76
+ @em_system = opts.fetch( :em_system, ::EM )
77
+ state.connection_status ||= :initial
78
+ state.status_of_request ||= :initial
79
+ end
80
+
81
+ # The POSLynx server to be conected to.
82
+ attr_reader :server
83
+
84
+ # The server port through which to connected.
85
+ attr_reader :port
86
+
87
+ # The encryption mode <tt>:use_ssl</tt> or <tt>:none</tt>
88
+ def_delegator :state, :encryption
89
+
90
+ # The connection handler instance (connection) after the
91
+ # first call to <tt>#connect</tt>. It will be an instance
92
+ # of the connection handler class.
93
+ def connection
94
+ state.connection
95
+ end
96
+
97
+ # The current connection status. One of <tt>:initial</tt>,
98
+ # <tt>:connecting</tt>, <tt>:connected</tt>,
99
+ # <tt>:disconnecting</tt>, <tt>:disconnected</tt>.
100
+ def_delegator :state, :connection_status
101
+
102
+ # True when no connection attempt has been initiated yet.
103
+ def_delegator :state, :connection_initial?
104
+
105
+ # True when a connection attempt is in progress.
106
+ def_delegator :state, :connecting?
107
+
108
+ # True when currently connected.
109
+ def_delegator :state, :connected?
110
+
111
+ # True when disconnection is in progress.
112
+ def_delegator :state, :disconnecting?
113
+
114
+ # True when a opening a connection has been previously
115
+ # attempted or successful, and not currently connected.
116
+ def_delegator :state, :disconnected?
117
+
118
+ # An instance of <tt>EM_Connector::RequestCall</tt>
119
+ # containing the request data and result callbacks from the
120
+ # most recent <tt>#send_request</tt> or
121
+ # <tt>#get_response</tt> call.
122
+ attr_reader :latest_request
123
+
124
+ # The current <tt>#send_request</tt> status. One of
125
+ # <tt>:initial</tt>, <tt>:pending</tt>,
126
+ # <tt>:got_response</tt>, <tt>:failed</tt>.
127
+ def_delegator :state, :status_of_request
128
+
129
+ # True when no request-send has been initiated yet.
130
+ def_delegator :state, :request_initial?
131
+
132
+ # True when a request-send is in progress.
133
+ def_delegator :state, :request_pending?
134
+
135
+ # True when a response was receied from the most recently
136
+ # attempted request-send.
137
+ def_delegator :state, :got_response?
138
+
139
+ # True when the most recently attempted request-send
140
+ # resulted in failure.
141
+ def_delegator :state, :request_failed?
142
+
143
+ # The connection handler class to be passed as the handler
144
+ # argument to <tt>EM::connect</tt>.
145
+ attr_reader :handler_class
146
+
147
+ # The Event Manager system that will be called on for
148
+ # making connections.
149
+ attr_reader :em_system
150
+
151
+ # Asynchronously attempts to open an EventMachine
152
+ # connection to the POSLynx lane.
153
+ #
154
+ # The underlying connection instance is available via
155
+ # <tt>#connection</tt> immediately after the call returns,
156
+ # though it might not yet represent a completed, open
157
+ # connection.
158
+ #
159
+ # If the connector already has a currently open connection,
160
+ # then the call to <tt>#connect</tt> succeeds immediately
161
+ # and synchronously.
162
+ #
163
+ # Once the connection is completed or fails, the
164
+ # <tt>#call</tt> method of the apporopriate callback
165
+ # object (if supplied) is invoked with no arguments.
166
+ #
167
+ # If another <tt>#connect</tt> request is already pending,
168
+ # then the the new request is combined with the pending for
169
+ # request, and the appropriate callback will be invoked
170
+ # each of those when the connection attempt is subsequently
171
+ # concluded.
172
+ #
173
+ # ==== Result callback options
174
+ # * <tt>:on_success</tt> - An object to receive
175
+ # <tt>#call</tt> when the connection is successfully
176
+ # opened.
177
+ # * <tt>:on_failure</tt> - An object to receive
178
+ # <tt>#call</tt> when the connection attempt fails.
179
+ def connect(result_callbacks={})
180
+ result_callbacks = EMC.CallbackMap(result_callbacks)
181
+ if connection_initial?
182
+ make_initial_connection result_callbacks
183
+ elsif connected?
184
+ result_callbacks.call :on_success
185
+ elsif connecting?
186
+ piggyback_connect result_callbacks
187
+ else
188
+ reconnect result_callbacks
189
+ end
190
+ end
191
+
192
+ # When called from within an EventManager event-handling
193
+ # loop, asynchronously attempts to disconnect the current
194
+ # EventMachine connection to the POSLynx lane.
195
+ #
196
+ # If there is no currently open connection, then the call
197
+ # to <tt>#disconnect</tt> succeeds immediately and
198
+ # synchronously.
199
+ #
200
+ # Note that you might never have reason to call this since
201
+ # Event Machine automatically closes connections when the
202
+ # run loop is stopped.
203
+ #
204
+ # ==== Result callback options
205
+ # * <tt>:on_completed</tt> - An object to receive
206
+ # <tt>#call</tt> when finished disconnecting.
207
+ def disconnect(result_callbacks={})
208
+ result_callbacks = EMC.CallbackMap( result_callbacks )
209
+ if connected?
210
+ connection.event_dispatcher =
211
+ EMC::EventDispatcher.for_disconnect( connection, result_callbacks )
212
+ state.connection_status = :disconnecting
213
+ connection.close_connection
214
+ else
215
+ result_callbacks.call :on_completed
216
+ end
217
+ end
218
+
219
+ # When called with an open connection, asynchronously sends
220
+ # a request to the POSLynx with the given request data.
221
+ #
222
+ # When the response is received, the response data is
223
+ # passed to the <tt>#call</tt> method of the
224
+ # <tt>:on_response</tt> handler.
225
+ #
226
+ # If <tt>#send_request</tt> is called without an open
227
+ # connection or when the connection is lost before any
228
+ # response is received, the <tt>#call</tt> method of the
229
+ # <tt>:on_failure</tt> callback is invoked.
230
+ #
231
+ # * <tt>request_data</tt> - The request to be sent. Should
232
+ # be an instance of one of the
233
+ # <tt>ClientForPoslynx::Data::Requests::...</tt> classes.
234
+ # * <tt>result_callbacks</tt> - A hash map of objects, each
235
+ # of which handles a response condition when it receives
236
+ # <tt>#call</tt>
237
+ #
238
+ # ==== Result callback options
239
+ # * <tt>:on_response</tt> - An object to receive
240
+ # <tt>#call</tt> with the response data when the response
241
+ # is received.
242
+ # * <tt>:on_failure</tt> - An object to receive
243
+ # <tt>#call</tt> if there is no open connection or when
244
+ # the connection is lost.
245
+ # * Any other callback(s) that might need to be received
246
+ # from a collaborator via <tt>#latest_request</tt> data
247
+ # while the request is pending. <tt>EMSession</tt> may
248
+ # invoke an <tt>:on_detached</tt> callback, for example.
249
+ def send_request(request_data, result_callbacks={})
250
+ result_callbacks = EMC.CallbackMap( result_callbacks )
251
+ unless connected?
252
+ result_callbacks.call :on_failure
253
+ return
254
+ end
255
+ self.latest_request = EMC.RequestCall( request_data, result_callbacks )
256
+ state.status_of_request = :pending
257
+ connection.event_dispatcher = EMC::EventDispatcher.for_send_request(
258
+ connection, result_callbacks
259
+ )
260
+ connection.send_request request_data
261
+ end
262
+
263
+ # This methid exists to support 2 special-case scenarios
264
+ # that client code may need to handle.
265
+ #
266
+ # Scenario A:
267
+ #
268
+ # A request is in progress, and the original result
269
+ # handlers must be replaced with new handlers. For
270
+ # instance, a PIN Pad Reset may have been in progress to
271
+ # initate one workflow, and that workflow is being aborted
272
+ # to initiate a different workflow.
273
+ #
274
+ # Scenario B:
275
+ #
276
+ # An attempt was made to cancel a previous pending request
277
+ # by sending another request, but a response to the
278
+ # original request was subsequently received because it was
279
+ # already in transit. Now, we need to resume waiting for
280
+ # the response to the second request since it is actually
281
+ # still pending.
282
+ #
283
+ # The options for <tt>#get_response</tt> are the same as
284
+ # for <tt>#send_request</tt> and have the same meanings,
285
+ # but since no new request is to be made, there is no
286
+ # <tt>request_data</tt> argument.
287
+ #
288
+ # If the <tt>#status_of_request</tt> is
289
+ # <tt>:got_response</tt> before the invocation, then it is
290
+ # reverted to # <tt>:pending</tt>.
291
+ def get_response(result_callbacks={})
292
+ result_callbacks = EMC.CallbackMap( result_callbacks )
293
+ unless connected? && ( request_pending? || got_response? )
294
+ result_callbacks.call :on_failure
295
+ return
296
+ end
297
+ self.latest_request = EMC.RequestCall( latest_request.request_data, result_callbacks )
298
+ state.status_of_request = :pending
299
+ connection.event_dispatcher = EMC::EventDispatcher.for_send_request(
300
+ connection, result_callbacks
301
+ )
302
+ end
303
+
304
+ private
305
+
306
+ def state
307
+ @state ||= State.new
308
+ end
309
+
310
+ def latest_request=(value)
311
+ args = Array(value)
312
+ @latest_request = EMC.RequestCall( *args )
313
+ end
314
+
315
+ def make_initial_connection(result_callbacks)
316
+ state.connection_status = :connecting
317
+ em_system.connect \
318
+ server, port,
319
+ handler_class, state
320
+ connection.event_dispatcher = EMC::EventDispatcher.for_connect(
321
+ connection, result_callbacks
322
+ )
323
+ end
324
+
325
+ def reconnect(connect_event_dispatch_opts)
326
+ state.connection_status = :connecting
327
+ connection.event_dispatcher = EMC::EventDispatcher.for_connect(
328
+ connection, connect_event_dispatch_opts
329
+ )
330
+ connection.reconnect server, port
331
+ end
332
+
333
+ def piggyback_connect(response_callbacks)
334
+ response_callbacks = response_callbacks.merge(
335
+ original_dispatcher: connection.event_dispatcher
336
+ )
337
+ connection.event_dispatcher = EMC::EventDispatcher.for_connect(
338
+ connection, response_callbacks
339
+ )
340
+ end
341
+ end
342
+
343
+ end
344
+ end