dredger-iot 0.1.2 → 0.2.1

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: 3249f8a115ef3351b820037510c1ef0259ae227eb881d191353ddd20378c7520
4
- data.tar.gz: 1bbc867919f8ba1faa5640d5b1f39370646b4a41d133dab98da0966fe44c2bdc
3
+ metadata.gz: 6710d17818b007805d0634e65fde9048c5fb191b9a11d4c5ac3d09aeccf9483e
4
+ data.tar.gz: cb7d2e68d335ee490f902b9eaed4b30b6499b8a688336f47678619e862b48cf6
5
5
  SHA512:
6
- metadata.gz: 10d9397170fa1c21e34a969595c21a48a4605cbceeb6b79c6833930a65bddaac5798d4537c881d0bda30decbb76a5029d35e5924180978c265b61e5a7f739e84
7
- data.tar.gz: 6d9f92320c43eb23c92aee74cc60cc24bcbb5b44cf5409487047aa2e34b8380011d3a8a508612216edf2e9ff00c7eafda562e4e683c46fd7bb988898f22d0552
6
+ metadata.gz: c14e577283263ea3a3674280cf416bde53ce0c172776fe85c2a9bde00835d7b42fd5badf5bcff6e8dc16b041abae9d810ad7fe510f2afbb86a1c7ffcf0b9d35b
7
+ data.tar.gz: bb7fc628012d3f9e26d83a792e24fdf20493de7cf984dee32a17658221aa53a7e5b208c49bbd528bb431320354edec09b3dd4506b52fff1b05ae6b4965de1daf
data/CHANGELOG.md CHANGED
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.1] - 2025-10-05
11
+
12
+ ### Added
13
+ - Examples: Raspberry Pi GPIO blink script (GPIO17)
14
+ - Docs: Raspberry Pi OS instructions to enable I2C and 1-Wire
15
+
16
+ ## [0.2.0] - 2025-10-05
17
+
18
+ ### Added
19
+ - New sensors: SHT31 (I2C temp/humidity), BH1750 (I2C lux), TSL2561 (I2C lux), INA219 (I2C bus voltage/current)
20
+ - CLI: --shunt option for INA219 to specify shunt resistance (default 0.1 Ω)
21
+ - Examples: example scripts for SHT31, BH1750, TSL2561, INA219
22
+
23
+ ### Changed
24
+ - README: document new sensors and CLI usage
25
+ - Coverage: exclude all provider implementations from coverage (hardware-dependent)
26
+
10
27
  ## [0.1.2] - 2025-10-04
11
28
 
12
29
  ### Added
@@ -53,7 +70,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
53
70
  - RuboCop configuration and compliance
54
71
  - Comprehensive documentation and usage examples
55
72
 
56
- [Unreleased]: https://github.com/TheMadBotterINC/dredger-iot/compare/v0.1.2...HEAD
73
+ [Unreleased]: https://github.com/TheMadBotterINC/dredger-iot/compare/v0.2.1...HEAD
74
+ [0.2.1]: https://github.com/TheMadBotterINC/dredger-iot/compare/v0.2.0...v0.2.1
75
+ [0.2.0]: https://github.com/TheMadBotterINC/dredger-iot/compare/v0.1.2...v0.2.0
57
76
  [0.1.2]: https://github.com/TheMadBotterINC/dredger-iot/compare/v0.1.1...v0.1.2
58
77
  [0.1.1]: https://github.com/TheMadBotterINC/dredger-iot/compare/v0.1.0...v0.1.1
59
78
  [0.1.0]: https://github.com/TheMadBotterINC/dredger-iot/releases/tag/v0.1.0
data/README.md CHANGED
@@ -70,6 +70,26 @@ Environment overrides:
70
70
  - `DREDGER_IOT_GPIO_BACKEND`: `simulation` | `libgpiod`
71
71
  - `DREDGER_IOT_I2C_BACKEND`: `simulation` | `linux`
72
72
 
73
+ ## Raspberry Pi GPIO label mapping
74
+
75
+ 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:
76
+ - GPIO17 or BCM17 (Broadcom numbering)
77
+ - PIN11 or BOARD11 (header pin numbers)
78
+
79
+ 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.
80
+
81
+ Example:
82
+
83
+ ```ruby path=null start=null
84
+ require 'dredger/iot'
85
+
86
+ gpio = Dredger::IoT::Bus::Auto.gpio # picks libgpiod on RPi, otherwise simulation
87
+
88
+ # Use Raspberry Pi labels
89
+ gpio.set_direction('GPIO17', :out)
90
+ gpio.write('GPIO17', 1)
91
+ ```
92
+
73
93
  ## Beaglebone P9_XX label mapping
