Compare commits

..

7 Commits

9 changed files with 357 additions and 90 deletions

View File

@ -1,12 +1,13 @@
[env:Helligkeit]
[env:Greenhouse]
platform = espressif8266
board = esp12e
framework = arduino
lib_deps = https://github.com/adafruit/Adafruit_TSL2561
https://github.com/knolleary/pubsubclient
build_flags = -DHOSTNAME=\"Helligkeit\"
https://github.com/adafruit/Adafruit_BME680
build_flags = -DHOSTNAME=\"Greenhouse\"
monitor_speed = 115200
upload_protocol = espota
upload_port = 10.0.0.160
upload_port = 10.0.0.169
;upload_port = /dev/ttyUSB0
;upload_speed = 460800

76
src/bme680.h Normal file
View File

@ -0,0 +1,76 @@
#ifndef BME680_H
#define BME680_H
#include <Adafruit_BME680.h>
#include <mqtt.h>
class BME680 {
Adafruit_BME680 bme; // NOLINT(*-interfaces-global-init)
unsigned long last = 0UL;
public:
const String name;
unsigned long intervalMs;
explicit BME680(const String& name, const unsigned long interval_ms = 5000) : name(name), intervalMs(interval_ms) {
//
}
void setup() {
if (bme.begin()) {
Log.printf("BME680 \"%s\": Initialized.\n", name.c_str());
} else {
Log.printf("BME680 \"%s\": Failed to initialize.\n", name.c_str());
}
bme.setTemperatureOversampling(BME680_OS_8X);
bme.setHumidityOversampling(BME680_OS_2X);
bme.setPressureOversampling(BME680_OS_4X);
bme.setIIRFilterSize(BME680_FILTER_SIZE_3);
bme.setGasHeater(320, 150);
}
void loop() {
const auto now = max(1UL, millis());
if (last == 0 || now - last >= intervalMs) {
if (bme.beginReading() == 0) {
Log.printf("BME680 \"%s\": Failed to request reading.\n", name.c_str());
setup();
}
last = now;
}
if (bme.remainingReadingMillis() == 0) {
if (bme.endReading()) {
const auto humidityAbsolute = calculateHumidityAbsolute(bme.temperature, bme.humidity);
mqttPublish(name + "/temperature", bme.temperature, "\xB0""C");
mqttPublish(name + "/pressure", bme.pressure / 100.0, "hPa");
mqttPublish(name + "/humidity/relative", bme.humidity, "%");
mqttPublish(name + "/humidity/absolute", humidityAbsolute, "mg/L");
mqttPublish(name + "/gas", bme.gas_resistance / 1000.0, "KOhms");
mqttPublish(name + "/altitude", bme.readAltitude(1013.25), "m");
} else {
Log.printf("BME680 \"%s\": Failed to complete reading\n", name.c_str());
setup();
}
}
}
static double calculateHumidityAbsolute(const double temperature, const double humidityRelative) {
constexpr auto A = 6.112;
constexpr auto m = 17.67;
constexpr auto Tn = 243.5;
constexpr auto Mw = 18.01534;
constexpr auto R = 8.314462618;
const auto Tk = temperature + 273.15;
const auto P_sat = A * exp((m * temperature) / (temperature + Tn));
const auto P_act = P_sat * (humidityRelative / 100.0);
return (P_act * Mw) / (R * Tk);
}
};
#endif

View File

@ -1,16 +1,31 @@
#include "wifi.h"
#include "tsl2561.h"
#include <bme680.h>
#include "mqtt.h"
#include "tsl2561.h"
#include "wifi.h"
TSL2561 gardenTSL("garden");
BME680 gardenBME("garden");
BME680 greenhouseBME("greenhouse");
void setup() {
delay(500);
MySerial.printf("\n\n\nStartup\n");
Log.printf("\n\n\nStartup\n");
wifiConnect();
sensorSetup();
}
void loop() {
wifiLoop();
sensorLoop();
mqttLoop();
}
if (isSetupTimeAfterBootDelay()) {
gardenTSL.setup();
gardenBME.setup();
greenhouseBME.setup();
}
if (isAfterBootDelay()) {
gardenTSL.loop();
gardenBME.loop();
greenhouseBME.loop();
}
}

View File

