denko-piboard 0.13.2 → 0.15.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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +4 -0
  3. data/LICENSE +1 -1
  4. data/README.md +181 -132
  5. data/Rakefile +0 -5
  6. data/board_maps/README.md +59 -0
  7. data/board_maps/le_potato.yml +89 -0
  8. data/board_maps/orange_pi_zero_2w.yml +85 -0
  9. data/board_maps/radxa_zero3.yml +88 -0
  10. data/board_maps/raspberry_pi.yml +95 -0
  11. data/board_maps/raspberry_pi5.yml +95 -0
  12. data/denko_piboard.gemspec +6 -7
  13. data/examples/digital_io/bench_out.rb +22 -0
  14. data/examples/digital_io/rotary_encoder.rb +31 -0
  15. data/examples/display/ssd1306.rb +53 -0
  16. data/examples/i2c/bitbang_aht10.rb +18 -0
  17. data/examples/i2c/bitbang_search.rb +24 -0
  18. data/examples/i2c/bitbang_ssd1306_bench.rb +29 -0
  19. data/examples/i2c/search.rb +24 -0
  20. data/examples/led/blink.rb +10 -0
  21. data/examples/led/fade.rb +22 -0
  22. data/examples/led/ws2812_bounce.rb +36 -0
  23. data/examples/motor/servo.rb +16 -0
  24. data/examples/pi_system_monitor.rb +10 -8
  25. data/examples/pulse_io/buzzer.rb +34 -0
  26. data/examples/pulse_io/infrared.rb +25 -0
  27. data/examples/sensor/aht10.rb +17 -0
  28. data/examples/sensor/dht.rb +24 -0
  29. data/examples/sensor/ds18b20.rb +59 -0
  30. data/examples/sensor/hcsr04.rb +16 -0
  31. data/examples/sensor/neat_tph_readings.rb +32 -0
  32. data/examples/spi/bb_loopback.rb +31 -0
  33. data/examples/spi/loopback.rb +37 -0
  34. data/examples/spi/output_register.rb +38 -0
  35. data/lib/denko/piboard.rb +10 -2
  36. data/lib/denko/piboard_base.rb +21 -63
  37. data/lib/denko/piboard_core.rb +150 -130
  38. data/lib/denko/piboard_core_optimize_lookup.rb +31 -0
  39. data/lib/denko/piboard_hardware_pwm.rb +32 -0
  40. data/lib/denko/piboard_i2c.rb +59 -82
  41. data/lib/denko/piboard_i2c_bb.rb +48 -0
  42. data/lib/denko/piboard_infrared.rb +7 -44
  43. data/lib/denko/piboard_led_array.rb +9 -0
  44. data/lib/denko/piboard_map.rb +125 -38
  45. data/lib/denko/piboard_one_wire.rb +42 -0
  46. data/lib/denko/piboard_pulse.rb +11 -68
  47. data/lib/denko/piboard_spi.rb +47 -73
  48. data/lib/denko/piboard_spi_bb.rb +41 -0
  49. data/lib/denko/piboard_tone.rb +15 -26
  50. data/lib/denko/piboard_version.rb +1 -1
  51. data/scripts/99-denko.rules +9 -0
  52. data/scripts/set_permissions.rb +131 -0
  53. metadata +48 -21
  54. data/ext/gpiod/extconf.rb +0 -9
  55. data/ext/gpiod/gpiod.c +0 -179
  56. data/lib/denko/piboard_servo.rb +0 -18
  57. data/lib/gpiod.rb +0 -6
