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.
- checksums.yaml +7 -0
- data/Gemfile +10 -0
- data/README.md +59 -0
- data/lib/train-serial.rb +7 -0
- data/lib/train-serial/connection.rb +132 -0
- data/lib/train-serial/transport.rb +32 -0
- data/lib/train-serial/version.rb +5 -0
- data/train-serial.gemspec +24 -0
- metadata +79 -0
checksums.yaml
ADDED
@@ -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
data/README.md
ADDED
@@ -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` |
|
data/lib/train-serial.rb
ADDED
@@ -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,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: []
|