dredger-iot 0.2.0 → 0.3.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: 84cb846712d4d17f969147389859dfae1bdc8a67fb304e491e857891726d7234
4
- data.tar.gz: 5c905f7f6e651692f6eff7145e9b9399357df33e340d4694eceab260f31b1b00
3
+ metadata.gz: 701ad99dd1f271df310ba3dffbb3295084b54e64689f4d3ed77f9b6ab9e31df5
4
+ data.tar.gz: e9b5ba95c4d2af83b529e6f66af5c81fb83c43f3f679e89e894209661651f93e
5
5
  SHA512:
6
- metadata.gz: a0cfe26a32e99b21b3a6ccbddede15162bf85f7a85aac6e32715f34cc8a300218515e358bbd271cd5dd648d9ca6df892eb4fda2cd3bf08761a128c25887142ba
7
- data.tar.gz: 179a10159ea14047fa146ed5a03223c2f362591357c39c754fef36718d75c2c35fff7bb0e7facafc9c094e48d0015e5eef0a367ca10cb4f93674e0369f2fa6f8
6
+ metadata.gz: 5362ae41f8f92d403809728857c139b00e3d8141e8f83725a3cc302eb7e7ace2c32b8bd7306fba5e83c2ee197e42c633b7beaeb9a84b9b77f1d95299e80aad1d
7
+ data.tar.gz: 220b5834cd302cef375c3922211c055788855d4b516b7260aa952d13fc312a3f9c028248863906e93a38badbd32839d2083783bb1431ba5fd1ada0b4df92bca5
data/CHANGELOG.md CHANGED
@@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+
11
+ ## [0.3.0] - 2025-10-06
12
+
13
+ ### Added
14
+ - New sensors for industrial IoT applications:
15
+ - **ADXL345** - I2C 3-axis accelerometer for vibration monitoring (±2g, ±4g, ±8g, ±16g ranges)
16
+ - **SCD30** - I2C NDIR CO2 sensor with integrated temperature and humidity (400-10,000 ppm range)
17
+ - **YF-S201** - GPIO hall effect flow meter for liquid flow measurement (1-30 L/min range)
18
+ - **NEO-6M** - UART/Serial GPS module with NMEA 0183 parsing for location tracking
19
+ - README: comprehensive usage examples for all new sensors
20
+ - README: organized sensor list by category (Environmental, Light & Motion, Industrial)
21
+
22
+ ## [0.2.1] - 2025-10-05
23
+
24
+ ### Added
25
+ - Examples: Raspberry Pi GPIO blink script (GPIO17)
26
+ - Docs: Raspberry Pi OS instructions to enable I2C and 1-Wire
27
+
10
28
  ## [0.2.0] - 2025-10-05
11
29
 
12
30
  ### Added
@@ -64,7 +82,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
64
82
  - RuboCop configuration and compliance
65
83
  - Comprehensive documentation and usage examples
66
84
 
67
- [Unreleased]: https://github.com/TheMadBotterINC/dredger-iot/compare/v0.2.0...HEAD
85
+ [Unreleased]: https://github.com/TheMadBotterINC/dredger-iot/compare/v0.3.0...HEAD
86
+ [0.3.0]: https://github.com/TheMadBotterINC/dredger-iot/compare/v0.2.1...v0.3.0
87
+ [0.2.1]: https://github.com/TheMadBotterINC/dredger-iot/compare/v0.2.0...v0.2.1
68
88
  [0.2.0]: https://github.com/TheMadBotterINC/dredger-iot/compare/v0.1.2...v0.2.0
69
89
  [0.1.2]: https://github.com/TheMadBotterINC/dredger-iot/compare/v0.1.1...v0.1.2
70
90
  [0.1.1]: https://github.com/TheMadBotterINC/dredger-iot/compare/v0.1.0...v0.1.1
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.
@@ -93,15 +113,24 @@ If you run on a development host (no /dev/gpiochip0), Auto will default to the s
93
113
 
94
114
  Dredger-IoT includes drivers for popular embedded sensors:
95
115
 
116
+ **Environmental Sensors:**
96
117
  - **DHT22** - GPIO humidity/temperature sensor
97
118
  - **BME280** - I2C temperature/humidity/pressure sensor
98
- - **DS18B20** - 1-Wire digital temperature sensor
99
119
  - **BMP180** - I2C barometric pressure/temperature sensor
100
120
  - **MCP9808** - I2C high-accuracy temperature sensor
101
121
  - **SHT31** - I2C temperature/humidity sensor
122
+ - **DS18B20** - 1-Wire digital temperature sensor
123
+ - **SCD30** - I2C NDIR CO2/temperature/humidity sensor (NEW)
124
+
125
+ **Light & Motion Sensors:**
102
126
  - **BH1750** - I2C ambient light sensor (lux)
103
127
  - **TSL2561** - I2C ambient light sensor (lux)
128
+ - **ADXL345** - I2C 3-axis accelerometer (vibration monitoring) (NEW)
129
+
130
+ **Industrial Sensors:**
104
131
  - **INA219** - I2C bus voltage/current monitor
132
+ - **YF-S201** - GPIO hall effect flow meter (liquid flow) (NEW)
133
+ - **NEO-6M** - UART/Serial GPS module (location tracking) (NEW)
105
134
 
106
135
  Sensors use a provider pattern for testability and hardware abstraction.
