client_for_poslynx 0.9.0 → 1.0.0.pre

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.
@@ -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