Initial commit: basic working version
The firmware currently has the following features: - Start a webserver on port 80, serving the measurement data as JSON - Read WLAN credentials from LittleFS - Asynchronous reading of the BME680 every 10 seconds
This commit is contained in:
commit
388d8ffc63
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
.pio
|
||||||
|
.vscode/.browse.c_cpp.db*
|
||||||
|
.vscode/c_cpp_properties.json
|
||||||
|
.vscode/launch.json
|
||||||
|
.vscode/ipch
|
||||||
|
|
||||||
|
data/etc/*
|
||||||
|
!data/etc/.*
|
0
data/etc/.keep
Normal file
0
data/etc/.keep
Normal file
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;
|
||||||
|
};
|
39
include/README
Normal file
39
include/README
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
|
||||||
|
This directory is intended for project header files.
|
||||||
|
|
||||||
|
A header file is a file containing C declarations and macro definitions
|
||||||
|
to be shared between several project source files. You request the use of a
|
||||||
|
header file in your project source file (C, C++, etc) located in `src` folder
|
||||||
|
by including it, with the C preprocessing directive `#include'.
|
||||||
|
|
||||||
|
```src/main.c
|
||||||
|
|
||||||
|
#include "header.h"
|
||||||
|
|
||||||
|
int main (void)
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Including a header file produces the same results as copying the header file
|
||||||
|
into each source file that needs it. Such copying would be time-consuming
|
||||||
|
and error-prone. With a header file, the related declarations appear
|
||||||
|
in only one place. If they need to be changed, they can be changed in one
|
||||||
|
place, and programs that include the header file will automatically use the
|
||||||
|
new version when next recompiled. The header file eliminates the labor of
|
||||||
|
finding and changing all the copies as well as the risk that a failure to
|
||||||
|
find one copy will result in inconsistencies within a program.
|
||||||
|
|
||||||
|
In C, the usual convention is to give header files names that end with `.h'.
|
||||||
|
It is most portable to use only letters, digits, dashes, and underscores in
|
||||||
|
header file names, and at most one dot.
|
||||||
|
|
||||||
|
Read more about using header files in official GCC documentation:
|
||||||
|
|
||||||
|
* Include Syntax
|
||||||
|
* Include Operation
|
||||||
|
* Once-Only Headers
|
||||||
|
* Computed Includes
|
||||||
|
|
||||||
|
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html
|
46
lib/README
Normal file
46
lib/README
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
|
||||||
|
This directory is intended for project specific (private) libraries.
|
||||||
|
PlatformIO will compile them to static libraries and link into executable file.
|
||||||
|
|
||||||
|
The source code of each library should be placed in a an own separate directory
|
||||||
|
("lib/your_library_name/[here are source files]").
|
||||||
|
|
||||||
|
For example, see a structure of the following two libraries `Foo` and `Bar`:
|
||||||
|
|
||||||
|
|--lib
|
||||||
|
| |
|
||||||
|
| |--Bar
|
||||||
|
| | |--docs
|
||||||
|
| | |--examples
|
||||||
|
| | |--src
|
||||||
|
| | |- Bar.c
|
||||||
|
| | |- Bar.h
|
||||||
|
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
|
||||||
|
| |
|
||||||
|
| |--Foo
|
||||||
|
| | |- Foo.c
|
||||||
|
| | |- Foo.h
|
||||||
|
| |
|
||||||
|
| |- README --> THIS FILE
|
||||||
|
|
|
||||||
|
|- platformio.ini
|
||||||
|
|--src
|
||||||
|
|- main.c
|
||||||
|
|
||||||
|
and a contents of `src/main.c`:
|
||||||
|
```
|
||||||
|
#include <Foo.h>
|
||||||
|
#include <Bar.h>
|
||||||
|
|
||||||
|
int main (void)
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
PlatformIO Library Dependency Finder will find automatically dependent
|
||||||
|
libraries scanning project source files.
|
||||||
|
|
||||||
|
More information about PlatformIO Library Dependency Finder
|
||||||
|
- https://docs.platformio.org/page/librarymanager/ldf.html
|
22
platformio.ini
Normal file
22
platformio.ini
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
; PlatformIO Project Configuration File
|
||||||
|
;
|
||||||
|
; Build options: build flags, source filter
|
||||||
|
; Upload options: custom upload port, speed and extra flags
|
||||||
|
; Library options: dependencies, extra library storages
|
||||||
|
; Advanced options: extra scripting
|
||||||
|
;
|
||||||
|
; Please visit documentation for the other options and examples
|
||||||
|
; https://docs.platformio.org/page/projectconf.html
|
||||||
|
|
||||||
|
[env:esp12e]
|
||||||
|
platform = espressif8266
|
||||||
|
board = d1_mini
|
||||||
|
framework = arduino
|
||||||
|
lib_deps =
|
||||||
|
adafruit/Adafruit BME680 Library@^1.1.1
|
||||||
|
adafruit/Adafruit Unified Sensor@^1.1.4
|
||||||
|
SPI
|
||||||
|
Wire
|
||||||
|
|
||||||
|
monitor_speed = 115200
|
||||||
|
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();
|
||||||
|
}
|
185
src/main.cpp
Normal file
185
src/main.cpp
Normal file
|
@ -0,0 +1,185 @@
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
#include <ESP8266WiFi.h>
|
||||||
|
#include <ESP8266WiFiMulti.h>
|
||||||
|
|
||||||
|
#include <LittleFS.h>
|
||||||
|
|
||||||
|
#include <Adafruit_BME680.h>
|
||||||
|
|
||||||
|
#include <ESP8266WebServer.h>
|
||||||
|
|
||||||
|
#include "Config.h"
|
||||||
|
|
||||||
|
#define ENDLESS_LOOP() while(true) { delay(100); }
|
||||||
|
|
||||||
|
#define BME680_UPDATE_INTERVAL 10000 // milliseconds
|
||||||
|
|
||||||
|
ESP8266WiFiMulti wiFiMulti;
|
||||||
|
|
||||||
|
ESP8266WebServer httpServer(80);
|
||||||
|
|
||||||
|
Adafruit_BME680 bme680;
|
||||||
|
|
||||||
|
unsigned long bme680LastReadingTime;
|
||||||
|
unsigned long bme680ReadingDuration;
|
||||||
|
|
||||||
|
String bme680_json_cache;
|
||||||
|
|
||||||
|
void httpHandleIndex(void)
|
||||||
|
{
|
||||||
|
httpServer.send(200, F("application/json"), bme680_json_cache);
|
||||||
|
}
|
||||||
|
|
||||||
|
void httpHandle404(void)
|
||||||
|
{
|
||||||
|
httpServer.send(404, F("text/plain"), F("This is not the page you are looking for."));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void wifi_setup(void)
|
||||||
|
{
|
||||||
|
Serial.println(F("Trying to connect..."));
|
||||||
|
|
||||||
|
//WiFi.setHostname(F("mlmini");
|
||||||
|
|
||||||
|
bool led_on = true;
|
||||||
|
for(size_t tries = 0; tries < 300; 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..."));
|
||||||
|
|
||||||
|
IPAddress apIP(192, 168, 42, 1);
|
||||||
|
|
||||||
|
WiFi.mode(WIFI_AP);
|
||||||
|
WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
|
||||||
|
WiFi.softAP(F("☀️🌧️🌩️"), F("How'sTheWeather?"));
|
||||||
|
WiFi.enableAP(true);
|
||||||
|
|
||||||
|
digitalWrite(LED_BUILTIN, false); // LED ON (active low)
|
||||||
|
} else {
|
||||||
|
digitalWrite(LED_BUILTIN, true); // LED OFF (active low)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void bme680_setup(void)
|
||||||
|
{
|
||||||
|
if(!bme680.begin()) {
|
||||||
|
Serial.println(F("Could not initialize BME680 sensor!"));
|
||||||
|
ENDLESS_LOOP();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up oversampling and filter initialization
|
||||||
|
bme680.setTemperatureOversampling(BME680_OS_8X);
|
||||||
|
bme680.setHumidityOversampling(BME680_OS_2X);
|
||||||
|
bme680.setPressureOversampling(BME680_OS_4X);
|
||||||
|
bme680.setIIRFilterSize(BME680_FILTER_SIZE_3);
|
||||||
|
bme680.setGasHeater(320, 150); // 320*C for 150 ms
|
||||||
|
}
|
||||||
|
|
||||||
|
void httpserver_setup()
|
||||||
|
{
|
||||||
|
httpServer.on("/", httpHandleIndex);
|
||||||
|
httpServer.onNotFound(httpHandle404);
|
||||||
|
|
||||||
|
httpServer.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup()
|
||||||
|
{
|
||||||
|
Serial.begin(115200);
|
||||||
|
|
||||||
|
pinMode(LED_BUILTIN, OUTPUT);
|
||||||
|
|
||||||
|
Serial.println(F("Hello World!"));
|
||||||
|
|
||||||
|
if(!LittleFS.begin()) {
|
||||||
|
Serial.println(F("LittleFS setup failed!"));
|
||||||
|
ENDLESS_LOOP();
|
||||||
|
}
|
||||||
|
|
||||||
|
bme680_setup();
|
||||||
|
|
||||||
|
Config::instance().load();
|
||||||
|
|
||||||
|
// 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
wifi_setup();
|
||||||
|
|
||||||
|
httpserver_setup();
|
||||||
|
|
||||||
|
bme680LastReadingTime = millis() + 10000;
|
||||||
|
bme680ReadingDuration = 0;
|
||||||
|
|
||||||
|
bme680_json_cache = F("{}");
|
||||||
|
}
|
||||||
|
|
||||||
|
void update_bme680_json(void)
|
||||||
|
{
|
||||||
|
std::ostringstream oss;
|
||||||
|
|
||||||
|
oss << "{\n";
|
||||||
|
|
||||||
|
oss << "\"temperature\": {\"value\": " << bme680.temperature << ", \"unit\": \"°C\"},\n";
|
||||||
|
oss << "\"pressure\": {\"value\": " << (bme680.pressure / 100.0) << ", \"unit\": \"hPa\"},\n";
|
||||||
|
oss << "\"humidity\": {\"value\": " << bme680.humidity << ", \"unit\": \"%rH\"},\n";
|
||||||
|
oss << "\"gas_resistance\": {\"value\": " << bme680.gas_resistance << ", \"unit\": \"Ohm\"},\n";
|
||||||
|
oss << "\"generated_at_millis\": " << millis() << "\n";
|
||||||
|
|
||||||
|
oss << "}";
|
||||||
|
|
||||||
|
bme680_json_cache = oss.str().c_str();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
unsigned long now = millis();
|
||||||
|
if((now - bme680LastReadingTime) >= BME680_UPDATE_INTERVAL) {
|
||||||
|
// Tell BME680 to begin measurement.
|
||||||
|
bme680ReadingDuration = bme680.beginReading();
|
||||||
|
if (bme680ReadingDuration == 0) {
|
||||||
|
Serial.println(F("Failed to begin BME680 reading :(F("));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bme680LastReadingTime = now;
|
||||||
|
bme680ReadingDuration -= now; // so far, the "duration" contained the absolute end time. Make it relative to the current timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
if((bme680ReadingDuration != 0) && ((millis() - bme680LastReadingTime) >= bme680ReadingDuration)) {
|
||||||
|
if(!bme680.endReading()) {
|
||||||
|
Serial.println(F("Could not finalize BME680 reading."));
|
||||||
|
} else {
|
||||||
|
// reading is good
|
||||||
|
update_bme680_json();
|
||||||
|
}
|
||||||
|
|
||||||
|
bme680ReadingDuration = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: do something else?
|
||||||
|
httpServer.handleClient();
|
||||||
|
}
|
11
test/README
Normal file
11
test/README
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
|
||||||
|
This directory is intended for PlatformIO Unit Testing and project tests.
|
||||||
|
|
||||||
|
Unit Testing is a software testing method by which individual units of
|
||||||
|
source code, sets of one or more MCU program modules together with associated
|
||||||
|
control data, usage procedures, and operating procedures, are tested to
|
||||||
|
determine whether they are fit for use. Unit testing finds problems early
|
||||||
|
in the development cycle.
|
||||||
|
|
||||||
|
More information about PlatformIO Unit Testing:
|
||||||
|
- https://docs.platformio.org/page/plus/unit-testing.html
|
Loading…
Reference in a new issue