107
136
 
@@ -225,6 +254,101 @@ temp = sensor.readings.first
225
254
  puts "#{temp.value}°C"
226
255
  ```
227
256
 
257
+ ### ADXL345 Accelerometer (Vibration Monitoring)
258
+
259
+ ```ruby path=null start=null
260
+ require 'dredger/iot'
261
+
262
+ # Set up I2C bus and ADXL345 provider
263
+ i2c = Dredger::IoT::Bus::Auto.i2c
264
+ provider = Dredger::IoT::Sensors::ADXL345Provider.new(i2c_bus: i2c, range: 2) # ±2g
265
+
266
+ # Create sensor instance (default I2C address 0x53)
267
+ sensor = Dredger::IoT::Sensors::ADXL345.new(
268
+ i2c_addr: 0x53,
269
+ provider: provider,
270
+ metadata: { location: 'motor_mount' }
271
+ )
272
+
273
+ readings = sensor.readings
274
+ readings.each { |r| puts "#{r.sensor_type}: #{r.value} #{r.unit}" }
275
+ # => acceleration_x: 0.024 g
276
+ # => acceleration_y: -0.012 g
277
+ # => acceleration_z: 0.980 g
278
+ ```
279
+
280
+ ### SCD30 CO2 Sensor
281
+
282
+ ```ruby path=null start=null
283
+ require 'dredger/iot'
284
+
285
+ # Set up I2C bus and SCD30 provider
286
+ i2c = Dredger::IoT::Bus::Auto.i2c
287
+ provider = Dredger::IoT::Sensors::SCD30Provider.new(i2c_bus: i2c, interval: 2)
288
+
289
+ # Create sensor instance (default I2C address 0x61)
290
+ sensor = Dredger::IoT::Sensors::SCD30.new(
291
+ i2c_addr: 0x61,
292
+ provider: provider,
293
+ metadata: { location: 'greenhouse' }
294
+ )
295
+
296
+ readings = sensor.readings
297
+ readings.each { |r| puts "#{r.sensor_type}: #{r.value} #{r.unit}" }
298
+ # => co2: 412.5 ppm
299
+ # => temperature: 23.45 celsius
300
+ # => humidity: 45.2 %
301
+ ```
302
+
303
+ ### YF-S201 Flow Meter
304
+
305
+ ```ruby path=null start=null
306
+ require 'dredger/iot'
307
+
308
+ # Set up GPIO bus and flow meter provider
309
+ gpio = Dredger::IoT::Bus::Auto.gpio
310
+ provider = Dredger::IoT::Sensors::YFS201Provider.new(gpio_bus: gpio, calibration_factor: 7.5)
311
+
312
+ # Create sensor instance
313
+ sensor = Dredger::IoT::Sensors::YFS201.new(
314
+ pin_label: 'P9_12',
315
+ provider: provider,
316
+ sample_duration: 1.0, # Count pulses for 1 second
317
+ metadata: { location: 'main_line' }
318
+ )
319
+
320
+ readings = sensor.readings
321
+ flow = readings.first
322
+ puts "Flow: #{flow.value} #{flow.unit}"
323
+ puts "Pulses: #{flow.metadata[:pulses]}"
324
+ # => Flow: 5.25 L/min
325
+ # => Pulses: 656
326
+ ```
327
+
328
+ ### NEO-6M GPS Module
329
+
330
+ ```ruby path=null start=null
331
+ require 'dredger/iot'
332
+
333
+ # Set up GPS provider
334
+ provider = Dredger::IoT::Sensors::NEO6MProvider.new(baud_rate: 9600, timeout: 5)
335
+
336
+ # Create sensor instance
337
+ sensor = Dredger::IoT::Sensors::NEO6M.new(
338
+ device: '/dev/ttyAMA0', # Serial device
339
+ provider: provider,
340
+ metadata: { vehicle: 'truck_1' }
341
+ )
342
+
343
+ readings = sensor.readings
344
+ readings.each { |r| puts "#{r.sensor_type}: #{r.value} #{r.unit}" }
345
+ # => latitude: 37.774929 degrees
346
+ # => longitude: -122.419418 degrees
347
+ # => altitude: 52.4 m
348
+ # => speed: 12.5 km/h
349
+ # => gps_quality: 8 satellites
350
+ ```
351
+
228
352
  ### Multiple Sensors with Scheduled Polling
229
353
 
230
354
  ```ruby path=null start=null
@@ -353,6 +477,29 @@ ls /sys/bus/w1/devices/
353
477
  # Should show devices like: 28-00000xxxxxx
354
478
  ```
355
479
 
480
+ #### Raspberry Pi OS: Enable I2C and 1-Wire
481
+
482
+ On Raspberry Pi OS you can enable I2C and 1-Wire via raspi-config or by editing /boot/config.txt.
483
+
484
+ Option A: raspi-config (recommended)
485
+ ```bash path=null start=null
486
+ sudo raspi-config
487
+ # Interface Options → I2C → Enable
488
+ # Interface Options → 1-Wire → Enable
489
+ sudo reboot
490
+ ```
491
+
492
+ Option B: edit /boot/config.txt
493
+ ```bash path=null start=null
494
+ # Enable I2C
495
+ sudo sed -i 's/^#\?dtparam=i2c_arm=.*/dtparam=i2c_arm=on/' /boot/config.txt
496
+ # Enable 1-Wire on default BCM4 (PIN7)
497
+ echo 'dtoverlay=w1-gpio,gpiopin=4' | sudo tee -a /boot/config.txt
498
+ sudo reboot
499
+ ```
500
+
501
+ Note: Dredger-IoT accepts Raspberry Pi labels like GPIO17, BCM17, and PIN11.
502
+
356
503
  #### Beaglebone Black Device Tree
357
504
 
358
505
  For Beaglebone Black, you may need to enable device tree overlays:
@@ -553,6 +700,25 @@ reading.timestamp # Time object when reading was taken
553
700
  dredger read ina219 0x40 --shunt 0.1
554
701
  ```
