denko 0.13.3 → 0.13.5

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.
@@ -0,0 +1,26 @@
1
+ #
2
+ # This helper method can be used in temp/pressure/humidity sensor examples.
3
+ # Give a hash with readings as float values and it prints them neatly.
4
+ #
5
+ def print_tph_reading(reading)
6
+ # Time
7
+ print "#{Time.now.strftime '%Y-%m-%d %H:%M:%S'} - "
8
+
9
+ # Temperature
10
+ formatted_temp = reading[:temperature].to_f.round(2).to_s.ljust(5, '0')
11
+ print "Temperature: #{formatted_temp} \xC2\xB0C"
12
+
13
+ # Pressure
14
+ if reading[:pressure]
15
+ formatted_pressure = (reading[:pressure] / 101325).round(5).to_s.ljust(7, '0')
16
+ print " | Pressure #{formatted_pressure} atm"
17
+ end
18
+
19
+ # Humidity
20
+ if reading[:humidity]
21
+ formatted_humidity = reading[:humidity].round(2).to_s.ljust(5, '0')
22
+ print " | Humidity #{formatted_humidity} %"
23
+ end
24
+
25
+ puts
26
+ end
@@ -0,0 +1,53 @@
1
+ #
2
+ # Example using QMP6988 sensor over I2C, for air temperature and pressure.
3
+ #
4
+ require 'bundler/setup'
5
+ require 'denko'
6
+
7
+ board = Denko::Board.new(Denko::Connection::Serial.new)
8
+ bus = Denko::I2C::Bus.new(board: board, pin: :SDA)
9
+ sensor = Denko::Sensor::QMP6988.new(bus: bus) # address: 0x70 default
10
+
11
+ # Verify chip_id.
12
+ print "I2C device has chip ID: 0x#{sensor.chip_id.to_s(16).upcase}. "
13
+ if sensor.chip_id == 0x5C
14
+ puts "This matches the QMP6988."
15
+ else
16
+ puts "This does not match the QMP6988."
17
+ end
18
+ puts
19
+
20
+ #
21
+ # Change measurement settings:
22
+ # temperature_samples can be 1,2,4,8,16,32 or 64 (default: 1)
23
+ # pressure_samples can be 1,2,4,8,16,32 or 64 (default: 1)
24
+ # iir_coefficient can be 0,2,4,8,16 or 32 (default: 0)
25
+ #
26
+ # High accuracy settings from datasheet, with IIR of 2.
27
+ sensor.temperature_samples = 2
28
+ sensor.pressure_samples = 16
29
+ sensor.iir_coefficient = 2
30
+
31
+ #
32
+ # Change mode (default: forced_mode)
33
+ #
34
+ # Buggy on ESP32S3 in forced mode. Data registers return zeroes on all but first read.
35
+ # Can't recreate on ESP32 V1, AVR or SAMD21. Put it in contiuous mode just in case.
36
+ sensor.continuous_mode
37
+ # sensor.forced_mode
38
+
39
+ #
40
+ # Set standby time (between measurements) for continuous mode only:
41
+ # standby_time (given in ms) can be 1,5,20,250,500,1000,2000 or 4000 (default: 1)
42
+ #
43
+ # sensor.standby_time = 500
44
+
45
+ # Get the shared #print_tph_reading method to print readings neatly.
46
+ require_relative 'neat_tph_readings'
47
+
48
+ # Poll it and print readings.
49
+ sensor.poll(5) do |reading|
50
+ print_tph_reading(reading)
51
+ end
52
+
53
+ sleep
@@ -0,0 +1,15 @@
1
+ #
2
+ # Example using an RCWL-9620 sensor over I2C to measure distance.
3
+ #
4
+ require 'bundler/setup'
5
+ require 'denko'
6
+
7
+ board = Denko::Board.new(Denko::Connection::Serial.new)
8
+ bus = Denko::I2C::Bus.new(board: board, pin: :SDA)
9
+ sensor = Denko::Sensor::RCWL9620.new(bus: bus) # address: 0x57 default
10
+
11
+ sensor.poll(1) do |distance|
12
+ puts "Distance is #{distance} mm"
13
+ end
14
+
15
+ sleep
@@ -0,0 +1,34 @@
1
+ #
2
+ # Example using SHT30/31/35 sensor over I2C, for temperature and humidity.
3
+ #
4
+ require 'bundler/setup'
5
+ require 'denko'
6
+
7
+ board = Denko::Board.new(Denko::Connection::Serial.new)
8
+ bus = Denko::I2C::Bus.new(board: board, pin: :SDA)
9
+ sensor = Denko::Sensor::SHT3X.new(bus: bus) # address: 0x44 default
10
+
11
+ # Heater control
12
+ sensor.heater_on
13
+ puts "Heater on: #{sensor.heater_on?}"
14
+ sensor.heater_off
15
+ puts "Heater off: #{sensor.heater_off?}"
16
+
17
+ # Reset (turns heater off)
18
+ sensor.reset
19
+ puts "Resetting..."
20
+ puts "Heater off: #{sensor.heater_off?}"
21
+ puts
22
+
23
+ # Set repeatability= :low, :medium or :high (default). See datasheet for details.
24
+ sensor.repeatability = :high
25
+
26
+ # Get the shared #print_tph_reading method to print readings neatly.
27
+ require_relative 'neat_tph_readings'
28
+
29
+ # Poll it and print readings.
30
+ sensor.poll(5) do |reading|
31
+ print_tph_reading(reading)
32
+ end
33
+
34
+ sleep
@@ -22,5 +22,9 @@ module Denko
22
22
  value: settings,
