rbuspirate 0.2.2
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/.gitignore +8 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +28 -0
- data/README.md +43 -0
- data/Rakefile +2 -0
- data/bin/console +30 -0
- data/bin/setup +8 -0
- data/examples/i2c.rb +37 -0
- data/examples/uart.rb +29 -0
- data/lib/rbuspirate.rb +103 -0
- data/lib/rbuspirate/commands.rb +82 -0
- data/lib/rbuspirate/helpers.rb +16 -0
- data/lib/rbuspirate/interfaces/1wire.rb +50 -0
- data/lib/rbuspirate/interfaces/abstract.rb +40 -0
- data/lib/rbuspirate/interfaces/i2c.rb +153 -0
- data/lib/rbuspirate/interfaces/uart.rb +110 -0
- data/lib/rbuspirate/responses.rb +21 -0
- data/lib/rbuspirate/timeouts.rb +31 -0
- data/lib/rbuspirate/version.rb +3 -0
- data/rbuspirate.gemspec +31 -0
- metadata +120 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 0b41cd3f0836279a307505163fc2689965c89f1da8a9e526388b805727e85b41
|
|
4
|
+
data.tar.gz: 9f8f4bd005c80e96776d0527b1a5f521fa1a639b095585551812f9f48da4d11d
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 206e7ca0c8075ef15f23ec0bb0b8e0d772d4173eb9abaec99388e2de095ed2cbc72d48e745e62c257898a94b541a910723f4005c8e4de8f200c63a14cabc2d0b
|
|
7
|
+
data.tar.gz: 62672ed1d3ffc5168f38d70796cae79d6120a732507076ad16567c211620ba34404834a42cc8d3d3fb3f187a7daafa74cee4cabb1461ef8f1ae0c77df7194efe
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
rbuspirate (0.2.2)
|
|
5
|
+
serialport (~> 1.3)
|
|
6
|
+
|
|
7
|
+
GEM
|
|
8
|
+
remote: https://rubygems.org/
|
|
9
|
+
specs:
|
|
10
|
+
coderay (1.1.2)
|
|
11
|
+
method_source (0.9.2)
|
|
12
|
+
pry (0.12.2)
|
|
13
|
+
coderay (~> 1.1.0)
|
|
14
|
+
method_source (~> 0.9.0)
|
|
15
|
+
rake (10.5.0)
|
|
16
|
+
serialport (1.3.1)
|
|
17
|
+
|
|
18
|
+
PLATFORMS
|
|
19
|
+
ruby
|
|
20
|
+
|
|
21
|
+
DEPENDENCIES
|
|
22
|
+
bundler (~> 2.0)
|
|
23
|
+
pry (~> 0.12.2)
|
|
24
|
+
rake (~> 10.0)
|
|
25
|
+
rbuspirate!
|
|
26
|
+
|
|
27
|
+
BUNDLED WITH
|
|
28
|
+
2.1.4
|
data/README.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Rbuspirate
|
|
2
|
+
|
|
3
|
+
Better ruby driver for buspirate
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add this line to your application's Gemfile:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
gem 'rbuspirate'
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
And then execute:
|
|
14
|
+
|
|
15
|
+
$ bundle
|
|
16
|
+
|
|
17
|
+
Or install it yourself as:
|
|
18
|
+
|
|
19
|
+
$ gem install rbuspirate
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
See examples/ dir
|
|
23
|
+
|
|
24
|
+
## Status
|
|
25
|
+
|
|
26
|
+
I2C: Full support
|
|
27
|
+
UART: Basic support (only bridge mode, no custom speed)
|
|
28
|
+
1WIRE: Full support
|
|
29
|
+
Windows not supported, contributions are welcomed
|
|
30
|
+
|
|
31
|
+
## Development
|
|
32
|
+
|
|
33
|
+
After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
34
|
+
|
|
35
|
+
## Contributing
|
|
36
|
+
|
|
37
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/sh7d/rbuspirate.
|
|
38
|
+
|
|
39
|
+
## Todo
|
|
40
|
+
* Documentation
|
|
41
|
+
* Full uart support
|
|
42
|
+
* SPI
|
|
43
|
+
* Other protocols
|
data/Rakefile
ADDED
data/bin/console
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'bundler/setup'
|
|
4
|
+
require 'rbuspirate'
|
|
5
|
+
require 'optparse'
|
|
6
|
+
require 'pry'
|
|
7
|
+
|
|
8
|
+
le_options = {}
|
|
9
|
+
|
|
10
|
+
optparse = OptParse.new do |opts|
|
|
11
|
+
opts.on(
|
|
12
|
+
'-d device', '--device device', String, 'Path to buspirate device'
|
|
13
|
+
) do |device|
|
|
14
|
+
dev_stat = File.stat(device).rdev rescue nil
|
|
15
|
+
raise 'Connect buspirate first' unless dev_stat
|
|
16
|
+
raise 'Device argument must be device' if dev_stat.zero?
|
|
17
|
+
|
|
18
|
+
le_options[:device] = device
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
optparse.parse!
|
|
22
|
+
le_options.freeze
|
|
23
|
+
if le_options[:device]
|
|
24
|
+
bp = SerialPort.new(le_options[:device], 115_200, 8, 1, SerialPort::NONE)
|
|
25
|
+
rs = Rbuspirate::Client.new(bp)
|
|
26
|
+
|
|
27
|
+
binding.pry
|
|
28
|
+
else
|
|
29
|
+
puts optparse.to_s
|
|
30
|
+
end
|
data/bin/setup
ADDED
data/examples/i2c.rb
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
require 'rbuspirate'
|
|
2
|
+
|
|
3
|
+
bp = Rbuspirate::Client.new('/dev/buspirate')
|
|
4
|
+
bp.enter_i2c
|
|
5
|
+
# valid settings are :'5khz', :'50khz', :'100khz', :'400khz'
|
|
6
|
+
bp.interface.speed = :'400khz'
|
|
7
|
+
# default vaules are false
|
|
8
|
+
bp.interface.configure_peripherals(
|
|
9
|
+
power: true, pullup: true, aux: false, cs: false
|
|
10
|
+
)
|
|
11
|
+
# you can see definet peripherals config via accesors
|
|
12
|
+
puts "Power #{bp.interface.power ? 'enabled' : 'disabled'}"
|
|
13
|
+
puts "Pull-up resitors #{bp.interface.pullup ? 'enabled' : 'disabled'}"
|
|
14
|
+
# Frist argument is command
|
|
15
|
+
# second expected data to read
|
|
16
|
+
# succes_timeout specifies timeout to read data from device
|
|
17
|
+
# allow_zerobyte: allows one single byte instead of expected data to read
|
|
18
|
+
data = bp.interface.write_then_read(0xA1.chr, 4, succes_timeout: 5, allow_zerobyte: false)
|
|
19
|
+
puts "First 4 bytes are #{data.unpack('H*').join}"
|
|
20
|
+
|
|
21
|
+
# You can also acces raw mode
|
|
22
|
+
# Block is optional
|
|
23
|
+
bp.interface.send_start
|
|
24
|
+
ack_array = bp.interface.bulk_write("\xA0\x00\x00\xDE\xAD".b, ack_timeout: 1, &method(:puts))
|
|
25
|
+
bp.interface.send_stop
|
|
26
|
+
puts ack_array
|
|
27
|
+
|
|
28
|
+
# And read
|
|
29
|
+
bp.interface.send_start
|
|
30
|
+
bp.interface.bulk_write("\xA0\x00\x00".b)
|
|
31
|
+
bp.interface.send_stop
|
|
32
|
+
bp.interface.send_start
|
|
33
|
+
bp.interface.bulk_write("\xA1".b)
|
|
34
|
+
# default vaules are true for acks, and 1 for readbyte_timeout
|
|
35
|
+
data = bp.interface.read(4, auto_ack: true, auto_nack: true, readbyte_timeout: 1)
|
|
36
|
+
bp.interface.send_stop
|
|
37
|
+
puts "First 4 bytes are: #{data.unpack('H*').join}"
|
data/examples/uart.rb
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require 'rbuspirate'
|
|
2
|
+
|
|
3
|
+
bp = Rbuspirate::Client.new('/dev/buspirate')
|
|
4
|
+
bp.enter_uart
|
|
5
|
+
# set speed
|
|
6
|
+
bp.interface.speed = 115_200
|
|
7
|
+
# parity_data possible vaules are: :n8, :e8, :o8, :n9
|
|
8
|
+
bp.interface.config_uart(
|
|
9
|
+
pin_out_33: true, stop_bits: 1, parity_data: :n8, rx_idle: true
|
|
10
|
+
)
|
|
11
|
+
# you can see defined settings via accesors
|
|
12
|
+
puts "Pin out voltage #{bp.interface.pin_out_33 ? '3.3V' : '5V'}"
|
|
13
|
+
# Enable power
|
|
14
|
+
bp.interface.configure_peripherals(
|
|
15
|
+
power: false, pullup: false, aux: false, cs: false
|
|
16
|
+
)
|
|
17
|
+
# you can call to configure_peripherals like configure_peripherals(power: true)
|
|
18
|
+
# rest of arguments will be in default state (false)
|
|
19
|
+
# you can also see peripherals config state via accesors
|
|
20
|
+
puts "Power #{bp.interface.power ? 'enabled' : 'disabled'}"
|
|
21
|
+
# Bridge mode
|
|
22
|
+
bp.interface.enter_bridge
|
|
23
|
+
|
|
24
|
+
# raw port is exposed via port accesor
|
|
25
|
+
port = bp.interface.port
|
|
26
|
+
|
|
27
|
+
while (line = port.readline)
|
|
28
|
+
puts line
|
|
29
|
+
end
|
data/lib/rbuspirate.rb
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
require 'expect'
|
|
2
|
+
require 'serialport'
|
|
3
|
+
|
|
4
|
+
require 'rbuspirate/commands'
|
|
5
|
+
require 'rbuspirate/responses'
|
|
6
|
+
require 'rbuspirate/timeouts'
|
|
7
|
+
require 'rbuspirate/interfaces/abstract'
|
|
8
|
+
require 'rbuspirate/interfaces/i2c'
|
|
9
|
+
require 'rbuspirate/interfaces/uart'
|
|
10
|
+
require 'rbuspirate/interfaces/1wire'
|
|
11
|
+
|
|
12
|
+
module Rbuspirate
|
|
13
|
+
class Client
|
|
14
|
+
attr_reader :mode, :interface, :needs_reset
|
|
15
|
+
|
|
16
|
+
def initialize(dvc, sync: true)
|
|
17
|
+
raise ArgumentError, 'Shitty arg' unless [SerialPort, String].include?(dvc.class)
|
|
18
|
+
|
|
19
|
+
if dvc.instance_of?(String)
|
|
20
|
+
raise 'Connect buspirate first' unless File.exist?(dvc)
|
|
21
|
+
raise 'Device argument must be device' if File.stat(dvc).rdev.zero?
|
|
22
|
+
|
|
23
|
+
dvc = SerialPort.new(dvc, 115_200, 8, 1, SerialPort::NONE)
|
|
24
|
+
dvc.flow_control = SerialPort::NONE
|
|
25
|
+
end
|
|
26
|
+
@le_port = dvc
|
|
27
|
+
@le_port.sync = true if sync
|
|
28
|
+
@needs_reset = false
|
|
29
|
+
reset_binary_mode
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def reset_binary_mode
|
|
33
|
+
raise 'Device needs reset to change mode' if @needs_reset
|
|
34
|
+
|
|
35
|
+
20.times do
|
|
36
|
+
@le_port.putc(Commands::RESET_BITBANG)
|
|
37
|
+
resp = @le_port.expect(
|
|
38
|
+
Responses::BITBANG_MODE, Timeouts::BINARY_RESET
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
if resp
|
|
42
|
+
@interface = nil
|
|
43
|
+
@mode = :bitbang
|
|
44
|
+
return true
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
raise 'Enter to bitbang failied'
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def enter_i2c
|
|
52
|
+
raise 'Device needs reset to change mode' if @needs_reset
|
|
53
|
+
|
|
54
|
+
switch_mode(
|
|
55
|
+
:i2c, Commands::I2C::ENTER,
|
|
56
|
+
Timeouts::I2C::ENTER, Responses::I2C::ENTER,
|
|
57
|
+
Interfaces::I2C
|
|
58
|
+
)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def enter_uart
|
|
62
|
+
raise 'Device needs reset to change mode' if @needs_reset
|
|
63
|
+
|
|
64
|
+
switch_mode(
|
|
65
|
+
:uart, Commands::UART::ENTER,
|
|
66
|
+
Timeouts::UART::ENTER, Responses::UART::ENTER,
|
|
67
|
+
Interfaces::UART
|
|
68
|
+
)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def enter_1wire
|
|
72
|
+
raise 'Device needs reset to change mode' if @needs_reset
|
|
73
|
+
|
|
74
|
+
switch_mode(
|
|
75
|
+
:'1wire', Commands::LE1WIRE::ENTER,
|
|
76
|
+
Timeouts::LE1WIRE::ENTER, Responses::LE1WIRE::ENTER,
|
|
77
|
+
Interfaces::LE1WIRE
|
|
78
|
+
)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
def switch_mode(
|
|
84
|
+
name_symbol, switch_command,
|
|
85
|
+
wait_timeout, enter_response,
|
|
86
|
+
interface_class
|
|
87
|
+
)
|
|
88
|
+
raise 'Device needs reset to change mode' if @needs_reset
|
|
89
|
+
|
|
90
|
+
@le_port.write(switch_command.chr)
|
|
91
|
+
resp = @le_port.expect(
|
|
92
|
+
enter_response, wait_timeout
|
|
93
|
+
)
|
|
94
|
+
if resp
|
|
95
|
+
@mode = name_symbol
|
|
96
|
+
@interface = interface_class.new(@le_port, self)
|
|
97
|
+
return true
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
raise "Switch to #{name_symbol.to_s.upcase} failied"
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Encoding: binary
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Rbuspirate
|
|
5
|
+
module Commands
|
|
6
|
+
RESET_BITBANG = 0b00000000
|
|
7
|
+
CONF_PER = 0b01000000
|
|
8
|
+
|
|
9
|
+
module Config
|
|
10
|
+
module Peripherals
|
|
11
|
+
POWER = 0b00001000
|
|
12
|
+
PULLUP = 0b00000100
|
|
13
|
+
AUX = 0b00000010
|
|
14
|
+
CS = 0b00000001
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
module LE1WIRE
|
|
19
|
+
ENTER = 0b00000100
|
|
20
|
+
RESET = 0b00000010
|
|
21
|
+
|
|
22
|
+
module IO
|
|
23
|
+
READ = 0b00000100
|
|
24
|
+
WRITE = 0b00010000
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
module I2C
|
|
29
|
+
ENTER = 0b00000010
|
|
30
|
+
PREPARE_WRITE = 0b00010000
|
|
31
|
+
READBYTE = 0b00000100
|
|
32
|
+
WRITE_THEN_READ = 0x8
|
|
33
|
+
|
|
34
|
+
module Config
|
|
35
|
+
module Speed
|
|
36
|
+
S5KHZ = 0b01100000
|
|
37
|
+
S50KHZ = 0b01100001
|
|
38
|
+
S100KHZ = 0b01100010
|
|
39
|
+
S400KHZ = 0b01100011
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
module Flow
|
|
44
|
+
START = 0b00000010
|
|
45
|
+
STOP = 0b00000011
|
|
46
|
+
ACK = 0b00000110
|
|
47
|
+
NACK = 0b00000111
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
module UART
|
|
52
|
+
ENTER = 0b00000011
|
|
53
|
+
START_BRIDGE = 0b00001111
|
|
54
|
+
|
|
55
|
+
module Config
|
|
56
|
+
CONF_UART = 0b10000000
|
|
57
|
+
|
|
58
|
+
module Speed
|
|
59
|
+
S300 = 0b01100000
|
|
60
|
+
S1200 = 0b01100001
|
|
61
|
+
S2400 = 0b01100010
|
|
62
|
+
S4800 = 0b01100011
|
|
63
|
+
S9600 = 0b01100100
|
|
64
|
+
S19200 = 0b01100101
|
|
65
|
+
S31250 = 0b01100110
|
|
66
|
+
S38400 = 0b01100111
|
|
67
|
+
S57600 = 0b01101000
|
|
68
|
+
S115200 = 0b01101010
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
module UartConf
|
|
72
|
+
PIN_OUT_33 = 0b00010000
|
|
73
|
+
DAT_PARITY_8E = 0b00000100
|
|
74
|
+
DAT_PARITY_80 = 0b00001000
|
|
75
|
+
DAT_PARITY_9N = 0b00001100
|
|
76
|
+
STOP_BIT_2 = 0b00000010
|
|
77
|
+
DISABLE_RX_IDLE = 0b00000001
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Encoding: binary
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Rbuspirate
|
|
5
|
+
module Helpers
|
|
6
|
+
private
|
|
7
|
+
|
|
8
|
+
def simplex_command(command, tout, ex_message)
|
|
9
|
+
@le_port.write(command.chr)
|
|
10
|
+
resp = @le_port.expect(Responses::SUCCESS, tout)
|
|
11
|
+
return true if resp
|
|
12
|
+
|
|
13
|
+
raise ex_message
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Encoding: binary
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Rbuspirate
|
|
5
|
+
module Interfaces
|
|
6
|
+
class LE1WIRE < Abstract
|
|
7
|
+
def initialize(serial, bup)
|
|
8
|
+
raise 'Bus pirate must be in 1wire mode' unless bup.mode == :'1wire'
|
|
9
|
+
@le_port = serial
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def reset
|
|
13
|
+
simplex_command(
|
|
14
|
+
Commands::LE1WIRE::RESET,
|
|
15
|
+
Timeouts::LE1WIRE::RESET,
|
|
16
|
+
'Unable to reset external device (comm timeout/no device)'
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def write(data, write_slice_timeout: Timeouts::LE1WIRE::WRITE)
|
|
21
|
+
!(data.is_a?(String) && !data.empty?) &&
|
|
22
|
+
raise(ArgumentError, 'data must be non empty String instance')
|
|
23
|
+
|
|
24
|
+
data = StringIO.new(data)
|
|
25
|
+
while (slice = data.read(16))
|
|
26
|
+
command = Commands::LE1WIRE::IO::WRITE | slice.bytesize - 1
|
|
27
|
+
simplex_command(
|
|
28
|
+
command, write_slice_timeout, 'Prepare slice write timeout'
|
|
29
|
+
)
|
|
30
|
+
@le_port.write(slice)
|
|
31
|
+
res = @le_port.expect(Responses::SUCCESS, write_slice_timeout)
|
|
32
|
+
raise 'Write timeout' unless res
|
|
33
|
+
end
|
|
34
|
+
true
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def read(bytes = 1, readbyte_timeout: Timeouts::LE1WIRE::READ)
|
|
38
|
+
result = ''.dup.b
|
|
39
|
+
bytes.times do
|
|
40
|
+
@le_port.write(Commands::LE1WIRE::IO::READ.chr)
|
|
41
|
+
Timeout.timeout(readbyte_timeout) do
|
|
42
|
+
result << @le_port.read(1)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
result
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Encoding: binary
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Rbuspirate
|
|
5
|
+
module Interfaces
|
|
6
|
+
class Abstract
|
|
7
|
+
def configure_peripherals(
|
|
8
|
+
power: false, pullup: false, aux: false, cs: false
|
|
9
|
+
)
|
|
10
|
+
[power, pullup, aux, cs].map(&:class).each do |cls|
|
|
11
|
+
raise ArgumentError, 'All args must be true or false' unless [FalseClass, TrueClass].include?(cls)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
bit_config = Commands::CONF_PER
|
|
15
|
+
bit_config |= Commands::Config::Peripherals::POWER if power
|
|
16
|
+
bit_config |= Commands::Config::Peripherals::PULLUP if pullup
|
|
17
|
+
bit_config |= Commands::Config::Peripherals::AUX if aux
|
|
18
|
+
bit_config |= Commands::Config::Peripherals::CS if cs
|
|
19
|
+
|
|
20
|
+
simplex_command(
|
|
21
|
+
bit_config,
|
|
22
|
+
Timeouts::SUCCESS,
|
|
23
|
+
'Unable to confgure peripherals'
|
|
24
|
+
)
|
|
25
|
+
@power, @pullup, @aux, @cs = power, pullup, aux, cs
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
protected
|
|
29
|
+
|
|
30
|
+
def simplex_command(command, tout, ex_message)
|
|
31
|
+
command = command.chr if command.instance_of?(Integer)
|
|
32
|
+
@le_port.write(command.chr)
|
|
33
|
+
resp = @le_port.expect(Responses::SUCCESS, tout)
|
|
34
|
+
return true if resp
|
|
35
|
+
|
|
36
|
+
raise ex_message
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# Encoding: binary
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'timeout'
|
|
5
|
+
|
|
6
|
+
module Rbuspirate
|
|
7
|
+
module Interfaces
|
|
8
|
+
class I2C < Abstract
|
|
9
|
+
attr_reader :speed, :power, :pullup, :aux, :cs
|
|
10
|
+
|
|
11
|
+
def initialize(serial, bup)
|
|
12
|
+
raise 'Bus pirate must be in i2c mode' unless bup.mode == :i2c
|
|
13
|
+
|
|
14
|
+
@le_port = serial
|
|
15
|
+
set_speed(:'400khz')
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def speed=(le_speed)
|
|
19
|
+
set_speed(le_speed)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def send_start
|
|
23
|
+
simplex_command(
|
|
24
|
+
Commands::I2C::Flow::START,
|
|
25
|
+
Timeouts::I2C::STARTSTOP,
|
|
26
|
+
'Unable to sent start bit'
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def send_stop
|
|
31
|
+
simplex_command(
|
|
32
|
+
Commands::I2C::Flow::STOP,
|
|
33
|
+
Timeouts::I2C::STARTSTOP,
|
|
34
|
+
'Unable to sent stop bit'
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def send_ack
|
|
39
|
+
simplex_command(
|
|
40
|
+
Commands::I2C::Flow::ACK,
|
|
41
|
+
Timeouts::I2C::ACKNACK,
|
|
42
|
+
'Unable to sent ack'
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def send_nack
|
|
47
|
+
simplex_command(
|
|
48
|
+
Commands::I2C::Flow::NACK,
|
|
49
|
+
Timeouts::I2C::ACKNACK,
|
|
50
|
+
'Unable to sent nack'
|
|
51
|
+
)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def read(bytes = 1, auto_ack: true, auto_nack: true, readbyte_timeout: Timeouts::I2C::READ)
|
|
55
|
+
result = ''.dup.b
|
|
56
|
+
bytes.times do |t|
|
|
57
|
+
@le_port.write(Commands::I2C::READBYTE.chr)
|
|
58
|
+
Timeout.timeout(readbyte_timeout) do
|
|
59
|
+
result << @le_port.read(1)
|
|
60
|
+
end
|
|
61
|
+
send_ack if auto_ack && t + 1 != bytes
|
|
62
|
+
send_nack if auto_nack && t + 1 == bytes
|
|
63
|
+
end
|
|
64
|
+
result
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def bulk_write(data, ack_timeout: Timeouts::I2C::SLAVE_ACKNACK)
|
|
68
|
+
raise ArgumentError, 'data must be String instance' unless data.instance_of?(String)
|
|
69
|
+
|
|
70
|
+
if !data.instance_of?(String) || data.instance_of?(String) && data.empty?
|
|
71
|
+
raise ArgumentError, 'Bad data argument'
|
|
72
|
+
end
|
|
73
|
+
raise ArgumentError, 'Data is too long' if data.bytesize > 16
|
|
74
|
+
|
|
75
|
+
bit_bulk_write = Commands::I2C::PREPARE_WRITE | data.bytesize - 1
|
|
76
|
+
simplex_command(
|
|
77
|
+
bit_bulk_write.chr,
|
|
78
|
+
Timeouts::I2C::PREPARE_WRITE,
|
|
79
|
+
'Unable to prepare write mode'
|
|
80
|
+
)
|
|
81
|
+
ack_array = []
|
|
82
|
+
data.each_byte do |data_byte|
|
|
83
|
+
@le_port.write(data_byte.chr)
|
|
84
|
+
Timeout.timeout(ack_timeout) do
|
|
85
|
+
ack_array << case @le_port.read(1).ord
|
|
86
|
+
when 0
|
|
87
|
+
:ack
|
|
88
|
+
when 1
|
|
89
|
+
:nack
|
|
90
|
+
else
|
|
91
|
+
raise 'Unknown bytewrite response'
|
|
92
|
+
end
|
|
93
|
+
yield(ack_array.last) if block_given?
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
ack_array.freeze
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def write_then_read(
|
|
100
|
+
data, expected_bytes = 0,
|
|
101
|
+
succes_timeout: Timeouts::I2C::WRITE_THEN_READ_S,
|
|
102
|
+
allow_zerobyte: false
|
|
103
|
+
)
|
|
104
|
+
raise ArgumentError, 'Bad data type' unless data.instance_of?(String)
|
|
105
|
+
raise ArgumentError, 'Data is too long' if data.bytesize > 4096
|
|
106
|
+
raise ArgumentError, 'Bad expected_bytes type' unless expected_bytes.instance_of?(Integer)
|
|
107
|
+
raise ArgumentError, 'Bad expected_bytes value' if expected_bytes.negative? || expected_bytes > 4096
|
|
108
|
+
|
|
109
|
+
binary_command = Commands::I2C::WRITE_THEN_READ.chr +
|
|
110
|
+
[data.bytesize, expected_bytes].pack('S>S>') +
|
|
111
|
+
data
|
|
112
|
+
@le_port.write(binary_command)
|
|
113
|
+
result = nil
|
|
114
|
+
# So fucking ugly
|
|
115
|
+
begin
|
|
116
|
+
Timeout.timeout(succes_timeout) do
|
|
117
|
+
result = @le_port.read(1)
|
|
118
|
+
end
|
|
119
|
+
rescue Timeout::Error
|
|
120
|
+
return false
|
|
121
|
+
end
|
|
122
|
+
return false if allow_zerobyte && result.ord.zero?
|
|
123
|
+
raise 'Write failed' if result.ord.zero?
|
|
124
|
+
if expected_bytes != 0
|
|
125
|
+
Timeout.timeout(Timeouts::I2C::WRITE_THEN_READ_D) do
|
|
126
|
+
result = @le_port.read(expected_bytes)
|
|
127
|
+
end
|
|
128
|
+
result
|
|
129
|
+
else
|
|
130
|
+
true
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
private
|
|
134
|
+
def set_speed(le_speed)
|
|
135
|
+
bit_speed = case le_speed.to_sym
|
|
136
|
+
when :'5khz'
|
|
137
|
+
Commands::I2C::Config::Speed::S5KHZ
|
|
138
|
+
when :'50khz'
|
|
139
|
+
Commands::I2C::Config::Speed::S50KHZ
|
|
140
|
+
when :'100khz'
|
|
141
|
+
Commands::I2C::Config::Speed::S100KHZ
|
|
142
|
+
when :'400khz'
|
|
143
|
+
Commands::I2C::Config::Speed::S400KHZ
|
|
144
|
+
else
|
|
145
|
+
raise ArgumentError, 'Bad speed argument'
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
simplex_command(bit_speed, Timeouts::SUCCESS, 'Unable to set speed')
|
|
149
|
+
@speed = le_speed
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Encoding: binary
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'timeout'
|
|
5
|
+
|
|
6
|
+
module Rbuspirate
|
|
7
|
+
module Interfaces
|
|
8
|
+
class UART < Abstract
|
|
9
|
+
attr_reader :bridge, :speed, :power, :pullup, :aux, :cs,
|
|
10
|
+
:pin_out_33, :parity_data, :stop_bits, :rx_idle,
|
|
11
|
+
:port
|
|
12
|
+
|
|
13
|
+
def initialize(serial, bup)
|
|
14
|
+
raise 'Bus pirate must be in uart mode' unless bup.mode == :uart
|
|
15
|
+
|
|
16
|
+
@bridge = false
|
|
17
|
+
@bup = bup
|
|
18
|
+
@le_port = serial
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def configure_peripherals(...)
|
|
22
|
+
raise 'Device needs reset in order to reconfigure it' if @bridge
|
|
23
|
+
|
|
24
|
+
super
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def speed=(le_speed)
|
|
28
|
+
raise 'Device needs reset in order to reconfigure it' if @bridge
|
|
29
|
+
|
|
30
|
+
bit_speed = case le_speed
|
|
31
|
+
when 300
|
|
32
|
+
Commands::UART::Config::Speed::S300
|
|
33
|
+
when 1200
|
|
34
|
+
Commands::UART::Config::Speed::S1200
|
|
35
|
+
when 2400
|
|
36
|
+
Commands::UART::Config::Speed::S2400
|
|
37
|
+
when 4800
|
|
38
|
+
Commands::UART::Config::Speed::S4800
|
|
39
|
+
when 9600
|
|
40
|
+
Commands::UART::Config::Speed::S9600
|
|
41
|
+
when 19_200
|
|
42
|
+
Commands::UART::Config::Speed::S19200
|
|
43
|
+
when 31_250
|
|
44
|
+
Commands::UART::Config::Speed::S31250
|
|
45
|
+
when 38_400
|
|
46
|
+
Commands::UART::Config::Speed::S38400
|
|
47
|
+
when 57_600
|
|
48
|
+
Commands::UART::Config::Speed::S57600
|
|
49
|
+
when 115_200
|
|
50
|
+
Commands::UART::Config::Speed::S115200
|
|
51
|
+
else
|
|
52
|
+
raise ArgumentError, 'Unsupported speed'
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
simplex_command(bit_speed, Timeouts::SUCCESS, 'Unable to set speed')
|
|
56
|
+
@speed = bit_speed
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def config_uart(
|
|
60
|
+
pin_out_33: false, parity_data: :n8, stop_bits: 1, rx_idle: true
|
|
61
|
+
)
|
|
62
|
+
raise 'Device needs reset in order to reconfigure it' if @bridge
|
|
63
|
+
|
|
64
|
+
raise ArgumentError, 'Pin out should be false or true' unless [true, false].include?(pin_out_33)
|
|
65
|
+
raise ArgumentError, 'Unknown praity and databits mode' unless [:n8, :e8, :o8, :n9].include?(parity_data)
|
|
66
|
+
raise ArgumentError, 'Unknown stop bits mode' unless [1, 2].include?(stop_bits)
|
|
67
|
+
raise ArgumentError, 'Rx idle should be false or true' unless [true, false].include?(rx_idle)
|
|
68
|
+
|
|
69
|
+
bit_conf_uart = Commands::UART::Config::CONF_UART
|
|
70
|
+
|
|
71
|
+
bit_conf_uart |= Commands::UART::Config::UartConf::PIN_OUT_33 if pin_out_33
|
|
72
|
+
bit_conf_uart |= case parity_data
|
|
73
|
+
when :e8
|
|
74
|
+
Commands::UART::Config::UartConf::DAT_PARITY_8E
|
|
75
|
+
when :o8
|
|
76
|
+
Commands::UART::Config::UartConf::DAT_PARITY_8O
|
|
77
|
+
when :n9
|
|
78
|
+
Commands::UART::Config::UartConf::DAT_PARITY_9N
|
|
79
|
+
else
|
|
80
|
+
0
|
|
81
|
+
end
|
|
82
|
+
bit_conf_uart |= Commands::UART::Config::UartConf::STOP_BIT_2 if stop_bits == 2
|
|
83
|
+
bit_conf_uart |= Commands::UART::Config::UartConf::DISABLE_RX_IDLE unless rx_idle
|
|
84
|
+
|
|
85
|
+
simplex_command(bit_conf_uart, Timeouts::SUCCESS, 'Unable to config uart')
|
|
86
|
+
|
|
87
|
+
@pin_out_33, @parity_data, @stop_bits, @rx_idle = pin_out_33, parity_data, stop_bits, rx_idle
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def enter_bridge
|
|
91
|
+
return @bridge if @bridge
|
|
92
|
+
|
|
93
|
+
@le_port.write(Commands::UART::START_BRIDGE.chr)
|
|
94
|
+
@bridge = true
|
|
95
|
+
@bup.instance_variable_set(:@needs_reset, true)
|
|
96
|
+
@port = @le_port
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def read(bytes = 0)
|
|
100
|
+
raise 'Enter to bridge mode first' unless @bridge
|
|
101
|
+
bytes.positive? ? @le_port.read(bytes) : @le_port.read
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def write(data)
|
|
105
|
+
raise 'Enter to bridge mode first' unless @bridge
|
|
106
|
+
@le_port.write(data.to_s.b)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Encoding: binary
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Rbuspirate
|
|
5
|
+
module Responses
|
|
6
|
+
BITBANG_MODE = 'BBIO1'
|
|
7
|
+
SUCCESS = 0x01.chr
|
|
8
|
+
|
|
9
|
+
module LE1WIRE
|
|
10
|
+
ENTER = '1W01'
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
module I2C
|
|
14
|
+
ENTER = 'I2C1'
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
module UART
|
|
18
|
+
ENTER = 'ART1'
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Encoding: binary
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Rbuspirate
|
|
5
|
+
module Timeouts
|
|
6
|
+
BINARY_RESET = 0.05
|
|
7
|
+
SUCCESS = 0.1
|
|
8
|
+
|
|
9
|
+
module LE1WIRE
|
|
10
|
+
ENTER = 0.2
|
|
11
|
+
RESET = 0.5
|
|
12
|
+
WRITE = 1
|
|
13
|
+
READ = 1
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
module I2C
|
|
17
|
+
ENTER = 0.2
|
|
18
|
+
STARTSTOP = 0.5
|
|
19
|
+
PREPARE_WRITE = 0.1
|
|
20
|
+
ACKNACK = 0.3
|
|
21
|
+
READ = 1
|
|
22
|
+
SLAVE_ACKNACK = 0.5
|
|
23
|
+
WRITE_THEN_READ_S = 5
|
|
24
|
+
WRITE_THEN_READ_D = 5
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
module UART
|
|
28
|
+
ENTER = 0.2
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
data/rbuspirate.gemspec
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
lib = File.expand_path("lib", __dir__)
|
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
3
|
+
require "rbuspirate/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "rbuspirate"
|
|
7
|
+
spec.version = Rbuspirate::VERSION
|
|
8
|
+
spec.authors = ["sh7d"]
|
|
9
|
+
spec.email = ["sh7d@sh7d"]
|
|
10
|
+
|
|
11
|
+
spec.summary = %q{Ruby better buspirate interface}
|
|
12
|
+
spec.description = %q{Simple buspirate ruby interface}
|
|
13
|
+
spec.homepage = "https://github.com/sh7d/rbuspirate"
|
|
14
|
+
|
|
15
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
16
|
+
|
|
17
|
+
# Specify which files should be added to the gem when it is released.
|
|
18
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
19
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
|
20
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
21
|
+
end
|
|
22
|
+
spec.bindir = "exe"
|
|
23
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
24
|
+
spec.require_paths = ['lib'] + Dir.glob('lib/**').select(&File.method(:directory?))
|
|
25
|
+
|
|
26
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
|
27
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
|
28
|
+
spec.add_development_dependency "pry", "~> 0.12"
|
|
29
|
+
spec.add_dependency "serialport", "~> 1.3"
|
|
30
|
+
end
|
|
31
|
+
|
metadata
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: rbuspirate
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.2.2
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- sh7d
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2020-02-05 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: bundler
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '2.0'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '2.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rake
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '10.0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '10.0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: pry
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0.12'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0.12'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: serialport
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '1.3'
|
|
62
|
+
type: :runtime
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '1.3'
|
|
69
|
+
description: Simple buspirate ruby interface
|
|
70
|
+
email:
|
|
71
|
+
- sh7d@sh7d
|
|
72
|
+
executables: []
|
|
73
|
+
extensions: []
|
|
74
|
+
extra_rdoc_files: []
|
|
75
|
+
files:
|
|
76
|
+
- ".gitignore"
|
|
77
|
+
- Gemfile
|
|
78
|
+
- Gemfile.lock
|
|
79
|
+
- README.md
|
|
80
|
+
- Rakefile
|
|
81
|
+
- bin/console
|
|
82
|
+
- bin/setup
|
|
83
|
+
- examples/i2c.rb
|
|
84
|
+
- examples/uart.rb
|
|
85
|
+
- lib/rbuspirate.rb
|
|
86
|
+
- lib/rbuspirate/commands.rb
|
|
87
|
+
- lib/rbuspirate/helpers.rb
|
|
88
|
+
- lib/rbuspirate/interfaces/1wire.rb
|
|
89
|
+
- lib/rbuspirate/interfaces/abstract.rb
|
|
90
|
+
- lib/rbuspirate/interfaces/i2c.rb
|
|
91
|
+
- lib/rbuspirate/interfaces/uart.rb
|
|
92
|
+
- lib/rbuspirate/responses.rb
|
|
93
|
+
- lib/rbuspirate/timeouts.rb
|
|
94
|
+
- lib/rbuspirate/version.rb
|
|
95
|
+
- rbuspirate.gemspec
|
|
96
|
+
homepage: https://github.com/sh7d/rbuspirate
|
|
97
|
+
licenses: []
|
|
98
|
+
metadata:
|
|
99
|
+
homepage_uri: https://github.com/sh7d/rbuspirate
|
|
100
|
+
post_install_message:
|
|
101
|
+
rdoc_options: []
|
|
102
|
+
require_paths:
|
|
103
|
+
- lib
|
|
104
|
+
- lib/rbuspirate
|
|
105
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - ">="
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '0'
|
|
110
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
111
|
+
requirements:
|
|
112
|
+
- - ">="
|
|
113
|
+
- !ruby/object:Gem::Version
|
|
114
|
+
version: '0'
|
|
115
|
+
requirements: []
|
|
116
|
+
rubygems_version: 3.1.2
|
|
117
|
+
signing_key:
|
|
118
|
+
specification_version: 4
|
|
119
|
+
summary: Ruby better buspirate interface
|
|
120
|
+
test_files: []
|