555
702
 
703
+ - **`ADXL345`** - 3-axis accelerometer (I2C)
704
+ - Parameters: `i2c_addr` (default: `0x53`), `provider`
705
+ - Returns: acceleration_x (g), acceleration_y (g), acceleration_z (g)
706
+ - Ranges: ±2g, ±4g, ±8g, ±16g (configurable in provider)
707
+
708
+ - **`SCD30`** - NDIR CO2 sensor (I2C)
709
+ - Parameters: `i2c_addr` (default: `0x61`), `provider`
710
+ - Returns: co2 (ppm), temperature (celsius), humidity (%)
711
+ - Range: 400-10,000 ppm CO2
712
+
713
+ - **`YFS201`** - Hall effect flow meter (GPIO)
714
+ - Parameters: `pin_label`, `provider`, `sample_duration` (default: `1.0`)
715
+ - Returns: flow_rate (L/min)
716
+ - Range: 1-30 L/min
717
+
718
+ - **`NEO6M`** - GPS module (UART/Serial)
719
+ - Parameters: `device` (default: `'/dev/ttyAMA0'`), `provider`
720
+ - Returns: latitude (degrees), longitude (degrees), altitude (m), speed (km/h), gps_quality (satellites)
721
+
556
722
  ### Scheduling
557
723
 
558
724
  #### `Dredger::IoT::Scheduler.periodic_with_jitter`
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:'
@@ -270,6 +273,51 @@ class DredgerCLI
270
273
  puts " DREDGER_IOT_I2C_BACKEND: #{ENV['DREDGER_IOT_I2C_BACKEND'] || '(not set)'}"
271
274
  end
272
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
+
273
321
  def setup_backends
274
322
  case @options[:backend]
