Commit 566eb489 authored by Eric Duminil's avatar Eric Duminil
Browse files

Merge branch 'develop'

parents aa5e2240 a5807d86
Pipeline #3776 passed with stage
in 1 minute and 45 seconds
...@@ -56,17 +56,6 @@ ...@@ -56,17 +56,6 @@
* and define your credentials and parameters in 'config.h'. * 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 * * Setup *
*****************************************************************/ *****************************************************************/
...@@ -78,14 +67,17 @@ void setup() { ...@@ -78,14 +67,17 @@ void setup() {
pinMode(0, INPUT); // Flash button (used for forced calibration) pinMode(0, INPUT); // Flash button (used for forced calibration)
led_effects::setupRing(); Serial.println();
sensor::initialize();
Serial.print(F("Sensor ID: ")); Serial.print(F("Sensor ID: "));
Serial.println(ampel.sensorId); Serial.println(ampel.sensorId);
Serial.print(F("Board : ")); Serial.print(F("Board : "));
Serial.println(ampel.board); Serial.println(ampel.board);
Serial.print(F("Firmware : "));
Serial.println(ampel.version);
led_effects::setupRing();
sensor::initialize();
#ifdef AMPEL_CSV #ifdef AMPEL_CSV
csv_writer::initialize(ampel.sensorId); csv_writer::initialize(ampel.sensorId);
...@@ -132,7 +124,6 @@ void checkSerialInput(); ...@@ -132,7 +124,6 @@ void checkSerialInput();
/***************************************************************** /*****************************************************************
* Main loop * * Main loop *
*****************************************************************/ *****************************************************************/
void loop() { void loop() {
#if defined(AMPEL_LORAWAN) && defined(ESP32) #if defined(AMPEL_LORAWAN) && defined(ESP32)
//LMIC Library seems to be very sensitive to timing issues, so run it first. //LMIC Library seems to be very sensitive to timing issues, so run it first.
......
#include "co2_sensor.h" #include "co2_sensor.h"
namespace config { namespace config {
// Values should be defined in config.h // UPPERCASE values should be defined in config.h
uint16_t measurement_timestep = MEASUREMENT_TIMESTEP; // [s] Value between 2 and 1800 (range for SCD30 sensor) 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] const uint16_t altitude_above_sea_level = ALTITUDE_ABOVE_SEA_LEVEL; // [m]
uint16_t co2_calibration_level = ATMOSPHERIC_CO2_CONCENTRATION; // [ppm] uint16_t co2_calibration_level = ATMOSPHERIC_CO2_CONCENTRATION; // [ppm]
int8_t max_deviation_during_calibration = 30; // [ppm] const uint16_t measurement_timestep_bootup = 5; // [s] Measurement timestep during acclimatization.
int8_t enough_stable_measurements = 60; 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 #ifdef TEMPERATURE_OFFSET
// Residual heat from CO2 sensor seems to be high enough to change the temperature reading. How much should it be 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. // NOTE: Sign isn't relevant. The returned temperature will always be shifted down.
...@@ -28,31 +32,27 @@ namespace sensor { ...@@ -28,31 +32,27 @@ namespace sensor {
/** /**
* Define sensor states * Define sensor states
* INITIAL -> initial state * BOOTUP -> initial state, until first >0 ppm values are returned
* BOOTUP -> state after initializing the sensor, i.e. after scd.begin()
* READY -> sensor does output valid information (> 0 ppm) and no other condition takes place * READY -> sensor does output valid information (> 0 ppm) and no other condition takes place
* NEEDS_CALIBRATION -> sensor measurements are too low (< 250 ppm) * NEEDS_CALIBRATION -> sensor measurements are too low (< 250 ppm)
* PREPARE_CALIBRATION -> forced calibration was initiated, waiting for stable measurements * PREPARE_CALIBRATION_UNSTABLE -> forced calibration was initiated, last measurements were too far apart
* CALIBRATION -> the sensor does calibrate itself * PREPARE_CALIBRATION_STABLE -> forced calibration was initiated, last measurements were close to each others
*/ */
enum state { enum state {
INITIAL,
BOOTUP, BOOTUP,
READY, READY,
NEEDS_CALIBRATION, NEEDS_CALIBRATION,
PREPARE_CALIBRATION_UNSTABLE, PREPARE_CALIBRATION_UNSTABLE,
PREPARE_CALIBRATION_STABLE, PREPARE_CALIBRATION_STABLE
CALIBRATION
}; };
const char *state_names[] = { const char *state_names[] = {
"INITIAL",
"BOOTUP", "BOOTUP",
"READY", "READY",
"NEEDS_CALIBRATION", "NEEDS_CALIBRATION",
"PREPARE_CALIBRATION_UNSTABLE", "PREPARE_CALIBRATION_UNSTABLE",
"PREPARE_CALIBRATION_STABLE", "PREPARE_CALIBRATION_STABLE" };
"CALIBRATION" };
state current_state = INITIAL; state current_state = BOOTUP;
void switchState(state); void switchState(state);
void initialize() { void initialize() {
...@@ -78,47 +78,51 @@ namespace sensor { ...@@ -78,47 +78,51 @@ namespace sensor {
ESP.restart(); ESP.restart();
} }
switchState(BOOTUP); // 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
// SCD30 has its own timer. // of the ESP the SCD30 sometimes needs a long time until switching back to 2 s
//NOTE: The timer seems to be inaccurate, though, possibly depending on voltage. Should it be offset? // for acclimatization. Resetting it after startup seems to fix this behaviour.
Serial.print(F("Setting SCD30 timestep to ")); scd30.reset();
Serial.print(config::measurement_timestep);
Serial.println(" s.");
scd30.setMeasurementInterval(config::measurement_timestep); // [s]
Serial.print(F("Setting temperature offset to -")); Serial.print(F("Setting temperature offset to -"));
Serial.print(abs(config::temperature_offset)); 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. scd30.setTemperatureOffset(abs(config::temperature_offset)); // setTemperatureOffset only accepts positive numbers, but shifts the temperature down.
delay(100); delay(100);
Serial.print(F("Temperature offset is : -")); Serial.print(F("Temperature offset is : -"));
Serial.print(scd30.getTemperatureOffset()); Serial.print(scd30.getTemperatureOffset());
Serial.println(" K"); Serial.println(F(" K"));
Serial.print(F("Auto-calibration is ")); Serial.print(F("Auto-calibration is "));
Serial.println(config::auto_calibrate_sensor ? "ON." : "OFF."); Serial.println(config::auto_calibrate_sensor ? "ON." : "OFF.");
sensor_console::defineIntCommand("co2", setCO2forDebugging, F(" 1500 (Sets co2 level, for debugging purposes)")); // SCD30 has its own timer.
sensor_console::defineIntCommand("timer", setTimer, F(" 30 (Sets measurement interval, in s)")); //NOTE: The timer seems to be inaccurate, though, possibly depending on voltage. Should it be offset?
sensor_console::defineCommand("calibrate", startCalibrationProcess, F(" (Starts calibration process)")); 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, 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, sensor_console::defineIntCommand("calibrate!", calibrateSensorRightNow,
F(" 600 (Calibrates right now, to given ppm)")); F("600 (Calibrates right now, to given ppm)"));
sensor_console::defineIntCommand("auto_calibrate", setAutoCalibration, sensor_console::defineIntCommand("auto_calibrate", setAutoCalibration, F("0/1 (Disables/enables autocalibration)"));
F(" 0/1 (Disables/enables autocalibration)"));
} }
//NOTE: should timer deviation be used to adjust measurement_timestep? bool hasSensorSettled() {
void checkTimerDeviation() { static uint16_t last_co2 = 0;
static int32_t previous_measurement_at = 0; uint16_t delta;
int32_t now = millis(); delta = abs(co2 - last_co2);
Serial.print(F("Measurement time offset : ")); last_co2 = co2;
Serial.print(now - previous_measurement_at - config::measurement_timestep * 1000); // We assume the sensor has acclimated to the environment if measurements
Serial.println(" ms."); // change less than a specified percentage of the current value.
previous_measurement_at = now; return (co2 > 0 && delta < ((uint32_t) co2 * config::max_deviation_during_bootup / 100));
} }
bool countStableMeasurements() { bool countStableMeasurements() {
...@@ -128,30 +132,32 @@ namespace sensor { ...@@ -128,30 +132,32 @@ namespace sensor {
&& co2 < (previous_co2 + config::max_deviation_during_calibration)) { && co2 < (previous_co2 + config::max_deviation_during_calibration)) {
stable_measurements++; stable_measurements++;
Serial.print(F("Number of 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); switchState(PREPARE_CALIBRATION_STABLE);
} else { } else {
stable_measurements = 0; stable_measurements = 0;
switchState(PREPARE_CALIBRATION_UNSTABLE); switchState(PREPARE_CALIBRATION_UNSTABLE);
} }
previous_co2 = co2; previous_co2 = co2;
return (stable_measurements == config::enough_stable_measurements); return (stable_measurements == config::stable_measurements_before_calibration);
} }
void startCalibrationProcess() { void startCalibrationProcess() {
/** From the sensor documentation: /** From the sensor documentation:
* For best results, the sensor has to be run in a stable environment in continuous mode at * Before applying FRC, SCD30 needs to be operated for 2 minutes with the desired measurement period in continuous mode.
* a measurement rate of 2s for at least two minutes before applying the FRC command and sending the reference value.
*/ */
Serial.println(F("Setting SCD30 timestep to 2s, prior to calibration.")); Serial.print(F("Setting SCD30 timestep to "));
scd30.setMeasurementInterval(2); // [s] The change will only take effect after next measurement. 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("Waiting until the measurements are stable for at least 2 minutes."));
Serial.println(F("It could take a very long time.")); Serial.println(F("It could take a very long time."));
switchState(PREPARE_CALIBRATION_UNSTABLE); switchState(PREPARE_CALIBRATION_UNSTABLE);
} }
void calibrateAndRestart() { void calibrateAndRestart() {
switchState(CALIBRATION);
Serial.print(F("Calibrating SCD30 now...")); Serial.print(F("Calibrating SCD30 now..."));
scd30.setAltitudeCompensation(config::altitude_above_sea_level); scd30.setAltitudeCompensation(config::altitude_above_sea_level);
scd30.setForcedRecalibrationFactor(config::co2_calibration_level); scd30.setForcedRecalibrationFactor(config::co2_calibration_level);
...@@ -177,19 +183,28 @@ namespace sensor { ...@@ -177,19 +183,28 @@ namespace sensor {
if (config::debug_sensor_states) { if (config::debug_sensor_states) {
Serial.print(F("Changing sensor state: ")); Serial.print(F("Changing sensor state: "));
Serial.print(state_names[current_state]); Serial.print(state_names[current_state]);
Serial.print(" -> "); Serial.print(F(" -> "));
Serial.println(state_names[new_state]); Serial.println(state_names[new_state]);
} }
current_state = new_state; current_state = new_state;
} }
void switchStateForCurrentPPM() { void switchStateForCurrentPPM() {
if (co2 == 0) { if (current_state == BOOTUP) {
// NOTE: Data is available, but it's sometimes erroneous: the sensor outputs if (!hasSensorSettled()) {
// zero ppm but non-zero temperature and non-zero humidity. return;
Serial.println(F("Invalid sensor data - CO2 concentration supposedly 0 ppm")); }
switchState(BOOTUP); switchState(READY);
} else if ((current_state == PREPARE_CALIBRATION_UNSTABLE) || (current_state == PREPARE_CALIBRATION_STABLE)) { 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 // Check for pre-calibration states first, because we do not want to
// leave them before calibration is done. // leave them before calibration is done.
bool ready_for_calibration = countStableMeasurements(); bool ready_for_calibration = countStableMeasurements();
...@@ -236,8 +251,6 @@ namespace sensor { ...@@ -236,8 +251,6 @@ namespace sensor {
case PREPARE_CALIBRATION_STABLE: case PREPARE_CALIBRATION_STABLE:
led_effects::showWaitingLED(color::green); led_effects::showWaitingLED(color::green);
break; break;
case CALIBRATION: // Nothing to do, will restart soon.
break;
default: default:
Serial.println(F("Encountered unknown sensor state")); // This should not happen. Serial.println(F("Encountered unknown sensor state")); // This should not happen.
} }
...@@ -250,7 +263,6 @@ namespace sensor { ...@@ -250,7 +263,6 @@ namespace sensor {
bool freshData = scd30.dataAvailable(); bool freshData = scd30.dataAvailable();
if (freshData) { if (freshData) {
// checkTimerDeviation();
ntp::getLocalTime(timestamp); ntp::getLocalTime(timestamp);
co2 = scd30.getCO2(); co2 = scd30.getCO2();
temperature = scd30.getTemperature(); temperature = scd30.getTemperature();
...@@ -264,7 +276,9 @@ namespace sensor { ...@@ -264,7 +276,9 @@ namespace sensor {
showState(); 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 { ...@@ -288,8 +302,8 @@ namespace sensor {
if (timestep >= 2 && timestep <= 1800) { if (timestep >= 2 && timestep <= 1800) {
Serial.print(F("Setting Measurement Interval to : ")); Serial.print(F("Setting Measurement Interval to : "));
Serial.print(timestep); Serial.print(timestep);
Serial.println("s."); Serial.println(F("s."));
sensor::scd30.setMeasurementInterval(timestep); scd30.setMeasurementInterval(timestep);
config::measurement_timestep = timestep; config::measurement_timestep = timestep;
led_effects::showKITTWheel(color::green, 1); led_effects::showKITTWheel(color::green, 1);
} }
...@@ -300,13 +314,18 @@ namespace sensor { ...@@ -300,13 +314,18 @@ namespace sensor {
Serial.print(F("Force calibration, at ")); Serial.print(F("Force calibration, at "));
config::co2_calibration_level = calibrationLevel; config::co2_calibration_level = calibrationLevel;
Serial.print(config::co2_calibration_level); Serial.print(config::co2_calibration_level);
Serial.println(" ppm."); Serial.println(F(" ppm."));
sensor::startCalibrationProcess(); startCalibrationProcess();
} }
} }
void calibrateSensorRightNow(int32_t calibrationLevel) { void calibrateSensorRightNow(int32_t calibrationLevel) {
stable_measurements = config::enough_stable_measurements; if (calibrationLevel >= 400 && calibrationLevel <= 2000) {
calibrateSensorToSpecificPPM(calibrationLevel); Serial.print(F("Force calibration, right now, at "));
config::co2_calibration_level = calibrationLevel;
Serial.print(config::co2_calibration_level);
Serial.println(F(" ppm."));
calibrateAndRestart();
}
} }
} }
...@@ -27,7 +27,9 @@ ...@@ -27,7 +27,9 @@
*/ */
// How often should measurement be performed, and displayed? // 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) # define MEASUREMENT_TIMESTEP 60 // [s] Value between 2 and 1800 (range for SCD30 sensor)
// How often should measurements be appended to CSV ? // How often should measurements be appended to CSV ?
...@@ -64,6 +66,8 @@ ...@@ -64,6 +66,8 @@
// MIN_BRIGHTNESS, if defined, should be between 0 and MAX_BRIGHTNESS - 1 // 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. // If MIN_BRIGHTNESS is not set, or if it is set to MAX_BRIGHTNESS, breathing is disabled.
# define MIN_BRIGHTNESS 60 # 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 * WEB SERVER
...@@ -101,7 +105,7 @@ ...@@ -101,7 +105,7 @@
*/ */
# define ALLOW_MQTT_COMMANDS false # 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 // 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 // Set to 0 if you want to send values after each measurement
// # define MQTT_SENDING_INTERVAL MEASUREMENT_TIMESTEP * 5 // [s] // # define MQTT_SENDING_INTERVAL MEASUREMENT_TIMESTEP * 5 // [s]
......
...@@ -118,9 +118,9 @@ namespace csv_writer { ...@@ -118,9 +118,9 @@ namespace csv_writer {
showFilesystemContent(); showFilesystemContent();
Serial.println(); Serial.println();
sensor_console::defineIntCommand("csv", setCSVinterval, F(" 60 (Sets CSV writing interval, in s)")); 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("format_filesystem", formatFilesystem, F("(Deletes the whole filesystem)"));
sensor_console::defineCommand("show_csv", showCSVContent, F(" (Displays the complete CSV file on Serial)")); sensor_console::defineCommand("show_csv", showCSVContent, F("(Displays the complete CSV file on Serial)"));
} }
File openOrCreate() { File openOrCreate() {
......
...@@ -12,8 +12,28 @@ namespace config { ...@@ -12,8 +12,28 @@ namespace config {
const uint8_t brightness_amplitude = config::max_brightness - config::min_brightness; 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 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. 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; //NOTE: Use a class instead? NightMode could then be another state.
bool night_mode = false;
#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) #if defined(ESP8266)
...@@ -24,20 +44,7 @@ const int NEOPIXELS_PIN = 5; ...@@ -24,20 +44,7 @@ const int NEOPIXELS_PIN = 5;
const int NEOPIXELS_PIN = 23; const int NEOPIXELS_PIN = 23;
#endif #endif
const int NUMPIXELS = 12; Adafruit_NeoPixel pixels(config::led_count, NEOPIXELS_PIN, NEO_GRB + NEO_KHZ800);
//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);
namespace led_effects { namespace led_effects {
//On-board LED on D4, aka GPIO02 //On-board LED on D4, aka GPIO02
...@@ -70,11 +77,19 @@ namespace led_effects { ...@@ -70,11 +77,19 @@ namespace led_effects {
onBoardLEDOff(); 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() { void setupRing() {
pixels.begin(); pixels.begin();
pixels.setBrightness(config::max_brightness); pixels.setBrightness(config::max_brightness);
LEDsOff(); 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() { void toggleNightMode() {
...@@ -89,14 +104,15 @@ namespace led_effects { ...@@ -89,14 +104,15 @@ namespace led_effects {
//NOTE: basically one iteration of KITT wheel //NOTE: basically one iteration of KITT wheel
void showWaitingLED(uint32_t color) { void showWaitingLED(uint32_t color) {
using namespace config;
delay(80); delay(80);
if (config::night_mode) { if (night_mode) {
return; return;
} }
static uint16_t kitt_offset = 0; static uint16_t kitt_offset = 0;
pixels.clear(); pixels.clear();
for (int j = config::kitt_tail; j >= 0; j--) { for (int j = kitt_tail; j >= 0; j--) {
int ledNumber = abs((kitt_offset - j + NUMPIXELS) % (2 * NUMPIXELS) - NUMPIXELS) % NUMPIXELS; // Triangular function 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.setPixelColor(ledNumber, color * pixels.gamma8(255 - j * 76) / 255);
} }
pixels.show(); pixels.show();
...@@ -108,7 +124,7 @@ namespace led_effects { ...@@ -108,7 +124,7 @@ namespace led_effects {
// Takes approximately 1s for each direction. // Takes approximately 1s for each direction.
void showKITTWheel(uint32_t color, uint16_t duration_s) { void showKITTWheel(uint32_t color, uint16_t duration_s) {
pixels.setBrightness(config::max_brightness); 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); showWaitingLED(color);
} }
} }
...@@ -118,10 +134,10 @@ namespace led_effects { ...@@ -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. * 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) { uint8_t getLedBrightness(uint16_t co2, int ledId) {
if (co2 >= CO2_TICKS[ledId + 1]) { if (co2 >= config::co2_ticks[ledId + 1]) {
return 255; return 255;
} else { } 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. // Show partial LED if co2 more than halfway between ticks.
return 27; // Brightness isn't linear, so 27 / 255 looks much brighter than 10% return 27; // Brightness isn't linear, so 27 / 255 looks much brighter than 10%
} else { } else {
...@@ -150,9 +166,9 @@ namespace led_effects { ...@@ -150,9 +166,9 @@ namespace led_effects {
return; return;
} }
pixels.setBrightness(config::max_brightness); 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); 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(); pixels.show();
if (config::brightness_amplitude > 0) { if (config::brightness_amplitude > 0) {
...@@ -160,17 +176,18 @@ namespace led_effects { ...@@ -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) { if (config::night_mode) {
return; return;
} }
static uint16_t wheel_offset = 0; static uint16_t wheel_offset = 0;
static uint16_t sine_offset = 0;
unsigned long t0 = millis(); unsigned long t0 = millis();
pixels.setBrightness(config::max_brightness); pixels.setBrightness(config::max_brightness);
while (millis() - t0 < duration_ms) { while (millis() - t0 < duration_ms) {
for (int i = 0; i < NUMPIXELS; i++) { for (int i = 0; i < config::led_count; i++) {
pixels.setPixelColor(i, pixels.ColorHSV(i * 65535 / NUMPIXELS + wheel_offset)); pixels.setPixelColor(i, pixels.ColorHSV(i * 65535 / config::led_count + wheel_offset));
wheel_offset += hue_increment; wheel_offset += (pixels.sine8(sine_offset++ / 50) - 127) / 2;
} }
pixels.show(); pixels.show();
delay(10); delay(10);
...@@ -206,7 +223,7 @@ namespace led_effects { ...@@ -206,7 +223,7 @@ namespace led_effects {
pixels.fill(color::blue); pixels.fill(color::blue);
pixels.show(); pixels.show();
int countdown; 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.setPixelColor(countdown, color::black);
pixels.show(); pixels.show();
Serial.println(countdown); Serial.println(countdown);
......
...@@ -29,7 +29,7 @@ namespace led_effects { ...@@ -29,7 +29,7 @@ namespace led_effects {
int countdownToZero(); int countdownToZero();
void showWaitingLED(uint32_t color); void showWaitingLED(uint32_t color);
void showKITTWheel(uint32_t color, uint16_t duration_s = 2); 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); void displayCO2color(uint16_t co2);
} }
#endif #endif
...@@ -47,7 +47,7 @@ namespace lorawan { ...@@ -47,7 +47,7 @@ namespace lorawan {
LMIC_reset(); LMIC_reset();
// Join, but don't send anything yet. // Join, but don't send anything yet.
LMIC_startJoining(); 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. // Checks if OTAA is connected, or if payload should be sent.
...@@ -96,7 +96,7 @@ namespace lorawan { ...@@ -96,7 +96,7 @@ namespace lorawan {
printHex2(artKey[i]); printHex2(artKey[i]);
} }
Serial.println(); Serial.println();
Serial.print(" NwkSKey: "); Serial.print(F(" NwkSKey: "));
for (size_t i = 0; i < sizeof(nwkKey); ++i) { for (size_t i = 0; i < sizeof(nwkKey); ++i) {
if (i != 0) if (i != 0)
Serial.print("-"); Serial.print("-");
......
...@@ -36,9 +36,9 @@ namespace mqtt { ...@@ -36,9 +36,9 @@ namespace mqtt {
// mqttClient.setSocketTimeout(config::mqtt_timeout); //NOTE: somehow doesn't seem to have any effect on connect() // mqttClient.setSocketTimeout(config::mqtt_timeout); //NOTE: somehow doesn't seem to have any effect on connect()
mqttClient.setServer(config::mqtt_server, config::mqtt_port); mqttClient.setServer(config::mqtt_server, config::mqtt_port);
sensor_console::defineIntCommand("mqtt", setMQTTinterval, F(" 60 (Sets MQTT sending interval, in s)")); sensor_console::defineIntCommand("mqtt", setMQTTinterval, F("60 (Sets MQTT sending interval, in s)"));
sensor_console::defineCommand("local_ip", sendInfoAboutLocalNetwork, sensor_console::defineCommand("send_local_ip", sendInfoAboutLocalNetwork,
F(" (Sends local IP and SSID via MQTT. Can be useful to find sensor)")); 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) { void publish(const char *timestamp, int16_t co2, float temperature, float humidity) {
...@@ -79,7 +79,7 @@ namespace mqtt { ...@@ -79,7 +79,7 @@ namespace mqtt {
command[i] = message[i]; command[i] = message[i];
} }
command[length] = 0; command[length] = 0;
sensor_console::runCommand(command); sensor_console::execute(command);
led_effects::onBoardLEDOff(); led_effects::onBoardLEDOff();
} }
......
...@@ -6,75 +6,112 @@ namespace sensor_console { ...@@ -6,75 +6,112 @@ namespace sensor_console {
uint8_t commands_count = 0; uint8_t commands_count = 0;
enum input_type {
NONE,
INT32,
STRING
};
struct Command { struct Command {
const char *name; const char *name;
union { union {
void (*voidFunction)();
void (*intFunction)(int32_t); void (*intFunction)(int32_t);
void (*voidFunction)(void); void (*strFunction)(char*);
}; };
const char *doc; 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]; Command commands[MAX_COMMANDS];
//NOTE: Probably possible to DRY (with templates?) bool addCommand(const char *name, const __FlashStringHelper *doc_fstring) {
void defineCommand(const char *name, void (*function)(void), const __FlashStringHelper *doc_fstring) {
const char *doc = (const char*) doc_fstring;
if (commands_count < MAX_COMMANDS) { if (commands_count < MAX_COMMANDS) {
commands[commands_count].name = name; commands[commands_count].name = name;
commands[commands_count].voidFunction = function; commands[commands_count].doc = (const char*) doc_fstring;
commands[commands_count].doc = doc; return true;
commands[commands_count].has_parameter = false;
commands_count++;
} else { } else {
Serial.println(F("Too many commands have been defined.")); 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) { void defineIntCommand(const char *name, void (*function)(int32_t), const __FlashStringHelper *doc_fstring) {
const char *doc = (const char*) doc_fstring; if (addCommand(name, doc_fstring)) {
if (commands_count < MAX_COMMANDS) {
commands[commands_count].name = name;
commands[commands_count].intFunction = function; commands[commands_count].intFunction = function;
commands[commands_count].doc = doc; commands[commands_count++].parameter_type = INT32;
commands[commands_count].has_parameter = true; }
commands_count++; }
} else {
Serial.println(F("Too many commands have been defined.")); 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. * Tries to split a string command (e.g. 'mqtt 60' or 'show_csv') into
* Returns 0 if both are found, 1 if there is a problem and 2 if no argument is found. * a CommandLine struct (function_name, argument_type and argument)
*/ */
uint8_t parseCommand(const char *command, char *function_name, int32_t &argument) { void parseCommand(const char *command, CommandLine &command_line) {
char split_command[MAX_COMMAND_SIZE]; if (strlen(command) == 0) {
strlcpy(split_command, command, MAX_COMMAND_SIZE);
char *arg;
char *part1;
part1 = strtok(split_command, " ");
if (!part1) {
Serial.println(F("Received empty command")); Serial.println(F("Received empty command"));
// Empty string command_line.argument_type = NONE;
return 1; return;
} }
strlcpy(function_name, part1, MAX_COMMAND_SIZE);
arg = strtok(NULL, " "); char *first_space;
uint8_t code = 0; first_space = strchr(command, ' ');
if (arg) {
char *end; if (first_space == NULL) {
argument = strtol(arg, &end, 10); command_line.argument_type = NONE;
if (*end) { strlcpy(command_line.function_name, command, MAX_COMMAND_SIZE);
// Second argument isn't a number return;
code = 2; }
}
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 { } else {
// No argument command_line.argument_type = INT32;
code = 2; }
}
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 { ...@@ -88,7 +125,7 @@ namespace sensor_console {
case '\n': // end of text case '\n': // end of text
Serial.println(); Serial.println();
input_line[input_pos] = 0; input_line[input_pos] = 0;
runCommand(input_line); execute(input_line);
input_pos = 0; input_pos = 0;
break; break;
case '\r': // discard carriage return case '\r': // discard carriage return
...@@ -112,50 +149,40 @@ namespace sensor_console { ...@@ -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) { void execute(const char *command_str) {
char function_name[MAX_COMMAND_SIZE]; CommandLine input;
int32_t argument = 0; parseCommand(command_str, input);
bool has_argument;
has_argument = (parseCommand(command, function_name, argument) == 0);
for (uint8_t i = 0; i < commands_count; i++) { 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(F("Calling : "));
Serial.print(function_name); Serial.print(input.function_name);
if (has_argument) { switch (input.argument_type) {
Serial.print(F("(")); case NONE:
Serial.print(argument);
Serial.println(F(")"));
commands[i].intFunction(argument);
} else {
Serial.println(F("()")); Serial.println(F("()"));
commands[i].voidFunction(); 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(F("'"));
Serial.print(command); Serial.print(command_str);
Serial.println(F("' not supported. Available commands :")); Serial.println(F("' not supported. Available commands :"));
listAvailableCommands(); listAvailableCommands();
} }
} }
...@@ -8,11 +8,13 @@ ...@@ -8,11 +8,13 @@
*/ */
namespace sensor_console { namespace sensor_console {
void defineCommand(const char *command, void (*function)(void), const __FlashStringHelper *ifsh); void defineCommand(const char *name, void (*function)(), const __FlashStringHelper *doc_fstring);
void defineIntCommand(const char *command, void (*function)(int32_t), const __FlashStringHelper *ifsh); 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 processSerialInput(const byte in_byte);
void runCommand(const char *command);
void execute(const char *command_line);
} }
#endif #endif
...@@ -7,6 +7,13 @@ namespace config { ...@@ -7,6 +7,13 @@ namespace config {
#if defined(ESP8266) #if defined(ESP8266)
const char *current_board = "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) #elif defined(ESP32)
const char *current_board = "ESP32"; const char *current_board = "ESP32";
#else #else
...@@ -74,11 +81,11 @@ char* getSensorId() { ...@@ -74,11 +81,11 @@ char* getSensorId() {
Ampel::Ampel() : Ampel::Ampel() :
board(current_board), sensorId(getSensorId()), max_loop_duration(0) { 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::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("free", Ampel::showFreeSpace, F("(Displays available heap space)"));
sensor_console::defineCommand("reset", []() { sensor_console::defineCommand("reset", []() {
ESP.restart(); ESP.restart();
}, F(" (Restarts the sensor)")); }, F("(Restarts the sensor)"));
} }
Ampel ampel; Ampel ampel;
...@@ -38,6 +38,7 @@ class Ampel { ...@@ -38,6 +38,7 @@ class Ampel {
private: private:
static void showFreeSpace(); static void showFreeSpace();
public: public:
const char *version = "v0.1.0"; // Update manually after significant changes.
const char *board; const char *board;
const char *sensorId; const char *sensorId;
uint32_t max_loop_duration; uint32_t max_loop_duration;
......
...@@ -115,6 +115,7 @@ namespace web_server { ...@@ -115,6 +115,7 @@ namespace web_server {
"<tr><td>Largest heap block</td><td>%6d bytes</td></tr>\n" "<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>Max loop duration</td><td>%5d ms</td></tr>\n"
"<tr><td>Board</td><td>%s</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" "<tr><td>Uptime</td><td>%2d d %4d h %02d min %02d s</td></tr>\n"
"</table>\n" "</table>\n"
"<div id='log' class='pure-u-1 pure-u-md-1-2'></div>\n" "<div id='log' class='pure-u-1 pure-u-md-1-2'></div>\n"
...@@ -219,8 +220,8 @@ namespace web_server { ...@@ -219,8 +220,8 @@ namespace web_server {
#endif #endif
); );
Serial.print(F("INFO - Header size : ")); // Serial.print(F("INFO - Header size : "));
Serial.print(strlen(content)); // Serial.print(strlen(content));
http.setContentLength(CONTENT_LENGTH_UNKNOWN); http.setContentLength(CONTENT_LENGTH_UNKNOWN);
http.send_P(200, PSTR("text/html"), content); http.send_P(200, PSTR("text/html"), content);
...@@ -239,11 +240,11 @@ namespace web_server { ...@@ -239,11 +240,11 @@ namespace web_server {
#endif #endif
config::temperature_offset, config::auto_calibrate_sensor ? "Yes" : "No", ampel.sensorId, ampel.sensorId, 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, 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); http.sendContent(content);
Serial.print(strlen(content));
// Script // Script
snprintf_P(content, sizeof(content), script_template snprintf_P(content, sizeof(content), script_template
...@@ -252,8 +253,8 @@ namespace web_server { ...@@ -252,8 +253,8 @@ namespace web_server {
#endif #endif
); );
Serial.print(F(" - Script size : ")); // Serial.print(F(" - Script size : "));
Serial.println(strlen(content)); // Serial.println(strlen(content));
http.sendContent(content); http.sendContent(content);
} }
...@@ -292,7 +293,7 @@ namespace web_server { ...@@ -292,7 +293,7 @@ namespace web_server {
} }
http.sendHeader("Location", "/"); http.sendHeader("Location", "/");
http.send(303); http.send(303);
sensor_console::runCommand(http.arg("send").c_str()); sensor_console::execute(http.arg("send").c_str());
} }
void handlePageNotFound() { void handlePageNotFound() {
......
...@@ -14,8 +14,38 @@ namespace config { ...@@ -14,8 +14,38 @@ namespace config {
namespace wifi { namespace wifi {
char local_ip[16]; // "255.255.255.255\0" 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 // Initialize Wi-Fi
void connect(const char *hostname) { 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. //NOTE: WiFi Multi could allow multiple SSID and passwords.
WiFi.persistent(false); // Don't write user & password to Flash. WiFi.persistent(false); // Don't write user & password to Flash.
WiFi.mode(WIFI_STA); // Set ESP to be a WiFi-client only WiFi.mode(WIFI_STA); // Set ESP to be a WiFi-client only
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment