littlewire 0.9.7 → 0.9.8

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.
@@ -1,18 +1,20 @@
1
- # A little library for a little wire, by Bluebie
2
- # Provides an arduino or wiring style interface to the LittleWire device's IO features
3
- # and provides a nicer invented ruby-style interface also.
4
1
  require 'libusb'
5
2
  class LittleWire; end
6
- require_relative 'digital'
7
- require_relative 'analog'
8
- require_relative 'hardware-pwm'
9
- require_relative 'software-pwm'
10
- require_relative 'servo'
11
- require_relative 'spi'
12
- require_relative 'i2c'
13
- require_relative 'one-wire'
14
- require_relative 'ws2811'
3
+ require_relative 'littlewire/digital'
4
+ require_relative 'littlewire/analog'
5
+ require_relative 'littlewire/hardware-pwm'
6
+ require_relative 'littlewire/software-pwm'
7
+ require_relative 'littlewire/servo'
8
+ require_relative 'littlewire/spi'
9
+ require_relative 'littlewire/i2c'
10
+ require_relative 'littlewire/one-wire'
11
+ require_relative 'littlewire/ws2811'
12
+ require_relative 'littlewire/version'
15
13
 
14
+ # A little library for a little wire, by Bluebie
15
+ # Provides an arduino or wiring style interface to the LittleWire device's IO features
16
+ # and provides a nicer invented ruby-style interface also.
17
+ #
16
18
  # LittleWire class represents LittleWire's connected to your computer via USB
17
19
  #
18
20
  # Most of the time you'll only have one LittleWire - in this case, use LittleWire.connect to get
@@ -66,7 +68,7 @@ class LittleWire
66
68
  softpwm_c: [:software_pwm, :softpwm_c],
67
69
  }
68
70
 
69
- SupportedVersions = ['1.2', '1.1', '1.0'] # in order of newness. # TODO: Add version 1.0?
71
+ SupportedVersions = ['1.2', '1.1', '1.0'] # in order of newness.
70
72
 
71
73
 
72
74
  # An array of all unclaimed littlewires connected to computer via USB
@@ -95,7 +97,7 @@ class LittleWire
95
97
 
96
98
  # shut everything down, trying to setup littlewire in consistent initial state in case previous programs
97
99
  # messed with it's state
98
- self.software_pwm_enabled = false
100
+ self.software_pwm_enabled = false if version_hex >= 0x11
99
101
  self.hardware_pwm_enabled = false
100
102
  self.pin_mode(pin1: :input, pin2: :input, pin3: :input, pin4: :input)
101
103
  self.digital_write(pin1: :gnd, pin2: :gnd, pin3: :gnd, pin4: :gnd)
File without changes
@@ -81,7 +81,7 @@ module LittleWire::Digital
81
81
  # Multiple pins can be set using `my_wire.pin_mode(:pin1 => :input, :pin2 => :output)`
82
82
  #
83
83
  # :input mode leaves the pin disconnected if `digital_write`n to false, or connected
84
- # via a 20kohm resistor to 5 volts if `digital_write`n to true.
84
+ # via a 20kohm resistor to 5 volts if `digital_write` to true.
85
85
  #
86
86
  # :output mode connects the pin directly to 5 volts when `digital_write`n to true
87
87
  # and connects it to ground when digitally written to false. Be careful not to create
@@ -0,0 +1,171 @@
1
+ require 'libusb'
2
+
3
+ # Abstracts access to micronucleus avr tiny85 bootloader - can be used only to erase and upload bytes
4
+ class Micronucleus
5
+ Functions = [
6
+ :get_info,
7
+ :write_page,
8
+ :erase_application,
9
+ :run_program
10
+ ]
11
+
12
+ # return all micronucleus devices connected to computer
13
+ def self.all
14
+ usb = LIBUSB::Context.new
15
+ usb.devices.select { |device|
16
+ device.idVendor == 0x16d0 && device.idProduct == 0x0753
17
+ }.map { |device|
18
+ self.new(device)
19
+ }
20
+ end
21
+
22
+ def initialize devref
23
+ @device = devref
24
+ end
25
+
26
+ def info
27
+ unless @info
28
+ result = control_transfer(function: :get_info, dataIn: 4)
29
+ flash_length, page_size, write_sleep = result.unpack('S>CC')
30
+
31
+ @info = {
32
+ flash_length: flash_length,
33
+ page_size: page_size,
34
+ pages: (flash_length.to_f / page_size).ceil,
35
+ write_sleep: write_sleep.to_f / 1000.0,
36
+ version: "#{@device.bcdDevice >> 8}.#{(@device.bcdDevice & 0xFF).to_s.rjust(2, '0')}",
37
+ version_numeric: @device.bcdDevice
38
+ }
39
+ end
40
+
41
+ @info
42
+ end
43
+
44
+ def erase!
45
+ puts "erasing"
46
+ info = self.info
47
+ control_transfer(function: :erase_application)
48
+ info[:pages].times do
49
+ sleep(info[:write_sleep]) # sleep for as many pages as the chip has to erase
50
+ end
51
+ puts "erased chip"
52
+ end
53
+
54
+ # upload a new program
55
+ def program= bytestring
56
+ info = self.info
57
+ raise "Program too long!" if bytestring.bytesize > info[:flash_length]
58
+ bytes = bytestring.bytes.to_a
59
+ bytes.push(0xFF) while bytes.length < info[:flash_length]
60
+
61
+ erase!
62
+
63
+ address = 0
64
+ bytes.each_slice(info[:page_size]) do |slice|
65
+ slice.push(0xFF) while slice.length < info[:page_size] # ensure every slice is one page_size long - pad out if needed
66
+
67
+ puts "uploading @ #{address} of #{bytes.length}"
68
+ control_transfer(function: :write_page, wIndex: address, wValue: slice.length, dataOut: slice.pack('C*'))
69
+ sleep(info[:write_sleep])
70
+ address += slice.length
71
+ end
72
+ end
73
+
74
+ def finished
75
+ info = self.info
76
+
77
+ puts "asking device to finish writing"
78
+ control_transfer(function: :run_program)
79
+ puts "waiting for device to finish"
80
+
81
+ # sleep for as many pages as the chip could potentially need to write - this could be smarter
82
+ info[:pages].times do
83
+ sleep(info[:write_sleep])
84
+ end
85
+
86
+ @io.close
87
+ @io = nil
88
+ end
89
+
90
+ def inspect
91
+ "<Micronucleus #{info[:version]}: #{(info[:flash_length] / 1024.0).round(1)} kb programmable>"
92
+ end
93
+
94
+ protected
95
+ # raw opened device
96
+ def io
97
+ unless @io
98
+ @io = @device.open
99
+ end
100
+
101
+ @io
102
+ end
103
+
104
+ def control_transfer(opts = {})
105
+ opts[:bRequest] = Functions.index(opts.delete(:function)) if opts[:function]
106
+ io.control_transfer({
107
+ wIndex: 0,
108
+ wValue: 0,
109
+ bmRequestType: usb_request_type(opts),
110
+ timeout: 5000
111
+ }.merge opts)
112
+ end
113
+
114
+ # calculate usb request type
115
+ def usb_request_type opts #:nodoc:
116
+ value = LIBUSB::REQUEST_TYPE_VENDOR | LIBUSB::RECIPIENT_DEVICE
117
+ value |= LIBUSB::ENDPOINT_OUT if opts.has_key? :dataOut
118
+ value |= LIBUSB::ENDPOINT_IN if opts.has_key? :dataIn
119
+ return value
120
+ end
121
+ end
122
+
123
+ class HexProgram
124
+ def initialize input
125
+ @bytes = Hash.new(0xFF)
126
+ input = input.read if input.is_a? IO
127
+ parse input
128
+ end
129
+
130
+ def binary
131
+ bytes.pack('C*')
132
+ end
133
+
134
+ def bytes
135
+ highest_address = @bytes.keys.max
136
+
137
+ bytes = Array.new(highest_address + 1) { |index|
138
+ @bytes[index]
139
+ }
140
+ end
141
+
142
+ protected
143
+
144
+ def parse input_text
145
+ input_text.each_line do |line|
146
+ next unless line.start_with? ':'
147
+ line.chomp!
148
+ length = line[1..2].to_i(16) # usually 16 or 32
149
+ address = line[3..6].to_i(16) # 16-bit start address
150
+ record_type = line[7..8].to_i(16)
151
+ data = line[9... 9 + (length * 2)]
152
+ checksum = line[9 + (length * 2).. 10 + (length * 2)].to_i(16)
153
+ checksum_section = line[1...9 + (length * 2)]
154
+
155
+ checksum_calculated = checksum_section.chars.to_a.each_slice(2).map { |slice|
156
+ slice.join('').to_i(16)
157
+ }.reduce(0, &:+)
158
+
159
+ checksum_calculated = (((checksum_calculated % 256) ^ 0xFF) + 1) % 256
160
+
161
+ raise "Hex file checksum mismatch @ #{line} should be #{checksum_calculated.to_s(16)}" unless checksum == checksum_calculated
162
+
163
+ if record_type == 0 # data record
164
+ data_bytes = data.chars.each_slice(2).map { |slice| slice.join('').to_i(16) }
165
+ data_bytes.each_with_index do |byte, index|
166
+ @bytes[address + index] = byte
167
+ end
168
+ end
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,102 @@
1
+ class LittleWire
2
+ # get Wii Nunchuck interface
3
+ def nunchuck
4
+ @nunchuck ||= LittleWire::Nunchuck.new self
5
+ end
6
+ end
7
+
8
+ class LittleWire::Nunchuck
9
+ BusAddress = 82 # Address of Nunchuck on i2c bus
10
+
11
+ def initialize wire, interval = 0
12
+ @wire = wire
13
+
14
+ raise "Nunchuck requires LittleWire firmware 1.3 or newer" unless wire.version_hex >= 0x13
15
+
16
+ # config i2c to reliably talk with nunchuck devices
17
+ i2c.delay = interval # The fastest option of 0 seems to work. Neat!
18
+
19
+ # initialize the nunchuck
20
+ set_register 0xf0, 0x55
21
+ set_register 0xfb, 0x00
22
+ sample # do a sample, to get the ball rolling
23
+ end
24
+
25
+ def sample
26
+ # tell device to gather new sensor data
27
+ i2c.transmit BusAddress, [0]
28
+ # load sample in to PC
29
+ data = i2c.request(BusAddress, 6)
30
+
31
+ LittleWire::Nunchuck::NunchuckFrame.new(data)
32
+ end
33
+
34
+ private
35
+ def i2c
36
+ @i2c ||= @wire.i2c
37
+ end
38
+
39
+ def set_register register, value
40
+ i2c.transmit BusAddress, [register, value]
41
+ sleep(0.01)
42
+ end
43
+ end
44
+
45
+ class LittleWire::Nunchuck::NunchuckFrame
46
+ def initialize data
47
+ @data = data
48
+ @joystick = Vector3D.new((data[0] / 127.5) - 1.0, (data[1] / 127.5) - 1.0)
49
+ @buttons = Buttons.new(((data[5] >> 1) & 1) == 0, (data[5] & 1) == 0)
50
+
51
+ #calculate accelerometer values
52
+ @accelerometer = Vector3D.new(data[2] << 2, data[3] << 2, data[4] << 2)
53
+ @accelerometer.x |= (data[5] >> 2) & 0b11
54
+ @accelerometer.y |= (data[5] >> 4) & 0b11
55
+ @accelerometer.z |= (data[5] >> 6) & 0b11
56
+
57
+ @accelerometer.x = (@accelerometer.x / 511.5) - 1.0
58
+ @accelerometer.y = (@accelerometer.y / 511.5) - 1.0
59
+ @accelerometer.z = (@accelerometer.z / 511.5) - 1.0
60
+ end
61
+
62
+ attr_reader :joystick, :accelerometer, :buttons
63
+
64
+ def inspect
65
+ "<Nunchuck:#{buttons.inspect}:#{accelerometer.inspect}:#{joystick.inspect}"
66
+ end
67
+
68
+ class Vector3D
69
+ def initialize x=nil, y=nil, z=nil
70
+ @x,@y,@z = x,y,z
71
+ end
72
+
73
+ attr_accessor :x,:y,:z
74
+ def to_a; [@x,@y,@z].compact; end
75
+ def to_h; {x: @x, y: @y, z: @z}; end
76
+ def to_hash; to_h; end
77
+ def inspect
78
+ precision = 2
79
+ pretty = lambda { |n| (n >= 0 ? '+' : '-') + n.abs.round(precision).to_s.ljust(precision + 2, '0') }
80
+ numbers = to_a.map { |n| pretty[n] }
81
+ "<Coords:" + numbers.join(',') + ">"
82
+ end
83
+ end
84
+
85
+ class Buttons
86
+ def initialize c,z
87
+ @c,@z = c,z
88
+ end
89
+ attr_accessor :c, :z
90
+
91
+ # is button down?
92
+ def down? button
93
+ instance_variable_get("@#{button.to_s.downcase}")
94
+ end
95
+
96
+ def up? button
97
+ not down? button
98
+ end
99
+
100
+ def inspect; "<Buttons:#{'C' if @c}#{'Z' if @z}>"; end
101
+ end
102
+ end
@@ -0,0 +1,92 @@
1
+ class LittleWire::I2C
2
+ def initialize wire
3
+ @wire = wire
4
+ raise "i2c support requires littlewire firmware 1.2 or newer. Please update to firmware #{LittleWire::SupportedVersions.first} with the `littlewire.rb install #{LittleWire::SupportedVersions.first}` command!" if wire.version_hex < 0x12
5
+ warn "i2c delay support is buggy in firmware 1.2. Please update firmware to at least 1.3" if wire.version_hex == 0x12
6
+ @wire.control_transfer(function: :i2c_init, dataIn:8)
7
+ end
8
+
9
+ # start an i2c message
10
+ #
11
+ # Arguments:
12
+ # address: (Integer) 7 bit numeric address
13
+ # direction: (Symbol) :in or :out
14
+ #
15
+ # Returns: true if device is active on i2c bus, false if it is unresponsive
16
+ def start address_7bit, direction
17
+ raise "Address is too high" if address_7bit > 127
18
+ raise "Address is too low" if address_7bit < 0
19
+
20
+ direction = :write if direction == :out || direction == :output || direction == :send
21
+ config = (address_7bit.to_i << 1) | ((direction == :write) ? 0 : 1)
22
+
23
+ @wire.control_transfer(function: :i2c_begin, wValue: config, dataIn: 8)
24
+ @wire.control_transfer(function: :read_buffer, dataIn: 8).bytes.first == 0
25
+ end
26
+
27
+ # read bytes from i2c device, optionally ending with a stop when finished
28
+ def read length, stop_at_end = true
29
+ @wire.control_transfer(function: :i2c_read, dataIn: 8,
30
+ wValue: ((length.to_i & 0xFF) << 8) + (stop_at_end ? 1 : 0),
31
+ wIndex: stop_at_end ? 1 : 0)
32
+ @wire.control_transfer(function: :read_buffer, dataIn: 8).bytes.first(length)
33
+ end
34
+
35
+ # write data to i2c device, optionally sending a stop when finished
36
+ def write send_buffer, stop_at_end = true
37
+ #send_buffer = send_buffer.pack('C*') if send_buffer.is_a? Array
38
+ send_buffer = send_buffer.bytes if send_buffer.is_a? String
39
+
40
+ #byte_sets = send_buffer.each_slice(4).to_a
41
+ #byte_sets.each_with_index.map do |slice, index|
42
+ # do_stop = stop_at_end && index >= byte_sets.length - 1
43
+ slice = send_buffer
44
+ do_stop = stop_at_end
45
+ @wire.control_transfer(
46
+ bRequest: 0xE0 + slice.length + ((do_stop ? 1 : 0) << 3),
47
+ wValue: ((slice[1] || 0) << 8) + (slice[0] || 0),
48
+ wIndex: ((slice[3] || 0) << 8) + (slice[2] || 0),
49
+ dataIn: 8
50
+ )
51
+ #end
52
+ end
53
+
54
+ # simplified syntax to send a message to an address
55
+ def transmit address, *args
56
+ raise "I2C Device #{address} Unresponsive" unless start(address, :write)
57
+ write(*args)
58
+ end
59
+
60
+ # simplified syntax to read value of a register
61
+ def request address, *args
62
+ raise "I2C Device #{address} Unresponsive" unless start(address, :read)
63
+ read *args
64
+ end
65
+
66
+ # set the update delay of LittleWire's i2c module in microseconds
67
+ def delay= update_delay
68
+ @wire.control_transfer(function: :i2c_update_delay, wValue: update_delay.to_i)
69
+ end
70
+
71
+ # Search all 128 possible i2c device addresses, starting a message to each and
72
+ # testing if devices respond at each location.
73
+ #
74
+ # Returns: Array of Integer addresses
75
+ def search
76
+ 128.times.select do |address|
77
+ address_responds? address
78
+ end
79
+ end
80
+
81
+ # Test if an i2c address responds to requests - e.g. is it plugged in to the network?
82
+ #
83
+ # Arguments:
84
+ # - address - an integer between 0 and 127 inclusive
85
+ # Returns: true or false
86
+ def address_responds? address
87
+ raise "Address must be an Integer" unless address.is_a? Integer
88
+ raise "Address too high. Max is 127" if address > 127
89
+ raise "Address too low. Needs to be at least 0" if address < 0
90
+ start(address, :write)
91
+ end
92
+ end
File without changes
File without changes
@@ -0,0 +1,8 @@
1
+ class LittleWire
2
+ Version = '0.9.8'
3
+
4
+ # correct interface, to enable dynamic stuff later if need be:
5
+ def self.version
6
+ LittleWire::Version
7
+ end
8
+ end