275
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,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dredger
4
+ module IoT
5
+ module Sensors
6
+ # ADXL345 3-axis digital accelerometer sensor (I2C/SPI)
7
+ # Measures acceleration/vibration in x, y, z axes
8
+ # Uses a provider interface to allow simulation in tests and hardware backends in production.
9
+ class ADXL345 < BaseSensor
10
+ # provider must respond to :read_measurements(i2c_addr) -> { x_g:, y_g:, z_g: }
11
+ def initialize(i2c_addr: 0x53, provider:, metadata: {})
12
+ super(metadata: metadata)
13
+ @i2c_addr = i2c_addr
14
+ @provider = provider
15
+ end
16
+
17
+ def readings
18
+ sample = @provider.read_measurements(@i2c_addr)
19
+ [
20
+ reading(sensor_type: 'acceleration_x', value: sample[:x_g], unit: 'g'),
21
+ reading(sensor_type: 'acceleration_y', value: sample[:y_g], unit: 'g'),
22
+ reading(sensor_type: 'acceleration_z', value: sample[:z_g], unit: 'g')
23
+ ]
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dredger
4
+ module IoT
5
+ module Sensors
6
+ # Hardware provider for ADXL345 3-axis accelerometer over I2C.
7
+ # Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ADXL345.pdf
8
+ #
9
+ # Key features:
10
+ # - ±2g, ±4g, ±8g, ±16g selectable measurement ranges
11
+ # - 10-bit to 13-bit resolution
12
+ # - I2C (up to 400 kHz) or SPI interface
13
+ # - Default I2C address: 0x53 (ALT address: 0x1D if SDO/ALT pulled high)
14
+ #
15
+ # Key registers:
16
+ # - 0x00: DEVID (should be 0xE5)
17
+ # - 0x2D: POWER_CTL (power modes)
18
+ # - 0x31: DATA_FORMAT (range and resolution)
19
+ # - 0x32-0x37: DATAX0, DATAX1, DATAY0, DATAY1, DATAZ0, DATAZ1 (acceleration data)
20
+ class ADXL345Provider
21
+ DEVID_REG = 0x00
22
+ DEVID_EXPECTED = 0xE5
23
+ POWER_CTL_REG = 0x2D
24
+ DATA_FORMAT_REG = 0x31
25
+ DATAX0_REG = 0x32
26
+
27
+ # Measurement mode bit (POWER_CTL register)
28
+ MEASURE_BIT = 0x08
29
+
30
+ # i2c_bus: an I2C bus interface (e.g., Dredger::IoT::Bus::Auto.i2c)
31
+ # range: measurement range in g (2, 4, 8, or 16)
32
+ def initialize(i2c_bus:, range: 2)
33
+ @i2c = i2c_bus
34
+ @range = range
35
+ @scale_factor = calculate_scale_factor(range)
36
+ end
37
+
38
+ # Read acceleration measurements from the ADXL345 at the given I2C address.
39
+ # Returns { x_g: Float, y_g: Float, z_g: Float }
40
+ def read_measurements(addr)
41
+ # Verify device ID
42
+ dev_id = @i2c.read(addr, 1, register: DEVID_REG).first
43
+ raise IOError, "ADXL345 not found (devid=0x#{dev_id.to_s(16)})" unless dev_id == DEVID_EXPECTED
44
+
45
+ # Configure sensor (one-time setup)
46
+ configure_sensor(addr)
47
+
48
+ # Read 6 bytes of acceleration data (X, Y, Z as 16-bit signed integers)
49
+ raw = @i2c.read(addr, 6, register: DATAX0_REG)
50
+
51
+ # Parse 16-bit signed values (little-endian)
52
+ x_raw = to_signed16(raw[0] | (raw[1] << 8))
53
+ y_raw = to_signed16(raw[2] | (raw[3] << 8))
54
+ z_raw = to_signed16(raw[4] | (raw[5] << 8))
55
+
56
+ # Convert to g units using scale factor
57
+ {
58
+ x_g: (x_raw * @scale_factor).round(3),
59
+ y_g: (y_raw * @scale_factor).round(3),
60
+ z_g: (z_raw * @scale_factor).round(3)
61
+ }
62
+ end
63
+
64
+ private
65
+
66
+ # Configure sensor for measurement mode with specified range
67
+ def configure_sensor(addr)
68
+ # Set data format: range bits [1:0]
69
+ # ±2g: 0b00, ±4g: 0b01, ±8g: 0b10, ±16g: 0b11
70
+ range_bits = case @range
71
+ when 2 then 0b00
72
+ when 4 then 0b01
73
+ when 8 then 0b10
74
+ when 16 then 0b11
75
+ else raise ArgumentError, "Invalid range: #{@range}g (must be 2, 4, 8, or 16)"
76
+ end
77
+ @i2c.write(addr, [range_bits], register: DATA_FORMAT_REG)
78
+
79
+ # Enable measurement mode
80
+ @i2c.write(addr, [MEASURE_BIT], register: POWER_CTL_REG)
81
+
82
+ # Wait for sensor to stabilize
83
+ sleep(0.01)
84
+ end
85
+
86
+ # Calculate scale factor for converting raw values to g
87
+ # ADXL345 uses 10-bit resolution in full resolution mode
88
+ # Scale factor: range / 512 (for 10-bit)
89
+ def calculate_scale_factor(range)
90
+ # In full resolution mode, scale is ~3.9 mg/LSB regardless of range
91
+ # In fixed 10-bit mode, scale depends on range
92
+ # Using simplified calculation: range / 512
93
+ range / 512.0
94
+ end
95
+
96
+ # Convert unsigned 16-bit to signed 16-bit
97
+ def to_signed16(val)
98
+ val > 32_767 ? val - 65_536 : val
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dredger
4
+ module IoT
5
+ module Sensors
6
+ # NEO-6M GPS module (UART/Serial)
7
+ # Provides location, altitude, speed, and satellite information
8
+ # Uses a provider interface to allow simulation in tests and hardware backends in production.
9
+ class NEO6M < BaseSensor
10
+ # provider must respond to :read_position(device) -> { latitude:, longitude:, altitude:, speed:, satellites: }
11
+ def initialize(device: '/dev/ttyAMA0', provider:, metadata: {})
12
+ super(metadata: metadata)
13
+ @device = device
14
+ @provider = provider
15
+ end
16
+
17
+ def readings
18
+ sample = @provider.read_position(@device)
19
+ [
20
+ reading(sensor_type: 'latitude', value: sample[:latitude], unit: 'degrees'),
21
+ reading(sensor_type: 'longitude', value: sample[:longitude], unit: 'degrees'),
22
+ reading(sensor_type: 'altitude', value: sample[:altitude], unit: 'm'),
23
+ reading(sensor_type: 'speed', value: sample[:speed], unit: 'km/h'),
24
+ reading(
25
+ sensor_type: 'gps_quality',
26
+ value: sample[:satellites],
27
+ unit: 'satellites',
28
+ metadata: { fix_quality: sample[:fix_quality] }
29
+ )
30
+ ]
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'io/wait'
4
+
5
+ module Dredger
6
+ module IoT
7
+ module Sensors
8
+ # Hardware provider for NEO-6M GPS module via UART/Serial with NMEA parsing.
9
+ # Datasheet: https://www.u-blox.com/sites/default/files/products/documents/NEO-6_DataSheet_(GPS.G6-HW-09005).pdf
10
+ #
11
+ # Key features:
12
+ # - 50-channel GPS receiver
13
+ # - UART interface (default: 9600 baud, 8N1)
14
+ # - NMEA 0183 protocol output
15
+ # - Update rate: 1-5 Hz (default: 1 Hz)
16
+ # - Cold start: ~27s, Warm start: ~1s
17
+ #
18
+ # NMEA Sentences:
19
+ # - $GPGGA: Global Positioning System Fix Data (position, altitude, satellites)
20
+ # - $GPRMC: Recommended Minimum Navigation Information (position, speed, date/time)
21
+ # - $GPGSA: GPS DOP and Active Satellites
22
+ # - $GPGSV: GPS Satellites in View
23
+ class NEO6MProvider
24
+ # baud_rate: serial baud rate (default: 9600)
25
+ # timeout: read timeout in seconds (default: 5)
26
+ def initialize(baud_rate: 9600, timeout: 5)
27
+ @baud_rate = baud_rate
28
+ @timeout = timeout
29
+ end
30
+
31
+ # Read GPS position by parsing NMEA sentences from the serial device.
32
+ # Returns { latitude: Float, longitude: Float, altitude: Float, speed: Float, satellites: Integer, fix_quality: Integer }
33
+ #
34
+ # @param device [String] Serial device path (e.g., '/dev/ttyAMA0', '/dev/ttyUSB0')
35
+ def read_position(device)
36
+ File.open(device, 'r+') do |serial|
37
+ configure_serial(serial)
38
+
39
+ # Read NMEA sentences until we have both GGA and RMC (or timeout)
40
+ gga_data = nil
41
+ rmc_data = nil
42
+ start_time = Time.now
43
+
44
+ while (gga_data.nil? || rmc_data.nil?) && (Time.now - start_time < @timeout)
45
+ line = read_line(serial, @timeout)
46
+ next unless line
47
+
48
+ if line.start_with?('$GPGGA') || line.start_with?('$GNGGA')
49
+ gga_data = parse_gga(line)
50
+ elsif line.start_with?('$GPRMC') || line.start_with?('$GNRMC')
51
+ rmc_data = parse_rmc(line)
52
+ end
53
+ end
54
+
55
+ raise IOError, 'GPS timeout: no valid NMEA sentences received' if gga_data.nil? && rmc_data.nil?
56
+
57
+ # Merge data from both sentences (GGA has altitude/satellites, RMC has speed)
58
+ {
59
+ latitude: gga_data&.dig(:latitude) || rmc_data&.dig(:latitude) || 0.0,
60
+ longitude: gga_data&.dig(:longitude) || rmc_data&.dig(:longitude) || 0.0,
61
+ altitude: gga_data&.dig(:altitude) || 0.0,
62
+ speed: rmc_data&.dig(:speed) || 0.0,
63
+ satellites: gga_data&.dig(:satellites) || 0,
64
+ fix_quality: gga_data&.dig(:fix_quality) || 0
65
+ }
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ # Configure serial port (Linux termios settings)
72
+ def configure_serial(serial)
73
+ # Set raw mode, no echo, baud rate
74
+ # This would typically use `stty` or `termios` gem
75
+ # For simplicity, assuming device is already configured
76
+ # In production, use: system("stty -F #{device} #{@baud_rate} raw -echo")
77
+ end
78
+
79
+ # Read a line from serial with timeout
80
+ def read_line(serial, timeout)
81
+ return nil unless serial.wait_readable(timeout)
82
+
83
+ serial.gets&.chomp
84
+ end
85
+
86
+ # Parse $GPGGA sentence: Global Positioning System Fix Data
87
+ # Format: $GPGGA,hhmmss.ss,llll.ll,a,yyyyy.yy,a,x,xx,x.x,x.x,M,x.x,M,x.x,xxxx*hh
88
+ # Example: $GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47
89
+ def parse_gga(sentence)
90
+ parts = sentence.split(',')
91
+ return nil if parts.size < 15 || parts[6].to_i.zero? # Check fix quality
92
+
93
+ {
94
+ latitude: parse_coordinate(parts[2], parts[3]),
95
+ longitude: parse_coordinate(parts[4], parts[5]),
96
+ fix_quality: parts[6].to_i,
97
+ satellites: parts[7].to_i,
98
+ altitude: parts[9].to_f
99
+ }
100
+ end
101
+
102
+ # Parse $GPRMC sentence: Recommended Minimum Navigation Information
103
+ # Format: $GPRMC,hhmmss.ss,A,llll.ll,a,yyyyy.yy,a,x.x,x.x,ddmmyy,x.x,a*hh
104
+ # Example: $GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A
105
+ def parse_rmc(sentence)
106
+ parts = sentence.split(',')
107
+ return nil if parts.size < 12 || parts[2] != 'A' # Check if data is valid
108
+
109
+ {
110
+ latitude: parse_coordinate(parts[3], parts[4]),
111
+ longitude: parse_coordinate(parts[5], parts[6]),
112
+ speed: parts[7].to_f * 1.852 # Convert knots to km/h
113
+ }
114
+ end
115
+
116
+ # Parse NMEA coordinate format (ddmm.mmmm) to decimal degrees
117
+ # @param coord_str [String] Coordinate string (e.g., "4807.038")
118
+ # @param direction [String] Direction (N/S for latitude, E/W for longitude)
119
+ def parse_coordinate(coord_str, direction)
120
+ return 0.0 if coord_str.nil? || coord_str.empty?
121
+
122
+ # Determine if latitude or longitude based on length
123
+ # Latitude: ddmm.mmmm (2 digit degrees)
124
+ # Longitude: dddmm.mmmm (3 digit degrees)
125
+ degree_digits = coord_str.length >= 5 && coord_str[4] == '.' ? 2 : 3
126
+
127
+ degrees = coord_str[0, degree_digits].to_f
128
+ minutes = coord_str[degree_digits..-1].to_f
129
+
130
+ decimal_degrees = degrees + (minutes / 60.0)
131
+
132
+ # Apply direction (negative for South and West)
133
+ decimal_degrees *= -1 if %w[S W].include?(direction)
134
+
135
+ decimal_degrees.round(6)
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dredger
4
+ module IoT
5
+ module Sensors
6
+ # SCD30 NDIR CO2 sensor with integrated temperature and humidity sensor
7
+ # Measures CO2 concentration, temperature, and humidity
8
+ # Uses a provider interface to allow simulation in tests and hardware backends in production.
9
+ class SCD30 < BaseSensor
10
+ # provider must respond to :read_measurements(i2c_addr) -> { co2_ppm:, temperature_c:, humidity: }
11
+ def initialize(i2c_addr: 0x61, provider:, metadata: {})
12
+ super(metadata: metadata)
13
+ @i2c_addr = i2c_addr
14
+ @provider = provider
15
+ end
16
+
17
+ def readings
18
+ sample = @provider.read_measurements(@i2c_addr)
19
+ [
20
+ reading(sensor_type: 'co2', value: sample[:co2_ppm], unit: 'ppm'),
21
+ reading(sensor_type: 'temperature', value: sample[:temperature_c], unit: 'celsius'),
22
+ reading(sensor_type: 'humidity', value: sample[:humidity], unit: '%')
23
+ ]
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dredger
4
+ module IoT
5
+ module Sensors
6
+ # Hardware provider for SCD30 CO2, temperature, and humidity sensor over I2C.
7
+ # Datasheet: https://sensirion.com/media/documents/4EAF6AF8/61652C3C/Sensirion_CO2_Sensors_SCD30_Datasheet.pdf
8
+ #
9
+ # Key features:
10
+ # - NDIR CO2 sensor (400-10,000 ppm range)
11
+ # - Integrated SHT31 temperature and humidity sensor
12
+ # - I2C interface (default address: 0x61)
13
+ # - Automatic self-calibration
14
+ # - Measurement interval: 2-1800 seconds
15
+ #
16
+ # Key commands:
17
+ # - 0x0010: Start continuous measurement
18
+ # - 0x0104: Stop continuous measurement
19
+ # - 0x0202: Set measurement interval
20
+ # - 0x0300: Data ready status
21
+ # - 0x0027: Read measurement (18 bytes: CO2, temp, humidity)
22
+ class SCD30Provider
23
+ CMD_START_MEASUREMENT = 0x0010
24
+ CMD_STOP_MEASUREMENT = 0x0104
25
+ CMD_SET_INTERVAL = 0x0202
26
+ CMD_DATA_READY = 0x0300
27
+ CMD_READ_MEASUREMENT = 0x0027
28
+
29
+ # i2c_bus: an I2C bus interface (e.g., Dredger::IoT::Bus::Auto.i2c)
30
+ # interval: measurement interval in seconds (2-1800)
31
+ # ambient_pressure: ambient pressure compensation in mBar (700-1400, 0=disable)
32
+ def initialize(i2c_bus:, interval: 2, ambient_pressure: 0)
33
+ @i2c = i2c_bus
34
+ @interval = interval
35
+ @ambient_pressure = ambient_pressure
36
+ @initialized = false
37
+ end
38
+
39
+ # Read measurements from the SCD30 at the given I2C address.
40
+ # Returns { co2_ppm: Float, temperature_c: Float, humidity: Float }
41
+ def read_measurements(addr)
42
+ # Initialize sensor on first read
43
+ initialize_sensor(addr) unless @initialized
44
+
45
+ # Wait for data ready
46
+ wait_for_data_ready(addr)
47
+
48
+ # Read 18 bytes of measurement data
49
+ # Format: [CO2_MSB, CO2_LSB, CO2_CRC] [CO2_MSB, CO2_LSB, CO2_CRC] [T_MSB, T_LSB, T_CRC] [T_MSB, T_LSB, T_CRC] [H_MSB, H_LSB, H_CRC] [H_MSB, H_LSB, H_CRC]
50
+ # Actually: 3 float32 values (4 bytes each + CRC after every 2 bytes = 6 bytes per value)
51
+ @i2c.write(addr, [CMD_READ_MEASUREMENT >> 8, CMD_READ_MEASUREMENT & 0xFF])
52
+ sleep(0.01) # Wait for response
53
+ raw = @i2c.read(addr, 18)
54
+
55
+ # Parse float32 values with CRC validation
56
+ co2_ppm = parse_float32_with_crc(raw, 0)
57
+ temp_c = parse_float32_with_crc(raw, 6)
58
+ humidity = parse_float32_with_crc(raw, 12)
59
+
60
+ {
61
+ co2_ppm: co2_ppm.round(1),
62
+ temperature_c: temp_c.round(2),
63
+ humidity: humidity.round(1)
64
+ }
65
+ end
66
+
67
+ private
68
+
69
+ # Initialize sensor with measurement interval and start continuous measurement
70
+ def initialize_sensor(addr)
71
+ # Set measurement interval
72
+ write_command_with_arg(addr, CMD_SET_INTERVAL, @interval)
73
+ sleep(0.01)
74
+
75
+ # Start continuous measurement with optional pressure compensation
76
+ write_command_with_arg(addr, CMD_START_MEASUREMENT, @ambient_pressure)
77
+ sleep(0.02)
78
+
79
+ @initialized = true
80
+ end
81
+
82
+ # Wait for data to be ready (poll data ready status)
83
+ def wait_for_data_ready(addr, timeout: 2.0)
84
+ start_time = Time.now
85
+ loop do
86
+ @i2c.write(addr, [CMD_DATA_READY >> 8, CMD_DATA_READY & 0xFF])
87
+ sleep(0.01)
88
+ status = @i2c.read(addr, 3)
89
+ # Data ready when bit 0 of word is 1
90
+ data_ready = (status[1] & 0x01) == 1
91
+ return if data_ready
92
+
93
+ raise IOError, 'SCD30 data ready timeout' if Time.now - start_time > timeout
94
+
95
+ sleep(0.1)
96
+ end
97
+ end
98
+
99
+ # Write a command with a 16-bit argument and CRC
100
+ def write_command_with_arg(addr, command, arg)
101
+ cmd_msb = command >> 8
102
+ cmd_lsb = command & 0xFF
103
+ arg_msb = arg >> 8
104
+ arg_lsb = arg & 0xFF
105
+ crc = calculate_crc8([arg_msb, arg_lsb])
106
+ @i2c.write(addr, [cmd_msb, cmd_lsb, arg_msb, arg_lsb, crc])
107
+ end
108
+
109
+ # Parse a float32 value from 6 bytes (4 data + 2 CRC)
110
+ # Format: [MSB0, LSB0, CRC0, MSB1, LSB1, CRC1]
111
+ def parse_float32_with_crc(data, offset)
112
+ # Verify CRCs
113
+ crc0 = calculate_crc8([data[offset], data[offset + 1]])
114
+ crc1 = calculate_crc8([data[offset + 3], data[offset + 4]])
115
+ raise IOError, 'SCD30 CRC error' unless crc0 == data[offset + 2] && crc1 == data[offset + 5]
116
+
117
+ # Combine bytes into uint32 and convert to float32
118
+ bytes = [
119
+ data[offset],
120
+ data[offset + 1],
121
+ data[offset + 3],
122
+ data[offset + 4]
123
+ ].pack('C4').unpack1('N')
124
+ [bytes].pack('L>').unpack1('g')
125
+ end
126
+
127
+ # Calculate CRC-8 checksum (polynomial: 0x31, init: 0xFF)
128
+ def calculate_crc8(data)
129
+ crc = 0xFF
130
+ data.each do |byte|
131
+ crc ^= byte
132
+ 8.times do
133
+ crc = (crc & 0x80) != 0 ? ((crc << 1) ^ 0x31) : (crc << 1)
134
+ crc &= 0xFF
135
+ end
136
+ end
137
+ crc
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dredger
4
+ module IoT
5
+ module Sensors
6
+ # YF-S201 water flow sensor (hall effect pulse counter)
7
+ # Measures liquid flow rate by counting pulses from hall effect sensor
8
+ # Uses a provider interface to allow simulation in tests and hardware backends in production.
9
+ class YFS201 < BaseSensor
10
+ # provider must respond to :read_flow_rate(pin_label, duration) -> { flow_rate_lpm: Float, pulses: Integer }
11
+ def initialize(pin_label:, provider:, sample_duration: 1.0, metadata: {})
12
+ super(metadata: metadata)
13
+ @pin_label = pin_label
14
+ @provider = provider
15
+ @sample_duration = sample_duration
16
+ end
17
+
18
+ def readings
19
+ sample = @provider.read_flow_rate(@pin_label, @sample_duration)
20
+ [
21
+ reading(
22
+ sensor_type: 'flow_rate',
23
+ value: sample[:flow_rate_lpm],
24
+ unit: 'L/min',
25
+ metadata: { pulses: sample[:pulses], duration: @sample_duration }
26
+ )
27
+ ]
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dredger
4
+ module IoT
5
+ module Sensors
6
+ # Hardware provider for YF-S201 water flow sensor via GPIO pulse counting.
7
+ # Datasheet: https://www.hobbytronics.co.uk/download/YF-S201.pdf
8
+ #
9
+ # Key features:
10
+ # - Hall effect sensor with digital output
11
+ # - Flow rate range: 1-30 L/min
12
+ # - Pulse frequency: ~4.5 * flow_rate (L/min)
13
+ # - Working voltage: 5V DC
14
+ # - Thread size: G1/2" (DN15)
15
+ #
16
+ # Calibration:
17
+ # - Frequency (Hz) = 7.5 * flow rate (L/min) (official spec)
18
+ # - In practice: F = K * Q where K ≈ 4.5-7.5 depending on unit
19
+ # - Default calibration factor: 7.5 (pulses per liter)
20
+ class YFS201Provider
21
+ # gpio_bus: a GPIO bus interface (e.g., Dredger::IoT::Bus::Auto.gpio)
22
+ # calibration_factor: pulses per liter (default: 7.5 for YF-S201)
23
+ def initialize(gpio_bus:, calibration_factor: 7.5)
24
+ @gpio = gpio_bus
25
+ @calibration_factor = calibration_factor
26
+ end
27
+
28
+ # Read flow rate by counting pulses over the specified duration.
29
+ # Returns { flow_rate_lpm: Float, pulses: Integer }
30
+ #
31
+ # @param pin_label [String] GPIO pin label (e.g., 'P9_12', 'GPIO17')
32
+ # @param duration [Float] Sampling duration in seconds
33
+ def read_flow_rate(pin_label, duration)
34
+ # Configure pin as input
35
+ @gpio.set_direction(pin_label, :in)
36
+
37
+ # Count rising edge pulses over the duration
38
+ pulses = count_pulses(pin_label, duration)
39
+
40
+ # Calculate flow rate in L/min
41
+ # pulses_per_second = pulses / duration
42
+ # liters_per_second = pulses_per_second / calibration_factor
43
+ # liters_per_minute = liters_per_second * 60
44
+ flow_rate_lpm = (pulses / duration / @calibration_factor * 60.0).round(2)
45
+
46
+ {
47
+ flow_rate_lpm: flow_rate_lpm,
48
+ pulses: pulses
49
+ }
50
+ end
51
+
52
+ private
53
+
54
+ # Count rising edge transitions on the pin over the specified duration.
55
+ # This is a simplified implementation - production code should use:
56
+ # - Hardware interrupts (kernel module or pigpio daemon)
57
+ # - Edge detection via sysfs GPIO (epoll)
58
+ # - High-priority thread for accurate timing
59
+ def count_pulses(pin_label, duration)
60
+ pulses = 0
61
+ last_state = @gpio.read(pin_label)
62
+ start_time = Time.now
63
+
64
+ # Poll GPIO at high frequency to detect edges
65
+ # Note: This is CPU-intensive and timing-dependent
66
+ # For production, use hardware interrupts or kernel GPIO edge detection
67
+ while Time.now - start_time < duration
68
+ current_state = @gpio.read(pin_label)
69
+
70
+ # Detect rising edge (0 -> 1 transition)
71
+ if current_state == 1 && last_state == 0
72
+ pulses += 1
73
+ end
74
+
75
+ last_state = current_state
76
+
77
+ # Small sleep to reduce CPU usage
78
+ # Trade-off: Higher sleep = lower CPU, but may miss pulses
79
+ # For accurate counting, use interrupts instead
80
+ sleep(0.0001) # 0.1ms
81
+ end
82
+
83
+ pulses
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -17,3 +17,11 @@ require_relative 'sensors/tsl2561'
17
17
  require_relative 'sensors/tsl2561_provider'
