ksr10 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (7) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +26 -0
  3. data/README.md +71 -0
  4. data/Rakefile +22 -0
  5. data/bin/ksr10 +14 -0
  6. data/lib/ksr10.rb +125 -0
  7. metadata +91 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: '04493410ad7bdb38f5601a106ba1d48b1524354e5c13aa9726fac3be8d9d1a88'
4
+ data.tar.gz: a3c7b22e7042b92b8939d84aa681f7a6ac18b7a1798406648a7174993f488946
5
+ SHA512:
6
+ metadata.gz: 811dd98d2afd2e73b679cf0449fab4da55c98bd642bc0a8618cae38a835b45dc9f1a6c6989cb29f3802a2a48a5d876c807d5233f8cfd37425591810df173f549
7
+ data.tar.gz: 131e8a334f4d96f52ddd627195cbd4ff503da6ef140d1b67eaba35df0a10c31fc93d59220a873f554eb7d61988ba7e06a9ee1fc7b28b8759872e5b4ed054b534
data/LICENSE ADDED
@@ -0,0 +1,26 @@
1
+ Copyright (c) 2020 Martin Carpenter <martin.carpenter@gmail.com>. All rights reserved.
2
+
3
+ Redistribution and use in source and binary forms, with or without
4
+ modification, are permitted provided that the following conditions are met:
5
+
6
+ 1. Redistributions of source code must retain the above copyright notice, this
7
+ list of conditions and the following disclaimer.
8
+
9
+ 2. Redistributions in binary form must reproduce the above copyright notice,
10
+ this list of conditions and the following disclaimer in the documentation
11
+ and/or other materials provided with the distribution.
12
+
13
+ 3. Neither the name of the copyright holder nor the names of its contributors
14
+ may be used to endorse or promote products derived from this software without
15
+ specific prior written permission.
16
+
17
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
21
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,71 @@
1
+ # ksr10
2
+
3
+ https://github.com/mcarpenter/ksr10
4
+
5
+ This Ruby Gem provides a simple programming interface to the Velleman KSR10
6
+ robotic arm with USB control board. It's simple enough that it can be used from
7
+ the REPL. The `krs10` script uses `pry` so that you can just call the instance
8
+ methods from the prompt. See the examples below.
9
+
10
+ For product details see:
11
+
12
+ * https://www.velleman.eu/products/view/?id=375310
13
+ * https://www.velleman.eu/products/view/?id=436330
14
+
15
+
16
+ ## Examples
17
+
18
+ ### API
19
+
20
+ ```ruby
21
+ require 'ksr10'
22
+
23
+ arm = Ksr10.new
24
+ arm.led(:on)
25
+ arm.elbow(:down, 500)
26
+ arm.gripper(:close, 1000)
27
+ arm.stop
28
+ ```
29
+
30
+ ### REPL
31
+
32
+ ```ruby
33
+ $ ksr10
34
+ [1] pry(#<Ksr10>)> led(:on)
35
+ => #<Ksr10:0x000055da9db684b0
36
+ @device=#<LIBUSB::Device 3/2 1267:0001 ? ? ? (Vendor specific (00,00))>,
37
+ @state=1,
38
+ @usb=#<LIBUSB::Context:0x000055da9db68488 @ctx=#<FFI::Pointer address=0x000055da9da582d0>, @hotplug_callbacks={}, @on_pollfd_added=nil, @on_pollfd_removed=nil>>
39
+ [2] pry(#<Ksr10>)> shoulder(:up, 400)
40
+ => #<Ksr10:0x000055da9db684b0
41
+ @device=#<LIBUSB::Device 3/2 1267:0001 ? ? ? (Vendor specific (00,00))>,
42
+ @state=1,
43
+ @usb=#<LIBUSB::Context:0x000055da9db68488 @ctx=#<FFI::Pointer address=0x000055da9da582d0>, @hotplug_callbacks={}, @on_pollfd_added=nil, @on_pollfd_removed=nil>>
44
+ [3] pry(#<Ksr10>)>
45
+ ```
46
+
47
+ ## Install
48
+
49
+ ```sh
50
+ sudo apt install libusb-1.0-0
51
+ sudo gem install ksr10
52
+ ```
53
+
54
+ Add the following lines to `/etc/udev/rules.d/95-ksr10.rules` so that
55
+ unprivileged users can connect to the KSR10:
56
+
57
+ ```
58
+ ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="1267", ATTR{idProduct}=="0000", MODE="0666"
59
+ ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="1267", ATTR{idProduct}=="0001", MODE="0666"
60
+ ```
61
+
62
+ If you find that the action is reversed (eg "up" is actually "down") then you
63
+ can simply reverse the appropriate Dupont connector on the battery box to
64
+ change the polarity.
65
+
66
+
67
+ ## Alternatives
68
+
69
+ * https://github.com/lvajxi03/roboarm (Python)
70
+ * https://github.com/TristanvanLeeuwen/KSR10 (Python)
71
+ * https://github.com/paly2/ksr10-linux-driver (C++)
@@ -0,0 +1,22 @@
1
+
2
+ require 'rake'
3
+ require 'rake/clean'
4
+ require 'rdoc/task'
5
+ require 'rubygems/package_task'
6
+
7
+ SPEC = eval(File.read('ksr10.gemspec'))
8
+
9
+ desc 'Default task (gem)'
10
+ task :default => [:gem]
11
+
12
+ Gem::PackageTask.new(SPEC) do |pkg|
13
+ end
14
+
15
+ Rake::RDocTask.new do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = SPEC.name
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.options << '--charset' << 'utf-8'
20
+ rdoc.options << '--main' << 'README.md'
21
+ rdoc.options << '--all'
22
+ end
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'pry'
4
+ require 'ksr10'
5
+
6
+ begin
7
+ arm = Ksr10.new
8
+ rescue Ksr10::Error => err
9
+ execname = File.basename(__FILE__)
10
+ STDERR.puts "#{execname}: #{err.class::MESSAGE}"
11
+ exit 2
12
+ end
13
+ arm.pry
14
+ arm.stop
@@ -0,0 +1,125 @@
1
+
2
+ require 'libusb'
3
+
4
+ # An object representing a Velleman KSR10 USB robotic arm.
5
+ class Ksr10
6
+
7
+ # Gem version.
8
+ VERSION = '1.0.0'
9
+
10
+ # USB product IDs of 0x0 and 0x1 have been seen in the wild so we look for
11
+ # either and take the first found when initializing a Ksr10 object.
12
+ PRODUCT_IDS = [0x0, 0x1]
13
+
14
+ # USB vendor ID.
15
+ VENDOR_ID = 0x1267
16
+
17
+ # Exceptions that might be thrown by the Ksr10 constructor.
18
+ class Error < StandardError
19
+
20
+ class DeviceNotFound < Error
21
+ MESSAGE = 'USB device not found'
22
+ end # DeviceNotFound
23
+
24
+ class PermissionDenied < Error
25
+ MESSAGE = 'Permission denied to open interface to USB device'
26
+ end # PermissionDenied
27
+
28
+ end # Error
29
+
30
+ def initialize
31
+ @usb = LIBUSB::Context.new
32
+ PRODUCT_IDS.each do |product_id|
33
+ @device = @usb.devices(idVendor: VENDOR_ID, idProduct: product_id).first
34
+ break if @device
35
+ end
36
+ raise Error::DeviceNotFound unless @device
37
+ # Check we can access the device. Libusb requires that #open_interface take
38
+ # a block, so we cannot keep and reuse the returned handle.
39
+ begin
40
+ @device.open_interface(0) { |_| }
41
+ rescue LIBUSB::ERROR_ACCESS
42
+ raise Error::PermissionDenied
43
+ end
44
+ stop # initializes @state
45
+ end
46
+
47
+ # Rotate the base.
48
+ def base(left_right_stop, duration_ms=0)
49
+ set_state(left_right_stop, duration_ms, {left: {on: 9, off: 8}, right: {on: 8, off: 9}})
50
+ end
51
+
52
+ # Action the shoulder motor.
53
+ def shoulder(up_down_stop, duration_ms=0)
54
+ set_state(up_down_stop, duration_ms, {up: {on: 22, off: 23}, down: {on: 23, off: 22}})
55
+ end
56
+
57
+ # Action the elbow motor.
58
+ def elbow(up_down_stop, duration_ms=0)
59
+ set_state(up_down_stop, duration_ms, {up: {on: 20, off: 21}, down: {on: 21, off: 20}})
60
+ end
61
+
62
+ # Action the wrist motor.
63
+ def wrist(up_down_stop, duration_ms=0)
64
+ set_state(up_down_stop, duration_ms, {up: {on: 18, off: 19}, down: {on: 19, off: 18}})
65
+ end
66
+
67
+ # Action the gripper.
68
+ def gripper(open_close_stop, duration_ms=0)
69
+ set_state(open_close_stop, duration_ms, {open: {on: 17, off: 16}, close: {on: 16, off: 17}})
70
+ end
71
+
72
+ # Turn on or off the LED.
73
+ def led(on_off, duration_ms=0)
74
+ set_state(on_off, duration_ms, {on: {on: 0}, off: {off: 0}})
75
+ end
76
+
77
+ # Stop all actions and turn off the LED.
78
+ def stop(duration_ms=0)
79
+ @state = 0
80
+ write_state!(duration_ms)
81
+ self
82
+ end
83
+
84
+ private
85
+
86
+ # The Hash spec defines the bits to flip in the USB control write for each
87
+ # permitted state. Bits are numbered from LSB and are 0-indexed. Returns
88
+ # "self" so that calls may be chained in the style of Fowler's "fluent
89
+ # interface".
90
+ def set_state(state, duration_ms, spec)
91
+ if state == :stop
92
+ spec.each do |_, bit_flips|
93
+ @state &= ~(1<<bit_flips[:off]) if bit_flips.has_key?(:off)
94
+ end
95
+ else
96
+ bit_flips = spec[state]
97
+ raise ArgumentError, "Invalid argument #{state.inspect} for #{caller_locations[0].label}" unless bit_flips
98
+ # Flip off bits first to avoid an invalid state "go up AND down
99
+ # simultaneously".
100
+ @state &= ~(1<<bit_flips[:off]) if bit_flips.has_key?(:off)
101
+ @state |= 1<<bit_flips[:on] if bit_flips.has_key?(:on)
102
+ end
103
+ write_state!(duration_ms)
104
+ # If we received a duration then flip off the bits we just flipped on.
105
+ if !duration_ms.zero? && bit_flips && bit_flips.has_key?(:on)
106
+ @state &= ~(1<<bit_flips[:on])
107
+ write_state! # return immediately, no duration_ms
108
+ end
109
+ self
110
+ end
111
+
112
+ # Write the state stored in the instance variable to the device.
113
+ def write_state!(duration_ms=0)
114
+ data = [@state.to_s(16).rjust(6, '0')].pack('H*')
115
+ @device.open_interface(0) do |handle|
116
+ handle.control_transfer(bmRequestType: 0x40,
117
+ bRequest: 0x06,
118
+ wValue: 0x0100,
119
+ wIndex: 0x00,
120
+ dataOut: data)
121
+ end
122
+ sleep(duration_ms / 1000.0)
123
+ end
124
+
125
+ end # Ksr10
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ksr10
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Martin Carpenter
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-01-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: libusb
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.6'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 0.6.4
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '0.6'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 0.6.4
33
+ - !ruby/object:Gem::Dependency
34
+ name: pry
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '0.12'
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: 0.12.2
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: '0.12'
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: 0.12.2
53
+ description: Interface and control script for the Velleman KSR-10 robotic arm.
54
+ email: martin.carpenter@gmail.com
55
+ executables:
56
+ - ksr10
57
+ extensions: []
58
+ extra_rdoc_files:
59
+ - LICENSE
60
+ - Rakefile
61
+ - README.md
62
+ files:
63
+ - LICENSE
64
+ - README.md
65
+ - Rakefile
66
+ - bin/ksr10
67
+ - lib/ksr10.rb
68
+ homepage: http://github.com/mcarpenter/ksr10
69
+ licenses:
70
+ - BSD-3-Clause
71
+ metadata: {}
72
+ post_install_message:
73
+ rdoc_options: []
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ requirements: []
87
+ rubygems_version: 3.0.1
88
+ signing_key:
89
+ specification_version: 4
90
+ summary: KSR-10 robotic arm API and CLI
91
+ test_files: []