Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
co2ampel
ampel-firmware
Commits
566eb489
Commit
566eb489
authored
May 10, 2021
by
Eric Duminil
Browse files
Merge branch 'develop'
parents
aa5e2240
a5807d86
Pipeline
#3776
passed with stage
in 1 minute and 45 seconds
Changes
14
Pipelines
2
Hide whitespace changes
Inline
Side-by-side
ampel-firmware/ampel-firmware.ino
View file @
566eb489
...
...
@@ -56,17 +56,6 @@
* and define your credentials and parameters in 'config.h'.
*/
/*****************************************************************
* PreInit *
*****************************************************************/
void
preinit
()
{
#if !defined(AMPEL_WIFI) && defined(ESP8266)
// WiFi would be initialized otherwise (on ESP8266), even if unused.
// see https://github.com/esp8266/Arduino/issues/2111#issuecomment-224251391
ESP8266WiFiClass
::
preinitWiFiOff
();
#endif
}
/*****************************************************************
* Setup *
*****************************************************************/
...
...
@@ -78,14 +67,17 @@ void setup() {
pinMode
(
0
,
INPUT
);
// Flash button (used for forced calibration)
led_effects
::
setupRing
();
sensor
::
initialize
();
Serial
.
println
();
Serial
.
print
(
F
(
"Sensor ID: "
));
Serial
.
println
(
ampel
.
sensorId
);
Serial
.
print
(
F
(
"Board : "
));
Serial
.
println
(
ampel
.
board
);
Serial
.
print
(
F
(
"Firmware : "
));
Serial
.
println
(
ampel
.
version
);
led_effects
::
setupRing
();
sensor
::
initialize
();
#ifdef AMPEL_CSV
csv_writer
::
initialize
(
ampel
.
sensorId
);
...
...
@@ -132,7 +124,6 @@ void checkSerialInput();
/*****************************************************************
* Main loop *
*****************************************************************/
void
loop
()
{
#if defined(AMPEL_LORAWAN) && defined(ESP32)
//LMIC Library seems to be very sensitive to timing issues, so run it first.
...
...
ampel-firmware/co2_sensor.cpp
View file @
566eb489
#include
"co2_sensor.h"
namespace
config
{
//
V
alues should be defined in config.h
uint16_t
measurement_timestep
=
MEASUREMENT_TIMESTEP
;
// [s] Value between 2 and 1800 (range for SCD30 sensor)
//
UPPERCASE v
alues should be defined in config.h
uint16_t
measurement_timestep
=
MEASUREMENT_TIMESTEP
;
// [s] Value between 2 and 1800 (range for SCD30 sensor)
.
const
uint16_t
altitude_above_sea_level
=
ALTITUDE_ABOVE_SEA_LEVEL
;
// [m]
uint16_t
co2_calibration_level
=
ATMOSPHERIC_CO2_CONCENTRATION
;
// [ppm]
int8_t
max_deviation_during_calibration
=
30
;
// [ppm]
int8_t
enough_stable_measurements
=
60
;
const
uint16_t
measurement_timestep_bootup
=
5
;
// [s] Measurement timestep during acclimatization.
const
uint8_t
max_deviation_during_bootup
=
20
;
// [%]
const
int8_t
max_deviation_during_calibration
=
30
;
// [ppm]
const
int16_t
timestep_during_calibration
=
10
;
// [s] WARNING: Measurements can be unreliable for timesteps shorter than 10s.
const
int8_t
stable_measurements_before_calibration
=
120
/
timestep_during_calibration
;
// [-] Stable measurements during at least 2 minutes.
#ifdef TEMPERATURE_OFFSET
// Residual heat from CO2 sensor seems to be high enough to change the temperature reading. How much should it be offset?
// NOTE: Sign isn't relevant. The returned temperature will always be shifted down.
...
...
@@ -28,31 +32,27 @@ namespace sensor {
/**
* Define sensor states
* INITIAL -> initial state
* BOOTUP -> state after initializing the sensor, i.e. after scd.begin()
* BOOTUP -> initial state, until first >0 ppm values are returned
* READY -> sensor does output valid information (> 0 ppm) and no other condition takes place
* NEEDS_CALIBRATION -> sensor measurements are too low (< 250 ppm)
* PREPARE_CALIBRATION -> forced calibration was initiated,
waiting for stable measurements
* CALIBRATION
-> the sensor does calibrate itself
* PREPARE_CALIBRATION
_UNSTABLE
-> forced calibration was initiated,
last measurements were too far apart
*
PREPARE_
CALIBRATION
_STABLE -> forced calibration was initiated, last measurements were close to each others
*/
enum
state
{
INITIAL
,
BOOTUP
,
READY
,
NEEDS_CALIBRATION
,
PREPARE_CALIBRATION_UNSTABLE
,
PREPARE_CALIBRATION_STABLE
,
CALIBRATION
PREPARE_CALIBRATION_STABLE
};
const
char
*
state_names
[]
=
{
"INITIAL"
,
"BOOTUP"
,
"READY"
,
"NEEDS_CALIBRATION"
,
"PREPARE_CALIBRATION_UNSTABLE"
,
"PREPARE_CALIBRATION_STABLE"
,
"CALIBRATION"
};
state
current_state
=
INITIAL
;
"PREPARE_CALIBRATION_STABLE"
};
state
current_state
=
BOOTUP
;
void
switchState
(
state
);
void
initialize
()
{
...
...
@@ -78,47 +78,51 @@ namespace sensor {
ESP
.
restart
();
}
switchState
(
BOOTUP
);
// SCD30 has its own timer.
//NOTE: The timer seems to be inaccurate, though, possibly depending on voltage. Should it be offset?
Serial
.
print
(
F
(
"Setting SCD30 timestep to "
));
Serial
.
print
(
config
::
measurement_timestep
);
Serial
.
println
(
" s."
);
scd30
.
setMeasurementInterval
(
config
::
measurement_timestep
);
// [s]
// Changes of the SCD30's measurement timestep do not come into effect
// before the next measurement takes place. That means that after a hard reset
// of the ESP the SCD30 sometimes needs a long time until switching back to 2 s
// for acclimatization. Resetting it after startup seems to fix this behaviour.
scd30
.
reset
();
Serial
.
print
(
F
(
"Setting temperature offset to -"
));
Serial
.
print
(
abs
(
config
::
temperature_offset
));
Serial
.
println
(
" K."
);
Serial
.
println
(
F
(
" K."
)
)
;
scd30
.
setTemperatureOffset
(
abs
(
config
::
temperature_offset
));
// setTemperatureOffset only accepts positive numbers, but shifts the temperature down.
delay
(
100
);
Serial
.
print
(
F
(
"Temperature offset is : -"
));
Serial
.
print
(
scd30
.
getTemperatureOffset
());
Serial
.
println
(
" K"
);
Serial
.
println
(
F
(
" K"
)
)
;
Serial
.
print
(
F
(
"Auto-calibration is "
));
Serial
.
println
(
config
::
auto_calibrate_sensor
?
"ON."
:
"OFF."
);
sensor_console
::
defineIntCommand
(
"co2"
,
setCO2forDebugging
,
F
(
" 1500 (Sets co2 level, for debugging purposes)"
));
sensor_console
::
defineIntCommand
(
"timer"
,
setTimer
,
F
(
" 30 (Sets measurement interval, in s)"
));
sensor_console
::
defineCommand
(
"calibrate"
,
startCalibrationProcess
,
F
(
" (Starts calibration process)"
));
// SCD30 has its own timer.
//NOTE: The timer seems to be inaccurate, though, possibly depending on voltage. Should it be offset?
Serial
.
println
();
Serial
.
print
(
F
(
"Setting SCD30 timestep to "
));
Serial
.
print
(
config
::
measurement_timestep_bootup
);
Serial
.
println
(
F
(
" s during acclimatization."
));
scd30
.
setMeasurementInterval
(
config
::
measurement_timestep_bootup
);
// [s]
sensor_console
::
defineIntCommand
(
"co2"
,
setCO2forDebugging
,
F
(
"1500 (Sets co2 level, for debugging purposes)"
));
sensor_console
::
defineIntCommand
(
"timer"
,
setTimer
,
F
(
"30 (Sets measurement interval, in s)"
));
sensor_console
::
defineCommand
(
"calibrate"
,
startCalibrationProcess
,
F
(
"(Starts calibration process)"
));
sensor_console
::
defineIntCommand
(
"calibrate"
,
calibrateSensorToSpecificPPM
,
F
(
"
600 (Starts calibration process, to given ppm)"
));
F
(
"600 (Starts calibration process, to given ppm)"
));
sensor_console
::
defineIntCommand
(
"calibrate!"
,
calibrateSensorRightNow
,
F
(
" 600 (Calibrates right now, to given ppm)"
));
sensor_console
::
defineIntCommand
(
"auto_calibrate"
,
setAutoCalibration
,
F
(
" 0/1 (Disables/enables autocalibration)"
));
F
(
"600 (Calibrates right now, to given ppm)"
));
sensor_console
::
defineIntCommand
(
"auto_calibrate"
,
setAutoCalibration
,
F
(
"0/1 (Disables/enables autocalibration)"
));
}
//NOTE: should timer deviation be used to adjust measurement_timestep?
void
checkTimerDeviation
()
{
static
int
32
_t
previous_measurement_at
=
0
;
int32_t
now
=
millis
(
);
Serial
.
print
(
F
(
"Measurement time offset : "
))
;
Serial
.
print
(
now
-
previous_measurement_at
-
config
::
measurement_timestep
*
1000
);
Serial
.
println
(
" ms."
);
p
re
vious_measurement_at
=
now
;
bool
hasSensorSettled
()
{
static
uint16_t
last_co2
=
0
;
u
int
16
_t
delta
;
delta
=
abs
(
co2
-
last_co2
);
last_co2
=
co2
;
// We assume the sensor has acclimated to the environment if measurements
// change less than a specified percentage of the current value.
re
turn
(
co2
>
0
&&
delta
<
((
uint32_t
)
co2
*
config
::
max_deviation_during_bootup
/
100
))
;
}
bool
countStableMeasurements
()
{
...
...
@@ -128,30 +132,32 @@ namespace sensor {
&&
co2
<
(
previous_co2
+
config
::
max_deviation_during_calibration
))
{
stable_measurements
++
;
Serial
.
print
(
F
(
"Number of stable measurements : "
));
Serial
.
println
(
stable_measurements
);
Serial
.
print
(
stable_measurements
);
Serial
.
print
(
F
(
" / "
));
Serial
.
println
(
config
::
stable_measurements_before_calibration
);
switchState
(
PREPARE_CALIBRATION_STABLE
);
}
else
{
stable_measurements
=
0
;
switchState
(
PREPARE_CALIBRATION_UNSTABLE
);
}
previous_co2
=
co2
;
return
(
stable_measurements
==
config
::
enough_
stable_measurements
);
return
(
stable_measurements
==
config
::
stable_measurements
_before_calibration
);
}
void
startCalibrationProcess
()
{
/** From the sensor documentation:
* For best results, the sensor has to be run in a stable environment in continuous mode at
* a measurement rate of 2s for at least two minutes before applying the FRC command and sending the reference value.
* Before applying FRC, SCD30 needs to be operated for 2 minutes with the desired measurement period in continuous mode.
*/
Serial
.
println
(
F
(
"Setting SCD30 timestep to 2s, prior to calibration."
));
scd30
.
setMeasurementInterval
(
2
);
// [s] The change will only take effect after next measurement.
Serial
.
print
(
F
(
"Setting SCD30 timestep to "
));
Serial
.
print
(
config
::
timestep_during_calibration
);
Serial
.
println
(
F
(
"s, prior to calibration."
));
scd30
.
setMeasurementInterval
(
config
::
timestep_during_calibration
);
// [s] The change will only take effect after next measurement.
Serial
.
println
(
F
(
"Waiting until the measurements are stable for at least 2 minutes."
));
Serial
.
println
(
F
(
"It could take a very long time."
));
switchState
(
PREPARE_CALIBRATION_UNSTABLE
);
}
void
calibrateAndRestart
()
{
switchState
(
CALIBRATION
);
Serial
.
print
(
F
(
"Calibrating SCD30 now..."
));
scd30
.
setAltitudeCompensation
(
config
::
altitude_above_sea_level
);
scd30
.
setForcedRecalibrationFactor
(
config
::
co2_calibration_level
);
...
...
@@ -177,19 +183,28 @@ namespace sensor {
if
(
config
::
debug_sensor_states
)
{
Serial
.
print
(
F
(
"Changing sensor state: "
));
Serial
.
print
(
state_names
[
current_state
]);
Serial
.
print
(
" -> "
);
Serial
.
print
(
F
(
" -> "
)
)
;
Serial
.
println
(
state_names
[
new_state
]);
}
current_state
=
new_state
;
}
void
switchStateForCurrentPPM
()
{
if
(
co2
==
0
)
{
// NOTE: Data is available, but it's sometimes erroneous: the sensor outputs
// zero ppm but non-zero temperature and non-zero humidity.
Serial
.
println
(
F
(
"Invalid sensor data - CO2 concentration supposedly 0 ppm"
));
switchState
(
BOOTUP
);
}
else
if
((
current_state
==
PREPARE_CALIBRATION_UNSTABLE
)
||
(
current_state
==
PREPARE_CALIBRATION_STABLE
))
{
if
(
current_state
==
BOOTUP
)
{
if
(
!
hasSensorSettled
())
{
return
;
}
switchState
(
READY
);
Serial
.
println
(
F
(
"Sensor acclimatization finished."
));
Serial
.
print
(
F
(
"Setting SCD30 timestep to "
));
Serial
.
print
(
config
::
measurement_timestep
);
Serial
.
println
(
F
(
" s."
));
if
(
config
::
measurement_timestep
<
10
)
{
Serial
.
println
(
F
(
"WARNING: Timesteps shorter than 10s can lead to unreliable measurements!"
));
}
scd30
.
setMeasurementInterval
(
config
::
measurement_timestep
);
// [s]
}
if
((
current_state
==
PREPARE_CALIBRATION_UNSTABLE
)
||
(
current_state
==
PREPARE_CALIBRATION_STABLE
))
{
// Check for pre-calibration states first, because we do not want to
// leave them before calibration is done.
bool
ready_for_calibration
=
countStableMeasurements
();
...
...
@@ -236,8 +251,6 @@ namespace sensor {
case
PREPARE_CALIBRATION_STABLE
:
led_effects
::
showWaitingLED
(
color
::
green
);
break
;
case
CALIBRATION
:
// Nothing to do, will restart soon.
break
;
default:
Serial
.
println
(
F
(
"Encountered unknown sensor state"
));
// This should not happen.
}
...
...
@@ -250,7 +263,6 @@ namespace sensor {
bool
freshData
=
scd30
.
dataAvailable
();
if
(
freshData
)
{
// checkTimerDeviation();
ntp
::
getLocalTime
(
timestamp
);
co2
=
scd30
.
getCO2
();
temperature
=
scd30
.
getTemperature
();
...
...
@@ -264,7 +276,9 @@ namespace sensor {
showState
();
return
freshData
;
// Report data for further processing only if the data is reliable
// (state 'READY') or manual calibration is necessary (state 'NEEDS_CALIBRATION').
return
freshData
&&
(
current_state
==
READY
||
current_state
==
NEEDS_CALIBRATION
);
}
/*****************************************************************
...
...
@@ -288,8 +302,8 @@ namespace sensor {
if
(
timestep
>=
2
&&
timestep
<=
1800
)
{
Serial
.
print
(
F
(
"Setting Measurement Interval to : "
));
Serial
.
print
(
timestep
);
Serial
.
println
(
"s."
);
sensor
::
scd30
.
setMeasurementInterval
(
timestep
);
Serial
.
println
(
F
(
"s."
)
)
;
scd30
.
setMeasurementInterval
(
timestep
);
config
::
measurement_timestep
=
timestep
;
led_effects
::
showKITTWheel
(
color
::
green
,
1
);
}
...
...
@@ -300,13 +314,18 @@ namespace sensor {
Serial
.
print
(
F
(
"Force calibration, at "
));
config
::
co2_calibration_level
=
calibrationLevel
;
Serial
.
print
(
config
::
co2_calibration_level
);
Serial
.
println
(
" ppm."
);
sensor
::
startCalibrationProcess
();
Serial
.
println
(
F
(
" ppm."
)
)
;
startCalibrationProcess
();
}
}
void
calibrateSensorRightNow
(
int32_t
calibrationLevel
)
{
stable_measurements
=
config
::
enough_stable_measurements
;
calibrateSensorToSpecificPPM
(
calibrationLevel
);
if
(
calibrationLevel
>=
400
&&
calibrationLevel
<=
2000
)
{
Serial
.
print
(
F
(
"Force calibration, right now, at "
));
config
::
co2_calibration_level
=
calibrationLevel
;
Serial
.
print
(
config
::
co2_calibration_level
);
Serial
.
println
(
F
(
" ppm."
));
calibrateAndRestart
();
}
}
}
ampel-firmware/config.public.h
View file @
566eb489
...
...
@@ -27,7 +27,9 @@
*/
// How often should measurement be performed, and displayed?
//NOTE: SCD30 timer does not seem to be very precise. Variations may occur.
//WARNING: On some sensors, measurements become very unreliable when timestep is set to 2s.
//NOTE: 10s or longer should be fine in order to get reliable results.
//NOTE: SCD30 timer does not seem to be very precise. Time variations may occur.
# define MEASUREMENT_TIMESTEP 60 // [s] Value between 2 and 1800 (range for SCD30 sensor)
// How often should measurements be appended to CSV ?
...
...
@@ -64,6 +66,8 @@
// MIN_BRIGHTNESS, if defined, should be between 0 and MAX_BRIGHTNESS - 1
// If MIN_BRIGHTNESS is not set, or if it is set to MAX_BRIGHTNESS, breathing is disabled.
# define MIN_BRIGHTNESS 60
// How many LEDs in the ring? 12 and 16 are currently supported. If undefined, 12 is used as default.
# define LED_COUNT 12
/**
* WEB SERVER
...
...
@@ -101,7 +105,7 @@
*/
# define ALLOW_MQTT_COMMANDS false
// How often measurements
should
be sent to MQTT server?
// How often
should
measurements be sent to MQTT server?
// Probably a good idea to use a multiple of MEASUREMENT_TIMESTEP, so that averages can be calculated
// Set to 0 if you want to send values after each measurement
// # define MQTT_SENDING_INTERVAL MEASUREMENT_TIMESTEP * 5 // [s]
...
...
ampel-firmware/csv_writer.cpp
View file @
566eb489
...
...
@@ -118,9 +118,9 @@ namespace csv_writer {
showFilesystemContent
();
Serial
.
println
();
sensor_console
::
defineIntCommand
(
"csv"
,
setCSVinterval
,
F
(
"
60 (Sets CSV writing interval, in s)"
));
sensor_console
::
defineCommand
(
"format_filesystem"
,
formatFilesystem
,
F
(
"
(Deletes the whole filesystem)"
));
sensor_console
::
defineCommand
(
"show_csv"
,
showCSVContent
,
F
(
"
(Displays the complete CSV file on Serial)"
));
sensor_console
::
defineIntCommand
(
"csv"
,
setCSVinterval
,
F
(
"60 (Sets CSV writing interval, in s)"
));
sensor_console
::
defineCommand
(
"format_filesystem"
,
formatFilesystem
,
F
(
"(Deletes the whole filesystem)"
));
sensor_console
::
defineCommand
(
"show_csv"
,
showCSVContent
,
F
(
"(Displays the complete CSV file on Serial)"
));
}
File
openOrCreate
()
{
...
...
ampel-firmware/led_effects.cpp
View file @
566eb489
...
...
@@ -12,8 +12,28 @@ namespace config {
const
uint8_t
brightness_amplitude
=
config
::
max_brightness
-
config
::
min_brightness
;
const
int
kitt_tail
=
3
;
// How many dimmer LEDs follow in K.I.T.T. wheel
const
uint16_t
poor_air_quality_ppm
=
1600
;
// Above this threshold, LED breathing effect is faster.
//NOTE: Use a class instead? NightMode could then be another state.
bool
night_mode
=
false
;
bool
night_mode
=
false
;
//NOTE: Use a class instead? NightMode could then be another state.
#if !defined(LED_COUNT)
# define LED_COUNT 12
#endif
const
uint16_t
led_count
=
LED_COUNT
;
#if LED_COUNT == 12
//NOTE: One value has been prepended, to make calculations easier and avoid out of bounds index.
const
uint16_t
co2_ticks
[
led_count
+
1
]
=
{
0
,
500
,
600
,
700
,
800
,
900
,
1000
,
1200
,
1400
,
1600
,
1800
,
2000
,
2200
};
// [ppm]
// For a given LED, which color should be displayed? First LED will be pure green (hue angle 120°),
// last 4 LEDs will be pure red (hue angle 0°), LEDs in-between will be yellowish.
const
uint16_t
led_hues
[
led_count
]
=
{
21845U
,
19114U
,
16383U
,
13653U
,
10922U
,
8191U
,
5461U
,
2730U
,
0
,
0
,
0
,
0
};
// [hue angle]
#elif LED_COUNT == 16
const
uint16_t
co2_ticks
[
led_count
+
1
]
=
{
0
,
400
,
500
,
600
,
700
,
800
,
900
,
1000
,
1100
,
1200
,
1300
,
1400
,
1500
,
1600
,
1800
,
2000
,
2200
};
// [ppm]
const
uint16_t
led_hues
[
led_count
]
=
{
21845U
,
20024U
,
18204U
,
16383U
,
14563U
,
12742U
,
10922U
,
9102U
,
7281U
,
5461U
,
3640U
,
1820U
,
0
,
0
,
0
,
0
};
// [hue angle]
#else
# error "Only 12 and 16 LEDs rings are currently supported."
#endif
}
#if defined(ESP8266)
...
...
@@ -24,20 +44,7 @@ const int NEOPIXELS_PIN = 5;
const
int
NEOPIXELS_PIN
=
23
;
#endif
const
int
NUMPIXELS
=
12
;
//NOTE: One value has been prepended, to make calculations easier and avoid out of bounds index.
const
uint16_t
CO2_TICKS
[
NUMPIXELS
+
1
]
=
{
0
,
500
,
600
,
700
,
800
,
900
,
1000
,
1200
,
1400
,
1600
,
1800
,
2000
,
2200
};
// [ppm]
// const uint16_t CO2_TICKS[NUMPIXELS + 1] = { 0, 400, 500, 600, 700, 800, 900, 1000, 1100, 1200, 1300, 1400, 1500, 1600, 1800, 2000, 2200 }; // [ppm]
// For a given LED, which color should be displayed? First LED will be pure green (hue angle 120°),
// last 4 LEDs will be pure red (hue angle 0°), LEDs in-between will be yellowish.
// For reference, this python code can be used to generate the array
// NUMPIXELS = 12
// RED_LEDS = 4
// hues = [ (2**16-1) // 3 * max(NUMPIXELS - RED_LEDS - i, 0) // (NUMPIXELS - RED_LEDS) for i in range(NUMPIXELS) ]
// '{' + ', '.join([str(hue) + ('U' if hue else '') for hue in hues]) + '}; // [hue angle]'
const
uint16_t
LED_HUES
[
NUMPIXELS
]
=
{
21845U
,
19114U
,
16383U
,
13653U
,
10922U
,
8191U
,
5461U
,
2730U
,
0
,
0
,
0
,
0
};
// [hue angle]
// const uint16_t LED_HUES[NUMPIXELS] = { 21845U, 20024U, 18204U, 16383U, 14563U, 12742U, 10922U, 9102U, 7281U, 5461U, 3640U, 1820U, 0, 0, 0, 0 }; // [hue angle]
Adafruit_NeoPixel
pixels
(
NUMPIXELS
,
NEOPIXELS_PIN
,
NEO_GRB
+
NEO_KHZ800
);
Adafruit_NeoPixel
pixels
(
config
::
led_count
,
NEOPIXELS_PIN
,
NEO_GRB
+
NEO_KHZ800
);
namespace
led_effects
{
//On-board LED on D4, aka GPIO02
...
...
@@ -70,11 +77,19 @@ namespace led_effects {
onBoardLEDOff
();
}
void
showColor
(
int32_t
color
)
{
config
::
night_mode
=
true
;
// In order to avoid overwriting the desired color next time CO2 is displayed
pixels
.
setBrightness
(
255
);
pixels
.
fill
(
color
);
pixels
.
show
();
}
void
setupRing
()
{
pixels
.
begin
();
pixels
.
setBrightness
(
config
::
max_brightness
);
LEDsOff
();
sensor_console
::
defineCommand
(
"night_mode"
,
toggleNightMode
,
F
(
" (Toggles night mode on/off)"
));
sensor_console
::
defineCommand
(
"night_mode"
,
toggleNightMode
,
F
(
"(Toggles night mode on/off)"
));
sensor_console
::
defineIntCommand
(
"color"
,
showColor
,
F
(
"0xFF0015 (Shows color, specified as RGB, for debugging)"
));
}
void
toggleNightMode
()
{
...
...
@@ -89,14 +104,15 @@ namespace led_effects {
//NOTE: basically one iteration of KITT wheel
void
showWaitingLED
(
uint32_t
color
)
{
using
namespace
config
;
delay
(
80
);
if
(
config
::
night_mode
)
{
if
(
night_mode
)
{
return
;
}
static
uint16_t
kitt_offset
=
0
;
pixels
.
clear
();
for
(
int
j
=
config
::
kitt_tail
;
j
>=
0
;
j
--
)
{
int
ledNumber
=
abs
((
kitt_offset
-
j
+
NUMPIXELS
)
%
(
2
*
NUMPIXELS
)
-
NUMPIXELS
)
%
NUMPIXELS
;
// Triangular function
for
(
int
j
=
kitt_tail
;
j
>=
0
;
j
--
)
{
int
ledNumber
=
abs
((
kitt_offset
-
j
+
led_count
)
%
(
2
*
led_count
)
-
led_count
)
%
led_count
;
// Triangular function
pixels
.
setPixelColor
(
ledNumber
,
color
*
pixels
.
gamma8
(
255
-
j
*
76
)
/
255
);
}
pixels
.
show
();
...
...
@@ -108,7 +124,7 @@ namespace led_effects {
// Takes approximately 1s for each direction.
void
showKITTWheel
(
uint32_t
color
,
uint16_t
duration_s
)
{
pixels
.
setBrightness
(
config
::
max_brightness
);
for
(
int
i
=
0
;
i
<
duration_s
*
NUMPIXELS
;
++
i
)
{
for
(
int
i
=
0
;
i
<
duration_s
*
config
::
led_count
;
++
i
)
{
showWaitingLED
(
color
);
}
}
...
...
@@ -118,10 +134,10 @@ namespace led_effects {
* For example, for 1500ppm, every LED between 0 and 7 (500 -> 1400ppm) should be on, LED at 8 (1600ppm) should be half-on.
*/
uint8_t
getLedBrightness
(
uint16_t
co2
,
int
ledId
)
{
if
(
co2
>=
CO2_TICKS
[
ledId
+
1
])
{
if
(
co2
>=
config
::
co2_ticks
[
ledId
+
1
])
{
return
255
;
}
else
{
if
(
2
*
co2
>=
CO2_TICKS
[
ledId
]
+
CO2_TICKS
[
ledId
+
1
])
{
if
(
2
*
co2
>=
config
::
co2_ticks
[
ledId
]
+
config
::
co2_ticks
[
ledId
+
1
])
{
// Show partial LED if co2 more than halfway between ticks.
return
27
;
// Brightness isn't linear, so 27 / 255 looks much brighter than 10%
}
else
{
...
...
@@ -150,9 +166,9 @@ namespace led_effects {
return
;
}
pixels
.
setBrightness
(
config
::
max_brightness
);
for
(
int
ledId
=
0
;
ledId
<
NUMPIXELS
;
++
ledId
)
{
for
(
int
ledId
=
0
;
ledId
<
config
::
led_count
;
++
ledId
)
{
uint8_t
brightness
=
getLedBrightness
(
co2
,
ledId
);
pixels
.
setPixelColor
(
ledId
,
pixels
.
ColorHSV
(
LED_HUES
[
ledId
],
255
,
brightness
));
pixels
.
setPixelColor
(
ledId
,
pixels
.
ColorHSV
(
config
::
led_hues
[
ledId
],
255
,
brightness
));
}
pixels
.
show
();
if
(
config
::
brightness_amplitude
>
0
)
{
...
...
@@ -160,17 +176,18 @@ namespace led_effects {
}
}
void
showRainbowWheel
(
uint16_t
duration_ms
,
uint16_t
hue_increment
)
{
void
showRainbowWheel
(
uint16_t
duration_ms
)
{
if
(
config
::
night_mode
)
{
return
;
}
static
uint16_t
wheel_offset
=
0
;
static
uint16_t
sine_offset
=
0
;
unsigned
long
t0
=
millis
();
pixels
.
setBrightness
(
config
::
max_brightness
);
while
(
millis
()
-
t0
<
duration_ms
)
{
for
(
int
i
=
0
;
i
<
NUMPIXELS
;
i
++
)
{
pixels
.
setPixelColor
(
i
,
pixels
.
ColorHSV
(
i
*
65535
/
NUMPIXELS
+
wheel_offset
));
wheel_offset
+=
hue_increment
;
for
(
int
i
=
0
;
i
<
config
::
led_count
;
i
++
)
{
pixels
.
setPixelColor
(
i
,
pixels
.
ColorHSV
(
i
*
65535
/
config
::
led_count
+
wheel_offset
));
wheel_offset
+=
(
pixels
.
sine8
(
sine_offset
++
/
50
)
-
127
)
/
2
;
}
pixels
.
show
();
delay
(
10
);
...
...
@@ -206,7 +223,7 @@ namespace led_effects {
pixels
.
fill
(
color
::
blue
);
pixels
.
show
();
int
countdown
;
for
(
countdown
=
NUMPIXELS
;
countdown
>=
0
&&
!
digitalRead
(
0
);
countdown
--
)
{
for
(
countdown
=
config
::
led_count
;
countdown
>=
0
&&
!
digitalRead
(
0
);
countdown
--
)
{
pixels
.
setPixelColor
(
countdown
,
color
::
black
);
pixels
.
show
();
Serial
.
println
(
countdown
);
...
...
ampel-firmware/led_effects.h
View file @
566eb489
...
...
@@ -29,7 +29,7 @@ namespace led_effects {
int
countdownToZero
();
void
showWaitingLED
(
uint32_t
color
);
void
showKITTWheel
(
uint32_t
color
,
uint16_t
duration_s
=
2
);
void
showRainbowWheel
(
uint16_t
duration_ms
=
1000
,
uint16_t
hue_increment
=
50
);
void
showRainbowWheel
(
uint16_t
duration_ms
=
1000
);
void
displayCO2color
(
uint16_t
co2
);
}
#endif
ampel-firmware/lorawan.cpp
View file @
566eb489
...
...
@@ -47,7 +47,7 @@ namespace lorawan {
LMIC_reset
();
// Join, but don't send anything yet.
LMIC_startJoining
();
sensor_console
::
defineIntCommand
(
"lora"
,
setLoRaInterval
,
F
(
"
300 (Sets LoRaWAN sending interval, in s)"
));
sensor_console
::
defineIntCommand
(
"lora"
,
setLoRaInterval
,
F
(
"300 (Sets LoRaWAN sending interval, in s)"
));
}
// Checks if OTAA is connected, or if payload should be sent.
...
...
@@ -96,7 +96,7 @@ namespace lorawan {
printHex2
(
artKey
[
i
]);
}
Serial
.
println
();
Serial
.
print
(
" NwkSKey: "
);
Serial
.
print
(
F
(
" NwkSKey: "
)
)
;
for
(
size_t
i
=
0
;
i
<
sizeof
(
nwkKey
);
++
i
)
{
if
(
i
!=
0
)
Serial
.
print
(
"-"
);
...
...
ampel-firmware/mqtt.cpp
View file @
566eb489
...
...
@@ -36,9 +36,9 @@ namespace mqtt {
// mqttClient.setSocketTimeout(config::mqtt_timeout); //NOTE: somehow doesn't seem to have any effect on connect()
mqttClient
.
setServer
(
config
::
mqtt_server
,
config
::
mqtt_port
);
sensor_console
::
defineIntCommand
(
"mqtt"
,
setMQTTinterval
,
F
(
"
60 (Sets MQTT sending interval, in s)"
));
sensor_console
::
defineCommand
(
"local_ip"
,
sendInfoAboutLocalNetwork
,
F
(
"
(Sends local IP and SSID via MQTT. Can be useful to find sensor)"
));
sensor_console
::
defineIntCommand
(
"mqtt"
,
setMQTTinterval
,
F
(
"60 (Sets MQTT sending interval, in s)"
));
sensor_console
::
defineCommand
(
"
send_
local_ip"
,
sendInfoAboutLocalNetwork
,
F
(
"(Sends local IP and SSID via MQTT. Can be useful to find sensor)"
));
}
void
publish
(
const
char
*
timestamp
,
int16_t
co2
,
float
temperature
,
float
humidity
)
{
...
...
@@ -79,7 +79,7 @@ namespace mqtt {
command
[
i
]
=
message
[
i
];
}
command
[
length
]
=
0
;
sensor_console
::
runCommand
(
command
);
sensor_console
::
execute
(
command
);
led_effects
::
onBoardLEDOff
();
}
...
...
ampel-firmware/sensor_console.cpp
View file @
566eb489
...
...
@@ -6,75 +6,112 @@ namespace sensor_console {
uint8_t
commands_count
=
0
;
enum
input_type
{
NONE
,
INT32
,
STRING
};
struct
Command
{
const
char
*
name
;
union
{
void
(
*
voidFunction
)();
void
(
*
intFunction
)(
int32_t
);
void
(
*
void
Function
)(
void
);
void
(
*
str
Function
)(
char
*
);
};
const
char
*
doc
;
bool
has_parameter
;
input_type
parameter_type
;
};
struct
CommandLine
{
char
function_name
[
MAX_COMMAND_SIZE
];
input_type
argument_type
;
int32_t
int_argument
;
char
str_argument
[
MAX_COMMAND_SIZE
];
};