rbbit 0.4.6

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/lib/rbbit.rb ADDED
@@ -0,0 +1,456 @@
1
+ require "rbbit/version"
2
+
3
+ require 'serialport'
4
+ require 'em-websocket'
5
+ require 'websocket-client-simple'
6
+ require 'json'
7
+
8
+ module Rbbit
9
+ class Error < StandardError; end
10
+
11
+ WS_PORT = 50215 # default WebSocket port
12
+
13
+ #
14
+ # WebSocket Server
15
+ #
16
+ class Agent
17
+ def initialize(mb, ws_server)
18
+ @mb = mb
19
+ #
20
+ ws_port = (ws_server == :default ? WS_PORT : ws_server)
21
+ run_server(ws_port)
22
+ Kernel.sleep 1
23
+ @con = WebSocket::Client::Simple.connect "ws://127.0.0.1:#{ws_port}"
24
+ @con.on :message do |msg|
25
+ #puts msg.data
26
+ end
27
+ @con.on :open do
28
+ @con.send('Hello')
29
+ end
30
+ @con.on :close do |e|
31
+ #p e
32
+ #exit 1
33
+ end
34
+ end
35
+
36
+ def send_to_ws(data)
37
+ @con.send(data)
38
+ end
39
+
40
+ private def send_to_mb(data)
41
+ #p data (例外処理...)
42
+ if data["command"] == 'on'
43
+ x = (data["arg1"] ? data["arg1"] : nil)
44
+ y = (data["arg2"] ? data["arg2"] : nil)
45
+ @mb.led_on(x, y)
46
+ elsif data["command"] == 'off'
47
+ x = (data["arg1"] ? data["arg1"] : nil)
48
+ y = (data["arg2"] ? data["arg2"] : nil)
49
+ @mb.led_off(x, y)
50
+ elsif data["command"] == 'turn'
51
+ x = data["arg1"]
52
+ y = data["arg2"]
53
+ @mb.led_turn(x, y)
54
+ elsif data["command"] == 'show'
55
+ pattern = data["arg1"]
56
+ @mb.led_show(pattern)
57
+ elsif data["command"] == 'puts'
58
+ str = data["arg1"]
59
+ @mb.led_puts(str)
60
+ elsif data["command"] == 'play'
61
+ freq = data["arg1"].to_sym
62
+ beat = data["arg2"].to_f
63
+ @mb.sound_play(freq, beat)
64
+ elsif data["command"] == 'rest'
65
+ beat = data["arg2"].to_f
66
+ @mb.sound_rest(beat)
67
+ elsif data["command"] == 'volume'
68
+ v = data["arg1"].to_f
69
+ @mb.sound_volume = v
70
+ elsif data["command"] == 'tempo'
71
+ bpm = data["arg1"].to_f
72
+ @mb.sound_tempo = bpm
73
+ end
74
+ end
75
+
76
+ private def run_server(ws_port)
77
+ Thread.new do
78
+ connections = Array.new
79
+ EventMachine::WebSocket.start(host: "127.0.0.1", port: ws_port) do |ws|
80
+ ws.onopen {
81
+ # ws.send "Connected"
82
+ connections.push(ws) unless connections.index(ws)
83
+ }
84
+ ws.onmessage { |msg|
85
+ data = JSON.parse(msg) rescue nil
86
+ if data
87
+ if data.has_key?("command")
88
+ #p data
89
+ #p connections.size
90
+ send_to_mb(data)
91
+ else
92
+ connections.each do |con|
93
+ con.send(msg)
94
+ end
95
+ end
96
+ end
97
+ }
98
+ ws.onclose {
99
+ #puts "Close"
100
+ connections.delete(ws) if connections.index(ws)
101
+ exit if connections.size == 0
102
+ }
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+
109
+ #
110
+ # Class Library
111
+ #
112
+ class Microbit
113
+ TONE = {
114
+ C3: 131 , # do3
115
+ Cs3: 139 , # do#3
116
+ D3: 147 , # re3
117
+ Ds3: 156 , # re#3
118
+ E3: 165 , # mi3
119
+ F3: 175 , # fa3
120
+ Fs3: 185 , # fa#3
121
+ G3: 196 , # so3
122
+ Gs3: 208 , # so#3
123
+ A3: 220 , # la3
124
+ As3: 233 , # la#3
125
+ B3: 247 , # ti3
126
+ C4: 262 , # do4
127
+ Cs4: 277 , # do#4
128
+ D4: 294 , # re4
129
+ Ds4: 311 , # re#4
130
+ E4: 330 , # mi4
131
+ F4: 349 , # fa4
132
+ Fs4: 370 , # fa#4
133
+ G4: 392 , # so4
134
+ Gs4: 415 , # so#4
135
+ A4: 440 , # la4
136
+ As4: 466 , # la#4
137
+ B4: 494 , # ti4
138
+ C5: 523 , # do5
139
+ Cs5: 554 , # do#5
140
+ D5: 587 , # re5
141
+ Ds5: 622 , # re#5
142
+ E5: 659 , # mi5
143
+ F5: 698 , # fa5
144
+ Fs5: 740 , # fa#5
145
+ G5: 784 , # so5
146
+ Gs5: 831 , # so#5
147
+ A5: 880 , # la5
148
+ As5: 932 , # la#5
149
+ B5: 988 , # ti5
150
+ C6: 1047 , # do6
151
+ }
152
+ WAIT = 0.05
153
+ attr_reader :x, :y, :z, :p, :r, :l, :t
154
+
155
+ def initialize(port = nil, ws_server = nil)
156
+ @agent = Agent.new(self, ws_server) if ws_server
157
+ @q = Queue.new
158
+
159
+ @x = nil
160
+ @y = nil
161
+ @z = nil
162
+ @p = nil
163
+ @r = nil
164
+ @l = nil
165
+ @t = nil
166
+ @button_down = {}
167
+ @button_down_last = {}
168
+ @button_press = {}
169
+ @button_release = {}
170
+ @button_down[:a] = nil
171
+ @button_down[:b] = nil
172
+ @button_down_last[:a] = nil
173
+ @button_down_last[:b] = nil
174
+ @button_press[:a] = nil
175
+ @button_press[:b] = nil
176
+ @button_release[:a] = nil
177
+ @button_release[:b] = nil
178
+
179
+ @on_press_a = nil
180
+ @on_press_b = nil
181
+ @on_release_a = nil
182
+ @on_release_b = nil
183
+
184
+ @volume = 127
185
+ @bpm = 120 # ウェイト調整のため、この値で明示的に初期化(initialize末尾)
186
+
187
+ @continue_thread = nil
188
+ @continue_loop = nil
189
+
190
+ @port = port || ENV['MB_PORT']
191
+ baud_rate = 115200
192
+ data_bits = 8
193
+ stop_bits = 1
194
+ parity = 0
195
+
196
+ raise Rbbit::Error, "serialport 'nil' is not available" unless @port
197
+
198
+ begin
199
+ @sp = SerialPort.open(@port, baud_rate, data_bits, stop_bits, parity)
200
+ rescue => e
201
+ #Kernel.puts e
202
+ #exit
203
+ raise Rbbit::Error, "serialport '#{@port}' is not available"
204
+ end
205
+ Kernel.sleep 0.5
206
+
207
+ # -- for write
208
+ @thread_w = Thread.new do
209
+ loop do
210
+ Thread.pass # for other threads
211
+ unless @q.empty?
212
+ cmd = @q.pop
213
+ if cmd.class == Array
214
+ @sp.write cmd[0]; Kernel.sleep cmd[1]
215
+ else
216
+ @sp.write cmd
217
+ end
218
+ end
219
+ end
220
+ end
221
+
222
+ # -- for read
223
+ @thread_r = Thread.new do
224
+ # Thread.pass
225
+ @sp.read_timeout = 200
226
+ @continue_thread = true
227
+ loop do
228
+ break unless @continue_thread
229
+ Thread.pass # for other threads
230
+ begin
231
+ value = @sp.readline.chomp.strip.split(',')
232
+ # Kernel.p value
233
+ rescue EOFError
234
+ Kernel.sleep 0.1
235
+ retry
236
+ rescue ArgumentError
237
+ # 回避:`strip': invalid byte sequence in UTF-8 (ArgumentError)
238
+ # `split': invalid byte sequence in UTF-8 (ArgumentError)
239
+ Kernel.sleep 0.1
240
+ retry
241
+ end
242
+
243
+ @x = value[0].to_i
244
+ @y = value[1].to_i
245
+ @z = value[2].to_i
246
+ @p = value[3].to_i # pitch
247
+ @r = value[4].to_i # roll
248
+ @l = value[5].to_i # light
249
+ @t = value[6].to_i # temp
250
+ @button_down[:a] = (value[7] == "1" ? true : false)
251
+ @button_down[:b] = (value[8] == "1" ? true : false)
252
+ button_status
253
+ event_proc
254
+ if @agent
255
+ senddata = JSON.generate({x: @x,
256
+ y: @y,
257
+ z: @z,
258
+ p: @p,
259
+ r: @r,
260
+ l: @l,
261
+ t: @t,
262
+ a_down: @button_down[:a],
263
+ a_press: @button_press[:a],
264
+ a_release: @button_release[:a],
265
+ b_down: @button_down[:b],
266
+ b_press: @button_press[:b],
267
+ b_release: @button_release[:b]})
268
+ @agent.send_to_ws senddata
269
+ end
270
+ end
271
+ end
272
+
273
+ # -- init
274
+ self.sound_volume = @volume
275
+ self.sound_tempo = @bpm
276
+ end
277
+
278
+ def on_press_a(&block)
279
+ @on_press_a = block
280
+ end
281
+
282
+ def on_release_a(&block)
283
+ @on_release_a = block
284
+ end
285
+
286
+ def on_press_b(&block)
287
+ @on_press_b = block
288
+ end
289
+
290
+ def on_release_b(&block)
291
+ @on_release_b = block
292
+ end
293
+
294
+ def mainloop(&block)
295
+ @b = block # ブロックを登録
296
+ @continue_loop = true
297
+ loop do
298
+ break unless @continue_loop
299
+ @b.call
300
+ Kernel.sleep WAIT # ブロック1回処理ごとにウェイト
301
+ end
302
+ end
303
+
304
+ def break
305
+ Kernel.sleep 0.5 ###
306
+ @continue_loop = false
307
+ end
308
+
309
+ def close(delay = 0)
310
+ @continue_thread = false
311
+ Kernel.sleep 1
312
+ until @q.empty? # wait for until command queue has been empty
313
+ Kernel.sleep 0.5
314
+ # Kernel.puts "sync #{@q.size}"
315
+ end
316
+ Kernel.sleep 0.5 + delay / 1000
317
+ @thread_w.kill
318
+ @thread_r.kill
319
+ @sp.close
320
+ end
321
+
322
+ def wait(ms)
323
+ @q << ["SLEEP %03d\n" % [ms], WAIT + ms / 1000.0]
324
+ end
325
+
326
+ def reset
327
+ @q << "RESET \n"
328
+ end
329
+
330
+ def port
331
+ @port
332
+ end
333
+
334
+ private def button_status
335
+ @button_down.each_key do |k|
336
+ change = @button_down[k] ^ @button_down_last[k] # Get change of mouse press status (XOR)
337
+ press = change & @button_down[k] # Detect change to press from release
338
+ release = change & !@button_down[k] # Detect change to release from press
339
+ @button_press[k] = press # Set mouse_press status
340
+ @button_release[k] = release # Set mouse_release status
341
+ @button_down_last[k] = @button_down[k]
342
+ end
343
+ end
344
+
345
+ private def event_proc
346
+ @on_press_a.call if @on_press_a and @button_press[:a]
347
+ @on_press_b.call if @on_press_b and @button_press[:b]
348
+ @on_release_a.call if @on_release_a and @button_release[:a]
349
+ @on_release_b.call if @on_release_b and @button_release[:b]
350
+ end
351
+
352
+ def button_down?(k)
353
+ @button_down[k]
354
+ end
355
+
356
+ # Pending...
357
+ #def button_press?(k)
358
+ # status = @button_press[k]
359
+ # @button_press[k] = false if @button_press[k] # avoid continuous judgment
360
+ # status
361
+ #end
362
+
363
+ # Pending...
364
+ #def button_release?(k)
365
+ # status = @button_release[k]
366
+ # @button_release[k] = false if @button_release[k] # avoid continuous judgment
367
+ # status
368
+ #end
369
+
370
+ def led_on(x = nil, y = nil)
371
+ if (x == nil and y == nil)
372
+ @q << ["LEDfil\n", WAIT]
373
+ else
374
+ @q << ["LEDon #{x}#{y}\n", WAIT]
375
+ end
376
+ end
377
+
378
+ def led_off(x = nil, y = nil)
379
+ if (x == nil and y == nil)
380
+ @q << ["LEDclr\n", WAIT]
381
+ else
382
+ @q << ["LEDoff#{x}#{y}\n", WAIT]
383
+ end
384
+ end
385
+
386
+ def led_turn(x, y)
387
+ @q << ["LEDtrn#{x}#{y}\n", WAIT]
388
+ end
389
+
390
+ def led_puts(str)
391
+ @q << ["LEDput#{str}\n", WAIT + str.length * 0.5]
392
+ end
393
+
394
+ def led_show(pattern)
395
+ ptn = []
396
+ 5.times do |j|
397
+ ptn[j] = 0
398
+ 5.times do |i|
399
+ ptn[j] += (2 ** (4 - i)) unless pattern[j][i] == 0 # 5-digits to decimal
400
+ end
401
+ end
402
+ #Kernel.p ptn
403
+ @q << ["LEDshw%02d%02d%02d%02d%02d\n" % ptn, WAIT]
404
+ end
405
+
406
+ def sound_volume=(v)
407
+ @q << ["SNDvol#{v}\n", WAIT]
408
+ @volume = v
409
+ end
410
+
411
+ def sound_tempo=(bpm)
412
+ @q << ["SNDbpm#{bpm}\n", WAIT]
413
+ @bpm = bpm
414
+ end
415
+
416
+ def sound_play(freq, beat)
417
+ @q << ["SNDply%04d%s\n" % [TONE[freq], _beat(beat)], WAIT * 0 + 60.0 / @bpm * beat] ###
418
+ end
419
+
420
+ def sound_rest(beat)
421
+ @q << ["SNDrst%s\n" % [_beat(beat)], WAIT + 60.0 / @bpm * beat]
422
+ end
423
+
424
+ private def _beat(beat)
425
+ if beat >= 4.0 # Whole note
426
+ return "4"
427
+ elsif beat >= 2.0 # Half note
428
+ return "2"
429
+ elsif beat >= 1.0 # Quarter note
430
+ return "1"
431
+ elsif beat >= 0.5 # Eighth note
432
+ return "/2"
433
+ elsif beat >= 0.25 # Sixteenth note
434
+ return "/4"
435
+ elsif beat >= 0.125 # Thirty-second note
436
+ return "/8"
437
+ else # Sixty-fourth note
438
+ return "/16"
439
+ end
440
+ end
441
+
442
+ alias_method :on, :led_on
443
+ alias_method :off, :led_off
444
+ alias_method :turn, :led_turn
445
+ alias_method :show, :led_show
446
+ alias_method :puts, :led_puts
447
+
448
+ alias_method :play, :sound_play
449
+ alias_method :rest, :sound_rest
450
+ alias_method :volume=, :sound_volume=
451
+ alias_method :tempo=, :sound_tempo=
452
+
453
+ end
454
+
455
+ end
456
+
@@ -0,0 +1,153 @@
1
+ function sound_rest (text: string) {
2
+ if (text == "1") {
3
+ music.rest(music.beat(BeatFraction.Whole))
4
+ } else if (text == "2") {
5
+ music.rest(music.beat(BeatFraction.Double))
6
+ } else if (text == "4") {
7
+ music.rest(music.beat(BeatFraction.Breve))
8
+ } else if (text == "/2") {
9
+ music.rest(music.beat(BeatFraction.Half))
10
+ } else if (text == "/4") {
11
+ music.rest(music.beat(BeatFraction.Quarter))
12
+ } else if (text == "/8") {
13
+ music.rest(music.beat(BeatFraction.Eighth))
14
+ } else if (text == "/16") {
15
+ music.rest(music.beat(BeatFraction.Sixteenth))
16
+ } else {
17
+
18
+ }
19
+ }
20
+ function sound_play (fq: number, text: string) {
21
+ if (text == "1") {
22
+ music.playTone(fq, music.beat(BeatFraction.Whole))
23
+ } else if (text == "2") {
24
+ music.playTone(fq, music.beat(BeatFraction.Double))
25
+ } else if (text == "4") {
26
+ music.playTone(fq, music.beat(BeatFraction.Breve))
27
+ } else if (text == "/2") {
28
+ music.playTone(fq, music.beat(BeatFraction.Half))
29
+ } else if (text == "/4") {
30
+ music.playTone(fq, music.beat(BeatFraction.Quarter))
31
+ } else if (text == "/8") {
32
+ music.playTone(fq, music.beat(BeatFraction.Eighth))
33
+ } else if (text == "/16") {
34
+ music.playTone(fq, music.beat(BeatFraction.Sixteenth))
35
+ } else {
36
+
37
+ }
38
+ }
39
+ let button_b = 0
40
+ let button_a = 0
41
+ let ms = 0
42
+ let y = 0
43
+ let x = 0
44
+ let bpm = 0
45
+ let volume = 0
46
+ let freq = 0
47
+ let value = 0
48
+ let param = ""
49
+ let cmd = ""
50
+ let recv = ""
51
+ serial.setRxBufferSize(128)
52
+ basic.showLeds(`
53
+ # # . # #
54
+ # . . . #
55
+ . . . . .
56
+ # . . . #
57
+ # # . # #
58
+ `)
59
+ basic.forever(function () {
60
+ let leng: string;
61
+ recv = serial.readLine()
62
+ cmd = recv.substr(0, 6)
63
+ if (cmd == "LEDshw") {
64
+ for (let j = 0; j <= 4; j++) {
65
+ param = recv.substr(6 + j * 2, 2)
66
+ value = parseFloat(param)
67
+ for (let i = 0; i <= 4; i++) {
68
+ if (value % 2 == 1) {
69
+ led.plot(4 - i, j)
70
+ value = value - 1
71
+ } else {
72
+ led.unplot(4 - i, j)
73
+ }
74
+ value = value / 2
75
+ }
76
+ }
77
+ } else if (recv == "LEDclr") {
78
+ basic.showLeds(`
79
+ . . . . .
80
+ . . . . .
81
+ . . . . .
82
+ . . . . .
83
+ . . . . .
84
+ `)
85
+ } else if (recv == "LEDfil") {
86
+ basic.showLeds(`
87
+ # # # # #
88
+ # # # # #
89
+ # # # # #
90
+ # # # # #
91
+ # # # # #
92
+ `)
93
+ } else if (cmd == "LEDput") {
94
+ param = recv.substr(6, recv.length - 6)
95
+ basic.showString(param)
96
+ } else if (cmd == "SNDply") {
97
+ param = recv.substr(6, 4)
98
+ freq = parseFloat(param)
99
+ leng = recv.substr(10, recv.length - 9)
100
+ sound_play(freq, leng)
101
+ } else if (cmd == "SNDrst") {
102
+ param = recv.substr(6, 3)
103
+ leng = param
104
+ sound_rest(leng)
105
+ } else if (cmd == "SNDvol") {
106
+ param = recv.substr(6, 3)
107
+ volume = parseFloat(param)
108
+ music.setVolume(volume)
109
+ } else if (cmd == "SNDbpm") {
110
+ param = recv.substr(6, 3)
111
+ bpm = parseFloat(param)
112
+ music.setTempo(bpm)
113
+ } else if (cmd == "LEDon ") {
114
+ param = recv.substr(6, 1)
115
+ x = parseFloat(param)
116
+ param = recv.substr(7, 1)
117
+ y = parseFloat(param)
118
+ led.plot(x, y)
119
+ } else if (cmd == "LEDoff") {
120
+ param = recv.substr(6, 1)
121
+ x = parseFloat(param)
122
+ param = recv.substr(7, 1)
123
+ y = parseFloat(param)
124
+ led.unplot(x, y)
125
+ } else if (cmd == "LEDtrn") {
126
+ param = recv.substr(6, 1)
127
+ x = parseFloat(param)
128
+ param = recv.substr(7, 1)
129
+ y = parseFloat(param)
130
+ led.toggle(x, y)
131
+ } else if (cmd == "RESET ") {
132
+ control.reset()
133
+ } else if (cmd == "SLEEP ") {
134
+ param = recv.substr(6, 3)
135
+ ms = parseFloat(param)
136
+ basic.pause(ms)
137
+ } else {
138
+
139
+ }
140
+ })
141
+ basic.forever(function () {
142
+ if (input.buttonIsPressed(Button.A)) {
143
+ button_a = 1
144
+ } else {
145
+ button_a = 0
146
+ }
147
+ if (input.buttonIsPressed(Button.B)) {
148
+ button_b = 1
149
+ } else {
150
+ button_b = 0
151
+ }
152
+ serial.writeNumbers([input.acceleration(Dimension.X), input.acceleration(Dimension.Y), input.acceleration(Dimension.Z), input.rotation(Rotation.Pitch), input.rotation(Rotation.Roll), input.lightLevel(), input.temperature(), button_a, button_b])
153
+ })