Connect to WLAN and get the time via NTP

This commit is contained in:
Thomas Kolb 2024-07-06 20:37:22 +02:00
parent 1197e083f4
commit 1045e85cfb
8 changed files with 336 additions and 9 deletions

3
.gitignore vendored
View file

@ -1,3 +1,6 @@
.pio
.cache
compile_commands.json
data/etc/*
!data/etc/.*

0
data/etc/.keep Normal file
View file

74
data/htdocs/index.html Normal file
View 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></td>
</tr>
</table>
</body>
</html>

25
data/htdocs/utils.js Normal file
View 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
View 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;
};

View file

@ -21,7 +21,9 @@ lib_deps =
adafruit/Adafruit SCD30@^1.0.11
SPI
Wire
NTPClient
#esp32_https_server
build_flags = -std=c++11
board_build.filesystem = littlefs

44
src/Config.cpp Normal file
View 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();
}

View file

@ -1,20 +1,92 @@
#include <Arduino.h>
#include <Adafruit_SCD30.h>
#include <WiFi.h>
#include <WiFiMulti.h>
#include <NTPClient.h>
#include <LittleFS.h>
#include <Adafruit_GFX.h>
#include <Fonts/FreeSans9pt7b.h>
#include <Fonts/FreeSans12pt7b.h>
#include <Fonts/FreeSansBold12pt7b.h>
#include "Config.h"
#include <epaper.h>
Adafruit_SCD30 scd30;
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_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)
{
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)
{
Serial.begin(115200);
Serial.println("Hello World!");
if(!LittleFS.begin()) {
Serial.println(F("LittleFS setup failed!"));
while(1) delay(1);
}
Config::instance().load();
delay(3000);
wifi_setup();
initSCD30();
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->setTextColor(GxEPD_BLACK);
int16_t y = FreeSans12pt7b.yAdvance;
int16_t y = FreeSans12pt7b.yAdvance*3/4;
// first line: CO₂
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);
// second line: temperature
y = 75 + FreeSans12pt7b.yAdvance;
y = 75 + FreeSans12pt7b.yAdvance*3/4;
display->setCursor(0, y);
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);
// second line: Humidity
y = 150 + FreeSans12pt7b.yAdvance;
y = 150 + FreeSans12pt7b.yAdvance*3/4;
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)
{
static bool calibrationDone = true; // change to false to recalibrate at REF_CO2_PPM
static bool first_scd30_readout = true;
static uint32_t lastEPaperRefresh = 0;
uint32_t now = millis();
wiFiMulti.run(); // maintain the WiFi connection
timeClient.update(); // keep time in sync
if(scd30.dataReady()) {
if (!scd30.read()) {
Serial.println("scd30: Error reading sensor data");
return;
}
timeseries_append(&ts_scd30_co2, scd30.CO2);
timeseries_append(&ts_scd30_temperature, scd30.temperature);
timeseries_append(&ts_scd30_humidity, scd30.relative_humidity);
// skip the first readout because the CO2 reading is then invalid
if(!first_scd30_readout) {
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(scd30.CO2, 3);
@ -196,6 +337,7 @@ void loop(void)
epaper_draw_and_hibernate(draw_epaper_callback, false);
}
/*
if(!calibrationDone && now >= 300000) {
uint16_t calValue = scd30.getForcedCalibrationReference();
@ -208,6 +350,7 @@ void loop(void)
calibrationDone = true;
}
*/
}
delay(1000);