@@ -0,0 +1,25 @@
1
+ #
2
+ # Send remote control infrared signals on a hardware PWM pin.
3
+ #
4
+ require 'denko/piboard'
5
+
6
+ # Must be assigned to a hardware PWM channel in your board map.
7
+ PIN = 226
8
+
9
+ board = Denko::PiBoard.new
10
+ ir = Denko::PulseIO::IRTransmitter.new(board: board, pin: PIN)
11
+
12
+ # NEC Raw-Data=0xF708FB04. LSBFIRST, so the binary for each hex digit below is backward.
13
+ code = [ 9000, 4500, # Start bit
14
+ 560, 560, 560, 560, 560, 1690, 560, 560, # 0010 0x4 command
15
+ 560, 560, 560, 560, 560, 560, 560, 560, # 0000 0x0 command
16
+ 560, 1690, 560, 1690, 560,560, 560, 1690, # 1101 0xB command inverted
17
+ 560, 1690, 560, 1690, 560, 1690, 560, 1690, # 1111 0xF command inverted
18
+ 560, 560, 560, 560, 560, 560, 560, 1690, # 0001 0x8 address
19
+ 560, 560, 560, 560, 560, 560, 560, 560, # 0000 0x0 address
20
+ 560, 1690, 560, 1690, 560, 1690, 560, 560, # 1110 0x7 address inverted
21
+ 560, 1690, 560, 1690, 560, 1690, 560, 1690, # 1111 0xF address inverted
22
+ 560] # Stop bit
23
+
24
+ ir.emit(code)
25
+ board.finish_write
@@ -0,0 +1,17 @@
1
+ #
2
+ # AHT10 sensor over I2C, for temperature and humidity.
3
+ #
4
+ require 'denko/piboard'
5
+ require_relative 'neat_tph_readings'
6
+
7
+ board = Denko::PiBoard.new
8
+ # Use the first hardware I2C interface.
9
+ i2c_index = board.map[:i2cs].keys.first
10
+ bus = Denko::I2C::Bus.new(board: board, index: i2c_index)
11
+
12
+ # Poll it and print readings.
13
+ sensor.poll(5) do |reading|
14
+ print_tph_reading(reading)
15
+ end
16
+
17
+ sleep
@@ -0,0 +1,24 @@
1
+ #
2
+ # Use a DHT class (DHT-11 / DHT-22) sensor for temperature and humidity.
3
+ #
4
+ require 'denko/piboard'
5
+ require_relative 'neat_tph_readings'
6
+
7
+ DHT_PIN = 267
8
+
9
+ board = Denko::PiBoard.new
10
+ sensor = Denko::Sensor::DHT.new(board: board, pin: DHT_PIN)
11
+
12
+ sensor.read
13
+ puts "Temperature unit helpers: #{sensor.temperature} \xC2\xB0C | #{sensor.temperature_f} \xC2\xB0F | #{sensor.temperature_k} K"
14
+ puts
15
+
16
+ # Don't try to read it again too quickly.
17
+ sleep(1)
18
+
19
+ # Poll it and print readings.
20
+ sensor.poll(5) do |reading|
21
+ print_tph_reading(reading)
22
+ end
23
+
24
+ sleep
@@ -0,0 +1,59 @@
1
+ #
2
+ # Use a Dallas DS18B20 temperature sensor on a 1-Wire bus.
3
+ #
4
+ require 'denko/piboard'
5
+
6
+ PIN = 256
7
+
8
+ board = Denko::PiBoard.new
9
+ bus = Denko::OneWire::Bus.new(board: board, pin: PIN)
10
+
11
+ # The bus detects parasite power automatically when initialized.
12
+ # It can tell that parasite power is in use, but not by WHICH devices.
13
+ if bus.parasite_power
14
+ puts "Parasite power detected..."; puts
15
+ end
16
+
17
+ # Call #device_present to reset the bus and return presence pulse as a boolean.
18
+ if bus.device_present?
19
+ puts "Devices present on bus..."; puts
20
+ else
21
+ puts "No devices present on bus... Quitting..."
22
+ return
23
+ end
24
+
25
+ # Calling #search finds connected devices and stores them in #found_devices.
26
+ # Each hash contains a device's ROM address and matching Ruby class if one exists.
27
+ bus.search
28
+ count = bus.found_devices.count
29
+ puts "Found #{count} device#{'s' if count > 1} on the bus:"
30
+ puts bus.found_devices.inspect; puts
31
+
32
+ # We can use the search results to setup instances of the device classes.
33
+ ds18b20s = []
34
+ bus.found_devices.each do |d|
35
+ if d[:class] == Denko::Sensor::DS18B20
36
+ ds18b20s << d[:class].new(bus: bus, address: d[:address])
37
+ end
38
+ end
39
+
40
+ # Format a reading for printing on a line.
41
+ def print_reading(reading, sensor)
42
+ print "#{Time.now.strftime '%Y-%m-%d %H:%M:%S'} - "
43
+ print "Serial(HEX): #{sensor.serial_number} | Res: #{sensor.resolution} bits | "
44
+
45
+ if reading[:crc_error]
46
+ puts "CRC check failed for this reading!"
47
+ else
48
+ fahrenheit = (reading[:temperature] * 1.8 + 32).round(1)
49
+ puts "#{reading[:temperature]} \xC2\xB0C | #{fahrenheit} \xC2\xB0F"
50
+ end
51
+ end
52
+
53
+ ds18b20s.each do |sensor|
54
+ sensor.poll(5) do |reading|
55
+ print_reading(reading, sensor)
56
+ end
57
+ end
58
+
59
+ sleep
@@ -0,0 +1,16 @@
1
+ #
2
+ # Use an HC-SR04 ultrasonic distance sensor.
3
+ #
4
+ require 'denko/piboard'
5
+
6
+ ECHO_PIN = 228
7
+ TRIGGER_PIN = 270
8
+
9
+ board = Denko::PiBoard.new
10
+ hcsr04 = Denko::Sensor::HCSR04.new(board: board, pins: {trigger: TRIGGER_PIN, echo: ECHO_PIN})
11
+
12
+ hcsr04.poll(1) do |distance|
13
+ puts "Distance: #{distance} mm"
14
+ end
15
+
16
+ sleep
@@ -0,0 +1,32 @@
1
+ #
2
+ # This helper method can be used in temp/pressure/humidity sensor examples.
3
+ # Give a hash with readings as float values and it prints them neatly.
4
+ #
5
+ def print_tph_reading(reading)
6
+ elements = []
7
+
8
+ # Temperature
9
+ if reading[:temperature]
10
+ formatted_temp = reading[:temperature].to_f.round(2).to_s.ljust(5, '0')
11
+ elements << "Temperature: #{formatted_temp} \xC2\xB0C"
12
+ end
13
+
14
+ # Pressure
15
+ if reading[:pressure]
16
+ formatted_pressure = reading[:pressure].round(2).to_s.ljust(7, '0')
17
+ elements << "Pressure: #{formatted_pressure} Pa"
18
+ end
19
+
20
+ # Humidity
21
+ if reading[:humidity]
22
+ formatted_humidity = reading[:humidity].round(2).to_s.ljust(5, '0')
23
+ elements << "Humidity: #{formatted_humidity} %"
24
+ end
25
+
26
+ return if elements.empty?
27
+
28
+ # Time
29
+ print "#{Time.now.strftime '%Y-%m-%d %H:%M:%S'} - "
30
+
31
+ puts elements.join(" | ")
32
+ end
@@ -0,0 +1,31 @@
1
+ require 'denko'
2
+ require 'denko/piboard'
3
+
4
+ CLOCK_PIN = 256
5
+ INPUT_PIN = 271
6
+ OUTPUT_PIN = 258
7
+ SELECT_PIN = 272
8
+
9
+ board = Denko::PiBoard.new
10
+ bus = Denko::SPI::BitBang.new(board: board, pins: { clock: CLOCK_PIN, input: INPUT_PIN, output: OUTPUT_PIN })
11
+
12
+ TEST_DATA = [0, 1, 2, 3, 4, 5, 6, 7]
13
+
14
+ # Create a simple test component class.
15
+ class SPITester
16
+ include Denko::SPI::Peripheral::SinglePin
17
+ end
18
+ spi_tester = SPITester.new(bus: bus, pin: SELECT_PIN)
19
+ spi_tester.add_callback do |rx_bytes|
20
+ # If MOSI and MISO are connected this should match TEST_DATA.
21
+ # If not, should be 8 bytes of 255.
22
+ puts "RX bytes: #{rx_bytes.inspect}"
23
+ end
24
+
25
+ # Send the test data.
26
+ puts "TX bytes: #{TEST_DATA.inspect}"
27
+ spi_tester.spi_transfer(write: TEST_DATA, read: 8)
28
+
29
+ # Wait for read callback to run.
30
+ sleep 1
31
+ board.finish_write
@@ -0,0 +1,37 @@
1
+ require 'denko'
2
+ require 'denko/piboard'
3
+
4
+ # Use board map from ~/.denko_piboard_map.yml
5
+ board = Denko::PiBoard.new
6
+
7
+ # Use the first hardware SPI interface, and its defined :cs0 pin.
8
+ spi_index = board.map[:spis].keys.first
9
+ chip_select = board.map[:spis][spi_index][:cs0]
10
+ bus = Denko::SPI::Bus.new(board: board, index: spi_index)
11
+
12
+ TEST_DATA = [0, 1, 2, 3, 4, 5, 6, 7]
13
+
14
+ # Create a simple test component class.
15
+ class SPITester
16
+ include Denko::SPI::Peripheral::SinglePin
17
+ end
18
+ spi_tester = SPITester.new(bus: bus, pin: chip_select)
19
+ spi_tester.add_callback do |rx_bytes|
20
+ # If MOSI and MISO are connected this should match TEST_DATA.
21
+ # If not, should be 8 bytes of 255.
22
+ puts "Result : #{rx_bytes.inspect}"
23
+ end
24
+
25
+ # Send and receive same data.
26
+ puts "Tx 8 / Rx 8 : #{TEST_DATA.inspect}"
27
+ spi_tester.spi_transfer(write: TEST_DATA, read: 8)
28
+
29
+ puts "Tx 8 / Rx 12: #{TEST_DATA.inspect}"
30
+ spi_tester.spi_transfer(write: TEST_DATA, read: 12)
31
+
32
+ puts "Tx 8 / Rx 4 : #{TEST_DATA.inspect}"
33
+ spi_tester.spi_transfer(write: TEST_DATA, read: 4)
34
+
35
+ # Wait for read callback to run.
36
+ sleep 1
37
+ board.finish_write
@@ -0,0 +1,38 @@
1
+ #
2
+ # Example of LED connected through an output shift register (74HC595).
3
+ # Can be used over either a bit bang or hardware SPI interface.
4
+ #
5
+ require 'denko/piboard'
6
+
7
+ LED_PIN = 3 # On the register's parallel outputs
8
+
9
+ # Use board map from ~/.denko_piboard_map.yml
10
+ board = Denko::PiBoard.new
11
+
12
+ # Use the first hardware SPI interface, and its defined :cs0 pin.
13
+ spi_index = board.map[:spis].keys.first
14
+ chip_select = board.map[:spis][spi_index][:cs0]
15
+ bus = Denko::SPI::Bus.new(board: board, index: spi_index)
16
+
17
+ # OutputRegister needs a bus and pin (chip select).
18
+ # Other options and their defaults:
19
+ # bytes: 1 - For daisy-chaining registers
20
+ # spi_frequency: 1000000 - Only affects hardware SPI interfaces
21
+ # spi_mode: 0
22
+ # spi_bit_order: :msbfirst
23
+ #
24
+ register = Denko::SPI::OutputRegister.new(bus: bus, pin: chip_select)
25
+
26
+ # Turn on the LED by setting the corresponding bit, then writing to the register.
27
+ register.bit_set(LED_PIN, 1)
28
+ register.write
29
+
30
+ # OutputRegister is a BoardProxy. It has #digital_write, and other methods from Board.
31
+ register.digital_write(LED_PIN, 0)
32
+
33
+ # DigitalOutputs can treat it as a Board.
34
+ led = Denko::LED.new(board: register, pin: LED_PIN)
35
+
36
+ # Blink the LED and sleep the main thread.
37
+ led.blink 0.5
38
+ sleep
data/lib/denko/piboard.rb CHANGED
@@ -1,11 +1,19 @@
1
- require_relative '../gpiod'
2
1
  require_relative 'piboard_version'
