pinoccio 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2 @@
1
+ .DS_Store
2
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "faraday"
@@ -0,0 +1,12 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ faraday (0.9.0)
5
+ multipart-post (>= 1.2, < 3)
6
+ multipart-post (2.0.0)
7
+
8
+ PLATFORMS
9
+ ruby
10
+
11
+ DEPENDENCIES
12
+ faraday
@@ -0,0 +1,46 @@
1
+ Pinoccio Ruby API
2
+ =================
3
+
4
+ This library gives access to the pinoccio REST API to configure and manage Troops & Scouts. It additionally bundles all the built in ScoutScript commands so that you can command your Scouts straight from Ruby.
5
+
6
+ Example
7
+ -------
8
+
9
+ require "pinoccio"
10
+
11
+ # Login to the API
12
+ client = Pinoccio::Client.new(USERNAME, PASSWORD) # or alternatively a valid API token
13
+ puts client.account.inspect
14
+
15
+ # get a handle to the first Troop (You'll most likely only have one)
16
+ troop = client.troops.first
17
+
18
+ # get the lead scout in this troop
19
+ lead_scout = troop.scouts.lead
20
+
21
+ # dump out some data about the scout
22
+ puts lead_scout.get('led.report')
23
+ puts lead_scout.power.charging?.inspect
24
+ puts lead_scout.power.percent.inspect
25
+ puts lead_scout.power.voltage.inspect
26
+ puts lead_scout.power.report
27
+
28
+ # create a new 'blink' function and print a list off all registered functions
29
+ lead_scout.functions.add(:blink, "if (led.isoff) { led.cyan; } else { led.off; }")
30
+ puts lead_scout.functions.list.inspect
31
+
32
+ # run the blink function every 250ms for 3 seconds
33
+ lead_scout.functions.repeat(:blink, 250)
34
+ puts lead_scout.functions.running.inspect
35
+ sleep(3)
36
+
37
+ # stop the blink function and ensure the LED is off when we finish
38
+ lead_scout.functions.stop(:blink)
39
+ lead_scout.led.off
40
+
41
+
42
+ Notes
43
+ -----
44
+ - I've not implemented the full API yet, only really enough to read data so far.
45
+ - sometimes the API throws timeout errors, you can rescue and retry these by catching Pinoccio::TimeoutError
46
+ - This is just a little playground, no tests yet, use at your own risk.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,60 @@
1
+ module Pinoccio
2
+ class Client
3
+ attr_reader :token
4
+
5
+ def initialize(email_or_token, password=nil)
6
+ @connection = Faraday.new(:url => 'https://api.pinocc.io/')
7
+ if password.nil?
8
+ @token = email_or_token
9
+ else
10
+ login(email_or_token, password)
11
+ end
12
+ end
13
+
14
+ def login(email, password)
15
+ response = post("login", email: email, password: password)
16
+ @token = response.token
17
+ end
18
+
19
+ def account
20
+ get "account"
21
+ end
22
+
23
+ def troops
24
+ TroopCollection.new(self)
25
+ end
26
+
27
+ def get(path, data={})
28
+ data = {token:@token}.merge(data) if @token
29
+ response = @connection.get("/v1/#{path}", data)
30
+ handle_response(response)
31
+ end
32
+
33
+ def post(path, data={})
34
+ data = {token:@token}.merge(data) if @token
35
+ response = @connection.post("/v1/#{path}", data)
36
+ handle_response(response)
37
+ end
38
+
39
+ private
40
+
41
+ def handle_response(response)
42
+ hsh = JSON.parse(response.body)
43
+ handle_error(hsh['error']) if hsh['error']
44
+ data = hsh['data']
45
+ if data.is_a? Array
46
+ data.map {|d| OpenStruct.new(d) }
47
+ else
48
+ OpenStruct.new(data)
49
+ end
50
+ end
51
+
52
+ def handle_error(err)
53
+ if err['message'] && (err['message'] =~ /timeout/ || err['message'] =~ /timed out/)
54
+ raise Pinoccio::TimeoutError.new(err)
55
+ else
56
+ raise Pinoccio::Error.new(err)
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,351 @@
1
+ module Pinoccio
2
+ module Commands
3
+ def lead?
4
+ @is_lead ||= get("scout.isleadscout", :boolean)
5
+ end
6
+
7
+ def report
8
+ get("scout.report", :json)
9
+ end
10
+
11
+ def daisy!
12
+ execute("scout.daisy")
13
+ execute("scout.daisy")
14
+ end
15
+
16
+ def boot
17
+ execute("scout.boot")
18
+ end
19
+
20
+ def temperature
21
+ get("temperature", :integer)
22
+ end
23
+
24
+ def random_number
25
+ get("randomnumber", :integer)
26
+ end
27
+
28
+ def uptime
29
+ get("uptime", :integer)
30
+ end
31
+
32
+ def power
33
+ PowerCommands.new(self)
34
+ end
35
+
36
+ def led
37
+ LedCommands.new(self)
38
+ end
39
+
40
+ def wifi
41
+ WifiCommands.new(self)
42
+ end
43
+
44
+ def mesh
45
+ MeshCommands.new(self)
46
+ end
47
+
48
+ def pin
49
+ PinCommands.new(self)
50
+ end
51
+
52
+ def events
53
+ EventsCommands.new(self)
54
+ end
55
+
56
+ def functions
57
+ FunctionCommands.new(self)
58
+ end
59
+ end
60
+
61
+
62
+
63
+ class CommandCollection
64
+ def initialize(target)
65
+ @target = target
66
+ end
67
+
68
+ def get(command, type=nil)
69
+ @target.get(command, type)
70
+ end
71
+
72
+ def run(command, type=nil)
73
+ @target.run(command)
74
+ end
75
+
76
+ def execute(command)
77
+ @target.execute(command)
78
+ end
79
+ end
80
+
81
+
82
+
83
+ class PowerCommands < CommandCollection
84
+ def charging?
85
+ get("power.ischarging", :boolean)
86
+ end
87
+
88
+ def percent
89
+ get("power.percent", :integer)
90
+ end
91
+
92
+ def voltage
93
+ get("power.voltage", :integer)
94
+ end
95
+
96
+ def enable_vcc
97
+ execute("power.enable_vcc")
98
+ end
99
+
100
+ def disable_vcc
101
+ execute("power.disable_vcc")
102
+ end
103
+
104
+ def report
105
+ run("power.report", :json)
106
+ end
107
+ end
108
+
109
+
110
+
111
+ class LedCommands < CommandCollection
112
+ def blink(r, g, b, ms=nil, continuous=nil)
113
+ if ms.nil?
114
+ execute("led.blink(#{r}, #{g}, #{b})")
115
+ elsif continuous.nil?
116
+ execute("led.blink(#{r}, #{g}, #{b}, #{ms})")
117
+ else
118
+ continuous = (continuous == 1)
119
+ execute("led.blink(#{r}, #{g}, #{b}, #{ms}, #{continuous})")
120
+ end
121
+ end
122
+
123
+ def off; execute("led.off"); end
124
+ def red; execute("led.red"); end
125
+ def green; execute("led.green"); end
126
+ def blue; execute("led.blue"); end
127
+ def cyan; execute("led.cyan"); end
128
+ def purple; execute("led.purple"); end
129
+ def magenta; execute("led.magenta"); end
130
+ def yellow; execute("led.yellow"); end
131
+ def orange; execute("led.orange"); end
132
+ def white; execute("led.white"); end
133
+ def torch; execute("led.torch"); end
134
+
135
+ def hex=(value)
136
+ execute(%[led.sethex("#{value}")])
137
+ end
138
+
139
+ def rgb=(r, g, b)
140
+ execute(%[led.setrgb(#{r}, #{g}, #{b})])
141
+ end
142
+
143
+ def torch=(r, g, b)
144
+ execute(%[led.savetorch(#{r}, #{g}, #{b})])
145
+ end
146
+
147
+ def report
148
+ run("led.report", :json)
149
+ end
150
+ end
151
+
152
+
153
+
154
+ class WifiCommands < CommandCollection
155
+ def report
156
+ run("wifi.report", :json)
157
+ end
158
+
159
+ def status
160
+ get('wifi.status')
161
+ end
162
+
163
+ def list
164
+ get('wifi.list')
165
+ end
166
+
167
+ def config(name, password)
168
+ execute(%[wifi.config("#{name}", "#{password}")])
169
+ end
170
+
171
+ def reassociate
172
+ execute("wifi.reassociate")
173
+ end
174
+
175
+ def connect(name, password)
176
+ config(name, password)
177
+ reassociate
178
+ report
179
+ end
180
+
181
+ def command(cmd)
182
+ get(%[wifi.command("#{cmd}")])
183
+ end
184
+ end
185
+
186
+
187
+ class MeshCommands < CommandCollection
188
+ def config(scout_id, troop_id, channel=20)
189
+ get(%[mesh.config(#{scout_id}, #{troop_id}, #{channel})])
190
+ end
191
+
192
+ def power=(level)
193
+ execute("mesh.setpower(#{level})")
194
+ end
195
+
196
+ def datarate=(rate)
197
+ execute("mesh.datarate(#{rate})")
198
+ end
199
+
200
+ def key=(k)
201
+ execute(%[mesh.setkey("#{k}")])
202
+ end
203
+
204
+ def key
205
+ get("mesh.getkey")
206
+ end
207
+
208
+ def reset_key
209
+ execute("mesh.resetkey")
210
+ end
211
+
212
+ def join_group(group_id)
213
+ execute("mesh.joingroup(#{group_id})")
214
+ end
215
+
216
+ def leave_group(group_id)
217
+ execute("mesh.leavegroup(#{group_id})")
218
+ end
219
+
220
+ def in_group?(group_id)
221
+ execute("mesh.ingroup(#{group_id})")
222
+ end
223
+
224
+ def send_message(scout_id, msg)
225
+ execute(%[mesh.send(#{scout_id}, "#{msg}")])
226
+ end
227
+
228
+ def report
229
+ run("mesh.report", :json)
230
+ end
231
+
232
+ def routing
233
+ get("mesh.routing")
234
+ end
235
+
236
+ def announce(group_id, msg)
237
+ execute(%[mesh.announce(#{group_id}, "#{msg}")])
238
+ end
239
+
240
+ def signal
241
+ get('mesh.signal', :integer)
242
+ end
243
+ end
244
+
245
+ class PinCommands < CommandCollection
246
+ INPUT = "INPUT"
247
+ OUTPUT = "OUTPUT"
248
+ INPUT_PULLUP = "INPUT_PULLUP"
249
+ DISABLED = "DISABLED"
250
+ HIGH = "HIGH"
251
+ LOW = "LOW"
252
+
253
+ def make_input(pin, pullup=INPUT_PULLUP)
254
+ execute(%[pin.makeinput("#{pin}", #{pullup})])
255
+ end
256
+
257
+ def make_output(pin)
258
+ execute(%[pin.makeoutput("#{pin}")])
259
+ end
260
+
261
+ def disable(pin)
262
+ execute(%[pin.disable("#{pin}")])
263
+ end
264
+
265
+ def set_mode(pin, mode)
266
+ execute(%[pin.setmode("#{pin}", #{mode})])
267
+ end
268
+
269
+ def read(pin)
270
+ get(%[pin.read("#{pin}")], :integer)
271
+ end
272
+
273
+ def write(pin, value)
274
+ execute(%[pin.write("#{pin}", value)], :integer)
275
+ end
276
+
277
+ def report_digital
278
+ get('pin.report.digital', :json)
279
+ end
280
+
281
+ def report_analog
282
+ get('pin.report.analog', :json)
283
+ end
284
+ end
285
+
286
+ class EventsCommands < CommandCollection
287
+ def start
288
+ execute("events.start")
289
+ end
290
+
291
+ def stop
292
+ execute("events.stop")
293
+ end
294
+
295
+ def set_cycle(digital_ms=50, analogue_ms=60_000, peripheral_ms=60_000)
296
+ execute("events.setcycle(#{digital_ms}, #{analogue_ms}, #{peripheral_ms})")
297
+ end
298
+ end
299
+
300
+ class FunctionCommands < CommandCollection
301
+ def list
302
+ ls = run('ls')
303
+ ls.split(/\r?\n/).reduce({}) do |hsh, fn|
304
+ matches = fn.match(/function ([a-z0-9]+) \{/)
305
+ hsh[matches[1]] = fn
306
+ hsh
307
+ end
308
+ end
309
+
310
+ def remove(fn)
311
+ execute("rm #{fn}")
312
+ end
313
+
314
+ def remove_all
315
+ execute("rm *")
316
+ end
317
+
318
+ def repeat(fn, delay)
319
+ execute("run #{fn}, #{delay}")
320
+ end
321
+
322
+ def stop(pid)
323
+ pid = pid_for(pid) unless pid.is_a?(Integer)
324
+ execute("stop #{pid}") if pid
325
+ end
326
+
327
+ def add(name, body=nil)
328
+ body = name if body.nil?
329
+ body = "function #{name} { #{body} }" unless body =~ /^function /
330
+ execute(body)
331
+ end
332
+
333
+ def startup(body)
334
+ add(:startup, body)
335
+ end
336
+
337
+ def pid_for(fn)
338
+ process = running.find {|p| p[:function].to_s == fn.to_s }
339
+ return process[:pid] if process
340
+ nil
341
+ end
342
+
343
+ def running
344
+ ps = run("ps")
345
+ ps.split(/\r?\n/).map do |line|
346
+ pid, fn = *line.split(": ")
347
+ {pid: pid.to_i, function: fn}
348
+ end
349
+ end
350
+ end
351
+ end
@@ -0,0 +1,17 @@
1
+ require "faraday"
2
+ require "json"
3
+ require "ostruct"
4
+
5
+ require_relative "./client"
6
+ require_relative "./troop_collection"
7
+ require_relative "./troop"
8
+ require_relative "./scout_collection"
9
+ require_relative "./scout"
10
+
11
+ module Pinoccio
12
+ class Error < StandardError
13
+ end
14
+
15
+ class TimeoutError < Error
16
+ end
17
+ end
@@ -0,0 +1,37 @@
1
+ require_relative "./commands"
2
+
3
+ module Pinoccio
4
+ class Scout
5
+ include Commands
6
+
7
+ attr_reader :troop, :id
8
+
9
+ def initialize(client, troop, props)
10
+ @client = client
11
+ @troop = troop
12
+ @id = props.id
13
+ end
14
+
15
+ def get(command, type=nil)
16
+ run("print #{command}", type)
17
+ end
18
+
19
+ def run(command, type=nil)
20
+ result = execute(command).reply.strip
21
+ case type
22
+ when :boolean
23
+ result == "1"
24
+ when :integer
25
+ result.to_i
26
+ when :json
27
+ JSON.parse(result)
28
+ else
29
+ result
30
+ end
31
+ end
32
+
33
+ def execute(command)
34
+ @client.get("#{@troop.id}/#{@id}/command", command: command)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,27 @@
1
+ module Pinoccio
2
+ class ScoutCollection
3
+ def initialize(client, troop)
4
+ @client = client
5
+ @troop = troop
6
+ end
7
+
8
+ def first
9
+ all.first
10
+ end
11
+
12
+ def lead
13
+ all.find {|scout| scout.lead? }
14
+ end
15
+
16
+ def all
17
+ result = @client.get("#{@troop.id}/scouts")
18
+ result = [result] unless result.is_a?(Array)
19
+ result.map {|s| Scout.new(@client, @troop, s) }
20
+ end
21
+
22
+ def get(scout_id)
23
+ result = @client.get("#{@troop.id}/#{scout_id}")
24
+ Scout.new(@client, @troop, result)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,41 @@
1
+ require_relative "./commands"
2
+
3
+ module Pinoccio
4
+ class Troop
5
+ include Commands
6
+
7
+ attr_reader :account, :id, :online, :name
8
+
9
+ def initialize(client, props)
10
+ @client = client
11
+ @account = props.account
12
+ @id = props.id
13
+ @online = props.online
14
+ @name = props.name
15
+ end
16
+
17
+ def scouts
18
+ ScoutCollection.new(@client, self)
19
+ end
20
+
21
+ def run(command, type=nil)
22
+ collect_across_all { |scout| scout.run(command, type) }
23
+ end
24
+
25
+ def get(command, type=nil)
26
+ collect_across_all { |scout| scout.get(command, type) }
27
+ end
28
+
29
+ def execute(command)
30
+ collect_across_all { |scout| scout.execute(command) }
31
+ end
32
+
33
+ private
34
+ def collect_across_all(&blk)
35
+ scouts.all.reduce({}) do |hsh, scout|
36
+ hsh[scout.id] = yield(scout)
37
+ hsh
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,22 @@
1
+ module Pinoccio
2
+ class TroopCollection
3
+ def initialize(client)
4
+ @client = client
5
+ end
6
+
7
+ def first
8
+ all.first
9
+ end
10
+
11
+ def all
12
+ result = @client.get("troops")
13
+ result = [result] unless result.is_a?(Array)
14
+ result.map {|t| Troop.new(@client, t) }
15
+ end
16
+
17
+ def get(troop_id)
18
+ result = @client.get("troops/#{troop_id}")
19
+ Troop.new(@client, result)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,15 @@
1
+ Gem::Specification.new do |gem|
2
+ gem.authors = ["Andy Kent"]
3
+ gem.email = ["andy.kent@me.com"]
4
+ gem.description = %q{This library gives access to the pinoccio REST API to configure and manage Troops & Scouts. It additionally bundles all the built in ScoutScript commands so that you can command your Scouts straight from Ruby.}
5
+ gem.summary = %q{Pinoccio Ruby API}
6
+ gem.homepage = "https://github.com/andykent/pinoccio"
7
+
8
+ gem.files = `git ls-files`.split($\)
9
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
10
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
11
+ gem.name = "pinoccio"
12
+ gem.require_paths = ["lib"]
13
+ gem.version = '0.1.0'
14
+ gem.add_dependency('faraday', '~> 0.9.0')
15
+ end
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pinoccio
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Andy Kent
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-04-15 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: faraday
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 0.9.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 0.9.0
30
+ description: This library gives access to the pinoccio REST API to configure and manage
31
+ Troops & Scouts. It additionally bundles all the built in ScoutScript commands so
32
+ that you can command your Scouts straight from Ruby.
33
+ email:
34
+ - andy.kent@me.com
35
+ executables: []
36
+ extensions: []
37
+ extra_rdoc_files: []
38
+ files:
39
+ - .gitignore
40
+ - Gemfile
41
+ - Gemfile.lock
42
+ - README.md
43
+ - Rakefile
44
+ - lib/client.rb
45
+ - lib/commands.rb
46
+ - lib/pinoccio.rb
47
+ - lib/scout.rb
48
+ - lib/scout_collection.rb
49
+ - lib/troop.rb
50
+ - lib/troop_collection.rb
51
+ - pinoccio.gemspec
52
+ homepage: https://github.com/andykent/pinoccio
53
+ licenses: []
54
+ post_install_message:
55
+ rdoc_options: []
56
+ require_paths:
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ none: false
60
+ requirements:
61
+ - - ! '>='
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ required_rubygems_version: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ requirements: []
71
+ rubyforge_project:
72
+ rubygems_version: 1.8.23
73
+ signing_key:
74
+ specification_version: 3
75
+ summary: Pinoccio Ruby API
76
+ test_files: []