ksr10 1.0.0

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.
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: []