3
2
  require_relative 'piboard_map'
4
3
  require_relative 'piboard_base'
4
+
5
5
  require_relative 'piboard_core'
6
+
6
7
  require_relative 'piboard_pulse'
7
- require_relative 'piboard_servo'
8
8
  require_relative 'piboard_tone'
9
+
10
+ require_relative 'piboard_hardware_pwm'
9
11
  require_relative 'piboard_infrared'
12
+
10
13
  require_relative 'piboard_i2c'
11
14
  require_relative 'piboard_spi'
15
+ require_relative 'piboard_led_array'
16
+
17
+ require_relative 'piboard_i2c_bb'
18
+ require_relative 'piboard_spi_bb'
19
+ require_relative 'piboard_one_wire'
@@ -1,81 +1,39 @@
1
1
  require 'denko'
2
- require 'pigpio'
2
+ require 'lgpio'
3
3
 
4
4
  module Denko
5
5
  class PiBoard
6
- include Pigpio::Constant
7
-
8
- attr_reader :high, :low, :pwm_high
9
-
10
- def initialize
11
- # On 64-bit systems there's a pigpio bug where wavelengths are doubled.
12
- @aarch64 = RUBY_PLATFORM.match(/aarch64/)
13
-
14
- # Pin state
15
- @pins = []
16
- @pwms = []
6
+ include Behaviors::Subcomponents
17
7
 
