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,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