dredger-iot 0.3.0 → 0.3.2

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: 701ad99dd1f271df310ba3dffbb3295084b54e64689f4d3ed77f9b6ab9e31df5
4
- data.tar.gz: e9b5ba95c4d2af83b529e6f66af5c81fb83c43f3f679e89e894209661651f93e
3
+ metadata.gz: 20a13d73758a754dfef92919adab2c1bd3f7a22de71e523ebb5601ecd292d97f
4
+ data.tar.gz: 32d57b97d774f40cec842d1ef99b90c134687c6b7d418e60587dcfa77d8bd17b
5
5
  SHA512:
6
- metadata.gz: 5362ae41f8f92d403809728857c139b00e3d8141e8f83725a3cc302eb7e7ace2c32b8bd7306fba5e83c2ee197e42c633b7beaeb9a84b9b77f1d95299e80aad1d
7
- data.tar.gz: 220b5834cd302cef375c3922211c055788855d4b516b7260aa952d13fc312a3f9c028248863906e93a38badbd32839d2083783bb1431ba5fd1ada0b4df92bca5
6
+ metadata.gz: fdfd0dde86a514d2ffd829d9e74f4c834c7e8784257114f80a66321a6bc8b1e7d3329998197b6deabd5a31a19ee227eb28c56f9c28a84b8835e63b721e7ec082
7
+ data.tar.gz: 4921e4d495ee0d6d7b06f4f69912f2e80e1c5711b43af5eb8259d2331b46b0f036ad1cb89d23613d5b7857af4b612a2494bf09a38cdd0411b5423a95cb81fbec
data/bin/dredger CHANGED
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'bundler/setup'
5
4
  require 'dredger/iot'
6
5
  require 'optparse'
7
6
  require 'json'
@@ -90,15 +89,17 @@ class DredgerCLI
90
89
 
91
90
  def list_sensors
92
91
  sensors = {
93
- 'dht22' => 'DHT22 - Temperature/Humidity (GPIO)',
92
+ 'dht22' => 'DHT22 - Temperature/Humidity (GPIO) [requires C extension]',
94
93
  'bme280' => 'BME280 - Temperature/Humidity/Pressure (I2C)',
95
94
  'ds18b20' => 'DS18B20 - Waterproof Temperature (1-Wire)',
96
- 'bmp180' => 'BMP180 - Barometric Pressure/Temperature (I2C)',
97
- 'mcp9808' => 'MCP9808 - High-Accuracy Temperature (I2C)',
98
95
  'sht31' => 'SHT31 - Temperature/Humidity (I2C)',
99
96
  'bh1750' => 'BH1750 - Ambient Light (I2C)',
100
97
  'tsl2561' => 'TSL2561 - Ambient Light (I2C)',
101
- 'ina219' => 'INA219 - Bus Voltage/Current (I2C)'
98
+ 'ina219' => 'INA219 - Bus Voltage/Current (I2C)',
99
+ 'adxl345' => 'ADXL345 - 3-Axis Accelerometer (I2C)',
100
+ 'scd30' => 'SCD30 - CO2/Temperature/Humidity (I2C)',
101
+ 'yf_s201' => 'YF-S201 - Water Flow Meter (GPIO)',
102
+ 'neo6m' => 'NEO-6M - GPS Module (UART/Serial)'
102
103
  }
103
104
 
104
105
  puts 'Available Sensors:'
@@ -168,6 +169,25 @@ class DredgerCLI
168
169
  shunt = @options[:ina219_shunt] || 0.1
169
170
  provider = Dredger::IoT::Sensors::INA219Provider.new(i2c_bus: i2c, shunt_resistance_ohms: shunt)
170
171
  Dredger::IoT::Sensors::INA219.new(i2c_addr: addr, provider: provider)
