Connect to WLAN and get the time via NTP
This commit is contained in:
parent
1197e083f4
commit
1045e85cfb
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,3 +1,6 @@
|
||||||
.pio
|
.pio
|
||||||
.cache
|
.cache
|
||||||
compile_commands.json
|
compile_commands.json
|
||||||
|
|
||||||
|
data/etc/*
|
||||||
|
!data/etc/.*
|
||||||
|
|
0
data/etc/.keep
Normal file
0
data/etc/.keep
Normal file
74
data/htdocs/index.html
Normal file
74
data/htdocs/index.html
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>BME680-Wetterstation</title>
|
||||||
|
<script type="text/javascript" src="utils.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
function updateWeatherTable(bme680data)
|
||||||
|
{
|
||||||
|
elements = ['temperature', 'humidity', 'pressure', 'gas_resistance'];
|
||||||
|
|
||||||
|
|
||||||
|
elements.forEach(function(item, index) {
|
||||||
|
elem = document.getElementById(item);
|
||||||
|
val = bme680data[item].value;
|
||||||
|
if(item == "gas_resistance") {
|
||||||
|
val /= 1000.0;
|
||||||
|
}
|
||||||
|
elem.textContent = val.toFixed(2);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setup()
|
||||||
|
{
|
||||||
|
getBME680Data(updateWeatherTable);
|
||||||
|
setInterval(function() { getBME680Data(updateWeatherTable) }, 60000);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
th, td {
|
||||||
|
border: 1px solid black;
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
td.value {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body onLoad="setup()">
|
||||||
|
<h1>BME680-Wetterstation</h1>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>Beschreibung</th>
|
||||||
|
<th>Messwert</th>
|
||||||
|
<th>Einheit</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Temperatur</td>
|
||||||
|
<td class="value" id="temperature">-,--</td>
|
||||||
|
<td>°C</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Luftfeuchtigkeit</td>
|
||||||
|
<td class="value" id="humidity">-,--</td>
|
||||||
|
<td>%rH</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Luftdruck</td>
|
||||||
|
<td class="value" id="pressure">----,--</td>
|
||||||
|
<td>hPa</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Gaswiderstand</td>
|
||||||
|
<td class="value" id="gas_resistance">-,--</td>
|
||||||
|
<td>kΩ</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
25
data/htdocs/utils.js
Normal file
25
data/htdocs/utils.js
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
function callURL(url)
|
||||||
|
{
|
||||||
|
var xmlhttp = new XMLHttpRequest();
|
||||||
|
xmlhttp.open("GET", url, true);
|
||||||
|
xmlhttp.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getjson(url, handler_func)
|
||||||
|
{
|
||||||
|
var xmlhttp = new XMLHttpRequest();
|
||||||
|
xmlhttp.onreadystatechange = function () {
|
||||||
|
if(this.readyState == 4 && this.status == 200) {
|
||||||
|
var data = JSON.parse(this.responseText);
|
||||||
|
handler_func(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xmlhttp.open("GET", url, true);
|
||||||
|
xmlhttp.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBME680Data(handler_func)
|
||||||
|
{
|
||||||
|
return getjson("/bme680.json", handler_func);
|
||||||
|
}
|
36
include/Config.h
Normal file
36
include/Config.h
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class Config
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
struct WLAN
|
||||||
|
{
|
||||||
|
std::string ssid;
|
||||||
|
std::string password;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef std::vector<WLAN> WLANList;
|
||||||
|
|
||||||
|
static Config &instance()
|
||||||
|
{
|
||||||
|
static Config theConfig;
|
||||||
|
return theConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
void load(void);
|
||||||
|
|
||||||
|
const WLANList& getWLANList(void) { return m_wlans; }
|
||||||
|
|
||||||
|
const std::string& getCRPassword(void) { return m_crPassword; }
|
||||||
|
const std::string& getCRSalt(void) { return m_crSalt; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
Config();
|
||||||
|
|
||||||
|
WLANList m_wlans;
|
||||||
|
std::string m_crPassword;
|
||||||
|
std::string m_crSalt;
|
||||||
|
};
|
|
@ -21,7 +21,9 @@ lib_deps =
|
||||||
adafruit/Adafruit SCD30@^1.0.11
|
adafruit/Adafruit SCD30@^1.0.11
|
||||||
SPI
|
SPI
|
||||||
Wire
|
Wire
|
||||||
|
NTPClient
|
||||||
|
|
||||||
#esp32_https_server
|
#esp32_https_server
|
||||||
|
|
||||||
build_flags = -std=c++11
|
build_flags = -std=c++11
|
||||||
|
board_build.filesystem = littlefs
|
||||||
|
|
44
src/Config.cpp
Normal file
44
src/Config.cpp
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
#include <LittleFS.h>
|
||||||
|
|
||||||
|
#include "Config.h"
|
||||||
|
|
||||||
|
Config::Config()
|
||||||
|
{}
|
||||||
|
|
||||||
|
void Config::load(void)
|
||||||
|
{
|
||||||
|
// load WLANs
|
||||||
|
File wlanFile = LittleFS.open("/etc/wlan", "r");
|
||||||
|
|
||||||
|
while(wlanFile.available()) {
|
||||||
|
String ssid = wlanFile.readStringUntil('\n');
|
||||||
|
|
||||||
|
if(!wlanFile.available()) {
|
||||||
|
Serial.println("/etc/wlan terminated early. Last entry ignored.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
String passwd = wlanFile.readStringUntil('\n');
|
||||||
|
|
||||||
|
m_wlans.emplace_back(WLAN{ssid.c_str(), passwd.c_str()});
|
||||||
|
}
|
||||||
|
|
||||||
|
wlanFile.close();
|
||||||
|
|
||||||
|
// load Challenge-Response data
|
||||||
|
File authFile = LittleFS.open("/etc/auth", "r");
|
||||||
|
|
||||||
|
if(authFile.available()) {
|
||||||
|
String passwd = authFile.readStringUntil('\n');
|
||||||
|
m_crPassword = passwd.c_str();
|
||||||
|
|
||||||
|
if(!authFile.available()) {
|
||||||
|
m_crSalt = "";
|
||||||
|
} else {
|
||||||
|
String salt = authFile.readStringUntil('\n');
|
||||||
|
m_crSalt = salt.c_str();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
authFile.close();
|
||||||
|
}
|
161
src/main.cpp
161
src/main.cpp
|
@ -1,20 +1,92 @@
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <Adafruit_SCD30.h>
|
#include <Adafruit_SCD30.h>
|
||||||
|
|
||||||
|
#include <WiFi.h>
|
||||||
|
#include <WiFiMulti.h>
|
||||||
|
#include <NTPClient.h>
|
||||||
|
|
||||||
|
#include <LittleFS.h>
|
||||||
|
|
||||||
#include <Adafruit_GFX.h>
|
#include <Adafruit_GFX.h>
|
||||||
#include <Fonts/FreeSans9pt7b.h>
|
#include <Fonts/FreeSans9pt7b.h>
|
||||||
#include <Fonts/FreeSans12pt7b.h>
|
#include <Fonts/FreeSans12pt7b.h>
|
||||||
#include <Fonts/FreeSansBold12pt7b.h>
|
#include <Fonts/FreeSansBold12pt7b.h>
|
||||||
|
|
||||||
|
#include "Config.h"
|
||||||
|
|
||||||
#include <epaper.h>
|
#include <epaper.h>
|
||||||
|
|
||||||
Adafruit_SCD30 scd30;
|
Adafruit_SCD30 scd30;
|
||||||
|
|
||||||
static timeseries_t ts_scd30_co2, ts_scd30_temperature, ts_scd30_humidity;
|
static timeseries_t ts_scd30_co2, ts_scd30_temperature, ts_scd30_humidity;
|
||||||
|
|
||||||
|
static WiFiMulti wiFiMulti;
|
||||||
|
static bool wiFiConnectedToStation;
|
||||||
|
|
||||||
|
static WiFiUDP ntpUDP;
|
||||||
|
|
||||||
|
NTPClient timeClient(ntpUDP, "europe.pool.ntp.org", 3600, 60000);
|
||||||
|
|
||||||
#define REF_ALTITUDE 320 // meters
|
#define REF_ALTITUDE 320 // meters
|
||||||
#define REF_CO2_PPM 425 // clean air according to DWD at 2024-06-17
|
#define REF_CO2_PPM 425 // clean air according to DWD at 2024-06-17
|
||||||
|
|
||||||
|
void wifi_setup(void)
|
||||||
|
{
|
||||||
|
// Connect the WiFi network (or start an AP if that doesn't work)
|
||||||
|
for (auto &net : Config::instance().getWLANList())
|
||||||
|
{
|
||||||
|
Serial.print(F("Adding network "));
|
||||||
|
Serial.println(net.ssid.c_str());
|
||||||
|
wiFiMulti.addAP(net.ssid.c_str(), net.password.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.println(F("Trying to connect for 5 Minutes..."));
|
||||||
|
|
||||||
|
WiFi.setHostname("SensorCube");
|
||||||
|
WiFi.mode(WIFI_STA);
|
||||||
|
//WiFi.setSleepMode(WIFI_MODEM_SLEEP);
|
||||||
|
|
||||||
|
wiFiConnectedToStation = true; // assume the best
|
||||||
|
|
||||||
|
bool led_on = true;
|
||||||
|
for(size_t tries = 0; tries < 3000; tries++)
|
||||||
|
{
|
||||||
|
if(wiFiMulti.run() == WL_CONNECTED) {
|
||||||
|
Serial.println(F(""));
|
||||||
|
Serial.println(F("WiFi connected"));
|
||||||
|
Serial.println(F("IP address: "));
|
||||||
|
Serial.println(WiFi.localIP());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
led_on = !led_on;
|
||||||
|
//digitalWrite(LED_BUILTIN, led_on);
|
||||||
|
delay(100);
|
||||||
|
|
||||||
|
Serial.print(F("."));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(WiFi.status() != WL_CONNECTED) {
|
||||||
|
Serial.println(F("Connection failed, setting up access point..."));
|
||||||
|
|
||||||
|
wiFiConnectedToStation = false;
|
||||||
|
|
||||||
|
IPAddress apIP(192, 168, 42, 1);
|
||||||
|
|
||||||
|
WiFi.mode(WIFI_AP);
|
||||||
|
WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
|
||||||
|
WiFi.softAP("sensorcube", "sensorcube");
|
||||||
|
WiFi.enableAP(true);
|
||||||
|
|
||||||
|
//digitalWrite(LED_BUILTIN, false); // LED ON (active low)
|
||||||
|
} else {
|
||||||
|
//digitalWrite(LED_BUILTIN, true); // LED OFF (active low)
|
||||||
|
}
|
||||||
|
|
||||||
|
timeClient.begin();
|
||||||
|
timeClient.update();
|
||||||
|
}
|
||||||
|
|
||||||
void initSCD30(void)
|
void initSCD30(void)
|
||||||
{
|
{
|
||||||
timeseries_init(&ts_scd30_co2, 0, 15, "ppm", "%.1f");
|
timeseries_init(&ts_scd30_co2, 0, 15, "ppm", "%.1f");
|
||||||
|
@ -71,25 +143,86 @@ void initSCD30(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void draw_epaper_initial_callback(GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> *display)
|
||||||
|
{
|
||||||
|
display->setRotation(1);
|
||||||
|
display->setTextColor(GxEPD_BLACK);
|
||||||
|
|
||||||
|
int16_t y = FreeSansBold12pt7b.yAdvance*3/4;
|
||||||
|
display->setFont(&FreeSansBold12pt7b);
|
||||||
|
display->setCursor(0, y);
|
||||||
|
display->print("SensorCube");
|
||||||
|
|
||||||
|
y += 2*FreeSansBold12pt7b.yAdvance;
|
||||||
|
display->setCursor(0, y);
|
||||||
|
|
||||||
|
display->setFont(&FreeSans12pt7b);
|
||||||
|
display->print("Connected to WLAN: ");
|
||||||
|
|
||||||
|
y += FreeSans12pt7b.yAdvance;
|
||||||
|
display->setCursor(0, y);
|
||||||
|
|
||||||
|
display->setFont(&FreeSansBold12pt7b);
|
||||||
|
display->print(WiFi.SSID());
|
||||||
|
|
||||||
|
y += FreeSansBold12pt7b.yAdvance;
|
||||||
|
display->setCursor(0, y);
|
||||||
|
|
||||||
|
display->setFont(&FreeSans12pt7b);
|
||||||
|
display->print("IPv4: ");
|
||||||
|
|
||||||
|
y += FreeSans12pt7b.yAdvance;
|
||||||
|
display->setCursor(0, y);
|
||||||
|
|
||||||
|
display->setFont(&FreeSansBold12pt7b);
|
||||||
|
display->print(WiFi.localIP());
|
||||||
|
|
||||||
|
y += 2*FreeSansBold12pt7b.yAdvance;
|
||||||
|
display->setCursor(0, y);
|
||||||
|
|
||||||
|
display->setFont(&FreeSans12pt7b);
|
||||||
|
display->print("Initial time: ");
|
||||||
|
|
||||||
|
y += FreeSans12pt7b.yAdvance;
|
||||||
|
display->setCursor(0, y);
|
||||||
|
|
||||||
|
display->setFont(&FreeSansBold12pt7b);
|
||||||
|
display->print(timeClient.getFormattedTime());
|
||||||
|
|
||||||
|
y += FreeSansBold12pt7b.yAdvance;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void setup(void)
|
void setup(void)
|
||||||
{
|
{
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
Serial.println("Hello World!");
|
Serial.println("Hello World!");
|
||||||
|
|
||||||
|
if(!LittleFS.begin()) {
|
||||||
|
Serial.println(F("LittleFS setup failed!"));
|
||||||
|
while(1) delay(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
Config::instance().load();
|
||||||
|
|
||||||
delay(3000);
|
delay(3000);
|
||||||
|
|
||||||
|
wifi_setup();
|
||||||
|
|
||||||
initSCD30();
|
initSCD30();
|
||||||
epaper_init();
|
epaper_init();
|
||||||
epaper_test();
|
|
||||||
|
epaper_draw_and_hibernate(draw_epaper_initial_callback, false);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void draw_epaper_callback(GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> *display)
|
static void draw_epaper_callback(GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> *display)
|
||||||
{
|
{
|
||||||
display->setRotation(1);
|
display->setRotation(1);
|
||||||
display->setTextColor(GxEPD_BLACK);
|
display->setTextColor(GxEPD_BLACK);
|
||||||
|
|
||||||
int16_t y = FreeSans12pt7b.yAdvance;
|
int16_t y = FreeSans12pt7b.yAdvance*3/4;
|
||||||
|
|
||||||
// first line: CO₂
|
// first line: CO₂
|
||||||
display->setCursor(0, y);
|
display->setCursor(0, y);
|
||||||
|
@ -115,7 +248,7 @@ void draw_epaper_callback(GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(G
|
||||||
epaper_plot(150, 5, 150, 65, &ts_scd30_co2, 900);
|
epaper_plot(150, 5, 150, 65, &ts_scd30_co2, 900);
|
||||||
|
|
||||||
// second line: temperature
|
// second line: temperature
|
||||||
y = 75 + FreeSans12pt7b.yAdvance;
|
y = 75 + FreeSans12pt7b.yAdvance*3/4;
|
||||||
display->setCursor(0, y);
|
display->setCursor(0, y);
|
||||||
|
|
||||||
display->setFont(&FreeSans12pt7b);
|
display->setFont(&FreeSans12pt7b);
|
||||||
|
@ -144,7 +277,7 @@ void draw_epaper_callback(GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(G
|
||||||
epaper_plot(150, 80, 150, 65, &ts_scd30_temperature, 900);
|
epaper_plot(150, 80, 150, 65, &ts_scd30_temperature, 900);
|
||||||
|
|
||||||
// second line: Humidity
|
// second line: Humidity
|
||||||
y = 150 + FreeSans12pt7b.yAdvance;
|
y = 150 + FreeSans12pt7b.yAdvance*3/4;
|
||||||
display->setCursor(0, y);
|
display->setCursor(0, y);
|
||||||
|
|
||||||
display->setCursor(0, y);
|
display->setCursor(0, y);
|
||||||
|
@ -166,19 +299,27 @@ void draw_epaper_callback(GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(G
|
||||||
|
|
||||||
void loop(void)
|
void loop(void)
|
||||||
{
|
{
|
||||||
static bool calibrationDone = true; // change to false to recalibrate at REF_CO2_PPM
|
static bool first_scd30_readout = true;
|
||||||
static uint32_t lastEPaperRefresh = 0;
|
static uint32_t lastEPaperRefresh = 0;
|
||||||
uint32_t now = millis();
|
uint32_t now = millis();
|
||||||
|
|
||||||
|
wiFiMulti.run(); // maintain the WiFi connection
|
||||||
|
timeClient.update(); // keep time in sync
|
||||||
|
|
||||||
if(scd30.dataReady()) {
|
if(scd30.dataReady()) {
|
||||||
if (!scd30.read()) {
|
if (!scd30.read()) {
|
||||||
Serial.println("scd30: Error reading sensor data");
|
Serial.println("scd30: Error reading sensor data");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
timeseries_append(&ts_scd30_co2, scd30.CO2);
|
// skip the first readout because the CO2 reading is then invalid
|
||||||
timeseries_append(&ts_scd30_temperature, scd30.temperature);
|
if(!first_scd30_readout) {
|
||||||
timeseries_append(&ts_scd30_humidity, scd30.relative_humidity);
|
timeseries_append(&ts_scd30_co2, scd30.CO2);
|
||||||
|
timeseries_append(&ts_scd30_temperature, scd30.temperature);
|
||||||
|
timeseries_append(&ts_scd30_humidity, scd30.relative_humidity);
|
||||||
|
} else {
|
||||||
|
first_scd30_readout = false;
|
||||||
|
}
|
||||||
|
|
||||||
Serial.print("CO2: ");
|
Serial.print("CO2: ");
|
||||||
Serial.print(scd30.CO2, 3);
|
Serial.print(scd30.CO2, 3);
|
||||||
|
@ -196,6 +337,7 @@ void loop(void)
|
||||||
epaper_draw_and_hibernate(draw_epaper_callback, false);
|
epaper_draw_and_hibernate(draw_epaper_callback, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
if(!calibrationDone && now >= 300000) {
|
if(!calibrationDone && now >= 300000) {
|
||||||
uint16_t calValue = scd30.getForcedCalibrationReference();
|
uint16_t calValue = scd30.getForcedCalibrationReference();
|
||||||
|
|
||||||
|
@ -208,6 +350,7 @@ void loop(void)
|
||||||
|
|
||||||
calibrationDone = true;
|
calibrationDone = true;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
delay(1000);
|
delay(1000);
|
||||||
|
|
Loading…
Reference in a new issue