23
23
  aux_message: aux
24
24
  end
25
+
26
+ def hcsr04_read(echo_pin, trigger_pin)
27
+ write Message.encode(command: 20, pin: echo_pin, value: trigger_pin)
28
+ end
25
29
  end
26
30
  end
data/lib/denko/board.rb CHANGED
@@ -110,6 +110,7 @@ module Denko
110
110
  # Component generating convenience methods. TODO: add more!
111
111
  #
112
112
  def eeprom
113
+ raise StandardError, 'board has no built-in EEPROM, or EEPROM disabled in sketch' if @eeprom_length == 0
113
114
  @eeprom ||= EEPROM::BuiltIn.new(board: self)
114
115
  end
115
116
  end
@@ -28,7 +28,7 @@ module Denko
28
28
  # be skipped, since the other 2 calculations depend on it.
29
29
  #
30
30
  # General formula:
31
- # 2 ^ (n-1), where n is the decimal value of the bits, up to 16x max oversampling.
31
+ # 2 ** (n-1), where n is the decimal value of the bits, up to 16x max oversampling.
32
32
  #
33
33
  OVERSAMPLE_FACTORS = {
34
34
  0 => 0b000, # Sensor skipped. Value will be 0x800000.
@@ -0,0 +1,223 @@
1
+ module Denko
2
+ module Sensor
3
+ class BMP180
4
+ include I2C::Peripheral
5
+ include Behaviors::Poller
6
+
7
+ # Write this to register 0xE0 for soft reset
8
+ SOFT_RESET = 0xB6
9
+
10
+ #
11
+ # Pressure Oversample Setting Values
12
+ #
13
+ # General formula:
14
+ # 2 ** n, where n is the decimal value of the bits, up to 8x pressure oversampling.
15
+ #
16
+ OVERSAMPLE_FACTORS = {
17
+ 1 => 0b00,
18
+ 2 => 0b01,
19
+ 4 => 0b10,
20
+ 8 => 0b11,
21
+ }
22
+
23
+ def before_initialize(options={})
24
+ @i2c_address = 0x77
25
+ super(options)
26
+ end
27
+
28
+ def after_initialize(options={})
29
+ super(options)
30
+
31
+ # Avoid repeated memory allocation for callback data and state.
32
+ @reading = { temperature: nil, pressure: nil }
33
+ self.state = { temperature: nil, pressure: nil }
34
+
35
+ # Default to start conversion off, reading temperature, no pressure oversampling.
36
+ @register = 0b00001110
37
+ @calibration_data_loaded = false
38
+ @oss = 0b00
39
+
40
+ # Temporary storage for raw bytes, since two I2C reads are needed for temperature and pressure.
41
+ @raw_bytes = [0, 0, 0, 0, 0]
42
+
43
+ soft_reset
44
+ end
45
+
46
+ #
47
+ # Configuration Methods
48
+ #
49
+ def soft_reset
50
+ i2c_write(SOFT_RESET)
51
+ end
52
+
53
+ attr_reader :measurement_time
54
+
55
+ def update_measurement_time
56
+ # Get oversample bits from current register setting.
57
+ oversample_exponent = (@register & 0b11000000) >> 6
58
+
59
+ # Calculate time in milliseconds.
60
+ extra_samples = (2 ** oversample_exponent) - 1
61
+ extra_time = extra_samples * 3
62
+ total_time = 4.5 + extra_time
63
+
64
+ # Sleep 1ms extra for safety and convert it to seconds.
65
+ @measurement_time = (total_time + 1) / 1000.0
66
+ end
67
+
68
+ def pressure_samples=(factor)
69
+ raise ArgumentError, "invalid oversampling factor: #{factor}" unless OVERSAMPLE_FACTORS.keys.include? factor
70
+ @oss = OVERSAMPLE_FACTORS[factor]
71
+ end
72
+
73
+ def write_settings
74
+ update_measurement_time
75
+ i2c_write [0xF4, @register]
76
+ end
77
+
78
+ #
79
+ # Reading Methods
80
+ #
81
+ def _start_read_temperature
82
+ @register = 0x2E
83
+ write_settings
84
+ end
85
+
86
+ def _start_read_pressure
87
+ @register = 0x34 | (@oss << 6)
88
+ write_settings
89
+ end
90
+
91
+ def _read
92
+ get_calibration_data unless calibration_data_loaded
93
+
94
+ _start_read_temperature
95
+ sleep(@measurement_time)
96
+ i2c_read 0xF6, 2
97
+
98
+ _start_read_pressure
99
+ sleep(@measurement_time)
100
+ i2c_read 0xF6, 3
101
+ end
102
+
103
+ def pre_callback_filter(data)
104
+ # Temperature is 2 bytes.
105
+ if data.length == 2
106
+ @raw_bytes[0] = data[0]
107
+ @raw_bytes[1] = data[1]
108
+ # Pressure is 3 bytes and triggers callbacks.
109
+ elsif data.length == 3
110
+ @raw_bytes[2] = data[0]
111
+ @raw_bytes[3] = data[1]
112
+ @raw_bytes[4] = data[2]
113
+ return decode_reading(@raw_bytes)
114
+ # Calibration data is 22 bytes.
115
+ elsif data.length == 22
116
+ process_calibration(data)
117
+ end
118
+
119
+ # Anything other than pressure avoids callbacks.
120
+ return nil
121
+ end
122
+
123
+ def update_state(reading)
124
+ # Checking for Hash ignores calibration data and nil.
125
+ if reading.class == Hash
126
+ @state_mutex.synchronize do
127
+ @state[:temperature] = reading[:temperature]
128
+ @state[:pressure] = reading[:pressure]
129
+ end
130
+ end
131
+ end
132
+
133
+ def [](key)
134
+ @state_mutex.synchronize do
135
+ return @state[key]
136
+ end
137
+ end
138
+
139
+ #
140
+ # Decoding Methods
141
+ #
142
+ def decode_reading(bytes)
143
+ temperature, b5 = decode_temperature(bytes)
144
+ @reading[:temperature] = temperature
145
+ @reading[:pressure] = decode_pressure(bytes, b5)
146
+ @reading
147
+ end
148
+
149
+ def decode_temperature(bytes)
150
+ # Temperature is bytes [0..2], MSB first.
151
+ ut = bytes[0] << 8 | bytes[1]
152
+
153
+ # Calibration compensation from datasheet
154
+ x1 = (ut - @calibration[:ac6]) * @calibration[:ac5] / 32768
155
+ x2 = (@calibration[:mc] * 2048) / (x1 + @calibration[:md])
156
+ b5 = x1 + x2
157
+
158
+ # 160 instead of 16 since datasheet calculates to 0.1 C units.
159
+ # Float to force the final value into float, but keep b5 integer for pressure.
160
+ temperature = (b5 + 8) / 160.0
161
+
162
+ # Return temperature and b5 for pressure calculation.
163
+ [temperature, b5]
164
+ end
165
+
166
+ def decode_pressure(bytes, b5)
167
+ # Pressure is bytes [2..3], MSB first.
168
+ up = ((bytes[2] << 16) | (bytes[3] << 8) | (bytes[4])) >> (8 - @oss)
169
+
170
+ # Calibration compensation from datasheet
171
+ b6 = b5 - 4000
172
+ x1 = (@calibration[:b2] * (b6 * b6 / 4096)) / 2048
173
+ x2 = @calibration[:ac2] * b6 / 2048
174
+ x3 = x1 + x2
175
+ b3 = (((@calibration[:ac1]*4 + x3) << @oss) + 2) / 4
176
+ x1 = @calibration[:ac3] * b6 / 8192
177
+ x2 = (@calibration[:b1] * (b6 * b6 / 4096)) / 65536
178
+ x3 = (x1 + x2 + 2) / 4
179
+ b4 = (@calibration[:ac4] * ((x3+32768) & 0xFFFF_FFFF)) / 32768
180
+ b7 = ((up & 0xFFFF_FFFF) - b3) * (50000 >> @oss)
181
+ if (b7 < 0x8000_0000)
182
+ p = (b7 * 2) / b4
183
+ else
184
+ p = (b7 / b4) * 2
185
+ end
186
+ x1 = (p / 256) * (p / 256)
187
+ x1 = (x1 * 3038) / 65536
188
+ x2 = (-7357 * p) / 65536
189
+ p = p + (x1 + x2 + 3791) / 16
190
+ pressure = p.to_f
191
+ end
192
+
193
+ #
194
+ # Calibration Methods
195
+ #
196
+ attr_reader :calibration_data_loaded
197
+
198
+ def get_calibration_data
199
+ # Calibration data is 22 bytes starting at address 0xAA.
200
+ read_using -> { i2c_read(0xAA, 22) }
201
+ end
202
+
203
+ def process_calibration(bytes)
204
+ if bytes
205
+ @calibration = {
206
+ ac1: bytes[0..1].pack('C*').unpack('s>')[0],
207
+ ac2: bytes[2..3].pack('C*').unpack('s>')[0],
208
+ ac3: bytes[4..5].pack('C*').unpack('s>')[0],
209
+ ac4: bytes[6..7].pack('C*').unpack('S>')[0],
210
+ ac5: bytes[8..9].pack('C*').unpack('S>')[0],
211
+ ac6: bytes[10..11].pack('C*').unpack('S>')[0],
212
+ b1: bytes[12..13].pack('C*').unpack('s>')[0],
213
+ b2: bytes[14..15].pack('C*').unpack('s>')[0],
214
+ mb: bytes[16..17].pack('C*').unpack('s>')[0],
215
+ mc: bytes[18..19].pack('C*').unpack('s>')[0],
216
+ md: bytes[20..21].pack('C*').unpack('s>')[0],
217
+ }
218
+ @calibration_data_loaded = true
219
+ end
220
+ end
221
+ end
222
+ end
223
+ end
@@ -0,0 +1,8 @@
1
+ module Denko
2
+ module Sensor
3
+ class GenericPIR < DigitalIO::Input
4
+ alias :on_motion_stop :on_low
5
+ alias :on_motion_start :on_high
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,33 @@
1
+ module Denko
2
+ module Sensor
3
+ class HCSR04
4
+ # Speed of sound in meters per second.
5
+ SPEED_OF_SOUND = 343.0
6
+
7
+ include Behaviors::MultiPin
8
+ include Behaviors::Poller
9
+
10
+ def initialize_pins(options={})
11
+ proxy_pin :trigger, DigitalIO::Output
12
+ proxy_pin :echo, DigitalIO::Input
13
+ end
14
+
15
+ def after_initialize(options={})
16
+ super(options)
17
+
18
+ # Receive values from echo pin.
19
+ echo.add_callback { |data| self.update(data) }
20
+ end
21
+
22
+ def _read
23
+ board.hcsr04_read(echo.pin, trigger.pin)
24
+ end
25
+
26
+ def pre_callback_filter(us)
27
+ # Data is microseconds roundtrip time. Convert to mm.
28
+ um = (us/2) * SPEED_OF_SOUND
29
+ mm = um / 1000.0
30
+ end
31
+ end
32
+ end
33
+ end
@@ -128,8 +128,12 @@ module Denko
128
128
 
129
129
  # Bit 1 of LSB determines type of reading; 0 for temperature, 1 for humidity.
130
130
  if (bytes[1] & 0b00000010) > 0
131
+ # Calculate humidity and limit within 0-100 range.
132
+ humidity = (raw_value.to_f / 524.288) - 6
133
+ humidity = 0.0 if humidity < 0.0
134
+ humidity = 100.0 if humidity > 100.0
131
135
  @reading[0] = :humidity
132
- @reading[1] = (raw_value.to_f / 524.288) - 6
136
+ @reading[1] = humidity
133
137
  @humidity.update(@reading[1])
134
138
  else
135
139
  @reading[0] = :temperature
@@ -138,7 +142,7 @@ module Denko
138
142
  end
139
143
  @reading
140
144
  end
141
-
145
+
142
146
  def update_state(reading)
143
147
  @state_mutex.synchronize do
144
148
  @state[reading[0]] = reading[1]