cmux 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.so
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in cmux.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Sergey Gridasov
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # Cmux
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'cmux'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install cmux
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env rake
2
+
3
+ require "bundler/gem_tasks"
data/cmux.gemspec ADDED
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/cmux/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Sergey Gridasov"]
6
+ gem.email = ["grindars@gmail.com"]
7
+ gem.description = %q{Ruby bindings for CMUX, GSM 07.10 multiplexer}
8
+ gem.summary = %q{Ruby bindings for CMUX, GSM 07.10 multiplexer}
9
+ gem.homepage = ""
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "cmux"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = CMUX::VERSION
17
+
18
+ gem.add_dependency 'ffi'
19
+ end
data/lib/cmux.rb ADDED
@@ -0,0 +1,12 @@
1
+ require "ffi"
2
+ require "set"
3
+
4
+ require "cmux/version"
5
+ require "cmux/modem_chatter"
6
+ require "cmux/modem_command"
7
+ require "cmux/modem_command_response"
8
+ require "cmux/library"
9
+ require "cmux/io"
10
+ require "cmux/connection"
11
+ require "cmux/mux"
12
+ require "cmux/channel"
@@ -0,0 +1,29 @@
1
+ module CMUX
2
+ class Channel
3
+ attr_reader :device
4
+ attr_reader :port
5
+
6
+ def initialize(device, port, mux)
7
+ @device = device
8
+ @port = port
9
+ @mux = mux
10
+ end
11
+
12
+ def open(&block)
13
+ io = IO.open_tty @device
14
+ if block_given?
15
+ begin
16
+ yield io
17
+ ensure
18
+ io.close
19
+ end
20
+ else
21
+ io
22
+ end
23
+ end
24
+
25
+ def close
26
+ @mux.close_port @port
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,72 @@
1
+ module CMUX
2
+ class Connection
3
+
4
+ class Pointer < FFI::AutoPointer
5
+ def self.release(pointer)
6
+ Library.cmux_destroy pointer
7
+ end
8
+ end
9
+
10
+ def initialize
11
+ buffer = FFI::Buffer.new :pointer, 1
12
+ context = Library.cmux_create buffer
13
+
14
+ if context.null?
15
+ ptr = buffer.read_pointer
16
+ begin
17
+ error = ptr.read_string
18
+ raise "MUX creation failed: #{error}"
19
+ ensure
20
+ Library.cmux_free ptr
21
+ end
22
+ end
23
+
24
+ @handle = Pointer.new context
25
+ end
26
+
27
+ def open(device)
28
+ status = Library.cmux_open @handle, device
29
+ if status == -1
30
+ raise Library.cmux_error(@handle)
31
+ end
32
+
33
+ nil
34
+ end
35
+
36
+ def activate
37
+ status = Library.cmux_activate @handle
38
+ if status == -1
39
+ raise Library.cmux_error(@handle)
40
+ end
41
+
42
+ nil
43
+ end
44
+
45
+ def close
46
+ @handle.free
47
+ @handle = nil
48
+ end
49
+
50
+ def open_port(index)
51
+ buffer = FFI::Buffer.new :pointer, 1
52
+ status = Library.cmux_open_port @handle, index, buffer
53
+ if status == -1
54
+ raise Library.cmux_error(@handle)
55
+ end
56
+
57
+ pointer = buffer.read_pointer
58
+ begin
59
+ pointer.read_string
60
+ ensure
61
+ Library.cmux_free pointer
62
+ end
63
+ end
64
+
65
+ def close_port(index)
66
+ status = Library.cmux_close_port @handle, index
67
+ if status == -1
68
+ raise Library.cmux_error(@handle)
69
+ end
70
+ end
71
+ end
72
+ end
data/lib/cmux/io.rb ADDED
@@ -0,0 +1,21 @@
1
+ module CMUX
2
+ module IO
3
+ def self.open_tty(filename)
4
+ buffer = FFI::Buffer.new :pointer, 1
5
+
6
+ fd = Library.cmux_open_device filename, buffer
7
+ if fd == -1
8
+ ptr = buffer.read_pointer
9
+ begin
10
+ error = ptr.read_string
11
+ ensure
12
+ Library.cmux_free ptr
13
+ end
14
+
15
+ raise error
16
+ end
17
+
18
+ FFI::IO.for_fd fd, "r+"
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,19 @@
1
+ module CMUX
2
+
3
+ module Library
4
+ extend FFI::Library
5
+
6
+ ffi_lib "cmux"
7
+
8
+ attach_function :cmux_create, [ :buffer_out ], :pointer
9
+ attach_function :cmux_destroy, [ :pointer ], :void
10
+ attach_function :cmux_error, [ :pointer ], :string
11
+ attach_function :cmux_open, [ :pointer, :string ], :int
12
+ attach_function :cmux_activate, [ :pointer ], :int
13
+ attach_function :cmux_open_port, [ :pointer, :int, :buffer_out ], :int
14
+ attach_function :cmux_close_port, [ :pointer, :int ], :int
15
+ attach_function :cmux_free, [ :pointer ], :void
16
+ attach_function :cmux_open_device, [ :string, :buffer_out ], :int
17
+ end
18
+
19
+ end
@@ -0,0 +1,192 @@
1
+ module CMUX
2
+ class ModemChatter
3
+ attr_reader :io
4
+
5
+ def initialize(io)
6
+ @io = io
7
+ @command_queue = []
8
+ @buffer = ""
9
+ @response = []
10
+ @unprocessed_lines = []
11
+ @have_running_command = true # because echo can be disabled before init string
12
+ @unsolicited = Hash.new do |hash, key|
13
+ hash[key] = Set.new
14
+ end
15
+
16
+ command "E1V1+CMEE=2", 2
17
+ end
18
+
19
+ def command(command, timeout = nil, &block)
20
+ submit_now = @command_queue.empty?
21
+ cmd = ModemCommand.new(command, self, timeout, &block)
22
+ @command_queue.push cmd
23
+
24
+ submit if submit_now
25
+
26
+ cmd
27
+ end
28
+
29
+ def submit
30
+ return if @command_queue.empty?
31
+
32
+ cmd = @command_queue.first
33
+ cmd.start
34
+
35
+ @io.write "AT#{cmd.command}\r\n"
36
+ end
37
+
38
+ def desired_timeout
39
+ return nil if @command_queue.empty?
40
+
41
+ command = @command_queue.first
42
+ return nil if command.timeout.nil?
43
+
44
+ now = DateTime.now.to_time
45
+
46
+ remaining = command.issued_at + command.timeout - now
47
+ if remaining < 0
48
+ remaining = 0
49
+ end
50
+
51
+ remaining
52
+ end
53
+
54
+ def run_periodic
55
+ remaining = desired_timeout
56
+
57
+ if remaining == 0
58
+ @have_running_command = false
59
+ complete_first "timeout"
60
+ end
61
+ end
62
+
63
+ def self.poll(devices, timeout_limit = nil)
64
+ timeouts = devices.map(&:desired_timeout)
65
+ timeouts << timeout_limit
66
+ timeouts.reject! &:nil?
67
+ read, write, except = ::IO.select devices.map(&:io), [], [], timeouts.min
68
+
69
+ unless read.nil?
70
+ read.each do |io|
71
+ devices.find { |dev| dev.io == io }.poll
72
+ end
73
+ end
74
+
75
+ devices.each(&:run_periodic)
76
+ end
77
+
78
+ def poll
79
+ begin
80
+ @buffer += @io.read_nonblock 4096
81
+ on_read
82
+ rescue Errno::EINTR, ::IO::WaitReadable
83
+ end
84
+ end
85
+
86
+ def subscribe(type, receiver)
87
+ @unsolicited[type].add receiver
88
+ end
89
+
90
+ def unsubscribe(type, receiver)
91
+ @unsolicited[type].delete receiver
92
+ end
93
+
94
+ private
95
+
96
+ def on_read
97
+ chunk = @buffer.split "\r\n", -1
98
+ @buffer = chunk.pop
99
+ @unprocessed_lines += chunk
100
+
101
+ completed = false
102
+
103
+ while @unprocessed_lines.any?
104
+ line = @unprocessed_lines.shift
105
+ line.rstrip!
106
+
107
+ if @have_running_command
108
+ if line == "OK" || line == "CONNECT"
109
+ @have_running_command = false
110
+ completed = true
111
+ complete_first
112
+ elsif line == "ERROR" || line == "NO CARRIER" || line == "NO DIALTONE" || line == "BUSY"
113
+ @have_running_command = false
114
+ completed = true
115
+ complete_first line
116
+ elsif line =~ /^\+CME ERROR: (.*)$/
117
+ @have_running_command = false
118
+ completed = true
119
+ complete_first $1
120
+ else
121
+ @response << line
122
+ end
123
+ elsif @command_queue.any? && line == "AT#{@command_queue.first.command}"
124
+ @have_running_command = true
125
+ elsif !line.empty?
126
+ on_unsolicited line
127
+ end
128
+ end
129
+
130
+ submit if completed
131
+ end
132
+
133
+ def on_unsolicited(line)
134
+ if line =~ /^\+([A-Z]+): (.*)$/
135
+ type, body = $1, $2
136
+ else
137
+ return
138
+ end
139
+
140
+ list = []
141
+ chunk = ""
142
+ state = :void
143
+
144
+ body.chars do |char|
145
+ case state
146
+ when :void
147
+ case char
148
+ when ','
149
+ list << chunk
150
+ chunk = ""
151
+
152
+ when '"'
153
+ state = :string
154
+
155
+ else
156
+ chunk << char
157
+ end
158
+
159
+ when :string
160
+ case char
161
+ when '"'
162
+ state = :void
163
+
164
+ when '\\'
165
+ state = :escape
166
+
167
+ else
168
+ chunk << char
169
+ end
170
+
171
+ when :escape
172
+ chunk << char
173
+ state = :string
174
+ end
175
+ end
176
+
177
+ list << chunk
178
+
179
+ @unsolicited[type].each do |receiver|
180
+ receiver.unsolicited type, list
181
+ end
182
+ end
183
+
184
+ def complete_first(error = nil)
185
+ response = ModemCommandResponse.new(error, @response)
186
+ request = @command_queue.shift
187
+ @response = []
188
+
189
+ request.complete response
190
+ end
191
+ end
192
+ end
@@ -0,0 +1,26 @@
1
+ module CMUX
2
+ class ModemCommand
3
+ attr_reader :state, :command, :timeout, :issued_at
4
+
5
+ def initialize(command, chatter, timeout, &block)
6
+ @command = command
7
+ @chatter = chatter
8
+ @timeout = timeout
9
+ @issued_at = DateTime.now.to_time
10
+ @block = block
11
+ @state = :queued
12
+ end
13
+
14
+ # modemchatter api
15
+
16
+ def start
17
+ @state = :executing
18
+ end
19
+
20
+ def complete(response)
21
+ @state = :complete
22
+
23
+ @block.call response unless @block.nil?
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,18 @@
1
+ module CMUX
2
+ class ModemCommandResponse
3
+ attr_reader :error, :response
4
+
5
+ def initialize(error, body)
6
+ @error = error
7
+ @response = body
8
+ end
9
+
10
+ def success?
11
+ @error.nil?
12
+ end
13
+
14
+ def failure?
15
+ !success?
16
+ end
17
+ end
18
+ end
data/lib/cmux/mux.rb ADDED
@@ -0,0 +1,69 @@
1
+ module CMUX
2
+ class MUX
3
+ def initialize(device)
4
+ begin
5
+ @connection = CMUX::Connection.new
6
+ @connection.open device
7
+
8
+ io = CMUX::IO.open_tty device
9
+ begin
10
+ # Magic!
11
+
12
+ # Modem will lose synchronization if previous session was interrupted
13
+ # in the middle of frame, and it will resync only at start of second
14
+ # frame.
15
+ 2.times do
16
+ # Send 'AT' to ensure proper autobaud after DTR rise in the AT mode.
17
+ io.write "AT\r"
18
+
19
+ # Terminate GSM 07.10 simple multiplexer.
20
+ # Will be ignored by modem in the AT mode.
21
+ io.write "\xf9\x03\xef\x05\xc3\x01\xf2\xf9"
22
+ io.flush
23
+
24
+ # Read any crap from the buffer
25
+ until ::IO.select([ io ], [], [], 0.2).nil?
26
+ io.read_nonblock 2048
27
+ end
28
+ end
29
+
30
+ chatter = CMUX::ModemChatter.new io
31
+
32
+ all_done = false
33
+
34
+ chatter.command("&D2+IPR=115200;+CMUX=0", 5) do |resp|
35
+ if resp.failure?
36
+ raise "CMUX failed: #{resp.error}"
37
+ else
38
+ all_done = true
39
+ end
40
+ end
41
+
42
+ until all_done
43
+ CMUX::ModemChatter.poll [ chatter ]
44
+ end
45
+ ensure
46
+ io.close
47
+ end
48
+
49
+ @connection.activate
50
+ rescue Exception => e
51
+ @connection.close
52
+
53
+ raise e
54
+ end
55
+ end
56
+
57
+ def close
58
+ @connection.close
59
+ end
60
+
61
+ def allocate(channel)
62
+ Channel.new @connection.open_port(channel), channel, self
63
+ end
64
+
65
+ def close_port(channel)
66
+ @connection.close_port channel
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,3 @@
1
+ module CMUX
2
+ VERSION = "0.0.1"
3
+ end
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cmux
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Sergey Gridasov
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-01-12 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: ffi
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ description: Ruby bindings for CMUX, GSM 07.10 multiplexer
31
+ email:
32
+ - grindars@gmail.com
33
+ executables: []
34
+ extensions: []
35
+ extra_rdoc_files: []
36
+ files:
37
+ - .gitignore
38
+ - Gemfile
39
+ - LICENSE
40
+ - README.md
41
+ - Rakefile
42
+ - cmux.gemspec
43
+ - lib/cmux.rb
44
+ - lib/cmux/channel.rb
45
+ - lib/cmux/connection.rb
46
+ - lib/cmux/io.rb
47
+ - lib/cmux/library.rb
48
+ - lib/cmux/modem_chatter.rb
49
+ - lib/cmux/modem_command.rb
50
+ - lib/cmux/modem_command_response.rb
51
+ - lib/cmux/mux.rb
52
+ - lib/cmux/version.rb
53
+ homepage: ''
54
+ licenses: []
55
+ post_install_message:
56
+ rdoc_options: []
57
+ require_paths:
58
+ - lib
59
+ required_ruby_version: !ruby/object:Gem::Requirement
60
+ none: false
61
+ requirements:
62
+ - - ! '>='
63
+ - !ruby/object:Gem::Version
64
+ version: '0'
65
+ required_rubygems_version: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ! '>='
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ requirements: []
72
+ rubyforge_project:
73
+ rubygems_version: 1.8.24
74
+ signing_key:
75
+ specification_version: 3
76
+ summary: Ruby bindings for CMUX, GSM 07.10 multiplexer
77
+ test_files: []