172
+ when 'adxl345'
173
+ addr = (args.shift || '0x53').to_i(16)
174
+ i2c = Dredger::IoT::Bus::Auto.i2c
175
+ provider = Dredger::IoT::Sensors::ADXL345Provider.new(i2c_bus: i2c)
176
+ Dredger::IoT::Sensors::ADXL345.new(i2c_addr: addr, provider: provider)
177
+ when 'scd30'
178
+ addr = (args.shift || '0x61').to_i(16)
179
+ i2c = Dredger::IoT::Bus::Auto.i2c
180
+ provider = Dredger::IoT::Sensors::SCD30Provider.new(i2c_bus: i2c)
181
+ Dredger::IoT::Sensors::SCD30.new(i2c_addr: addr, provider: provider)
182
+ when 'yf_s201'
183
+ pin = args.shift || 'P9_12'
184
+ gpio = Dredger::IoT::Bus::Auto.gpio
185
+ provider = Dredger::IoT::Sensors::YFS201Provider.new(gpio_bus: gpio)
186
+ Dredger::IoT::Sensors::YFS201.new(pin_label: pin, provider: provider)
187
+ when 'neo6m'
188
+ device = args.shift || '/dev/ttyAMA0'
189
+ provider = Dredger::IoT::Sensors::NEO6MProvider.new
190
+ Dredger::IoT::Sensors::NEO6M.new(device: device, provider: provider)
171
191
  else
172
192
  puts "Error: Unknown sensor type '#{type}'"
173
193
  exit 1
@@ -201,7 +221,7 @@ class DredgerCLI
201
221
  sensor_type: r.sensor_type,
202
222
  value: r.value,
203
223
  unit: r.unit,
204
- timestamp: r.timestamp.iso8601
224
+ timestamp: r.recorded_at.iso8601
205
225
  }
206
226
  end
207
227
  puts JSON.pretty_generate(data)
@@ -289,11 +309,11 @@ class DredgerCLI
289
309
 
290
310
  puts
291
311
  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"
312
+ puts ' sudo apt-get install gpiod i2c-tools'
313
+ puts ' sudo usermod -a -G gpio $USER # for GPIO access'
314
+ puts ' sudo usermod -a -G i2c $USER # for I2C access'
315
+ puts ' sudo modprobe i2c-dev'
316
+ puts ' sudo modprobe w1-gpio; sudo modprobe w1-therm'
297
317
  end
298
318
 
299
319
  def check_device_node(path, desc)
@@ -21,6 +21,10 @@ module Dredger
21
21
  @backend.read(pin_label)
22
22
  end
23
23
 
24
+ def close
25
+ @backend.close if @backend.respond_to?(:close)
26
+ end
27
+
24
28
  # Simulation backend for tests/development
25
29
  class Simulation
26
30
  def initialize
@@ -24,6 +24,10 @@ module Dredger
24
24
  @backend.read(resolve(pin))
25
25
  end
26
26
 
27
+ def close
28
+ @backend.close if @backend.respond_to?(:close)
29
+ end
30
+
27
31
  private
28
32
 
29
33
  # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
@@ -19,6 +19,10 @@ module Dredger
19
19
  @backend.read(addr, length, register: register)
20
20
  end
21
21
 
22
+ def close
23
+ @backend.close if @backend.respond_to?(:close)
24
+ end
25
+
22
26
  # Simulation backend keeps a per-address register map
23
27
  class Simulation
24
28
  def initialize
@@ -5,13 +5,7 @@ module Dredger
5
5
  module Pins
6
6
  # Beaglebone header label mapping placeholder.
7
7
  class Beaglebone
8
- # Provides validation and PinRef objects.
9
- # Chip:line resolution is done at runtime by the agent or a backend.
10
- PinRef = Struct.new(:label, :chip, :line, keyword_init: true) do
11
- def to_s
12
- chip && line ? "#{label}(chip#{chip}:#{line})" : label.to_s
13
- end
14
- end
8
+ PinRef = Pins::PinRef
15
9
 
