levinalex-LiaisonLabor 0.1.7
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.
- data/History.txt +10 -0
- data/Manifest.txt +13 -0
- data/README.txt +49 -0
- data/Rakefile +29 -0
- data/bin/liaison_server +9 -0
- data/liaison_labor.gemspec +36 -0
- data/lib/liaison_labor.rb +8 -0
- data/lib/liaison_labor/interface.rb +236 -0
- data/lib/liaison_labor/packets.rb +302 -0
- data/lib/liaison_server.rb +169 -0
- data/spec/liaison_interface_spec.rb +43 -0
- data/spec/liaison_packet_spec.rb +234 -0
- data/spec/mock/mock_liaison.rb +43 -0
- metadata +78 -0
data/History.txt
ADDED
data/Manifest.txt
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
History.txt
|
|
2
|
+
Manifest.txt
|
|
3
|
+
README.txt
|
|
4
|
+
Rakefile
|
|
5
|
+
bin/liaison_server
|
|
6
|
+
liaison_labor.gemspec
|
|
7
|
+
lib/liaison_labor.rb
|
|
8
|
+
lib/liaison_labor/interface.rb
|
|
9
|
+
lib/liaison_labor/packets.rb
|
|
10
|
+
lib/liaison_server.rb
|
|
11
|
+
spec/liaison_interface_spec.rb
|
|
12
|
+
spec/liaison_packet_spec.rb
|
|
13
|
+
spec/mock/mock_liaison.rb
|
data/README.txt
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
LiaisonLabor
|
|
2
|
+
by Levin Alexander
|
|
3
|
+
http://levinalex.net/src/liaison_labor
|
|
4
|
+
|
|
5
|
+
== DESCRIPTION:
|
|
6
|
+
|
|
7
|
+
An interface to the LIAISON® analyser by DiaSorin
|
|
8
|
+
<http://www.diasorin.com/en/productsandsystems/liaison>
|
|
9
|
+
|
|
10
|
+
== FEATURES/PROBLEMS:
|
|
11
|
+
|
|
12
|
+
* not yet written
|
|
13
|
+
|
|
14
|
+
== SYNOPSIS:
|
|
15
|
+
|
|
16
|
+
not yet written
|
|
17
|
+
|
|
18
|
+
== REQUIREMENTS:
|
|
19
|
+
|
|
20
|
+
* not yet written
|
|
21
|
+
|
|
22
|
+
== INSTALL:
|
|
23
|
+
|
|
24
|
+
* not yet written
|
|
25
|
+
|
|
26
|
+
== LICENSE:
|
|
27
|
+
|
|
28
|
+
(The MIT License)
|
|
29
|
+
|
|
30
|
+
Copyright (c) 2007-2008 Levin Alexander
|
|
31
|
+
|
|
32
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
33
|
+
a copy of this software and associated documentation files (the
|
|
34
|
+
'Software'), to deal in the Software without restriction, including
|
|
35
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
36
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
37
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
38
|
+
the following conditions:
|
|
39
|
+
|
|
40
|
+
The above copyright notice and this permission notice shall be
|
|
41
|
+
included in all copies or substantial portions of the Software.
|
|
42
|
+
|
|
43
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
|
44
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
45
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
46
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
47
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
48
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
49
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'hoe'
|
|
3
|
+
require 'spec/rake/spectask'
|
|
4
|
+
|
|
5
|
+
require './lib/liaison_labor.rb'
|
|
6
|
+
|
|
7
|
+
Hoe.new('LiaisonLabor', LiaisonLabor::VERSION) do |p|
|
|
8
|
+
p.rubyforge_name = 'liaison_labor'
|
|
9
|
+
p.summary = 'interfaces Liaison device to worklist_manager'
|
|
10
|
+
|
|
11
|
+
p.url = 'http://levinalex.net/src/liaison'
|
|
12
|
+
p.developer('Levin Alexander', 'mail@levinalex.net')
|
|
13
|
+
p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
|
|
14
|
+
|
|
15
|
+
# p.extra_deps ['levinalex-serial_interface']
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
Rake.application.instance_eval { @tasks["test"] = nil }
|
|
19
|
+
|
|
20
|
+
Spec::Rake::SpecTask.new do |t|
|
|
21
|
+
t.warning = true
|
|
22
|
+
t.spec_opts = %w(-c -f specdoc)
|
|
23
|
+
end
|
|
24
|
+
task :test => :spec
|
|
25
|
+
|
|
26
|
+
task :cultivate do
|
|
27
|
+
system "touch Manifest.txt; rake check_manifest | grep -v \"(in \" | patch"
|
|
28
|
+
system "rake debug_gem | grep -v \"(in \" > `basename \\`pwd\\``.gemspec"
|
|
29
|
+
end
|
data/bin/liaison_server
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
Gem::Specification.new do |s|
|
|
4
|
+
s.name = %q{LiaisonLabor}
|
|
5
|
+
s.version = "0.1.7"
|
|
6
|
+
|
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
|
8
|
+
s.authors = ["Levin Alexander"]
|
|
9
|
+
s.date = %q{2009-06-11}
|
|
10
|
+
s.default_executable = %q{liaison_server}
|
|
11
|
+
s.description = %q{An interface to the LIAISON® analyser by DiaSorin <http://www.diasorin.com/en/productsandsystems/liaison>}
|
|
12
|
+
s.email = ["mail@levinalex.net"]
|
|
13
|
+
s.executables = ["liaison_server"]
|
|
14
|
+
s.extra_rdoc_files = ["History.txt", "Manifest.txt", "README.txt"]
|
|
15
|
+
s.files = ["History.txt", "Manifest.txt", "README.txt", "Rakefile", "bin/liaison_server", "liaison_labor.gemspec", "lib/liaison_labor.rb", "lib/liaison_labor/interface.rb", "lib/liaison_labor/packets.rb", "lib/liaison_server.rb", "spec/liaison_interface_spec.rb", "spec/liaison_packet_spec.rb", "spec/mock/mock_liaison.rb"]
|
|
16
|
+
s.has_rdoc = true
|
|
17
|
+
s.homepage = %q{http://levinalex.net/src/liaison}
|
|
18
|
+
s.rdoc_options = ["--main", "README.txt"]
|
|
19
|
+
s.require_paths = ["lib"]
|
|
20
|
+
s.rubyforge_project = %q{liaison_labor}
|
|
21
|
+
s.rubygems_version = %q{1.3.1}
|
|
22
|
+
s.summary = %q{interfaces Liaison device to worklist_manager}
|
|
23
|
+
|
|
24
|
+
if s.respond_to? :specification_version then
|
|
25
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
|
26
|
+
s.specification_version = 2
|
|
27
|
+
|
|
28
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
|
29
|
+
s.add_development_dependency(%q<hoe>, [">= 1.12.1"])
|
|
30
|
+
else
|
|
31
|
+
s.add_dependency(%q<hoe>, [">= 1.12.1"])
|
|
32
|
+
end
|
|
33
|
+
else
|
|
34
|
+
s.add_dependency(%q<hoe>, [">= 1.12.1"])
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'serial_interface'
|
|
3
|
+
|
|
4
|
+
module Diasorin
|
|
5
|
+
module Liaison
|
|
6
|
+
|
|
7
|
+
class Interface < PacketIO
|
|
8
|
+
|
|
9
|
+
def initialize(read, write=read, options = {}, &blk)
|
|
10
|
+
super(LiaisonProtocol, read, write, options)
|
|
11
|
+
|
|
12
|
+
# define default handlers for import/export of patients
|
|
13
|
+
# and results
|
|
14
|
+
#
|
|
15
|
+
@on_order_request = lambda { nil }
|
|
16
|
+
|
|
17
|
+
# is called whenever a result is sent
|
|
18
|
+
#
|
|
19
|
+
@on_result = lambda { |patient, order, result| }
|
|
20
|
+
|
|
21
|
+
yield self if block_given?
|
|
22
|
+
|
|
23
|
+
add_sender( {
|
|
24
|
+
:message_header => Packets::MessageHeaderRecord,
|
|
25
|
+
:patient => Packets::PatientInformationRecord,
|
|
26
|
+
:order => Packets::TestOrderRecord,
|
|
27
|
+
:terminator => Packets::MessageTerminatorRecord
|
|
28
|
+
} )
|
|
29
|
+
|
|
30
|
+
add_receiver :message_header => Packets::MessageHeaderRecord do |p|
|
|
31
|
+
# delete the list of patients
|
|
32
|
+
@patient_information ||= {}
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
add_receiver :patient_record => Packets::PatientInformationRecord do |p|
|
|
36
|
+
@last_order_packet = nil
|
|
37
|
+
@last_result_record = nil
|
|
38
|
+
|
|
39
|
+
@last_patient_packet = p
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
add_receiver :comment => Packets::CommentRecord do |p|
|
|
43
|
+
@on_result.call(@last_patient_packet, @last_order_packet, @last_result_record, p)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
add_receiver :order => Packets::TestOrderRecord do |p|
|
|
47
|
+
@last_result_record = nil
|
|
48
|
+
|
|
49
|
+
@last_order_packet = p
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
add_receiver :test_result => Packets::ResultRecord do |p|
|
|
53
|
+
@last_result_record = p
|
|
54
|
+
@on_result.call(@last_patient_packet, @last_order_packet, @last_result_record)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
add_receiver :request_information => Packets::RequestInformationSegment do |p|
|
|
59
|
+
puts "RequestInformation: #{p.inspect}"
|
|
60
|
+
|
|
61
|
+
@patient_information ||= {}
|
|
62
|
+
requests = @on_order_request.call(p.starting_range)
|
|
63
|
+
@patient_information[p.sequence_number] = requests if requests
|
|
64
|
+
|
|
65
|
+
@mode = :request_information
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
add_receiver :ack => Packets::Acknowledge do |p|
|
|
69
|
+
if @mode == :request_information
|
|
70
|
+
@mode = @transmitting
|
|
71
|
+
send_packet(:message_header, "HOST", "Liaison")
|
|
72
|
+
transmit_requests
|
|
73
|
+
else
|
|
74
|
+
# keep on sending
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
add_receiver :nack => Packets::NotAcknowledge do |p|
|
|
79
|
+
puts "Nack!"
|
|
80
|
+
raise "NACK received"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
add_receiver :message_terminator => Packets::MessageTerminatorRecord do |p|
|
|
84
|
+
puts "got MessageTerminatorRecord"
|
|
85
|
+
if @mode == :request_information
|
|
86
|
+
puts "should now send information"
|
|
87
|
+
# initiate transfer (ENQ)
|
|
88
|
+
send_packet("\x05", :raw => true)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
run
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def on_order_request(&blk)
|
|
96
|
+
@on_order_request = blk
|
|
97
|
+
end
|
|
98
|
+
def on_result(&blk)
|
|
99
|
+
@on_result = blk
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def transmit_requests
|
|
103
|
+
@patient_information.each do |sequence_nr, data|
|
|
104
|
+
p "sequence: #{sequence_nr.inspect}"
|
|
105
|
+
p "data: #{data.inspect}"
|
|
106
|
+
send_packet(:patient, sequence_nr, data["patient"]["number"], data["patient"]["last_name"], data["patient"]["first_name"])
|
|
107
|
+
data["types"].each do |request|
|
|
108
|
+
send_packet(:order, sequence_nr, data["id"], request)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
send_packet(:terminator, "1")
|
|
112
|
+
# EOT
|
|
113
|
+
send_packet("\x04", :raw => true)
|
|
114
|
+
|
|
115
|
+
@mode = :neutral
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class LiaisonProtocol
|
|
122
|
+
STX = 0x02 # STX
|
|
123
|
+
ETX = 0x03 # ETX
|
|
124
|
+
NACK = 0x15 # NACK
|
|
125
|
+
ACK = 0x06 # ACK
|
|
126
|
+
ENQ = 0x05 # ENQ
|
|
127
|
+
CR = 0x0d
|
|
128
|
+
LF = 0x0a
|
|
129
|
+
EOT = 0x04
|
|
130
|
+
|
|
131
|
+
def initialize(send_callback, receive_callback, options = {})
|
|
132
|
+
@send_callback, @receive_callback = send_callback, receive_callback
|
|
133
|
+
@packet_buffer = ""
|
|
134
|
+
@send_buffer = []
|
|
135
|
+
@checksum = 0
|
|
136
|
+
@frame_number = 1
|
|
137
|
+
@state = :neutral
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def self.checksum_valid?(string, expected)
|
|
141
|
+
sum = string.to_enum(:each_byte).inject(ETX) { |s,v| s+v } % 0x100
|
|
142
|
+
if sum == expected
|
|
143
|
+
return true
|
|
144
|
+
else
|
|
145
|
+
raise ::SerialProtocol::ChecksumMismatch, "expected #{expected}, got #{sum}"
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def get_packet(str)
|
|
150
|
+
@receive_callback.call(str)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def add_char_to_packet(char)
|
|
154
|
+
|
|
155
|
+
case @state
|
|
156
|
+
when :neutral
|
|
157
|
+
if char == ENQ # establishment
|
|
158
|
+
send_packet(ACK.chr, :raw => true)
|
|
159
|
+
@state = :transfer_begin
|
|
160
|
+
elsif char == ACK
|
|
161
|
+
get_packet(ACK.chr)
|
|
162
|
+
elsif char == NACK
|
|
163
|
+
get_packet(NACK.chr)
|
|
164
|
+
else
|
|
165
|
+
# receive and ignore partial data
|
|
166
|
+
end
|
|
167
|
+
when :transfer_begin
|
|
168
|
+
if char == STX
|
|
169
|
+
# begin to receive a packet
|
|
170
|
+
@packet_buffer = ""
|
|
171
|
+
@state = :packet_data
|
|
172
|
+
elsif char == EOT
|
|
173
|
+
@state = :neutral
|
|
174
|
+
else
|
|
175
|
+
raise "unexpected data after ENQ, expected STX, got #{char.to_i}"
|
|
176
|
+
end
|
|
177
|
+
when :packet_data
|
|
178
|
+
if char == ETX
|
|
179
|
+
@state = :packet_checksum_1
|
|
180
|
+
else
|
|
181
|
+
@packet_buffer << char
|
|
182
|
+
end
|
|
183
|
+
when :packet_checksum_1
|
|
184
|
+
@checksum_str = "" << char
|
|
185
|
+
@state = :packet_checksum_2
|
|
186
|
+
when :packet_checksum_2
|
|
187
|
+
@checksum_str << char
|
|
188
|
+
@state = :packet_cr
|
|
189
|
+
when :packet_cr
|
|
190
|
+
if char == CR
|
|
191
|
+
@state = :packet_lf
|
|
192
|
+
else
|
|
193
|
+
raise "unexpected data after checksum, expected CR, got #{char.to_i}"
|
|
194
|
+
end
|
|
195
|
+
when :packet_lf
|
|
196
|
+
if char == LF
|
|
197
|
+
if self.class.checksum_valid?(@packet_buffer, @checksum_str.to_i(16))
|
|
198
|
+
send_packet(ACK.chr, :raw => true)
|
|
199
|
+
# cut the sequence ID
|
|
200
|
+
get_packet(@packet_buffer[1..-1])
|
|
201
|
+
else
|
|
202
|
+
send_packet(NACK.chr, :raw => true)
|
|
203
|
+
end
|
|
204
|
+
@state = :transfer_begin
|
|
205
|
+
else
|
|
206
|
+
raise "unexpected data after checksum, expected LF, got #{char.to_i}"
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def checksum(data)
|
|
212
|
+
val = data.to_enum(:each_byte).inject(0) { |sum,b| sum+b } % 0x100
|
|
213
|
+
val.to_s(16).rjust(2,'0')
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def send_packet(data, options = {})
|
|
217
|
+
warn "--> #{data.inspect}"
|
|
218
|
+
if options[:raw]
|
|
219
|
+
if data == ENQ.chr
|
|
220
|
+
@frame_number = 1 # reset frame number on new transmission
|
|
221
|
+
end
|
|
222
|
+
@send_callback.call(data)
|
|
223
|
+
else
|
|
224
|
+
@frame_number ||= 1
|
|
225
|
+
|
|
226
|
+
data = "" << @frame_number.to_s << data
|
|
227
|
+
packet_str = "" << "\x02" << data << "\r" << "\x03" << checksum("" << data << "\r\x03") << "\r\n"
|
|
228
|
+
p "--~> #{packet_str.inspect}"
|
|
229
|
+
@send_callback.call(packet_str)
|
|
230
|
+
|
|
231
|
+
@frame_number = (@frame_number + 1) % 8
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
require 'date'
|
|
2
|
+
require 'serial_interface'
|
|
3
|
+
|
|
4
|
+
module Diasorin
|
|
5
|
+
module Liaison
|
|
6
|
+
module Packets
|
|
7
|
+
class LiaisonPacket < SerialPacket
|
|
8
|
+
FieldDelimiter = '|'
|
|
9
|
+
RepeatDelimiter = '\\'
|
|
10
|
+
ComponentDelimiter = '^'
|
|
11
|
+
EscapeDelimiter = "&"
|
|
12
|
+
|
|
13
|
+
header_format "A"
|
|
14
|
+
|
|
15
|
+
def sequence_number=(val)
|
|
16
|
+
@sequence_number = val.to_i
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def to_str
|
|
20
|
+
to_s
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class Acknowledge < LiaisonPacket
|
|
25
|
+
def self.matches?(str)
|
|
26
|
+
str == "\x06" ? true : false
|
|
27
|
+
end
|
|
28
|
+
def to_s; "\x06"; end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
class NotAcknowledge < LiaisonPacket
|
|
32
|
+
def self.matches?(str)
|
|
33
|
+
str == "\x15" ? true : false
|
|
34
|
+
end
|
|
35
|
+
def to_s; "\x15"; end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
class MessageHeaderRecord < LiaisonPacket
|
|
39
|
+
header_format "A"
|
|
40
|
+
header_filter "H"
|
|
41
|
+
header ["H"]
|
|
42
|
+
|
|
43
|
+
attr_reader :sender_id
|
|
44
|
+
attr_reader :receiver_id
|
|
45
|
+
attr_reader :timestamp
|
|
46
|
+
|
|
47
|
+
def initialize(sender_id, receiver_id)
|
|
48
|
+
@sender_id = sender_id
|
|
49
|
+
@receiver_id = receiver_id
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def to_s
|
|
53
|
+
arr = []
|
|
54
|
+
arr[1] = "H"
|
|
55
|
+
arr[2] = "\\^&"
|
|
56
|
+
arr[5] = @sender_id
|
|
57
|
+
arr[10] = @receiver_id
|
|
58
|
+
arr.shift
|
|
59
|
+
|
|
60
|
+
arr.join("|")
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def initialize_from_packet(str)
|
|
64
|
+
records = str.split(FieldDelimiter)
|
|
65
|
+
|
|
66
|
+
@sender_id = records[4]
|
|
67
|
+
@receiver_id = records[9]
|
|
68
|
+
@timestamp = DateTime.new(*records[13].scan(/(....)(..)(..)(..)(..)(..)/)[0].map {|x| x.to_i })
|
|
69
|
+
|
|
70
|
+
@version = records[12]
|
|
71
|
+
raise "Version is not supported" unless @version == '1'
|
|
72
|
+
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
class MessageTerminatorRecord < LiaisonPacket
|
|
77
|
+
header_format "A"
|
|
78
|
+
header_filter "L"
|
|
79
|
+
header ["L"]
|
|
80
|
+
|
|
81
|
+
attr_reader :sequence_number
|
|
82
|
+
attr_reader :termination_code
|
|
83
|
+
|
|
84
|
+
def initialize_from_packet(str)
|
|
85
|
+
records = str.split(FieldDelimiter)
|
|
86
|
+
_, self.sequence_number, @termination_code = records
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def initialize(sequence_number, termination_code = "N")
|
|
90
|
+
@sequence_number = sequence_number
|
|
91
|
+
@termination_code = termination_code
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def to_s
|
|
95
|
+
arr = []
|
|
96
|
+
arr[1] = "L"
|
|
97
|
+
arr[2] = @sequence_number
|
|
98
|
+
arr[3] = @termination_code
|
|
99
|
+
arr.shift
|
|
100
|
+
|
|
101
|
+
arr.join("|")
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
class PatientInformationRecord < LiaisonPacket
|
|
106
|
+
header_format "A"
|
|
107
|
+
header_filter "P"
|
|
108
|
+
header ["P"]
|
|
109
|
+
|
|
110
|
+
attr_reader :sequence_number
|
|
111
|
+
attr_reader :patient_id
|
|
112
|
+
attr_reader :patient_last_name
|
|
113
|
+
attr_reader :patient_first_name
|
|
114
|
+
attr_reader :birthdate
|
|
115
|
+
attr_reader :sex
|
|
116
|
+
attr_reader :physician
|
|
117
|
+
|
|
118
|
+
def patient_name=(val)
|
|
119
|
+
@patient_last_name, @patient_first_name = val.split(ComponentDelimiter)
|
|
120
|
+
end
|
|
121
|
+
def birthdate=(val)
|
|
122
|
+
@birthdate = case val
|
|
123
|
+
when String
|
|
124
|
+
DateTime.new(*val.scan(/(....)(..)(..)/)[0].map {|x| x.to_i }) rescue nil
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def initialize(sequence_nr, patient_id, lastname = "", firstname = "")
|
|
129
|
+
@sequence_number = sequence_nr.to_i
|
|
130
|
+
@patient_id = patient_id
|
|
131
|
+
@patient_last_name, @patient_first_name = lastname, firstname
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def to_s
|
|
135
|
+
arr = []
|
|
136
|
+
arr[1] = "P"
|
|
137
|
+
arr[2] = @sequence_number
|
|
138
|
+
arr[4] = @patient_id.to_s
|
|
139
|
+
arr[6] = [@patient_last_name, @patient_first_name].join("^")
|
|
140
|
+
arr.shift
|
|
141
|
+
|
|
142
|
+
arr.join("|")
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def initialize_from_packet(str)
|
|
146
|
+
records = str.split(FieldDelimiter)
|
|
147
|
+
|
|
148
|
+
_1,
|
|
149
|
+
self.sequence_number,
|
|
150
|
+
_3,
|
|
151
|
+
@patient_id,
|
|
152
|
+
_5,
|
|
153
|
+
self.patient_name,
|
|
154
|
+
_7,
|
|
155
|
+
self.birthdate,
|
|
156
|
+
@sex,
|
|
157
|
+
_10,
|
|
158
|
+
_11,
|
|
159
|
+
_12,
|
|
160
|
+
_13,
|
|
161
|
+
@physician,
|
|
162
|
+
_rest = records
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
class RequestInformationSegment < LiaisonPacket
|
|
167
|
+
header_format "A"
|
|
168
|
+
header_filter "Q"
|
|
169
|
+
header ["Q"]
|
|
170
|
+
|
|
171
|
+
attr_reader :sequence_number
|
|
172
|
+
attr_reader :starting_range
|
|
173
|
+
|
|
174
|
+
def initialize_from_packet(str)
|
|
175
|
+
records = str.split(FieldDelimiter)
|
|
176
|
+
|
|
177
|
+
_1, self.sequence_number, @starting_range, _rest = records
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
class TestOrderRecord < LiaisonPacket
|
|
182
|
+
header_format "A"
|
|
183
|
+
header_filter "O"
|
|
184
|
+
header ["O"]
|
|
185
|
+
|
|
186
|
+
attr_reader :sequence_number
|
|
187
|
+
attr_reader :specimen_id
|
|
188
|
+
attr_reader :test_id
|
|
189
|
+
attr_reader :dilution
|
|
190
|
+
attr_reader :priority
|
|
191
|
+
attr_reader :timestamp
|
|
192
|
+
attr_reader :record_type
|
|
193
|
+
|
|
194
|
+
def sequence_number=(val)
|
|
195
|
+
@sequence_number = val.to_i
|
|
196
|
+
end
|
|
197
|
+
def test_order=(data)
|
|
198
|
+
_1, _2, _3, @test_id, @dilution = data.split(ComponentDelimiter)
|
|
199
|
+
end
|
|
200
|
+
def timestamp=(val)
|
|
201
|
+
@timestamp = case val
|
|
202
|
+
when String
|
|
203
|
+
DateTime.new(*val.scan(/(....)(..)(..)/)[0].map {|x| x.to_i }) rescue nil
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
def initialize_from_packet(str)
|
|
207
|
+
records = str.split(FieldDelimiter)
|
|
208
|
+
|
|
209
|
+
_1,
|
|
210
|
+
self.sequence_number,
|
|
211
|
+
@specimen_id,
|
|
212
|
+
_4,
|
|
213
|
+
self.test_order,
|
|
214
|
+
@priority,
|
|
215
|
+
self.timestamp, _, _, _10, _, _, _, _, _, _, _, _, _, _20, _, _, _, _, _, @record_type,
|
|
216
|
+
_rest = records
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def initialize(sequence_number, specimen_id, test_id)
|
|
220
|
+
@sequence_number = sequence_number
|
|
221
|
+
@specimen_id = specimen_id
|
|
222
|
+
@test_id = test_id
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def to_s
|
|
227
|
+
arr = []
|
|
228
|
+
arr[1] = "O"
|
|
229
|
+
arr[2] = @sequence_number
|
|
230
|
+
arr[3] = @specimen_id
|
|
231
|
+
arr[5] = [nil,nil,nil,@test_id].join("^")
|
|
232
|
+
arr.shift
|
|
233
|
+
|
|
234
|
+
arr.join("|")
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
class ResultRecord < LiaisonPacket
|
|
239
|
+
header_format "A"
|
|
240
|
+
header_filter "R"
|
|
241
|
+
header ["R"]
|
|
242
|
+
|
|
243
|
+
attr_reader :sequence_number
|
|
244
|
+
attr_reader :test_id
|
|
245
|
+
attr_reader :value
|
|
246
|
+
attr_reader :unit
|
|
247
|
+
attr_reader :abnormal_flags
|
|
248
|
+
attr_reader :result_status
|
|
249
|
+
attr_reader :timestamp
|
|
250
|
+
attr_reader :instrument
|
|
251
|
+
|
|
252
|
+
def test_result=(data)
|
|
253
|
+
_1, _2, _3, @test_id = data.split(ComponentDelimiter)
|
|
254
|
+
end
|
|
255
|
+
def timestamp=(val)
|
|
256
|
+
@timestamp = case val
|
|
257
|
+
when String
|
|
258
|
+
DateTime.new(*val.scan(/(....)(..)(..)(..)(..)(..)/)[0].map {|x| x.to_i }) rescue nil
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def initialize_from_packet(str)
|
|
263
|
+
records = str.split(FieldDelimiter)
|
|
264
|
+
|
|
265
|
+
_1,
|
|
266
|
+
self.sequence_number,
|
|
267
|
+
self.test_result,
|
|
268
|
+
@value,
|
|
269
|
+
@unit,
|
|
270
|
+
_6,
|
|
271
|
+
@abnormal_flags,
|
|
272
|
+
_7,
|
|
273
|
+
@result_status,
|
|
274
|
+
_10, _11, _12,
|
|
275
|
+
self.timestamp,
|
|
276
|
+
@instrument,
|
|
277
|
+
rest = records
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
class CommentRecord < LiaisonPacket
|
|
282
|
+
header_format "A"
|
|
283
|
+
header_filter "C"
|
|
284
|
+
header ["C"]
|
|
285
|
+
|
|
286
|
+
attr_reader :comment
|
|
287
|
+
|
|
288
|
+
def initialize_from_packet(str)
|
|
289
|
+
records = str.split(FieldDelimiter)
|
|
290
|
+
|
|
291
|
+
_1,
|
|
292
|
+
self.sequence_number,
|
|
293
|
+
_3,
|
|
294
|
+
@comment,
|
|
295
|
+
rest = records
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
end
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'optparse'
|
|
4
|
+
require 'net/http'
|
|
5
|
+
require 'yaml'
|
|
6
|
+
|
|
7
|
+
require 'rubygems'
|
|
8
|
+
|
|
9
|
+
require File.join(File.dirname(__FILE__),'liaison_labor.rb')
|
|
10
|
+
|
|
11
|
+
class Hash
|
|
12
|
+
def symbolize_keys
|
|
13
|
+
inject({}) do |options, (key, value)|
|
|
14
|
+
options[(key.to_sym rescue key) || key] = value
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def stringify_keys
|
|
19
|
+
inject({}) do |options, (key, value)|
|
|
20
|
+
options[key.to_s] = value
|
|
21
|
+
options
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
module Diasorin
|
|
29
|
+
ConfigFilename = ".liaison_server"
|
|
30
|
+
ConfigFile = File.join(ENV['HOME'] || ENV['APPDATA'], ConfigFilename)
|
|
31
|
+
|
|
32
|
+
# defaults are used when they are not overwritten in a config file
|
|
33
|
+
# or with command line options
|
|
34
|
+
#
|
|
35
|
+
DefaultConfig = {
|
|
36
|
+
:serial_port => "/dev/ttyUSB0",
|
|
37
|
+
:baudrate => 9600,
|
|
38
|
+
:uri => "http://localhost/liaison/"
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
class LiaisonServer
|
|
42
|
+
|
|
43
|
+
# load configuration from configfile, merge with
|
|
44
|
+
# default options
|
|
45
|
+
#
|
|
46
|
+
def load_configuration
|
|
47
|
+
# try to read configuration from file
|
|
48
|
+
@options_from_file = YAML.load_file(ConfigFile) || {} rescue {}
|
|
49
|
+
@options_from_file = @options_from_file.symbolize_keys
|
|
50
|
+
|
|
51
|
+
# if an option is not given on the command line
|
|
52
|
+
# it is taken from the config file, or the default is used
|
|
53
|
+
@options = Hash.new() { |h,k| @options_from_file[k] || DefaultConfig[k] }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# parse command line options
|
|
57
|
+
#
|
|
58
|
+
def initialize
|
|
59
|
+
load_configuration
|
|
60
|
+
|
|
61
|
+
@opts = OptionParser.new do |opts|
|
|
62
|
+
|
|
63
|
+
opts.separator ""
|
|
64
|
+
opts.separator "liaison_labor is an interface between the Liaison device connected via "
|
|
65
|
+
opts.separator "serial port, and a worklist_manager HTTP-server"
|
|
66
|
+
opts.separator ""
|
|
67
|
+
opts.separator "configuration can be given in '#{ConfigFile}' "
|
|
68
|
+
opts.separator ""
|
|
69
|
+
|
|
70
|
+
opts.on "-V","--version","Display version and exit" do
|
|
71
|
+
puts "#{self.class} #{::LiaisonLabor::VERSION}"
|
|
72
|
+
exit
|
|
73
|
+
end
|
|
74
|
+
opts.on "-s", "--serial-port DEVICE", "serial port, default is /dev/ttyUSB0" do |arg|
|
|
75
|
+
@options[:serial_port] = arg
|
|
76
|
+
end
|
|
77
|
+
opts.on "--baudrate BAUDRATE", "baudrate of the serial connection, default is 9600" do |arg|
|
|
78
|
+
@options[:baudrate] = arg.to_i
|
|
79
|
+
end
|
|
80
|
+
opts.on "-u", "--uri URI", "URI of the HTTP-Endpoint where data is read or posted" do |arg|
|
|
81
|
+
@options[:endpoint] = arg
|
|
82
|
+
end
|
|
83
|
+
opts.on_tail "-p", "--print-config", "Print the current configuration",
|
|
84
|
+
"in a format that can be used as a configuration file" do
|
|
85
|
+
puts @options_from_file.merge(@options).stringify_keys.to_yaml
|
|
86
|
+
exit
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# open the serial port for reading and writing
|
|
93
|
+
#
|
|
94
|
+
# make sure that output buffering is disabled so that data
|
|
95
|
+
# is sent immediately
|
|
96
|
+
#
|
|
97
|
+
def open_port(portfile, speed)
|
|
98
|
+
system("stty -echo raw ospeed #{speed} ispeed #{speed} < #{portfile}")
|
|
99
|
+
port = File.open(portfile, "w+")
|
|
100
|
+
port.sync = true
|
|
101
|
+
port
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def communicate!
|
|
105
|
+
port = open_port(@options[:serial_port], @options[:baudrate])
|
|
106
|
+
|
|
107
|
+
client = Liaison::Interface.new(port) do |liaison|
|
|
108
|
+
|
|
109
|
+
# yields the barcode of a sample
|
|
110
|
+
#
|
|
111
|
+
# expects a hash consisting of patient information and a list
|
|
112
|
+
# of requests for this patient
|
|
113
|
+
#
|
|
114
|
+
liaison.on_order_request do |barcode|
|
|
115
|
+
begin
|
|
116
|
+
data = YAML.load(::Net::HTTP.get( URI.join(@options[:endpoint],"find_requests/",barcode) ))
|
|
117
|
+
rescue Exception => e
|
|
118
|
+
puts e
|
|
119
|
+
puts e.backtrace
|
|
120
|
+
data = nil
|
|
121
|
+
end
|
|
122
|
+
p data
|
|
123
|
+
data
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
liaison.on_result do |patient, order, result, comment|
|
|
127
|
+
if comment
|
|
128
|
+
comment_str = comment.comment
|
|
129
|
+
else
|
|
130
|
+
comment_str = nil
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
barcode = order.specimen_id
|
|
134
|
+
data = {
|
|
135
|
+
"test_name" => order.test_id,
|
|
136
|
+
"value" => result.value,
|
|
137
|
+
"unit" => result.unit,
|
|
138
|
+
"status" => result.result_status,
|
|
139
|
+
"flags" => result.abnormal_flags,
|
|
140
|
+
"comment" => comment_str,
|
|
141
|
+
"result_timestamp" => result.timestamp
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
::Net::HTTP.post_form(URI.join(@options[:endpoint], "result/", "#{URI.encode(barcode)}"), data.to_hash )
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
client.run
|
|
149
|
+
|
|
150
|
+
puts "Listening on #{File.expand_path(port.path)}"
|
|
151
|
+
|
|
152
|
+
begin
|
|
153
|
+
yield client if block_given?
|
|
154
|
+
client.join
|
|
155
|
+
rescue Interrupt => e
|
|
156
|
+
puts "exiting"
|
|
157
|
+
raise
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# run the application
|
|
162
|
+
#
|
|
163
|
+
def run!(args = ARGV)
|
|
164
|
+
@opts.parse!(args)
|
|
165
|
+
|
|
166
|
+
communicate!
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
require 'lib/liaison_labor/interface.rb'
|
|
2
|
+
|
|
3
|
+
require 'stringio'
|
|
4
|
+
|
|
5
|
+
include Diasorin::Liaison
|
|
6
|
+
|
|
7
|
+
context "" do
|
|
8
|
+
setup do
|
|
9
|
+
@to_host = StringIO.new
|
|
10
|
+
@from_host = StringIO.new
|
|
11
|
+
|
|
12
|
+
@host = PacketIO.new(LiaisonProtocol, @to_host, @from_host)
|
|
13
|
+
@host.run
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def transmit_string(str)
|
|
17
|
+
@to_host << str
|
|
18
|
+
@to_host.rewind
|
|
19
|
+
30.times { Thread.pass }
|
|
20
|
+
@from_host.rewind
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
specify "ENQ should be acknowledged" do
|
|
24
|
+
transmit_string "\x05"
|
|
25
|
+
response = @from_host.read
|
|
26
|
+
response.should == "\x06"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
specify "Valid packet should be acknowledged" do
|
|
30
|
+
s = "\x05\x026L|1|N\x0d\x0309\x0d\x0a"
|
|
31
|
+
transmit_string s
|
|
32
|
+
response = @from_host.read
|
|
33
|
+
response.should == "\x06\x06"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
specify "another valid packet" do
|
|
37
|
+
s = "\005\0023R|1|^^^TSH|0.182|mIU/l||L||F||||20070510161808|Liaison\r\003E2\r\n"
|
|
38
|
+
lambda {
|
|
39
|
+
transmit_string s
|
|
40
|
+
response = @from_host.read
|
|
41
|
+
}.should_not raise_error
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
require 'lib/liaison_labor/packets.rb'
|
|
2
|
+
|
|
3
|
+
include Diasorin
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def has_header(packet, char)
|
|
7
|
+
specify "should be matched by packets stating with #{char}" do
|
|
8
|
+
instance_variable_get("@#{packet}").matches?("#{char}...").should be_true
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
specify "different headers should not match" do
|
|
12
|
+
instance_variable_get("@#{packet}").matches?("random garbage").should be_false
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def has_example(packet, example_str)
|
|
17
|
+
specify "example should parse without errors" do
|
|
18
|
+
lambda {
|
|
19
|
+
instance_variable_get("@#{packet}").from_str(example_str)
|
|
20
|
+
}.should_not raise_error
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
context "checksum for packets" do
|
|
25
|
+
setup do
|
|
26
|
+
@full_packet = "\0021O|1|00000023||^^^FT4^|||||||||||||||||||||F\r\003EE\r\n"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
specify "data should have a correct checksum" do
|
|
30
|
+
LiaisonProtocol.checksum_valid?("1O|1|00000023||^^^FT4^|||||||||||||||||||||F\r","EE".to_i(16)).should be_true
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
context "The MessageHeaderRecord packet from/to Liaison" do
|
|
37
|
+
setup do
|
|
38
|
+
@packet = Liaison::Packets::MessageHeaderRecord
|
|
39
|
+
@str = "H|\^&|||LaborEDV|||||Liaison|||1|19971113154903"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
has_header :packet, "H"
|
|
43
|
+
|
|
44
|
+
specify "should have a sender_id" do
|
|
45
|
+
@packet.from_str(@str).sender_id.should == "LaborEDV"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
specify "should have a receiver_id" do
|
|
49
|
+
@packet.from_str(@str).receiver_id.should == "Liaison"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
specify "should have a timestamp" do
|
|
53
|
+
@packet.from_str(@str).timestamp.should == DateTime.new(1997,11,13,15,49,03)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
context "Building a MessageHeaderRecord" do
|
|
58
|
+
setup do
|
|
59
|
+
@header = Liaison::Packets::MessageHeaderRecord
|
|
60
|
+
@packet = @header.new("Sender","Receiver")
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
specify "should build a correct packet" do
|
|
64
|
+
@packet.to_s.should == "H|\\^&|||Sender|||||Receiver"
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
context "the MessageTerminatorRecord packet to Liaison" do
|
|
69
|
+
setup do
|
|
70
|
+
@terminator_record = Liaison::Packets::MessageTerminatorRecord
|
|
71
|
+
@str = "L|1|N"
|
|
72
|
+
@packet = @terminator_record.from_str(@str)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
has_header :terminator_record, "L"
|
|
76
|
+
|
|
77
|
+
specify "should have sequence number" do
|
|
78
|
+
@packet.sequence_number.should == 1
|
|
79
|
+
end
|
|
80
|
+
specify "should have termination code" do
|
|
81
|
+
@packet.termination_code.should == "N"
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
context "the PatientInformationRecord from/to Liaison" do
|
|
86
|
+
setup do
|
|
87
|
+
@packet = Liaison::Packets::PatientInformationRecord
|
|
88
|
+
@example = "P|1||PatID01||Meyer^Anna||19741001|F|||||MARTINEZ"
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
has_header :packet, "P"
|
|
92
|
+
|
|
93
|
+
specify "should have a sequence number" do
|
|
94
|
+
@packet.from_str(@example).sequence_number.should == 1
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
specify "should have a patient ID" do
|
|
98
|
+
@packet.from_str(@example).patient_id.should == "PatID01"
|
|
99
|
+
end
|
|
100
|
+
specify "should have a patient name" do
|
|
101
|
+
@packet.from_str(@example).patient_last_name.should == "Meyer"
|
|
102
|
+
@packet.from_str(@example).patient_first_name.should == "Anna"
|
|
103
|
+
end
|
|
104
|
+
specify "should have a birthday" do
|
|
105
|
+
@packet.from_str(@example).birthdate.should == Date.new(1974,10,01)
|
|
106
|
+
end
|
|
107
|
+
specify "should have sex" do
|
|
108
|
+
@packet.from_str(@example).sex.should == "F"
|
|
109
|
+
end
|
|
110
|
+
specify "should have attending physician" do
|
|
111
|
+
@packet.from_str(@example).physician.should == "MARTINEZ"
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
context "building a PatientInformationRecord" do
|
|
116
|
+
setup do
|
|
117
|
+
@record = Liaison::Packets::PatientInformationRecord
|
|
118
|
+
@packet = @record.new(1, "000204060", "Sierra", "Rudolph")
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
specify "should just work" do
|
|
122
|
+
@packet.to_s.should == "P|1||000204060||Sierra^Rudolph"
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
context "the RequestInformationSegment" do
|
|
127
|
+
setup do
|
|
128
|
+
@request_information = Liaison::Packets::RequestInformationSegment
|
|
129
|
+
@str = "Q|1|Sample01||ALL||||||||O"
|
|
130
|
+
@packet = @request_information.from_str(@str)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
has_header :request_information, "Q"
|
|
134
|
+
|
|
135
|
+
specify "should have a sequence number" do
|
|
136
|
+
@packet.sequence_number.should == 1
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
specify "should have starting range id" do
|
|
140
|
+
@packet.starting_range.should == "Sample01"
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
context "the TestOrderRecord from/to Liaison" do
|
|
145
|
+
setup do
|
|
146
|
+
@order_record = Liaison::Packets::TestOrderRecord
|
|
147
|
+
@example = "O|1|SampleID01||^^^AFP^1:10|N|19980506|||||||||S||||||||||X"
|
|
148
|
+
@packet = @order_record.from_str(@example)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
has_header :order_record, "O"
|
|
152
|
+
|
|
153
|
+
specify "should have a sequence number" do
|
|
154
|
+
@packet.sequence_number.should == 1
|
|
155
|
+
end
|
|
156
|
+
specify "should have a specimen id" do
|
|
157
|
+
@packet.specimen_id.should == "SampleID01"
|
|
158
|
+
end
|
|
159
|
+
specify "should have a universal test id" do
|
|
160
|
+
@packet.test_id.should == "AFP"
|
|
161
|
+
end
|
|
162
|
+
specify "should have a dilution" do
|
|
163
|
+
@packet.dilution.should == "1:10"
|
|
164
|
+
end
|
|
165
|
+
specify "should have a priority" do
|
|
166
|
+
@packet.priority.should == "N"
|
|
167
|
+
end
|
|
168
|
+
specify "should have timestamp" do
|
|
169
|
+
@packet.timestamp.should == DateTime.new(1998,5,6,0,0,0)
|
|
170
|
+
end
|
|
171
|
+
specify "should have report type" do
|
|
172
|
+
@packet.record_type.should == "X"
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
context "building a TestOrderRecord" do
|
|
177
|
+
setup do
|
|
178
|
+
@order_record = Liaison::Packets::TestOrderRecord
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
specify "should generate a correct packet" do
|
|
182
|
+
@packet = @order_record.new(17, "BarcodeID", "TSH")
|
|
183
|
+
@packet.to_s.should == "O|17|BarcodeID||^^^TSH"
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
context "the ResultRecord" do
|
|
188
|
+
setup do
|
|
189
|
+
@result_record = Liaison::Packets::ResultRecord
|
|
190
|
+
@example = "R|1|^^^AFP|0.20|IU/ml||<||F||||19980506123145|Liaison"
|
|
191
|
+
@packet = @result_record.from_str(@example)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
has_header :result_record, "R"
|
|
195
|
+
|
|
196
|
+
specify "should have sequence number" do
|
|
197
|
+
@packet.sequence_number.should == 1
|
|
198
|
+
end
|
|
199
|
+
specify "should have test id" do
|
|
200
|
+
@packet.test_id.should == "AFP"
|
|
201
|
+
end
|
|
202
|
+
specify "should have value" do
|
|
203
|
+
@packet.value.should == "0.20"
|
|
204
|
+
end
|
|
205
|
+
specify "should have unit" do
|
|
206
|
+
@packet.unit.should == "IU/ml"
|
|
207
|
+
end
|
|
208
|
+
specify "should have abnormal flags" do
|
|
209
|
+
@packet.abnormal_flags.should == "<"
|
|
210
|
+
end
|
|
211
|
+
specify "should have result status" do
|
|
212
|
+
@packet.result_status.should == "F"
|
|
213
|
+
end
|
|
214
|
+
specify "should have timestamp" do
|
|
215
|
+
@packet.timestamp.should == DateTime.new(1998,05,06,12,31,45)
|
|
216
|
+
end
|
|
217
|
+
specify "should have instrument" do
|
|
218
|
+
@packet.instrument.should == "Liaison"
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# TODO: empty result data should be allowed
|
|
224
|
+
|
|
225
|
+
context "the CommentRecord" do
|
|
226
|
+
setup do
|
|
227
|
+
@packet = Liaison::Packets::CommentRecord
|
|
228
|
+
end
|
|
229
|
+
has_header :packet, "C"
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
|
|
2
|
+
module Diasorin
|
|
3
|
+
module Liaison
|
|
4
|
+
class MockInterface < PacketIO
|
|
5
|
+
def initialize(read, write, options = {})
|
|
6
|
+
super(SerialProtocol, read, write, options)
|
|
7
|
+
|
|
8
|
+
add_sender( {
|
|
9
|
+
:init => Packets::MessageHeaderRecord,
|
|
10
|
+
:next_patient => Packets::NextPatient,
|
|
11
|
+
:result => Packets::Result,
|
|
12
|
+
:end => Packets::EndOfData
|
|
13
|
+
} )
|
|
14
|
+
|
|
15
|
+
add_receiver :end => Packets::EndOfData do |d|
|
|
16
|
+
self.end_of_list
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
add_receiver :next_result => Packets::NextResult do |d|
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
run
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def init
|
|
26
|
+
send_packet :init
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def result(pat_with_results)
|
|
30
|
+
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def next_patient nr
|
|
34
|
+
send_packet :next_patient, nr
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def end_of_list
|
|
38
|
+
send_packet :end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: levinalex-LiaisonLabor
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.7
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Levin Alexander
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
|
|
12
|
+
date: 2009-06-11 00:00:00 -07:00
|
|
13
|
+
default_executable: liaison_server
|
|
14
|
+
dependencies:
|
|
15
|
+
- !ruby/object:Gem::Dependency
|
|
16
|
+
name: hoe
|
|
17
|
+
type: :development
|
|
18
|
+
version_requirement:
|
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
20
|
+
requirements:
|
|
21
|
+
- - ">="
|
|
22
|
+
- !ruby/object:Gem::Version
|
|
23
|
+
version: 1.12.1
|
|
24
|
+
version:
|
|
25
|
+
description: "An interface to the LIAISON\xC2\xAE analyser by DiaSorin <http://www.diasorin.com/en/productsandsystems/liaison>"
|
|
26
|
+
email:
|
|
27
|
+
- mail@levinalex.net
|
|
28
|
+
executables:
|
|
29
|
+
- liaison_server
|
|
30
|
+
extensions: []
|
|
31
|
+
|
|
32
|
+
extra_rdoc_files:
|
|
33
|
+
- History.txt
|
|
34
|
+
- Manifest.txt
|
|
35
|
+
- README.txt
|
|
36
|
+
files:
|
|
37
|
+
- History.txt
|
|
38
|
+
- Manifest.txt
|
|
39
|
+
- README.txt
|
|
40
|
+
- Rakefile
|
|
41
|
+
- bin/liaison_server
|
|
42
|
+
- liaison_labor.gemspec
|
|
43
|
+
- lib/liaison_labor.rb
|
|
44
|
+
- lib/liaison_labor/interface.rb
|
|
45
|
+
- lib/liaison_labor/packets.rb
|
|
46
|
+
- lib/liaison_server.rb
|
|
47
|
+
- spec/liaison_interface_spec.rb
|
|
48
|
+
- spec/liaison_packet_spec.rb
|
|
49
|
+
- spec/mock/mock_liaison.rb
|
|
50
|
+
has_rdoc: true
|
|
51
|
+
homepage: http://levinalex.net/src/liaison
|
|
52
|
+
post_install_message:
|
|
53
|
+
rdoc_options:
|
|
54
|
+
- --main
|
|
55
|
+
- README.txt
|
|
56
|
+
require_paths:
|
|
57
|
+
- lib
|
|
58
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
59
|
+
requirements:
|
|
60
|
+
- - ">="
|
|
61
|
+
- !ruby/object:Gem::Version
|
|
62
|
+
version: "0"
|
|
63
|
+
version:
|
|
64
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: "0"
|
|
69
|
+
version:
|
|
70
|
+
requirements: []
|
|
71
|
+
|
|
72
|
+
rubyforge_project: liaison_labor
|
|
73
|
+
rubygems_version: 1.2.0
|
|
74
|
+
signing_key:
|
|
75
|
+
specification_version: 2
|
|
76
|
+
summary: interfaces Liaison device to worklist_manager
|
|
77
|
+
test_files: []
|
|
78
|
+
|