Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ The **General settings** tab controls which detailed statistics are created. Dis
| Current year: Weekday | Stores the current week's values by weekday. |
| Current year: Weeks / Months / Quarters | Stores values for each period below `<source>.currentYear`. |
| Current year: Previous period | Stores the completed day, week, month, quarter and year, plus the previous week's weekday values. |
| Rounding: Decimals for consumption values | Decimals for calculated quantities and meter readings, `3` by default. |
| Rounding: Decimals for cost values | Decimals for calculated costs and earnings, `2` by default. |
| Rounding: Decimals for consumption values | Initial decimals copied into newly configured sources, `3` by default. |
| Rounding: Decimals for cost values | Initial decimals copied into newly configured sources, `2` by default. |

Both rounding settings accept `-1` to store the exact calculated value without rounding. A single source can deviate from them: its **Decimals for consumption values** and **Decimals for cost values** fields override the global setting and use it whenever they are left empty. Rounding only affects the values written to states; internal calculations, the cumulative reading and the persisted memories always keep full precision, so no accuracy is lost over time.
Both rounding settings accept `-1` to store the exact calculated value without rounding. Every source stores its own explicit **Decimals for consumption values** and **Decimals for cost values**. New sources are pre-filled from the instance settings above. Existing sources without these fields receive their previously effective instance values once during migration, so later changes to the instance defaults do not alter their results. Rounding only affects the values written to states; internal calculations, the cumulative reading and the persisted memories always keep full precision, so no accuracy is lost over time.

SourceAnalytix remembers the last successfully processed calendar periods. If the adapter or ioBroker is not running at midnight, missed day, week, month, quarter and year changes are processed once at the next start.

Expand Down Expand Up @@ -128,7 +128,8 @@ SourceAnalytix is configured through the ioBroker custom settings of each source
| Setting | Description |
| --- | --- |
| Enabled | Activates this source for the selected SourceAnalytix instance. |
| Alias | Optional display name for the generated device. It does not change the generated state ID. |
| Name | Optional display name for the generated device. |
| Output ID | Technical device ID below `sourceanalytix.<instance>`. It is initialized with the backward-compatible source-derived ID and can be shortened. |
| Select price definition | Mandatory category from the adapter's price definitions. |
| Select Unit | Source unit. Leave on automatic detection when the source object has a correct supported unit. |
| Calculate costs | Creates and updates cost or earnings states. |
Expand All @@ -140,7 +141,7 @@ SourceAnalytix is configured through the ioBroker custom settings of each source
| Device value reset detection | Continues a cumulative total after a meter reset or replacement. |
| Threshold | Largest backwards fluctuation ignored as measurement jitter, expressed in the target unit. |

The source state ID is converted to the generated SourceAnalytix device ID by replacing dots with double underscores.
For existing and newly activated sources, the initial output ID is derived from the source state ID by replacing dots with double underscores. It may be changed to a shorter unique ID containing letters, numbers, underscores and hyphens. SourceAnalytix copies and verifies its complete generated object tree before deleting the old tree. Existing scripts, visualizations, aliases and external history queries which refer to the old ID must be updated manually.

## Source Values And Units

Expand Down Expand Up @@ -263,7 +264,7 @@ The state is rebuilt from existing statistics when the adapter starts and its wr

## Meter Resets And Corrections

With reset detection enabled, a decrease larger than **Threshold** is treated as a real meter reset or replacement. SourceAnalytix stores an offset and continues its cumulative reading without losing earlier consumption. A smaller backwards change is treated as jitter and ignored. A threshold of `0` treats every decrease as a reset.
With reset detection enabled, a decrease larger than **Threshold** starts reset confirmation. SourceAnalytix keeps the last accepted total until another reading remains in the lower range and confirms the reset or replacement. It then stores an offset and continues its cumulative reading without losing earlier consumption. If the source returns to its previous range instead, the candidate is discarded as a temporary invalid reading. A smaller backwards change is treated as jitter and ignored. A threshold of `0` treats every decrease as a possible reset that still requires confirmation.

If reset detection is disabled, decreasing source readings are accepted and can reduce calculated totals. This mode is intended only for sources where that behavior is expected.

Expand Down
93 changes: 93 additions & 0 deletions admin-custom.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
'use strict';

const assert = require('node:assert/strict');
const customConfig = require('./admin/jsonCustom.json');
const schema = customConfig.items;

function executeCustom(expression, data, customObj, instanceObj = {}) {
return new Function(
'data',
'originalData',
'_system',
'instanceObj',
'customObj',
'_socket',
'arrayIndex',
'globalData',
'_changed',
`return ${expression}`,
)(data, {}, {}, instanceObj, customObj, {}, 0, {}, false);
}

describe('custom settings validation', () => {
const source = {_id: '0_userdata.0.energy', common: {unit: 'kWh'}};

it('blocks saving while required source settings are invalid', () => {
assert.equal(Object.hasOwn(customConfig, 'validatorNoSaveOnError'), false);
assert.equal(schema.outputId.validatorNoSaveOnError, true);
assert.equal(schema.selectedPrice.validatorNoSaveOnError, true);
assert.equal(schema.selectedUnit.validatorNoSaveOnError, true);
});

it('restricts prices and units to the options provided by the adapter', () => {
assert.equal(schema.selectedPrice.type, 'selectSendTo');
assert.equal(schema.selectedPrice.manual, false);
assert.equal(schema.selectedUnit.type, 'selectSendTo');
assert.equal(schema.selectedUnit.manual, false);
});

it('pre-fills the backward-compatible output ID', () => {
assert.equal(executeCustom(schema.outputId.defaultFunc, {}, source), '0_userdata__0__energy');
});

it('requires an output ID and price definition when enabled', () => {
assert.equal(executeCustom(schema.outputId.validator, {enabled: true, outputId: ''}, source), false);
assert.equal(executeCustom(schema.outputId.validator, {enabled: true, outputId: 'Kitchen'}, source), true);
assert.equal(executeCustom(schema.selectedPrice.validator, {enabled: true}, source), false);
assert.equal(executeCustom(schema.selectedPrice.validator, {enabled: true, selectedPrice: 'Electricity'}, source), true);
});

it('rejects an output ID owned by another source', () => {
assert.equal(executeCustom(schema.outputId.validator, {
enabled: true,
outputId: 'Kitchen',
_usedOutputIds: {Kitchen: 'alias.0.other'},
}, source), false);
assert.equal(executeCustom(schema.outputId.validator, {
enabled: true,
outputId: 'Kitchen',
_usedOutputIds: {Kitchen: source._id},
}, source), true);
});

it('handles output ID validation without a single source object', () => {
assert.equal(executeCustom(schema.outputId.validator, {
enabled: true,
outputId: 'Kitchen',
_usedOutputIds: {Kitchen: 'alias.0.other'},
}, {common: {custom: {}}, native: {}}), false);
});

it('accepts detected or manually selected supported units', () => {
assert.equal(executeCustom(schema.selectedUnit.validator, {enabled: true, selectedUnit: 'Detect automatically'}, source), true);
assert.equal(executeCustom(schema.selectedUnit.validator, {enabled: true, selectedUnit: 'kW'}, {_id: source._id, common: {}}), true);
assert.equal(executeCustom(schema.selectedUnit.validator, {enabled: true, selectedUnit: 'Detect automatically'}, {_id: source._id, common: {}}), false);
});

it('pre-fills explicit rounding values from the selected instance', () => {
const instance = {native: {decimalsQuantity: 5, decimalsCosts: 4}};
assert.equal(executeCustom(schema.decimalsQuantity.defaultFunc, {}, source, instance), 5);
assert.equal(executeCustom(schema.decimalsCosts.defaultFunc, {}, source, instance), 4);
assert.equal(executeCustom(schema.decimalsQuantity.defaultFunc, {}, source), 3);
assert.equal(executeCustom(schema.decimalsCosts.defaultFunc, {}, source), 2);
});

it('normalizes unusual instance templates before pre-filling a source', () => {
assert.equal(executeCustom(schema.decimalsQuantity.defaultFunc, {}, source, {native: {decimalsQuantity: 0}}), 0);
assert.equal(executeCustom(schema.decimalsCosts.defaultFunc, {}, source, {native: {decimalsCosts: -1}}), -1);
assert.equal(executeCustom(schema.decimalsQuantity.defaultFunc, {}, source, {native: {decimalsQuantity: ''}}), 3);
assert.equal(executeCustom(schema.decimalsCosts.defaultFunc, {}, source, {native: {decimalsCosts: 'invalid'}}), 2);
assert.equal(executeCustom(schema.decimalsQuantity.defaultFunc, {}, source, {native: {decimalsQuantity: 99}}), 15);
assert.equal(executeCustom(schema.decimalsCosts.defaultFunc, {}, source, {native: {decimalsCosts: -5}}), -1);
});
});
10 changes: 8 additions & 2 deletions admin/i18n/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@
"Month": "Monat",
"Months": "Monate",
"Name": "Name",
"Name help": "Lesbarer Anzeigename des erzeugten SourceAnalytix-Geräts",
"Output ID": "Ausgabe-ID",
"Output ID help": "Technische ID unter sourceanalytix.0. Beim Ändern einer bestehenden ID werden deren Werte migriert; Skripte, Visualisierungen und externe Historienverweise müssen angepasst werden.",
"Output ID format error": "Die ID ist ungültig, reserviert oder bereits vergeben",
"Price definition required error": "Bitte eine Preisdefinition auswählen",
"Unit required error": "Keine passende Einheit erkannt. Bitte eine Einheit auswählen",
"Number of quarters": "Anzahl Quartale",
"Number of weeks": "Anzahl Wochen",
"Number of years": "Anzahl Jahre",
Expand Down Expand Up @@ -119,6 +125,6 @@
"Rounding": "Rundung",
"Decimals for consumption values": "Nachkommastellen für Verbrauchswerte",
"Decimals for cost values": "Nachkommastellen für Kostenwerte",
"Rounding decimals help": "Anzahl der Nachkommastellen für berechnete Werte. Mit -1 wird der exakte Wert ohne Rundung gespeichert.",
"Individual rounding decimals help": "Überschreibt die globale Einstellung für diese Quelle. Leer lassen, um die globale Einstellung zu verwenden; -1 speichert den exakten Wert."
"Rounding decimals help": "Startwert, der in neu konfigurierte Quellen übernommen wird. Bestehende Quellen behalten ihren eigenen Wert. Mit -1 wird der exakte Wert ohne Rundung gespeichert.",
"Individual rounding decimals help": "Mit -1 wird der exakte Wert ohne Rundung gespeichert."
}
10 changes: 8 additions & 2 deletions admin/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@
"Month": "Month",
"Months": "Months",
"Name": "Name",
"Name help": "Readable display name of the generated SourceAnalytix device",
"Output ID": "Output ID",
"Output ID help": "Technical ID below sourceanalytix.0. Changing an existing ID migrates its values, but scripts, visualizations and external history references must be adjusted.",
"Output ID format error": "The ID is invalid, reserved or already in use",
"Price definition required error": "Please select a price definition",
"Unit required error": "No supported unit was detected. Please select a unit",
"Number of quarters": "Number of quarters",
"Number of weeks": "Number of weeks",
"Number of years": "Number of years",
Expand Down Expand Up @@ -119,6 +125,6 @@
"Rounding": "Rounding",
"Decimals for consumption values": "Decimals for consumption values",
"Decimals for cost values": "Decimals for cost values",
"Rounding decimals help": "Number of decimals for calculated values. Use -1 to store the exact value without rounding.",
"Individual rounding decimals help": "Overrides the global setting for this source. Leave empty to use the global setting, -1 stores the exact value."
"Rounding decimals help": "Initial value copied into newly configured sources. Existing sources keep their own value. Use -1 to store the exact value without rounding.",
"Individual rounding decimals help": "Use -1 to store the exact value without rounding."
}
12 changes: 9 additions & 3 deletions admin/i18n/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,13 @@
"ID": "ID",
"Invert selection": "Invert selection",
"Last changed": "Last changed",
"Name": "Name",
"Name": "Nombre",
"Name help": "Nombre visible del dispositivo SourceAnalytix generado",
"Output ID": "ID de salida",
"Output ID help": "ID técnica debajo de sourceanalytix.0. Al cambiar una ID existente se migran sus valores; deben adaptarse los scripts, las visualizaciones y las referencias de historial externas.",
"Output ID format error": "La ID no es válida, está reservada o ya está en uso",
"Price definition required error": "Seleccione una definición de precio",
"Unit required error": "No se detectó ninguna unidad compatible. Seleccione una unidad",
"Number of quarters": "Number of quarters",
"Number of weeks": "Number of weeks",
"Number of years": "Number of years",
Expand All @@ -119,6 +125,6 @@
"Rounding": "Redondeo",
"Decimals for consumption values": "Decimales para los valores de consumo",
"Decimals for cost values": "Decimales para los valores de coste",
"Rounding decimals help": "Número de decimales para los valores calculados. Use -1 para guardar el valor exacto sin redondeo.",
"Individual rounding decimals help": "Sustituye el ajuste global para esta fuente. Déjelo vacío para usar el ajuste global; -1 guarda el valor exacto."
"Rounding decimals help": "Valor inicial que se copia en las fuentes recién configuradas. Las fuentes existentes conservan su propio valor. Use -1 para guardar el valor exacto sin redondeo.",
"Individual rounding decimals help": "Use -1 para guardar el valor exacto sin redondeo."
}
12 changes: 9 additions & 3 deletions admin/i18n/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,13 @@
"ID": "ID",
"Invert selection": "Invert selection",
"Last changed": "Last changed",
"Name": "Name",
"Name": "Nom",
"Name help": "Nom d'affichage lisible du périphérique SourceAnalytix créé",
"Output ID": "ID de sortie",
"Output ID help": "ID technique sous sourceanalytix.0. La modification d'une ID existante migre ses valeurs ; les scripts, visualisations et références d'historique externes doivent être adaptés.",
"Output ID format error": "L'ID est invalide, réservée ou déjà utilisée",
"Price definition required error": "Veuillez sélectionner une définition de prix",
"Unit required error": "Aucune unité compatible n'a été détectée. Veuillez sélectionner une unité",
"Number of quarters": "Number of quarters",
"Number of weeks": "Number of weeks",
"Number of years": "Number of years",
Expand All @@ -119,6 +125,6 @@
"Rounding": "Arrondi",
"Decimals for consumption values": "Décimales pour les valeurs de consommation",
"Decimals for cost values": "Décimales pour les valeurs de coût",
"Rounding decimals help": "Nombre de décimales pour les valeurs calculées. Utilisez -1 pour enregistrer la valeur exacte sans arrondi.",
"Individual rounding decimals help": "Remplace le réglage global pour cette source. Laissez vide pour utiliser le réglage global ; -1 enregistre la valeur exacte."
"Rounding decimals help": "Valeur initiale copiée dans les nouvelles sources configurées. Les sources existantes conservent leur propre valeur. Utilisez -1 pour enregistrer la valeur exacte sans arrondi.",
"Individual rounding decimals help": "Utilisez -1 pour enregistrer la valeur exacte sans arrondi."
}
12 changes: 9 additions & 3 deletions admin/i18n/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,13 @@
"ID": "ID",
"Invert selection": "Invert selection",
"Last changed": "Last changed",
"Name": "Name",
"Name": "Nome",
"Name help": "Nome visualizzato del dispositivo SourceAnalytix generato",
"Output ID": "ID di output",
"Output ID help": "ID tecnica sotto sourceanalytix.0. Modificando un'ID esistente vengono migrati i valori; script, visualizzazioni e riferimenti alla cronologia esterna devono essere aggiornati.",
"Output ID format error": "L'ID non è valida, è riservata o è già in uso",
"Price definition required error": "Selezionare una definizione del prezzo",
"Unit required error": "Non è stata rilevata un'unità supportata. Selezionare un'unità",
"Number of quarters": "Number of quarters",
"Number of weeks": "Number of weeks",
"Number of years": "Number of years",
Expand All @@ -119,6 +125,6 @@
"Rounding": "Arrotondamento",
"Decimals for consumption values": "Decimali per i valori di consumo",
"Decimals for cost values": "Decimali per i valori di costo",
"Rounding decimals help": "Numero di decimali per i valori calcolati. Usare -1 per memorizzare il valore esatto senza arrotondamento.",
"Individual rounding decimals help": "Sostituisce l'impostazione globale per questa sorgente. Lasciare vuoto per usare l'impostazione globale; -1 memorizza il valore esatto."
"Rounding decimals help": "Valore iniziale copiato nelle nuove sorgenti configurate. Le sorgenti esistenti mantengono il proprio valore. Usare -1 per memorizzare il valore esatto senza arrotondamento.",
"Individual rounding decimals help": "Usare -1 per memorizzare il valore esatto senza arrotondamento."
}
12 changes: 9 additions & 3 deletions admin/i18n/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,13 @@
"ID": "ID",
"Invert selection": "Invert selection",
"Last changed": "Last changed",
"Name": "Name",
"Name": "Naam",
"Name help": "Leesbare weergavenaam van het aangemaakte SourceAnalytix-apparaat",
"Output ID": "Uitvoer-ID",
"Output ID help": "Technische ID onder sourceanalytix.0. Bij wijziging van een bestaande ID worden de waarden gemigreerd; scripts, visualisaties en externe historieverwijzingen moeten worden aangepast.",
"Output ID format error": "De ID is ongeldig, gereserveerd of al in gebruik",
"Price definition required error": "Selecteer een prijsdefinitie",
"Unit required error": "Er is geen ondersteunde eenheid herkend. Selecteer een eenheid",
"Number of quarters": "Number of quarters",
"Number of weeks": "Number of weeks",
"Number of years": "Number of years",
Expand All @@ -119,6 +125,6 @@
"Rounding": "Afronding",
"Decimals for consumption values": "Decimalen voor verbruikswaarden",
"Decimals for cost values": "Decimalen voor kostenwaarden",
"Rounding decimals help": "Aantal decimalen voor berekende waarden. Gebruik -1 om de exacte waarde zonder afronding op te slaan.",
"Individual rounding decimals help": "Overschrijft de globale instelling voor deze bron. Laat leeg om de globale instelling te gebruiken; -1 slaat de exacte waarde op."
"Rounding decimals help": "Beginwaarde die naar nieuw geconfigureerde bronnen wordt gekopieerd. Bestaande bronnen behouden hun eigen waarde. Gebruik -1 om de exacte waarde zonder afronding op te slaan.",
"Individual rounding decimals help": "Gebruik -1 om de exacte waarde zonder afronding op te slaan."
}
Loading