18
18
  require_relative 'sensors/ina219'
19
19
  require_relative 'sensors/ina219_provider'
20
+ require_relative 'sensors/adxl345'
21
+ require_relative 'sensors/adxl345_provider'
22
+ require_relative 'sensors/scd30'
23
+ require_relative 'sensors/scd30_provider'
24
+ require_relative 'sensors/yf_s201'
25
+ require_relative 'sensors/yf_s201_provider'
26
+ require_relative 'sensors/neo6m'
27
+ require_relative 'sensors/neo6m_provider'
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Dredger
4
4
  module IoT
5
- VERSION = '0.2.0'
5
+ VERSION = '0.3.0'
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.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - The Mad Botter INC
@@ -49,9 +49,12 @@ 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
56
+ - lib/dredger/iot/sensors/adxl345.rb
57
+ - lib/dredger/iot/sensors/adxl345_provider.rb
55
58
  - lib/dredger/iot/sensors/base_sensor.rb
56
59
  - lib/dredger/iot/sensors/bh1750.rb
57
60
  - lib/dredger/iot/sensors/bh1750_provider.rb
@@ -65,10 +68,16 @@ files:
65
68
  - lib/dredger/iot/sensors/ina219.rb
66
69
  - lib/dredger/iot/sensors/ina219_provider.rb
