train-serial 0.2.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,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: []