@ -1,6 +1,7 @@
#include "mqtt.h"
#include "wifi.h"
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <WiFiClient.h>
@ -8,35 +9,90 @@ WiFiClient client;
PubSubClient mqtt(client);
MySerialClass MySerial(mqtt);
LogClass Log(mqtt);
unsigned long mqttFailureMillis = 0;
// ReSharper disable once CppUseAuto
const String logTopic = String("log/") + HOSTNAME;
// ReSharper disable once CppUseAuto
const String cmdTopic = String("cmd/") + HOSTNAME;
void mqttCallback(char *topic, const uint8_t *payload, unsigned int length) {
char message[500];
length = min(sizeof message - 1, length);
memcpy(message, payload, length);
*(message + length) = 0;
Log.printf("MQTT received: topic=\"%s\", message=\"%s\"\n", topic, message);
if (strcmp(message, "help") == 0) {
Log.printf("HELP\n");
Log.printf(" %s\n", "help");
Log.printf(" %s\n", "info");
Log.printf(" %s\n", "reboot");
} else if (strcmp(message, "info") == 0) {
Log.printf("INFO\n");
Log.printf(" %-10s %s\n", "SSID:", WiFi.SSID().c_str());
Log.printf(" %-10s %s\n", "IP:", WiFi.localIP().toString().c_str());
Log.printf(" %-10s %d\n", "RSSI:", WiFi.RSSI());
Log.printf(" %-10s %s\n", "uptime:", uptimeString().c_str());
} else if (strcmp(message, "reboot") == 0) {
Log.printf("rebooting...\n");
delay(500);
mqtt.disconnect();
delay(500);
EspClass::restart();
}
}
void mqttLoop() {
if (!mqtt.loop() && isWifiConnected() && (mqttFailureMillis == 0 || millis() - mqttFailureMillis >= 3000)) {
mqtt.setServer("10.0.0.50", 1883);
if (mqtt.connect(HOSTNAME, HOSTNAME, 0, false, "disconnected\n")) {
if (mqtt.connect(HOSTNAME, logTopic.c_str(), 0, false, "disconnected\n")) {
yield();
mqttPublish("garten/log", "connected\n");
MySerial.printf("MQTT connected as \"%s\"\n", HOSTNAME);
mqttPublish(logTopic.c_str(), "connected\n");
mqtt.setCallback(mqttCallback);
mqtt.subscribe(cmdTopic.c_str());
Log.printf("MQTT connected as \"%s\"\n", HOSTNAME);
mqttFailureMillis = 0;
} else {
MySerial.printf("Failed to connect MQTT.\n");
Log.printf("Failed to connect MQTT.\n");
mqttFailureMillis = max(1UL, millis());
}
}
}
void mqttPublish(const char *topic, const float value, const char *unit) {
if (!isTimeSet()) {
return;
}
void mqttPublish(const String& topic, const int32_t value, const char *unit) {
mqttPublish(topic, String(value), unit);
}
void mqttPublish(const String& topic, const int64_t value, const char *unit) {
mqttPublish(topic, String(value), unit);
}
void mqttPublish(const String& topic, const uint32_t value, const char *unit) {
mqttPublish(topic, String(value), unit);
}
void mqttPublish(const String& topic, const uint64_t value, const char *unit) {
mqttPublish(topic, String(value), unit);
}
void mqttPublish(const String& topic, const float value, const char *unit) {
mqttPublish(topic, String(value), unit);
}
void mqttPublish(const String& topic, const double value, const char *unit) {
mqttPublish(topic, String(value), unit);
}
void mqttPublish(const String& topic, const String& value, const char *unit) {
char buffer[200];
snprintf(buffer, sizeof buffer, R"({"timestamp": %lld, "value": %s, "unit": "%s"})", time(nullptr), isnan(value) ? "null" : String(value).c_str(), unit);
snprintf(buffer, sizeof buffer, R"({"timestamp": %lld, "value": %s, "unit": "%s"})", time(nullptr), value.c_str(), unit);
mqttPublish(topic, buffer);
}
void mqttPublish(const char *topic, const char *payload) {
mqtt.publish(topic, payload);
void mqttPublish(const String& topic, const String& payload) {
mqtt.publish(topic.c_str(), payload.c_str());
yield();
}

View File

@ -5,13 +5,29 @@
#include "PubSubClient.h"
#include "wifi.h"
extern const String logTopic;
extern const String cmdTopic;
void mqttLoop();
void mqttPublish(const char *topic, float value, const char *unit);
void mqttPublish(const String& topic, int32_t value, const char *unit);
void mqttPublish(const char *topic, const char *payload);
void mqttPublish(const String& topic, int64_t value, const char *unit);
class MySerialClass : public Stream {
void mqttPublish(const String& topic, uint32_t value, const char *unit);
void mqttPublish(const String& topic, uint64_t value, const char *unit);
void mqttPublish(const String& topic, float value, const char *unit);
void mqttPublish(const String& topic, double value, const char *unit);
void mqttPublish(const String& topic, const String& value, const char *unit);
void mqttPublish(const String& topic, const String& payload);
class LogClass final : public Stream {
PubSubClient& mqtt;
@ -27,11 +43,11 @@ class MySerialClass : public Stream {
public:
explicit MySerialClass(PubSubClient& mqttClient) : mqtt(mqttClient) {
explicit LogClass(PubSubClient& mqttClient) : mqtt(mqttClient) {
Serial.begin(115200);
}
size_t write(uint8_t data) override {
size_t write(const uint8_t data) override {
Serial.write(data);
if (bufferWrite < bufferLast) {
*bufferWrite++ = data;
@ -43,10 +59,10 @@ public:
due = true;
if (mqtt.connected()) {
if (overflow > 0) {
mqtt.publish(HOSTNAME, "\n### LOG BUFFER OVERFLOW BY %d BYTES ###\n");
mqttPublish(logTopic, "\n### LOG BUFFER OVERFLOW BY %d BYTES ###\n");
overflow = 0;
}
mqtt.publish(HOSTNAME, (const char *) buffer);
mqttPublish(logTopic, reinterpret_cast<const char *>(buffer));
bufferWrite = buffer;
*bufferWrite = 0;
due = false;
@ -68,6 +84,6 @@ public:
}
};
extern MySerialClass MySerial;
extern LogClass Log;
#endif

View File

@ -1,37 +0,0 @@
#include "tsl2561.h"
#include "mqtt.h"
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_TSL2561_U.h>
Adafruit_TSL2561_Unified tsl = Adafruit_TSL2561_Unified(TSL2561_ADDR_FLOAT, 12345);
void sensorSetup() {
if (tsl.begin()) {
MySerial.printf("TSL2561: Initialized.\n");
} else {
MySerial.printf("TSL2561: Failed to initialize.\n");
}
tsl.enableAutoRange(true);
tsl.setIntegrationTime(TSL2561_INTEGRATIONTIME_402MS);
}
void sensorRead() {
sensors_event_t event;
if (!tsl.getEvent(&event)) {
MySerial.printf("TSL2561: Failed to read.\n");
sensorSetup();
} else {
mqttPublish("garten/helligkeit", event.light, "lux");
}
}
void sensorLoop() {
unsigned long now = millis();
static unsigned long last = 0;
if (last == 0 || now - last >= 2000) {
last = max(1UL, now);
sensorRead();
}
}

View File

@ -1,8 +1,57 @@
#ifndef TSL2561_H
#define TSL2561_H
void sensorSetup();
#include <Adafruit_TSL2561_U.h>
void sensorLoop();
class TSL2561 {
Adafruit_TSL2561_Unified tsl;
unsigned long last = 0UL;
public:
const String name;
unsigned long intervalMs;
explicit TSL2561(const String& name, const uint8_t address = TSL2561_ADDR_FLOAT, const unsigned long interval_ms = 5000) : tsl(Adafruit_TSL2561_Unified(address)), name(name), intervalMs(interval_ms) {
//
}
void setup() {
if (tsl.begin()) {
Log.printf("TSL2561 \"%s\": Initialized.\n", name.c_str());
} else {
Log.printf("TSL2561 \"%s\": Failed to initialize.\n", name.c_str());
}
tsl.enableAutoRange(true);
tsl.setIntegrationTime(TSL2561_INTEGRATIONTIME_402MS);
}
void loop() {
const auto now = millis();
if (last == 0 || now - last >= intervalMs) {
last = max(1UL, now);
read();
}
}
private:
void read() {
uint16_t broadband;
uint16_t ir;
tsl.getLuminosity(&broadband, &ir);
const auto illuminance = tsl.calculateLux(broadband, ir);
if (illuminance == 65536) {
Log.printf("TSL2561 \"%s\": Failed to read.\n", name.c_str());
setup();
} else {
mqttPublish(name + "/illuminance", illuminance, "lux");
}
}
};
#endif

View File

@ -1,37 +1,111 @@
#include <TZ.h>
#include <ArduinoOTA.h>
#include "wifi.h"
#include <ArduinoOTA.h>
#include <TZ.h>
#include "mqtt.h"
bool wifiConnected = false;
#define WIFI_TIMEOUT_MS 10000
#define WIFI_SSID "HappyNet"
#define WIFI_PASSWORD "1Grausame!Sackratte7"
#define NTP_SERVER "107.189.12.98" /* pool.ntp.org */
#define BOOT_DELAY_SEC 5
bool timeSet = false;
auto wifiConnected = false;
unsigned long wifiSince = 0;
auto timeSetSince = 0UL;
unsigned long wifiConnectBeginMillis = 0;
auto bootDelayOver = false;
void wifiConnect() {
wifiSince = max(1UL, millis());
wifiConnectBeginMillis = max(1UL, millis());
WiFi.setHostname(HOSTNAME);
WiFi.begin("HappyNet", "1Grausame!Sackratte7");
configTime(TZ_Europe_Berlin, "107.189.12.98");
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
configTime(TZ_Europe_Berlin, NTP_SERVER);
ArduinoOTA.onStart([]() {
Log.printf("OTA start...\n");
});
ArduinoOTA.onError([](const ota_error_t error) {
const char *name;
switch (error) {
case OTA_AUTH_ERROR: name = "AUTH";
break;
case OTA_BEGIN_ERROR: name = "BEGIN";
break;
case OTA_CONNECT_ERROR: name = "CONNECT";
break;
case OTA_RECEIVE_ERROR: name = "RECEIVE";
break;
case OTA_END_ERROR: name = "END";
break;
default:
name = "[???]";
break;
}
Log.printf("OTA error #%d: %s\n", error, name);
});
ArduinoOTA.onEnd([]() {
Log.printf("OTA success\n");
});
ArduinoOTA.begin();
yield();
}
uint64_t uptimeOffset = 0;
void uptimeLoop() {
const auto now = millis();
static auto uptimeLast = 0UL;
if (now < uptimeLast) {
uptimeOffset += static_cast<uint64_t>(-1);
}
uptimeLast = now;
}
uint64_t uptimeMillis() {
return uptimeOffset + millis();
}
String uptimeString() {
const auto ms = uptimeMillis();
const auto days = static_cast<int>(ms / (24 * 60 * 60 * 1000));
const auto hours = static_cast<int>(ms / (60 * 60 * 1000) % 24);
const auto minutes = static_cast<int>(ms / (60 * 1000) % 60);
const auto seconds = static_cast<int>(ms / 1000 % 60);
char buffer[32];
if (days > 0) {
snprintf(buffer, sizeof buffer, "%d day%s, %d hour%s", days, days == 1 ? "" : "s", hours, hours == 1 ? "" : "s");
} else if (hours > 0) {
snprintf(buffer, sizeof buffer, "%d hour%s, %d minute%s", hours, hours == 1 ? "" : "s", minutes, minutes == 1 ? "" : "s");
} else if (minutes > 0) {
snprintf(buffer, sizeof buffer, "%d minute%s, %d second%s", minutes, minutes == 1 ? "" : "s", seconds, seconds == 1 ? "" : "s");
} else {
snprintf(buffer, sizeof buffer, "%d second%s", seconds, seconds == 1 ? "" : "s");
}
return {buffer};
}
void timeLoop() {
const time_t now = time(nullptr);
const time_t nowHour = now / 3600;
const auto now = time(nullptr);
const auto nowHour = now / 3600;
static unsigned long lastHour = 0;
if (!timeSet) {
if (timeSetSince == 0) {
if (now > 1700000000) {
timeSet = true;
timeSetSince = now;
lastHour = nowHour;
MySerial.printf("Got time: %s\n", getTimeString().c_str());
Log.printf("Got time: %s\n", getTimeString().c_str());
Log.printf("Delaying boot for %d seconds.\n", BOOT_DELAY_SEC);
}
} else {
if (lastHour != nowHour) {
lastHour = nowHour;
MySerial.printf("%s\n", getTimeString().c_str());
Log.printf("%s\n", getTimeString().c_str());
}
if (!bootDelayOver) {
bootDelayOver = time(nullptr) - timeSetSince > BOOT_DELAY_SEC;
if (bootDelayOver) {
Log.printf("Boot delay complete.\n");
}
}
}
}
@ -41,9 +115,9 @@ void wifiLoop() {
if (WiFi.localIP() == 0UL) {
if (wifiConnected) {
wifiConnected = false;
MySerial.printf("WiFi disconnected.\n");
Log.printf("WiFi disconnected.\n");
wifiConnect();
} else if (wifiSince == 0 || millis() - wifiSince >= 10000) {
} else if (wifiConnectBeginMillis == 0 || millis() - wifiConnectBeginMillis >= WIFI_TIMEOUT_MS) {
WiFi.disconnect();
yield();
wifiConnect();
@ -51,10 +125,12 @@ void wifiLoop() {
} else {
if (!wifiConnected) {
wifiConnected = true;
MySerial.printf("WiFi connected as \"%s\" (%s)\n", HOSTNAME, WiFi.localIP().toString().c_str());
wifiConnectBeginMillis = 0;
Log.printf("WiFi connected as \"%s\" (%s)\n", HOSTNAME, WiFi.localIP().toString().c_str());
}
timeLoop();
}
uptimeLoop();
timeLoop();
}
String getTimeString() {
@ -71,6 +147,15 @@ bool isWifiConnected() {
return wifiConnected;
}
bool isTimeSet() {
return timeSet;
}
bool isSetupTimeAfterBootDelay() {
static auto needed = true;
if (needed && bootDelayOver) {
needed = false;
return true;
}
return false;
}
bool isAfterBootDelay() {
return bootDelayOver;
}

View File

@ -11,6 +11,12 @@ String getTimeString();
bool isWifiConnected();
bool isTimeSet();
bool isSetupTimeAfterBootDelay();
bool isAfterBootDelay();
uint64_t uptimeMillis();
String uptimeString();
#endif