_______________
| DREDGER-IoT |
|_______________|
/| ___ ___ |\
/ | |___| |___|| \
/ |______________| \
====|========================|====
| | |-----------| | |
| |____| |_____| |
___|____| |____|___
~~~~{________|_________________________|________}~~~~~~~
~~ | \\ // | ~~~
| \\___________________// |
|_____________________________|
~~~ \\ // ~~~
\\_______________//
Hardware Integration for Embedded Linux
A small, FOSS Ruby library for hardware access on embedded Linux (Beaglebone Black, etc.).
Goals:
- Generic, device-agnostic hardware integration (no proprietary references)
- Clean abstractions for GPIO and I2C
- Pluggable sensor drivers (DHT22, BME280 as starters)
- Simple scheduling/retry helpers
- Config-driven pin mapping (Beaglebone P9_XX labels)
License: MIT (c) The Mad Botter INC
Installation
Add to your Gemfile:
gem 'dredger-iot', git: 'https://github.com/TheMadBotterINC/dredger-iot'
Backends
Dredger-IoT supports multiple backends per bus:
- GPIO: Simulation (default), libgpiod (via FFI)
- I2C: Simulation (default), Linux i2c-dev (via FFI + ioctl)
Backends are not auto-required for portability. Use the Auto API to choose an appropriate backend at runtime.
Auto-selection API
require 'dredger/iot'
# GPIO
gpio = Dredger::IoT::Bus::Auto.gpio
# I2C
i2c = Dredger::IoT::Bus::Auto.i2c
Auto rules:
- GPIO: if /dev/gpiochip0 exists and libgpiod is available → libgpiod; else simulation
- I2C: if bus path exists (default /dev/i2c-1) and backend is available → linux; else simulation
Environment overrides:
-
DREDGER_IOT_GPIO_BACKEND
:simulation
|libgpiod
-
DREDGER_IOT_I2C_BACKEND
:simulation
|linux
Raspberry Pi GPIO label mapping
When the libgpiod backend is selected via Auto, Dredger-IoT resolves Raspberry Pi labels to the corresponding chip:line before accessing the GPIO line. Accepted labels:
- GPIO17 or BCM17 (Broadcom numbering)
- PIN11 or BOARD11 (header pin numbers)
On most Raspberry Pi boards, GPIO lines are exposed on gpiochip0 and the line offset matches the BCM number. The adapter will translate labels accordingly.
Example:
require 'dredger/iot'
gpio = Dredger::IoT::Bus::Auto.gpio # picks libgpiod on RPi, otherwise simulation
# Use Raspberry Pi labels
gpio.set_direction('GPIO17', :out)
gpio.write('GPIO17', 1)
Beaglebone P9_XX label mapping
When the libgpiod backend is selected via Auto, Dredger-IoT resolves Beaglebone labels like P9_12
to the corresponding gpiochipN:line
before accessing the GPIO line. A minimal built-in table is provided and can be extended in future releases.
Example:
require 'dredger/iot'
gpio = Dredger::IoT::Bus::Auto.gpio # picks libgpiod on BBB, otherwise simulation
# Works with labels; on BBB this is translated to the correct chip:line
gpio.set_direction('P9_12', :out)
gpio.write('P9_12', 1)
value = gpio.read('P9_12')
If you run on a development host (no /dev/gpiochip0), Auto will default to the simulation backend and still accept labels without error, though they have no hardware effect.
Sensors
Dredger-IoT includes drivers for popular embedded sensors:
Environmental Sensors:
- DHT22 - GPIO humidity/temperature sensor
- BME280 - I2C temperature/humidity/pressure sensor
- BMP180 - I2C barometric pressure/temperature sensor
- MCP9808 - I2C high-accuracy temperature sensor
- SHT31 - I2C temperature/humidity sensor
- DS18B20 - 1-Wire digital temperature sensor
- SCD30 - I2C NDIR CO2/temperature/humidity sensor (NEW)
Light & Motion Sensors:
- BH1750 - I2C ambient light sensor (lux)
- TSL2561 - I2C ambient light sensor (lux)
- ADXL345 - I2C 3-axis accelerometer (vibration monitoring) (NEW)
Industrial Sensors:
- INA219 - I2C bus voltage/current monitor
- YF-S201 - GPIO hall effect flow meter (liquid flow) (NEW)
- NEO-6M - UART/Serial GPS module (location tracking) (NEW)
Sensors use a provider pattern for testability and hardware abstraction.
CLI Usage
Commands:
- list-sensors
- List available sensor types supported by dredger-iot.
- read SENSOR [ARGS]
- Read once or continuously from a sensor.
- Examples:
- dredger read bme280 0x76
- dredger read dht22 P9_12
- dredger read ina219 0x40 --shunt 0.1
- test-gpio PIN
- Simple blink test to verify GPIO output works.
- test-i2c
- Scans a few common I2C addresses (hardware backend only).
- info
- Prints version, detected backends, and environment variables.
Options:
- --backend BACKEND
- auto (default), simulation, hardware
- simulation forces both GPIO and I2C simulation backends
- hardware forces libgpiod (GPIO) and linux (I2C)
- --format FORMAT
- text (default) or json
- --interval SECONDS
- Poll continuously at the specified interval (e.g., 2.0)
- --shunt OHMS
- INA219-only: specify the shunt resistance in ohms (default 0.1)
Examples:
# Read a BME280 once (auto-detected backends)
dredger read bme280 0x76
# Read a DHT22 every 2 seconds, JSON output
dredger read dht22 P9_12 --interval 2 --format json
# Force simulation backends for local testing
dredger --backend simulation read bh1750 0x23
# INA219 with custom shunt
dredger read ina219 0x40 --shunt 0.05
Usage Examples
DHT22 Temperature/Humidity Sensor
require 'dredger/iot'
# Set up GPIO bus and DHT22 provider
gpio = Dredger::IoT::Bus::Auto.gpio
provider = Dredger::IoT::Sensors::DHT22Provider.new(gpio_bus: gpio)
# Create sensor instance
sensor = Dredger::IoT::Sensors::DHT22.new(
pin_label: 'P9_12',
provider: provider,
metadata: { location: 'greenhouse' }
)
# Read measurements
readings = sensor.readings
readings.each do |r|
puts "#{r.sensor_type}: #{r.value} #{r.unit}"
end
# => humidity: 65.2 %
# => temperature: 22.3 celsius
BME280 Environmental Sensor
require 'dredger/iot'
# Set up I2C bus and BME280 provider
i2c = Dredger::IoT::Bus::Auto.i2c
provider = Dredger::IoT::Sensors::BME280Provider.new(i2c_bus: i2c)
# Create sensor instance (default I2C address 0x76)
sensor = Dredger::IoT::Sensors::BME280.new(
i2c_addr: 0x76,
provider: provider,
metadata: { location: 'weather_station' }
)
# Read all measurements
readings = sensor.readings
readings.each { |r| puts "#{r.sensor_type}: #{r.value} #{r.unit}" }
# => temperature: 24.1 celsius
# => humidity: 40.2 %
# => pressure: 101.4 kPa
DS18B20 Waterproof Temperature Sensor
require 'dredger/iot'
# Uses Linux kernel w1-gpio module (no FFI needed)
provider = Dredger::IoT::Sensors::DS18B20Provider.new
# List available devices
devices = provider.list_devices
puts "Found devices: #{devices.inspect}"
# => ["28-0000056789ab", "28-0000056789cd"]
# Read from specific device
sensor = Dredger::IoT::Sensors::DS18B20.new(
device_id: '28-0000056789ab',
provider: provider,
metadata: { location: 'tank_a' }
)
temp = sensor.readings.first
puts "#{temp.value}°C"
ADXL345 Accelerometer (Vibration Monitoring)
require 'dredger/iot'
# Set up I2C bus and ADXL345 provider
i2c = Dredger::IoT::Bus::Auto.i2c
provider = Dredger::IoT::Sensors::ADXL345Provider.new(i2c_bus: i2c, range: 2) # ±2g
# Create sensor instance (default I2C address 0x53)
sensor = Dredger::IoT::Sensors::ADXL345.new(
i2c_addr: 0x53,
provider: provider,
metadata: { location: 'motor_mount' }
)
readings = sensor.readings
readings.each { |r| puts "#{r.sensor_type}: #{r.value} #{r.unit}" }
# => acceleration_x: 0.024 g
# => acceleration_y: -0.012 g
# => acceleration_z: 0.980 g
SCD30 CO2 Sensor
require 'dredger/iot'
# Set up I2C bus and SCD30 provider
i2c = Dredger::IoT::Bus::Auto.i2c
provider = Dredger::IoT::Sensors::SCD30Provider.new(i2c_bus: i2c, interval: 2)
# Create sensor instance (default I2C address 0x61)
sensor = Dredger::IoT::Sensors::SCD30.new(
i2c_addr: 0x61,
provider: provider,
metadata: { location: 'greenhouse' }
)
readings = sensor.readings
readings.each { |r| puts "#{r.sensor_type}: #{r.value} #{r.unit}" }
# => co2: 412.5 ppm
# => temperature: 23.45 celsius
# => humidity: 45.2 %
YF-S201 Flow Meter
require 'dredger/iot'
# Set up GPIO bus and flow meter provider
gpio = Dredger::IoT::Bus::Auto.gpio
provider = Dredger::IoT::Sensors::YFS201Provider.new(gpio_bus: gpio, calibration_factor: 7.5)
# Create sensor instance
sensor = Dredger::IoT::Sensors::YFS201.new(
pin_label: 'P9_12',
provider: provider,
sample_duration: 1.0, # Count pulses for 1 second
metadata: { location: 'main_line' }
)
readings = sensor.readings
flow = readings.first
puts "Flow: #{flow.value} #{flow.unit}"
puts "Pulses: #{flow.metadata[:pulses]}"
# => Flow: 5.25 L/min
# => Pulses: 656
NEO-6M GPS Module
require 'dredger/iot'
# Set up GPS provider
provider = Dredger::IoT::Sensors::NEO6MProvider.new(baud_rate: 9600, timeout: 5)
# Create sensor instance
sensor = Dredger::IoT::Sensors::NEO6M.new(
device: '/dev/ttyAMA0', # Serial device
provider: provider,
metadata: { vehicle: 'truck_1' }
)
readings = sensor.readings
readings.each { |r| puts "#{r.sensor_type}: #{r.value} #{r.unit}" }
# => latitude: 37.774929 degrees
# => longitude: -122.419418 degrees
# => altitude: 52.4 m
# => speed: 12.5 km/h
# => gps_quality: 8 satellites
Multiple Sensors with Scheduled Polling
require 'dredger/iot'
# Set up buses
gpio = Dredger::IoT::Bus::Auto.gpio
i2c = Dredger::IoT::Bus::Auto.i2c
# Create multiple sensors
sensors = [
Dredger::IoT::Sensors::DHT22.new(
pin_label: 'P9_12',
provider: Dredger::IoT::Sensors::DHT22Provider.new(gpio_bus: gpio),
metadata: { zone: 'indoor' }
),
Dredger::IoT::Sensors::BME280.new(
i2c_addr: 0x76,
provider: Dredger::IoT::Sensors::BME280Provider.new(i2c_bus: i2c),
metadata: { zone: 'outdoor' }
)
]
# Poll sensors with jitter to avoid harmonics
scheduler = Dredger::IoT::Scheduler.periodic_with_jitter(
base_interval: 60.0,
jitter_ratio: 0.1
)
scheduler.each do |interval|
sleep interval
sensors.each do |sensor|
readings = sensor.readings
timestamp = Time.now.iso8601
readings.each do |r|
puts "#{timestamp} [#{r.metadata[:zone]}] #{r.sensor_type}: #{r.value} #{r.unit}"
end
end
end
Scheduling Utilities
Periodic with Jitter
Avoid harmonic spikes by adding randomized jitter to intervals:
enum = Dredger::IoT::Scheduler.periodic_with_jitter(
base_interval: 10.0,
jitter_ratio: 0.2
)
sleeps = enum.take(3) # => [8.2, 10.4, 11.7] etc.
Exponential Backoff
Retry failed operations with increasing delays:
backoff = Dredger::IoT::Scheduler.exponential_backoff(
base: 1.0,
max: 30.0,
attempts: 5
)
backoff.each do |delay|
sleep delay
break if try_operation # retry until success or attempts exhausted
end
Hardware Setup
Prerequisites
On embedded Linux hardware (Beaglebone, Raspberry Pi, etc.), certain kernel modules and permissions are required.
GPIO (libgpiod)
# Ensure gpiod tools are installed (optional but useful for testing)
sudo apt-get install gpiod
# Verify GPIO chips are available
ls /dev/gpiochip*
# Add user to gpio group for permissions
sudo usermod -a -G gpio $USER
# Log out and back in for group changes to take effect
I2C
# Enable I2C kernel module
sudo modprobe i2c-dev
# Make it permanent
echo 'i2c-dev' | sudo tee -a /etc/modules
# Add user to i2c group
sudo usermod -a -G i2c $USER
# Set permissions (if group doesn't exist)
sudo chmod 666 /dev/i2c-*
DS18B20 (1-Wire)
The DS18B20 sensor requires the kernel w1-gpio module:
# Load 1-Wire kernel modules
sudo modprobe w1-gpio
sudo modprobe w1-therm
# Make it permanent
echo 'w1-gpio' | sudo tee -a /etc/modules
echo 'w1-therm' | sudo tee -a /etc/modules
# Verify devices are detected
ls /sys/bus/w1/devices/
# Should show devices like: 28-00000xxxxxx
Raspberry Pi OS: Enable I2C and 1-Wire
On Raspberry Pi OS you can enable I2C and 1-Wire via raspi-config or by editing /boot/config.txt.
Option A: raspi-config (recommended)
sudo raspi-config
# Interface Options → I2C → Enable
# Interface Options → 1-Wire → Enable
sudo reboot
Option B: edit /boot/config.txt
# Enable I2C
sudo sed -i 's/^#\?dtparam=i2c_arm=.*/dtparam=i2c_arm=on/' /boot/config.txt
# Enable 1-Wire on default BCM4 (PIN7)
echo 'dtoverlay=w1-gpio,gpiopin=4' | sudo tee -a /boot/config.txt
sudo reboot
Note: Dredger-IoT accepts Raspberry Pi labels like GPIO17, BCM17, and PIN11.
Beaglebone Black Device Tree
For Beaglebone Black, you may need to enable device tree overlays:
# Edit /boot/uEnv.txt and enable overlays:
# For I2C:
uboot_overlay_addr4=/lib/firmware/BB-I2C1-00A0.dtbo
# For GPIO/1-Wire (if using P9_12 for example):
uboot_overlay_addr5=/lib/firmware/BB-W1-P9.12-00A0.dtbo
# Reboot after changes
sudo reboot
Troubleshooting
Permission Denied Errors
Problem: Errno::EACCES: Permission denied - /dev/gpiochip0
or /dev/i2c-1
Solution:
# Add your user to the appropriate group
sudo usermod -a -G gpio $USER
sudo usermod -a -G i2c $USER
# Or temporarily change permissions (not recommended for production)
sudo chmod 666 /dev/gpiochip*
sudo chmod 666 /dev/i2c-*
# Log out and back in for group changes
DS18B20 Not Found
Problem: No DS18B20 devices found
or empty /sys/bus/w1/devices/
Solution:
# Verify modules are loaded
lsmod | grep w1
# Load if missing
sudo modprobe w1-gpio
sudo modprobe w1-therm
# Check dmesg for errors
sudo dmesg | grep w1
# Verify wiring: DS18B20 requires 4.7kΩ pull-up resistor on data line
FFI Library Not Found
Problem: LoadError: cannot load such file -- ffi
Solution:
# Install FFI gem
gem install ffi
# Or ensure it's in your Gemfile
bundle install
Sensor Reading Returns Nil or Errors
Problem: Sensor readings fail or return nil values
Solutions:
- Verify wiring: Double-check sensor connections (VCC, GND, data lines)
-
Check I2C address: Use
i2cdetect -y 1
to scan for devices - Timing issues: DHT22 requires ~2 second intervals between reads
- Power supply: Ensure adequate power for sensors (some require 3.3V, others 5V)
-
Enable simulation backend for testing without hardware:
export DREDGER_IOT_GPIO_BACKEND=simulation export DREDGER_IOT_I2C_BACKEND=simulation
I2C Device Not Detected
Problem: I2C sensor not responding
Solution:
# Install i2c-tools
sudo apt-get install i2c-tools
# Scan I2C bus (bus 1 is common, try 0 if not found)
i2cdetect -y 1
# Should show device at its address (e.g., 0x76 for BME280)
# If not detected:
# - Check wiring and pull-up resistors (typically 4.7kΩ on SDA/SCL)
# - Verify I2C is enabled in device tree
# - Check for bus conflicts
API Reference
Core Modules
Dredger::IoT::Bus::Auto
Automatic backend selection for GPIO and I2C buses.
gpio = Dredger::IoT::Bus::Auto.gpio # Returns GPIO backend
i2c = Dredger::IoT::Bus::Auto.i2c # Returns I2C backend
Dredger::IoT::Bus::GPIOLibgpiod
FFI-based GPIO access using libgpiod.
Methods:
-
set_direction(pin, direction)
- Set pin as:in
or:out
-
write(pin, value)
- Write0
or1
to output pin -
read(pin)
- Read current value from pin (returns0
or1
) -
close
- Release GPIO resources
Dredger::IoT::Bus::I2CLinux
FFI-based I2C access using Linux i2c-dev.
Methods:
-
write(addr, data)
- Write bytes to I2C device at address -
read(addr, count)
- Read bytes from I2C device -
write_read(addr, write_data, read_count)
- Combined write then read -
close
- Release I2C bus
Sensors
All sensors follow a common interface:
sensor = Dredger::IoT::Sensors::SensorClass.new(
# Sensor-specific parameters
provider: provider_instance,
metadata: { custom: 'data' } # Optional metadata
)
readings = sensor.readings # Returns array of Reading objects
Reading
Object
reading.sensor_type # e.g., 'temperature', 'humidity', 'pressure'
reading.value # Numeric value
reading.unit # Unit string (e.g., 'celsius', '%', 'kPa')
reading.metadata # Hash of custom metadata
reading.timestamp # Time object when reading was taken
Available Sensors
-
DHT22
- Temperature/humidity (GPIO)- Parameters:
pin_label
,provider
- Returns: temperature (celsius), humidity (%)
- Parameters:
-
BME280
- Environmental sensor (I2C)- Parameters:
i2c_addr
(default:0x76
),provider
- Returns: temperature (celsius), humidity (%), pressure (kPa)
- Parameters:
-
DS18B20
- Waterproof temperature (1-Wire)- Parameters:
device_id
,provider
- Returns: temperature (celsius)
- Parameters:
-
BMP180
- Barometric pressure (I2C)- Parameters:
i2c_addr
(default:0x77
),provider
- Returns: temperature (celsius), pressure (kPa)
- Parameters:
-
MCP9808
- High-accuracy temperature (I2C)- Parameters:
i2c_addr
(default:0x18
),provider
- Returns: temperature (celsius)
- Parameters:
-
SHT31
- Temperature/humidity (I2C)- Parameters:
i2c_addr
(default:0x44
),provider
- Returns: temperature (celsius), humidity (%)
- Parameters:
-
BH1750
- Ambient light (I2C)- Parameters:
i2c_addr
(default:0x23
),provider
- Returns: illuminance (lux)
- Parameters:
-
TSL2561
- Ambient light (I2C)- Parameters:
i2c_addr
(default:0x39
),provider
- Returns: illuminance (lux)
- Parameters:
-
INA219
- Bus voltage/current monitor (I2C)- Parameters:
i2c_addr
(default:0x40
),provider
- Returns: bus_voltage (V), current (mA)
- CLI example:
dredger read ina219 0x40 --shunt 0.1
- Parameters:
-
ADXL345
- 3-axis accelerometer (I2C)- Parameters:
i2c_addr
(default:0x53
),provider
- Returns: acceleration_x (g), acceleration_y (g), acceleration_z (g)
- Ranges: ±2g, ±4g, ±8g, ±16g (configurable in provider)
- Parameters:
-
SCD30
- NDIR CO2 sensor (I2C)- Parameters:
i2c_addr
(default:0x61
),provider
- Returns: co2 (ppm), temperature (celsius), humidity (%)
- Range: 400-10,000 ppm CO2
- Parameters:
-
YFS201
- Hall effect flow meter (GPIO)- Parameters:
pin_label
,provider
,sample_duration
(default:1.0
) - Returns: flow_rate (L/min)
- Range: 1-30 L/min
- Parameters:
-
NEO6M
- GPS module (UART/Serial)- Parameters:
device
(default:'/dev/ttyAMA0'
),provider
- Returns: latitude (degrees), longitude (degrees), altitude (m), speed (km/h), gps_quality (satellites)
- Parameters:
Scheduling
Dredger::IoT::Scheduler.periodic_with_jitter
Generates intervals with randomized jitter to avoid harmonic patterns.
scheduler = Dredger::IoT::Scheduler.periodic_with_jitter(
base_interval: 60.0, # Base interval in seconds
jitter_ratio: 0.1 # ±10% jitter
)
scheduler.each { |interval| sleep interval; poll_sensors }
Dredger::IoT::Scheduler.exponential_backoff
Generates exponentially increasing delays for retry logic.
backoff = Dredger::IoT::Scheduler.exponential_backoff(
base: 1.0, # Initial delay
max: 30.0, # Maximum delay
attempts: 5 # Number of attempts
)
backoff.each { |delay| sleep delay; break if retry_operation }
Notes
- libgpiod and i2c-dev backends are optional and only required on hardware.
- You can explicitly
require 'dredger/iot/bus/gpio_libgpiod'
or'dredger/iot/bus/i2c_linux'
when running on target devices. - The Auto API will attempt to load these backends if it detects the corresponding device nodes are present.
- Simulation backends are perfect for development and testing without hardware access.