67
70
  - lib/dredger/iot/sensors/mcp9808.rb
71
+ - lib/dredger/iot/sensors/neo6m.rb
72
+ - lib/dredger/iot/sensors/neo6m_provider.rb
73
+ - lib/dredger/iot/sensors/scd30.rb
74
+ - lib/dredger/iot/sensors/scd30_provider.rb
68
75
  - lib/dredger/iot/sensors/sht31.rb
69
76
  - lib/dredger/iot/sensors/sht31_provider.rb
70
77
  - lib/dredger/iot/sensors/tsl2561.rb
71
78
  - lib/dredger/iot/sensors/tsl2561_provider.rb
79
+ - lib/dredger/iot/sensors/yf_s201.rb
80
+ - lib/dredger/iot/sensors/yf_s201_provider.rb
72
81
  - lib/dredger/iot/version.rb
73
82
  homepage: https://github.com/TheMadBotterINC/dredger-iot
74
83
  licenses:
@@ -97,7 +106,7 @@ post_install_message: "\n ═════════════════
97
106
  \ | \n |_____________________________| \n
98
107
  \ ~~~ \\ // ~~~ \n \\_______________//
99
108
  \ \n ═══════════════════════════════════════════════════════════════════\nHardware
100
- Integration for Embedded Linux v0.2.0\n ═══════════════════════════════════════════════════════════════════\n\n
109
+ Integration for Embedded Linux v0.3.0\n ═══════════════════════════════════════════════════════════════════\n\n
101
110
  \ \U0001F389 Thanks for installing!\n\n \U0001F4DA Hardware Setup (kernel modules
102
111
  & permissions):\n https://github.com/TheMadBotterINC/dredger-iot#hardware-setup\n\n
103
112
  \ \U0001F680 Quick Start:\n require 'dredger/iot'\n gpio = Dredger::IoT::Bus::Auto.gpio\n