From 45ce6e2dc010db41394bcb824c414cb2331e8dba Mon Sep 17 00:00:00 2001 From: Nick Barone Date: Sat, 3 Aug 2019 16:02:42 -0700 Subject: [PATCH] Get a websocket frame stream working (base64 encoded) --- server/Gemfile | 1 + server/Gemfile.lock | 17 +++++++ server/lib/integer_patches.rb | 13 +++++ server/lib/serial.rb | 61 ++++++---------------- server/server.rb | 95 ++++++++++++++++++++++++++--------- server/spec/spec_helper.rb | 8 --- server/tmp/rspec_state.txt | 42 ++++++++-------- 7 files changed, 138 insertions(+), 99 deletions(-) create mode 100644 server/lib/integer_patches.rb diff --git a/server/Gemfile b/server/Gemfile index 4607c72..90214fa 100644 --- a/server/Gemfile +++ b/server/Gemfile @@ -2,6 +2,7 @@ source 'https://rubygems.org' ruby '2.5.5' gem 'sinatra' +gem 'sinatra-websocket' gem 'slim' gem 'rubyserial' diff --git a/server/Gemfile.lock b/server/Gemfile.lock index a82d5f5..724a59a 100644 --- a/server/Gemfile.lock +++ b/server/Gemfile.lock @@ -1,14 +1,22 @@ GEM remote: https://rubygems.org/ specs: + addressable (2.6.0) + public_suffix (>= 2.0.2, < 4.0) coderay (1.1.2) + daemons (1.3.1) diff-lcs (1.3) + em-websocket (0.3.8) + addressable (>= 2.1.1) + eventmachine (>= 0.12.9) + eventmachine (1.2.7) ffi (1.11.1) method_source (0.9.2) mustermann (1.0.3) pry (0.12.2) coderay (~> 1.1.0) method_source (~> 0.9.0) + public_suffix (3.1.1) rack (2.0.7) rack-protection (2.0.5) rack @@ -32,10 +40,18 @@ GEM rack (~> 2.0) rack-protection (= 2.0.5) tilt (~> 2.0) + sinatra-websocket (0.3.1) + em-websocket (~> 0.3.6) + eventmachine + thin (>= 1.3.1, < 2.0.0) slim (4.0.1) temple (>= 0.7.6, < 0.9) tilt (>= 2.0.6, < 2.1) temple (0.8.1) + thin (1.7.2) + daemons (~> 1.0, >= 1.0.9) + eventmachine (~> 1.0, >= 1.0.4) + rack (>= 1, < 3) tilt (2.0.9) PLATFORMS @@ -46,6 +62,7 @@ DEPENDENCIES rspec rubyserial sinatra + sinatra-websocket slim RUBY VERSION diff --git a/server/lib/integer_patches.rb b/server/lib/integer_patches.rb new file mode 100644 index 0000000..945f786 --- /dev/null +++ b/server/lib/integer_patches.rb @@ -0,0 +1,13 @@ +module IntegerPatches + def to_hex_s + "0x" + to_s(16).upcase.rjust(6, '0') + end + + def to_chr_bytes + ((self & 0xFF0000) >> 16).chr + + ((self & 0x00FF00) >> 8).chr + + ((self & 0x0000FF) ).chr + end +end + +Integer.include IntegerPatches diff --git a/server/lib/serial.rb b/server/lib/serial.rb index abce2de..efadbec 100644 --- a/server/lib/serial.rb +++ b/server/lib/serial.rb @@ -1,15 +1,17 @@ require 'rubyserial' -module Arduino +module PrintHex + def to_hex_s + "0x" + to_s(16).upcase.rjust(6, '0') + end +end +Integer.include PrintHex +module Arduino class SerialOut - attr_accessor :playing, :frame - attr_reader :ports, :connections, :func, :frame_size, :slice_size - - def initialize &func - @func = func - @frame = func.call + attr_reader :ports, :connections, :slice_size + def initialize # Establish the connections @connections = if RUBY_PLATFORM =~ /darwin/ # MacOS @ports = `ls /dev/cu.*`.split("\n").select{|s| s =~ /cu.usbmodem/} @@ -21,47 +23,14 @@ def initialize &func end end - def play - @playing = true - if @connections.size == 0 - @frame = fake_play - else - @frame = real_play - end - @playing = false - @frame - end - - private - - def fake_play - @frame = func.call - while(@playing) do - # Render the frame, and do nothing with it.... - @frame = func.call - sleep(0.1) - end - @frame - end - - def real_play - # Get a first frame for sizing - @frame = func.call - @frame_size = frame.size - @slice_size = (@frame_size/@connections.size.to_f).round - - while(@playing) do - # Render the frame - @frame = func.call - - # Split the frame amongst the connections - @frame.each_slice( @slice_size ).with_index do |slice, index| - @connections[index].write(slice.map(&:chr).join("")) + def write frame + if @connections.any? + slice_size = (frame.size/@connections.size.to_f).round + frame.each_slice( slice_size ).with_index do |slice, index| + byte_string = slice.map(&:to_chr_bytes).join("") + @connections[index].write(byte_string) end end - - # Return the last frame - @frame end end end diff --git a/server/server.rb b/server/server.rb index eecbd11..bdf76bd 100644 --- a/server/server.rb +++ b/server/server.rb @@ -1,42 +1,89 @@ require 'sinatra' +require 'sinatra-websocket' Dir[File.join("./lib", "**/*.rb")].each do |f| require f end -module PrintHex - def to_hex_s - "0x" + to_s(16).upcase.rjust(6, '0') +require 'pry' + +set :server, 'thin' +set :sockets, [] +set :num_leds, 300 +set :version, File.read('.version').chomp + +class Renderer + attr_accessor :pattern, :num_leds + + def initialize pattern, num_leds + @pattern = pattern + @num_leds = num_leds + end + + def render + t = Time.now + c = Effects::Context.new(@num_leds, t: t) + (0..@num_leds).collect do |i| + c.position = i + @pattern.apply(c) + c.color + end end end -Integer.include PrintHex - -NUM_LEDS = 300 -$version = File.read('.version').chomp -$active_pattern = Compositions::MASTERAVERBAITER_V1 -$s = Arduino::SerialOut.new do - t = Time.now - c = Effects::Context.new(NUM_LEDS, t: t) - (0..NUM_LEDS).collect do |i| - c.position = i - $active_pattern.apply(c) - c.color + +set :renderer, Renderer.new(Compositions::MASTERAVERBAITER_V1, settings.num_leds) + +set :playing, true + +def play + serial_out = Arduino::SerialOut.new + + Thread.new do + while(settings.playing) + # Render frame + frame = settings.renderer.render + + # Write out to serial ports + serial_out.write frame + + # Write out to websockets + settings.sockets.each do |s| + byte_string = frame.map(&:to_chr_bytes).join("") + s.send(Base64.encode64(byte_string)) + end + + # Give other threads some time + sleep(0.1) + end end end get '/' do - "Butterfly Server #{$version} #{NUM_LEDS} #{$s.playing}" + "Butterfly Server #{settings.version} #{settings.num_leds}" end get '/frame' do - $s.frame.map{|c| c.to_hex_s}.join("\n") - #$frame.map{|c| c.to_hex_s}.join("\n") + settings.renderer.render.map{|c| c.to_hex_s}.join("\n") +end + +get '/subscribe' do + if request.websocket? + request.websocket do |ws| + ws.onopen do + settings.sockets << ws + end + + ws.onclose do + settings.sockets.delete(ws) + end + end + else + "for websockets only" + end end -# get '/debug' do -# require 'pry' -# binding.pry -# $s.frame.map{|c| c.to_hex_s}.join("\n") -# end +get '/pry' do + binding.pry +end -$t = Thread.new { $s.play } +$t = play diff --git a/server/spec/spec_helper.rb b/server/spec/spec_helper.rb index 221c74a..452a359 100644 --- a/server/spec/spec_helper.rb +++ b/server/spec/spec_helper.rb @@ -3,14 +3,6 @@ require f end -module PrintHex - def to_hex_s - "0x" + to_s(16).upcase.rjust(6, '0') - end -end - -Integer.include PrintHex - RSpec.configure do |config| config.expect_with :rspec do |expectations| diff --git a/server/tmp/rspec_state.txt b/server/tmp/rspec_state.txt index 099116e..fda6924 100644 --- a/server/tmp/rspec_state.txt +++ b/server/tmp/rspec_state.txt @@ -1,28 +1,28 @@ example_id | status | run_time | ----------------------------------------------------- | ------ | --------------- | -./spec/lib/colors_spec.rb[1:1:1] | passed | 0.00008 seconds | -./spec/lib/colors_spec.rb[1:1:2] | passed | 0.00007 seconds | +./spec/lib/colors_spec.rb[1:1:1] | passed | 0.00006 seconds | +./spec/lib/colors_spec.rb[1:1:2] | passed | 0.00005 seconds | ./spec/lib/colors_spec.rb[1:1:3] | passed | 0.00007 seconds | ./spec/lib/colors_spec.rb[1:1:4] | passed | 0.00008 seconds | -./spec/lib/compositions/masteraverbaiter_spec.rb[1:1] | passed | 0.00094 seconds | -./spec/lib/compositions/masteraverbaiter_spec.rb[1:2] | passed | 0.00271 seconds | -./spec/lib/effects/core/pulse_spec.rb[1:1] | passed | 0.00017 seconds | -./spec/lib/effects/core/pulse_spec.rb[1:2] | passed | 0.00016 seconds | -./spec/lib/effects/core/pulse_spec.rb[1:3] | passed | 0.00014 seconds | -./spec/lib/effects/core/pulse_spec.rb[1:4] | passed | 0.00018 seconds | -./spec/lib/effects/core/wheel_spec.rb[1:1] | passed | 0.00012 seconds | -./spec/lib/effects/core/wheel_spec.rb[1:2] | passed | 0.00016 seconds | -./spec/lib/effects/core/wheel_spec.rb[1:3] | passed | 0.00008 seconds | +./spec/lib/compositions/masteraverbaiter_spec.rb[1:1] | passed | 0.00093 seconds | +./spec/lib/compositions/masteraverbaiter_spec.rb[1:2] | passed | 0.00345 seconds | +./spec/lib/effects/core/pulse_spec.rb[1:1] | passed | 0.00016 seconds | +./spec/lib/effects/core/pulse_spec.rb[1:2] | passed | 0.00019 seconds | +./spec/lib/effects/core/pulse_spec.rb[1:3] | passed | 0.00019 seconds | +./spec/lib/effects/core/pulse_spec.rb[1:4] | passed | 0.00016 seconds | +./spec/lib/effects/core/wheel_spec.rb[1:1] | passed | 0.00096 seconds | +./spec/lib/effects/core/wheel_spec.rb[1:2] | passed | 0.00011 seconds | +./spec/lib/effects/core/wheel_spec.rb[1:3] | passed | 0.00009 seconds | ./spec/lib/effects/core/wipe_spec.rb[1:1] | passed | 0.00005 seconds | -./spec/lib/effects/meta/composition_spec.rb[1:1] | passed | 0.00124 seconds | -./spec/lib/effects/meta/composition_spec.rb[1:2:1] | passed | 0.00017 seconds | -./spec/lib/effects/meta/composition_spec.rb[1:2:2] | passed | 0.00012 seconds | -./spec/lib/effects/meta/moving_spec.rb[1:1] | passed | 0.00011 seconds | -./spec/lib/effects/meta/moving_spec.rb[1:2] | passed | 0.00011 seconds | +./spec/lib/effects/meta/composition_spec.rb[1:1] | passed | 0.00028 seconds | +./spec/lib/effects/meta/composition_spec.rb[1:2:1] | passed | 0.00018 seconds | +./spec/lib/effects/meta/composition_spec.rb[1:2:2] | passed | 0.00008 seconds | +./spec/lib/effects/meta/moving_spec.rb[1:1] | passed | 0.00008 seconds | +./spec/lib/effects/meta/moving_spec.rb[1:2] | passed | 0.00007 seconds | ./spec/lib/effects/meta/moving_spec.rb[1:3] | passed | 0.00014 seconds | -./spec/lib/effects/meta/moving_spec.rb[1:4] | passed | 0.00007 seconds | -./spec/lib/effects/meta/moving_spec.rb[1:5] | passed | 0.00008 seconds | +./spec/lib/effects/meta/moving_spec.rb[1:4] | passed | 0.00011 seconds | +./spec/lib/effects/meta/moving_spec.rb[1:5] | passed | 0.00011 seconds | ./spec/lib/effects/meta/moving_spec.rb[1:6] | passed | 0.0001 seconds | -./spec/lib/effects_spec.rb[1:1] | passed | 0.00005 seconds | -./spec/lib/effects_spec.rb[1:2] | passed | 0.00005 seconds | -./spec/lib/effects_spec.rb[1:3] | passed | 0.00007 seconds | +./spec/lib/effects_spec.rb[1:1] | passed | 0.00006 seconds | +./spec/lib/effects_spec.rb[1:2] | passed | 0.00009 seconds | +./spec/lib/effects_spec.rb[1:3] | passed | 0.00005 seconds |