dredger-iot 0.1.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/CHANGELOG.md +33 -0
- data/LICENSE.txt +21 -0
- data/README.md +521 -0
- data/lib/dredger/iot/bus/auto.rb +87 -0
- data/lib/dredger/iot/bus/gpio.rb +59 -0
- data/lib/dredger/iot/bus/gpio_label_adapter.rb +41 -0
- data/lib/dredger/iot/bus/gpio_libgpiod.rb +63 -0
- data/lib/dredger/iot/bus/i2c.rb +55 -0
- data/lib/dredger/iot/bus/i2c_linux.rb +52 -0
- data/lib/dredger/iot/bus.rb +5 -0
- data/lib/dredger/iot/pins/beaglebone.rb +56 -0
- data/lib/dredger/iot/pins.rb +3 -0
- data/lib/dredger/iot/reading.rb +28 -0
- data/lib/dredger/iot/scheduler.rb +43 -0
- data/lib/dredger/iot/sensors/base_sensor.rb +31 -0
- data/lib/dredger/iot/sensors/bme280.rb +26 -0
- data/lib/dredger/iot/sensors/bme280_provider.rb +157 -0
- data/lib/dredger/iot/sensors/bmp180.rb +26 -0
- data/lib/dredger/iot/sensors/dht22.rb +26 -0
- data/lib/dredger/iot/sensors/dht22_provider.rb +79 -0
- data/lib/dredger/iot/sensors/ds18b20.rb +24 -0
- data/lib/dredger/iot/sensors/ds18b20_provider.rb +55 -0
- data/lib/dredger/iot/sensors/mcp9808.rb +23 -0
- data/lib/dredger/iot/sensors.rb +11 -0
- data/lib/dredger/iot/version.rb +7 -0
- data/lib/dredger/iot.rb +14 -0
- metadata +112 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: be57692da679526bf1757f3f76cda63f472b0cbe73767b31d75bb2b1d6b063ca
|
|
4
|
+
data.tar.gz: 2a888f1d324b99fc306c9fc4fa27d27f3d8b0ef799703faae48843230459ec0c
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 0f257ec33e61e47a21f03e741068909f04cc2c701a6a4dc6d1c06e5268071cdb1887dd2b72183c96178bc3ebb0f80de00b8e62dd02493c3cbb04d4335db86825
|
|
7
|
+
data.tar.gz: eb57aad72c0629d2bf05ec953b1cc8951e6e51a9e3f80f3af50681015f0439a1f592e5f572b66a5f2c00449b72562c2725b0e8b513c9d4f90b1b44c757fb0d48
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.0] - 2025-10-03
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Initial release of dredger-iot gem
|
|
14
|
+
- GPIO access via libgpiod FFI backend with simulation fallback
|
|
15
|
+
- I2C access via Linux i2c-dev FFI backend with simulation fallback
|
|
16
|
+
- Auto-selection API for automatic backend detection
|
|
17
|
+
- Beaglebone Black P9_XX pin label mapping for libgpiod
|
|
18
|
+
- Sensor drivers:
|
|
19
|
+
- DHT22 (GPIO-based temperature/humidity sensor)
|
|
20
|
+
- BME280 (I2C temperature/humidity/pressure sensor)
|
|
21
|
+
- DS18B20 (1-Wire temperature sensor via kernel w1-gpio)
|
|
22
|
+
- BMP180 (I2C barometric pressure/temperature sensor)
|
|
23
|
+
- MCP9808 (I2C high-accuracy temperature sensor)
|
|
24
|
+
- Provider pattern for sensor abstraction and testability
|
|
25
|
+
- Scheduling utilities:
|
|
26
|
+
- `periodic_with_jitter` for distributed polling
|
|
27
|
+
- `exponential_backoff` for retry logic
|
|
28
|
+
- Full RSpec test suite with 100% coverage (excluding hardware providers)
|
|
29
|
+
- RuboCop configuration and compliance
|
|
30
|
+
- Comprehensive documentation and usage examples
|
|
31
|
+
|
|
32
|
+
[Unreleased]: https://github.com/TheMadBotterINC/dredger-iot/compare/v0.1.0...HEAD
|
|
33
|
+
[0.1.0]: https://github.com/TheMadBotterINC/dredger-iot/releases/tag/v0.1.0
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 The Mad Botter INC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
```
|
|
2
|
+
_______________
|
|
3
|
+
| DREDGER-IoT |
|
|
4
|
+
|_______________|
|
|
5
|
+
/| ___ ___ |\
|
|
6
|
+
/ | |___| |___|| \
|
|
7
|
+
/ |______________| \
|
|
8
|
+
====|========================|====
|
|
9
|
+
| | |-----------| | |
|
|
10
|
+
| |____| |_____| |
|
|
11
|
+
___|____| |____|___
|
|
12
|
+
~~~~{________|_________________________|________}~~~~~~~
|
|
13
|
+
~~ | \\ // | ~~~
|
|
14
|
+
| \\___________________// |
|
|
15
|
+
|_____________________________|
|
|
16
|
+
~~~ \\ // ~~~
|
|
17
|
+
\\_______________//
|
|
18
|
+
|
|
19
|
+
Hardware Integration for Embedded Linux
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
[](https://github.com/TheMadBotterINC/dredger-iot/actions/workflows/ci.yml)
|
|
23
|
+
[](https://badge.fury.io/rb/dredger-iot)
|
|
24
|
+
[](https://opensource.org/licenses/MIT)
|
|
25
|
+
[](https://www.ruby-lang.org/)
|
|
26
|
+
|
|
27
|
+
A small, FOSS Ruby library for hardware access on embedded Linux (Beaglebone Black, etc.).
|
|
28
|
+
|
|
29
|
+
Goals:
|
|
30
|
+
- Generic, device-agnostic hardware integration (no proprietary references)
|
|
31
|
+
- Clean abstractions for GPIO and I2C
|
|
32
|
+
- Pluggable sensor drivers (DHT22, BME280 as starters)
|
|
33
|
+
- Simple scheduling/retry helpers
|
|
34
|
+
- Config-driven pin mapping (Beaglebone P9_XX labels)
|
|
35
|
+
|
|
36
|
+
License: MIT (c) The Mad Botter INC
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
Add to your Gemfile:
|
|
41
|
+
|
|
42
|
+
```ruby path=null start=null
|
|
43
|
+
gem 'dredger-iot', git: 'https://github.com/TheMadBotterINC/dredger-iot'
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Backends
|
|
47
|
+
|
|
48
|
+
Dredger-IoT supports multiple backends per bus:
|
|
49
|
+
- GPIO: Simulation (default), libgpiod (via FFI)
|
|
50
|
+
- I2C: Simulation (default), Linux i2c-dev (via FFI + ioctl)
|
|
51
|
+
|
|
52
|
+
Backends are not auto-required for portability. Use the Auto API to choose an appropriate backend at runtime.
|
|
53
|
+
|
|
54
|
+
## Auto-selection API
|
|
55
|
+
|
|
56
|
+
```ruby path=null start=null
|
|
57
|
+
require 'dredger/iot'
|
|
58
|
+
|
|
59
|
+
# GPIO
|
|
60
|
+
gpio = Dredger::IoT::Bus::Auto.gpio
|
|
61
|
+
# I2C
|
|
62
|
+
i2c = Dredger::IoT::Bus::Auto.i2c
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Auto rules:
|
|
66
|
+
- GPIO: if /dev/gpiochip0 exists and libgpiod is available → libgpiod; else simulation
|
|
67
|
+
- I2C: if bus path exists (default /dev/i2c-1) and backend is available → linux; else simulation
|
|
68
|
+
|
|
69
|
+
Environment overrides:
|
|
70
|
+
- `DREDGER_IOT_GPIO_BACKEND`: `simulation` | `libgpiod`
|
|
71
|
+
- `DREDGER_IOT_I2C_BACKEND`: `simulation` | `linux`
|
|
72
|
+
|
|
73
|
+
## Beaglebone P9_XX label mapping
|
|
74
|
+
|
|
75
|
+
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.
|
|
76
|
+
|
|
77
|
+
Example:
|
|
78
|
+
|
|
79
|
+
```ruby path=null start=null
|
|
80
|
+
require 'dredger/iot'
|
|
81
|
+
|
|
82
|
+
gpio = Dredger::IoT::Bus::Auto.gpio # picks libgpiod on BBB, otherwise simulation
|
|
83
|
+
|
|
84
|
+
# Works with labels; on BBB this is translated to the correct chip:line
|
|
85
|
+
gpio.set_direction('P9_12', :out)
|
|
86
|
+
gpio.write('P9_12', 1)
|
|
87
|
+
value = gpio.read('P9_12')
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
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.
|
|
91
|
+
|
|
92
|
+
## Sensors
|
|
93
|
+
|
|
94
|
+
Dredger-IoT includes drivers for popular embedded sensors:
|
|
95
|
+
|
|
96
|
+
- **DHT22** - GPIO humidity/temperature sensor
|
|
97
|
+
- **BME280** - I2C temperature/humidity/pressure sensor
|
|
98
|
+
- **DS18B20** - 1-Wire digital temperature sensor
|
|
99
|
+
- **BMP180** - I2C barometric pressure/temperature sensor
|
|
100
|
+
- **MCP9808** - I2C high-accuracy temperature sensor
|
|
101
|
+
|
|
102
|
+
Sensors use a provider pattern for testability and hardware abstraction.
|
|
103
|
+
|
|
104
|
+
## Usage Examples
|
|
105
|
+
|
|
106
|
+
### DHT22 Temperature/Humidity Sensor
|
|
107
|
+
|
|
108
|
+
```ruby path=null start=null
|
|
109
|
+
require 'dredger/iot'
|
|
110
|
+
|
|
111
|
+
# Set up GPIO bus and DHT22 provider
|
|
112
|
+
gpio = Dredger::IoT::Bus::Auto.gpio
|
|
113
|
+
provider = Dredger::IoT::Sensors::DHT22Provider.new(gpio_bus: gpio)
|
|
114
|
+
|
|
115
|
+
# Create sensor instance
|
|
116
|
+
sensor = Dredger::IoT::Sensors::DHT22.new(
|
|
117
|
+
pin_label: 'P9_12',
|
|
118
|
+
provider: provider,
|
|
119
|
+
metadata: { location: 'greenhouse' }
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# Read measurements
|
|
123
|
+
readings = sensor.readings
|
|
124
|
+
readings.each do |r|
|
|
125
|
+
puts "#{r.sensor_type}: #{r.value} #{r.unit}"
|
|
126
|
+
end
|
|
127
|
+
# => humidity: 65.2 %
|
|
128
|
+
# => temperature: 22.3 celsius
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### BME280 Environmental Sensor
|
|
132
|
+
|
|
133
|
+
```ruby path=null start=null
|
|
134
|
+
require 'dredger/iot'
|
|
135
|
+
|
|
136
|
+
# Set up I2C bus and BME280 provider
|
|
137
|
+
i2c = Dredger::IoT::Bus::Auto.i2c
|
|
138
|
+
provider = Dredger::IoT::Sensors::BME280Provider.new(i2c_bus: i2c)
|
|
139
|
+
|
|
140
|
+
# Create sensor instance (default I2C address 0x76)
|
|
141
|
+
sensor = Dredger::IoT::Sensors::BME280.new(
|
|
142
|
+
i2c_addr: 0x76,
|
|
143
|
+
provider: provider,
|
|
144
|
+
metadata: { location: 'weather_station' }
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# Read all measurements
|
|
148
|
+
readings = sensor.readings
|
|
149
|
+
readings.each { |r| puts "#{r.sensor_type}: #{r.value} #{r.unit}" }
|
|
150
|
+
# => temperature: 24.1 celsius
|
|
151
|
+
# => humidity: 40.2 %
|
|
152
|
+
# => pressure: 101.4 kPa
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### DS18B20 Waterproof Temperature Sensor
|
|
156
|
+
|
|
157
|
+
```ruby path=null start=null
|
|
158
|
+
require 'dredger/iot'
|
|
159
|
+
|
|
160
|
+
# Uses Linux kernel w1-gpio module (no FFI needed)
|
|
161
|
+
provider = Dredger::IoT::Sensors::DS18B20Provider.new
|
|
162
|
+
|
|
163
|
+
# List available devices
|
|
164
|
+
devices = provider.list_devices
|
|
165
|
+
puts "Found devices: #{devices.inspect}"
|
|
166
|
+
# => ["28-0000056789ab", "28-0000056789cd"]
|
|
167
|
+
|
|
168
|
+
# Read from specific device
|
|
169
|
+
sensor = Dredger::IoT::Sensors::DS18B20.new(
|
|
170
|
+
device_id: '28-0000056789ab',
|
|
171
|
+
provider: provider,
|
|
172
|
+
metadata: { location: 'tank_a' }
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
temp = sensor.readings.first
|
|
176
|
+
puts "#{temp.value}°C"
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Multiple Sensors with Scheduled Polling
|
|
180
|
+
|
|
181
|
+
```ruby path=null start=null
|
|
182
|
+
require 'dredger/iot'
|
|
183
|
+
|
|
184
|
+
# Set up buses
|
|
185
|
+
gpio = Dredger::IoT::Bus::Auto.gpio
|
|
186
|
+
i2c = Dredger::IoT::Bus::Auto.i2c
|
|
187
|
+
|
|
188
|
+
# Create multiple sensors
|
|
189
|
+
sensors = [
|
|
190
|
+
Dredger::IoT::Sensors::DHT22.new(
|
|
191
|
+
pin_label: 'P9_12',
|
|
192
|
+
provider: Dredger::IoT::Sensors::DHT22Provider.new(gpio_bus: gpio),
|
|
193
|
+
metadata: { zone: 'indoor' }
|
|
194
|
+
),
|
|
195
|
+
Dredger::IoT::Sensors::BME280.new(
|
|
196
|
+
i2c_addr: 0x76,
|
|
197
|
+
provider: Dredger::IoT::Sensors::BME280Provider.new(i2c_bus: i2c),
|
|
198
|
+
metadata: { zone: 'outdoor' }
|
|
199
|
+
)
|
|
200
|
+
]
|
|
201
|
+
|
|
202
|
+
# Poll sensors with jitter to avoid harmonics
|
|
203
|
+
scheduler = Dredger::IoT::Scheduler.periodic_with_jitter(
|
|
204
|
+
base_interval: 60.0,
|
|
205
|
+
jitter_ratio: 0.1
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
scheduler.each do |interval|
|
|
209
|
+
sleep interval
|
|
210
|
+
|
|
211
|
+
sensors.each do |sensor|
|
|
212
|
+
readings = sensor.readings
|
|
213
|
+
timestamp = Time.now.iso8601
|
|
214
|
+
|
|
215
|
+
readings.each do |r|
|
|
216
|
+
puts "#{timestamp} [#{r.metadata[:zone]}] #{r.sensor_type}: #{r.value} #{r.unit}"
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## Scheduling Utilities
|
|
223
|
+
|
|
224
|
+
### Periodic with Jitter
|
|
225
|
+
|
|
226
|
+
Avoid harmonic spikes by adding randomized jitter to intervals:
|
|
227
|
+
|
|
228
|
+
```ruby path=null start=null
|
|
229
|
+
enum = Dredger::IoT::Scheduler.periodic_with_jitter(
|
|
230
|
+
base_interval: 10.0,
|
|
231
|
+
jitter_ratio: 0.2
|
|
232
|
+
)
|
|
233
|
+
sleeps = enum.take(3) # => [8.2, 10.4, 11.7] etc.
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Exponential Backoff
|
|
237
|
+
|
|
238
|
+
Retry failed operations with increasing delays:
|
|
239
|
+
|
|
240
|
+
```ruby path=null start=null
|
|
241
|
+
backoff = Dredger::IoT::Scheduler.exponential_backoff(
|
|
242
|
+
base: 1.0,
|
|
243
|
+
max: 30.0,
|
|
244
|
+
attempts: 5
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
backoff.each do |delay|
|
|
248
|
+
sleep delay
|
|
249
|
+
break if try_operation # retry until success or attempts exhausted
|
|
250
|
+
end
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## Hardware Setup
|
|
254
|
+
|
|
255
|
+
### Prerequisites
|
|
256
|
+
|
|
257
|
+
On embedded Linux hardware (Beaglebone, Raspberry Pi, etc.), certain kernel modules and permissions are required.
|
|
258
|
+
|
|
259
|
+
#### GPIO (libgpiod)
|
|
260
|
+
|
|
261
|
+
```bash path=null start=null
|
|
262
|
+
# Ensure gpiod tools are installed (optional but useful for testing)
|
|
263
|
+
sudo apt-get install gpiod
|
|
264
|
+
|
|
265
|
+
# Verify GPIO chips are available
|
|
266
|
+
ls /dev/gpiochip*
|
|
267
|
+
|
|
268
|
+
# Add user to gpio group for permissions
|
|
269
|
+
sudo usermod -a -G gpio $USER
|
|
270
|
+
# Log out and back in for group changes to take effect
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
#### I2C
|
|
274
|
+
|
|
275
|
+
```bash path=null start=null
|
|
276
|
+
# Enable I2C kernel module
|
|
277
|
+
sudo modprobe i2c-dev
|
|
278
|
+
|
|
279
|
+
# Make it permanent
|
|
280
|
+
echo 'i2c-dev' | sudo tee -a /etc/modules
|
|
281
|
+
|
|
282
|
+
# Add user to i2c group
|
|
283
|
+
sudo usermod -a -G i2c $USER
|
|
284
|
+
|
|
285
|
+
# Set permissions (if group doesn't exist)
|
|
286
|
+
sudo chmod 666 /dev/i2c-*
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
#### DS18B20 (1-Wire)
|
|
290
|
+
|
|
291
|
+
The DS18B20 sensor requires the kernel w1-gpio module:
|
|
292
|
+
|
|
293
|
+
```bash path=null start=null
|
|
294
|
+
# Load 1-Wire kernel modules
|
|
295
|
+
sudo modprobe w1-gpio
|
|
296
|
+
sudo modprobe w1-therm
|
|
297
|
+
|
|
298
|
+
# Make it permanent
|
|
299
|
+
echo 'w1-gpio' | sudo tee -a /etc/modules
|
|
300
|
+
echo 'w1-therm' | sudo tee -a /etc/modules
|
|
301
|
+
|
|
302
|
+
# Verify devices are detected
|
|
303
|
+
ls /sys/bus/w1/devices/
|
|
304
|
+
# Should show devices like: 28-00000xxxxxx
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
#### Beaglebone Black Device Tree
|
|
308
|
+
|
|
309
|
+
For Beaglebone Black, you may need to enable device tree overlays:
|
|
310
|
+
|
|
311
|
+
```bash path=null start=null
|
|
312
|
+
# Edit /boot/uEnv.txt and enable overlays:
|
|
313
|
+
# For I2C:
|
|
314
|
+
uboot_overlay_addr4=/lib/firmware/BB-I2C1-00A0.dtbo
|
|
315
|
+
|
|
316
|
+
# For GPIO/1-Wire (if using P9_12 for example):
|
|
317
|
+
uboot_overlay_addr5=/lib/firmware/BB-W1-P9.12-00A0.dtbo
|
|
318
|
+
|
|
319
|
+
# Reboot after changes
|
|
320
|
+
sudo reboot
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
## Troubleshooting
|
|
324
|
+
|
|
325
|
+
### Permission Denied Errors
|
|
326
|
+
|
|
327
|
+
**Problem:** `Errno::EACCES: Permission denied - /dev/gpiochip0` or `/dev/i2c-1`
|
|
328
|
+
|
|
329
|
+
**Solution:**
|
|
330
|
+
```bash path=null start=null
|
|
331
|
+
# Add your user to the appropriate group
|
|
332
|
+
sudo usermod -a -G gpio $USER
|
|
333
|
+
sudo usermod -a -G i2c $USER
|
|
334
|
+
|
|
335
|
+
# Or temporarily change permissions (not recommended for production)
|
|
336
|
+
sudo chmod 666 /dev/gpiochip*
|
|
337
|
+
sudo chmod 666 /dev/i2c-*
|
|
338
|
+
|
|
339
|
+
# Log out and back in for group changes
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### DS18B20 Not Found
|
|
343
|
+
|
|
344
|
+
**Problem:** `No DS18B20 devices found` or empty `/sys/bus/w1/devices/`
|
|
345
|
+
|
|
346
|
+
**Solution:**
|
|
347
|
+
```bash path=null start=null
|
|
348
|
+
# Verify modules are loaded
|
|
349
|
+
lsmod | grep w1
|
|
350
|
+
|
|
351
|
+
# Load if missing
|
|
352
|
+
sudo modprobe w1-gpio
|
|
353
|
+
sudo modprobe w1-therm
|
|
354
|
+
|
|
355
|
+
# Check dmesg for errors
|
|
356
|
+
sudo dmesg | grep w1
|
|
357
|
+
|
|
358
|
+
# Verify wiring: DS18B20 requires 4.7kΩ pull-up resistor on data line
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### FFI Library Not Found
|
|
362
|
+
|
|
363
|
+
**Problem:** `LoadError: cannot load such file -- ffi`
|
|
364
|
+
|
|
365
|
+
**Solution:**
|
|
366
|
+
```bash path=null start=null
|
|
367
|
+
# Install FFI gem
|
|
368
|
+
gem install ffi
|
|
369
|
+
|
|
370
|
+
# Or ensure it's in your Gemfile
|
|
371
|
+
bundle install
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### Sensor Reading Returns Nil or Errors
|
|
375
|
+
|
|
376
|
+
**Problem:** Sensor readings fail or return nil values
|
|
377
|
+
|
|
378
|
+
**Solutions:**
|
|
379
|
+
- **Verify wiring:** Double-check sensor connections (VCC, GND, data lines)
|
|
380
|
+
- **Check I2C address:** Use `i2cdetect -y 1` to scan for devices
|
|
381
|
+
- **Timing issues:** DHT22 requires ~2 second intervals between reads
|
|
382
|
+
- **Power supply:** Ensure adequate power for sensors (some require 3.3V, others 5V)
|
|
383
|
+
- **Enable simulation backend** for testing without hardware:
|
|
384
|
+
```bash path=null start=null
|
|
385
|
+
export DREDGER_IOT_GPIO_BACKEND=simulation
|
|
386
|
+
export DREDGER_IOT_I2C_BACKEND=simulation
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### I2C Device Not Detected
|
|
390
|
+
|
|
391
|
+
**Problem:** I2C sensor not responding
|
|
392
|
+
|
|
393
|
+
**Solution:**
|
|
394
|
+
```bash path=null start=null
|
|
395
|
+
# Install i2c-tools
|
|
396
|
+
sudo apt-get install i2c-tools
|
|
397
|
+
|
|
398
|
+
# Scan I2C bus (bus 1 is common, try 0 if not found)
|
|
399
|
+
i2cdetect -y 1
|
|
400
|
+
|
|
401
|
+
# Should show device at its address (e.g., 0x76 for BME280)
|
|
402
|
+
# If not detected:
|
|
403
|
+
# - Check wiring and pull-up resistors (typically 4.7kΩ on SDA/SCL)
|
|
404
|
+
# - Verify I2C is enabled in device tree
|
|
405
|
+
# - Check for bus conflicts
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
## API Reference
|
|
409
|
+
|
|
410
|
+
### Core Modules
|
|
411
|
+
|
|
412
|
+
#### `Dredger::IoT::Bus::Auto`
|
|
413
|
+
|
|
414
|
+
Automatic backend selection for GPIO and I2C buses.
|
|
415
|
+
|
|
416
|
+
```ruby path=null start=null
|
|
417
|
+
gpio = Dredger::IoT::Bus::Auto.gpio # Returns GPIO backend
|
|
418
|
+
i2c = Dredger::IoT::Bus::Auto.i2c # Returns I2C backend
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
#### `Dredger::IoT::Bus::GPIOLibgpiod`
|
|
422
|
+
|
|
423
|
+
FFI-based GPIO access using libgpiod.
|
|
424
|
+
|
|
425
|
+
**Methods:**
|
|
426
|
+
- `set_direction(pin, direction)` - Set pin as `:in` or `:out`
|
|
427
|
+
- `write(pin, value)` - Write `0` or `1` to output pin
|
|
428
|
+
- `read(pin)` - Read current value from pin (returns `0` or `1`)
|
|
429
|
+
- `close` - Release GPIO resources
|
|
430
|
+
|
|
431
|
+
#### `Dredger::IoT::Bus::I2CLinux`
|
|
432
|
+
|
|
433
|
+
FFI-based I2C access using Linux i2c-dev.
|
|
434
|
+
|
|
435
|
+
**Methods:**
|
|
436
|
+
- `write(addr, data)` - Write bytes to I2C device at address
|
|
437
|
+
- `read(addr, count)` - Read bytes from I2C device
|
|
438
|
+
- `write_read(addr, write_data, read_count)` - Combined write then read
|
|
439
|
+
- `close` - Release I2C bus
|
|
440
|
+
|
|
441
|
+
### Sensors
|
|
442
|
+
|
|
443
|
+
All sensors follow a common interface:
|
|
444
|
+
|
|
445
|
+
```ruby path=null start=null
|
|
446
|
+
sensor = Dredger::IoT::Sensors::SensorClass.new(
|
|
447
|
+
# Sensor-specific parameters
|
|
448
|
+
provider: provider_instance,
|
|
449
|
+
metadata: { custom: 'data' } # Optional metadata
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
readings = sensor.readings # Returns array of Reading objects
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
#### `Reading` Object
|
|
456
|
+
|
|
457
|
+
```ruby path=null start=null
|
|
458
|
+
reading.sensor_type # e.g., 'temperature', 'humidity', 'pressure'
|
|
459
|
+
reading.value # Numeric value
|
|
460
|
+
reading.unit # Unit string (e.g., 'celsius', '%', 'kPa')
|
|
461
|
+
reading.metadata # Hash of custom metadata
|
|
462
|
+
reading.timestamp # Time object when reading was taken
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
#### Available Sensors
|
|
466
|
+
|
|
467
|
+
- **`DHT22`** - Temperature/humidity (GPIO)
|
|
468
|
+
- Parameters: `pin_label`, `provider`
|
|
469
|
+
- Returns: temperature (celsius), humidity (%)
|
|
470
|
+
|
|
471
|
+
- **`BME280`** - Environmental sensor (I2C)
|
|
472
|
+
- Parameters: `i2c_addr` (default: `0x76`), `provider`
|
|
473
|
+
- Returns: temperature (celsius), humidity (%), pressure (kPa)
|
|
474
|
+
|
|
475
|
+
- **`DS18B20`** - Waterproof temperature (1-Wire)
|
|
476
|
+
- Parameters: `device_id`, `provider`
|
|
477
|
+
- Returns: temperature (celsius)
|
|
478
|
+
|
|
479
|
+
- **`BMP180`** - Barometric pressure (I2C)
|
|
480
|
+
- Parameters: `i2c_addr` (default: `0x77`), `provider`
|
|
481
|
+
- Returns: temperature (celsius), pressure (kPa)
|
|
482
|
+
|
|
483
|
+
- **`MCP9808`** - High-accuracy temperature (I2C)
|
|
484
|
+
- Parameters: `i2c_addr` (default: `0x18`), `provider`
|
|
485
|
+
- Returns: temperature (celsius)
|
|
486
|
+
|
|
487
|
+
### Scheduling
|
|
488
|
+
|
|
489
|
+
#### `Dredger::IoT::Scheduler.periodic_with_jitter`
|
|
490
|
+
|
|
491
|
+
Generates intervals with randomized jitter to avoid harmonic patterns.
|
|
492
|
+
|
|
493
|
+
```ruby path=null start=null
|
|
494
|
+
scheduler = Dredger::IoT::Scheduler.periodic_with_jitter(
|
|
495
|
+
base_interval: 60.0, # Base interval in seconds
|
|
496
|
+
jitter_ratio: 0.1 # ±10% jitter
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
scheduler.each { |interval| sleep interval; poll_sensors }
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
#### `Dredger::IoT::Scheduler.exponential_backoff`
|
|
503
|
+
|
|
504
|
+
Generates exponentially increasing delays for retry logic.
|
|
505
|
+
|
|
506
|
+
```ruby path=null start=null
|
|
507
|
+
backoff = Dredger::IoT::Scheduler.exponential_backoff(
|
|
508
|
+
base: 1.0, # Initial delay
|
|
509
|
+
max: 30.0, # Maximum delay
|
|
510
|
+
attempts: 5 # Number of attempts
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
backoff.each { |delay| sleep delay; break if retry_operation }
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
## Notes
|
|
517
|
+
|
|
518
|
+
- libgpiod and i2c-dev backends are optional and only required on hardware.
|
|
519
|
+
- You can explicitly `require 'dredger/iot/bus/gpio_libgpiod'` or `'dredger/iot/bus/i2c_linux'` when running on target devices.
|
|
520
|
+
- The Auto API will attempt to load these backends if it detects the corresponding device nodes are present.
|
|
521
|
+
- Simulation backends are perfect for development and testing without hardware access.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dredger
|
|
4
|
+
module IoT
|
|
5
|
+
module Bus
|
|
6
|
+
module Auto
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
10
|
+
def gpio(prefer: :auto, chip: 'gpiochip0', active_low: false)
|
|
11
|
+
pref = prefer_from_env(ENV.fetch('DREDGER_IOT_GPIO_BACKEND', nil), prefer)
|
|
12
|
+
case pref
|
|
13
|
+
when :simulation
|
|
14
|
+
Dredger::IoT::Bus::GPIO.new
|
|
15
|
+
when :libgpiod
|
|
16
|
+
begin
|
|
17
|
+
safe_require('dredger/iot/bus/gpio_libgpiod')
|
|
18
|
+
safe_require('dredger/iot/bus/gpio_label_adapter')
|
|
19
|
+
raw = Dredger::IoT::Bus::GpioLibgpiod.new(chip: chip, active_low: active_low)
|
|
20
|
+
backend = Dredger::IoT::Bus::GPIOLabelAdapter.new(backend: raw)
|
|
21
|
+
Dredger::IoT::Bus::GPIO.new(backend: backend)
|
|
22
|
+
rescue LoadError
|
|
23
|
+
Dredger::IoT::Bus::GPIO.new
|
|
24
|
+
end
|
|
25
|
+
else # :auto
|
|
26
|
+
if File.exist?('/dev/gpiochip0')
|
|
27
|
+
begin
|
|
28
|
+
safe_require('dredger/iot/bus/gpio_libgpiod')
|
|
29
|
+
safe_require('dredger/iot/bus/gpio_label_adapter')
|
|
30
|
+
raw = Dredger::IoT::Bus::GpioLibgpiod.new(chip: chip, active_low: active_low)
|
|
31
|
+
backend = Dredger::IoT::Bus::GPIOLabelAdapter.new(backend: raw)
|
|
32
|
+
return Dredger::IoT::Bus::GPIO.new(backend: backend)
|
|
33
|
+
rescue LoadError
|
|
34
|
+
# fallthrough
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
Dredger::IoT::Bus::GPIO.new
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
|
41
|
+
|
|
42
|
+
# rubocop:disable Metrics/MethodLength
|
|
43
|
+
def i2c(prefer: :auto, bus_path: '/dev/i2c-1')
|
|
44
|
+
pref = prefer_from_env(ENV.fetch('DREDGER_IOT_I2C_BACKEND', nil), prefer)
|
|
45
|
+
case pref
|
|
46
|
+
when :simulation
|
|
47
|
+
Dredger::IoT::Bus::I2C.new
|
|
48
|
+
when :linux
|
|
49
|
+
begin
|
|
50
|
+
safe_require('dredger/iot/bus/i2c_linux')
|
|
51
|
+
backend = Dredger::IoT::Bus::I2cLinux.new(bus_path: bus_path)
|
|
52
|
+
Dredger::IoT::Bus::I2C.new(backend: backend)
|
|
53
|
+
rescue LoadError
|
|
54
|
+
Dredger::IoT::Bus::I2C.new
|
|
55
|
+
end
|
|
56
|
+
else # :auto
|
|
57
|
+
if File.exist?(bus_path)
|
|
58
|
+
begin
|
|
59
|
+
safe_require('dredger/iot/bus/i2c_linux')
|
|
60
|
+
backend = Dredger::IoT::Bus::I2cLinux.new(bus_path: bus_path)
|
|
61
|
+
return Dredger::IoT::Bus::I2C.new(backend: backend)
|
|
62
|
+
rescue LoadError
|
|
63
|
+
# fallthrough
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
Dredger::IoT::Bus::I2C.new
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
# rubocop:enable Metrics/MethodLength
|
|
70
|
+
|
|
71
|
+
def prefer_from_env(env_value, default)
|
|
72
|
+
case env_value&.downcase
|
|
73
|
+
when 'simulation' then :simulation
|
|
74
|
+
when 'libgpiod' then :libgpiod
|
|
75
|
+
when 'linux' then :linux
|
|
76
|
+
else default
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def safe_require(path)
|
|
81
|
+
require path
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
# EOF
|