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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 77401daa8434854747d298a3d6e92faf903ef6157d29c11c449e048e664fbdce
4
- data.tar.gz: fe634bdf185a0787963ca1f45d60db33accf330f2ac72753f15338732aae7f57
3
+ metadata.gz: 84cb846712d4d17f969147389859dfae1bdc8a67fb304e491e857891726d7234
4
+ data.tar.gz: 5c905f7f6e651692f6eff7145e9b9399357df33e340d4694eceab260f31b1b00
5
5
  SHA512:
6
- metadata.gz: a517bb67ee51d067d8581552928998e111989f92914f147d36d26f4a6d3f5b555286fc0f831039eea19242ce36423f8c2951bbba08c12b72daa46be10ba25eab
7
- data.tar.gz: 50fde0ea268c0259486098223a530659646c6b9a0586d403a777ba0cf1053e30c7794f11bfa61939fd3d8f964bd7935e81ecded9d211e50187dd50ce7f4630d7
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.1.1...HEAD
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
@@ -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'
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Dredger
4
4
  module IoT
5
- VERSION = '0.1.1'
5
+ VERSION = '0.2.0'
6
6
  end
7
7
  end
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.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - The Mad Botter INC
8
- bindir: exe
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
- \ \n _______________
78
- \ \n | DREDGER-IoT | \n
79
- \ |_______________| \n /|
80
- \ ___ ___ | \n / | |___| |___||
81
- \ \n / |______________| \n
82
- \ ====|========================|==== \n | | |-----------|
83
- \ | | \n | |____| |_____| | \n
84
- \ ___|____| |____|___ \n~~~~{________|_________________________|________}~~~~~~~
85
- \ \n ~~ | \\ // | ~~~ \n |
86
- \ \\___________________// | \n |_____________________________|
87
- \ \n ~~~ \\ // ~~~ \n
88
- \ \\_______________// \n═══════════════════════════════════════════════════════════════════\n
89
- \ Hardware Integration for Embedded Linux v0.1.1\n═══════════════════════════════════════════════════════════════════\n\n\U0001F389
90
- Thanks for installing!\n\n\U0001F4DA Hardware Setup (kernel modules & permissions):\n
91
- \ https://github.com/TheMadBotterINC/dredger-iot#hardware-setup\n\n\U0001F680 Quick
92
- Start:\n require 'dredger/iot'\n gpio = Dredger::IoT::Bus::Auto.gpio\n i2c
93
- \ = Dredger::IoT::Bus::Auto.i2c\n\n\U0001F4A1 Supported Sensors:\n DHT22, BME280,
94
- DS18B20, BMP180, MCP9808\n\n\U0001F4D6 Full Documentation:\n https://github.com/TheMadBotterINC/dredger-iot\n\n"
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