16
10
  KNOWN_LABELS = (
17
11
  (0..46).map { |n| "P8_#{n}" } + (0..46).map { |n| "P9_#{n}" }
@@ -8,11 +8,7 @@ module Dredger
8
8
  # - GPIO17, BCM17
9
9
  # - PIN11 (a.k.a. BOARD11)
10
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
11
+ PinRef = Pins::PinRef
16
12
 
17
13
  # Subset of BOARD pin to BCM mapping for common usable GPIOs on 40-pin header
18
14
  BOARD_TO_BCM = {
@@ -1,4 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ module Dredger
4
+ module IoT
5
+ module Pins
6
+ PinRef = Struct.new(:label, :chip, :line, keyword_init: true) do
7
+ def to_s
8
+ chip && line ? "#{label}(chip#{chip}:#{line})" : label.to_s
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
14
+
3
15
  require_relative 'pins/beaglebone'
4
16
  require_relative 'pins/raspberry_pi'
@@ -8,7 +8,7 @@ module Dredger
8
8
  # Uses a provider interface to allow simulation in tests and hardware backends in production.
9
9
  class ADXL345 < BaseSensor
10
10
  # provider must respond to :read_measurements(i2c_addr) -> { x_g:, y_g:, z_g: }
11
- def initialize(i2c_addr: 0x53, provider:, metadata: {})
11
+ def initialize(provider:, i2c_addr: 0x53, metadata: {})
12
12
  super(metadata: metadata)
13
13
  @i2c_addr = i2c_addr
14
14
  @provider = provider
@@ -33,6 +33,7 @@ module Dredger
33
33
  @i2c = i2c_bus
34
34
  @range = range
35
35
  @scale_factor = calculate_scale_factor(range)
36
+ @configured = false
36
37
  end
37
38
 
38
39
  # Read acceleration measurements from the ADXL345 at the given I2C address.
@@ -43,7 +44,10 @@ module Dredger
43
44
  raise IOError, "ADXL345 not found (devid=0x#{dev_id.to_s(16)})" unless dev_id == DEVID_EXPECTED
44
45
 
45
46
  # Configure sensor (one-time setup)
46
- configure_sensor(addr)
47
+ unless @configured
48
+ configure_sensor(addr)
49
+ @configured = true
50
+ end
47
51
 
48
52
  # Read 6 bytes of acceleration data (X, Y, Z as 16-bit signed integers)
49
53
  raw = @i2c.read(addr, 6, register: DATAX0_REG)
@@ -64,14 +64,9 @@ module Dredger
64
64
  # This is a stub - real implementation requires precise timing.
65
65
  # In production, consider using a kernel driver or hardware peripheral.
66
66
  def read_40_bits(_pin_label)
67
- # Placeholder: return simulated data for now
68
- # Real implementation would read GPIO with microsecond timing
69
- # For each bit: wait for low->high transition, measure high duration
70
- # If high > 40us, bit=1; else bit=0
71
- warn 'DHT22Provider: bit-banging not fully implemented, returning stub data'
72
- # humidity=65.2% => 0x028C, temp=31.4°C => 0x013A
73
- # checksum = (0x02 + 0x8C + 0x01 + 0x3A) & 0xFF = 0xC9
74
- [0x02, 0x8C, 0x01, 0x3A, 0xC9]
67
+ raise NotImplementedError,
68
+ 'DHT22 bit-banging requires microsecond timing not achievable in pure Ruby. ' \
69
+ 'Use a C extension, kernel driver (e.g., dht11 kernel module), or hardware peripheral.'
75
70
  end
76
71
  end
77
72
  end
@@ -23,7 +23,13 @@ module Dredger
23
23
  # Read temperature from DS18B20 sensor.
24
24
  # device_id: device address (e.g., "28-0000056789ab")
25
25
  # Returns temperature in Celsius as Float
26
+ VALID_DEVICE_ID = /\A28-[0-9a-f]+\z/
27
+
26
28
  def read_temperature(device_id)
29
+ unless device_id.match?(VALID_DEVICE_ID)
30
+ raise ArgumentError, "Invalid DS18B20 device_id: #{device_id}. Expected format: 28-xxxxxxxxxxxx"
31
+ end
32
+
27
33
  device_path = File.join(@base_path, device_id, 'w1_slave')
28
34
  raise IOError, "DS18B20 device not found: #{device_id}" unless File.exist?(device_path)
29
35
 
@@ -8,7 +8,7 @@ module Dredger
8
8
  # Uses a provider interface to allow simulation in tests and hardware backends in production.
9
9
  class NEO6M < BaseSensor
10
10
  # provider must respond to :read_position(device) -> { latitude:, longitude:, altitude:, speed:, satellites: }
11
- def initialize(device: '/dev/ttyAMA0', provider:, metadata: {})
11
+ def initialize(provider:, device: '/dev/ttyAMA0', metadata: {})
12
12
  super(metadata: metadata)
13
13
  @device = device
14
14
  @provider = provider
@@ -45,9 +45,9 @@ module Dredger
45
45
  line = read_line(serial, @timeout)
46
46
  next unless line
47
47
 
48
- if line.start_with?('$GPGGA') || line.start_with?('$GNGGA')
48
+ if line.start_with?('$GPGGA', '$GNGGA')
49
49
  gga_data = parse_gga(line)
50
- elsif line.start_with?('$GPRMC') || line.start_with?('$GNRMC')
50
+ elsif line.start_with?('$GPRMC', '$GNRMC')
51
51
  rmc_data = parse_rmc(line)
52
52
  end
53
53
  end
@@ -125,7 +125,7 @@ module Dredger
125
125
  degree_digits = coord_str.length >= 5 && coord_str[4] == '.' ? 2 : 3
126
126
 
127
127
  degrees = coord_str[0, degree_digits].to_f
128
- minutes = coord_str[degree_digits..-1].to_f
128
+ minutes = coord_str[degree_digits..].to_f
129
129
 
130
130
  decimal_degrees = degrees + (minutes / 60.0)
131
131
 
@@ -8,7 +8,7 @@ module Dredger
8
8
  # Uses a provider interface to allow simulation in tests and hardware backends in production.
9
9
  class SCD30 < BaseSensor
10
10
  # provider must respond to :read_measurements(i2c_addr) -> { co2_ppm:, temperature_c:, humidity: }
11
- def initialize(i2c_addr: 0x61, provider:, metadata: {})
11
+ def initialize(provider:, i2c_addr: 0x61, metadata: {})
12
12
  super(metadata: metadata)
13
13
  @i2c_addr = i2c_addr
14
14
  @provider = provider
@@ -87,7 +87,7 @@ module Dredger
87
87
  sleep(0.01)
88
88
  status = @i2c.read(addr, 3)
89
89
  # Data ready when bit 0 of word is 1
90
- data_ready = (status[1] & 0x01) == 1
90
+ data_ready = status[1].allbits?(0x01)
91
91
  return if data_ready
92
92
 
93
93
  raise IOError, 'SCD30 data ready timeout' if Time.now - start_time > timeout
@@ -130,7 +130,7 @@ module Dredger
130
130
  data.each do |byte|
131
131
  crc ^= byte
132
132
  8.times do
133
- crc = (crc & 0x80) != 0 ? ((crc << 1) ^ 0x31) : (crc << 1)
133
+ crc = crc.anybits?(0x80) ? ((crc << 1) ^ 0x31) : (crc << 1)
134
134
  crc &= 0xFF
135
135
  end
136
136
  end
@@ -66,14 +66,12 @@ module Dredger
66
66
  # For production, use hardware interrupts or kernel GPIO edge detection
67
67
  while Time.now - start_time < duration
68
68
  current_state = @gpio.read(pin_label)
69
-
69
+
70
70
  # Detect rising edge (0 -> 1 transition)
71
- if current_state == 1 && last_state == 0
72
- pulses += 1
73
- end
74
-
71
+ pulses += 1 if current_state == 1 && last_state.zero?
72
+
75
73
  last_state = current_state
76
-
74
+
77
75
  # Small sleep to reduce CPU usage
78
76
  # Trade-off: Higher sleep = lower CPU, but may miss pulses
79
77
  # For accurate counting, use interrupts instead
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Dredger
4
4
  module IoT
5
- VERSION = '0.3.0'
5
+ VERSION = '0.3.2'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,13 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dredger-iot
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - The Mad Botter INC
8
+ autorequire:
8
9
  bindir: bin
9
10
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
11
+ date: 2026-03-22 00:00:00.000000000 Z
11
12
  dependencies:
12
13
  - !ruby/object:Gem::Dependency
13
14
  name: ffi
@@ -26,7 +27,8 @@ dependencies:
26
27
  description: |
27
28
  Dredger-IoT provides FFI-based GPIO and I2C access for embedded Linux systems like Beaglebone Black.
28
29
  Features include: libgpiod GPIO backend, Linux i2c-dev I2C backend, simulation backends for testing,
29
- sensor drivers (DHT22, BME280, DS18B20, BMP180, MCP9808), Beaglebone pin label mapping, and
30
+ sensor drivers (BME280, DS18B20, SHT31, BH1750, TSL2561, INA219, ADXL345, SCD30, YF-S201, NEO-6M),
31
+ Beaglebone and Raspberry Pi pin label mapping, and
30
32
  scheduling utilities for periodic polling and exponential backoff.
31
33
  email:
32
34
  - opensource@themadbotter.com
@@ -91,7 +93,7 @@ metadata:
91
93
  documentation_uri: https://github.com/TheMadBotterINC/dredger-iot/blob/master/README.md
92
94
  rubygems_mfa_required: 'true'
93
95
  keywords: iot, embedded, linux, gpio, i2c, beaglebone, raspberry-pi, hardware, sensors,
94
- dht22, bme280, ds18b20, bmp180, mcp9808, libgpiod, automation
96
+ bme280, ds18b20, sht31, bh1750, tsl2561, ina219, adxl345, scd30, libgpiod, automation
95
97
  post_install_message: "\n ═══════════════════════════════════════════════════════════════════\n
96
98
  \ \n _______________
97
99
  \ \n | DREDGER-IoT | \n
@@ -106,13 +108,13 @@ post_install_message: "\n ═════════════════
106
108
  \ | \n |_____________________________| \n
107
109
  \ ~~~ \\ // ~~~ \n \\_______________//
108
110
  \ \n ═══════════════════════════════════════════════════════════════════\nHardware
109
- Integration for Embedded Linux v0.3.0\n ═══════════════════════════════════════════════════════════════════\n\n
111
+ Integration for Embedded Linux v0.3.2\n ═══════════════════════════════════════════════════════════════════\n\n
110
112
  \ \U0001F389 Thanks for installing!\n\n \U0001F4DA Hardware Setup (kernel modules
111
113
  & permissions):\n https://github.com/TheMadBotterINC/dredger-iot#hardware-setup\n\n
112
114
  \ \U0001F680 Quick Start:\n require 'dredger/iot'\n gpio = Dredger::IoT::Bus::Auto.gpio\n
113
115
  \ i2c = Dredger::IoT::Bus::Auto.i2c\n\n \U0001F4A1 Supported Sensors:\n
114
- \ DHT22, BME280, DS18B20, BMP180, MCP9808\n\n \U0001F4D6 Full Documentation:\n
115
- \ https://github.com/TheMadBotterINC/dredger-iot\n\n"
116
+ \ BME280, DS18B20, SHT31, INA219, ADXL345, SCD30, and more\n\n \U0001F4D6
117
+ Full Documentation:\n https://github.com/TheMadBotterINC/dredger-iot\n\n"
116
118
  rdoc_options: []
117
119
  require_paths:
118
120
  - lib
@@ -127,7 +129,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
127
129
  - !ruby/object:Gem::Version
128
130
  version: '0'
129
131
  requirements: []
130
- rubygems_version: 3.6.9
132
+ rubygems_version: 3.4.19
133
+ signing_key:
131
134
  specification_version: 4
132
135
  summary: Generic hardware integration for embedded Linux (GPIO, I2C) with sensor drivers.
133
136
  test_files: []