denko 0.13.3 → 0.13.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +55 -0
- data/DEPS_IDE.md +3 -2
- data/HARDWARE.md +73 -35
- data/README.md +3 -3
- data/benchmarks/i2c_ssd1306_refresh.rb +5 -0
- data/denko.gemspec +4 -4
- data/examples/advanced/m5_env.rb +48 -0
- data/examples/digital_io/button.rb +13 -0
- data/examples/i2c/search.rb +2 -0
- data/examples/sensor/aht10.rb +7 -3
- data/examples/sensor/aht20.rb +7 -3
- data/examples/sensor/bme280.rb +6 -43
- data/examples/sensor/bmp180.rb +22 -0
- data/examples/sensor/generic_pir.rb +25 -0
- data/examples/sensor/hcsr04.rb +14 -0
- data/examples/sensor/htu21d.rb +1 -1
- data/examples/sensor/htu31d.rb +15 -11
- data/examples/sensor/neat_tph_readings.rb +26 -0
- data/examples/sensor/qmp6988.rb +53 -0
- data/examples/sensor/rcwl9620.rb +15 -0
- data/examples/sensor/sht3x.rb +34 -0
- data/lib/denko/board/pulse.rb +4 -0
- data/lib/denko/board.rb +1 -0
- data/lib/denko/sensor/bme280.rb +1 -1
- data/lib/denko/sensor/bmp180.rb +223 -0
- data/lib/denko/sensor/generic_pir.rb +8 -0
- data/lib/denko/sensor/hcsr04.rb +33 -0
- data/lib/denko/sensor/htu21d.rb +6 -2
- data/lib/denko/sensor/qmp6988.rb +308 -0
- data/lib/denko/sensor/rcwl9620.rb +34 -0
- data/lib/denko/sensor/sht3x.rb +128 -0
- data/lib/denko/sensor.rb +7 -0
- data/lib/denko/version.rb +1 -1
- data/src/denko_wifi.ino +39 -14
- data/src/lib/Denko.cpp +6 -2
- data/src/lib/Denko.h +4 -2
- data/src/lib/DenkoDefines.h +16 -1
- data/src/lib/DenkoLEDArray.cpp +1 -1
- data/src/lib/DenkoPulseInput.cpp +32 -1
- metadata +17 -2
@@ -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
|
data/lib/denko/board/pulse.rb
CHANGED
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
|
data/lib/denko/sensor/bme280.rb
CHANGED
@@ -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
|
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,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
|
data/lib/denko/sensor/htu21d.rb
CHANGED
@@ -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] =
|
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]
|