bolt_train_runner 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c5b930e3262ec9ac6eb6989949bbdb71e9ceefecad32ef7abfad03486bef32d0
4
+ data.tar.gz: 7c5ea7ff98b82eadc1c3111e67c425abb6e43ae518b66ee2c996fa14f27368da
5
+ SHA512:
6
+ metadata.gz: 5a29ee08a95f7c76bef3972a9595b344f1d04cc511fa7ce733a030350e0a7bc82fb1d83d50c3e3ee9c125ca4eba0aa7e43b188200f31175c5f054a93e5e54a9f
7
+ data.tar.gz: 2e7bf384b4e5cba13285c1972fe7fa1c80efa299ac055cee2d1d594aac8be9435ee14d2589314fe882a65e3a43d4dbfc8800579f5a6893d83f7fa3cbb0e1517a
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 Nick Burgan-Illig
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,35 @@
1
+ # BoltTrainRunner
2
+
3
+ This is a CLI tool for communicating with the Bolt Train JMRI JSON server. It will allow you to control the Bolt train in real time. It will also serve as the executor, reading session files laid down by the bolt-train-api server, and sending the appropriate JMRI commands to implement the commands in those session files.
4
+
5
+ ## Installation
6
+
7
+ This is packaged as a gem on RubyGems. So all you need to do to install is:
8
+
9
+ ```ruby
10
+ gem install bolt_train_runner
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ To start the program, simply run `bolt_train` from the command line. This will start the bolt-train-runner shell. Type `help` for a list of commands, and `<command> help` for information on how to use each command.
16
+
17
+ The first command you must run before all others is `connect`. This will open a websocket connection to the JMRI JSON server. After successfully connecting, the server you connected to will be saved to `~/.bolttrain.conf`, so you will not need to type the server information in again.
18
+
19
+ Turning debug mode on by doing `debug on` will print the JSON blobs sent to and received from the the JMRI JSON server.
20
+
21
+ ## Development
22
+
23
+ After checking out the repo, do a `bundle install`. Then, you can do `bundle exec rake build` to create the gem in the `pkg` folder. You may then install this local copy of the gem using `gem install`.
24
+
25
+ Using `bundle exec rake install` will probably also work in place of the above steps.
26
+
27
+ To release a new version of the gem, do `bundle exec rake release`.
28
+
29
+ ## Contributing
30
+
31
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/bolt_train_runner.
32
+
33
+ ## License
34
+
35
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/bin/bolt_train ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
5
+
6
+ require 'bolt_train_runner'
7
+
8
+ BoltTrainRunner.new.run
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'colorize'
5
+ require 'bolt_train_runner/comms'
6
+
7
+ # Load all commands
8
+ Dir[File.join(File.absolute_path(__dir__) + '/bolt_train_runner/commands') + "/**/*.rb"].each do |file|
9
+ require file
10
+ end
11
+
12
+ # TODO:
13
+ # Add a lot more error handling
14
+ # Add thread for consuming session files and issuing commands
15
+ # Figure out best way to verify commands worked given that responses from JMRI are async
16
+ # Figure out why it gets multiple messages back each time
17
+ # Make debug output show what method/command the output is from
18
+ # Make debug output not screw with the console prompt
19
+ # Changing debug on or off and reconnecting doesn't actually seem to work to change debug level
20
+ # Add a way to log to a file in addition to the console
21
+
22
+
23
+ class BoltTrainRunner
24
+
25
+ def run
26
+ comms = nil
27
+ puts 'Welcome to the Bolty McBoltTrain Runner! Choo choo!'.cyan
28
+ puts 'To list all commands, enter "help"'.cyan
29
+ puts 'For help with a specific command, enter "<command> help"'.cyan
30
+ puts 'First order of business: run "connect" to connect to the JMRI server'.cyan
31
+ loop do
32
+ print '> '
33
+ input = gets.chomp
34
+ args = input.split(' ')
35
+ command = args.shift
36
+ case command
37
+ when /^help$/i
38
+ help
39
+ when /^connect$/i
40
+ newcomms = Commands.connect(args)
41
+ comms = newcomms if newcomms
42
+ when /^disconnect$/i
43
+ Commands.disconnect(comms)
44
+ comms = nil
45
+ when /^debug$/i
46
+ Commands.debug(args)
47
+ when /^power$/i
48
+ Commands.power(args, comms)
49
+ when /^throttle$/i
50
+ Commands.throttle(args, comms)
51
+ when /^move$/i
52
+ Commands.move(args, comms)
53
+ when /^exit$/i
54
+ Commands.exit_program(comms)
55
+ else
56
+ puts "Unknown command: #{command}".red
57
+ end
58
+ end
59
+ end
60
+
61
+ def help
62
+ puts 'Available commands:'.cyan
63
+ puts 'help - This help text'.cyan
64
+ puts 'connect - Connect to the Bolt Train JMRI JSON server'.cyan
65
+ puts 'disconnect - Disconnect from the JMRI server'.cyan
66
+ puts 'power - Turn power on or off to the train'.cyan
67
+ puts 'throttle - Set throttle to a value between 0 and 10'.cyan
68
+ puts 'move - Move the train in the given direction at the given speed for a certain length of time'.cyan
69
+ puts 'exit - Exit program'.cyan
70
+ end
71
+
72
+ end
@@ -0,0 +1,32 @@
1
+ require 'bolt_train_runner/conf'
2
+ require 'bolt_train_runner/comms'
3
+ require 'colorize'
4
+
5
+ module Commands
6
+ def self.connect(args)
7
+ conf = Conf.load_conf
8
+ if !args.empty? && args[0] =~ /help/i
9
+ puts 'Command: connect'.cyan
10
+ puts 'Syntax: connect [server:port]'.cyan
11
+ puts 'If called with no argument, it will attempt to use the last specified server:port'.cyan
12
+ puts 'If this is the first time connect was called with no arguments, it will prompt you for the server and port'.cyan
13
+ return
14
+ end
15
+ if args.empty?
16
+ server = conf['server']
17
+ unless server
18
+ print 'Please enter server:port [10.0.7.82:12080] > '
19
+ server = gets.chomp
20
+ server = '10.0.7.82:12080' if server.empty?
21
+ end
22
+ else
23
+ server = args[0]
24
+ end
25
+ conf['server'] = server
26
+ Conf.save_conf(conf)
27
+
28
+ puts "Connecting to ws://#{server}/json/".green
29
+ comms = Comms.new(server)
30
+ return comms
31
+ end
32
+ end
@@ -0,0 +1,24 @@
1
+ require 'colorize'
2
+ require 'bolt_train_runner/conf'
3
+
4
+ module Commands
5
+ def self.debug(args)
6
+ if args.empty? || args[0] =~ /help/i
7
+ puts 'Command: debug'.cyan
8
+ puts 'Syntax: debug <on|off>'.cyan
9
+ #Should fix this at some point
10
+ puts 'Turns debug logging on or off. Will require disconnecting and reconnecting to take effect.'.cyan
11
+ puts 'Choice will be persistent across program invocations.'.cyan
12
+ return
13
+ end
14
+ state = args[0]
15
+ if !['on','off'].include?(state)
16
+ puts 'Debug must be called with either "on" or "off"'.red
17
+ return
18
+ end
19
+ conf = Conf.load_conf
20
+ conf['debug'] = state == 'on'
21
+ Conf.save_conf(conf)
22
+ puts "Debug mode #{state}".green
23
+ end
24
+ end
@@ -0,0 +1,13 @@
1
+ require 'bolt_train_runner/comms'
2
+ require 'colorize'
3
+
4
+ module Commands
5
+ def self.disconnect(comms)
6
+ if comms.nil?
7
+ puts 'Not currently connected'.red
8
+ return
9
+ end
10
+ comms.disconnect
11
+ puts 'Disconnected'.green
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ require 'colorize'
2
+ require 'bolt_train_runner/comms'
3
+
4
+ module Commands
5
+ def self.exit_program(comms)
6
+ if comms
7
+ comms.disconnect
8
+ end
9
+ puts 'Seeya later!'.green
10
+ exit 0
11
+ end
12
+ end
@@ -0,0 +1,65 @@
1
+ require 'bolt_train_runner/comms'
2
+ require 'colorize'
3
+
4
+ module Commands
5
+ def self.move(args, comms)
6
+ unless comms
7
+ puts 'Please connect first'.red
8
+ return
9
+ end
10
+ if args.empty? || args[0] =~ /help/i
11
+ puts 'Command: move'.cyan
12
+ puts 'Syntax: move <forward|reverse> <speed 0-10> <time 1-60>'.cyan
13
+ puts 'Move the train the given direction at the given speed for the given number of seconds'.cyan
14
+ puts 'Note that the power must be on first'.cyan
15
+ return
16
+ end
17
+
18
+ if args.length < 3
19
+ puts 'Please provide direction, speed, and time'.red
20
+ return
21
+ end
22
+ direction = args[0]
23
+ speed = args[1].to_i
24
+ time = args[2].to_i
25
+ unless ['forward','reverse'].include?(direction)
26
+ puts 'Please provide "forward" or "reverse" for direction'.red
27
+ return
28
+ end
29
+ if speed < 0 or speed > 10
30
+ puts 'Please select a speed between 0 and 10'.red
31
+ return
32
+ end
33
+ if time < 1 or time > 60
34
+ puts 'Please select a time between 1 and 60 seconds'.red
35
+ return
36
+ end
37
+
38
+ message = {
39
+ 'type' => 'throttle',
40
+ 'method' => 'put',
41
+ 'data' => {
42
+ 'throttle' => 'bolttrain',
43
+ 'address' => '6871',
44
+ 'speed' => "#{speed/10.0}",
45
+ 'forward' => "#{direction == 'forward'}"
46
+ }
47
+ }
48
+ comms.send_message(message)
49
+ puts "Train moving #{direction} at speed #{speed}".green
50
+ puts "Waiting #{time} seconds".green
51
+ sleep(time)
52
+ puts "Stopping train".green
53
+ message = {
54
+ 'type' => 'throttle',
55
+ 'method' => 'post',
56
+ 'data' => {
57
+ 'throttle' => 'bolttrain',
58
+ 'address' => '6871',
59
+ 'speed' => '0'
60
+ }
61
+ }
62
+ comms.send_message(message)
63
+ puts 'Move complete'.green
64
+ end
65
+ end
@@ -0,0 +1,34 @@
1
+ require 'bolt_train_runner/comms'
2
+ require 'colorize'
3
+
4
+ module Commands
5
+ def self.power(args, comms)
6
+ unless comms
7
+ puts 'Please connect first'.red
8
+ return
9
+ end
10
+ if args.empty? || args[0] =~ /help/i
11
+ puts 'Command: power'.cyan
12
+ puts 'Syntax: power <on|off>'.cyan
13
+ puts 'Turns power to the train on or off. Must first be connected.'.cyan
14
+ return
15
+ end
16
+ state = args[0]
17
+ unless ['on','off'].include?(state)
18
+ puts 'Please provide either "on" or "off"'.red
19
+ return
20
+ end
21
+
22
+ vals = {'on' => '2', 'off' => '4'}
23
+ value = vals[state]
24
+ message = {
25
+ 'type' => 'power',
26
+ 'method' => 'put',
27
+ 'data' => {
28
+ 'name' => 'LocoNet',
29
+ 'state' => value
30
+ }
31
+ }
32
+ comms.send_message(message)
33
+ end
34
+ end
@@ -0,0 +1,46 @@
1
+ require 'bolt_train_runner/comms'
2
+ require 'colorize'
3
+
4
+ module Commands
5
+ def self.throttle(args, comms)
6
+ unless comms
7
+ puts 'Please connect first'.red
8
+ return
9
+ end
10
+ if args.empty? || args[0] =~ /help/i
11
+ puts 'Command: throttle'.cyan
12
+ puts 'Syntax: throttle <0-10> [forward|reverse]'.cyan
13
+ puts 'Sets the throttle to the given level, between 0 and 10. May optionally provide a direction.'.cyan
14
+ puts 'Note that power must be on first for this to take effect.'.cyan
15
+ return
16
+ end
17
+
18
+ speed = args[0].to_i
19
+ if speed < 0 or speed > 10
20
+ puts 'Please select a speed between 0 and 10'.red
21
+ return
22
+ end
23
+ direction = nil
24
+ if args.length > 1
25
+ direction = args[1]
26
+ unless ['forward','reverse'].include?(direction)
27
+ puts 'Direction must be either "forward" or "reverse"'.red
28
+ return
29
+ end
30
+ end
31
+
32
+ message = {
33
+ 'type' => 'throttle',
34
+ 'method' => 'post',
35
+ 'data' => {
36
+ 'throttle' => 'bolttrain',
37
+ 'address' => '6871',
38
+ 'speed' => "#{speed/10.0}",
39
+ }
40
+ }
41
+ message['data']['forward'] = direction == 'forward' if direction
42
+ comms.send_message(message)
43
+ direction_string = direction.nil? ? '' : " with direction #{direction}"
44
+ puts "Throttle set to #{speed}#{direction_string}".green
45
+ end
46
+ end
@@ -0,0 +1,65 @@
1
+ require 'websocket-client-simple'
2
+ require 'json'
3
+ require 'colorize'
4
+ require 'bolt_train_runner/conf'
5
+ require 'pry-byebug'
6
+
7
+ # Sending and receiving responses is a little bit funky
8
+ # Since we have to receive messages asynchronously, and because
9
+ # the server sometimes sends back more than one message to a given
10
+ # command, and because the server will send out status messages
11
+ # periodically, it is hard to verify that the command you sent
12
+ # was received correctly. So for now, it's just sending the command
13
+ # and not checking the result.
14
+
15
+ class Comms
16
+
17
+ @ws = nil
18
+ @heartbeat_thread = nil
19
+ @consumer_thread = nil
20
+ @kill_threads = false
21
+ @queue = []
22
+
23
+ def initialize(server)
24
+ debug = Conf.debug
25
+ @ws = WebSocket::Client::Simple.connect("ws://#{server}/json/")
26
+ @ws.on :message do |msg|
27
+ data = JSON.parse(msg.data)
28
+ puts "Received #{data}" if (data['type'] != 'hello') if debug
29
+ end
30
+ @ws.on :error do |e|
31
+ puts "Error from Websocket: #{e}".red
32
+ end
33
+
34
+ @heartbeat_thread = Thread.new { run_heartbeat }
35
+ end
36
+
37
+ def disconnect
38
+ @kill_threads = true
39
+ @heartbeat_thread.join if @heartbeat_thread
40
+ end
41
+
42
+ def run_heartbeat
43
+ count = 0
44
+ check_interval = 0.5
45
+ ping_interval = 10
46
+ while !@kill_threads
47
+ count = count % ping_interval
48
+ if count == 0
49
+ message = send_message({'type'=>'ping'})
50
+ end
51
+ count += check_interval
52
+ sleep(check_interval)
53
+ end
54
+ end
55
+
56
+ # Expects a hash with the correctly formed message, which
57
+ # will get transformed to a JSON string by this function
58
+ def send_message(message)
59
+ debug = Conf.debug
60
+ message = JSON.generate(message)
61
+ puts "Sending #{message}" if debug
62
+ @ws.send(message)
63
+ end
64
+
65
+ end
@@ -0,0 +1,21 @@
1
+ require 'yaml'
2
+
3
+ class Conf
4
+ attr_reader :CONFFILE
5
+
6
+ CONFFILE = File.expand_path('~/.bolttrain.conf')
7
+ File.write(CONFFILE, "---\n{}") unless File.exist?(CONFFILE)
8
+
9
+ def self.load_conf
10
+ YAML.load_file(CONFFILE)
11
+ end
12
+
13
+ def self.save_conf(conf)
14
+ File.write(CONFFILE, conf.to_yaml)
15
+ end
16
+
17
+ def self.debug
18
+ conf = load_conf
19
+ conf['debug'] || false
20
+ end
21
+ end
@@ -0,0 +1,3 @@
1
+ module BoltTrainRunner
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,145 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bolt_train_runner
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Nick Burgan
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-11-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.16'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.16'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry-byebug
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: websocket-client-simple
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: colorize
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: commander
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: This is a command line tool for talking to the Bolt Train JMRI JSON Server.
98
+ It includes a method for installing it as a service, working in concert with the
99
+ Bolt Train API server.
100
+ email:
101
+ - nickb@puppet.com
102
+ executables:
103
+ - bolt_train
104
+ extensions: []
105
+ extra_rdoc_files: []
106
+ files:
107
+ - LICENSE.txt
108
+ - README.md
109
+ - bin/bolt_train
110
+ - lib/bolt_train_runner.rb
111
+ - lib/bolt_train_runner/commands/connect.rb
112
+ - lib/bolt_train_runner/commands/debug.rb
113
+ - lib/bolt_train_runner/commands/disconnect.rb
114
+ - lib/bolt_train_runner/commands/exit.rb
115
+ - lib/bolt_train_runner/commands/move.rb
116
+ - lib/bolt_train_runner/commands/power.rb
117
+ - lib/bolt_train_runner/commands/throttle.rb
118
+ - lib/bolt_train_runner/comms.rb
119
+ - lib/bolt_train_runner/conf.rb
120
+ - lib/bolt_train_runner/version.rb
121
+ homepage: https://github.com/puppetlabs/bolt-train-runner
122
+ licenses:
123
+ - MIT
124
+ metadata: {}
125
+ post_install_message:
126
+ rdoc_options: []
127
+ require_paths:
128
+ - lib
129
+ required_ruby_version: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ required_rubygems_version: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ requirements: []
140
+ rubyforge_project:
141
+ rubygems_version: 2.7.6
142
+ signing_key:
143
+ specification_version: 4
144
+ summary: CLI/Service for talking to the Bolt Train JMRI JSON Server
145
+ test_files: []