Skip to content

Commit

Permalink
EV3 Bluetooth connection and tone working
Browse files Browse the repository at this point in the history
  • Loading branch information
justincinmd committed Mar 24, 2014
1 parent 682a366 commit 45b31b5
Show file tree
Hide file tree
Showing 15 changed files with 481 additions and 16 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ _yardoc
coverage
doc/
lib/bundler/man
local_helpers
pkg
rdoc
spec/reports
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Ruby::Ev3
# Ruby::EV3

TODO: Write a gem description

Expand All @@ -22,7 +22,7 @@ TODO: Write usage instructions here

## Contributing

1. Fork it ( http://github.com/<my-github-username>/ruby-ev3/fork )
1. Fork it (http://github.com/jcnnghm/ruby-ev3/fork)
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
Expand Down
9 changes: 9 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -1 +1,10 @@
require "bundler/gem_tasks"

desc "Open an irb session preloaded with this library"
task :console do
sh "irb -rubygems -I lib -r ev3.rb"
end

Dir["tasks/**/*.rake"].each do |file|
load(file)
end
36 changes: 36 additions & 0 deletions lib/ev3.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
require "ev3/brick"
require "ev3/commands"
require "ev3/constants"
require "ev3/version"

module EV3
# Helper to reload the gem in dev.
def self.reload!
$".grep(/lib\/ev3/).each{ |f| load(f) if File.exists?(f) }
end
end

class Integer
# Convert to EV3 variable data
def to_ev3_data
[0b1000_0011] + self.to_little_endian_byte_array(4)
end

# Convert the number to an array of little endian bytes
#
# @param [Integer] number_of_bytes that should represent the integer
def to_little_endian_byte_array(number_of_bytes = 4)
raise NotImplementedError if self < 0
bytes = []
tmp_number = self
while tmp_number.abs > 0
bytes << (tmp_number & 0xFF)
tmp_number = tmp_number >> 8
end
bytes = bytes[0..(number_of_bytes - 1)]
until bytes.size == number_of_bytes
bytes << 0
end
bytes
end
end
35 changes: 35 additions & 0 deletions lib/ev3/brick.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
module EV3
class Brick
attr_reader :connection

# Create a new brick connection
#
# @param [instance subclassing Connections::Base] connection to the brick
def initialize(connection)
@connection = connection
end

# Connect to the EV3
def connect
self.connection.connect
end

# Play a short beep on the EV3
def beep
self.execute(Commands::SoundTone.new)
end

# Play a tone on the EV3 using the specified options
def play_tone(volume, frequency, duration)
command = Commands::SoundTone.new(volume, frequency, duration)
self.execute(command)
end

# Execute the command
#
# @param [instance subclassing Commands::Base] command to execute
def execute(command)
self.connection.write(command)
end
end
end
3 changes: 3 additions & 0 deletions lib/ev3/commands.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
require 'ev3/commands/base'

require 'ev3/commands/sound_tone'
62 changes: 62 additions & 0 deletions lib/ev3/commands/base.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
module EV3
module Commands
class Base
attr_accessor :sequence_number

def initialize(local_variables = 0, global_variables = 0)
@local_variables = local_variables
@global_variables = global_variables

# Sequence number is currently unused, so I am setting it to zero
self.sequence_number = 0
@bytes = []
end

# Converts the command to an array of bytes to send to the EV3
def to_bytes
message = self.sequence_number.to_little_endian_byte_array(2) + variable_size_bytes + @bytes.clone

# The message is proceeded by the message length
message.size.to_little_endian_byte_array(2) + message
end

# Append a byte or multiple bytes to the command
#
# @param [Integer, Array<Integer>] byte_or_bytes to append to the command
def <<(byte_or_bytes)
bytes = byte_or_bytes.is_a?(Array) ? byte_or_bytes : [byte_or_bytes]
bytes.each { |byte| @bytes << byte }
end

# String representation of the command
def to_s
"#{self.class.name}: #{self.to_bytes.map{|byte| byte.to_s(16)}.join(', ')}"
end

# Raises an exception if the value isn't found in the range
#
# @param [Integer] value to check against the range
# @param [String] name of the variable, for the exception message
# @param [Range(Integer)] range the value should be in
def validate_range!(value, variable_name, range)
raise(ArgumentError, "#{variable_name} should be between #{range.min} and #{range.max}") unless range.include?(value)
end

private

def variable_size_bytes
# Byte 6 Byte 5
# 76543210 76543210
# -------- --------
# llllllgg gggggggg
#
# gg gggggggg Global variables [0..MAX_COMMAND_GLOBALS]
# llllll Local variables [0..MAX_COMMAND_LOCALS]
[
(@global_variables & 0xFF),
(((@local_variables << 2) & 0b1111_1100) | (((@global_variables >> 8) & 0b0000_0011)))
]
end
end
end
end
25 changes: 25 additions & 0 deletions lib/ev3/commands/sound_tone.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module EV3
module Commands
class SoundTone < Base
# Creates a new sound tone command
#
# @param [Integer] Volume of the sound [0..100]
# @param [Integer] Frequency in Hertz [0..50,000]
# @param [Integer] Duration in miliseconds [0..1 Year]
def initialize(volume = 50, frequency = 1000, duration = 500)
super()

validate_range!(volume, 'volume', 0..100)
validate_range!(frequency, 'frequency', 0..50_000) # 0 - 50,000 Hz
validate_range!(duration, 'duration', 0..(1000 * 60 * 60 * 24 * 365)) # Up to a year

self << CommandType::DIRECT_COMMAND_NO_REPLY
self << ByteCodes::SOUND
self << SoundSubCodes::TONE
self << volume.to_ev3_data
self << frequency.to_ev3_data
self << duration.to_ev3_data
end
end
end
end
14 changes: 14 additions & 0 deletions lib/ev3/connections/base.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module EV3
module Connections
class Base

def connect
raise NotImplementedError
end

def write(command)
raise NotImplementedError
end
end
end
end
37 changes: 37 additions & 0 deletions lib/ev3/connections/bluetooth.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
require 'ev3/connections/base'
require 'serialport'

module EV3
module Connections

class Bluetooth < Base
attr_reader :device

# Create a new bluetooth device
#
# @param [String] device on the Mac, a dev device, and on windows a com port
def initialize(device = '/dev/tty.EV3-SerialPort')
@device = device
@commands_sent = 0
end

def connect
@serial_port = ::SerialPort.new(@device, 57600, 8, 1, SerialPort::NONE)
@serial_port.flow_control = ::SerialPort::HARD
@serial_port.read_timeout = 5000
end

# Set the sequence number on the command and write it to the bluetooth connection
#
# @param [instance subclassing Commands::Base] command to execute
def write(command)
command.sequence_number = @commands_sent
command.to_bytes.each do |b|
@serial_port.putc b
end
@commands_sent += 1
end
end

end
end
Loading

0 comments on commit 45b31b5

Please sign in to comment.