74
94
 
75
95
  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.
@@ -98,9 +118,58 @@ Dredger-IoT includes drivers for popular embedded sensors:
98
118
  - **DS18B20** - 1-Wire digital temperature sensor
99
119
  - **BMP180** - I2C barometric pressure/temperature sensor
100
120
  - **MCP9808** - I2C high-accuracy temperature sensor
121
+ - **SHT31** - I2C temperature/humidity sensor
122
+ - **BH1750** - I2C ambient light sensor (lux)
123
+ - **TSL2561** - I2C ambient light sensor (lux)
124
+ - **INA219** - I2C bus voltage/current monitor
101
125
 
102
126
  Sensors use a provider pattern for testability and hardware abstraction.
103
127
 
128
+ ## CLI Usage
129
+
130
+ Commands:
131
+ - list-sensors
132
+ - List available sensor types supported by dredger-iot.
133
+ - read SENSOR [ARGS]
134
+ - Read once or continuously from a sensor.
135
+ - Examples:
136
+ - dredger read bme280 0x76
137
+ - dredger read dht22 P9_12
138
+ - dredger read ina219 0x40 --shunt 0.1
139
+ - test-gpio PIN
140
+ - Simple blink test to verify GPIO output works.
141
+ - test-i2c
142
+ - Scans a few common I2C addresses (hardware backend only).
143
+ - info
144
+ - Prints version, detected backends, and environment variables.
145
+
146
+ Options:
147
+ - --backend BACKEND
148
+ - auto (default), simulation, hardware
149
+ - simulation forces both GPIO and I2C simulation backends
150
+ - hardware forces libgpiod (GPIO) and linux (I2C)
151
+ - --format FORMAT
152
+ - text (default) or json
153
+ - --interval SECONDS
154
+ - Poll continuously at the specified interval (e.g., 2.0)
155
+ - --shunt OHMS
156
+ - INA219-only: specify the shunt resistance in ohms (default 0.1)
157
+
158
+ Examples:
159
+ ```bash
160
+ # Read a BME280 once (auto-detected backends)
161
+ dredger read bme280 0x76
162
+
163
+ # Read a DHT22 every 2 seconds, JSON output
164
+ dredger read dht22 P9_12 --interval 2 --format json
165
+
166
+ # Force simulation backends for local testing
167
+ dredger --backend simulation read bh1750 0x23
168
+
169
+ # INA219 with custom shunt
170
+ dredger read ina219 0x40 --shunt 0.05
171
+ ```
172
+
104
173
  ## Usage Examples
105
174
 
106
175
  ### DHT22 Temperature/Humidity Sensor
@@ -304,6 +373,29 @@ ls /sys/bus/w1/devices/
304
373
  # Should show devices like: 28-00000xxxxxx
