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.
- checksums.yaml +7 -0
- data/LICENSE +26 -0
- data/README.md +71 -0
- data/Rakefile +22 -0
- data/bin/ksr10 +14 -0
- data/lib/ksr10.rb +125 -0
- metadata +91 -0
checksums.yaml
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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++)
|
data/Rakefile
ADDED
@@ -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
|
data/bin/ksr10
ADDED
data/lib/ksr10.rb
ADDED
@@ -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: []
|