Compare commits

..

No commits in common. "0d63c7cc4b8f23a443771dfb447a9d8e5790fc84" and "8bae83ea32431c3df367ce11bc8f7514e55b33ec" have entirely different histories.

18 changed files with 192 additions and 296 deletions

View File

@ -19,13 +19,15 @@ monitor_filters = esp32_exception_decoder
build_flags = -DWIFI_SSID=\"HappyNet\" build_flags = -DWIFI_SSID=\"HappyNet\"
-DWIFI_PKEY=\"1Grausame!Sackratte7\" -DWIFI_PKEY=\"1Grausame!Sackratte7\"
[env:test_OTA] [env:test]
platform = ${common.platform} platform = ${common.platform}
board = ${common.board} board = ${common.board}
framework = ${common.framework} framework = ${common.framework}
lib_deps = ${common.lib_deps} lib_deps = ${common.lib_deps}
upload_port = 10.0.0.119 upload_port = 10.0.0.119
upload_protocol = espota upload_protocol = espota
;upload_port = ${common.upload_port}
;upload_speed = ${common.upload_speed}
monitor_port = ${common.monitor_port} monitor_port = ${common.monitor_port}
monitor_speed = ${common.monitor_speed} monitor_speed = ${common.monitor_speed}
monitor_filters = ${common.monitor_filters} monitor_filters = ${common.monitor_filters}
@ -33,21 +35,7 @@ build_flags = ${common.build_flags}
-DWIFI_HOST=\"Test\" -DWIFI_HOST=\"Test\"
-DNODE_TEST -DNODE_TEST
[env:test_USB] [env:heizung]
platform = ${common.platform}
board = ${common.board}
framework = ${common.framework}
lib_deps = ${common.lib_deps}
upload_port = ${common.upload_port}
upload_speed = ${common.upload_speed}
monitor_port = ${common.monitor_port}
monitor_speed = ${common.monitor_speed}
monitor_filters = ${common.monitor_filters}
build_flags = ${common.build_flags}
-DWIFI_HOST=\"Test\"
-DNODE_TEST
[env:heizung_USB]
platform = ${common.platform} platform = ${common.platform}
board = ${common.board} board = ${common.board}
framework = ${common.framework} framework = ${common.framework}

View File

