multi_movingsign 0.0.1
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.
- data/.gitignore +21 -0
- data/.rspec +2 -0
- data/.travis.yml +9 -0
- data/.yardopts +1 -0
- data/CHANGELOG.md +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/PAGE_DEFINITION.md +115 -0
- data/README.md +133 -0
- data/Rakefile +1 -0
- data/bin/multi_movingsign +5 -0
- data/example.jpg +0 -0
- data/fonts/7-row-normal.png +0 -0
- data/fonts/README.md +1 -0
- data/lib/multi_movingsign.rb +9 -0
- data/lib/multi_movingsign/cli.rb +81 -0
- data/lib/multi_movingsign/errors.rb +10 -0
- data/lib/multi_movingsign/page_renderer.rb +223 -0
- data/lib/multi_movingsign/server.rb +317 -0
- data/lib/multi_movingsign/settings.rb +55 -0
- data/lib/multi_movingsign/sign.rb +37 -0
- data/lib/multi_movingsign/signs.rb +39 -0
- data/lib/multi_movingsign/testrc_loader.rb +17 -0
- data/lib/multi_movingsign/version.rb +3 -0
- data/multi_movingsign.gemspec +31 -0
- data/spec/cli_spec.rb +166 -0
- data/spec/noop_movingsign_sign.rb +47 -0
- data/spec/noop_movingsign_sign.yml +7 -0
- data/spec/page_renderer/example_1/1.yml +16 -0
- data/spec/page_renderer/example_1/2.yml +28 -0
- data/spec/page_renderer/example_1/4.yml +44 -0
- data/spec/page_renderer/example_1/5.json +10 -0
- data/spec/page_renderer/example_1/example_spec.rb +23 -0
- data/spec/page_renderer/example_1/page.yml +27 -0
- data/spec/page_renderer/example_2/1.yml +7 -0
- data/spec/page_renderer/example_2/2.yml +8 -0
- data/spec/page_renderer/example_2/4.json +9 -0
- data/spec/page_renderer/example_2/example_spec.rb +24 -0
- data/spec/page_renderer/example_2/page.yml +7 -0
- data/spec/page_renderer/example_3/1.yml +9 -0
- data/spec/page_renderer/example_3/3.yml +14 -0
- data/spec/page_renderer/example_3/example_spec.rb +22 -0
- data/spec/page_renderer/example_3/page.yml +12 -0
- data/spec/page_renderer/example_4/2.yml +12 -0
- data/spec/page_renderer/example_4/4.json +9 -0
- data/spec/page_renderer/example_4/example_spec.rb +24 -0
- data/spec/page_renderer/example_4/page.yml +12 -0
- data/spec/settings_1.yml +3 -0
- data/spec/settings_spec.rb +36 -0
- data/spec/spec_helper.rb +36 -0
- data/spec/support/doubles_support.rb +26 -0
- data/spec/support/executable_support.rb +112 -0
- metadata +244 -0
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'hashie'
|
3
|
+
require 'multi_movingsign/sign'
|
4
|
+
|
5
|
+
module MultiMovingsign
|
6
|
+
# Settings wrapper (reads/saves from/to YAML file via {load} / {#dump})
|
7
|
+
class Settings
|
8
|
+
attr_accessor :mash
|
9
|
+
|
10
|
+
# Constructs a new {Settings} instance from settings saves in the specified YAML file
|
11
|
+
def self.load(path)
|
12
|
+
if File.exists? path
|
13
|
+
self.new YAML.load(File.read(path))
|
14
|
+
else
|
15
|
+
self.new({})
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(hash = {})
|
20
|
+
self.mash = Hashie::Mash.new hash
|
21
|
+
end
|
22
|
+
|
23
|
+
# Returns an array of the configured {Sign}s
|
24
|
+
def signs
|
25
|
+
self.mash.signs ||= []
|
26
|
+
|
27
|
+
self.mash.signs.map { |hash| Sign.load(hash) }
|
28
|
+
end
|
29
|
+
|
30
|
+
# +true+ if any signs are configured
|
31
|
+
def signs?
|
32
|
+
! self.signs.empty?
|
33
|
+
end
|
34
|
+
|
35
|
+
# Sets the list of conifgured {Sign}s via an array of serial port paths
|
36
|
+
#
|
37
|
+
# @example
|
38
|
+
# settings.sign_paths = ['/dev/ttyUSB0', '/dev/ttyUSB1']
|
39
|
+
def sign_paths=(paths)
|
40
|
+
self.mash.signs = paths.map { |path| {'path' => path} }
|
41
|
+
end
|
42
|
+
|
43
|
+
# Serializes (dumps) the settings into the specified YAML file
|
44
|
+
def dump(path)
|
45
|
+
File.open(path, 'w') do |f|
|
46
|
+
f.write(self.mash.to_hash.to_yaml)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Default path for the settings YAML file
|
51
|
+
def self.default_settings_path
|
52
|
+
File.join(ENV['HOME'], '.multi_movingsign.yml')
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'movingsign_api'
|
2
|
+
require 'multi_movingsign/sign'
|
3
|
+
|
4
|
+
module MultiMovingsign
|
5
|
+
# Represents an individual Movingsign LED sign being driven
|
6
|
+
class Sign
|
7
|
+
attr_accessor :path
|
8
|
+
|
9
|
+
def initialize(path)
|
10
|
+
self.path = path
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.load(hash)
|
14
|
+
path = hash['path'] || (raise InvalidInputError, "path key not specified")
|
15
|
+
|
16
|
+
self.new path
|
17
|
+
end
|
18
|
+
|
19
|
+
def show_text(text, options = {})
|
20
|
+
sign.show_text text, options
|
21
|
+
end
|
22
|
+
|
23
|
+
def set_sound(on)
|
24
|
+
sign.set_sound on
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_hash
|
28
|
+
{'path' => self.path}
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def sign
|
34
|
+
MovingsignApi::Sign.new self.path
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'multi_movingsign/sign'
|
2
|
+
|
3
|
+
module MultiMovingsign
|
4
|
+
# Represents a collection of {Sign}s that you want to drive in unison
|
5
|
+
class Signs
|
6
|
+
attr_accessor :signs
|
7
|
+
|
8
|
+
# @param options [Array<Sign>] an array of {Sign} instances
|
9
|
+
def initialize(options)
|
10
|
+
if options.kind_of?(Array) && options.all? { |v| v.kind_of?(Sign) }
|
11
|
+
self.signs = options
|
12
|
+
else
|
13
|
+
raise InvalidInputError, "Invalid Signs#new options"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Sends/show the specified page solution on the signs
|
18
|
+
def show_page_solution(solution)
|
19
|
+
threads = []
|
20
|
+
solution['signs'].each_with_index do |hash, i|
|
21
|
+
threads << Thread.new do
|
22
|
+
# Prepend "\xFDB" to set color to HIGH RED
|
23
|
+
# .gsub("\n", "\x7F") - replace new lines with the sign specific newline character
|
24
|
+
text = "\xFDB" + hash['content'].gsub("\n", "\x7F")
|
25
|
+
self.signs[i].show_text text, :display_pause => 3
|
26
|
+
end
|
27
|
+
end
|
28
|
+
threads.each { |t| t.join }
|
29
|
+
end
|
30
|
+
|
31
|
+
def show_identity
|
32
|
+
self.signs.each_with_index { |s, i| s.show_text "#{i} #{s.path}" }
|
33
|
+
end
|
34
|
+
|
35
|
+
def length
|
36
|
+
self.signs.length
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
# Helper that reads a test YAML file and loads/executes the appropriate scripts
|
4
|
+
# {file:spec/noop_movingsign_sign.yml}
|
5
|
+
class TestRCLoader
|
6
|
+
def self.load(testrc_path)
|
7
|
+
base_dir = File.expand_path(File.dirname testrc_path)
|
8
|
+
testrc_hash = YAML::load(File.read(testrc_path))
|
9
|
+
|
10
|
+
script_path = testrc_hash['script']
|
11
|
+
script_path = File.join(base_dir, script_path) if Pathname.new(script_path).relative?
|
12
|
+
|
13
|
+
parameters = testrc_hash['parameters'] || {}
|
14
|
+
|
15
|
+
Kernel.eval(File.read(script_path), binding, script_path)
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'multi_movingsign/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "multi_movingsign"
|
8
|
+
spec.version = MultiMovingsign::VERSION
|
9
|
+
spec.authors = ["Eric Webb"]
|
10
|
+
spec.email = ["eric@collectivegenius.net"]
|
11
|
+
spec.description = "Code that drives multiple MovingSign display boards in unison."
|
12
|
+
spec.summary = "Code that drives multiple MovingSign display boards in unison."
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
.reject { |path| path.match /\A\.idea\// } # Ignore .idea/ IntelliJ project directory
|
18
|
+
.reject { |path| path.match /\A[^\/]+\.iml/ } # Ignore *.iml IntelliJ module file
|
19
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
20
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
21
|
+
spec.require_paths = ["lib"]
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
24
|
+
spec.add_development_dependency "rake"
|
25
|
+
spec.add_development_dependency "rspec"
|
26
|
+
spec.add_development_dependency "wwtd"
|
27
|
+
|
28
|
+
spec.add_runtime_dependency 'thor', '~> 0.18.1'
|
29
|
+
spec.add_runtime_dependency 'hashie', '~> 2.0'
|
30
|
+
spec.add_runtime_dependency 'movingsign_api', '0.0.2'
|
31
|
+
end
|
data/spec/cli_spec.rb
ADDED
@@ -0,0 +1,166 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'tempfile'
|
3
|
+
require 'timeout'
|
4
|
+
|
5
|
+
describe MultiMovingsign::Cli do
|
6
|
+
let(:temp_settings_file) { File.join(Dir.mktmpdir('test-settings-'), 'test-settings.yml') }
|
7
|
+
let(:multi_movingsign_exe) { Gem.bin_path('multi_movingsign', 'multi_movingsign') }
|
8
|
+
|
9
|
+
# Testing magic, @see TestRCLoader and {file:spec/noop_movingsign_sign.rb}
|
10
|
+
let(:testrc_noop) { "spec/noop_movingsign_sign.yml" }
|
11
|
+
let(:testrc_results) { Hashie::Mash.new YAML::load(File.read('testrc.yml')) }
|
12
|
+
|
13
|
+
let(:setup_command_results) { execute("#{multi_movingsign_exe} setup --rc #{temp_settings_file} --signs /dev/ttyUSB0") }
|
14
|
+
|
15
|
+
describe "'setup' command" do
|
16
|
+
it "No Options" do
|
17
|
+
expect(execute("#{multi_movingsign_exe} setup")).to be_success
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "--signs /dev/ttyUSB0" do
|
21
|
+
let (:execution_results) { execute("#{multi_movingsign_exe} setup --rc #{temp_settings_file} --signs /dev/ttyUSB0") }
|
22
|
+
let (:hash) { YAML.load(File.read(temp_settings_file)) }
|
23
|
+
|
24
|
+
before(:each) do
|
25
|
+
execution_results
|
26
|
+
end
|
27
|
+
|
28
|
+
it "Succeeds" do
|
29
|
+
expect(execution_results).to be_success
|
30
|
+
end
|
31
|
+
|
32
|
+
it "Settings File Exists" do
|
33
|
+
expect(File.exists? temp_settings_file).to be_true
|
34
|
+
end
|
35
|
+
|
36
|
+
it "Settings File Contains ttyUSB0" do
|
37
|
+
expect(File.read(temp_settings_file)).to include 'ttyUSB0'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "'settings' command" do
|
43
|
+
let (:settings_command_results) { execute("#{multi_movingsign_exe} settings --rc #{temp_settings_file}") }
|
44
|
+
|
45
|
+
describe "Without Setup" do
|
46
|
+
before(:each) do
|
47
|
+
settings_command_results
|
48
|
+
end
|
49
|
+
|
50
|
+
it "Succeeds" do
|
51
|
+
expect(settings_command_results).to be_success
|
52
|
+
end
|
53
|
+
|
54
|
+
it "Containts 'Signs (0)'" do
|
55
|
+
expect(settings_command_results.stdout).to include 'Signs (0)'
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "With Setup" do
|
60
|
+
before(:each) do
|
61
|
+
setup_command_results
|
62
|
+
settings_command_results
|
63
|
+
end
|
64
|
+
|
65
|
+
it "Succeeds" do
|
66
|
+
expect(settings_command_results).to be_success
|
67
|
+
end
|
68
|
+
|
69
|
+
it "Containts 'ttyUSB0'" do
|
70
|
+
expect(settings_command_results.stdout).to include 'ttyUSB0'
|
71
|
+
end
|
72
|
+
|
73
|
+
it "Containts 'Signs (1)'" do
|
74
|
+
expect(settings_command_results.stdout).to include 'Signs (1)'
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe "'show-identity' command" do
|
80
|
+
let (:show_identity_command_results) { execute("#{multi_movingsign_exe} show-identity --rc #{temp_settings_file} --testrc #{testrc_noop}") }
|
81
|
+
|
82
|
+
before(:each) do
|
83
|
+
setup_command_results
|
84
|
+
|
85
|
+
double_movingsign_sign
|
86
|
+
show_identity_command_results
|
87
|
+
end
|
88
|
+
|
89
|
+
it "Succeeds" do
|
90
|
+
expect_command_success show_identity_command_results
|
91
|
+
end
|
92
|
+
|
93
|
+
it "Signs Calls == 1" do
|
94
|
+
expect(testrc_results.calls.length).to eq 1
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe "'server' subcommand" do
|
99
|
+
let (:server_configuration_directory) { Dir.mktmpdir "multi_movingsign_server-" }
|
100
|
+
let (:server_thread) { thread = Thread.new { execute("#{multi_movingsign_exe} server start --rc #{temp_settings_file} --serverrc #{server_configuration_directory} --testrc #{testrc_noop}") }; sleep 1; thread }
|
101
|
+
|
102
|
+
before(:each) do
|
103
|
+
setup_command_results
|
104
|
+
end
|
105
|
+
|
106
|
+
describe "'start' command" do
|
107
|
+
before(:each) do
|
108
|
+
double_movingsign_sign
|
109
|
+
server_thread
|
110
|
+
end
|
111
|
+
|
112
|
+
after(:each) do
|
113
|
+
server_thread.kill
|
114
|
+
server_thread.join
|
115
|
+
end
|
116
|
+
|
117
|
+
it "Is Alive?" do
|
118
|
+
expect(server_thread).to be_alive
|
119
|
+
end
|
120
|
+
|
121
|
+
it "Creates PID File" do
|
122
|
+
expect(File.exists? File.join(server_configuration_directory, 'server.lock')).to be_true
|
123
|
+
end
|
124
|
+
|
125
|
+
it "Creates SOCK File" do
|
126
|
+
expect(File.exists? File.join(server_configuration_directory, 'server.sock')).to be_true
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
describe "'stop' command" do
|
131
|
+
let (:server_stop_results) { execute "#{multi_movingsign_exe} server stop --rc #{temp_settings_file} --serverrc #{server_configuration_directory}" }
|
132
|
+
|
133
|
+
before(:each) do
|
134
|
+
double_movingsign_sign
|
135
|
+
server_thread
|
136
|
+
|
137
|
+
timeout(60) { server_stop_results }
|
138
|
+
end
|
139
|
+
|
140
|
+
after(:each) do
|
141
|
+
server_thread.kill
|
142
|
+
server_thread.join
|
143
|
+
end
|
144
|
+
|
145
|
+
it "Shuts Down Server" do
|
146
|
+
sleep 3
|
147
|
+
|
148
|
+
expect(server_thread).to_not be_alive
|
149
|
+
expect(server_stop_results).to be_success
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
pending "'add-page' command"
|
154
|
+
pending "'delete-page' command"
|
155
|
+
pending "'alert' command"
|
156
|
+
end
|
157
|
+
|
158
|
+
#it "show-page command"
|
159
|
+
#it "show-page succeeds"
|
160
|
+
#it "show-page sends show_text on each display"
|
161
|
+
#it "server"
|
162
|
+
#multi_movingsign server add-page --name=NAME --page=PAGE # Adds a page to the server rotation
|
163
|
+
#multi_movingsign server alert --page=PAGE # Sends a page to display as an alert
|
164
|
+
#multi_movingsign server delete-page --name=NAME # Deletes a page to the server rotation
|
165
|
+
#multi_movingsign server help [COMMAND] # Describe subcommands or one specific subcommand
|
166
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
#
|
2
|
+
# Loaded by noop_movingsign_sign.yml
|
3
|
+
#
|
4
|
+
# This script makes all MovingsignApi::Sign methods a noop, allowing you to run/test/validate the CLI
|
5
|
+
# without having physical LED signs plugged in.
|
6
|
+
#
|
7
|
+
require 'movingsign_api/sign'
|
8
|
+
|
9
|
+
klass = Class.new do
|
10
|
+
def double_movingsign_sign
|
11
|
+
MovingsignApi::Sign.class_exec do
|
12
|
+
@@mutex = Mutex.new
|
13
|
+
|
14
|
+
class << self
|
15
|
+
attr_accessor :calls
|
16
|
+
end
|
17
|
+
|
18
|
+
(MovingsignApi::Sign.public_instance_methods.to_a - Object.public_instance_methods.to_a - [:device_path]).each do |method|
|
19
|
+
define_method(method) do |*args|
|
20
|
+
@@mutex.synchronize do
|
21
|
+
# sanitize arguments (since they contain invalid characters)
|
22
|
+
sanitized_args = []
|
23
|
+
args.each do |arg|
|
24
|
+
if arg.kind_of? String
|
25
|
+
sanitized_args << arg.encode("ASCII", :invalid => :replace, :undef => :replace, :replace => "?")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
self.calls << [self.device_path, method, sanitized_args]
|
30
|
+
|
31
|
+
File.open('testrc.yml', 'w') do |f|
|
32
|
+
f.write({'calls' => self.calls}.to_yaml)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def calls
|
39
|
+
self.class.calls
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
MovingsignApi::Sign.calls = []
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
klass.new.double_movingsign_sign
|
@@ -0,0 +1,28 @@
|
|
1
|
+
lines: 12
|
2
|
+
signs:
|
3
|
+
- content: |-
|
4
|
+
Top Drinkers
|
5
|
+
Top Drinkers
|
6
|
+
Top Drinkers
|
7
|
+
Top Drinkers
|
8
|
+
Top Drinkers
|
9
|
+
Top Drinkers
|
10
|
+
Top Drinkers
|
11
|
+
Top Drinkers
|
12
|
+
Top Drinkers
|
13
|
+
Top Drinkers
|
14
|
+
Top Drinkers
|
15
|
+
Top Drinkers
|
16
|
+
- content: |-
|
17
|
+
1. mikey
|
18
|
+
1. 445 pints
|
19
|
+
2. capn
|
20
|
+
2. 183 pints
|
21
|
+
3. eric
|
22
|
+
3. 122 pints
|
23
|
+
4. whitey
|
24
|
+
4. 114 pints
|
25
|
+
5. justin
|
26
|
+
5. 110 pints
|
27
|
+
6. scarfjerk
|
28
|
+
6. 109 pints
|