dredger-iot 0.1.1 → 0.2.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 +4 -4
- data/CHANGELOG.md +23 -1
- data/README.md +69 -0
- data/bin/dredger +293 -0
- data/lib/dredger/iot/sensors/bh1750.rb +22 -0
- data/lib/dredger/iot/sensors/bh1750_provider.rb +34 -0
- data/lib/dredger/iot/sensors/ina219.rb +25 -0
- data/lib/dredger/iot/sensors/ina219_provider.rb +54 -0
- data/lib/dredger/iot/sensors/sht31.rb +25 -0
- data/lib/dredger/iot/sensors/sht31_provider.rb +41 -0
- data/lib/dredger/iot/sensors/tsl2561.rb +22 -0
- data/lib/dredger/iot/sensors/tsl2561_provider.rb +72 -0
- data/lib/dredger/iot/sensors.rb +8 -0
- data/lib/dredger/iot/version.rb +1 -1
- metadata +34 -22
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 84cb846712d4d17f969147389859dfae1bdc8a67fb304e491e857891726d7234
|
|
4
|
+
data.tar.gz: 5c905f7f6e651692f6eff7145e9b9399357df33e340d4694eceab260f31b1b00
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a0cfe26a32e99b21b3a6ccbddede15162bf85f7a85aac6e32715f34cc8a300218515e358bbd271cd5dd648d9ca6df892eb4fda2cd3bf08761a128c25887142ba
|
|
7
|
+
data.tar.gz: 179a10159ea14047fa146ed5a03223c2f362591357c39c754fef36718d75c2c35fff7bb0e7facafc9c094e48d0015e5eef0a367ca10cb4f93674e0369f2fa6f8
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.0] - 2025-10-05
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- New sensors: SHT31 (I2C temp/humidity), BH1750 (I2C lux), TSL2561 (I2C lux), INA219 (I2C bus voltage/current)
|
|
14
|
+
- CLI: --shunt option for INA219 to specify shunt resistance (default 0.1 Ω)
|
|
15
|
+
- Examples: example scripts for SHT31, BH1750, TSL2561, INA219
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- README: document new sensors and CLI usage
|
|
19
|
+
- Coverage: exclude all provider implementations from coverage (hardware-dependent)
|
|
20
|
+
|
|
21
|
+
## [0.1.2] - 2025-10-04
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
- Developer tools and community infrastructure.
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
- Examples: seed BME280 calibration and sample data for I2C simulation.
|
|
28
|
+
- Examples: allow interval/cycles override via env for fast simulation runs.
|
|
29
|
+
|
|
10
30
|
## [0.1.1] - 2025-10-04
|
|
11
31
|
|
|
12
32
|
### Fixed
|
|
@@ -44,6 +64,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
44
64
|
- RuboCop configuration and compliance
|
|
45
65
|
- Comprehensive documentation and usage examples
|
|
46
66
|
|
|
47
|
-
[Unreleased]: https://github.com/TheMadBotterINC/dredger-iot/compare/v0.
|
|
67
|
+
[Unreleased]: https://github.com/TheMadBotterINC/dredger-iot/compare/v0.2.0...HEAD
|
|
68
|
+
[0.2.0]: https://github.com/TheMadBotterINC/dredger-iot/compare/v0.1.2...v0.2.0
|
|
69
|
+
[0.1.2]: https://github.com/TheMadBotterINC/dredger-iot/compare/v0.1.1...v0.1.2
|
|
48
70
|
[0.1.1]: https://github.com/TheMadBotterINC/dredger-iot/compare/v0.1.0...v0.1.1
|
|
49
71
|
[0.1.0]: https://github.com/TheMadBotterINC/dredger-iot/releases/tag/v0.1.0
|
data/README.md
CHANGED
|
@@ -98,9 +98,58 @@ Dredger-IoT includes drivers for popular embedded sensors:
|
|
|
98
98
|
- **DS18B20** - 1-Wire digital temperature sensor
|
|
99
99
|
- **BMP180** - I2C barometric pressure/temperature sensor
|
|
100
100
|
- **MCP9808** - I2C high-accuracy temperature sensor
|
|
101
|
+
- **SHT31** - I2C temperature/humidity sensor
|
|
102
|
+
- **BH1750** - I2C ambient light sensor (lux)
|
|
103
|
+
- **TSL2561** - I2C ambient light sensor (lux)
|
|
104
|
+
- **INA219** - I2C bus voltage/current monitor
|
|
101
105
|
|
|
102
106
|
Sensors use a provider pattern for testability and hardware abstraction.
|
|
103
107
|
|
|
108
|
+
## CLI Usage
|
|
109
|
+
|
|
110
|
+
Commands:
|
|
111
|
+
- list-sensors
|
|
112
|
+
- List available sensor types supported by dredger-iot.
|
|
113
|
+
- read SENSOR [ARGS]
|
|
114
|
+
- Read once or continuously from a sensor.
|
|
115
|
+
- Examples:
|
|
116
|
+
- dredger read bme280 0x76
|
|
117
|
+
- dredger read dht22 P9_12
|
|
118
|
+
- dredger read ina219 0x40 --shunt 0.1
|
|
119
|
+
- test-gpio PIN
|
|
120
|
+
- Simple blink test to verify GPIO output works.
|
|
121
|
+
- test-i2c
|
|
122
|
+
- Scans a few common I2C addresses (hardware backend only).
|
|
123
|
+
- info
|
|
124
|
+
- Prints version, detected backends, and environment variables.
|
|
125
|
+
|
|
126
|
+
Options:
|
|
127
|
+
- --backend BACKEND
|
|
128
|
+
- auto (default), simulation, hardware
|
|
129
|
+
- simulation forces both GPIO and I2C simulation backends
|
|
130
|
+
- hardware forces libgpiod (GPIO) and linux (I2C)
|
|
131
|
+
- --format FORMAT
|
|
132
|
+
- text (default) or json
|
|
133
|
+
- --interval SECONDS
|
|
134
|
+
- Poll continuously at the specified interval (e.g., 2.0)
|
|
135
|
+
- --shunt OHMS
|
|
136
|
+
- INA219-only: specify the shunt resistance in ohms (default 0.1)
|
|
137
|
+
|
|
138
|
+
Examples:
|
|
139
|
+
```bash
|
|
140
|
+
# Read a BME280 once (auto-detected backends)
|
|
141
|
+
dredger read bme280 0x76
|
|
142
|
+
|
|
143
|
+
# Read a DHT22 every 2 seconds, JSON output
|
|
144
|
+
dredger read dht22 P9_12 --interval 2 --format json
|
|
145
|
+
|
|
146
|
+
# Force simulation backends for local testing
|
|
147
|
+
dredger --backend simulation read bh1750 0x23
|
|
148
|
+
|
|
149
|
+
# INA219 with custom shunt
|
|
150
|
+
dredger read ina219 0x40 --shunt 0.05
|
|
151
|
+
```
|
|
152
|
+
|
|
104
153
|
## Usage Examples
|
|
105
154
|
|
|
106
155
|
### DHT22 Temperature/Humidity Sensor
|
|
@@ -483,6 +532,26 @@ reading.timestamp # Time object when reading was taken
|
|
|
483
532
|
- **`MCP9808`** - High-accuracy temperature (I2C)
|
|
484
533
|
- Parameters: `i2c_addr` (default: `0x18`), `provider`
|
|
485
534
|
- Returns: temperature (celsius)
|
|
535
|
+
|
|
536
|
+
- **`SHT31`** - Temperature/humidity (I2C)
|
|
537
|
+
- Parameters: `i2c_addr` (default: `0x44`), `provider`
|
|
538
|
+
- Returns: temperature (celsius), humidity (%)
|
|
539
|
+
|
|
540
|
+
- **`BH1750`** - Ambient light (I2C)
|
|
541
|
+
- Parameters: `i2c_addr` (default: `0x23`), `provider`
|
|
542
|
+
- Returns: illuminance (lux)
|
|
543
|
+
|
|
544
|
+
- **`TSL2561`** - Ambient light (I2C)
|
|
545
|
+
- Parameters: `i2c_addr` (default: `0x39`), `provider`
|
|
546
|
+
- Returns: illuminance (lux)
|
|
547
|
+
|
|
548
|
+
- **`INA219`** - Bus voltage/current monitor (I2C)
|
|
549
|
+
- Parameters: `i2c_addr` (default: `0x40`), `provider`
|
|
550
|
+
- Returns: bus_voltage (V), current (mA)
|
|
551
|
+
- CLI example:
|
|
552
|
+
```bash path=null start=null
|
|
553
|
+
dredger read ina219 0x40 --shunt 0.1
|
|
554
|
+
```
|
|
486
555
|
|
|
487
556
|
### Scheduling
|
|
488
557
|
|
data/bin/dredger
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'bundler/setup'
|
|
5
|
+
require 'dredger/iot'
|
|
6
|
+
require 'optparse'
|
|
7
|
+
require 'json'
|
|
8
|
+
|
|
9
|
+
# Dredger CLI - Quick sensor testing and debugging tool
|
|
10
|
+
class DredgerCLI
|
|
11
|
+
def initialize
|
|
12
|
+
@options = {
|
|
13
|
+
backend: 'auto',
|
|
14
|
+
format: 'text',
|
|
15
|
+
interval: nil
|
|
16
|
+
}
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def run(args)
|
|
20
|
+
parser = build_parser
|
|
21
|
+
parser.parse!(args)
|
|
22
|
+
|
|
23
|
+
command = args.shift
|
|
24
|
+
case command
|
|
25
|
+
when 'list-sensors'
|
|
26
|
+
list_sensors
|
|
27
|
+
when 'read'
|
|
28
|
+
read_sensor(args)
|
|
29
|
+
when 'test-gpio'
|
|
30
|
+
test_gpio(args)
|
|
31
|
+
when 'test-i2c'
|
|
32
|
+
test_i2c
|
|
33
|
+
when 'info'
|
|
34
|
+
show_info
|
|
35
|
+
else
|
|
36
|
+
puts parser
|
|
37
|
+
exit 1
|
|
38
|
+
end
|
|
39
|
+
rescue StandardError => e
|
|
40
|
+
puts "Error: #{e.message}"
|
|
41
|
+
puts e.backtrace if ENV['DEBUG']
|
|
42
|
+
exit 1
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def build_parser
|
|
48
|
+
OptionParser.new do |opts|
|
|
49
|
+
opts.banner = 'Usage: dredger [options] COMMAND [args]'
|
|
50
|
+
opts.separator ''
|
|
51
|
+
opts.separator 'Commands:'
|
|
52
|
+
opts.separator ' list-sensors List available sensor types'
|
|
53
|
+
opts.separator ' read SENSOR [OPTIONS] Read from a sensor'
|
|
54
|
+
opts.separator ' test-gpio PIN Test GPIO pin'
|
|
55
|
+
opts.separator ' test-i2c Scan I2C bus'
|
|
56
|
+
opts.separator ' info Show system information'
|
|
57
|
+
opts.separator ''
|
|
58
|
+
opts.separator 'Options:'
|
|
59
|
+
|
|
60
|
+
opts.on('--backend BACKEND', 'Backend: auto, simulation, hardware') do |b|
|
|
61
|
+
@options[:backend] = b
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
opts.on('--format FORMAT', 'Output format: text, json') do |f|
|
|
65
|
+
@options[:format] = f
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
opts.on('--interval SECONDS', Float, 'Continuous reading interval') do |i|
|
|
69
|
+
@options[:interval] = i
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
opts.on('--shunt OHMS', Float, 'INA219 shunt resistance in ohms (default: 0.1)') do |ohms|
|
|
73
|
+
@options[:ina219_shunt] = ohms
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
opts.on('-h', '--help', 'Show this help') do
|
|
77
|
+
puts opts
|
|
78
|
+
exit
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
opts.on('-v', '--version', 'Show version') do
|
|
82
|
+
puts "dredger-iot #{Dredger::IoT::VERSION}"
|
|
83
|
+
exit
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def list_sensors
|
|
89
|
+
sensors = {
|
|
90
|
+
'dht22' => 'DHT22 - Temperature/Humidity (GPIO)',
|
|
91
|
+
'bme280' => 'BME280 - Temperature/Humidity/Pressure (I2C)',
|
|
92
|
+
'ds18b20' => 'DS18B20 - Waterproof Temperature (1-Wire)',
|
|
93
|
+
'bmp180' => 'BMP180 - Barometric Pressure/Temperature (I2C)',
|
|
94
|
+
'mcp9808' => 'MCP9808 - High-Accuracy Temperature (I2C)',
|
|
95
|
+
'sht31' => 'SHT31 - Temperature/Humidity (I2C)',
|
|
96
|
+
'bh1750' => 'BH1750 - Ambient Light (I2C)',
|
|
97
|
+
'tsl2561' => 'TSL2561 - Ambient Light (I2C)',
|
|
98
|
+
'ina219' => 'INA219 - Bus Voltage/Current (I2C)'
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
puts 'Available Sensors:'
|
|
102
|
+
puts
|
|
103
|
+
sensors.each do |key, desc|
|
|
104
|
+
puts " #{key.ljust(10)} - #{desc}"
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def read_sensor(args)
|
|
109
|
+
sensor_type = args.shift
|
|
110
|
+
unless sensor_type
|
|
111
|
+
puts 'Error: Sensor type required'
|
|
112
|
+
puts 'Run "dredger list-sensors" to see available sensors'
|
|
113
|
+
exit 1
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
setup_backends
|
|
117
|
+
sensor = create_sensor(sensor_type, args)
|
|
118
|
+
|
|
119
|
+
if @options[:interval]
|
|
120
|
+
read_continuous(sensor)
|
|
121
|
+
else
|
|
122
|
+
read_once(sensor)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def create_sensor(type, args)
|
|
127
|
+
case type
|
|
128
|
+
when 'dht22'
|
|
129
|
+
pin = args.shift || 'P9_12'
|
|
130
|
+
gpio = Dredger::IoT::Bus::Auto.gpio
|
|
131
|
+
provider = Dredger::IoT::Sensors::DHT22Provider.new(gpio_bus: gpio)
|
|
132
|
+
Dredger::IoT::Sensors::DHT22.new(pin_label: pin, provider: provider)
|
|
133
|
+
when 'bme280'
|
|
134
|
+
addr = (args.shift || '0x76').to_i(16)
|
|
135
|
+
i2c = Dredger::IoT::Bus::Auto.i2c
|
|
136
|
+
provider = Dredger::IoT::Sensors::BME280Provider.new(i2c_bus: i2c)
|
|
137
|
+
Dredger::IoT::Sensors::BME280.new(i2c_addr: addr, provider: provider)
|
|
138
|
+
when 'ds18b20'
|
|
139
|
+
provider = Dredger::IoT::Sensors::DS18B20Provider.new
|
|
140
|
+
devices = provider.list_devices
|
|
141
|
+
device_id = args.shift || devices.first
|
|
142
|
+
unless device_id
|
|
143
|
+
puts 'Error: No DS18B20 devices found'
|
|
144
|
+
exit 1
|
|
145
|
+
end
|
|
146
|
+
Dredger::IoT::Sensors::DS18B20.new(device_id: device_id, provider: provider)
|
|
147
|
+
when 'sht31'
|
|
148
|
+
addr = (args.shift || '0x44').to_i(16)
|
|
149
|
+
i2c = Dredger::IoT::Bus::Auto.i2c
|
|
150
|
+
provider = Dredger::IoT::Sensors::SHT31Provider.new(i2c_bus: i2c)
|
|
151
|
+
Dredger::IoT::Sensors::SHT31.new(i2c_addr: addr, provider: provider)
|
|
152
|
+
when 'bh1750'
|
|
153
|
+
addr = (args.shift || '0x23').to_i(16)
|
|
154
|
+
i2c = Dredger::IoT::Bus::Auto.i2c
|
|
155
|
+
provider = Dredger::IoT::Sensors::BH1750Provider.new(i2c_bus: i2c)
|
|
156
|
+
Dredger::IoT::Sensors::BH1750.new(i2c_addr: addr, provider: provider)
|
|
157
|
+
when 'tsl2561'
|
|
158
|
+
addr = (args.shift || '0x39').to_i(16)
|
|
159
|
+
i2c = Dredger::IoT::Bus::Auto.i2c
|
|
160
|
+
provider = Dredger::IoT::Sensors::TSL2561Provider.new(i2c_bus: i2c)
|
|
161
|
+
Dredger::IoT::Sensors::TSL2561.new(i2c_addr: addr, provider: provider)
|
|
162
|
+
when 'ina219'
|
|
163
|
+
addr = (args.shift || '0x40').to_i(16)
|
|
164
|
+
i2c = Dredger::IoT::Bus::Auto.i2c
|
|
165
|
+
shunt = @options[:ina219_shunt] || 0.1
|
|
166
|
+
provider = Dredger::IoT::Sensors::INA219Provider.new(i2c_bus: i2c, shunt_resistance_ohms: shunt)
|
|
167
|
+
Dredger::IoT::Sensors::INA219.new(i2c_addr: addr, provider: provider)
|
|
168
|
+
else
|
|
169
|
+
puts "Error: Unknown sensor type '#{type}'"
|
|
170
|
+
exit 1
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def read_once(sensor)
|
|
175
|
+
readings = sensor.readings
|
|
176
|
+
output_readings(readings)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def read_continuous(sensor)
|
|
180
|
+
puts "Reading every #{@options[:interval]} seconds (Ctrl+C to stop)..."
|
|
181
|
+
puts
|
|
182
|
+
|
|
183
|
+
loop do
|
|
184
|
+
readings = sensor.readings
|
|
185
|
+
output_readings(readings)
|
|
186
|
+
puts if @options[:format] == 'text'
|
|
187
|
+
sleep @options[:interval]
|
|
188
|
+
end
|
|
189
|
+
rescue Interrupt
|
|
190
|
+
puts "\nStopped"
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def output_readings(readings)
|
|
194
|
+
case @options[:format]
|
|
195
|
+
when 'json'
|
|
196
|
+
data = readings.map do |r|
|
|
197
|
+
{
|
|
198
|
+
sensor_type: r.sensor_type,
|
|
199
|
+
value: r.value,
|
|
200
|
+
unit: r.unit,
|
|
201
|
+
timestamp: r.timestamp.iso8601
|
|
202
|
+
}
|
|
203
|
+
end
|
|
204
|
+
puts JSON.pretty_generate(data)
|
|
205
|
+
else
|
|
206
|
+
timestamp = Time.now.strftime('%Y-%m-%d %H:%M:%S')
|
|
207
|
+
puts "[#{timestamp}]"
|
|
208
|
+
readings.each do |r|
|
|
209
|
+
puts " #{r.sensor_type}: #{r.value} #{r.unit}"
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def test_gpio(args)
|
|
215
|
+
pin = args.shift || 'P9_12'
|
|
216
|
+
setup_backends
|
|
217
|
+
gpio = Dredger::IoT::Bus::Auto.gpio
|
|
218
|
+
|
|
219
|
+
puts "Testing GPIO pin #{pin}..."
|
|
220
|
+
gpio.set_direction(pin, :out)
|
|
221
|
+
|
|
222
|
+
3.times do |i|
|
|
223
|
+
puts " Cycle #{i + 1}: HIGH"
|
|
224
|
+
gpio.write(pin, 1)
|
|
225
|
+
sleep 0.5
|
|
226
|
+
|
|
227
|
+
puts " Cycle #{i + 1}: LOW"
|
|
228
|
+
gpio.write(pin, 0)
|
|
229
|
+
sleep 0.5
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
puts 'GPIO test complete'
|
|
233
|
+
ensure
|
|
234
|
+
gpio&.close
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def test_i2c
|
|
238
|
+
setup_backends
|
|
239
|
+
i2c = Dredger::IoT::Bus::Auto.i2c
|
|
240
|
+
|
|
241
|
+
puts 'Scanning I2C bus...'
|
|
242
|
+
puts 'Note: This only works with hardware backend'
|
|
243
|
+
puts
|
|
244
|
+
|
|
245
|
+
# Common I2C addresses
|
|
246
|
+
addresses = [0x18, 0x76, 0x77]
|
|
247
|
+
addresses.each do |addr|
|
|
248
|
+
print " 0x#{addr.to_s(16).upcase}: "
|
|
249
|
+
begin
|
|
250
|
+
i2c.read(addr, 1)
|
|
251
|
+
puts 'FOUND'
|
|
252
|
+
rescue StandardError
|
|
253
|
+
puts '--'
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
ensure
|
|
257
|
+
i2c&.close
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def show_info
|
|
261
|
+
puts "Dredger-IoT v#{Dredger::IoT::VERSION}"
|
|
262
|
+
puts "Ruby #{RUBY_VERSION}"
|
|
263
|
+
puts
|
|
264
|
+
puts 'Backends:'
|
|
265
|
+
puts " GPIO: #{gpio_backend_info}"
|
|
266
|
+
puts " I2C: #{i2c_backend_info}"
|
|
267
|
+
puts
|
|
268
|
+
puts 'Environment:'
|
|
269
|
+
puts " DREDGER_IOT_GPIO_BACKEND: #{ENV['DREDGER_IOT_GPIO_BACKEND'] || '(not set)'}"
|
|
270
|
+
puts " DREDGER_IOT_I2C_BACKEND: #{ENV['DREDGER_IOT_I2C_BACKEND'] || '(not set)'}"
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def setup_backends
|
|
274
|
+
case @options[:backend]
|
|
275
|
+
when 'simulation'
|
|
276
|
+
ENV['DREDGER_IOT_GPIO_BACKEND'] = 'simulation'
|
|
277
|
+
ENV['DREDGER_IOT_I2C_BACKEND'] = 'simulation'
|
|
278
|
+
when 'hardware'
|
|
279
|
+
ENV['DREDGER_IOT_GPIO_BACKEND'] = 'libgpiod'
|
|
280
|
+
ENV['DREDGER_IOT_I2C_BACKEND'] = 'linux'
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def gpio_backend_info
|
|
285
|
+
File.exist?('/dev/gpiochip0') ? 'libgpiod (detected)' : 'simulation (no hardware)'
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def i2c_backend_info
|
|
289
|
+
File.exist?('/dev/i2c-1') ? 'linux (detected)' : 'simulation (no hardware)'
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
DredgerCLI.new.run(ARGV) if $PROGRAM_NAME == __FILE__
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dredger
|
|
4
|
+
module IoT
|
|
5
|
+
module Sensors
|
|
6
|
+
# BH1750 ambient light sensor (I2C)
|
|
7
|
+
# Provider must respond to :read_lux(addr) -> Float (lux)
|
|
8
|
+
class BH1750 < BaseSensor
|
|
9
|
+
def initialize(i2c_addr:, provider:, metadata: {})
|
|
10
|
+
super(metadata: metadata)
|
|
11
|
+
@i2c_addr = i2c_addr
|
|
12
|
+
@provider = provider
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def readings
|
|
16
|
+
lux = @provider.read_lux(@i2c_addr)
|
|
17
|
+
[reading(sensor_type: 'illuminance', value: lux, unit: 'lux')]
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dredger
|
|
4
|
+
module IoT
|
|
5
|
+
module Sensors
|
|
6
|
+
# Hardware provider for BH1750 ambient light sensor (I2C)
|
|
7
|
+
# Measurement: Continuous High-Resolution Mode (1 lx, typical 120ms)
|
|
8
|
+
class BH1750Provider
|
|
9
|
+
POWER_ON = 0x01
|
|
10
|
+
RESET = 0x07
|
|
11
|
+
CONT_HIRES = 0x10
|
|
12
|
+
|
|
13
|
+
def initialize(i2c_bus:)
|
|
14
|
+
@i2c = i2c_bus
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Returns lux as Float
|
|
18
|
+
def read_lux(addr)
|
|
19
|
+
# Power on and reset
|
|
20
|
+
@i2c.write(addr, [POWER_ON])
|
|
21
|
+
@i2c.write(addr, [RESET])
|
|
22
|
+
# Start continuous high-resolution measurement
|
|
23
|
+
@i2c.write(addr, [CONT_HIRES])
|
|
24
|
+
# Wait for conversion
|
|
25
|
+
sleep(0.18)
|
|
26
|
+
# Read 2 bytes (big-endian)
|
|
27
|
+
bytes = @i2c.read(addr, 2)
|
|
28
|
+
raw = (bytes[0] << 8) | bytes[1]
|
|
29
|
+
(raw / 1.2).to_f
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dredger
|
|
4
|
+
module IoT
|
|
5
|
+
module Sensors
|
|
6
|
+
# INA219 current/voltage/power monitor (I2C)
|
|
7
|
+
# Provider must respond to :read_measurements(addr) -> { bus_voltage_v:, current_ma:, shunt_voltage_mv: }
|
|
8
|
+
class INA219 < BaseSensor
|
|
9
|
+
def initialize(i2c_addr:, provider:, metadata: {})
|
|
10
|
+
super(metadata: metadata)
|
|
11
|
+
@i2c_addr = i2c_addr
|
|
12
|
+
@provider = provider
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def readings
|
|
16
|
+
m = @provider.read_measurements(@i2c_addr)
|
|
17
|
+
[
|
|
18
|
+
reading(sensor_type: 'bus_voltage', value: m[:bus_voltage_v], unit: 'V'),
|
|
19
|
+
reading(sensor_type: 'current', value: m[:current_ma], unit: 'mA')
|
|
20
|
+
]
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dredger
|
|
4
|
+
module IoT
|
|
5
|
+
module Sensors
|
|
6
|
+
# Hardware provider for INA219 current/voltage monitor (I2C)
|
|
7
|
+
# Simplified: computes current from shunt voltage and provided shunt resistance,
|
|
8
|
+
# avoids calibration register dependency.
|
|
9
|
+
class INA219Provider
|
|
10
|
+
REG_SHUNT_VOLTAGE = 0x01
|
|
11
|
+
REG_BUS_VOLTAGE = 0x02
|
|
12
|
+
|
|
13
|
+
def initialize(i2c_bus:, shunt_resistance_ohms: 0.1)
|
|
14
|
+
@i2c = i2c_bus
|
|
15
|
+
@r_shunt = shunt_resistance_ohms.to_f
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Returns { bus_voltage_v: Float, current_ma: Float, shunt_voltage_mv: Float }
|
|
19
|
+
def read_measurements(addr)
|
|
20
|
+
shunt_raw = read_word(addr, REG_SHUNT_VOLTAGE, signed: true)
|
|
21
|
+
bus_raw = read_word(addr, REG_BUS_VOLTAGE, signed: false)
|
|
22
|
+
|
|
23
|
+
# Shunt voltage LSB = 10uV => mV = raw * 0.01
|
|
24
|
+
shunt_mv = shunt_raw * 0.01
|
|
25
|
+
# Current (mA) = (shunt_mv / R_ohms)
|
|
26
|
+
current_ma = (@r_shunt.positive? ? (shunt_mv / @r_shunt) : 0.0)
|
|
27
|
+
|
|
28
|
+
# Bus voltage: bits [15:3] * 4mV
|
|
29
|
+
bus_voltage_v = ((bus_raw >> 3) * 0.004)
|
|
30
|
+
|
|
31
|
+
{
|
|
32
|
+
bus_voltage_v: bus_voltage_v,
|
|
33
|
+
current_ma: current_ma,
|
|
34
|
+
shunt_voltage_mv: shunt_mv
|
|
35
|
+
}
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
# Read 16-bit word
|
|
41
|
+
# INA219 registers are big-endian; register reads may return bytes LSB-first
|
|
42
|
+
def read_word(addr, reg, signed: false)
|
|
43
|
+
bytes = @i2c.read(addr, 2, register: reg)
|
|
44
|
+
val = (bytes[0] << 8) | bytes[1]
|
|
45
|
+
if signed && val > 0x7FFF
|
|
46
|
+
val - 0x1_0000
|
|
47
|
+
else
|
|
48
|
+
val
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dredger
|
|
4
|
+
module IoT
|
|
5
|
+
module Sensors
|
|
6
|
+
# SHT31 temperature/humidity sensor (I2C)
|
|
7
|
+
# Provider must respond to :read_measurements(addr) -> { temperature_c:, humidity: }
|
|
8
|
+
class SHT31 < BaseSensor
|
|
9
|
+
def initialize(i2c_addr:, provider:, metadata: {})
|
|
10
|
+
super(metadata: metadata)
|
|
11
|
+
@i2c_addr = i2c_addr
|
|
12
|
+
@provider = provider
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def readings
|
|
16
|
+
m = @provider.read_measurements(@i2c_addr)
|
|
17
|
+
[
|
|
18
|
+
reading(sensor_type: 'temperature', value: m[:temperature_c], unit: 'celsius'),
|
|
19
|
+
reading(sensor_type: 'humidity', value: m[:humidity], unit: '%')
|
|
20
|
+
]
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dredger
|
|
4
|
+
module IoT
|
|
5
|
+
module Sensors
|
|
6
|
+
# Hardware provider for Sensirion SHT31 temperature/humidity over I2C.
|
|
7
|
+
# Basic single-shot measurement without CRC checks for simplicity.
|
|
8
|
+
# Datasheet formula:
|
|
9
|
+
# - Temperature (°C) = -45 + 175 * (ST / 65535)
|
|
10
|
+
# - Humidity (%RH) = 100 * (SRH / 65535)
|
|
11
|
+
class SHT31Provider
|
|
12
|
+
# I2C commands
|
|
13
|
+
CMD_SINGLE_SHOT_HIGHREP = [0x24, 0x00].freeze # High repeatability, clock stretching disabled
|
|
14
|
+
|
|
15
|
+
def initialize(i2c_bus:)
|
|
16
|
+
@i2c = i2c_bus
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Returns { temperature_c: Float, humidity: Float }
|
|
20
|
+
def read_measurements(addr)
|
|
21
|
+
# Trigger single-shot measurement
|
|
22
|
+
@i2c.write(addr, CMD_SINGLE_SHOT_HIGHREP)
|
|
23
|
+
# Max measurement duration ~15ms (high repeatability). Add margin.
|
|
24
|
+
sleep(0.02)
|
|
25
|
+
|
|
26
|
+
# Read 6 bytes: T_MSB, T_LSB, T_CRC, RH_MSB, RH_LSB, RH_CRC
|
|
27
|
+
data = @i2c.read(addr, 6)
|
|
28
|
+
|
|
29
|
+
st = (data[0] << 8) | data[1]
|
|
30
|
+
srh = (data[3] << 8) | data[4]
|
|
31
|
+
|
|
32
|
+
temp_c = -45.0 + (175.0 * st / 65_535.0)
|
|
33
|
+
humidity = (100.0 * srh / 65_535.0)
|
|
34
|
+
humidity = humidity.clamp(0.0, 100.0)
|
|
35
|
+
|
|
36
|
+
{ temperature_c: temp_c, humidity: humidity }
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dredger
|
|
4
|
+
module IoT
|
|
5
|
+
module Sensors
|
|
6
|
+
# TSL2561 ambient light sensor (I2C)
|
|
7
|
+
# Provider must respond to :read_lux(addr) -> Float (lux)
|
|
8
|
+
class TSL2561 < BaseSensor
|
|
9
|
+
def initialize(i2c_addr:, provider:, metadata: {})
|
|
10
|
+
super(metadata: metadata)
|
|
11
|
+
@i2c_addr = i2c_addr
|
|
12
|
+
@provider = provider
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def readings
|
|
16
|
+
lux = @provider.read_lux(@i2c_addr)
|
|
17
|
+
[reading(sensor_type: 'illuminance', value: lux, unit: 'lux')]
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dredger
|
|
4
|
+
module IoT
|
|
5
|
+
module Sensors
|
|
6
|
+
# Hardware provider for TSL2561 ambient light sensor (I2C)
|
|
7
|
+
# Uses simple configuration: integration 402ms, low gain.
|
|
8
|
+
class TSL2561Provider
|
|
9
|
+
CMD = 0x80
|
|
10
|
+
CONTROL = 0x00
|
|
11
|
+
TIMING = 0x01
|
|
12
|
+
DATA0LOW = 0x0C
|
|
13
|
+
DATA1LOW = 0x0E
|
|
14
|
+
|
|
15
|
+
POWER_ON = 0x03
|
|
16
|
+
POWER_OFF = 0x00
|
|
17
|
+
INTEG_402MS = 0x02 # integration time bits [1:0] = 10
|
|
18
|
+
GAIN_LOW = 0x00 # gain bit [4] = 0
|
|
19
|
+
|
|
20
|
+
def initialize(i2c_bus:)
|
|
21
|
+
@i2c = i2c_bus
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Returns lux as Float
|
|
25
|
+
def read_lux(addr)
|
|
26
|
+
# Power on
|
|
27
|
+
@i2c.write(addr, [POWER_ON], register: CMD | CONTROL)
|
|
28
|
+
# Set timing: 402ms, low gain
|
|
29
|
+
@i2c.write(addr, [GAIN_LOW | INTEG_402MS], register: CMD | TIMING)
|
|
30
|
+
|
|
31
|
+
# Wait for integration
|
|
32
|
+
sleep(0.45)
|
|
33
|
+
|
|
34
|
+
ch0 = read_word(addr, CMD | DATA0LOW)
|
|
35
|
+
ch1 = read_word(addr, CMD | DATA1LOW)
|
|
36
|
+
|
|
37
|
+
compute_lux(ch0, ch1).to_f
|
|
38
|
+
ensure
|
|
39
|
+
# Power off to save energy
|
|
40
|
+
@i2c.write(addr, [POWER_OFF], register: CMD | CONTROL)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
# Read 16-bit little-endian word from LSB register
|
|
46
|
+
def read_word(addr, reg)
|
|
47
|
+
bytes = @i2c.read(addr, 2, register: reg)
|
|
48
|
+
bytes[0] | (bytes[1] << 8)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Lux calculation based on TSL2561 datasheet/Adafruit library
|
|
52
|
+
def compute_lux(ch0, ch1)
|
|
53
|
+
return 0.0 if ch0.zero?
|
|
54
|
+
|
|
55
|
+
ratio = ch1.to_f / ch0
|
|
56
|
+
|
|
57
|
+
if ratio <= 0.5
|
|
58
|
+
(0.0304 * ch0) - (0.062 * ch0 * (ratio**1.4))
|
|
59
|
+
elsif ratio <= 0.61
|
|
60
|
+
(0.0224 * ch0) - (0.031 * ch1)
|
|
61
|
+
elsif ratio <= 0.80
|
|
62
|
+
(0.0128 * ch0) - (0.0153 * ch1)
|
|
63
|
+
elsif ratio <= 1.30
|
|
64
|
+
(0.00146 * ch0) - (0.00112 * ch1)
|
|
65
|
+
else
|
|
66
|
+
0.0
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
data/lib/dredger/iot/sensors.rb
CHANGED
|
@@ -9,3 +9,11 @@ require_relative 'sensors/ds18b20'
|
|
|
9
9
|
require_relative 'sensors/ds18b20_provider'
|
|
10
10
|
require_relative 'sensors/bmp180'
|
|
11
11
|
require_relative 'sensors/mcp9808'
|
|
12
|
+
require_relative 'sensors/sht31'
|
|
13
|
+
require_relative 'sensors/sht31_provider'
|
|
14
|
+
require_relative 'sensors/bh1750'
|
|
15
|
+
require_relative 'sensors/bh1750_provider'
|
|
16
|
+
require_relative 'sensors/tsl2561'
|
|
17
|
+
require_relative 'sensors/tsl2561_provider'
|
|
18
|
+
require_relative 'sensors/ina219'
|
|
19
|
+
require_relative 'sensors/ina219_provider'
|
data/lib/dredger/iot/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: dredger-iot
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- The Mad Botter INC
|
|
8
|
-
bindir:
|
|
8
|
+
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
@@ -30,13 +30,15 @@ description: |
|
|
|
30
30
|
scheduling utilities for periodic polling and exponential backoff.
|
|
31
31
|
email:
|
|
32
32
|
- opensource@themadbotter.com
|
|
33
|
-
executables:
|
|
33
|
+
executables:
|
|
34
|
+
- dredger
|
|
34
35
|
extensions: []
|
|
35
36
|
extra_rdoc_files: []
|
|
36
37
|
files:
|
|
37
38
|
- CHANGELOG.md
|
|
38
39
|
- LICENSE.txt
|
|
39
40
|
- README.md
|
|
41
|
+
- bin/dredger
|
|
40
42
|
- lib/dredger/iot.rb
|
|
41
43
|
- lib/dredger/iot/bus.rb
|
|
42
44
|
- lib/dredger/iot/bus/auto.rb
|
|
@@ -51,6 +53,8 @@ files:
|
|
|
51
53
|
- lib/dredger/iot/scheduler.rb
|
|
52
54
|
- lib/dredger/iot/sensors.rb
|
|
53
55
|
- lib/dredger/iot/sensors/base_sensor.rb
|
|
56
|
+
- lib/dredger/iot/sensors/bh1750.rb
|
|
57
|
+
- lib/dredger/iot/sensors/bh1750_provider.rb
|
|
54
58
|
- lib/dredger/iot/sensors/bme280.rb
|
|
55
59
|
- lib/dredger/iot/sensors/bme280_provider.rb
|
|
56
60
|
- lib/dredger/iot/sensors/bmp180.rb
|
|
@@ -58,7 +62,13 @@ files:
|
|
|
58
62
|
- lib/dredger/iot/sensors/dht22_provider.rb
|
|
59
63
|
- lib/dredger/iot/sensors/ds18b20.rb
|
|
60
64
|
- lib/dredger/iot/sensors/ds18b20_provider.rb
|
|
65
|
+
- lib/dredger/iot/sensors/ina219.rb
|
|
66
|
+
- lib/dredger/iot/sensors/ina219_provider.rb
|
|
61
67
|
- lib/dredger/iot/sensors/mcp9808.rb
|
|
68
|
+
- lib/dredger/iot/sensors/sht31.rb
|
|
69
|
+
- lib/dredger/iot/sensors/sht31_provider.rb
|
|
70
|
+
- lib/dredger/iot/sensors/tsl2561.rb
|
|
71
|
+
- lib/dredger/iot/sensors/tsl2561_provider.rb
|
|
62
72
|
- lib/dredger/iot/version.rb
|
|
63
73
|
homepage: https://github.com/TheMadBotterINC/dredger-iot
|
|
64
74
|
licenses:
|
|
@@ -73,25 +83,27 @@ metadata:
|
|
|
73
83
|
rubygems_mfa_required: 'true'
|
|
74
84
|
keywords: iot, embedded, linux, gpio, i2c, beaglebone, raspberry-pi, hardware, sensors,
|
|
75
85
|
dht22, bme280, ds18b20, bmp180, mcp9808, libgpiod, automation
|
|
76
|
-
post_install_message: "\n═══════════════════════════════════════════════════════════════════\n
|
|
77
|
-
\
|
|
78
|
-
\ \n
|
|
79
|
-
\
|
|
80
|
-
\ ___ ___ | \n
|
|
81
|
-
\ \n
|
|
82
|
-
\
|
|
83
|
-
\ | | \n
|
|
84
|
-
\
|
|
85
|
-
\
|
|
86
|
-
\
|
|
87
|
-
\
|
|
88
|
-
\
|
|
89
|
-
\
|
|
90
|
-
|
|
91
|
-
\
|
|
92
|
-
|
|
93
|
-
\ = Dredger::IoT::Bus::Auto.
|
|
94
|
-
|
|
86
|
+
post_install_message: "\n ═══════════════════════════════════════════════════════════════════\n
|
|
87
|
+
\ \n _______________
|
|
88
|
+
\ \n | DREDGER-IoT | \n
|
|
89
|
+
\ |_______________| \n /|
|
|
90
|
+
\ ___ ___ | \n / | |___| |___||
|
|
91
|
+
\ \n / |______________| \n
|
|
92
|
+
\ ====|========================|==== \n |
|
|
93
|
+
\ | |-----------| | | \n | |____| |_____|
|
|
94
|
+
\ | \n ___|____| |____|___ \n
|
|
95
|
+
\ ~~~~{________|_________________________|________}~~~~~~~ \n ~~ |
|
|
96
|
+
\ \\ // | ~~~ \n | \\___________________//
|
|
97
|
+
\ | \n |_____________________________| \n
|
|
98
|
+
\ ~~~ \\ // ~~~ \n \\_______________//
|
|
99
|
+
\ \n ═══════════════════════════════════════════════════════════════════\nHardware
|
|
100
|
+
Integration for Embedded Linux v0.2.0\n ═══════════════════════════════════════════════════════════════════\n\n
|
|
101
|
+
\ \U0001F389 Thanks for installing!\n\n \U0001F4DA Hardware Setup (kernel modules
|
|
102
|
+
& permissions):\n https://github.com/TheMadBotterINC/dredger-iot#hardware-setup\n\n
|
|
103
|
+
\ \U0001F680 Quick Start:\n require 'dredger/iot'\n gpio = Dredger::IoT::Bus::Auto.gpio\n
|
|
104
|
+
\ i2c = Dredger::IoT::Bus::Auto.i2c\n\n \U0001F4A1 Supported Sensors:\n
|
|
105
|
+
\ DHT22, BME280, DS18B20, BMP180, MCP9808\n\n \U0001F4D6 Full Documentation:\n
|
|
106
|
+
\ https://github.com/TheMadBotterINC/dredger-iot\n\n"
|
|
95
107
|
rdoc_options: []
|
|
96
108
|
require_paths:
|
|
97
109
|
- lib
|