Compare commits
No commits in common. "0d63c7cc4b8f23a443771dfb447a9d8e5790fc84" and "8bae83ea32431c3df367ce11bc8f7514e55b33ec" have entirely different histories.
0d63c7cc4b
...
8bae83ea32
@ -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}
|
||||||
|
|||||||
@ -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;
|
||||||
|
|
||||||
|
|||||||
@ -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;
|
||||||
|
|
||||||
|
|||||||
@ -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
|
|
||||||
@ -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,58 +18,50 @@ 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");
|
|
||||||
|
|
||||||
let last = null;
|
let last = null;
|
||||||
|
|
||||||
function updateTime() {
|
function updateTime() {
|
||||||
if (last === null) {
|
if (last === null) {
|
||||||
time.innerText = "Noch keine Daten";
|
time.innerText = "Noch keine Daten";
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
const ageSeconds = Math.floor((Date.now() - last) / 1000);
|
||||||
|
if (ageSeconds === 0) {
|
||||||
|
time.innerText = "Gerade eben";
|
||||||
|
} else {
|
||||||
|
time.innerText = "Vor " + ageSeconds + " Sekunde" + (ageSeconds === 1 ? "" : "n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const ageSeconds = Math.floor((Date.now() - last) / 1000);
|
|
||||||
if (ageSeconds === 0) {
|
function connect() {
|
||||||
time.innerText = "Gerade eben";
|
console.log("connecting websocket...");
|
||||||
} else {
|
const host = location.host || "10.0.0.119";
|
||||||
time.innerText = "Vor " + ageSeconds + " Sekunde" + (ageSeconds === 1 ? "" : "n");
|
const port = location.port || "80";
|
||||||
|
const socket = new WebSocket(`ws://${host}:${port}/ws`);
|
||||||
|
socket.timeout = 1;
|
||||||
|
socket.addEventListener('open', _ => console.log('websocket connected'));
|
||||||
|
socket.addEventListener('message', event => {
|
||||||
|
last = Date.now();
|
||||||
|
updateTime();
|
||||||
|
const parsed = JSON.parse(event.data);
|
||||||
|
console.log("websocket received", parsed);
|
||||||
|
content.innerText = JSON.stringify(parsed, null, 2);
|
||||||
|
});
|
||||||
|
socket.addEventListener('close', _ => {
|
||||||
|
console.log('websocket disconnected');
|
||||||
|
connect();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function connect() {
|
updateTime();
|
||||||
console.log("connecting websocket...");
|
connect();
|
||||||
const host = location.host || "10.0.0.119";
|
setInterval(updateTime, 1000);
|
||||||
const port = location.port || "80";
|
|
||||||
const socket = new WebSocket(`ws://${host}:${port}/ws`);
|
|
||||||
socket.timeout = 1;
|
|
||||||
socket.addEventListener('open', _ => console.log('websocket connected'));
|
|
||||||
socket.addEventListener('message', event => {
|
|
||||||
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();
|
|
||||||
});
|
|
||||||
socket.addEventListener('close', _ => {
|
|
||||||
console.log('websocket disconnected');
|
|
||||||
connect();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
updateTime();
|
|
||||||
connect();
|
|
||||||
setInterval(updateTime, 1000);
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
return changed;
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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(¤tTemperature, ¤tHumidityRelative)) {
|
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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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) {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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) {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user