lis 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
data/lib/lis/messages.rb CHANGED
@@ -1,6 +1,9 @@
1
1
  require 'date'
2
2
 
3
3
  module LIS::Message
4
+
5
+ # declarative definition of message structure
6
+ #
4
7
  module ClassMethods
5
8
  CONVERSION_WRITER = {
6
9
  :string => lambda { |s| s },
@@ -13,21 +16,6 @@ module LIS::Message
13
16
  @field_count
14
17
  end
15
18
 
16
- def from_string(message)
17
- type, data = parse(message)
18
- klass = (@@messages_by_type || {})[type]
19
- raise "unknown message type #{type.inspect}" unless klass
20
-
21
- obj = klass.allocate
22
-
23
- data = data.to_enum(:each_with_index).inject({}) do |h,(elem,idx)|
24
- h[idx+2] = elem; h
25
- end
26
-
27
- obj.instance_variable_set("@fields", data)
28
- obj
29
- end
30
-
31
19
  def default_fields
32
20
  arr = Array.new(@field_count)
33
21
  (0 .. @field_count).inject(arr) do |a,i|
@@ -40,6 +28,8 @@ module LIS::Message
40
28
  end
41
29
  end
42
30
 
31
+ # define a field of a message at a specific index and an optional type
32
+ #
43
33
  def has_field(idx, name = nil, opts={})
44
34
  set_index_for(name, idx) if name
45
35
  set_field_attributes(idx, { :name => name,
@@ -65,13 +55,8 @@ module LIS::Message
65
55
  end
66
56
  end
67
57
 
68
- def parse(string)
69
- type, data = string.scan(/^(.)\|(.*)$/)[0]
70
- data = data.split(/\|/)
71
-
72
- return [type, data]
73
- end
74
-
58
+ # registers a message with a specific type prefix to make {Message::Base.from_string} work
59
+ #
75
60
  def type_id(char = nil)
76
61
  return @@messages_by_type.find {|c,klass| klass == self }.first unless char
77
62
  @@messages_by_type ||= {}
@@ -100,11 +85,25 @@ module LIS::Message
100
85
  @field_names ||= {}
101
86
  @field_names[index] = hash
102
87
  end
88
+
89
+ private
90
+
91
+ def class_for_type(type)
92
+ klass = (@@messages_by_type || {})[type]
93
+ raise "unknown message type #{type.inspect}" unless klass
94
+ return klass
95
+ end
96
+
97
+ def parse(string)
98
+ type, data = string.scan(/^(.)\|(.*)$/)[0]
99
+ data = data.split(/\|/)
100
+
101
+ return [type, data]
102
+ end
103
103
  end
104
104
 
105
105
  class Base
106
106
  extend ClassMethods
107
- attr_accessor :frame_number
108
107
 
109
108
  has_field 2, :sequence_number, :type => :int, :default => 1
110
109
 
@@ -112,11 +111,36 @@ module LIS::Message
112
111
  self.class.type_id
113
112
  end
114
113
 
114
+ # serialize a Message object into a String
115
+ #
116
+ # message = Message.from_string("5L|1|N") #=> <LIS::Message>
117
+ # message.to_message #=> "5L|1|N"
118
+ #
115
119
  def to_message
116
120
  @fields ||= {}
117
121
  arr = Array.new(self.class.default_fields)
118
122
  type_id + @fields.inject(arr) { |a,(k,v)| a[k-1] = v; a }.join("|")
119
123
  end
120
- end
121
124
 
125
+ # instantiate a new Message from a string
126
+ #
127
+ # Message.from_string("5L|1|N") #=> <LIS::Message::Terminator>
128
+ #
129
+ # register subclasses by using {ClassMethods#type_id type_id}
130
+ #
131
+ def self.from_string(message)
132
+ type, data = parse(message)
133
+ klass = class_for_type(type)
134
+
135
+ obj = klass.allocate
136
+
137
+ data = data.to_enum(:each_with_index).inject({}) do |h,(elem,idx)|
138
+ h[idx+2] = elem; h
139
+ end
140
+
141
+ obj.instance_variable_set("@fields", data)
142
+ obj
143
+ end
144
+ end
122
145
  end
146
+
@@ -0,0 +1,104 @@
1
+
2
+ module LIS::Transfer
3
+ class ApplicationProtocol < ::PacketIO::Base
4
+ attr_reader :device_name
5
+
6
+ def on_result(&block)
7
+ @on_result_callback = block
8
+ end
9
+
10
+ def on_request(&block)
11
+ @on_request_callback = block
12
+ end
13
+
14
+ def received_header(message)
15
+ @patient_information_requests ||= {} # delete the list of patients
16
+ @device_name = message.sender_name
17
+ end
18
+
19
+ def result_for(patient, order, result)
20
+ @on_result_callback.call(@device_name, patient, order, result)
21
+ end
22
+
23
+ def received_patient_information(message)
24
+ @last_order = nil
25
+ @last_patient = message
26
+ end
27
+
28
+ def received_order_record(message)
29
+ @last_order = message
30
+ end
31
+
32
+ def received_result(message)
33
+ result_for(@last_patient, @last_order, message)
34
+ end
35
+
36
+ def received_request_for_information(message)
37
+ @patient_information_requests ||= {}
38
+ requests = @on_request_callback.call(@device_name, message.starting_range_id)
39
+ @patient_information_requests[message.sequence_number] = requests if requests
40
+ end
41
+
42
+ def send_pending_requests
43
+ sending_session(@patient_information_requests) do |patient_information|
44
+ patient_information.each do |sequence_nr, data|
45
+ write :message, LIS::Message::Patient.new(sequence_nr,
46
+ data["patient"]["number"],
47
+ data["patient"]["last_name"],
48
+ data["patient"]["first_name"]).to_message
49
+ data["types"].each do |request|
50
+ write :message, LIS::Message::Order.new(sequence_nr, data["id"], request).to_message
51
+ end
52
+ end
53
+ end
54
+ @patient_information_requests = {}
55
+ end
56
+
57
+ def initialize(*args)
58
+ super
59
+
60
+ @patient_information_requests = {}
61
+ @last_patient = nil
62
+ @last_order = nil
63
+ @handlers = {
64
+ LIS::Message::Header => :received_header,
65
+ LIS::Message::Patient => :received_patient_information,
66
+ LIS::Message::Order => :received_order_record,
67
+ LIS::Message::Result => :received_result,
68
+ LIS::Message::Query => :received_request_for_information
69
+ }
70
+ end
71
+
72
+ def receive(type, message = nil)
73
+ warn "[R] #{message}" if type == :message and $VERBOSE
74
+ case type
75
+ when :begin
76
+ @last_patient = nil
77
+ @last_order = nil
78
+ when :idle
79
+ send_pending_requests
80
+ when :message
81
+ @message = LIS::Message::Base.from_string(message)
82
+ handler = @handlers[@message.class]
83
+ send(handler, @message) if handler
84
+ end
85
+ end
86
+
87
+ def write(type, message=nil)
88
+ warn "[S] #{message}" if type == :message and $VERBOSE
89
+ super
90
+ end
91
+
92
+ # @yield data
93
+ def sending_session(data)
94
+ # don't send anything if there are no pending requests
95
+ return if data.nil? or data.empty?
96
+
97
+ write :begin
98
+ write :message, LIS::Message::Header.new("LIS", @device_name).to_message
99
+ yield data
100
+ write :message, LIS::Message::Terminator.new.to_message
101
+ write :idle
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,121 @@
1
+ module LIS::Transfer
2
+
3
+ module ASTM
4
+ end
5
+
6
+ # splits a stream into lis packets and only lets packets through
7
+ # that are inside a session delimited by ENQ .. EOT
8
+ #
9
+ # check the checksum and do acknowledgement of messages
10
+ #
11
+ # forwards the following events:
12
+ #
13
+ # - :message, String :: when a message is received
14
+ # - :idle :: when a transmission is finished (after EOT is received)
15
+ #
16
+ class ASTM::E1394 < ::PacketIO::Base
17
+ ACK = "\006"
18
+ NAK = "\025"
19
+ ENQ = "\005"
20
+ EOT = "\004"
21
+
22
+ # format of a message
23
+ RX = /(?:
24
+ \005 | # ENQ - start a transaction
25
+ \004 | # EOT - ends a transaction
26
+ \005 | # ACK
27
+ \025 | # NAK
28
+ (?:\002 (.) (.*?) \015 \003 (.+?) \015 \012) # a message with a checksum
29
+ # | | `-- checksum
30
+ # | `------------------ message
31
+ # `---------------------- frame number
32
+ )
33
+ /xm
34
+
35
+ def initialize(*args)
36
+ super(*args)
37
+ @memo = ""
38
+ @inside_transmission = false
39
+ end
40
+
41
+ def receive(data)
42
+ scanner = StringScanner.new(@memo + data)
43
+ while scanner.scan_until(RX)
44
+ match = scanner.matched
45
+ case match
46
+ when ENQ then transmission_start
47
+ when EOT then transmission_end
48
+ when ACK, NAK then nil
49
+ else
50
+ received_message(match)
51
+ write :ack
52
+ end
53
+ end
54
+ @memo = scanner.rest
55
+ nil
56
+ end
57
+
58
+ def write(type, data = nil)
59
+ str = case type
60
+ when :ack then ACK
61
+ when :nak then NAK
62
+ when :begin then
63
+ @frame_number = 0
64
+ ENQ
65
+ when :idle then EOT
66
+ when :message then
67
+ @frame_number = (@frame_number + 1) % 8
68
+ self.class.wrap_message(data, @frame_number)
69
+ else
70
+ raise ArgumentError
71
+ end
72
+ super(str)
73
+ end
74
+
75
+
76
+ private
77
+
78
+ def self.message_from_string(string)
79
+ match = string.match(RX)
80
+
81
+ frame_number, data, checksum = match[1 .. 3]
82
+
83
+ expected_checksum = (frame_number + data).each_byte.inject(16) { |a,b| (a+b) % 0x100 }
84
+ actual_checksum = checksum.to_i(16)
85
+
86
+ raise "checksum mismatch: expected %03x got %03x" % [expected_checksum, actual_checksum] unless expected_checksum == actual_checksum
87
+ return [frame_number.to_i, data]
88
+ end
89
+
90
+ def self.wrap_message(string, frame_number)
91
+ frame_number = (frame_number % 8).to_s
92
+ checksum = (frame_number + string).each_byte.inject(16) { |a,b| (a+b) % 0x100 }
93
+ checksum = checksum.to_s(16).upcase.rjust(2,"0")
94
+
95
+ "\002#{frame_number}#{string}\015\003#{checksum}\015\012"
96
+ end
97
+
98
+
99
+ def received_message(message)
100
+ return false unless @inside_transmission
101
+ frame_number, message = *self.class.message_from_string(message)
102
+ forward(:message, message)
103
+ end
104
+
105
+ def transmission_start
106
+ return false if @inside_transmission
107
+ write :ack
108
+ forward :begin
109
+ @inside_transmission = true
110
+ true
111
+ end
112
+
113
+ def transmission_end
114
+ return false unless @inside_transmission
115
+ forward :idle
116
+ @inside_transmission = false
117
+ true
118
+ end
119
+ end
120
+ end
121
+
@@ -0,0 +1,4 @@
1
+ module LIS
2
+ VERSION = "0.2.2"
3
+ end
4
+
@@ -6,6 +6,15 @@ class WorklistManagerInterface
6
6
  @endpoint = endpoint
7
7
  end
8
8
 
9
+ # expects all pending requests for the given device and barcode
10
+ #
11
+ # { "id" => "1234",
12
+ # "patient" => { "number" => 98,
13
+ # "last_name" => "Sierra",
14
+ # "first_name" => "Rudolph" },
15
+ # "types" => [ "TSH", "FT3", "FT4" ] }
16
+ #
17
+ #
9
18
  def load_requests(device_name, barcode)
10
19
  begin
11
20
  uri = URI.join(@endpoint,"find_requests/#{[device_name, barcode].join('-')}")
@@ -32,7 +41,7 @@ class WorklistManagerInterface
32
41
  "result_timestamp" => result.test_completed_at
33
42
  }
34
43
 
35
- p data
44
+ # FIXME: WTF: should not just catch everything
36
45
  begin
37
46
  res = Net::HTTP.post_form(URI.join(@endpoint, "result/#{[device_name, barcode].join('-')}"), data.to_hash)
38
47
  rescue Exception => e
data/lib/lis.rb CHANGED
@@ -1,16 +1,15 @@
1
1
  $:.unshift File.dirname(__FILE__)
2
2
 
3
- module LIS
4
- VERSION = "0.2.1"
5
- end
3
+ require 'packet_io'
6
4
 
7
- require 'lis/io_listener.rb'
8
- require 'lis/messages.rb'
5
+ require 'lis/version'
9
6
 
7
+ require 'lis/messages.rb'
10
8
  Dir[File.join(File.dirname(__FILE__), 'lis/messages/**/*.rb')].each { |f| require f }
11
9
 
12
- require 'lis/packetized_protocol.rb'
13
- require 'lis/application_protocol.rb'
10
+ require 'lis/transfer/astm_e1394.rb'
11
+ require 'lis/transfer/application_protocol.rb'
12
+
14
13
  require 'lis/worklist_manager_interface.rb'
15
14
  require 'lis/interface_server.rb'
16
15
 
data/lis.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
  $:.push File.expand_path("../lib", __FILE__)
3
- require 'lis'
3
+ require 'lis/version'
4
4
 
5
5
  Gem::Specification.new do |s|
6
6
  s.name = %q{lis}
@@ -20,9 +20,12 @@ Gem::Specification.new do |s|
20
20
  s.require_paths = ["lib"]
21
21
  s.summary = %q{LIS interface to Siemens Immulite 2000XPi or other similar analyzers}
22
22
 
23
+ s.add_dependency "packet_io", "~> 0.4.0.rc4"
24
+
23
25
  s.add_development_dependency("shoulda", ["~> 2.11.3"])
24
26
  s.add_development_dependency("mocha", ["~> 0.9.12"])
25
27
  s.add_development_dependency("yard", ["~> 0.7.1"])
26
28
  s.add_development_dependency("cucumber", ["~> 0.10.3"])
29
+ s.add_development_dependency("webmock")
27
30
  end
28
31
 
data/test/helper.rb CHANGED
@@ -2,7 +2,6 @@ require 'rubygems'
2
2
  require 'test/unit'
3
3
  require 'shoulda'
4
4
  require 'mocha'
5
- require 'lib/mock_server'
6
5
 
7
6
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
8
7
  $LOAD_PATH.unshift(File.dirname(__FILE__))
@@ -17,7 +17,7 @@ class TestPacketizedProtocol < Test::Unit::TestCase
17
17
  str = self.class.gsub_nonprintable(str)
18
18
  expected = self.class.gsub_nonprintable(expected)
19
19
 
20
- match = LIS::Transfer::PacketizedProtocol::RX.match(str)
20
+ match = LIS::Transfer::ASTM::E1394::RX.match(str)
21
21
  assert_not_nil match, expected
22
22
  assert_equal expected, match[0]
23
23
  end
@@ -39,7 +39,7 @@ class TestPacketizedProtocol < Test::Unit::TestCase
39
39
  setup do
40
40
  @sent = []
41
41
  @data = []
42
- @protocol = LIS::Transfer::PacketizedProtocol.new(nil, @sent)
42
+ @protocol = LIS::Transfer::ASTM::E1394.new(nil, @sent)
43
43
  @protocol.on_data do |*d|
44
44
  @data << d
45
45
  end
@@ -95,4 +95,4 @@ class TestPacketizedProtocol < Test::Unit::TestCase
95
95
  end
96
96
  end
97
97
 
98
- end
98
+ end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 2
8
- - 1
9
- version: 0.2.1
8
+ - 2
9
+ version: 0.2.2
10
10
  platform: ruby
11
11
  authors:
12
12
  - Levin Alexander
@@ -14,13 +14,29 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2011-05-30 00:00:00 +02:00
17
+ date: 2011-06-01 00:00:00 +02:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
- name: shoulda
21
+ name: packet_io
22
22
  prerelease: false
23
23
  requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ - 4
31
+ - 0
32
+ - rc4
33
+ version: 0.4.0.rc4
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: shoulda
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
24
40
  none: false
25
41
  requirements:
26
42
  - - ~>
@@ -31,11 +47,11 @@ dependencies:
31
47
  - 3
32
48
  version: 2.11.3
33
49
  type: :development
34
- version_requirements: *id001
50
+ version_requirements: *id002
35
51
  - !ruby/object:Gem::Dependency
36
52
  name: mocha
37
53
  prerelease: false
38
- requirement: &id002 !ruby/object:Gem::Requirement
54
+ requirement: &id003 !ruby/object:Gem::Requirement
39
55
  none: false
40
56
  requirements:
41
57
  - - ~>
@@ -46,11 +62,11 @@ dependencies:
46
62
  - 12
47
63
  version: 0.9.12
48
64
  type: :development
49
- version_requirements: *id002
65
+ version_requirements: *id003
50
66
  - !ruby/object:Gem::Dependency
51
67
  name: yard
52
68
  prerelease: false
53
- requirement: &id003 !ruby/object:Gem::Requirement
69
+ requirement: &id004 !ruby/object:Gem::Requirement
54
70
  none: false
55
71
  requirements:
56
72
  - - ~>
@@ -61,11 +77,11 @@ dependencies:
61
77
  - 1
62
78
  version: 0.7.1
63
79
  type: :development
64
- version_requirements: *id003
80
+ version_requirements: *id004
65
81
  - !ruby/object:Gem::Dependency
66
82
  name: cucumber
67
83
  prerelease: false
68
- requirement: &id004 !ruby/object:Gem::Requirement
84
+ requirement: &id005 !ruby/object:Gem::Requirement
69
85
  none: false
70
86
  requirements:
71
87
  - - ~>
@@ -76,7 +92,20 @@ dependencies:
76
92
  - 3
77
93
  version: 0.10.3
78
94
  type: :development
79
- version_requirements: *id004
95
+ version_requirements: *id005
96
+ - !ruby/object:Gem::Dependency
97
+ name: webmock
98
+ prerelease: false
99
+ requirement: &id006 !ruby/object:Gem::Requirement
100
+ none: false
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ segments:
105
+ - 0
106
+ version: "0"
107
+ type: :development
108
+ version_requirements: *id006
80
109
  description: ""
81
110
  email: mail@levinalex.net
82
111
  executables:
@@ -86,14 +115,12 @@ extensions: []
86
115
  extra_rdoc_files: []
87
116
 
88
117
  files:
89
- - .document
90
118
  - .gitignore
91
119
  - .yardopts
92
120
  - Gemfile
93
121
  - LICENSE
94
122
  - README.markdown
95
123
  - Rakefile
96
- - VERSION
97
124
  - bin/lis2http
98
125
  - features/communication basics.feature
99
126
  - features/lis.feature
@@ -103,7 +130,6 @@ files:
103
130
  - lib/lis/application_protocol.rb
104
131
  - lib/lis/commands/application.rb
105
132
  - lib/lis/interface_server.rb
106
- - lib/lis/io_listener.rb
107
133
  - lib/lis/messages.rb
108
134
  - lib/lis/messages/comment.rb
109
135
  - lib/lis/messages/header.rb
@@ -113,6 +139,9 @@ files:
113
139
  - lib/lis/messages/result.rb
114
140
  - lib/lis/messages/terminator.rb
115
141
  - lib/lis/packetized_protocol.rb
142
+ - lib/lis/transfer/application_protocol.rb
143
+ - lib/lis/transfer/astm_e1394.rb
144
+ - lib/lis/version.rb
116
145
  - lib/lis/worklist_manager_interface.rb
117
146
  - lis.gemspec
118
147
  - test/helper.rb
@@ -122,9 +151,8 @@ files:
122
151
  - test/messages/test_patients.rb
123
152
  - test/messages/test_terminator.rb
124
153
  - test/test_application_protocol.rb
125
- - test/test_io_listener.rb
154
+ - test/test_astm_e1394.rb
126
155
  - test/test_messages.rb
127
- - test/test_packetized_protocol.rb
128
156
  has_rdoc: true
129
157
  homepage: http://github.com/levinalex/lis
130
158
  licenses: []
@@ -169,6 +197,5 @@ test_files:
169
197
  - test/messages/test_patients.rb
170
198
  - test/messages/test_terminator.rb
171
199
  - test/test_application_protocol.rb
172
- - test/test_io_listener.rb
200
+ - test/test_astm_e1394.rb
173
201
  - test/test_messages.rb
174
- - test/test_packetized_protocol.rb
data/.document DELETED
@@ -1,5 +0,0 @@
1
- README.rdoc
2
- lib/**/*.rb
3
- bin/*
4
- features/**/*.feature
5
- LICENSE
data/VERSION DELETED
@@ -1 +0,0 @@
1
- 0.1.0