pinoccio 0.1.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.
@@ -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: []