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,308 @@
|
|
1
|
+
module Denko
|
2
|
+
module Sensor
|
3
|
+
class QMP6988
|
4
|
+
include I2C::Peripheral
|
5
|
+
include Behaviors::Poller
|
6
|
+
|
7
|
+
UPDATE_TIME = 0.020
|
8
|
+
RESET_REGISTER = 0xE0
|
9
|
+
RESET_COMMAND = 0xE6
|
10
|
+
CTRL_MEAS_REGISTER = 0xF4
|
11
|
+
STANDBY_TIME_REGISTER = 0xF5
|
12
|
+
IIR_REGISTER = 0xF1
|
13
|
+
CONFIG_LENGTH = 5
|
14
|
+
DATA_REGISTER = 0xF7
|
15
|
+
DATA_LENGTH = 6
|
16
|
+
CALIBRATION_REGISTER = 0xA0
|
17
|
+
CALIBRATION_LENGTH = 25
|
18
|
+
CHIP_ID_REGISTER = 0xD1
|
19
|
+
CHIP_ID_LENGTH = 1
|
20
|
+
FORCED_MODE = 0b01
|
21
|
+
NORMAL_MODE = 0b11
|
22
|
+
|
23
|
+
# Standby Times for Normal (Continuous) Mode in milliseconds
|
24
|
+
STANDBY_TIMES = {
|
25
|
+
1 => 0b000,
|
26
|
+
5 => 0b001,
|
27
|
+
50 => 0b010,
|
28
|
+
250 => 0b011,
|
29
|
+
500 => 0b100,
|
30
|
+
1000 => 0b101,
|
31
|
+
2000 => 0b110,
|
32
|
+
4000 => 0b111,
|
33
|
+
}
|
34
|
+
|
35
|
+
#
|
36
|
+
# Oversample Setting Values
|
37
|
+
# Note: Each sensor has a separate oversample setting.
|
38
|
+
#
|
39
|
+
# General formula:
|
40
|
+
# 2 ** (n-1), where n is the decimal value of the bits, up to 16x max oversampling.
|
41
|
+
#
|
42
|
+
OVERSAMPLE_FACTORS = {
|
43
|
+
# 0 => 0b000, # Sensor skipped. Value will be 0x800000.
|
44
|
+
1 => 0b001,
|
45
|
+
2 => 0b010,
|
46
|
+
4 => 0b011,
|
47
|
+
8 => 0b100,
|
48
|
+
16 => 0b101,
|
49
|
+
32 => 0b110,
|
50
|
+
64 => 0b111,
|
51
|
+
}
|
52
|
+
#
|
53
|
+
# Single sample times (in milliseconds) for each sensor, derived from datasheet examples.
|
54
|
+
TEMPERATURE_SAMPLE_TIME = 0.9
|
55
|
+
PRESSURE_SAMPLE_TIME = 0.85
|
56
|
+
|
57
|
+
# IIR Filter Coefficients
|
58
|
+
IIR_COEFFICIENTS = {
|
59
|
+
0 => 0b000,
|
60
|
+
2 => 0b001,
|
61
|
+
4 => 0b010,
|
62
|
+
8 => 0b011,
|
63
|
+
16 => 0b100,
|
64
|
+
32 => 0b101, # 0b110 and 0b111 are also valid for 16.
|
65
|
+
}
|
66
|
+
|
67
|
+
def before_initialize(options={})
|
68
|
+
@i2c_address = 0x70
|
69
|
+
super(options)
|
70
|
+
end
|
71
|
+
|
72
|
+
def after_initialize(options={})
|
73
|
+
super(options)
|
74
|
+
|
75
|
+
# Avoid repeated memory allocation for callback data and state.
|
76
|
+
@reading = { temperature: nil, pressure: nil }
|
77
|
+
self.state = { temperature: nil, pressure: nil }
|
78
|
+
|
79
|
+
reset
|
80
|
+
|
81
|
+
# Get 5 config registers. Copy 0xF4 to modify it for control.
|
82
|
+
get_config_registers
|
83
|
+
@ctrl_meas_register = @registers[:f4].dup
|
84
|
+
|
85
|
+
# Default settings
|
86
|
+
self.iir_coefficient = 0
|
87
|
+
self.temperature_samples = 1
|
88
|
+
self.pressure_samples = 1
|
89
|
+
self.forced_mode
|
90
|
+
|
91
|
+
# self.forced mode triggered an initial measurement so IIR works properly if enabled.
|
92
|
+
# Wait for those values to enter the data registers, but don't read them back.
|
93
|
+
sleep @measurement_time
|
94
|
+
|
95
|
+
get_calibration_data
|
96
|
+
end
|
97
|
+
|
98
|
+
#
|
99
|
+
# Configuration Methods
|
100
|
+
#
|
101
|
+
def reset
|
102
|
+
i2c_write [RESET_REGISTER, RESET_COMMAND]
|
103
|
+
sleep UPDATE_TIME
|
104
|
+
end
|
105
|
+
|
106
|
+
def iir_coefficient=(coeff)
|
107
|
+
raise ArgumentError, "invalid IIR coefficient: #{coeff}" unless IIR_COEFFICIENTS.keys.include? coeff
|
108
|
+
i2c_write [IIR_REGISTER, IIR_COEFFICIENTS[coeff]]
|
109
|
+
@iir_coefficient = coeff
|
110
|
+
end
|
111
|
+
attr_reader :iir_coefficient
|
112
|
+
|
113
|
+
def standby_time=(ms)
|
114
|
+
raise ArgumentError, "invalid standby time: #{ms}" unless self.class::STANDBY_TIMES.keys.include? ms
|
115
|
+
byte = STANDBY_TIMES[ms] << 5
|
116
|
+
@standby_time = ms
|
117
|
+
i2c_write [STANDBY_TIME_REGISTER, byte]
|
118
|
+
sleep UPDATE_TIME
|
119
|
+
end
|
120
|
+
attr_reader :standby_time
|
121
|
+
|
122
|
+
def temperature_samples=(factor)
|
123
|
+
raise ArgumentError, "invalid oversampling factor: #{factor}" unless OVERSAMPLE_FACTORS.keys.include? factor
|
124
|
+
@ctrl_meas_register = (@ctrl_meas_register & 0b00011111) | (OVERSAMPLE_FACTORS[factor] << 5)
|
125
|
+
@temperature_samples = factor
|
126
|
+
calculate_measurement_time
|
127
|
+
i2c_write [CTRL_MEAS_REGISTER, @ctrl_meas_register]
|
128
|
+
sleep UPDATE_TIME
|
129
|
+
end
|
130
|
+
attr_reader :temperature_samples
|
131
|
+
|
132
|
+
def pressure_samples=(factor)
|
133
|
+
raise ArgumentError, "invalid oversampling factor: #{factor}" unless OVERSAMPLE_FACTORS.keys.include? factor
|
134
|
+
@ctrl_meas_register = (@ctrl_meas_register & 0b11100011) | (OVERSAMPLE_FACTORS[factor] << 2)
|
135
|
+
@pressure_samples = factor
|
136
|
+
calculate_measurement_time
|
137
|
+
i2c_write [CTRL_MEAS_REGISTER, @ctrl_meas_register]
|
138
|
+
sleep UPDATE_TIME
|
139
|
+
end
|
140
|
+
attr_reader :pressure_samples
|
141
|
+
|
142
|
+
def calculate_measurement_time
|
143
|
+
@measurement_time = (@temperature_samples.to_i * TEMPERATURE_SAMPLE_TIME) +
|
144
|
+
(@pressure_samples.to_i * PRESSURE_SAMPLE_TIME)
|
145
|
+
# Add 5ms for safety and convert to seconds.
|
146
|
+
@measurement_time = (@measurement_time + 5) * 0.001
|
147
|
+
end
|
148
|
+
|
149
|
+
def forced_mode
|
150
|
+
@ctrl_meas_register = (@ctrl_meas_register & 0b11111100) | FORCED_MODE
|
151
|
+
i2c_write [CTRL_MEAS_REGISTER, @ctrl_meas_register]
|
152
|
+
@forced_mode = true
|
153
|
+
sleep UPDATE_TIME
|
154
|
+
end
|
155
|
+
|
156
|
+
def continuous_mode
|
157
|
+
@ctrl_meas_register = (@ctrl_meas_register & 0b11111100) | NORMAL_MODE
|
158
|
+
i2c_write [CTRL_MEAS_REGISTER, @ctrl_meas_register]
|
159
|
+
@forced_mode = false
|
160
|
+
sleep UPDATE_TIME
|
161
|
+
end
|
162
|
+
|
163
|
+
def chip_id
|
164
|
+
return @chip_id if @chip_id
|
165
|
+
i2c_read(CHIP_ID_REGISTER, 1)
|
166
|
+
sleep 0.001 while !@chip_id
|
167
|
+
@chip_id
|
168
|
+
end
|
169
|
+
|
170
|
+
#
|
171
|
+
# Reading & Processing
|
172
|
+
#
|
173
|
+
def _read
|
174
|
+
if @forced_mode
|
175
|
+
# Write CTRL_MEAS register to trigger reading, then wait for measurement.
|
176
|
+
i2c_write [CTRL_MEAS_REGISTER, @ctrl_meas_register]
|
177
|
+
sleep @measurement_time
|
178
|
+
end
|
179
|
+
|
180
|
+
# Read the data bytes.
|
181
|
+
i2c_read(DATA_REGISTER, DATA_LENGTH)
|
182
|
+
end
|
183
|
+
|
184
|
+
def pre_callback_filter(bytes)
|
185
|
+
if bytes.length == DATA_LENGTH
|
186
|
+
return process_reading(bytes)
|
187
|
+
elsif bytes.length == CONFIG_LENGTH
|
188
|
+
process_config(bytes)
|
189
|
+
elsif bytes.length == CALIBRATION_LENGTH
|
190
|
+
process_calibration(bytes)
|
191
|
+
elsif bytes.length == CHIP_ID_LENGTH
|
192
|
+
@chip_id = bytes[0]
|
193
|
+
end
|
194
|
+
return nil
|
195
|
+
end
|
196
|
+
|
197
|
+
def process_reading(bytes)
|
198
|
+
# Temperature and pressure are 24-bits long each, and need 2^23 subtracted.
|
199
|
+
dt = ((bytes[3] << 16) + (bytes[4] << 8) + bytes[5]) - (0b1 << 23)
|
200
|
+
dp = ((bytes[0] << 16) + (bytes[1] << 8) + bytes[2]) - (0b1 << 23)
|
201
|
+
|
202
|
+
# Compensated temperature calculated in 1/256 of a degree Celsius.
|
203
|
+
tr = @calibration[:a0] +
|
204
|
+
@calibration[:a1] * dt +
|
205
|
+
@calibration[:a2] * (dt ** 2)
|
206
|
+
@reading[:temperature] = tr / 256.0
|
207
|
+
|
208
|
+
# Compensated pressure calculated in Pascals.
|
209
|
+
@reading[:pressure] = @calibration[:b00] +
|
210
|
+
@calibration[:bt1] * tr +
|
211
|
+
@calibration[:bp1] * dp +
|
212
|
+
@calibration[:b11] * (tr * dp) +
|
213
|
+
@calibration[:bt2] * (tr ** 2) +
|
214
|
+
@calibration[:bp2] * (dp ** 2) +
|
215
|
+
@calibration[:b12] * (dp * (tr ** 2)) +
|
216
|
+
@calibration[:b21] * ((dp ** 2) * tr) +
|
217
|
+
@calibration[:bp3] * (dp ** 3)
|
218
|
+
|
219
|
+
# Return reading for callbacks.
|
220
|
+
@reading
|
221
|
+
end
|
222
|
+
|
223
|
+
def update_state(reading)
|
224
|
+
@state_mutex.synchronize do
|
225
|
+
@state[:temperature] = reading[:temperature]
|
226
|
+
@state[:pressure] = reading[:pressure]
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def get_config_registers
|
231
|
+
@registers = {}
|
232
|
+
i2c_read(IIR_REGISTER, CONFIG_LENGTH)
|
233
|
+
sleep 0.001 while @registers.empty?
|
234
|
+
@registers
|
235
|
+
end
|
236
|
+
|
237
|
+
def process_config(bytes)
|
238
|
+
@registers = { f1: bytes[0], f2: bytes[1], f3: bytes[2], f4: bytes[3], f5: bytes[4] }
|
239
|
+
end
|
240
|
+
attr_reader :registers
|
241
|
+
|
242
|
+
#
|
243
|
+
# Calibration
|
244
|
+
#
|
245
|
+
attr_reader :calibration_data_loaded
|
246
|
+
|
247
|
+
CONVERSION_FACTORS = {
|
248
|
+
a1: { A: -6.3e-03, S: 4.3e-04 },
|
249
|
+
a2: { A: -1.9e-11, S: 1.2e-10 },
|
250
|
+
bt1: { A: 1.0e-01, S: 9.1e-02 },
|
251
|
+
bt2: { A: 1.2e-08, S: 1.2e-06 },
|
252
|
+
bp1: { A: 3.3e-02, S: 1.9e-02 },
|
253
|
+
b11: { A: 2.1e-07, S: 1.4e-07 },
|
254
|
+
bp2: { A: -6.3e-10, S: 3.5e-10 },
|
255
|
+
b12: { A: 2.9e-13, S: 7.6e-13 },
|
256
|
+
b21: { A: 2.1e-15, S: 1.2e-14 },
|
257
|
+
bp3: { A: 1.3e-16, S: 7.9e-17 },
|
258
|
+
a0: 16.0,
|
259
|
+
b00: 16.0,
|
260
|
+
}
|
261
|
+
|
262
|
+
def get_calibration_data
|
263
|
+
i2c_read(CALIBRATION_REGISTER, CALIBRATION_LENGTH)
|
264
|
+
end
|
265
|
+
|
266
|
+
def process_calibration(bytes)
|
267
|
+
if bytes
|
268
|
+
# These 2 values are 20-bit instead of 16-bit, so can't combine them with #pack.
|
269
|
+
a0_unsigned = (bytes[18] << 12) + (bytes[19] << 4) + (bytes[24] & 0b00001111)
|
270
|
+
b00_unsigned = (bytes[0] << 12) + (bytes[1] << 4) + ((bytes[24] & 0b11110000) >> 4)
|
271
|
+
|
272
|
+
# Cast the raw bytes as big-endian signed.
|
273
|
+
@calibration_raw = {
|
274
|
+
# Shift these to 32-bit before converting to signed, then reverse the shift after.
|
275
|
+
a0: [(a0_unsigned << 12)].pack('L>').unpack('l>')[0] >> 12,
|
276
|
+
b00: [(b00_unsigned << 12)].pack('L>').unpack('l>')[0] >> 12,
|
277
|
+
|
278
|
+
a1: bytes[20..21].pack('C*').unpack('s>')[0],
|
279
|
+
a2: bytes[22..23].pack('C*').unpack('s>')[0],
|
280
|
+
|
281
|
+
b11: bytes[8..9].pack('C*').unpack('s>')[0],
|
282
|
+
b12: bytes[12..13].pack('C*').unpack('s>')[0],
|
283
|
+
b21: bytes[14..15].pack('C*').unpack('s>')[0],
|
284
|
+
|
285
|
+
bp1: bytes[6..7].pack('C*').unpack('s>')[0],
|
286
|
+
bp2: bytes[10..11].pack('C*').unpack('s>')[0],
|
287
|
+
bp3: bytes[16..17].pack('C*').unpack('s>')[0],
|
288
|
+
|
289
|
+
bt1: bytes[2..3].pack('C*').unpack('s>')[0],
|
290
|
+
bt2: bytes[4..5].pack('C*').unpack('s>')[0],
|
291
|
+
}
|
292
|
+
|
293
|
+
# Use conversion formulae to calculate compensation coefficients, all as floats.
|
294
|
+
@calibration = {}
|
295
|
+
@calibration_raw.keys.each do |key|
|
296
|
+
if CONVERSION_FACTORS[key].class == Float
|
297
|
+
@calibration[key] = @calibration_raw[key] / CONVERSION_FACTORS[key]
|
298
|
+
else
|
299
|
+
@calibration[key] = CONVERSION_FACTORS[key][:A] + (CONVERSION_FACTORS[key][:S] * @calibration_raw[key] / 32767.0)
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
@calibration_data_loaded = true
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Denko
|
2
|
+
module Sensor
|
3
|
+
class RCWL9620
|
4
|
+
include I2C::Peripheral
|
5
|
+
include Behaviors::Poller
|
6
|
+
|
7
|
+
def before_initialize(options={})
|
8
|
+
@i2c_address = 0x57
|
9
|
+
super(options)
|
10
|
+
end
|
11
|
+
|
12
|
+
def _read
|
13
|
+
i2c_write(0x01)
|
14
|
+
sleep(0.120)
|
15
|
+
i2c_read(nil, 3)
|
16
|
+
end
|
17
|
+
|
18
|
+
def pre_callback_filter(bytes)
|
19
|
+
# Data is in micrometers, 3 bytes, big-endian.
|
20
|
+
um = (bytes[0] << 16) + (bytes[1] << 8) + bytes[2]
|
21
|
+
mm = um / 1000.0
|
22
|
+
|
23
|
+
# Limit output between 20 and 4500mm.
|
24
|
+
if mm > 4500.0
|
25
|
+
return 4500.0
|
26
|
+
elsif mm < 20.0
|
27
|
+
return 20.0
|
28
|
+
else
|
29
|
+
return mm
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
module Denko
|
2
|
+
module Sensor
|
3
|
+
class SHT3X
|
4
|
+
include I2C::Peripheral
|
5
|
+
include Behaviors::Poller
|
6
|
+
|
7
|
+
RESET = 0x30A2
|
8
|
+
RESET_TIME = 0.002
|
9
|
+
HEATER_OFF = 0x3066
|
10
|
+
HEATER_ON = 0x306D
|
11
|
+
FETCH_DATA = 0xE000
|
12
|
+
REPEATABILITY = {
|
13
|
+
high: { lsb: 0x00, measurement_time: 0.016 },
|
14
|
+
medium: { lsb: 0x0B, measurement_time: 0.007 },
|
15
|
+
low: { lsb: 0x16, measurement_time: 0.005 },
|
16
|
+
}
|
17
|
+
|
18
|
+
# Unused
|
19
|
+
READ_STATUS_REGISTER = 0xF32D
|
20
|
+
CLEAR_STATUS_REGISTER = 0x3041
|
21
|
+
BREAK = 0x3093
|
22
|
+
ART = 0x2B32
|
23
|
+
|
24
|
+
def before_initialize(options={})
|
25
|
+
@i2c_address = 0x44
|
26
|
+
super(options)
|
27
|
+
end
|
28
|
+
|
29
|
+
def after_initialize(options={})
|
30
|
+
super(options)
|
31
|
+
|
32
|
+
# Avoid repeated memory allocation for callback data and state.
|
33
|
+
@reading = { temperature: nil, humidity: nil }
|
34
|
+
self.state = { temperature: nil, humidity: nil }
|
35
|
+
|
36
|
+
reset
|
37
|
+
self.repeatability = :high
|
38
|
+
end
|
39
|
+
|
40
|
+
def repeatability=(key)
|
41
|
+
raise ArgumentError, "invalid repeatability setting: #{key}" unless REPEATABILITY.keys.include? key
|
42
|
+
@measurement_lsb = REPEATABILITY[key][:lsb]
|
43
|
+
@measurement_time = REPEATABILITY[key][:measurement_time]
|
44
|
+
end
|
45
|
+
|
46
|
+
def _read
|
47
|
+
i2c_write [0x24, @measurement_lsb]
|
48
|
+
sleep(@measurement_time)
|
49
|
+
i2c_read(FETCH_DATA, 6)
|
50
|
+
end
|
51
|
+
|
52
|
+
def pre_callback_filter(bytes)
|
53
|
+
# Temperature is bytes 0 to 2: MSB, LSB, CRC
|
54
|
+
if calculate_crc(bytes[0..2]) == bytes[2]
|
55
|
+
t_raw = (bytes[0] << 8) | bytes[1]
|
56
|
+
@reading[:temperature] = (175 * t_raw / 65535.0) - 45
|
57
|
+
else
|
58
|
+
@reading[:temperature] = nil
|
59
|
+
end
|
60
|
+
|
61
|
+
# Humidity is bytes 3 to 5: MSB, LSB, CRC
|
62
|
+
if calculate_crc(bytes[3..5]) == bytes[5]
|
63
|
+
h_raw = (bytes[3] << 8) | bytes[4]
|
64
|
+
@reading[:humidity] = 100 * h_raw / 65535.0
|
65
|
+
else
|
66
|
+
@reading[:humidity] = nil
|
67
|
+
end
|
68
|
+
|
69
|
+
@reading
|
70
|
+
end
|
71
|
+
|
72
|
+
def update_state(reading)
|
73
|
+
@state_mutex.synchronize do
|
74
|
+
@state[:temperature] = reading[:temperature]
|
75
|
+
@state[:humidity] = reading[:humidity]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def reset
|
80
|
+
i2c_write [RESET]
|
81
|
+
sleep RESET_TIME
|
82
|
+
@heater_on = false
|
83
|
+
end
|
84
|
+
|
85
|
+
def heater_on?
|
86
|
+
@heater_on
|
87
|
+
end
|
88
|
+
|
89
|
+
def heater_off?
|
90
|
+
!@heater_on
|
91
|
+
end
|
92
|
+
|
93
|
+
def heater_on
|
94
|
+
i2c_write [HEATER_ON]
|
95
|
+
@heater_on = true
|
96
|
+
end
|
97
|
+
|
98
|
+
def heater_off
|
99
|
+
i2c_write [HEATER_OFF]
|
100
|
+
@heater_on = false
|
101
|
+
end
|
102
|
+
|
103
|
+
# CRC is same as AHT20 sensor. Copied from that file.
|
104
|
+
CRC_INITIAL_VALUE = 0xFF
|
105
|
+
CRC_POLYNOMIAL = 0x31
|
106
|
+
MSBIT_MASK = 0x80
|
107
|
+
|
108
|
+
def calculate_crc(bytes)
|
109
|
+
crc = CRC_INITIAL_VALUE
|
110
|
+
|
111
|
+
# Ignore last byte. That's the CRC value to compare with.
|
112
|
+
bytes.take(bytes.length - 1).each do |byte|
|
113
|
+
crc = crc ^ byte
|
114
|
+
8.times do
|
115
|
+
if (crc & MSBIT_MASK) > 0
|
116
|
+
crc = (crc << 1) ^ CRC_POLYNOMIAL
|
117
|
+
else
|
118
|
+
crc = crc << 1
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# Limit CRC size to 8 bits.
|
124
|
+
crc = crc & 0xFF
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
data/lib/denko/sensor.rb
CHANGED
@@ -4,10 +4,17 @@ module Denko
|
|
4
4
|
autoload :Humidity, "#{__dir__}/sensor/virtual"
|
5
5
|
autoload :DHT, "#{__dir__}/sensor/dht"
|
6
6
|
autoload :DS18B20, "#{__dir__}/sensor/ds18b20"
|
7
|
+
autoload :BMP180, "#{__dir__}/sensor/bmp180"
|
7
8
|
autoload :BME280, "#{__dir__}/sensor/bme280"
|
9
|
+
autoload :BMP280, "#{__dir__}/sensor/bme280"
|
8
10
|
autoload :HTU21D, "#{__dir__}/sensor/htu21d"
|
9
11
|
autoload :HTU31D, "#{__dir__}/sensor/htu31d"
|
10
12
|
autoload :AHT10, "#{__dir__}/sensor/aht"
|
11
13
|
autoload :AHT20, "#{__dir__}/sensor/aht"
|
14
|
+
autoload :SHT3X, "#{__dir__}/sensor/sht3x"
|
15
|
+
autoload :QMP6988, "#{__dir__}/sensor/qmp6988"
|
16
|
+
autoload :RCWL9620, "#{__dir__}/sensor/rcwl9620"
|
17
|
+
autoload :HCSR04, "#{__dir__}/sensor/hcsr04"
|
18
|
+
autoload :GenericPIR, "#{__dir__}/sensor/generic_pir"
|
12
19
|
end
|
13
20
|
end
|
data/lib/denko/version.rb
CHANGED
data/src/denko_wifi.ino
CHANGED
@@ -30,8 +30,8 @@
|
|
30
30
|
#elif defined(ESP32)
|
31
31
|
#include <WiFi.h>
|
32
32
|
#include <ESPmDNS.h>
|
33
|
-
|
34
|
-
|
33
|
+
#include <WiFiUdp.h>
|
34
|
+
#include <ArduinoOTA.h>
|
35
35
|
#define WIFI_STATUS_LED 2
|
36
36
|
#else
|
37
37
|
#define WIFI_STATUS_LED 13
|
@@ -48,13 +48,13 @@
|
|
48
48
|
#endif
|
49
49
|
|
50
50
|
// Configure your WiFi options here. IP address is not configurable. Uses DHCP.
|
51
|
-
|
52
|
-
|
53
|
-
|
51
|
+
#define DENKO_TCP_PORT 3466
|
52
|
+
#define WIFI_SSID "yourNetwork"
|
53
|
+
#define WIFI_PASSWORD "yourPassword"
|
54
54
|
boolean connected = false;
|
55
55
|
|
56
56
|
Denko denko;
|
57
|
-
WiFiServer server(
|
57
|
+
WiFiServer server(DENKO_TCP_PORT);
|
58
58
|
WiFiClient client;
|
59
59
|
|
60
60
|
// Use the built in LED to indicate WiFi status.
|
@@ -82,7 +82,7 @@ void printWifiStatus() {
|
|
82
82
|
DENKO_SERIAL_IF.println(WiFi.localIP());
|
83
83
|
#endif
|
84
84
|
DENKO_SERIAL_IF.print("Denko TCP Port: ");
|
85
|
-
DENKO_SERIAL_IF.println(
|
85
|
+
DENKO_SERIAL_IF.println(DENKO_TCP_PORT);
|
86
86
|
indicateWiFi(true);
|
87
87
|
}
|
88
88
|
|
@@ -94,7 +94,7 @@ void connect(){
|
|
94
94
|
|
95
95
|
// Try to connect.
|
96
96
|
DENKO_SERIAL_IF.print("Connecting to WiFi ");
|
97
|
-
WiFi.begin(
|
97
|
+
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
|
98
98
|
|
99
99
|
// Delay until connected.
|
100
100
|
while (WiFi.status() != WL_CONNECTED) {
|
@@ -117,14 +117,39 @@ void setup() {
|
|
117
117
|
DENKO_SERIAL_IF.begin(115200);
|
118
118
|
while(!DENKO_SERIAL_IF);
|
119
119
|
|
120
|
-
//
|
121
|
-
|
120
|
+
// Attempt initial WiFi connection.
|
121
|
+
connect();
|
122
|
+
|
123
|
+
// Enable over the air updates on the ESP8266 and ESP32.
|
124
|
+
// Taken from standard ESP8266/ESP32 OTA examples.
|
125
|
+
#if defined(ESP8266) || (ESP32)
|
126
|
+
ArduinoOTA.onStart([]() {
|
127
|
+
String type;
|
128
|
+
if (ArduinoOTA.getCommand() == U_FLASH) {
|
129
|
+
type = "sketch";
|
130
|
+
} else { // U_FS (ESP8266) or U_SPIFFS (ESP32)
|
131
|
+
type = "filesystem";
|
132
|
+
}
|
133
|
+
// NOTE: if updating FS or SPIFFS, this would be the place to unmount using FS.end() or SPIFFS.end()
|
134
|
+
Serial.println("Arduino OTA: Start updating " + type);
|
135
|
+
});
|
136
|
+
ArduinoOTA.onEnd([]() {
|
137
|
+
Serial.println("\nArduino OTA: End\n");
|
138
|
+
});
|
139
|
+
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
|
140
|
+
Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
|
141
|
+
});
|
142
|
+
ArduinoOTA.onError([](ota_error_t error) {
|
143
|
+
Serial.printf("Error[%u]: ", error);
|
144
|
+
if (error == OTA_AUTH_ERROR) Serial.println("Arduino OTA: Auth Failed");
|
145
|
+
else if (error == OTA_BEGIN_ERROR) Serial.println("Arduino OTA: Begin Failed");
|
146
|
+
else if (error == OTA_CONNECT_ERROR) Serial.println("Arduino OTA: Connect Failed");
|
147
|
+
else if (error == OTA_RECEIVE_ERROR) Serial.println("Arduino OTA: Receive Failed");
|
148
|
+
else if (error == OTA_END_ERROR) Serial.println("Arduino OTA: End Failed");
|
149
|
+
});
|
122
150
|
ArduinoOTA.begin();
|
123
151
|
#endif
|
124
152
|
|
125
|
-
// Attempt initial WiFi connection.
|
126
|
-
connect();
|
127
|
-
|
128
153
|
// Start the denko TCP server.
|
129
154
|
server.begin();
|
130
155
|
|
@@ -141,7 +166,7 @@ void loop() {
|
|
141
166
|
maintainWiFi();
|
142
167
|
|
143
168
|
// Handle OTA updates.
|
144
|
-
#if defined(ESP8266)
|
169
|
+
#if defined(ESP8266) || (ESP32)
|
145
170
|
ArduinoOTA.handle();
|
146
171
|
#endif
|
147
172
|
|
data/src/lib/Denko.cpp
CHANGED
@@ -3,6 +3,9 @@
|
|
3
3
|
*/
|
4
4
|
#include "Denko.h"
|
5
5
|
#include "BoardMap.h"
|
6
|
+
#ifdef DENKO_EEPROM
|
7
|
+
#include "EEPROM.h"
|
8
|
+
#endif
|
6
9
|
|
7
10
|
Denko::Denko(){
|
8
11
|
messageFragments[0] = cmdStr;
|
@@ -119,7 +122,8 @@ void Denko::process() {
|
|
119
122
|
#endif
|
120
123
|
|
121
124
|
// Implemented in DenkoPulseInput.cpp
|
122
|
-
case 9:
|
125
|
+
case 9: pulseRead (); break;
|
126
|
+
case 20: hcsr04Read (); break;
|
123
127
|
|
124
128
|
// Implemented in DenkoServo.cpp
|
125
129
|
#ifdef DENKO_SERVO
|
@@ -275,7 +279,7 @@ void Denko::handshake() {
|
|
275
279
|
stream->print(',');
|
276
280
|
#if defined(EEPROM_EMULATED)
|
277
281
|
stream->print(EMULATED_EEPROM_LENGTH);
|
278
|
-
#elif defined(
|
282
|
+
#elif defined(DENKO_EEPROM)
|
279
283
|
stream->print(EEPROM.length());
|
280
284
|
#endif
|
281
285
|
|
data/src/lib/Denko.h
CHANGED
@@ -95,11 +95,13 @@ class Denko {
|
|
95
95
|
void eepromRead (); //cmd = 7
|
96
96
|
void eepromWrite (); //cmd = 8
|
97
97
|
|
98
|
-
//
|
98
|
+
// Pulse inputs (DHT and HC-SR04)
|
99
99
|
void pulseRead (); //cmd = 9
|
100
|
+
void hcsr04Read (); //cmd = 20
|
101
|
+
|
102
|
+
// Servos
|
100
103
|
void servoToggle (); //cmd = 10
|
101
104
|
void servoWrite (); //cmd = 11
|
102
|
-
void handleSerial (); //cmd = 12
|
103
105
|
|
104
106
|
// Single Bit Bang UART
|
105
107
|
#ifdef DENKO_UART_BB
|
data/src/lib/DenkoDefines.h
CHANGED
@@ -134,9 +134,24 @@
|
|
134
134
|
// Best performance acknowledging at 64 bytes, or 32 if buffer is only 64.
|
135
135
|
//
|
136
136
|
// These are 256/64 regardless of whether native USB CDC or UART bridge.
|
137
|
-
#if defined(ARDUINO_ARCH_RP2040) ||
|
137
|
+
#if defined(ARDUINO_ARCH_RP2040) || defined(ESP8266) || defined(__SAM3X8E__)
|
138
138
|
#define DENKO_SERIAL_BUFFER_SIZE 256
|
139
139
|
#define DENKO_RX_ACK_INTERVAL 64
|
140
|
+
// ESP32 defaults to 256 buffer. Stay one under.
|
141
|
+
#elif defined(ESP32)
|
142
|
+
#define DENKO_SERIAL_BUFFER_SIZE 255
|
143
|
+
#ifdef ARDUINO_USB_CDC_ON_BOOT
|
144
|
+
// S2 unreliable with acknowledgement before buffer is full.
|
145
|
+
#ifdef CONFIG_IDF_TARGET_ESP32S2
|
146
|
+
#define DENKO_RX_ACK_INTERVAL 255
|
147
|
+
// S3 and C3 are fine acknowledging at half buffer filled.
|
148
|
+
#else
|
149
|
+
#define DENKO_RX_ACK_INTERVAL 128
|
150
|
+
#endif
|
151
|
+
// Default to 64 if using a UART bridge.
|
152
|
+
#else
|
153
|
+
#define DENKO_RX_ACK_INTERVAL 64
|
154
|
+
#endif
|
140
155
|
// RA4M1 has a 512 Serial buffer.
|
141
156
|
#elif defined(_RENESAS_RA_)
|
142
157
|
#define DENKO_SERIAL_BUFFER_SIZE 512
|
data/src/lib/DenkoLEDArray.cpp
CHANGED