lis 0.0.0

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,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,23 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+ .yardoc
21
+ doc
22
+
23
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Levin Alexander
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,7 @@
1
+ = LIS
2
+
3
+ Description goes here.
4
+
5
+ == Copyright
6
+
7
+ Copyright (c) 2010 Levin Alexander. See LICENSE for details.
@@ -0,0 +1,64 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "lis"
8
+ gem.summary = %Q{LIS interface to Siemens Immulite 2000XPi or other similar analyzers}
9
+ gem.description = %Q{}
10
+ gem.email = "mail@levinalex.net"
11
+ gem.homepage = "http://github.com/levinalex/lis"
12
+ gem.authors = ["Levin Alexander"]
13
+ gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
14
+ gem.add_development_dependency "yard", ">= 0"
15
+ gem.add_development_dependency "cucumber", ">= 0"
16
+ end
17
+ Jeweler::GemcutterTasks.new
18
+ rescue LoadError
19
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
20
+ end
21
+
22
+ require 'rake/testtask'
23
+ Rake::TestTask.new(:test) do |test|
24
+ test.libs << 'lib' << 'test'
25
+ test.pattern = 'test/**/test_*.rb'
26
+ test.verbose = true
27
+ end
28
+
29
+ begin
30
+ require 'rcov/rcovtask'
31
+ Rcov::RcovTask.new do |test|
32
+ test.libs << 'test'
33
+ test.pattern = 'test/**/test_*.rb'
34
+ test.verbose = true
35
+ end
36
+ rescue LoadError
37
+ task :rcov do
38
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
39
+ end
40
+ end
41
+
42
+ task :test => :check_dependencies
43
+
44
+ begin
45
+ require 'cucumber/rake/task'
46
+ Cucumber::Rake::Task.new(:features)
47
+
48
+ task :features => :check_dependencies
49
+ rescue LoadError
50
+ task :features do
51
+ abort "Cucumber is not available. In order to run features, you must: sudo gem install cucumber"
52
+ end
53
+ end
54
+
55
+ task :default => :test
56
+
57
+ begin
58
+ require 'yard'
59
+ YARD::Rake::YardocTask.new
60
+ rescue LoadError
61
+ task :yardoc do
62
+ abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
63
+ end
64
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.0
data/bin/lis ADDED
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env ruby -w
2
+ require 'lib/lis.rb'
3
+
4
+ # protocol = LIS::Transfer::PacketizedProtocol.new
5
+ # server = LIS::Transfer::Server.new(protocol, socket)
6
+
7
+ socket = File.open("/dev/cu.usbserial-FTC95RQI", "w+")
8
+ server = LIS::Transfer::IOListener.new(socket)
9
+ packet_protocol = LIS::Transfer::PacketizedProtocol.new(server)
10
+ app_protocol = LIS::Transfer::ApplicationProtocol.new(packet_protocol)
11
+
12
+ interface = WorklistManagerInterface.new("http://localhost:3000")
13
+
14
+ app_protocol.on_request do |*args|
15
+ p "ON REQUEST"
16
+ p args
17
+
18
+ nil
19
+ # interface.load_reqests(*args)
20
+ end
21
+
22
+ app_protocol.on_result do |*args|
23
+ p "SEND RESULT"
24
+ p args
25
+ interface.send_result(*args)
26
+ end
27
+
28
+ app_protocol.on_data do |data|
29
+ p data
30
+ end
31
+
32
+ server.run!
@@ -0,0 +1,6 @@
1
+ Feature: Initializing the connection
2
+ In order to
3
+ As a role
4
+ I want feature
5
+
6
+
@@ -0,0 +1,9 @@
1
+ Feature: something something
2
+ In order to something something
3
+ A user something something
4
+ something something something
5
+
6
+ Scenario: something something
7
+ Given inspiration
8
+ When I create a sweet new gem
9
+ Then everyone should see how awesome I am
File without changes
@@ -0,0 +1,6 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../../lib')
2
+ require 'lis'
3
+
4
+ require 'test/unit/assertions'
5
+
6
+ World(Test::Unit::Assertions)
@@ -0,0 +1,10 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+
3
+ module LIS
4
+ end
5
+
6
+ require 'lis/io_listener.rb'
7
+ require 'lis/messages.rb'
8
+ require 'lis/packetized_protocol.rb'
9
+ require 'lis/application_protocol.rb'
10
+ require 'lis/worklist_manager_interface.rb'
@@ -0,0 +1,69 @@
1
+
2
+ module LIS::Transfer
3
+ class ApplicationProtocol < Base
4
+
5
+ def on_result(&block)
6
+ @on_result_callback = block
7
+ end
8
+
9
+ def on_request(&block)
10
+ @on_request_callback = block
11
+ end
12
+
13
+
14
+ def received_header(message)
15
+ @patient_information ||= {} # delete the list of patients
16
+ end
17
+
18
+ def result_for(patient, order, result)
19
+ @on_result_callback.call(patient, order, result)
20
+ end
21
+
22
+ def received_patient_information(message)
23
+ @last_order = nil
24
+ @last_patient = message
25
+ end
26
+
27
+ def received_order_record(message)
28
+ @last_order = message
29
+ end
30
+
31
+ def received_result(message)
32
+ result_for(@last_patient, @last_order, message)
33
+ end
34
+
35
+ def received_request_for_information(message)
36
+ @patient_information ||= {}
37
+ requests = @on_request_callback.call(p.starting_range)
38
+ @patient_information[p.sequence_number] = requests if requests
39
+ end
40
+
41
+ def initialize(*args)
42
+ super
43
+
44
+ @last_patient = nil
45
+ @last_order = nil
46
+ @handlers = {
47
+ LIS::Message::Header => :received_header,
48
+ LIS::Message::Patient => :received_patient_information,
49
+ LIS::Message::Order => :received_order_record,
50
+ LIS::Message::Result => :received_result,
51
+ LIS::Message::Query => :received_request_for_information
52
+ }
53
+ end
54
+
55
+ def receive(type, message = nil)
56
+ case type
57
+ when :begin
58
+ @last_patient = nil
59
+ @last_order = nil
60
+ when :idle
61
+ when :message
62
+ @message = LIS::Message::Base.from_string(message)
63
+ handler = @handlers[@message.class]
64
+ send(handler, @message) if handler
65
+ end
66
+ end
67
+ end
68
+
69
+ end
@@ -0,0 +1,76 @@
1
+ require 'strscan'
2
+
3
+ module LIS::Transfer
4
+
5
+ # a chainable IO-Listener that provides to methods:
6
+ #
7
+ # +on_data+ :: a callback that is called whenever a message is received
8
+ # +write+ :: can be called so send messages to the underlying IO
9
+ #
10
+ # when overriding this class, you need to implement two methods:
11
+ #
12
+ # +receive+ :: is called from an underlying IO whenever a message is received
13
+ # the message can be handled, if messages should be propagated, you
14
+ # need to call `forward`
15
+ #
16
+ # +write+ :: when data needs to be encoded, formatted before it can be send
17
+ # send it with `super`
18
+ #
19
+ #
20
+ class Base
21
+ def initialize(read, write = read)
22
+ @reader, @writer = read, write
23
+ @on_data = nil
24
+ @reader.on_data { |*data| receive(*data) } if @reader.respond_to?(:on_data)
25
+ end
26
+
27
+ def on_data(&block)
28
+ @on_data = block
29
+ end
30
+
31
+ def write(message)
32
+ @writer << message if @writer
33
+ end
34
+ def <<(*args)
35
+ write(*args)
36
+ end
37
+
38
+ private
39
+
40
+ def receive(data)
41
+ forward(data)
42
+ end
43
+
44
+ def forward(*data)
45
+ @on_data.call(*data) if @on_data
46
+ end
47
+ end
48
+
49
+
50
+ class LineBasedProtocol < Base
51
+ def receive(data)
52
+ @memo ||= ""
53
+ scanner = StringScanner.new(@memo + data)
54
+ while s = scanner.scan(/.*?\n/)
55
+ forward(s.strip)
56
+ end
57
+ @memo = scanner.rest
58
+ nil
59
+ end
60
+
61
+ def write(data)
62
+ super(data + "\n")
63
+ end
64
+ end
65
+
66
+ class IOListener < Base
67
+ def run!
68
+ while not @reader.eof?
69
+ str = @reader.readpartial(4096)
70
+ forward(str)
71
+ end
72
+ end
73
+ end
74
+ end
75
+
76
+
@@ -0,0 +1,125 @@
1
+
2
+ module LIS::Message
3
+ module ClassMethods
4
+ FIELD_TYPES = {
5
+ :string => lambda { |s| s },
6
+ :int => lambda { |s| s.to_i }
7
+ }
8
+
9
+ def from_string(message)
10
+ frame_number, type, data = parse(message)
11
+ klass = (@@messages_by_type || {})[type]
12
+ raise "unknown message type #{type.inspect}" unless klass
13
+
14
+ obj = klass.allocate
15
+ obj.frame_number = frame_number
16
+ obj.type_id = type
17
+
18
+ # populate named fields
19
+ data.each_with_index do |val, index|
20
+ klass.get_named_field_attributes(index + 2)
21
+ if field = klass.get_named_field_attributes(index+2)
22
+ obj.send(:"#{field[:name]}=", FIELD_TYPES[field[:type]].call(val))
23
+ end
24
+ end
25
+
26
+ obj
27
+ end
28
+
29
+ def initialize_from_message(*list_of_fields)
30
+ end
31
+
32
+ protected
33
+
34
+ def parse(string)
35
+ frame_number, type, data = string.scan(/^(.)(.)\|(.*)$/)[0]
36
+ data = data.split(/\|/)
37
+
38
+ return [frame_number.to_i, type, data]
39
+ end
40
+
41
+ def type_id(char)
42
+ @@messages_by_type ||= {}
43
+ @@messages_by_type[char] = self
44
+ end
45
+
46
+ def named_field(idx, name, type = :string)
47
+ set_named_field_attributes(idx, :name => name, :type => type)
48
+ attr_accessor name
49
+ end
50
+
51
+ def get_named_field_attributes(key)
52
+ @field_names ||= {}
53
+ val = (@field_names || {})[key]
54
+ val ||= superclass.get_named_field_attributes(key) if superclass.respond_to?(:get_named_field_attributes)
55
+ val
56
+ end
57
+
58
+ private
59
+
60
+ def set_named_field_attributes(key, *val)
61
+ @field_names ||= {}
62
+ @field_names[key] = *val
63
+ end
64
+ end
65
+
66
+ class Base
67
+ extend ClassMethods
68
+ attr_accessor :frame_number
69
+ attr_accessor :type_id
70
+
71
+ named_field 2, :sequence_number, :int
72
+ end
73
+
74
+
75
+ class Header < Base
76
+ type_id "H"
77
+ named_field 2, :delimiter_definition
78
+ named_field 4, :access_password
79
+ named_field 5, :sender_name
80
+ named_field 10, :receiver_id
81
+ end
82
+
83
+ class Order < Base
84
+ type_id "O"
85
+ named_field 3, :specimen_id
86
+ named_field 5, :universal_test_id
87
+ named_field 6, :priority
88
+ named_field 7, :requested_at
89
+ named_field 8, :collected_at
90
+ end
91
+
92
+ class Result < Base
93
+ type_id "R"
94
+ named_field 3, :universal_test_id
95
+ named_field 4, :result_value
96
+ named_field 5, :unit
97
+ named_field 6, :reference_ranges
98
+ named_field 7, :abnormal_flags
99
+ end
100
+
101
+ class Patient < Base
102
+ type_id "P"
103
+ end
104
+
105
+ class Query < Base
106
+ type_id "Q"
107
+ end
108
+
109
+
110
+ class TerminatorRecord < Base
111
+ TERMINATION_CODES = {
112
+ "N" => "Normal termination",
113
+ "T" => "Sender Aborted",
114
+ "R" => "Receiver Abort",
115
+ "E" => "Unknown system error",
116
+ "Q" => "Error in last request for information",
117
+ "I" => "No information available from last query",
118
+ "F" => "Last request for information Processed"
119
+ }
120
+
121
+ type_id "L"
122
+ named_field 3, :termination_code
123
+ end
124
+
125
+ end
@@ -0,0 +1,83 @@
1
+
2
+ module LIS::Transfer
3
+
4
+ # splits a stream into lis packets and only lets packets through
5
+ # that are inside a session delimited by ENQ .. EOT
6
+ #
7
+ # check the checksum and do acknowledgement of messages
8
+ #
9
+ # forwards the following events:
10
+ #
11
+ # - :message, String :: when a message is received
12
+ # - :idle :: when a transmission is finished (after EOT is received)
13
+ #
14
+ class PacketizedProtocol < Base
15
+ ACK = "\006"
16
+ NAK = "\025"
17
+ ENQ = "\005"
18
+ EOT = "\004"
19
+
20
+ RX = /(?:
21
+ \005 | # ENQ - start a transaction
22
+ \004 | # EOT - ends a transaction
23
+ (?:\002 (.*?) \015 \003 (.+?) \015 \012)) # a message with a checksum
24
+ /xm
25
+
26
+ def initialize(*args)
27
+ super(*args)
28
+ @memo = ""
29
+ @inside_transmission = false
30
+ end
31
+
32
+ def receive(data)
33
+ scanner = StringScanner.new(@memo + data)
34
+ while match = scanner.scan(RX)
35
+ case match
36
+ when ENQ then transmission_start
37
+ when EOT then transmission_end
38
+ else
39
+ received_message(match)
40
+ write ACK
41
+ end
42
+ end
43
+ @memo = scanner.rest
44
+ nil
45
+ end
46
+
47
+
48
+ private
49
+
50
+ def self.message_from_string(string)
51
+ match = string.match(RX)
52
+ data = match[1]
53
+ checksum = match[2]
54
+
55
+ expected_checksum = data.to_enum(:each_byte).inject(16) { |a,b| (a+b) % 0x100 }
56
+ actual_checksum = checksum.to_i(16)
57
+
58
+ raise "checksum mismatch" unless expected_checksum == actual_checksum
59
+ return data
60
+ end
61
+
62
+ def received_message(message)
63
+ return false unless @inside_transmission
64
+ forward(:message, self.class.message_from_string(message))
65
+ end
66
+
67
+ def transmission_start
68
+ return false if @inside_transmission
69
+ write ACK
70
+ forward :begin
71
+ @inside_transmission = true
72
+ true
73
+ end
74
+
75
+ def transmission_end
76
+ return false unless @inside_transmission
77
+ forward :idle
78
+ @inside_transmission = false
79
+ true
80
+ end
81
+ end
82
+
83
+ end
@@ -0,0 +1,47 @@
1
+ class WorklistManagerInterface
2
+ def initialize(endpoint)
3
+ @endpoint = endpoint
4
+ end
5
+
6
+ def load_requests(barcode)
7
+ begin
8
+ uri = URI.join(@endpoint,"find_requests/#{barcode}")
9
+ result = fetch_with_redirect(uri.to_s)
10
+ data = YAML.load(result.body)
11
+ data["id"] = barcode
12
+ rescue Exception => e
13
+ puts e
14
+ puts e.backtrace
15
+ data = nil
16
+ end
17
+ end
18
+
19
+ def send_result(patient, order, result)
20
+ barcode = order.specimen_id
21
+ data = {
22
+ "test_name" => order.test_id,
23
+ "value" => result.value,
24
+ "unit" => result.unit,
25
+ "status" => result.result_status,
26
+ "flags" => result.abnormal_flags,
27
+ "result_timestamp" => result.timestamp
28
+ }
29
+
30
+ Net::HTTP.post_form(URI.join(@endpoint, "result/#{URI.encode(barcode)}"), data.to_hash)
31
+ end
32
+
33
+
34
+ private
35
+
36
+ def fetch_with_redirect(uri_str, limit = 10)
37
+ raise ArgumentError, 'HTTP redirect too deep' if limit == 0
38
+
39
+ response = Net::HTTP.get_response(URI.parse(uri_str))
40
+ case response
41
+ when Net::HTTPSuccess then response
42
+ when Net::HTTPRedirection then fetch_with_redirect(response['location'], limit - 1)
43
+ else
44
+ response.error!
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,78 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{lis}
8
+ s.version = "0.0.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Levin Alexander"]
12
+ s.date = %q{2010-02-07}
13
+ s.default_executable = %q{lis}
14
+ s.description = %q{}
15
+ s.email = %q{mail@levinalex.net}
16
+ s.executables = ["lis"]
17
+ s.extra_rdoc_files = [
18
+ "LICENSE",
19
+ "README.rdoc"
20
+ ]
21
+ s.files = [
22
+ ".document",
23
+ ".gitignore",
24
+ "LICENSE",
25
+ "README.rdoc",
26
+ "Rakefile",
27
+ "VERSION",
28
+ "bin/lis",
29
+ "features/communication basics.feature",
30
+ "features/lis.feature",
31
+ "features/step_definitions/lis_steps.rb",
32
+ "features/support/env.rb",
33
+ "lib/lis.rb",
34
+ "lib/lis/application_protocol.rb",
35
+ "lib/lis/io_listener.rb",
36
+ "lib/lis/messages.rb",
37
+ "lib/lis/packetized_protocol.rb",
38
+ "lib/lis/worklist_manager_interface.rb",
39
+ "lis.gemspec",
40
+ "test/helper.rb",
41
+ "test/lib/mock_server.rb",
42
+ "test/test_io_listener.rb",
43
+ "test/test_messages.rb",
44
+ "test/test_packetized_protocol.rb"
45
+ ]
46
+ s.homepage = %q{http://github.com/levinalex/lis}
47
+ s.rdoc_options = ["--charset=UTF-8"]
48
+ s.require_paths = ["lib"]
49
+ s.rubygems_version = %q{1.3.5}
50
+ s.summary = %q{LIS interface to Siemens Immulite 2000XPi or other similar analyzers}
51
+ s.test_files = [
52
+ "test/helper.rb",
53
+ "test/lib/mock_server.rb",
54
+ "test/test_io_listener.rb",
55
+ "test/test_messages.rb",
56
+ "test/test_packetized_protocol.rb"
57
+ ]
58
+
59
+ if s.respond_to? :specification_version then
60
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
61
+ s.specification_version = 3
62
+
63
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
64
+ s.add_development_dependency(%q<thoughtbot-shoulda>, [">= 0"])
65
+ s.add_development_dependency(%q<yard>, [">= 0"])
66
+ s.add_development_dependency(%q<cucumber>, [">= 0"])
67
+ else
68
+ s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
69
+ s.add_dependency(%q<yard>, [">= 0"])
70
+ s.add_dependency(%q<cucumber>, [">= 0"])
71
+ end
72
+ else
73
+ s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
74
+ s.add_dependency(%q<yard>, [">= 0"])
75
+ s.add_dependency(%q<cucumber>, [">= 0"])
76
+ end
77
+ end
78
+
@@ -0,0 +1,12 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+ require 'lib/mock_server'
5
+
6
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
7
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
8
+ require 'lis'
9
+
10
+ class Test::Unit::TestCase
11
+ end
12
+
@@ -0,0 +1,50 @@
1
+ module Mock
2
+ end
3
+
4
+ class Mock::Server
5
+ def initialize(read, write)
6
+ @read, @write = read, write
7
+ @queue = Queue.new
8
+ @thread = Thread.new do
9
+ parse_commands
10
+ end
11
+ end
12
+
13
+ def write(string)
14
+ @queue.push [:write, string]
15
+ self
16
+ end
17
+
18
+ def read_all
19
+ @read.readpartial(4096)
20
+ end
21
+
22
+ def wait(seconds = 0.02)
23
+ @queue.push [:wait, seconds]
24
+ self
25
+ end
26
+
27
+ def eof
28
+ @queue.push [:close]
29
+ end
30
+
31
+
32
+ private
33
+
34
+ def parse_commands
35
+ loop do
36
+ action, data = @queue.pop
37
+
38
+ case action
39
+ when :close
40
+ @write.close
41
+ break
42
+ when :write
43
+ @write.write(data)
44
+ @write.flush
45
+ when :wait
46
+ sleep data
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,34 @@
1
+ require 'helper'
2
+
3
+ class TestIOListener < Test::Unit::TestCase
4
+ context "a server" do
5
+ setup do
6
+ r1, w1 = IO.pipe # Immulite -> LIS
7
+ r2, w2 = IO.pipe # LIS -> Immulite
8
+
9
+ @server = LIS::Transfer::IOListener.new(r1, w2)
10
+ @protocol = LIS::Transfer::LineBasedProtocol.new(@server)
11
+ @device = Mock::Server.new(r2, w1)
12
+ end
13
+
14
+ should "exist" do
15
+ assert_not_nil @server
16
+ end
17
+
18
+ should "yield packets written to it" do
19
+ @packets = []
20
+ @protocol.on_data { |packet| @packets << packet }
21
+
22
+ @device.write("fo").wait.write("o\n").wait.write("bar\n").eof
23
+ @server.run!
24
+
25
+ assert_equal ["foo", "bar"], @packets
26
+ end
27
+
28
+ should "send data" do
29
+ @protocol << "hello world"
30
+ data = @device.read_all
31
+ assert_equal "hello world\n", data
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,48 @@
1
+ require 'helper'
2
+
3
+ class TestPacketizedProtocol < Test::Unit::TestCase
4
+
5
+ context "message parsing" do
6
+ setup do
7
+ @message = LIS::Message::Base.from_string("3L|1|N")
8
+ end
9
+
10
+ should "have correct frame number" do
11
+ assert_equal 3, @message.frame_number
12
+ end
13
+
14
+ should "have correct type" do
15
+ assert_equal LIS::Message::TerminatorRecord, @message.class
16
+ assert_equal "L", @message.type_id
17
+ end
18
+
19
+ should "have correct sequence number" do
20
+ assert_equal 1, @message.sequence_number
21
+ end
22
+ end
23
+
24
+ context "parsing a result message" do
25
+ setup do
26
+ @str = "7R|1|^^^TSH|0.902|mIU/L|0.400\\0.004^4.00\\75.0|N|N|R|||20100115105636|20100115120641|B0135"
27
+ @message = LIS::Message::Base.from_string(@str)
28
+ end
29
+
30
+ should "have correct type" do
31
+ assert_equal LIS::Message::Result, @message.type
32
+ assert_equal "R", @message.type_id
33
+ end
34
+
35
+ should "have correct test id" do
36
+ assert_equal "TSH", @message.universal_test_id
37
+ end
38
+
39
+ should "have correct value" do
40
+ assert_equal "0.902", @message.result_value
41
+ end
42
+
43
+ should "have currect value and unit" do
44
+ assert_equal "mIU/L", @message.unit
45
+ end
46
+ end
47
+
48
+ end
@@ -0,0 +1,81 @@
1
+ require 'helper'
2
+
3
+ class TestPacketizedProtocol < Test::Unit::TestCase
4
+
5
+ def self.gsub_nonprintable(str)
6
+ str.gsub(/<[A-Z]+?>/) do |match|
7
+ { "<ETX>" => "\003",
8
+ "<STX>" => "\002",
9
+ "<CR>" => "\015",
10
+ "<LF>" => "\012",
11
+ "<ENQ>" => "\004",
12
+ "<EOT>" => "\005" }[match] or raise ArgumentError, "match #{match.inspect} not found"
13
+ end
14
+ end
15
+
16
+ def assert_packet_matches(expected, str)
17
+ str = self.class.gsub_nonprintable(str)
18
+ expected = self.class.gsub_nonprintable(expected)
19
+
20
+ match = LIS::Transfer::PacketizedProtocol::RX.match(str)
21
+ assert_not_nil match, expected
22
+ assert_equal expected, match[0]
23
+ end
24
+
25
+ def self.packet_should_match(expected, str)
26
+ should "match \"#{str}\" as \"#{expected}\"" do
27
+ assert_packet_matches(expected, str)
28
+ assert_packet_matches(expected, "garbage" + str)
29
+ assert_packet_matches(expected, str + "garbage")
30
+ end
31
+ end
32
+
33
+ packet_should_match "<ENQ>", "<ENQ>"
34
+ packet_should_match "<EOT>", "<EOT>"
35
+ packet_should_match "<STX>packet_data<CR><ETX>checksum<CR><LF>",
36
+ "<STX>packet_data<CR><ETX>checksum<CR><LF><STX>packet_data<CR><ETX>checksum<CR><LF>rest"
37
+
38
+ context "packetized protocol" do
39
+ setup do
40
+ @sent = []
41
+ @data = []
42
+ @protocol = LIS::Transfer::PacketizedProtocol.new(nil, @sent)
43
+ @protocol.on_data do |*d|
44
+ @data << d
45
+ end
46
+ end
47
+ should "fire start_of_transmission event when receiving ENQ" do
48
+ @protocol.receive("\005")
49
+ assert_equal [[:begin]], @data
50
+ end
51
+
52
+ should "fire end_of_transmission event after EOT is received" do
53
+ @protocol.receive("\005\004")
54
+ assert_equal [[:begin], [:idle]], @data
55
+ end
56
+
57
+ should "not fire end_of_transmission event after EOT is received" do
58
+ @protocol.receive("\004")
59
+ assert_equal [], @data
60
+ end
61
+
62
+ should "fire trasmission events the correct number of times" do
63
+ @protocol.receive("\005\005")
64
+ @protocol.receive("\004")
65
+ assert_equal [[:begin], [:idle]], @data
66
+ @protocol.receive("\004\005")
67
+ @protocol.receive("\004")
68
+ assert_equal [[:begin], [:idle], [:begin], [:idle]], @data
69
+ end
70
+
71
+ should "propagate only packet data" do
72
+ @str = "\0023L|1\r\0033C\r\n"
73
+ @protocol.receive("\005")
74
+ @protocol.receive(@str)
75
+ @protocol.receive("\004")
76
+
77
+ assert_equal [[:begin], [:message, "3L|1"], [:idle]], @data
78
+ end
79
+ end
80
+
81
+ end
metadata ADDED
@@ -0,0 +1,111 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lis
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Levin Alexander
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-02-07 00:00:00 +01:00
13
+ default_executable: lis
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: thoughtbot-shoulda
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: yard
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: cucumber
37
+ type: :development
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ description: ""
46
+ email: mail@levinalex.net
47
+ executables:
48
+ - lis
49
+ extensions: []
50
+
51
+ extra_rdoc_files:
52
+ - LICENSE
53
+ - README.rdoc
54
+ files:
55
+ - .document
56
+ - .gitignore
57
+ - LICENSE
58
+ - README.rdoc
59
+ - Rakefile
60
+ - VERSION
61
+ - bin/lis
62
+ - features/communication basics.feature
63
+ - features/lis.feature
64
+ - features/step_definitions/lis_steps.rb
65
+ - features/support/env.rb
66
+ - lib/lis.rb
67
+ - lib/lis/application_protocol.rb
68
+ - lib/lis/io_listener.rb
69
+ - lib/lis/messages.rb
70
+ - lib/lis/packetized_protocol.rb
71
+ - lib/lis/worklist_manager_interface.rb
72
+ - lis.gemspec
73
+ - test/helper.rb
74
+ - test/lib/mock_server.rb
75
+ - test/test_io_listener.rb
76
+ - test/test_messages.rb
77
+ - test/test_packetized_protocol.rb
78
+ has_rdoc: true
79
+ homepage: http://github.com/levinalex/lis
80
+ licenses: []
81
+
82
+ post_install_message:
83
+ rdoc_options:
84
+ - --charset=UTF-8
85
+ require_paths:
86
+ - lib
87
+ required_ruby_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: "0"
92
+ version:
93
+ required_rubygems_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: "0"
98
+ version:
99
+ requirements: []
100
+
101
+ rubyforge_project:
102
+ rubygems_version: 1.3.5
103
+ signing_key:
104
+ specification_version: 3
105
+ summary: LIS interface to Siemens Immulite 2000XPi or other similar analyzers
106
+ test_files:
107
+ - test/helper.rb
108
+ - test/lib/mock_server.rb
109
+ - test/test_io_listener.rb
110
+ - test/test_messages.rb
111
+ - test/test_packetized_protocol.rb