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.
- 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]
|