denko 0.13.3 → 0.13.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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]