diff --git a/examples/analog_io/joystick.rb b/examples/analog_io/joystick.rb new file mode 100644 index 0000000..caf258d --- /dev/null +++ b/examples/analog_io/joystick.rb @@ -0,0 +1,31 @@ +# +# Use a generic 2-axis (double potentiometer) joystick. +# +require 'bundler/setup' +require 'denko' + +X_PIN = :A1 +Y_PIN = :A2 + +board = Denko::Board.new(Denko::Connection::Serial.new) +joystick = Denko::AnalogIO::Joystick.new board: board, + pins: {x: X_PIN, y: Y_PIN}, + invert_x: true, + invert_y: true, + # maxzone: 98, # as percentage + deadzone: 2 # as percentage + +# Listen to both analog inputs at 250 Hz. +# joystick.listen(4) + +# Simple 60Hz game loop. +loop do + last_tick = Time.now + + # If listening, joystick.state is constantly updated in the background. + # Otherwise, #read must be called each tick to update it. + joystick.read + puts joystick.state.inspect + + sleep(0.0001) while (Time.now - last_tick < 0.0166) +end diff --git a/lib/denko/analog_io.rb b/lib/denko/analog_io.rb index 83916f5..8a50601 100644 --- a/lib/denko/analog_io.rb +++ b/lib/denko/analog_io.rb @@ -4,6 +4,7 @@ module AnalogIO autoload :Input, "#{__dir__}/analog_io/input" autoload :Output, "#{__dir__}/analog_io/output" autoload :Potentiometer, "#{__dir__}/analog_io/potentiometer" + autoload :Joystick, "#{__dir__}/analog_io/joystick" autoload :ADS111X, "#{__dir__}/analog_io/ads111x" autoload :ADS1100, "#{__dir__}/analog_io/ads1100" autoload :ADS1115, "#{__dir__}/analog_io/ads1115" diff --git a/lib/denko/analog_io/joystick.rb b/lib/denko/analog_io/joystick.rb new file mode 100644 index 0000000..67473e3 --- /dev/null +++ b/lib/denko/analog_io/joystick.rb @@ -0,0 +1,65 @@ +module Denko + module AnalogIO + class Joystick + include Behaviors::MultiPin + include Behaviors::Lifecycle + + def initialize_pins(options={}) + proxy_pin(:x, AnalogIO::Input) + proxy_pin(:y, AnalogIO::Input) + end + + after_initialize do + # Midpoint as float + @mid = board.adc_high / 2.0 + + # Invert settings as +1 or -1 multipliers + @invert_x = params[:invert_x] ? -1 : 1 + @invert_y = params[:invert_y] ? -1 : 1 + + # Deadzones as percentages + @deadzone = 0 + @maxzone = @mid + @deadzone = ((params[:deadzone] * @mid) / 100).round if params[:deadzone] + @maxzone = ((params[:maxzone] * @mid) / 100).round if params[:maxzone] + + # Per axis callbacks + x.on_data { |value| state[:x] = raw_to_percent(value, @invert_x) } + y.on_data { |value| state[:y] = raw_to_percent(value, @invert_y) } + end + + def state + @state ||= { x: nil, y: nil } + end + + def raw_to_percent(value, invert) + float = (value - @mid) * invert + abs = float.abs + if abs < @deadzone + return 0 + elsif abs > @maxzone + return (float > 0) ? 100 : -100 + else + return ((float * 100) / @mid).round + end + end + + def read + # Blocking read only for last axis read. + x._read + y.read + state + end + + def listen(divider=16) + x.listen(divider) + y.listen(divider) + end + + def stop + x.stop + y.stop + end + end + end +end