train-serial 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 192c2f9e6fa2af8ac0c3aede2f018333bbb13cf623258aebde8e19fe311ac836
4
+ data.tar.gz: 6506c0c27a00f789a20e40ab6cb73e2fbd14b0bae667e61eafe2b51fb4eeab11
5
+ SHA512:
6
+ metadata.gz: e223acc177c1a39595cf1f09ea301293509e1884cf10c0b08004172fb8034695f2c603a096f0e799286ed3da4f79490fb116eba6fe91ed7f749200c90520cc63
7
+ data.tar.gz: 1fbb7448ef03448ad839ffe314985116cb54ee49ca70abd8d4c238fa1f7705f7b7d592d85e8dd4843aa8e1b03ba44a71a6a14a0625aebac2408c7a838c972b38
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ group :development do
6
+ gem "pry"
7
+ gem "bundler"
8
+ gem "rake"
9
+ gem "chefstyle"
10
+ end
@@ -0,0 +1,59 @@
1
+ # train-serial - Train Plugin for connecting to serial interfaces
2
+
3
+ This plugin allows applications that rely on Train to communicate with devices
4
+ connected over serial interfaces. This could be the console port of network gear
5
+ or embedded systems.
6
+
7
+ ## Installation
8
+
9
+ You will have to build this gem yourself to install it as it is not yet on
10
+ Rubygems.Org. For this there is a rake task which makes this a one-liner:
11
+
12
+ ```bash
13
+ rake install:local
14
+ ```
15
+
16
+ ## Transport parameters
17
+
18
+ | Option | Explanation | Default |
19
+ | ---------------- | ---------------------------- | ---------------- |
20
+ | `device` | Device to connect to | `/dev/ttyUSB0` |
21
+ | `baud` | Baud rate | `9600` |
22
+ | `data_bits` | Data Bits for connection | `8` |
23
+ | `parity` | Parity (:none, :even, :odd) | `:none` |
24
+ | `stop_bits` | Stop Bits for connection | `1` |
25
+ | `buffer_size` | Number of bytes per read | `1024` |
26
+ | `buffer_wait` | Wait between reads | `250` |
27
+ | `buffer_retries` | Retries until stopping reads | `3` |
28
+ | `setup` | Commands to issue on open | |
29
+ | `teardown` | Commands to issue on close | |
30
+ | `raw_output` | Suppress stdout processing | `false` |
31
+ | `error_pattern` | Regex to match error lines | `ERROR.*` |
32
+ | `prompt_pattern` | Regex to match device prompt | `[-a-zA-Z0-9]+(?:\((?:config\|config-[a-z]+\|vlan)\))?[#>]\s*$` |
33
+
34
+ ## Example use
35
+
36
+ This will work for some Cisco devices on /dev/ttyUSB0:
37
+ ```ruby
38
+ require 'train'
39
+ train = Train.create('serial', {
40
+ device: '/dev/ttyUSB0',
41
+ setup: %Q[
42
+ enable
43
+ #{ENV['ENABLE_PASSWORD']}
44
+ terminal pager 0
45
+ ],
46
+ teardown: "disable",
47
+ logger: Logger.new($stdout, level: :info)
48
+ })
49
+ conn = train.connection
50
+ result = conn.run_command("show version\n")
51
+ conn.close
52
+ ```
53
+
54
+ ## Useful Setup/Teardown Patterns
55
+
56
+ | Device | Setup | Teardown |
57
+ | -------------- | ----------------------------------------------------------- | --------- |
58
+ | Cisco Catalyst | `enable\nterminal length 0\n` | `disable` |
59
+ | Cisco ASA | `enable\n#{ENV['ENABLE_PASSWORD']}\nterminal pager 0\n` | `disable` |
@@ -0,0 +1,7 @@
1
+ libdir = File.dirname(__FILE__)
2
+ $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
3
+
4
+ require "train-serial/version"
5
+
6
+ require "train-serial/transport"
7
+ require "train-serial/connection"
@@ -0,0 +1,132 @@
1
+ require "rubyserial"
2
+ require "train"
3
+
4
+ module TrainPlugins
5
+ module Serial
6
+ class Connection < Train::Plugins::Transport::BaseConnection
7
+
8
+ def initialize(options)
9
+ super(options)
10
+ end
11
+
12
+ def close
13
+ return if @session.nil?
14
+
15
+ unless @options[:teardown].empty?
16
+ logger.debug format("[Serial] Sending teardown command to %s", @options[:device])
17
+ execute_on_channel(@options[:teardown])
18
+ end
19
+
20
+ logger.info format("[Serial] Closed connection %s", @options[:device])
21
+ session.close
22
+ ensure
23
+ @session = nil
24
+ end
25
+
26
+ def uri
27
+ "serial://#{@options[:port]}/#{@options[:baud]}"
28
+ end
29
+
30
+ def run_command_via_connection(cmd, &data_handler)
31
+ reset_session if session.closed?
32
+
33
+ logger.debug format("[Serial] Sending command to %s", @options[:device])
34
+ exit_status, stdout, stderr = execute_on_channel(cmd, &data_handler)
35
+
36
+ CommandResult.new(stdout, stderr, exit_status)
37
+ end
38
+
39
+ def execute_on_channel(cmd, &data_handler)
40
+ stdout = ""
41
+ stderr = ""
42
+ exit_status = 0
43
+
44
+ if @options[:debug_serial]
45
+ logger.debug "[Serial] => #{cmd}\n"
46
+ end
47
+
48
+ session.write(cmd + "\n")
49
+
50
+ retries = 0
51
+ loop do
52
+ chunk = session.read(@options[:buffer_size])
53
+ if chunk.empty?
54
+ if @options[:debug_serial]
55
+ logger.debug format("[Serial] Buffering on %s (attempt %d/%d, %d bytes received)", @options[:device], retries + 1, @options[:buffer_retries], stdout.size)
56
+ end
57
+ retries += 1
58
+ sleep @options[:buffer_wait] / 1000.0
59
+ else
60
+ retries = 0
61
+ end
62
+ break if retries >= @options[:buffer_retries]
63
+
64
+ stdout << chunk
65
+ end
66
+
67
+ # Remove \r in linebreaks
68
+ stdout.delete!("\r")
69
+
70
+ if @options[:debug_serial]
71
+ logger.debug "[Serial] <= '#{stdout}'"
72
+ end
73
+
74
+ # Extract command output only (no leading/trailing prompts)
75
+ unless @options[:raw_output]
76
+ stdout = stdout.match(/#{Regexp.quote(cmd.strip)}\n(.*?)\n#{@options[:prompt_pattern]}/m)&.captures&.first
77
+ end
78
+ stdout = "" if stdout.nil?
79
+
80
+ # Simulate exit code and stderr
81
+ errors = stdout.match(/^(#{@options[:error_pattern]})/)
82
+ if errors
83
+ exit_status = 1
84
+ stderr = errors.captures.first
85
+ stdout.gsub!(/^#{@options[:error_pattern]}/, "")
86
+ end
87
+
88
+ [exit_status, stdout, stderr]
89
+ end
90
+
91
+ def session(retry_options = {})
92
+ @session ||= create_session
93
+ end
94
+
95
+ def create_session
96
+ logger.info format("[Serial] Opening connection %s (%s)", @options[:device], readable_config)
97
+ @session = ::Serial.new(
98
+ @options[:device],
99
+ @options[:baud],
100
+ @options[:data_bits],
101
+ @options[:parity],
102
+ @options[:stop_bits]
103
+ )
104
+
105
+ unless @options[:setup].empty?
106
+ logger.debug format("[Serial] Sending setup command to %s", @options[:device])
107
+
108
+ execute_on_channel(@options[:setup])
109
+ end
110
+
111
+ @session
112
+ end
113
+
114
+ def reset_session
115
+ session.close unless session.nil?
116
+ @session = nil
117
+ end
118
+
119
+ def readable_config
120
+ parity = "N"
121
+ parity = "E" if @options[:parity] == :even
122
+ parity = "O" if @options[:parity] == :odd
123
+
124
+ format("%d %d%s%d",
125
+ @options[:baud],
126
+ @options[:data_bits],
127
+ parity,
128
+ @options[:stop_bits])
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,32 @@
1
+ require "train-serial/connection"
2
+
3
+ module TrainPlugins
4
+ module Serial
5
+ class Transport < Train.plugin(1)
6
+ name "serial"
7
+
8
+ option :device, default: "/dev/ttyUSB0"
9
+ option :baud, default: 9600
10
+ option :data_bits, default: 8
11
+ option :parity, default: :none
12
+ option :stop_bits, default: 1
13
+
14
+ option :buffer_size, default: 1024
15
+ option :buffer_wait, default: 250
16
+ option :buffer_retries, default: 3
17
+
18
+ option :setup, default: ""
19
+ option :teardown, default: ""
20
+ option :error_pattern, default: "ERROR: .*$"
21
+ option :raw_output, default: false
22
+ option :prompt_pattern, default: "[-a-zA-Z0-9]+(?:\((?:config|config-[a-z]+|vlan)\))?[#>]\s*$"
23
+
24
+ # Non documented options for development
25
+ option :debug_serial, default: false
26
+
27
+ def connection(_instance_opts = nil)
28
+ @connection ||= TrainPlugins::Serial::Connection.new(@options)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,5 @@
1
+ module TrainPlugins
2
+ module Serial
3
+ VERSION = "0.2.0".freeze
4
+ end
5
+ end
@@ -0,0 +1,24 @@
1
+ lib = File.expand_path("../lib", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "train-serial/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "train-serial"
7
+ spec.version = TrainPlugins::Serial::VERSION
8
+ spec.authors = ["Thomas Heinen"]
9
+ spec.email = ["theinen@tecracer.de"]
10
+ spec.summary = "Train Transport for serial/USB interfaces"
11
+ spec.description = "Allows applications using Train to speak to serial interaces, like console ports"
12
+ spec.homepage = "https://github.com/tecracer_theinen/train-serial"
13
+ spec.license = "Apache-2.0"
14
+
15
+ spec.files = %w{
16
+ README.md train-serial.gemspec Gemfile
17
+ } + Dir.glob(
18
+ "lib/**/*", File::FNM_DOTMATCH
19
+ ).reject { |f| File.directory?(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_dependency "train", "~> 2.0"
23
+ spec.add_dependency "rubyserial", "~> 0.6"
24
+ end
metadata ADDED
@@ -0,0 +1,79 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: train-serial
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Thomas Heinen
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-08-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: train
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rubyserial
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.6'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.6'
41
+ description: Allows applications using Train to speak to serial interaces, like console
42
+ ports
43
+ email:
44
+ - theinen@tecracer.de
45
+ executables: []
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - Gemfile
50
+ - README.md
51
+ - lib/train-serial.rb
52
+ - lib/train-serial/connection.rb
53
+ - lib/train-serial/transport.rb
54
+ - lib/train-serial/version.rb
55
+ - train-serial.gemspec
56
+ homepage: https://github.com/tecracer_theinen/train-serial
57
+ licenses:
58
+ - Apache-2.0
59
+ metadata: {}
60
+ post_install_message:
61
+ rdoc_options: []
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ requirements: []
75
+ rubygems_version: 3.0.3
76
+ signing_key:
77
+ specification_version: 4
78
+ summary: Train Transport for serial/USB interfaces
79
+ test_files: []