305
374
  ```
306
375
 
376
+ #### Raspberry Pi OS: Enable I2C and 1-Wire
377
+
378
+ On Raspberry Pi OS you can enable I2C and 1-Wire via raspi-config or by editing /boot/config.txt.
379
+
380
+ Option A: raspi-config (recommended)
381
+ ```bash path=null start=null
382
+ sudo raspi-config
383
+ # Interface Options → I2C → Enable
384
+ # Interface Options → 1-Wire → Enable
385
+ sudo reboot
386
+ ```
387
+
388
+ Option B: edit /boot/config.txt
389
+ ```bash path=null start=null
390
+ # Enable I2C
391
+ sudo sed -i 's/^#\?dtparam=i2c_arm=.*/dtparam=i2c_arm=on/' /boot/config.txt
392
+ # Enable 1-Wire on default BCM4 (PIN7)
393
+ echo 'dtoverlay=w1-gpio,gpiopin=4' | sudo tee -a /boot/config.txt
394
+ sudo reboot
395
+ ```
396
+
397
+ Note: Dredger-IoT accepts Raspberry Pi labels like GPIO17, BCM17, and PIN11.
398
+
307
399
  #### Beaglebone Black Device Tree
308
400
 
309
401
  For Beaglebone Black, you may need to enable device tree overlays:
@@ -483,6 +575,26 @@ reading.timestamp # Time object when reading was taken
483
575
  - **`MCP9808`** - High-accuracy temperature (I2C)
484
576
  - Parameters: `i2c_addr` (default: `0x18`), `provider`
485
577
  - Returns: temperature (celsius)
578
+
579
+ - **`SHT31`** - Temperature/humidity (I2C)
580
+ - Parameters: `i2c_addr` (default: `0x44`), `provider`
581
+ - Returns: temperature (celsius), humidity (%)
582
+
583
+ - **`BH1750`** - Ambient light (I2C)
584
+ - Parameters: `i2c_addr` (default: `0x23`), `provider`
585
+ - Returns: illuminance (lux)
586
+
587
+ - **`TSL2561`** - Ambient light (I2C)
588
+ - Parameters: `i2c_addr` (default: `0x39`), `provider`
589
+ - Returns: illuminance (lux)
590
+
591
+ - **`INA219`** - Bus voltage/current monitor (I2C)
592
+ - Parameters: `i2c_addr` (default: `0x40`), `provider`
593
+ - Returns: bus_voltage (V), current (mA)
594
+ - CLI example:
595
+ ```bash path=null start=null
596
+ dredger read ina219 0x40 --shunt 0.1
597
+ ```
486
598
 
487
599
  ### Scheduling
488
600
 
data/bin/dredger CHANGED
@@ -32,6 +32,8 @@ class DredgerCLI
32
32
  test_i2c
33
33
  when 'info'
34
34
  show_info
35
+ when 'doctor'
36
+ doctor
35
37
  else
36
38
  puts parser
37
39
  exit 1
@@ -53,6 +55,7 @@ class DredgerCLI
53
55
  opts.separator ' read SENSOR [OPTIONS] Read from a sensor'
54
56
  opts.separator ' test-gpio PIN Test GPIO pin'
55
57
  opts.separator ' test-i2c Scan I2C bus'
58
+ opts.separator ' doctor Check system prerequisites'
56
59
  opts.separator ' info Show system information'
57
60
  opts.separator ''
58
61
  opts.separator 'Options:'
@@ -69,6 +72,10 @@ class DredgerCLI
69
72
  @options[:interval] = i
70
73
  end
71
74
 
75
+ opts.on('--shunt OHMS', Float, 'INA219 shunt resistance in ohms (default: 0.1)') do |ohms|
76
+ @options[:ina219_shunt] = ohms
77
+ end
78
+
72
79
  opts.on('-h', '--help', 'Show this help') do
73
80
  puts opts
74
81
  exit
@@ -87,7 +94,11 @@ class DredgerCLI
87
94
  'bme280' => 'BME280 - Temperature/Humidity/Pressure (I2C)',
88
95
  'ds18b20' => 'DS18B20 - Waterproof Temperature (1-Wire)',
89
96
  'bmp180' => 'BMP180 - Barometric Pressure/Temperature (I2C)',
90
- 'mcp9808' => 'MCP9808 - High-Accuracy Temperature (I2C)'
97
+ 'mcp9808' => 'MCP9808 - High-Accuracy Temperature (I2C)',
98
+ 'sht31' => 'SHT31 - Temperature/Humidity (I2C)',
99
+ 'bh1750' => 'BH1750 - Ambient Light (I2C)',
100
+ 'tsl2561' => 'TSL2561 - Ambient Light (I2C)',
101
+ 'ina219' => 'INA219 - Bus Voltage/Current (I2C)'
91
102
  }
92
103
 
93
104
  puts 'Available Sensors:'
@@ -136,6 +147,27 @@ class DredgerCLI
136
147
  exit 1
137
148
  end
138
149
  Dredger::IoT::Sensors::DS18B20.new(device_id: device_id, provider: provider)
150
+ when 'sht31'
151
+ addr = (args.shift || '0x44').to_i(16)
152
+ i2c = Dredger::IoT::Bus::Auto.i2c
153
+ provider = Dredger::IoT::Sensors::SHT31Provider.new(i2c_bus: i2c)
154
+ Dredger::IoT::Sensors::SHT31.new(i2c_addr: addr, provider: provider)
155
+ when 'bh1750'
156
+ addr = (args.shift || '0x23').to_i(16)
157
+ i2c = Dredger::IoT::Bus::Auto.i2c
158
+ provider = Dredger::IoT::Sensors::BH1750Provider.new(i2c_bus: i2c)
159
+ Dredger::IoT::Sensors::BH1750.new(i2c_addr: addr, provider: provider)
160
+ when 'tsl2561'
161
+ addr = (args.shift || '0x39').to_i(16)
162
+ i2c = Dredger::IoT::Bus::Auto.i2c
163
+ provider = Dredger::IoT::Sensors::TSL2561Provider.new(i2c_bus: i2c)
164
+ Dredger::IoT::Sensors::TSL2561.new(i2c_addr: addr, provider: provider)
165
+ when 'ina219'
166
+ addr = (args.shift || '0x40').to_i(16)
167
+ i2c = Dredger::IoT::Bus::Auto.i2c
168
+ shunt = @options[:ina219_shunt] || 0.1
169
+ provider = Dredger::IoT::Sensors::INA219Provider.new(i2c_bus: i2c, shunt_resistance_ohms: shunt)
170
+ Dredger::IoT::Sensors::INA219.new(i2c_addr: addr, provider: provider)
139
171
  else
140
172
  puts "Error: Unknown sensor type '#{type}'"
141
173
  exit 1
@@ -241,6 +273,51 @@ class DredgerCLI
241
273
  puts " DREDGER_IOT_I2C_BACKEND: #{ENV['DREDGER_IOT_I2C_BACKEND'] || '(not set)'}"
242
274
  end
243
275
 
276
+ def doctor
277
+ puts 'Doctor: checking system prerequisites...'
278
+ puts
279
+ check_device_node('/dev/gpiochip0', 'GPIO (libgpiod) device')
280
+ check_device_node('/dev/i2c-1', 'I2C device (bus 1)')
281
+ check_path('/sys/bus/w1/devices', '1-Wire bus (DS18B20) directory')
282
+
283
+ puts
284
+ puts 'Kernel modules:'
285
+ mods = read_proc_modules
286
+ puts " i2c-dev: #{mods.include?('i2c_dev') ? 'loaded' : 'missing'}"
287
+ puts " w1-gpio: #{mods.include?('w1_gpio') ? 'loaded' : 'missing'}"
288
+ puts " w1-therm: #{mods.include?('w1_therm') ? 'loaded' : 'missing'}"
289
+
290
+ puts
291
+ puts 'Recommendations:'
292
+ puts " sudo apt-get install gpiod i2c-tools"
293
+ puts " sudo usermod -a -G gpio $USER # for GPIO access"
294
+ puts " sudo usermod -a -G i2c $USER # for I2C access"
295
+ puts " sudo modprobe i2c-dev"
296
+ puts " sudo modprobe w1-gpio; sudo modprobe w1-therm"
297
+ end
298
+
299
+ def check_device_node(path, desc)
300
+ if File.exist?(path)
301
+ puts " ✔ #{desc}: present (#{path})"
302
+ else
303
+ puts " ✖ #{desc}: missing (#{path})"
304
+ end
305
+ end
306
+
307
+ def check_path(path, desc)
308
+ if Dir.exist?(path)
309
+ puts " ✔ #{desc}: present (#{path})"
310
+ else
311
+ puts " ✖ #{desc}: missing (#{path})"
312
+ end
313
+ end
314
+
315
+ def read_proc_modules
316
+ File.read('/proc/modules')
317
+ rescue StandardError
318
+ ''
319
+ end
320
+
244
321
  def setup_backends
245
322
  case @options[:backend]
246
323
  when 'simulation'
@@ -5,9 +5,11 @@ module Dredger
5
5
  module Bus
6
6
  # Adapts label strings (e.g. 'P9_12') to PinRef with chip:line for libgpiod backends
7
7
  class GPIOLabelAdapter
8
- def initialize(backend:, mapper: Dredger::IoT::Pins::Beaglebone)
8
+ # mapper can be a single mapper module/class or an Array of them.
9
+ # Defaults to both Beaglebone and RaspberryPi mappers.
10
+ def initialize(backend:, mapper: [Dredger::IoT::Pins::Beaglebone, Dredger::IoT::Pins::RaspberryPi])
9
11
  @backend = backend
10
- @mapper = mapper
12
+ @mappers = Array(mapper)
11
13
  end
12
14
 
13
15
  def set_direction(pin, direction)
@@ -24,16 +26,24 @@ module Dredger
24
26
 
25
27
  private
26
28
 
29
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
27
30
  def resolve(pin)
28
31
  # Already a PinRef with line
29
32
  return pin if pin.respond_to?(:line) && !pin.line.nil?
33
+
30
34
  # Numeric line
31
35
  return Integer(pin) if pin.is_a?(Integer) || pin.to_s =~ /^\d+$/
32
- # Beaglebone-style label
33
- return @mapper.resolve_label_to_pinref(pin) if @mapper.respond_to?(:resolve_label_to_pinref)
36
+
37
+ # Try all mappers in order
38
+ @mappers.each do |m|
39
+ if m.respond_to?(:resolve_label_to_pinref) && m.respond_to?(:valid_label?) && m.valid_label?(pin)
40
+ return m.resolve_label_to_pinref(pin)
41
+ end
42
+ end
34
43
 
35
44
  raise ArgumentError, 'Unsupported pin format'
36
45
  end
46
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
37
47
  end
38
48
  end
39
49
  end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dredger
4
+ module IoT
5
+ module Pins
6
+ # Raspberry Pi header/BCM label mapping.
7
+ # Supports labels like:
8
+ # - GPIO17, BCM17
9
+ # - PIN11 (a.k.a. BOARD11)
10
+ class RaspberryPi
11
+ PinRef = Struct.new(:label, :chip, :line, keyword_init: true) do
12
+ def to_s
13
+ chip && line ? "#{label}(chip#{chip}:#{line})" : label.to_s
14
+ end
15
+ end
16
+
17
+ # Subset of BOARD pin to BCM mapping for common usable GPIOs on 40-pin header
18
+ BOARD_TO_BCM = {
19
+ 3 => 2, 5 => 3, 7 => 4, 8 => 14, 10 => 15, 11 => 17, 12 => 18, 13 => 27,
20
+ 15 => 22, 16 => 23, 18 => 24, 19 => 10, 21 => 9, 22 => 25, 23 => 11,
21
+ 24 => 8, 26 => 7, 29 => 5, 31 => 6, 32 => 12, 33 => 13, 35 => 19,
22
+ 36 => 16, 37 => 26, 38 => 20, 40 => 21
23
+ }.freeze
24
+
25
+ # Accept variants like GPIO17, BCM17, PIN11, BOARD11
26
+ def self.valid_label?(label)
27
+ s = label.to_s.upcase
28
+ return true if s.match?(/^(GPIO|BCM)\d+$/)
29
+ return true if s.match?(/^(PIN|BOARD)\d+$/)
30
+
31
+ false
32
+ end
33
+
34
+ def self.resolve_label_to_pinref(label)
35
+ s = label.to_s.upcase
36
+ if s =~ /^(GPIO|BCM)(\d+)$/
37
+ bcm = Regexp.last_match(2).to_i
38
+ return PinRef.new(label: label.to_s, chip: 0, line: bcm)
39
+ end
40
+
41
+ if s =~ /^(PIN|BOARD)(\d+)$/
42
+ board = Regexp.last_match(2).to_i
43
+ bcm = BOARD_TO_BCM[board]
44
+ raise ArgumentError, "Unknown/unsupported board pin: #{label}" if bcm.nil?
45
+
46
+ return PinRef.new(label: label.to_s, chip: 0, line: bcm)
47
+ end
48
+
49
+ raise ArgumentError, "Unknown Raspberry Pi pin label: #{label}"
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -1,3 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'pins/beaglebone'
4
+ require_relative 'pins/raspberry_pi'
@@ -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.2'
5
+ VERSION = '0.2.1'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dredger-iot
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - The Mad Botter INC
@@ -49,10 +49,13 @@ files:
49
49
  - lib/dredger/iot/bus/i2c_linux.rb
50
50
  - lib/dredger/iot/pins.rb
51
51
  - lib/dredger/iot/pins/beaglebone.rb
52
+ - lib/dredger/iot/pins/raspberry_pi.rb
52
53
  - lib/dredger/iot/reading.rb
53
54
  - lib/dredger/iot/scheduler.rb
54
55
  - lib/dredger/iot/sensors.rb
55
56
  - lib/dredger/iot/sensors/base_sensor.rb
57
+ - lib/dredger/iot/sensors/bh1750.rb
58
+ - lib/dredger/iot/sensors/bh1750_provider.rb
56
59
  - lib/dredger/iot/sensors/bme280.rb
57
60
  - lib/dredger/iot/sensors/bme280_provider.rb
58
61
  - lib/dredger/iot/sensors/bmp180.rb
@@ -60,7 +63,13 @@ files:
60
63
  - lib/dredger/iot/sensors/dht22_provider.rb
61
64
  - lib/dredger/iot/sensors/ds18b20.rb
62
65
  - lib/dredger/iot/sensors/ds18b20_provider.rb
66
+ - lib/dredger/iot/sensors/ina219.rb
67
+ - lib/dredger/iot/sensors/ina219_provider.rb
63
68
  - lib/dredger/iot/sensors/mcp9808.rb
69
+ - lib/dredger/iot/sensors/sht31.rb
70
+ - lib/dredger/iot/sensors/sht31_provider.rb
71
+ - lib/dredger/iot/sensors/tsl2561.rb
72
+ - lib/dredger/iot/sensors/tsl2561_provider.rb
64
73
  - lib/dredger/iot/version.rb
65
74
  homepage: https://github.com/TheMadBotterINC/dredger-iot
66
75
  licenses:
@@ -75,25 +84,27 @@ metadata:
75
84
  rubygems_mfa_required: 'true'
76
85
  keywords: iot, embedded, linux, gpio, i2c, beaglebone, raspberry-pi, hardware, sensors,
77
86
  dht22, bme280, ds18b20, bmp180, mcp9808, libgpiod, automation
78
- post_install_message: "\n═══════════════════════════════════════════════════════════════════\n
79
- \ \n _______________
80
- \ \n | DREDGER-IoT | \n
81
- \ |_______________| \n /|
82
- \ ___ ___ | \n / | |___| |___||
83
- \ \n / |______________| \n
84
- \ ====|========================|==== \n | | |-----------|
85
- \ | | \n | |____| |_____| | \n
86
- \ ___|____| |____|___ \n~~~~{________|_________________________|________}~~~~~~~
87
- \ \n ~~ | \\ // | ~~~ \n |
88
- \ \\___________________// | \n |_____________________________|
89
- \ \n ~~~ \\ // ~~~ \n
90
- \ \\_______________// \n═══════════════════════════════════════════════════════════════════\n
91
- \ Hardware Integration for Embedded Linux v0.1.2\n═══════════════════════════════════════════════════════════════════\n\n\U0001F389
92
- Thanks for installing!\n\n\U0001F4DA Hardware Setup (kernel modules & permissions):\n
93
- \ https://github.com/TheMadBotterINC/dredger-iot#hardware-setup\n\n\U0001F680 Quick
94
- Start:\n require 'dredger/iot'\n gpio = Dredger::IoT::Bus::Auto.gpio\n i2c
95
- \ = Dredger::IoT::Bus::Auto.i2c\n\n\U0001F4A1 Supported Sensors:\n DHT22, BME280,
96
- DS18B20, BMP180, MCP9808\n\n\U0001F4D6 Full Documentation:\n https://github.com/TheMadBotterINC/dredger-iot\n\n"
87
+ post_install_message: "\n ═══════════════════════════════════════════════════════════════════\n
88
+ \ \n _______________
89
+ \ \n | DREDGER-IoT | \n
90
+ \ |_______________| \n /|
91
+ \ ___ ___ | \n / | |___| |___||
92
+ \ \n / |______________| \n
93
+ \ ====|========================|==== \n |
94
+ \ | |-----------| | | \n | |____| |_____|
95
+ \ | \n ___|____| |____|___ \n
96
+ \ ~~~~{________|_________________________|________}~~~~~~~ \n ~~ |
97
+ \ \\ // | ~~~ \n | \\___________________//
98
+ \ | \n |_____________________________| \n
99
+ \ ~~~ \\ // ~~~ \n \\_______________//
100
+ \ \n ═══════════════════════════════════════════════════════════════════\nHardware
101
+ Integration for Embedded Linux v0.2.1\n ═══════════════════════════════════════════════════════════════════\n\n
102
+ \ \U0001F389 Thanks for installing!\n\n \U0001F4DA Hardware Setup (kernel modules
103
+ & permissions):\n https://github.com/TheMadBotterINC/dredger-iot#hardware-setup\n\n
104
+ \ \U0001F680 Quick Start:\n require 'dredger/iot'\n gpio = Dredger::IoT::Bus::Auto.gpio\n
105
+ \ i2c = Dredger::IoT::Bus::Auto.i2c\n\n \U0001F4A1 Supported Sensors:\n
106
+ \ DHT22, BME280, DS18B20, BMP180, MCP9808\n\n \U0001F4D6 Full Documentation:\n
107
+ \ https://github.com/TheMadBotterINC/dredger-iot\n\n"
97
108
  rdoc_options: []
98
109
  require_paths:
99
110
  - lib