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
];
};
Command
commands
[
MAX_COMMANDS
];
//NOTE: Probably possible to DRY (with templates?)
void
defineCommand
(
const
char
*
name
,
void
(
*
function
)(
void
),
const
__FlashStringHelper
*
doc_fstring
)
{
const
char
*
doc
=
(
const
char
*
)
doc_fstring
;
bool
addCommand
(
const
char
*
name
,
const
__FlashStringHelper
*
doc_fstring
)
{
if
(
commands_count
<
MAX_COMMANDS
)
{
commands
[
commands_count
].
name
=
name
;
commands
[
commands_count
].
voidFunction
=
function
;
commands
[
commands_count
].
doc
=
doc
;
commands
[
commands_count
].
has_parameter
=
false
;
commands_count
++
;
commands
[
commands_count
].
doc
=
(
const
char
*
)
doc_fstring
;
return
true
;
}
else
{
Serial
.
println
(
F
(
"Too many commands have been defined."
));
return
false
;
}
}
void
defineCommand
(
const
char
*
name
,
void
(
*
function
)(),
const
__FlashStringHelper
*
doc_fstring
)
{
if
(
addCommand
(
name
,
doc_fstring
))
{
commands
[
commands_count
].
voidFunction
=
function
;
commands
[
commands_count
++
].
parameter_type
=
NONE
;
}
}
void
defineIntCommand
(
const
char
*
name
,
void
(
*
function
)(
int32_t
),
const
__FlashStringHelper
*
doc_fstring
)
{
const
char
*
doc
=
(
const
char
*
)
doc_fstring
;
if
(
commands_count
<
MAX_COMMANDS
)
{
commands
[
commands_count
].
name
=
name
;
if
(
addCommand
(
name
,
doc_fstring
))
{
commands
[
commands_count
].
intFunction
=
function
;
commands
[
commands_count
].
doc
=
doc
;
commands
[
commands_count
].
has_parameter
=
true
;
commands_count
++
;
}
else
{
Serial
.
println
(
F
(
"Too many commands have been defined."
));
commands
[
commands_count
++
].
parameter_type
=
INT32
;
}
}
void
defineStringCommand
(
const
char
*
name
,
void
(
*
function
)(
char
*
),
const
__FlashStringHelper
*
doc_fstring
)
{
if
(
addCommand
(
name
,
doc_fstring
))
{
commands
[
commands_count
].
strFunction
=
function
;
commands
[
commands_count
++
].
parameter_type
=
STRING
;
}
}
/*
* Tries to split a string command (e.g. 'mqtt 60' or 'show_csv') into
a function_name and an argument.
*
Returns 0 if both are found, 1 if there is a problem and 2 if no argument is found.
* Tries to split a string command (e.g. 'mqtt 60' or 'show_csv') into
*
a CommandLine struct (function_name, argument_type and argument)
*/
uint8_t
parseCommand
(
const
char
*
command
,
char
*
function_name
,
int32_t
&
argument
)
{
char
split_command
[
MAX_COMMAND_SIZE
];
strlcpy
(
split_command
,
command
,
MAX_COMMAND_SIZE
);
char
*
arg
;
char
*
part1
;
part1
=
strtok
(
split_command
,
" "
);
if
(
!
part1
)
{
void
parseCommand
(
const
char
*
command
,
CommandLine
&
command_line
)
{
if
(
strlen
(
command
)
==
0
)
{
Serial
.
println
(
F
(
"Received empty command"
));
// Empty string
return
1
;
command_line
.
argument_type
=
NONE
;
return
;
}
strlcpy
(
function_name
,
part1
,
MAX_COMMAND_SIZE
);
arg
=
strtok
(
NULL
,
" "
);
uint8_t
code
=
0
;
if
(
arg
)
{
char
*
end
;
argument
=
strtol
(
arg
,
&
end
,
10
);
if
(
*
end
)
{
// Second argument isn't a number
code
=
2
;
}
char
*
first_space
;
first_space
=
strchr
(
command
,
' '
);
if
(
first_space
==
NULL
)
{
command_line
.
argument_type
=
NONE
;
strlcpy
(
command_line
.
function_name
,
command
,
MAX_COMMAND_SIZE
);
return
;
}
strlcpy
(
command_line
.
function_name
,
command
,
first_space
-
command
+
1
);
strlcpy
(
command_line
.
str_argument
,
first_space
+
1
,
MAX_COMMAND_SIZE
-
(
first_space
-
command
)
-
1
);
char
*
end
;
command_line
.
int_argument
=
strtol
(
command_line
.
str_argument
,
&
end
,
0
);
// Accepts 123 or 0xFF00FF
if
(
*
end
)
{
command_line
.
argument_type
=
STRING
;
}
else
{
// No argument
code
=
2
;
command_line
.
argument_type
=
INT32
;
}
}
int
compareCommandNames
(
const
void
*
s1
,
const
void
*
s2
)
{
struct
Command
*
c1
=
(
struct
Command
*
)
s1
;
struct
Command
*
c2
=
(
struct
Command
*
)
s2
;
return
strcmp
(
c1
->
name
,
c2
->
name
);
}
void
listAvailableCommands
()
{
qsort
(
commands
,
commands_count
,
sizeof
(
commands
[
0
]),
compareCommandNames
);
for
(
uint8_t
i
=
0
;
i
<
commands_count
;
i
++
)
{
Serial
.
print
(
F
(
" "
));
Serial
.
print
(
commands
[
i
].
name
);
Serial
.
print
(
F
(
" "
));
Serial
.
print
(
commands
[
i
].
doc
);
Serial
.
println
(
F
(
"."
));
}
return
code
;
}
/*
...
...
@@ -88,7 +125,7 @@ namespace sensor_console {
case
'\n'
:
// end of text
Serial
.
println
();
input_line
[
input_pos
]
=
0
;
runCommand
(
input_line
);
execute
(
input_line
);
input_pos
=
0
;
break
;
case
'\r'
:
// discard carriage return
...
...
@@ -112,50 +149,40 @@ namespace sensor_console {
}
}
int
compareName
(
const
void
*
s1
,
const
void
*
s2
)
{
struct
Command
*
c1
=
(
struct
Command
*
)
s1
;
struct
Command
*
c2
=
(
struct
Command
*
)
s2
;
return
strcmp
(
c1
->
name
,
c2
->
name
);
}
void
listAvailableCommands
()
{
qsort
(
commands
,
commands_count
,
sizeof
(
commands
[
0
]),
compareName
);
for
(
uint8_t
i
=
0
;
i
<
commands_count
;
i
++
)
{
Serial
.
print
(
" "
);
Serial
.
print
(
commands
[
i
].
name
);
Serial
.
print
(
commands
[
i
].
doc
);
Serial
.
println
(
"."
);
}
}
/*
* Tries to find the corresponding callback for a given command. Name and
number of argument
should fit.
* Tries to find the corresponding callback for a given command. Name and
parameter type
should fit.
*/
void
runCommand
(
const
char
*
command
)
{
char
function_name
[
MAX_COMMAND_SIZE
];
int32_t
argument
=
0
;
bool
has_argument
;
has_argument
=
(
parseCommand
(
command
,
function_name
,
argument
)
==
0
);
void
execute
(
const
char
*
command_str
)
{
CommandLine
input
;
parseCommand
(
command_str
,
input
);
for
(
uint8_t
i
=
0
;
i
<
commands_count
;
i
++
)
{
if
(
!
strcmp
(
function_name
,
commands
[
i
].
name
)
&&
has_
argument
==
commands
[
i
].
has_
parameter
)
{
if
(
!
strcmp
(
input
.
function_name
,
commands
[
i
].
name
)
&&
input
.
argument
_type
==
commands
[
i
].
parameter
_type
)
{
Serial
.
print
(
F
(
"Calling : "
));
Serial
.
print
(
function_name
);
if
(
has_argument
)
{
Serial
.
print
(
F
(
"("
));
Serial
.
print
(
argument
);
Serial
.
println
(
F
(
")"
));
commands
[
i
].
intFunction
(
argument
);
}
else
{
Serial
.
print
(
input
.
function_name
);
switch
(
input
.
argument_type
)
{
case
NONE
:
Serial
.
println
(
F
(
"()"
));
commands
[
i
].
voidFunction
();
return
;
case
INT32
:
Serial
.
print
(
F
(
"("
));
Serial
.
print
(
input
.
int_argument
);
Serial
.
println
(
F
(
")"
));
commands
[
i
].
intFunction
(
input
.
int_argument
);
return
;
case
STRING
:
Serial
.
print
(
F
(
"('"
));
Serial
.
print
(
input
.
str_argument
);
Serial
.
println
(
F
(
"')"
));
commands
[
i
].
strFunction
(
input
.
str_argument
);
return
;
}
return
;
}
}
Serial
.
print
(
F
(
"'"
));
Serial
.
print
(
command
);
Serial
.
print
(
command
_str
);
Serial
.
println
(
F
(
"' not supported. Available commands :"
));
listAvailableCommands
();
}
}
ampel-firmware/sensor_console.h
View file @
566eb489
...
...
@@ -8,11 +8,13 @@
*/
namespace
sensor_console
{
void
defineCommand
(
const
char
*
command
,
void
(
*
function
)(
void
),
const
__FlashStringHelper
*
ifsh
);
void
defineIntCommand
(
const
char
*
command
,
void
(
*
function
)(
int32_t
),
const
__FlashStringHelper
*
ifsh
);
void
defineCommand
(
const
char
*
name
,
void
(
*
function
)(),
const
__FlashStringHelper
*
doc_fstring
);
void
defineIntCommand
(
const
char
*
name
,
void
(
*
function
)(
int32_t
),
const
__FlashStringHelper
*
doc_fstring
);
void
defineStringCommand
(
const
char
*
name
,
void
(
*
function
)(
char
*
),
const
__FlashStringHelper
*
doc_fstring
);
void
processSerialInput
(
const
byte
in_byte
);
void
runCommand
(
const
char
*
command
);
void
execute
(
const
char
*
command_line
);
}
#endif
ampel-firmware/util.cpp
View file @
566eb489
...
...
@@ -7,6 +7,13 @@ namespace config {
#if defined(ESP8266)
const
char
*
current_board
=
"ESP8266"
;
# if !defined(AMPEL_WIFI)
void
preinit
()
{
// WiFi would be initialized otherwise (on ESP8266), even if unused.
// see https://github.com/esp8266/Arduino/issues/2111#issuecomment-224251391
ESP8266WiFiClass
::
preinitWiFiOff
();
}
# endif
#elif defined(ESP32)
const
char
*
current_board
=
"ESP32"
;
#else
...
...
@@ -74,11 +81,11 @@ char* getSensorId() {
Ampel
::
Ampel
()
:
board
(
current_board
),
sensorId
(
getSensorId
()),
max_loop_duration
(
0
)
{
sensor_console
::
defineIntCommand
(
"set_time"
,
ntp
::
setLocalTime
,
F
(
"
1618829570 (Sets time to the given UNIX time)"
));
sensor_console
::
defineCommand
(
"free"
,
Ampel
::
showFreeSpace
,
F
(
"
(Displays available heap space)"
));
sensor_console
::
defineIntCommand
(
"set_time"
,
ntp
::
setLocalTime
,
F
(
"1618829570 (Sets time to the given UNIX time)"
));
sensor_console
::
defineCommand
(
"free"
,
Ampel
::
showFreeSpace
,
F
(
"(Displays available heap space)"
));
sensor_console
::
defineCommand
(
"reset"
,
[]()
{
ESP
.
restart
();
},
F
(
"
(Restarts the sensor)"
));
},
F
(
"(Restarts the sensor)"
));
}
Ampel
ampel
;
ampel-firmware/util.h
View file @
566eb489
...
...
@@ -38,6 +38,7 @@ class Ampel {
private:
static
void
showFreeSpace
();
public:
const
char
*
version
=
"v0.1.0"
;
// Update manually after significant changes.
const
char
*
board
;
const
char
*
sensorId
;
uint32_t
max_loop_duration
;
...
...
ampel-firmware/web_server.cpp
View file @
566eb489
...
...
@@ -115,6 +115,7 @@ namespace web_server {
"<tr><td>Largest heap block</td><td>%6d bytes</td></tr>
\n
"
"<tr><td>Max loop duration</td><td>%5d ms</td></tr>
\n
"
"<tr><td>Board</td><td>%s</td></tr>
\n
"
"<tr><td>Ampel firmware</td><td>%s</td></tr>
\n
"
"<tr><td>Uptime</td><td>%2d d %4d h %02d min %02d s</td></tr>
\n
"
"</table>
\n
"
"<div id='log' class='pure-u-1 pure-u-md-1-2'></div>
\n
"
...
...
@@ -219,8 +220,8 @@ namespace web_server {
#endif
);
Serial
.
print
(
F
(
"INFO - Header size : "
));
Serial
.
print
(
strlen
(
content
));
//
Serial.print(F("INFO - Header size : "));
//
Serial.print(strlen(content));
http
.
setContentLength
(
CONTENT_LENGTH_UNKNOWN
);
http
.
send_P
(
200
,
PSTR
(
"text/html"
),
content
);
...
...
@@ -239,11 +240,11 @@ namespace web_server {
#endif
config
::
temperature_offset
,
config
::
auto_calibrate_sensor
?
"Yes"
:
"No"
,
ampel
.
sensorId
,
ampel
.
sensorId
,
wifi
::
local_ip
,
wifi
::
local_ip
,
ESP
.
getFreeHeap
(),
esp_get_max_free_block_size
(),
ampel
.
max_loop_duration
,
ampel
.
board
,
dd
,
hh
,
mm
,
ss
);
ampel
.
board
,
ampel
.
version
,
dd
,
hh
,
mm
,
ss
);
Serial
.
print
(
F
(
" - Body size : "
));
// Serial.print(F(" - Body size : "));
// Serial.print(strlen(content));
http
.
sendContent
(
content
);
Serial
.
print
(
strlen
(
content
));
// Script
snprintf_P
(
content
,
sizeof
(
content
),
script_template
...
...
@@ -252,8 +253,8 @@ namespace web_server {
#endif
);
Serial
.
print
(
F
(
" - Script size : "
));
Serial
.
println
(
strlen
(
content
));
//
Serial.print(F(" - Script size : "));
//
Serial.println(strlen(content));
http
.
sendContent
(
content
);
}
...
...
@@ -292,7 +293,7 @@ namespace web_server {
}
http
.
sendHeader
(
"Location"
,
"/"
);
http
.
send
(
303
);
sensor_console
::
runCommand
(
http
.
arg
(
"send"
).
c_str
());
sensor_console
::
execute
(
http
.
arg
(
"send"
).
c_str
());
}
void
handlePageNotFound
()
{
...
...
ampel-firmware/wifi_util.cpp
View file @
566eb489
...
...
@@ -14,8 +14,38 @@ namespace config {
namespace
wifi
{
char
local_ip
[
16
];
// "255.255.255.255\0"
void
scanNetworks
()
{
Serial
.
println
();
Serial
.
println
(
F
(
"WiFi - Scanning..."
));
bool
async
=
false
;
bool
showHidden
=
true
;
int
n
=
WiFi
.
scanNetworks
(
async
,
showHidden
);
for
(
int
i
=
0
;
i
<
n
;
++
i
)
{
Serial
.
print
(
F
(
" * '"
));
Serial
.
print
(
WiFi
.
SSID
(
i
));
Serial
.
print
(
F
(
"' ("
));
int16_t
quality
=
2
*
(
100
+
WiFi
.
RSSI
(
i
));
Serial
.
print
(
util
::
min
(
util
::
max
(
quality
,
0
),
100
));
Serial
.
println
(
F
(
" %)"
));
}
Serial
.
println
(
F
(
"Done!"
));
Serial
.
println
();
}
void
showLocalIp
()
{
Serial
.
print
(
F
(
"WiFi - Local IP : "
));
Serial
.
println
(
wifi
::
local_ip
);
Serial
.
print
(
F
(
"WiFi - SSID : "
));
Serial
.
println
(
WIFI_SSID
);
}
// Initialize Wi-Fi
void
connect
(
const
char
*
hostname
)
{
sensor_console
::
defineCommand
(
"wifi_scan"
,
scanNetworks
,
F
(
"(Scans available WiFi networks)"
));
sensor_console
::
defineCommand
(
"local_ip"
,
showLocalIp
,
F
(
"(Displays local IP and current SSID)"
));
//NOTE: WiFi Multi could allow multiple SSID and passwords.
WiFi
.
persistent
(
false
);
// Don't write user & password to Flash.
WiFi
.
mode
(
WIFI_STA
);
// Set ESP to be a WiFi-client only
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment