Okay, es ist besser, es in 2 PR zu machen, aber für mich löst das nur 1% meines Problems. Vielleicht verstehe ich es falsch, aber ich habe die Argumente der anderen Personen in diesem Sinne verstanden.
Ich möchte fast die gesamte Historie behalten. Das Problem ist, dass die Datenbank extrem schnell wächst, weil ich sehr oft unnötige Duplikate speichere…
Daher löst das alleinige keep_history in meinem Fall 99% des Problems nicht. Geht es euch genauso @lmilcent und @VonOx? Oder habe ich euren Anwendungsfall falsch verstanden?
Ein einfaches Beispiel:
Ich habe eine Sonoff POWR2-Steckdose mit Tasmota, die eine Pumpe versorgt + den Stromverbrauch + einen geschweißten Kontakt am Pin IO4 für einen Pegel, der die Pumpe abschaltet und Ventile über eine Szene öffnet und schließt.
Die Historie des Stromverbrauchs interessiert mich fast immer,
Die Historie der Pumpe interessiert mich nicht,
Die Historie des niedrigen Pegels interessiert mich, aber nur, wenn sich der Zustand ändert (manchmal 1 Mal pro Woche / manchmal 1 Mal pro Monat) und im Moment speichert es jede Minute…
Ja, der Bedarf ist auf meiner Seite ähnlich, wir haben uns verstanden.
Aus Design-/Logik-Sicht ist das für die Aggregation ultra kompliziert, weil sie davon ausgeht, dass es in einem Zeitraum keine Werte gab.
Wenn sich also Zustand/Wert ändert, muss man zuerst den vorherigen Zustand direkt davor einfügen (damit eine korrekte Chronologie entsteht. Das ist ein bisschen tricky, aber es könnte funktionieren.
Das lässt sich relativ einfach testen (um das Verhalten der Aggregation und das Ergebnis in Diagrammen zu sehen).
Beispiel: Raumtemperatur
Um 8 Uhr => 19°C
Um 9 Uhr => 19°C (ich mache nichts in der DB)
Um 9:30 Uhr => 19,5°C (da schreibe ich den alten Wert und dann den neuen in die DB)
Das ist ultra simplistisch, aber nur zur Veranschaulichung.
Ja, das war genau das, was ich in meinem anderen Beitrag vorgeschlagen habe. Und genau das habe ich bei Netatmo gemacht
Ich könnte euch sogar zeigen, wie das auf dem Aggregator aussieht
Bah die States sind ja nach Feature, also ist es in der gleichen Logik. Bei einer Philips Hue Lampe zum Beispiel, kann es interessant sein, das Ein/Aus zu behalten, aber bei den Farben… für mich ist das egal… oder?
@pierre-gilles, @AlexTrovato, @VonOx,
Ich erlaube mir, das Thema wieder aufzugreifen, da es für einige etwas schwergewichtige Instanzen möglicherweise kritisch sein könnte.
Zur Erinnerung die Themen der Debatte:
Falls wir die gesamte Roadmap definieren und eine Spezifikation validieren können, bin ich bereit, mich so weit wie möglich darum zu kümmern und einen ersten Entwurf schnell zu erstellen.
Zur Erinnerung auch mein Vorschlag (modifiziert nach dem Vorschlag von Pierre-Gilles in der Reihenfolge) zur Einleitung der Diskussion:
1. PR:
Frontend:
Ein Auswahlfeld für jedes Feature „Historische Zustände der Feature beibehalten“ mit denselben Optionen wie im Tab Einstellungen/Systeme,
Das Feld ist standardmäßig auf der allgemeinen Gladys-Konfiguration ausgewählt (außer für Kameras)
Server:
Die Spalte « keep_history » ist bereits vorhanden, also nichts zu tun auf dieser Seite
Entweder:
Wir fügen in der Tabelle t_device_feature eine Spalte „delay_keep_history“ hinzu, die standardmäßig auf der allgemeinen Gladys-Konfiguration basiert?
Oder wir ändern die Spalte „keep_history“, um direkt den Wert einzutragen?
Oder wir gehen über einen Geräteparameter mit zum Beispiel:
{
"name": "[FEATURE_SELECTOR]_HISTORY_IN_DAYS",
"value": [Verzögerung in Tagen]
}
Falls vorhanden, berücksichtigen wir diesen Parameter für die Werte des Features, andernfalls verwenden wir den allgemeinen Gladys-Parameter wie heute.
destroy_states entsprechend diesem Parameter überarbeiten.
2. PR:
Frontend:
Ein Kontrollkästchen für jedes Feature „Nur Zustandsänderungen speichern“,
Dieses Kästchen ist standardmäßig deaktiviert, um den aktuellen Zustand beizubehalten,
Server:
Wenn das Kästchen „Nur Zustandsänderungen speichern“ aktiviert ist, entweder:
Wir fügen in der Tabelle t_device_feature eine Spalte „only_keep_value_changes“ hinzu, die standardmäßig auf 0 gesetzt ist,
Oder wir fügen bei Aktivierung einen Parameter in device_param hinzu:
Wenn der Wert dieses Parameters true ist und der neue Wert derselbe wie der vorherige ist, gehen wir über eine neue Datei server/lib/device/device.saveLastStateChanged.js, um in der Tabelle t_device_feature die saveLastStateChanged zu speichern, wie du es mir damals während der Netatmo-Entwicklung vorgeschlagen hast:
Codevorschlag
const db = require('../../models');
const logger = require('../../utils/logger');
const { EVENTS, WEBSOCKET_MESSAGE_TYPES } = require('../../utils/constants');
/**
* @description Save new device feature state in DB.
* @param {Object} deviceFeature - A DeviceFeature object.
* @example
* saveLastValueChanged({
* id: 'fc235c88-b10d-4706-8b59-fef92a7119b2',
* selector: 'my-light'
* });
*/
async function saveLastStateChanged(deviceFeature) {
// logger.debug(`device.saveLastStateChanged of deviceFeature ${deviceFeature.selector}`);
const now = new Date();
// save local state in RAM
this.stateManager.setState('deviceFeature', deviceFeature.selector, {
last_value_changed: now,
});
await db.sequelize.transaction(async (t) => {
// update deviceFeature lastValue in DB
await db.DeviceFeature.update(
{
last_value_changed: now,
},
{
where: {
id: deviceFeature.id,
},
},
{
transaction: t,
},
);
});
// send websocket event
this.eventManager.emit(EVENTS.WEBSOCKET.SEND_ALL, {
type: WEBSOCKET_MESSAGE_TYPES.DEVICE.NEW_STATE_NO_CHANGED,
payload: {
device_feature_selector: deviceFeature.selector,
last_value_changed: now,
},
});
}
module.exports = {
saveLastStateChanged,
};
Wenn der Wert sich erneut ändert, gehen wir wieder über die Funktion der Datei server/lib/device/device.saveState.js, die wir ändern müssen, um vor dem neuen Wert den alten Wert, der bereits in der Datenbank gespeichert ist, erneut zu speichern, um eine angepasste Kurve zu erhalten:
Codevorschlag
const db = require('../../models');
const logger = require('../../utils/logger');
const { EVENTS, WEBSOCKET_MESSAGE_TYPES } = require('../../utils/constants');
const { BadParameters } = require('../../utils/coreErrors');
const DEFAULT_OPTIONS = {
skip: 0,
order_dir: 'DESC',
order_by: 'created_at',
};
/**
* @description Save new device feature state in DB.
* @param {Object} deviceFeature - A DeviceFeature object.
* @param {number} newValue - The new value of the deviceFeature to save.
* @example
* saveState({
* id: 'fc235c88-b10d-4706-8b59-fef92a7119b2',
* selector: 'my-light'
* }, 12);
*/
async function saveState(deviceFeature, newValue) {
if (Number.isNaN(newValue)) {
throw new BadParameters(`device.saveState of NaN value on ${deviceFeature.selector}`);
}
const optionsWithDefault = Object.assign({}, DEFAULT_OPTIONS);
// logger.debug(`device.saveState of deviceFeature ${deviceFeature.selector}`);
const now = new Date();
const previousDeviceFeature = this.stateManager.get('deviceFeature', deviceFeature.selector);
const previousDeviceFeatureValue = previousDeviceFeature ? previousDeviceFeature.last_value : null;
const previousDeviceFeatureLastValueChanged = previousDeviceFeature ? previousDeviceFeature.last_value_changed : null;
const deviceFeaturesState = await db.DeviceFeatureState.findOne({
attributes: ['device_feature_id', 'value', 'created_at'],
order: [[optionsWithDefault.order_by, optionsWithDefault.order_dir]],
where: {
device_feature_id: deviceFeature.id,
},
});
const previousDeviceFeatureStateLastValueChanged = deviceFeaturesState ? deviceFeaturesState.created_at : 0;
// save local state in RAM
this.stateManager.setState('deviceFeature', deviceFeature.selector, {
last_value: newValue,
last_value_changed: now,
});
// update deviceFeature lastValue in DB
await db.DeviceFeature.update(
{
last_value: newValue,
last_value_changed: now,
},
{
where: {
id: deviceFeature.id,
},
},
);
// if the deviceFeature should keep history, we save a new deviceFeatureState
if (deviceFeature.keep_history) {
// if the previous created deviceFeatureState is different of deviceFeature
// last value changed, we save a new deviceFeatureState of old state
if (previousDeviceFeatureLastValueChanged - previousDeviceFeatureStateLastValueChanged > 0) {
await db.DeviceFeatureState.create({
device_feature_id: deviceFeature.id,
value: previousDeviceFeatureValue,
created_at: previousDeviceFeatureLastValueChanged,
});
}
await db.DeviceFeatureState.create({
device_feature_id: deviceFeature.id,
value: newValue,
});
}
// });
// send websocket event
this.eventManager.emit(EVENTS.WEBSOCKET.SEND_ALL, {
type: WEBSOCKET_MESSAGE_TYPES.DEVICE.NEW_STATE,
payload: {
device_feature_selector: deviceFeature.selector,
last_value: newValue,
last_value_changed: now,
},
});
// check if there is a trigger matching
this.eventManager.emit(EVENTS.TRIGGERS.CHECK, {
type: EVENTS.DEVICE.NEW_STATE,
device_feature: deviceFeature.selector,
previous_value: previousDeviceFeatureValue,
last_value: newValue,
last_value_changed: now,
});
}
module.exports = {
saveState,
};
{
value: 1014.7,
created_at: 2022-04-19 04:20:13.277 +00:00
},
{
value: 1014.9,
created_at: 2022-04-19 04:30:13.277 +00:00
},
// Hier haben wir die identischen Werte nicht gespeichert
{
value: 1014.9,
created_at: 2022-04-19 05:10:13.277 +00:00
},
// Nach dem Schreiben des folgenden Wertes wurde zurückgeschickt
{
value: 78.0,
created_at: 2022-04-19 05:20:13.277 +00:00
}
Frage: Ist es nützlich oder unerlässlich, etwas für die aggregierten Daten zu sehen? Ich erkläre: nach dem Lesen des Beitrags von lmilcent (zuvor zitiert bezüglich der Retention), muss man bei der ersten PR dies berücksichtigen, um die Aggregation der Daten nicht zu brechen, wenn der keep_history der Funktion auf 1 Tag eingestellt ist. Die Aggregation von 1 Jahr sollte trotzdem alle Daten behalten, außer wenn der keep_history auf „OFF“ eingestellt ist, oder nicht??
Zusätzlich wäre es möglich, eine Alternative zu „keep_history“ vorzuschlagen, die dem vollständigen Sensorverlauf seit dem Start der Gladys-Instanz entspricht, zum Beispiel „keep aggregated history“, was den täglich aggregierten Daten entspricht, also maximal 100 Werte pro Tag.
In meiner Installation, mit einer Steckdose, die mir alle 3 Sekunden Infos sendet, ermöglicht dies, von 480 28.800 Nachrichten pro Tag auf 100 zu reduzieren, also eine erhebliche Reduzierung der Daten!
Mal sehen, ob die Vielzahl an Optionen wirklich eine Lösung ist. Aber @lmilcent, sind wir uns einig, dass die Möglichkeit, auf diesen Geräten nur Statusänderungen zu speichern, dieses Problem weit über diese Reduzierung hinaus lösen würde?
Hmm, ich glaube, arbeiten und gleichzeitig auf eine Forumnachricht antworten ist keine gute Idee
Also eine Nachricht alle 3 Sekunden ergibt 20 Nachrichten pro Minute, also 1200 Nachrichten pro Stunde, also 28.800 pro Tag.
Also klar, wenn ich die Historien der Werte behalten will, ohne den Speicherplatz zu sprengen, scheint mir die Aggregation mehr als eine gute Alternative.
Ja, absolut. Für bestimmte Geräte ist mir egal, wenn die Werte identisch sind (Luftdruck, Temperatur usw.), und das ist hier auch ein echter Hebel, um Speicherplatz zu sparen.
Aber Achtung, es ist wichtig zu wissen, dass mein Messgerät immer noch 200W Sofortleistung erkennt, und ich möchte in manchen Fällen die identischen Werte im Gedächtnis behalten. Es muss also vorgesehen werden, die Option „Identische Werte nicht speichern“ für jedes Gerät (oder Feature) aktivieren zu können.
Ich denke, ich habe die Idee nicht richtig verstanden, jetzt ist es aber klarer, danke
Also bestätige ich, dass das bereits etwas Speicherplatz spart.
Im Fall meines Smart Plugs kann dieser 201W, dann 200W, dann 202W zurückgeben, also ist die Aggregation auch eine gangbare und interessante Lösung, um Speicherplatz zu gewinnen, und damit Geschwindigkeit für alles (gladys, I/O SD-Karte, etc).
Also, der erste PR für Keep History ist fast fertig, wie wir es gestern besprochen haben. Einfach… oder fast ^^
Es gibt aber einen Punkt, den wir uns ansehen sollten: Ich habe die Änderung für die Integrationen vorgenommen, bei denen die Bearbeitung der Geräte möglich ist, sowie für MQTT, das unabhängig ist. Allerdings sind einige Integrationen (wie Philips Hue) nicht bearbeitbar. Soll ich die Gelegenheit nutzen, um diese Bearbeitungsseite für die fehlenden Integrationen hinzuzufügen?
Ich finde, das Label ist nicht besonders aussagekräftig, ich würde eher „Verlauf der Zustände speichern“ verwenden und darunter eine kleine Beschreibung wie in der Kategorie „MQTT-Topic zum Veröffentlichen“ hinzufügen, um zu erklären, was diese Option bewirkt.
Beschreibung:
Wenn Sie diese Option aktivieren, speichert Gladys den Verlauf aller Zustände, die diese Funktion annimmt, was es ermöglicht, Diagramme auf dem Dashboard anzuzeigen.
Außerdem, jetzt wo ich darüber nachdenke, aber das ist etwas, das wir tun müssen, in der Clean-Task, die durchläuft, um die DB zu bereinigen, müssen wir eine Task hinzufügen, um die device_feature_state + Aggregate der Features zu bereinigen, deren keep_history auf false gesetzt wurde (da man keep_history = false nachträglich aktivieren kann und daher eine Bereinigung erforderlich ist).
Sehr gut. Erledigt, jetzt müssen wir nur noch sehen, ob dir die Beschreibung gefällt? Ich habe denselben Satz wie in den Systemeinstellungen verwendet (Beibehalten statt Speichern und Funktionalität statt Gerät, daher.)
Ach, entschuldige! Ich denke nicht, dass das für diese Integrationen im Moment notwendig ist. Ich denke, für diese Integrationen könnten wir einfach Standardwerte festlegen, die Sinn ergeben. Philips Hue ist ohnehin nicht sehr ausführlich…
Stimmt, ich hatte nicht genau hingeschaut, das ist bereits bei Philips Hue der Fall, die Temperaturen, Farbe, Helligkeit und andere sind nicht in keep_history ^^
Gut, ich habe etwas dafür gemacht, du wirst mir Bescheid sagen!! Es funktioniert sehr gut, aber ich bin mir nicht sicher, ob der Code sauber ist…
Docker-Image ebenfalls verfügbar, falls es Tester gibt, insbesondere für Zigbee (ich konnte es nicht testen): docker push terdious/gladys:featureKeepHistory