18
- # Listener state
19
- @pin_listeners = []
20
- @listen_mutex = Mutex.new
21
- @listen_states = Array.new(32) { 0 }
22
- @listen_thread = nil
23
- @listen_reading = 0
24
-
25
- # PiGPIO callback state. Unused for now.
26
- @pin_callbacks = []
8
+ LOW = 0
9
+ HIGH = 1
10
+ PWM_HIGH = 100
11
+ def low; LOW; end
12
+ def high; HIGH; end
13
+ def pwm_high; PWM_HIGH; end
14
+ def analog_write_resolution; 8; end
15
+
16
+ def initialize(map_yaml_file=nil)
17
+ map_yaml_file ||= Dir.home + "/" + DEFAULT_MAP_FILE
18
+ unless File.exist?(map_yaml_file)
19
+ raise StandardError, "board map file not given to PiBoard#new, and does not exist at #{map_yaml_file}"
20
+ end
27
21
 
28
- # Logic levels
29
- @low = 0
30
- @high = 1
31
- @pwm_high = 255
22
+ parse_map(map_yaml_file)
23
+ end
32
24
 
33
- # Use the pigpiod interface directly.
34
- @pi_handle = Pigpio::IF.pigpio_start
35
- exit(-1) if @pi_handle < 0
36
-
37
- # Open the libgpiod interface too.
38
- Denko::GPIOD.open_chip
25
+ def platform
26
+ :linux
39
27
  end
40
28
 
41
29
  def finish_write
42
- Pigpio::IF.pigpio_stop(@pi_handle)
43
- Denko::GPIOD.close_chip
30
+ gpio_handles.each { |h| LGPIO.chip_close(h) if h }
44
31
  end
45
32
 
46
- #
47
- # Use standard Subcomponents behavior.
48
- #
49
- include Behaviors::Subcomponents
50
-
51
- def update(pin, message, time=nil)
33
+ def update(pin, message)
52
34
  if single_pin_components[pin]
53
35
  single_pin_components[pin].update(message)
54
36
  end
55
37
  end
56
-
57
- private
58
-
59
- attr_reader :pi_handle, :wave
60
-
61
- def get_gpio(pin)
62
- @pins[pin] ||= Pigpio::UserGPIO.new(@pi_handle, pin)
63
- end
64
-
65
- def pwm_clear(pin)
66
- @pwms[pin] = nil
67
- end
68
-
69
- def new_wave
70
- stop_wave
71
- @wave = Pigpio::Wave.new(pi_handle)
72
- end
73
-
74
- def stop_wave
75
- return unless wave
76
- wave.tx_stop
77
- wave.clear
78
- @wave = nil
79
- end
80
38
  end
81
39
  end