@ -7,7 +7,7 @@
#include "patrix/sensor/DHT22.h" #include "patrix/sensor/DHT22.h"
#include "patrix/sensor/Max6675Sensor.h" #include "patrix/sensor/Max6675Sensor.h"
class NodeHeizung final : public Node<100, 14> { class NodeHeizung final : public Node {
const unsigned long MAX_AGE_SECONDS = 10; const unsigned long MAX_AGE_SECONDS = 10;

View File

@ -1,14 +1,12 @@
#ifndef NODE_TEST_H #ifndef NODE_TEST_H
#define NODE_TEST_H #define NODE_TEST_H
#include <AsyncWebSocket.h>
#include "patrix/Node.h" #include "patrix/Node.h"
#include "patrix/sensor/Dallas.h" #include "patrix/sensor/Dallas.h"
#include "patrix/sensor/DallasSensor.h" #include "patrix/sensor/DallasSensor.h"
#include "patrix/sensor/DHT22.h" #include "patrix/sensor/DHT22.h"
class NodeTest final : public Node<100, 4> { class NodeTest final : public Node {
const unsigned long MAX_AGE_SECONDS = 10; const unsigned long MAX_AGE_SECONDS = 10;

View File

@ -1,82 +0,0 @@
#ifndef CACHE_H
#define CACHE_H
#include "log.h"
template<size_t ENTRY_COUNT, size_t VALUE_COUNT>
class Cache {
public:
struct Entry {
time_t timestamp;
double data[VALUE_COUNT];
};
private:
Entry entries[ENTRY_COUNT];
Entry *r = entries;
Entry *w = entries;
void clearWriteEntry() {
for (double& value: r->data) {
value = NAN;
}
}
Entry *findEntry(time_t timestamp) {
if (w->timestamp == timestamp) {
return w;
}
auto wNext = (w + 1 - entries) % ENTRY_COUNT + entries;
if (w->timestamp == 0) {
w->timestamp = timestamp;
clearWriteEntry();
} else if (wNext != r) {
w = wNext;
w->timestamp = timestamp;
clearWriteEntry();
} else {
return nullptr;
}
return w;
}
public:
void put(const time_t timestamp, const int valueIndex, const double value) {
if (valueIndex >= VALUE_COUNT) {
error("Cache.put: valueIndex to big for valueCount: valueIndex=%d, valueCount=%d", valueIndex, VALUE_COUNT);
return;
}
Entry *entry = findEntry(timestamp);
if (entry == nullptr) {
warn("Cache full!");
return;
}
entry->data[valueIndex] = value;
}
Entry *read() {
if (w != r || r->timestamp != 0) {
return r;
}
return nullptr;
}
void skip() {
if (w != r || w->timestamp != 0) {
r->timestamp = 0;
r = (r + 1 - entries) % ENTRY_COUNT + entries;
}
}
};
#endif

View File

@ -8,20 +8,6 @@ const char *INDEX_HTML = R"(<!DOCTYPE html>
<style> <style>
body { body {
font-size: 4vw; font-size: 4vw;
font-family: monospace;
}
table {
width: 100%;
}
th {
text-align: left;
font-weight: inherit;
}
td {
text-align: right;
} }
@media (min-width: 1200px) { @media (min-width: 1200px) {
@ -32,10 +18,9 @@ const char *INDEX_HTML = R"(<!DOCTYPE html>
</style> </style>
</head> </head>
<body> <body>
<table id="content"></table> <pre id="content"></pre>
<pre id="time"></pre> <pre id="time"></pre>
<script> <script>
const map = new Map();
const time = document.getElementById("time"); const time = document.getElementById("time");
const content = document.getElementById("content"); const content = document.getElementById("content");
@ -63,17 +48,10 @@ const char *INDEX_HTML = R"(<!DOCTYPE html>
socket.addEventListener('open', _ => console.log('websocket connected')); socket.addEventListener('open', _ => console.log('websocket connected'));
socket.addEventListener('message', event => { socket.addEventListener('message', event => {
last = Date.now(); last = Date.now();
const parsed = JSON.parse(event.data);
map.set(parsed.name, parsed);
const innerHTML = `<th>${parsed.name}</th><td>${parsed.value?.toFixed(1) || '-'}</td>`;
let tr = document.getElementById(parsed.name);
if (!tr) {
tr = document.createElement("tr");
tr.setAttribute("id", parsed.name);
content.appendChild(tr);
}
tr.innerHTML = innerHTML;
updateTime(); updateTime();
const parsed = JSON.parse(event.data);
console.log("websocket received", parsed);
content.innerText = JSON.stringify(parsed, null, 2);
}); });
socket.addEventListener('close', _ => { socket.addEventListener('close', _ => {
console.log('websocket disconnected'); console.log('websocket disconnected');

View File

@ -1,38 +1,33 @@
#ifndef NODE_H #ifndef NODE_H
#define NODE_H #define NODE_H
#include "Cache.h"
#include "clock.h"
#include "sensor/Sensor.h" #include "sensor/Sensor.h"
template<size_t ENTRY_COUNT, size_t VALUE_COUNT>
class Node { class Node {
Cache<ENTRY_COUNT, VALUE_COUNT> cache;
public: public:
Node() : cache() { virtual ~Node() = default;
//
void toJson(const JsonObject& json) {
auto index = 0;
Sensor *sensor;
while ((sensor = getSensor(index++)) != nullptr) {
sensor->toJson(json[sensor->getName()].to<JsonObject>());
}
} }
virtual ~Node() = default; void send(const bool mqtt, const bool websocket) {
JsonDocument json;
toJson(json.to<JsonObject>());
mqttPublish(String(WiFiClass::getHostname()) + "/json", json, mqtt, websocket);
}
void loop() { void loop() {
loopBeforeSensors(); loopBeforeSensors();
loopSensors(); const auto changed = loopSensors();
cacheSend(); if (changed) {
} send(true, true);
void websocketSendOne(AsyncWebSocketClient *client) {
auto sensorIndex = 0;
Sensor *sensor;
while ((sensor = this->getSensor(sensorIndex++)) != nullptr) {
auto valueIndex = 0;
Value *value;
while ((value = sensor->getValue(valueIndex++)) != nullptr) {
client->text(value->toJson(true));
}
} }
} }
@ -44,53 +39,16 @@ public:
private: private:
void loopSensors() { bool loopSensors() {
const auto timestamp = time(nullptr); auto changed = false;
auto cacheIndex = 0; auto index = 0;
auto sensorIndex = 0;
Sensor *sensor; Sensor *sensor;
while ((sensor = getSensor(sensorIndex++)) != nullptr) { while ((sensor = getSensor(index++)) != nullptr) {
sensor->loopBeforeValues(); if (sensor->loop()) {
loopValues(timestamp, cacheIndex, sensor); changed = true;
} }
} }
return changed;
void loopValues(const time_t timestamp, int& cacheIndex, Sensor *sensor) {
auto valueIndex = 0;
Value *value;
while ((value = sensor->getValue(valueIndex++)) != nullptr) {
if (value->loop()) {
cache.put(timestamp, cacheIndex++, value->getCurrentValue());
}
}
}
void cacheSend() {
const auto entry = cache.read();
if (entry == nullptr) {
return;
}
auto cacheIndex = 0;
auto sensorIndex = 0;
Sensor *sensor;
while ((sensor = getSensor(sensorIndex++)) != nullptr) {
auto valueIndex = 0;
Value *value;
while ((value = sensor->getValue(valueIndex++)) != nullptr) {
const auto v = entry->data[cacheIndex++];
const auto t = entry->timestamp;
if (isCorrectTime(t) && !isnan(v)) {
auto json = value->toJson(t, v, false);
if (mqttPublish(value->name + "/persist", json, NO_RETAIN)) {
entry->data[cacheIndex++] = NAN;
} else {
return;
}
}
}
}
cache.skip();
} }
}; };

View File

@ -2,10 +2,10 @@
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>
#include "INDEX_HTML.h"
#include "log.h" #include "log.h"
#include "main.h" #include "main.h"
#include "system.h" #include "system.h"
#include "INDEX_HTML.h"
AsyncWebServer server(80); AsyncWebServer server(80);
@ -31,7 +31,7 @@ void httpSetup() {
switch (type) { switch (type) {
case WS_EVT_CONNECT: case WS_EVT_CONNECT:
t = "CONNECT"; t = "CONNECT";
node.websocketSendOne(client); node.send(false, true); // TODO this currently sends to ALL
break; break;
case WS_EVT_DISCONNECT: case WS_EVT_DISCONNECT:
t = "DISCONNECT"; t = "DISCONNECT";
@ -62,6 +62,6 @@ void httpLoop() {
ws.cleanupClients(); ws.cleanupClients();
} }
void websocketSendAll(const String& payload) { void httpPublish(char *payload) {
ws.textAll(payload); ws.textAll(payload);
} }

View File

@ -1,12 +1,10 @@
#ifndef HTTP_H #ifndef HTTP_H
#define HTTP_H #define HTTP_H
#include <Arduino.h>
void httpSetup(); void httpSetup();
void httpLoop(); void httpLoop();
void websocketSendAll(const String& payload); void httpPublish(char *payload);
#endif #endif

View File

@ -7,13 +7,9 @@
#include "mqtt.h" #include "mqtt.h"
#include "wifi.h" #include "wifi.h"
// ReSharper disable CppUnusedIncludeDirective
#include "sensor/DallasSensor.h" #include "sensor/DallasSensor.h"
#include "sensor/DHT22.h" #include "sensor/DHT22.h"
#include "sensor/Max6675Sensor.h" #include "sensor/Max6675Sensor.h"
// ReSharper restore CppUnusedIncludeDirective
// ReSharper disable CppUseAuto
#ifdef NODE_TEST #ifdef NODE_TEST
NodeTest node = NodeTest(); NodeTest node = NodeTest();
@ -23,8 +19,6 @@ NodeTest node = NodeTest();
NodeHeizung node = NodeHeizung(); NodeHeizung node = NodeHeizung();
#endif #endif
// ReSharper restore CppUseAuto
void setup() { void setup() {
logSetup(); logSetup();
bootDelay(); bootDelay();

View File

@ -1,10 +1,8 @@
#ifndef MAIN_H #ifndef MAIN_H
#define MAIN_H #define MAIN_H
// ReSharper disable CppUnusedIncludeDirective
#include "NodeHeizung.h"
#include "NodeTest.h" #include "NodeTest.h"
// ReSharper restore CppUnusedIncludeDirective #include "NodeHeizung.h"
#ifdef NODE_TEST #ifdef NODE_TEST
extern NodeTest node; extern NodeTest node;

View File

@ -3,6 +3,7 @@
#include <PubSubClient.h> #include <PubSubClient.h>
#include <WiFi.h> #include <WiFi.h>
#include "http.h"
#include "log.h" #include "log.h"
#include "wifi.h" #include "wifi.h"
@ -46,6 +47,19 @@ void mqttLoop() {
} }
} }
bool mqttPublish(const String& topic, const String& payload, const Retain retain) { bool mqttPublish(const String& topic, const double value, const bool retained) {
return mqtt.publish(topic.c_str(), payload.c_str(), retain == RETAIN); return mqtt.publish(topic.c_str(), String(value).c_str(), retained);
}
bool mqttPublish(const String& topic, const JsonDocument& json, const bool mqttSend, const bool websocketSend) {
char buffer[512];
serializeJson(json, buffer);
if (websocketSend) {
httpPublish(buffer);
}
if (mqttSend) {
return mqtt.publish(topic.c_str(), buffer);
}
return true;
} }

View File

@ -1,14 +1,12 @@
#ifndef MQTT_H #ifndef MQTT_H
#define MQTT_H #define MQTT_H
#include <AsyncWebSocket.h> #include <ArduinoJson.h>
enum Retain {
NO_RETAIN, RETAIN
};
void mqttLoop(); void mqttLoop();
bool mqttPublish(const String& topic, const String& payload, Retain retain); bool mqttPublish(const String& topic, const double value, bool retained);
bool mqttPublish(const String& topic, const JsonDocument& json, bool mqtt, bool websocketSend);
#endif #endif

View File

@ -19,7 +19,7 @@ class DHT22 final : public Sensor {
public: public:
DHT22(const int pin, DHT22(const int pin,
const String& name, const char *name,
const double temperatureThreshold, const double temperatureThreshold,
const double humidityRelativeThreshold, const double humidityRelativeThreshold,
const double humidityAbsoluteThreshold, const double humidityAbsoluteThreshold,
@ -27,18 +27,18 @@ public:
const unsigned long overdueSeconds const unsigned long overdueSeconds
) : Sensor(name), ) : Sensor(name),
sensor(pin, DHT_TYPE_22), sensor(pin, DHT_TYPE_22),
temperature(name + "/temperature", temperatureThreshold, maxAgeSeconds, overdueSeconds), temperature(name, "temperature", temperatureThreshold, maxAgeSeconds, overdueSeconds),
humidityRelative(name + "/humidity/relative", humidityRelativeThreshold, maxAgeSeconds, overdueSeconds), humidityRelative(name, "humidity/relative", humidityRelativeThreshold, maxAgeSeconds, overdueSeconds),
humidityAbsolute(name + "/humidity/absolute", humidityAbsoluteThreshold, maxAgeSeconds, overdueSeconds) { humidityAbsolute(name, "humidity/absolute", humidityAbsoluteThreshold, maxAgeSeconds, overdueSeconds) {
// //
} }
void loopBeforeValues() override { void loopBeforeValues() override {
float currentTemperature, currentHumidityRelative; float t, hr;
if (sensor.measure(&currentTemperature, &currentHumidityRelative)) { if (sensor.measure(&t, &hr)) {
temperature.update(currentTemperature); temperature.update(t);
humidityRelative.update(currentHumidityRelative); humidityRelative.update(hr);
humidityAbsolute.update(6.112 * exp(17.67 * currentTemperature / (243.5 + currentTemperature)) * currentHumidityRelative * 2.1674 / (currentTemperature + 273.15)); humidityAbsolute.update(6.112 * exp(17.67 * t / (243.5 + t)) * hr * 2.1674 / (t + 273.15));
info("%s: temperature=%.1f^C, humidityRelative=%.0f%%, humidityAbsolute=%.0fmg/L", name, temperature.getCurrentValue(), humidityRelative.getCurrentValue(), humidityAbsolute.getCurrentValue()); info("%s: temperature=%.1f^C, humidityRelative=%.0f%%, humidityAbsolute=%.0fmg/L", name, temperature.getCurrentValue(), humidityRelative.getCurrentValue(), humidityAbsolute.getCurrentValue());
} }
} }

View File

@ -1,9 +1,9 @@
#ifndef DALLAS_H #ifndef DALLAS_H
#define DALLAS_H #define DALLAS_H
#include "DallasTemperature.h"
#include "OneWire.h"
#include "../log.h" #include "../log.h"
#include "OneWire.h"
#include "DallasTemperature.h"
#define DALLAS_INTERVAL_MILLISECONDS 2000 #define DALLAS_INTERVAL_MILLISECONDS 2000
@ -42,7 +42,7 @@ public:
const auto count = sensors.getDeviceCount(); const auto count = sensors.getDeviceCount();
if (count != 0) { if (count != 0) {
uint64_t address; uint64_t address;
for (auto index = 0; index < count; ++index) { for (int index = 0; index < count; ++index) {
sensors.getAddress(reinterpret_cast<uint8_t *>(&address), index); sensors.getAddress(reinterpret_cast<uint8_t *>(&address), index);
info("Dallas %d/%d 0x%016llX = %5.1f^C", index + 1, count, address, sensors.getTempC(reinterpret_cast<uint8_t *>(&address))); info("Dallas %d/%d 0x%016llX = %5.1f^C", index + 1, count, address, sensors.getTempC(reinterpret_cast<uint8_t *>(&address)));
} }

View File

@ -19,14 +19,14 @@ public:
DallasSensor( DallasSensor(
Dallas& sensors, Dallas& sensors,
const uint64_t address, const uint64_t address,
const String& name, const char *name,
const double threshold, const double threshold,
const unsigned long maxAgeSeconds, const unsigned long maxAgeSeconds,
const unsigned long overdueSeconds const unsigned long overdueSeconds
) : Sensor(name), ) : Sensor(name),
sensors(sensors), sensors(sensors),
address(address), address(address),
temperature(name + "/temperature", threshold, maxAgeSeconds, overdueSeconds) { temperature(name, "temperature", threshold, maxAgeSeconds, overdueSeconds) {
// //
} }

View File

@ -20,13 +20,13 @@ public:
const int8_t pinMISO, const int8_t pinMISO,
const int8_t pinCS, const int8_t pinCS,
const int8_t pinCLK, const int8_t pinCLK,
const String& name, const char *name,
const double threshold, const double threshold,
const unsigned long maxAgeSeconds, const unsigned long maxAgeSeconds,
const unsigned long overdueSeconds const unsigned long overdueSeconds
) : Sensor(name), ) : Sensor(name),
sensor(pinCLK, pinCS, pinMISO), sensor(pinCLK, pinCS, pinMISO),
temperature(name + "/temperature", threshold, maxAgeSeconds, overdueSeconds) { temperature(name, "temperature", threshold, maxAgeSeconds, overdueSeconds) {
// //
} }

View File

@ -6,20 +6,63 @@
class Sensor { class Sensor {
protected:
const char *name;
public: public:
const String& name; explicit Sensor(const char *name): name(name) {
explicit Sensor(const String& name): name(name) {
// //
} }
virtual ~Sensor() = default; virtual ~Sensor() = default;
const char *getName() const {
return name;
}
bool loop() {
loopBeforeValues();
const auto changed = loopValues();
if (changed) {
send();
}
return changed;
}
void send() {
JsonDocument json;
toJson(json.to<JsonObject>());
mqttPublish(String(name) + "/json", json, true, false);
}
void toJson(const JsonObject& json) {
auto index = 0;
Value *value;
while ((value = getValue(index++)) != nullptr) {
value->toJson(json[value->getName()].to<JsonObject>());
}
}
virtual void loopBeforeValues() {} virtual void loopBeforeValues() {}
virtual Value *getValue(int index) = 0; virtual Value *getValue(int index) = 0;
private:
bool loopValues() {
auto changed = false;
auto index = 0;
Value *value;
while ((value = getValue(index++)) != nullptr) {
if (value->loop()) {
changed = true;
}
}
return changed;
}
}; };
#endif #endif

View File

@ -1,13 +1,16 @@
#ifndef VALUE_H #ifndef VALUE_H
#define VALUE_H #define VALUE_H
#include <ArduinoJson.h> #include <WiFi.h>
#include <patrix/http.h>
#include <patrix/log.h> #include <patrix/log.h>
#include <patrix/mqtt.h> #include <patrix/mqtt.h>
class Value { class Value {
const char *parent;
const char *name;
double threshold; double threshold;
unsigned long maxAgeMillis; unsigned long maxAgeMillis;
@ -26,17 +29,17 @@ class Value {
public: public:
const String name;
Value( Value(
const String& name, const char *parent,
const char *name,
const double threshold, const double threshold,
const unsigned long maxAgeSeconds, const unsigned long maxAgeSeconds,
const unsigned long overdueSeconds const unsigned long overdueSeconds
) : threshold(threshold), ) : parent(parent),
name(name),
threshold(threshold),
maxAgeMillis(maxAgeSeconds * 1000), maxAgeMillis(maxAgeSeconds * 1000),
overdueSeconds(overdueSeconds), overdueSeconds(overdueSeconds) {
name(name) {
// //
} }
@ -46,46 +49,54 @@ public:
currentEpoch = time(nullptr); currentEpoch = time(nullptr);
} }
void toJson(const JsonObject& json) const {
json["value"] = currentValue;
json["time"] = currentEpoch;
}
void send() {
mqttPublish(String(parent) + "/" + name + "/plain", currentValue, true);
JsonDocument json;
toJson(json.to<JsonObject>());
mqttPublish(String(parent) + "/" + name + "/json", json, true, false);
markSent();
}
bool loop() { bool loop() {
if (!isnan(currentValue) && millis() - currentMillis > maxAgeMillis) { if (!isnan(currentValue) && millis() - currentMillis > maxAgeMillis) {
warn("Value too old: %s", name.c_str()); warn("Value too old: %s/%s", parent, name);
update(NAN); update(NAN);
} }
const auto now = time(nullptr);
const auto dueToNAN = isnan(currentValue) != isnan(sentValue); const auto dueToNAN = isnan(currentValue) != isnan(sentValue);
const auto dueToThreshold = abs(sentValue - currentValue) >= threshold; const auto dueToThreshold = abs(sentValue - currentValue) >= threshold;
const auto dueToTime = sentInterval != 0 && sentInterval != now / overdueSeconds; const auto dueToTime = sentInterval != 0 && sentInterval != time(nullptr) / overdueSeconds;
const auto changed = dueToNAN || dueToThreshold || dueToTime; const auto changed = dueToNAN || dueToThreshold || dueToTime;
if (changed) { if (changed) {
info("%s = %f", name.c_str(), currentValue); send();
mqttPublish(name + "/retain", String(currentValue), RETAIN);
websocketSendAll(toJson(true));
sentValue = currentValue;
sentInterval = now / overdueSeconds;
} }
return changed; return changed;
} }
const char *getName() const {
return name;
}
double getCurrentValue() const { double getCurrentValue() const {
return currentValue; return currentValue;
} }
String toJson(const bool addNameField) const { time_t getCurrentEpoch() const {
return toJson(currentEpoch, currentValue, addNameField); return currentEpoch;
} }
String toJson(const time_t timestamp, const double value, const bool addNameField) const { private:
JsonDocument json;
if (addNameField) {
json["name"] = name.c_str();
}
json["timestamp"] = timestamp;
json["value"] = value;
char buffer[256]; void markSent() {
serializeJson(json, buffer, sizeof buffer); sentValue = currentValue;
return String(buffer); sentInterval = time(nullptr) / overdueSeconds;
} }
}; };