whipped-cream 0.1.1 → 0.2.0.beta1
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/.travis.yml +3 -0
- data/CHANGELOG.md +7 -1
- data/lib/whipped-cream/cli.rb +51 -1
- data/lib/whipped-cream/deployer.rb +6 -0
- data/lib/whipped-cream/runner.rb +13 -11
- data/lib/whipped-cream/server.rb +15 -2
- data/lib/whipped-cream/version.rb +1 -1
- data/lib/whipped-cream/views/switch.erb +13 -3
- data/spec/lib/whipped-cream/cli_spec.rb +85 -6
- data/spec/lib/whipped-cream/runner_spec.rb +54 -10
- data/spec/lib/whipped-cream/server_spec.rb +12 -1
- data/whipped-cream.gemspec +1 -0
- metadata +21 -5
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,8 +1,14 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 0.2.0
|
4
|
+
|
5
|
+
* Broadcast and discover Whipped Cream servers using mDNS (@rringler)
|
6
|
+
* Improve button functionality (@rringler)
|
7
|
+
* Support starting plugins with or without an `.rb` extension (@rringler)
|
8
|
+
|
3
9
|
## 0.1.1
|
4
10
|
|
5
|
-
* Validate GPIO pin numbers (@
|
11
|
+
* Validate GPIO pin numbers (@rringler)
|
6
12
|
|
7
13
|
## 0.1.0
|
8
14
|
|
data/lib/whipped-cream/cli.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'thor'
|
2
|
+
require 'dnssd'
|
2
3
|
|
3
4
|
module WhippedCream
|
4
5
|
# The CLI gets invoked from the binary, and encapsulates all user interaction
|
@@ -47,9 +48,58 @@ module WhippedCream
|
|
47
48
|
server.start
|
48
49
|
end
|
49
50
|
|
51
|
+
desc "discover", "Discovers any whipped-cream servers on the local network"
|
52
|
+
def discover
|
53
|
+
services = browse_services('_whipped-cream._tcp.')
|
54
|
+
|
55
|
+
services.select { |_, service| service.flags.add? }.each do |_, service|
|
56
|
+
host = resolve_service(service)
|
57
|
+
|
58
|
+
puts "#{host[:address]}:#{host[:port]}\t#{host[:name]}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
50
62
|
no_tasks do
|
51
63
|
def resolve_plugin(name)
|
52
|
-
name
|
64
|
+
name += '.rb' unless name.split('.').last == 'rb'
|
65
|
+
name
|
66
|
+
end
|
67
|
+
|
68
|
+
def browse_services(service_type)
|
69
|
+
services = {}
|
70
|
+
|
71
|
+
DNSSD::Service.new.browse(service_type) do |reply|
|
72
|
+
services[reply.fullname] = reply
|
73
|
+
break unless reply.flags.more_coming?
|
74
|
+
end
|
75
|
+
|
76
|
+
services
|
77
|
+
end
|
78
|
+
|
79
|
+
def resolve_service(service)
|
80
|
+
host = {}
|
81
|
+
|
82
|
+
DNSSD::Service.new.resolve(service) do |reply|
|
83
|
+
address = get_ipv4_address(reply)
|
84
|
+
|
85
|
+
host[:name] = "#{reply.name}"
|
86
|
+
host[:address] = "#{address}"
|
87
|
+
host[:port] = "#{reply.port}"
|
88
|
+
break unless reply.flags.more_coming?
|
89
|
+
end
|
90
|
+
|
91
|
+
host
|
92
|
+
end
|
93
|
+
|
94
|
+
def get_ipv4_address(reply)
|
95
|
+
address = nil
|
96
|
+
|
97
|
+
DNSSD::Service.new.getaddrinfo(reply.target, 1) do |addrinfo|
|
98
|
+
address = addrinfo.address
|
99
|
+
break unless addrinfo.flags.more_coming?
|
100
|
+
end
|
101
|
+
|
102
|
+
address
|
53
103
|
end
|
54
104
|
end
|
55
105
|
end
|
@@ -55,6 +55,12 @@ module WhippedCream
|
|
55
55
|
time sudo apt-get install ruby1.9.3 -y)
|
56
56
|
fi
|
57
57
|
|
58
|
+
dpkg --status avahi-daemon > /dev/null ||
|
59
|
+
(time sudo apt-get update &&
|
60
|
+
time sudo apt-get install avahi-daemon &&
|
61
|
+
time sudo apt-get install libavahi-compat-libdnssd-dev &&
|
62
|
+
time sudo insserv avahi-daemon)
|
63
|
+
|
58
64
|
which whipped-cream ||
|
59
65
|
time sudo gem install whipped-cream --no-ri --no-rdoc --pre
|
60
66
|
|
data/lib/whipped-cream/runner.rb
CHANGED
@@ -31,6 +31,10 @@ module WhippedCream
|
|
31
31
|
@pins ||= {}
|
32
32
|
end
|
33
33
|
|
34
|
+
def read_pin(pin)
|
35
|
+
pin.read.zero? ? :off : :on
|
36
|
+
end
|
37
|
+
|
34
38
|
private
|
35
39
|
|
36
40
|
def configure
|
@@ -65,7 +69,7 @@ module WhippedCream
|
|
65
69
|
define_singleton_method sensor.id do
|
66
70
|
pin = pins[sensor.id]
|
67
71
|
|
68
|
-
pin
|
72
|
+
read_pin(pin) == :on ? sensor.high : sensor.low
|
69
73
|
end
|
70
74
|
end
|
71
75
|
|
@@ -79,8 +83,8 @@ module WhippedCream
|
|
79
83
|
plugin.switches.each do |switch|
|
80
84
|
create_pin switch, direction: :out
|
81
85
|
|
82
|
-
define_singleton_method switch.id do
|
83
|
-
|
86
|
+
define_singleton_method switch.id do |state|
|
87
|
+
set_pin(pins[switch.id], state)
|
84
88
|
end
|
85
89
|
end
|
86
90
|
end
|
@@ -94,20 +98,18 @@ module WhippedCream
|
|
94
98
|
end
|
95
99
|
|
96
100
|
def tap_pin(pin)
|
97
|
-
pin
|
101
|
+
set_pin(pin, :on)
|
98
102
|
|
99
103
|
Thread.new {
|
100
104
|
sleep 0.25
|
101
|
-
pin
|
105
|
+
set_pin(pin, :off)
|
102
106
|
}
|
103
107
|
end
|
104
108
|
|
105
|
-
def
|
106
|
-
if pin
|
107
|
-
|
108
|
-
|
109
|
-
pin.on
|
110
|
-
end
|
109
|
+
def set_pin(pin, state)
|
110
|
+
return if read_pin(pin) == state
|
111
|
+
|
112
|
+
pin.send(state)
|
111
113
|
end
|
112
114
|
end
|
113
115
|
end
|
data/lib/whipped-cream/server.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'rack'
|
2
|
+
require 'dnssd'
|
2
3
|
|
3
4
|
module WhippedCream
|
4
5
|
# A server handles building a plugin/runner and starting a web server
|
@@ -14,6 +15,7 @@ module WhippedCream
|
|
14
15
|
ensure_routes_built
|
15
16
|
ensure_runner_started
|
16
17
|
|
18
|
+
register_server
|
17
19
|
start_web
|
18
20
|
end
|
19
21
|
|
@@ -47,6 +49,17 @@ module WhippedCream
|
|
47
49
|
Rack::Server.start rack_options
|
48
50
|
end
|
49
51
|
|
52
|
+
def register_server
|
53
|
+
name = runner.name || "<none>"
|
54
|
+
service = "_whipped-cream._tcp"
|
55
|
+
domain = nil
|
56
|
+
port = rack_options[:Port]
|
57
|
+
|
58
|
+
DNSSD.register(name, service, domain, port) do |reply|
|
59
|
+
raise "Unable to register web server" unless reply.flags.add?
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
50
63
|
def build_routes
|
51
64
|
build_button_routes
|
52
65
|
build_switch_routes
|
@@ -63,8 +76,8 @@ module WhippedCream
|
|
63
76
|
|
64
77
|
def build_switch_routes
|
65
78
|
plugin.switches.each do |switch|
|
66
|
-
web.
|
67
|
-
runner.send switch.id
|
79
|
+
web.post "/#{switch.id}" do
|
80
|
+
runner.send switch.id, params[:state]
|
68
81
|
redirect to('/')
|
69
82
|
end
|
70
83
|
end
|
@@ -1,7 +1,17 @@
|
|
1
1
|
<div class="item">
|
2
2
|
<h2><%= control.name %></h2>
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
<% pin = runner.pins[control.id] %>
|
4
|
+
<% pin_state = runner.read_pin(pin) %>
|
5
|
+
<p><%= pin_state.to_s.capitalize %></p>
|
6
|
+
<form action="/<%= control.id %>" method="post">
|
7
|
+
<input type="radio"
|
8
|
+
name="state"
|
9
|
+
value="on"
|
10
|
+
<%= "checked" if pin_state == :on %>> On <br />
|
11
|
+
<input type="radio"
|
12
|
+
name="state"
|
13
|
+
value="off"
|
14
|
+
<%= "checked" if pin_state == :off %>> Off <br />
|
15
|
+
<input type="submit" value="Submit">
|
6
16
|
</form>
|
7
17
|
</div>
|
@@ -7,7 +7,7 @@ describe WhippedCream::CLI do
|
|
7
7
|
subject { cli }
|
8
8
|
let(:cli) { described_class.new }
|
9
9
|
|
10
|
-
let(:plugin_filename) { File.join(tmpdir, "garage
|
10
|
+
let(:plugin_filename) { File.join(tmpdir, "garage") }
|
11
11
|
let(:plugin_string) {
|
12
12
|
<<-PLUGIN
|
13
13
|
name "Garage"
|
@@ -19,7 +19,7 @@ describe WhippedCream::CLI do
|
|
19
19
|
let(:tmpdir) { Dir.mktmpdir }
|
20
20
|
|
21
21
|
before do
|
22
|
-
File.open(plugin_filename, 'w') { |file| file.write plugin_string }
|
22
|
+
File.open("#{plugin_filename}.rb", 'w') { |file| file.write plugin_string }
|
23
23
|
end
|
24
24
|
|
25
25
|
after do
|
@@ -48,11 +48,22 @@ describe WhippedCream::CLI do
|
|
48
48
|
describe "#start" do
|
49
49
|
let(:server_double) { double(WhippedCream::Server) }
|
50
50
|
|
51
|
-
|
52
|
-
|
53
|
-
|
51
|
+
context "with a filename and extension" do
|
52
|
+
it "starts a server for the plugin" do
|
53
|
+
expect(WhippedCream::Server).to receive(:new) { server_double }
|
54
|
+
expect(server_double).to receive(:start)
|
55
|
+
|
56
|
+
cli.start("#{plugin_filename}.rb")
|
57
|
+
end
|
58
|
+
end
|
54
59
|
|
55
|
-
|
60
|
+
context "with just a filename and no extension" do
|
61
|
+
it "starts a server for the plugin" do
|
62
|
+
expect(WhippedCream::Server).to receive(:new) { server_double }
|
63
|
+
expect(server_double).to receive(:start)
|
64
|
+
|
65
|
+
cli.start(plugin_filename)
|
66
|
+
end
|
56
67
|
end
|
57
68
|
|
58
69
|
context "with --daemonize" do
|
@@ -83,6 +94,36 @@ describe WhippedCream::CLI do
|
|
83
94
|
end
|
84
95
|
end
|
85
96
|
|
97
|
+
describe "#discover" do
|
98
|
+
let(:service_type) { "_whipped-cream._tcp." }
|
99
|
+
let(:services_double) {
|
100
|
+
{ "Test._whipped-cream._tcp.local." => double("reply") }
|
101
|
+
}
|
102
|
+
let(:host_double) {
|
103
|
+
{ name: "Test",
|
104
|
+
address: "192.168.0.100",
|
105
|
+
port: "8080" }
|
106
|
+
}
|
107
|
+
let(:output_string) {
|
108
|
+
"#{host_double[:address]}:#{host_double[:port]}\t#{host_double[:name]}"
|
109
|
+
}
|
110
|
+
|
111
|
+
it "displays the host information for any running servers" do
|
112
|
+
cli.should_receive(:browse_services)
|
113
|
+
.with(service_type)
|
114
|
+
.and_return(services_double)
|
115
|
+
|
116
|
+
cli.should_receive(:resolve_service)
|
117
|
+
.and_return(host_double)
|
118
|
+
|
119
|
+
services_double.should_receive(:select).and_return(services_double)
|
120
|
+
|
121
|
+
expect(cli).to receive(:puts).with(output_string)
|
122
|
+
|
123
|
+
cli.discover
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
86
127
|
describe "#usage" do
|
87
128
|
it "displays a banner and help" do
|
88
129
|
expect(cli).to receive(:puts).exactly(2).times
|
@@ -91,4 +132,42 @@ describe WhippedCream::CLI do
|
|
91
132
|
cli.usage
|
92
133
|
end
|
93
134
|
end
|
135
|
+
|
136
|
+
describe "#browse_services" do
|
137
|
+
let(:service_type) { '_whipped-cream._tcp.' }
|
138
|
+
|
139
|
+
it "should call DNSSD::Services#browse" do
|
140
|
+
DNSSD::Service.any_instance
|
141
|
+
.should_receive(:browse)
|
142
|
+
.with(service_type)
|
143
|
+
|
144
|
+
cli.browse_services(service_type)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
describe "#resolve_service" do
|
149
|
+
let(:service_double) { double("service") }
|
150
|
+
|
151
|
+
it "should call DNSSD::Services#resolve" do
|
152
|
+
DNSSD::Service.any_instance
|
153
|
+
.should_receive(:resolve)
|
154
|
+
.with(service_double)
|
155
|
+
|
156
|
+
cli.resolve_service(service_double)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
describe "#get_ipv4_address" do
|
161
|
+
let(:reply_double) { double("dnssd_reply") }
|
162
|
+
|
163
|
+
it "should call DNSSD::Services#getaddrinfo" do
|
164
|
+
reply_double.stub(:target).and_return("test_host")
|
165
|
+
|
166
|
+
DNSSD::Service.any_instance
|
167
|
+
.should_receive(:getaddrinfo)
|
168
|
+
.with(reply_double.target, 1)
|
169
|
+
|
170
|
+
cli.get_ipv4_address(reply_double)
|
171
|
+
end
|
172
|
+
end
|
94
173
|
end
|
@@ -29,9 +29,7 @@ describe WhippedCream::Runner do
|
|
29
29
|
it "defines an open_close method that taps the pin" do
|
30
30
|
pin = runner.pins[:open_close]
|
31
31
|
|
32
|
-
expect(
|
33
|
-
expect(runner).to receive(:sleep).with(0.25)
|
34
|
-
expect(pin).to receive(:off)
|
32
|
+
expect(runner).to receive(:tap_pin).with(pin).and_call_original
|
35
33
|
|
36
34
|
runner.open_close.join
|
37
35
|
end
|
@@ -96,13 +94,59 @@ describe WhippedCream::Runner do
|
|
96
94
|
it "defines a light method that switches the pin on and off" do
|
97
95
|
pin = runner.pins[:light]
|
98
96
|
|
99
|
-
expect(pin
|
100
|
-
runner.light
|
101
|
-
expect(pin
|
102
|
-
runner.light
|
103
|
-
expect(pin
|
104
|
-
runner.light
|
105
|
-
expect(pin
|
97
|
+
expect(runner.read_pin(pin)).to eq(:off)
|
98
|
+
runner.light(:on)
|
99
|
+
expect(runner.read_pin(pin)).to eq(:on)
|
100
|
+
runner.light(:off)
|
101
|
+
expect(runner.read_pin(pin)).to eq(:off)
|
102
|
+
runner.light(:on)
|
103
|
+
expect(runner.read_pin(pin)).to eq(:on)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe "#tap_pin" do
|
108
|
+
let(:plugin) {
|
109
|
+
WhippedCream::Plugin.build do
|
110
|
+
button "Open/Close", pin: 4
|
111
|
+
end
|
112
|
+
}
|
113
|
+
|
114
|
+
it "turns the pin on momentarily and then turns it off" do
|
115
|
+
pin = runner.pins[:garage]
|
116
|
+
|
117
|
+
expect(runner).to receive(:set_pin).with(pin, :on)
|
118
|
+
expect(runner).to receive(:sleep).with(0.25)
|
119
|
+
expect(runner).to receive(:set_pin).with(pin, :off)
|
120
|
+
|
121
|
+
runner.send(:tap_pin, pin).join
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
describe "#set_pin" do
|
126
|
+
let(:plugin) {
|
127
|
+
WhippedCream::Plugin.build do
|
128
|
+
switch "Light", pin: 18
|
129
|
+
end
|
130
|
+
}
|
131
|
+
let(:pin) { runner.pins[:light] }
|
132
|
+
before(:each) { runner.send(:set_pin, pin, :off) }
|
133
|
+
|
134
|
+
it "orders the desired pin state when different from the current state" do
|
135
|
+
expect(pin).to receive(:on).and_call_original
|
136
|
+
runner.send(:set_pin, pin, :on)
|
137
|
+
|
138
|
+
expect(pin).to receive(:off)
|
139
|
+
runner.send(:set_pin, pin, :off)
|
140
|
+
end
|
141
|
+
|
142
|
+
it "does nothing when the ordered pin state matches the current state" do
|
143
|
+
expect(pin).not_to receive(:off)
|
144
|
+
runner.send(:set_pin, pin, :off)
|
145
|
+
|
146
|
+
runner.send(:set_pin, pin, :on)
|
147
|
+
|
148
|
+
expect(pin).not_to receive(:on)
|
149
|
+
runner.send(:set_pin, pin, :on)
|
106
150
|
end
|
107
151
|
end
|
108
152
|
end
|
@@ -51,7 +51,7 @@ describe WhippedCream::Server do
|
|
51
51
|
before { server.start }
|
52
52
|
|
53
53
|
it "creates a switch route" do
|
54
|
-
expect(server.web.routes['
|
54
|
+
expect(server.web.routes['POST'].find { |route|
|
55
55
|
route.first.match('/light')
|
56
56
|
}).to be_true
|
57
57
|
end
|
@@ -66,6 +66,17 @@ describe WhippedCream::Server do
|
|
66
66
|
server.start
|
67
67
|
end
|
68
68
|
|
69
|
+
it "registers the server via mDNS" do
|
70
|
+
expect(DNSSD).to receive(:register).with(
|
71
|
+
server.runner.name || "<none>",
|
72
|
+
'_whipped-cream._tcp',
|
73
|
+
nil,
|
74
|
+
server.options.fetch(:port, 8080)
|
75
|
+
)
|
76
|
+
|
77
|
+
server.start
|
78
|
+
end
|
79
|
+
|
69
80
|
context "with daemonize: true" do
|
70
81
|
let(:options) {
|
71
82
|
{ daemonize: true }
|
data/whipped-cream.gemspec
CHANGED
@@ -27,6 +27,7 @@ Gem::Specification.new do |spec|
|
|
27
27
|
spec.add_runtime_dependency 'pi_piper'
|
28
28
|
spec.add_runtime_dependency 'sinatra'
|
29
29
|
spec.add_runtime_dependency 'thor'
|
30
|
+
spec.add_runtime_dependency 'dnssd'
|
30
31
|
|
31
32
|
spec.add_development_dependency 'bundler', '~> 1.3'
|
32
33
|
spec.add_development_dependency 'cane'
|
metadata
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: whipped-cream
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.2.0.beta1
|
5
|
+
prerelease: 6
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Justin Campbell
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-
|
12
|
+
date: 2014-02-06 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: net-scp
|
@@ -91,6 +91,22 @@ dependencies:
|
|
91
91
|
- - ! '>='
|
92
92
|
- !ruby/object:Gem::Version
|
93
93
|
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: dnssd
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :runtime
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
94
110
|
- !ruby/object:Gem::Dependency
|
95
111
|
name: bundler
|
96
112
|
requirement: !ruby/object:Gem::Requirement
|
@@ -269,9 +285,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
269
285
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
270
286
|
none: false
|
271
287
|
requirements:
|
272
|
-
- - ! '
|
288
|
+
- - ! '>'
|
273
289
|
- !ruby/object:Gem::Version
|
274
|
-
version:
|
290
|
+
version: 1.3.1
|
275
291
|
requirements: []
|
276
292
|
rubyforge_project:
|
277
293
|
rubygems_version: 1.8.23
|