//jshint maxerr:1000
// https://github.com/hdering/homematic_stromverbrauch_protokollieren
//----------------------------------------------------------------------------//
// Version: 1.3.3
//----------------------------------------------------------------------------//
// +++++++++  USER ANPASSUNGEN ++++++++++++++++++++++++
// debug logging
var logging = true;
// Aktivieren der History Instanz
var enable_history = false;
// history Instanz
var instance_history = 'history.0';
// Geräte können unterschiedliche Preise haben
var enable_unterschiedlichePreise = true;
// Speichern der Werte in zusätzlichen Objekten.
// Wenn 0, dann deaktiviert
var Tag_Anzahl_Werte_in_der_Vergangenheit       = 7;
var Woche_Anzahl_Werte_in_der_Vergangenheit     = 4;
var Monat_Anzahl_Werte_in_der_Vergangenheit     = 12;
var Quartal_Anzahl_Werte_in_der_Vergangenheit   = 4;
var Jahr_Anzahl_Werte_in_der_Vergangenheit      = 2;
// Grundpreis einberechnen
var enable_Grundpreis_einberechnen = false;
var instanznr    = '0';
//var instanz     = 'javascript.' + instanznr + '.';
var instanz     = '0_userdata.' + instanznr + '.';
// Pfad innerhalb der Instanz
var pfad        = 'Statistik.';
var default_unit = 'Wh';
var default_unit_kilo = 'kWh';
// Diese Teile werden aus den Gerätenamen entfernt
var blacklist   = [':1', ':2', ':3', ':4', ':5', ':6', ':7', ':8'];
var AnzahlKommastellenKosten = 2;
var AnzahlKommastellenVerbrauch = 3;
var AnzahlKommastellenZaehlerstand = 3;
var KumulierterWertIstBereitsInKilo = true;
var eigeneDatenpunkte = [
//[ 'smartmeter.0.1-0:1_8_0_255.value', 'Verbrauchszähler.Strom', 'Wh', 'kWh' ], // Bezug 
//[ 'smartmeter.0.1-0:2_8_0_255.value', 'Lieferungszähler.Strom', 'Wh', 'kWh' ], // Lieferungswerte
 [ 'shelly.0.SHPLG-S#041491#1.Relay0.Energy', 'Kaffeemaschine.Strom', 'kWh', 'kWh'],
 [ 'sonoff.0.luminea-Trockner.ENERGY_Total' , 'Trockner.Strom'      , 'kWh', 'kWh'],
 [ 'sonoff.0.luminea-WaMa.ENERGY_Total'     , 'Waschmaschine.Strom' , 'kWh', 'kWh']     
   // Beispiel:
   // ['Datenpunkt', 'Aliasname', 'Einheit kumulierter Wert', 'Einheit berechnete Werte (kilo/1000)' ],
   // [ 'hm-rpc.2.NEQ0861663.1.ENERGY_COUNTER', 'Stromzaehler:1.ENERGY_COUNTER', 'Wh', 'kWh' ],
   // [ 'javascript.1.MeinSonoffGeraet1', 'Strom.Sonoff.MeinSonoffGeraet1', 'Wh', 'kWh' ],
   // [ 'javascript.1.MeinSonoffGeraet2', 'Strom.Sonoff.MeinSonoffGeraet2', 'kWh', 'kWh' ],
   // [ 'javascript.1.MeineGas1', 'Gas.MeinGaszaehler1', 'm3', 'm3' ],
   // [ 'javascript.1.MeineGas2', 'Gas.MeinGaszaehler2', 'm3', 'm3' ],
];
// Pushmeldung
function send_message(text) {
   // Hier können die Pushmeldung über alle möglichen Wege verschickt werden.
   //console.log(text);
   //sendTelegram(text);
}
// ++++ ENDE USER ANPASSUNGEN ++++++++++++++++++++++++
//----------------------------------------------------------------------------//
var statesToCreate = [
      [pfad + 'Allgemein.Stromkosten.aktuell.Arbeitspreis', {'name':'Strompreis - aktueller Arbeitspreis (brutto)', 'type':'number','unit': '€/' + default_unit_kilo, 'read':true, 'write':true, 'role':'info', 'min':0, 'def':0.25 }],
      [pfad + 'Allgemein.Stromkosten.aktuell.Grundpreis', {'name':'Strompreis - aktueller Grundpreis (brutto)', 'type':'number', 'unit': '€/Monat', 'read':true, 'write':true, 'role':'info', 'min':0,  'def':0.25 }],
      [pfad + 'Allgemein.Stromkosten.neu.Arbeitspreis', {'name':'Strompreis - neuer Arbeitspreis ab Datum (brutto)', 'type':'number','unit': '€/' + default_unit_kilo, 'read':true, 'write':true, 'role':'info', 'min':0,  'def':0.25 }],
      [pfad + 'Allgemein.Stromkosten.neu.Grundpreis', {'name':'Strompreis - neuer Grundpreis ab Datum (brutto)', 'type':'number', 'unit': '€/Monat', 'read':true, 'write':true, 'role':'info', 'min':0,  'def':0.25 }],
      [pfad + 'Allgemein.Stromkosten.neu.Datum', {'name':'Strompreis und Grundpreis ab folgendem Datum zur Berechnung heranziehen (Beispiel 01.01.2019)', 'type':'string', 'read':true, 'write':true, 'def':'01.01.1970' }],
      [pfad + 'Allgemein.Stromkosten.neu.PreisaenderungDurchgefuehrt', {'name':'Preisänderung durchgeführt?', 'type':'boolean', 'read':true, 'write':true, 'def':false }]
   ];
createUserStates('0_userdata.0', false, statesToCreate);
//----------------------------------------------------------------------------//
var cacheSelectorStateMeter  = $('channel[state.id=*.METER]');
var cacheSelectorStateEnergyCounter  = $('channel[state.id=*.ENERGY_COUNTER$]');
//----------------------------------------------------------------------------//
function parseObjects(id) {
   var obj = getObject(id);
   return entferneDatenpunkt(obj.common.name);
}
function setRecognizedChange(type, anzahl) {
   cacheSelectorStateMeter.each(function (id, i) {
       var geraetename = parseObjects(id);
       rotateVerbrauchUndKosten(geraetename, type, anzahl);
       resetVerbrauchUndKosten(geraetename, type);
       schreibeZaehlerstand(geraetename, type);
   });
   cacheSelectorStateEnergyCounter.each(function (id, i) {
       var geraetename = parseObjects(id);
       rotateVerbrauchUndKosten(geraetename, type, anzahl);
       resetVerbrauchUndKosten(geraetename, type);
       schreibeZaehlerstand(geraetename, type);
   });
   if (eigeneDatenpunkte.length > 0) {
       for(var i = 0; i < eigeneDatenpunkte.length; i++) {
           var alias = eigeneDatenpunkte[i][1];
           rotateVerbrauchUndKosten(alias, type, anzahl);
           resetVerbrauchUndKosten(alias, type);
           schreibeZaehlerstand(alias, type);
       }
   }
}
//----------------------------------------------------------------------------//
// Tageswechsel
schedule("0 0 * * *", function() {
   setRecognizedChange('Tag', Tag_Anzahl_Werte_in_der_Vergangenheit);
});
// Wochenwechsel
schedule("0 0 * * 1", function() {
   setRecognizedChange('Woche', Woche_Anzahl_Werte_in_der_Vergangenheit);
});
// Monatswechsel
schedule("0 0 1 * *", function() {
   setRecognizedChange('Monat', Monat_Anzahl_Werte_in_der_Vergangenheit);
});
// Quartalswechsel
schedule("0 0 1 */3 *", function() {
   setRecognizedChange('Quartal', Quartal_Anzahl_Werte_in_der_Vergangenheit);
});
// Jahreswechsel
schedule("0 0 1 1 *", function() {
   setRecognizedChange('Jahr', Jahr_Anzahl_Werte_in_der_Vergangenheit);
});
//----------------------------------------------------------------------------//
// Eigene Datenpunkte
function pruefeEigeneDatenpunkte() {
   if (eigeneDatenpunkte.length > 0) {
       for(var i = 0; i < eigeneDatenpunkte.length; i++) {
           var datenpunkt = eigeneDatenpunkte[i][0];
           var alias = eigeneDatenpunkte[i][1];
           var einheit = eigeneDatenpunkte[i][2];
           var einheit_kilo = eigeneDatenpunkte[i][3];
           if(logging) console.log("Alias:" + alias + " Datenpunkt:" + datenpunkt + " Einheit:" + einheit + " Einheit_kilo:" + einheit_kilo);
           on(datenpunkt, function(obj) {
               for(var i = 0; i < eigeneDatenpunkte.length; i++) {
                   if(eigeneDatenpunkte[i][0] === obj.id)    
                       run(obj, eigeneDatenpunkte[i][1], eigeneDatenpunkte[i][2], eigeneDatenpunkte[i][3]);
               }
           });
       }
   }
}
pruefeEigeneDatenpunkte();
//----------------------------------------------------------------------------//
// Einlesen der aktuellen Daten vom Zähler
function run(obj, alias, unit, unit_kilo) {
   if (logging) {   
       log('-------- Strommesser ---------');
       log('RegExp-Funktion ausgelöst');
       log('id:           ' + obj.id);
       log('Name:         ' + obj.common.name);   // Waschmaschine Küche:2.ENERGY_COUNTER
       log('channel ID:   ' + obj.channelId);     // hm-rpc.0.MEQ0170864.2
       log('channel Name: ' + obj.channelName);   // Waschmaschine Küche:2
       log('device ID:    ' + obj.deviceId);      // hm-rpc.0.MEQ0170864
       log('device name:  ' + obj.deviceName);    // Küche Waschmaschine
       log('neuer Wert:   ' + obj.newState.val);  // 16499.699982
       log('alter Wert:   ' + obj.oldState.val);  // 16499.699982
   }
   // Gerätenamen erstellen
   if (logging) log('vor der Aufbereitung: ' + obj.common.name); 
   var geraetename = entferneDatenpunkt(obj.common.name);
   if(typeof alias !== "undefined")  {
       if(logging) console.log("Es wird der Aliasname gesetzt:" + alias);
       geraetename = alias;
   }
   if (logging) log('Nach der Aufbereitung: ' + geraetename); 
   if(typeof geraetename !== "undefined") {
       //------------------------------------------------------------------------//
       var _unit = default_unit;
       var _unit_kilo = default_unit_kilo;
       // States erstellen (CreateStates für dieses Gerät)
       if(typeof unit !== "undefined")  {
           _unit = unit;
       }
       if(typeof unit_kilo !== "undefined")  {
           _unit_kilo = unit_kilo;
       }
       erstelleStates(geraetename, _unit, _unit_kilo);
       //------------------------------------------------------------------------//
       // Schreiben der neuen Werte
       var idKumuliert =  instanz + pfad + geraetename + '.Zaehlerstand.kumuliert';
       var NeustartEventuellErkannt = false;
       var NeustartSicherErkannt = false;
       var oldState = obj.oldState.val;
       var newState = obj.newState.val;
       var difference = newState - oldState;
       if(difference > 0) {
           if(oldState !== 0) {
               // Kumulierten Wert mit Ist-Wert (inkl. Backup) synchronisieren
               var newValueKumuliert = getState(idKumuliert).val + difference;
               newValueKumuliert = parseFloat(newValueKumuliert);
               setState(idKumuliert, newValueKumuliert);
           } else {
               if(newState < getState(pfad + geraetename + '.config.NeustartErkanntAlterWert').val) {
                   NeustartSicherErkannt = true;
               }
           }
       } else {
           // Fall 2 oder 3
           // Irgendetwas läuft außerplanmäßig. Wert wird sicherheitshalber gespeichert und nächster Lauf abgewartet
           NeustartEventuellErkannt = true;
           setState(pfad + geraetename + '.config.NeustartErkanntAlterWert', obj.oldState.val);
       }
       if(NeustartEventuellErkannt) {
           if(logging) {
               var message =  geraetename + '\n'
                               + 'Entweder die CCU oder Stromzähler wurden neugestartet/zurückgesetzt.\n'
                               + 'Dieser Wert wird einmal ignoriert und auf den nächsten Wert gewartet.';
               send_message(message);
           }
       }
       if(NeustartSicherErkannt) {
           // zurücksetzen der Variable
           setState(pfad + geraetename + '.config.NeustartErkanntAlterWert', 0);
           //----------------------------------------------------------------//
           var message2 = geraetename + '\n'
                       + 'Der Stromzähler (' + geraetename + ') ist übergelaufen, gelöscht oder neugestartet worden (ggf. Stromausfall).\n'
                       + 'newState:' + obj.newState.val + '\n' 
                       + 'oldState:' + obj.oldState.val + '\n'
                       + 'differenz:' + difference + '\n'
                       + 'idKumuliert:' + getState(idKumuliert).val;
           send_message(message2);
       }
       //--------------------------------------------------------------------//
       pruefePreisaenderung();
       if(enable_unterschiedlichePreise)
           pruefePreisaenderung(geraetename);
       var idStrompreis = instanz + pfad + 'Allgemein.Stromkosten.aktuell.Arbeitspreis';
       var idGrundpreis = instanz + pfad + 'Allgemein.Stromkosten.aktuell.Grundpreis';
       var _zaehler = null;
       // aktualisiere den Verbrauch und die Kosten
       if(KumulierterWertIstBereitsInKilo)
          _zaehler    = (getState(idKumuliert).val).toFixed(AnzahlKommastellenZaehlerstand);
       else
          _zaehler    = (getState(idKumuliert).val / 1000).toFixed(AnzahlKommastellenZaehlerstand);
       var _preis      = getState(idStrompreis).val;
       var _grundpreis = getState(idGrundpreis).val;
       // Wenn das Gerät einen eigenen Strompreis / Grundpreis hat
       if(enable_unterschiedlichePreise) {
           if(getState(instanz + pfad + geraetename + '.eigenerPreis.aktuell.Arbeitspreis').val > 0) {
               _preis = getState(instanz + pfad + geraetename + '.eigenerPreis.aktuell.Arbeitspreis').val;
               if (logging) console.log("Das Gerät:" + geraetename + " hat eigenen Strompreis: " + _preis);
           }
           if(getState(instanz + pfad + geraetename + '.eigenerPreis.aktuell.Grundpreis').val > 0) {
               _grundpreis = getState(instanz + pfad + geraetename + '.eigenerPreis.aktuell.Grundpreis').val;
               if (logging) console.log("Das Gerät:" + geraetename + " hat eigenen Grundpreis: " + _grundpreis);
           }
       }
       berechneVerbrauchUndKosten(geraetename, _zaehler, _preis, _grundpreis); // in kWh
       if (logging) log('------------ ENDE ------------');
   } else {
       var message3 = 'Fehler beim Erstellen des Gerätenamens:\n'
                   + 'obj.common.name: ' + obj.common.name;
       send_message(message3);
   }
}
cacheSelectorStateMeter.on(function(obj) {
  run(obj);
});
cacheSelectorStateEnergyCounter.on(function(obj) {
  run(obj);
});
//----------------------------------------------------------------------------//
function entferneDatenpunkt(geraet) {
   var rueckgabe = geraet;
   // ":2.ENERGY_COUNTER" --> ".ENERGY_COUNTER"
   if (geraet.indexOf(".ENERGY_COUNTER") != -1) {
       rueckgabe = geraet.substring(0, geraet.indexOf(".ENERGY_COUNTER"));
   } else if (geraet.indexOf(".METER") != -1) {
       rueckgabe = geraet.substring(0, geraet.indexOf(".METER"));
   }
   if (logging) log('entferneDatenpunkt - rueckgabe1:' + rueckgabe);
   // Rückgabe sollte keine Sonderzeichen oder Leerzeichen enthalten. Wenn doch, werden die entfernt oder ersetzt
   try {
       rueckgabe = checkBlacklist(rueckgabe);
   }
   catch(err) {
       if (logging) log('entferneDatenpunkt - rueckgabe2:' + rueckgabe + ' error:' + err);
   }
   finally {
       if (logging) log('entferneDatenpunkt - rueckgabe2:' + rueckgabe);
   }
   try {
       if (rueckgabe.charAt(rueckgabe.length - 1) == "-") rueckgabe = rueckgabe.substr(0, rueckgabe.length - 1);
       if (rueckgabe.charAt(rueckgabe.length - 1) == "\\") rueckgabe = rueckgabe.substr(0, rueckgabe.length - 1);
       if (rueckgabe.charAt(rueckgabe.length - 1) == ":") rueckgabe = rueckgabe.substr(0, rueckgabe.length - 1);
   }
   catch(err) {
       if (logging) log('entferneDatenpunkt - rueckgabe3:' + rueckgabe + ' error:' + err);
   }
   finally {
       if (logging) log('entferneDatenpunkt - rueckgabe3:' + rueckgabe);
   }
   // per Regexp Leerzeichen entfernen
   try {
       rueckgabe = rueckgabe.replace(/\s/g, "");
   }
   catch(err) {
       if (logging) log('entferneDatenpunkt - rueckgabe4:' + rueckgabe + ' error:' + err);
   }
   finally {
       if (logging) log('entferneDatenpunkt - rueckgabe4:' + rueckgabe);
   }
   return rueckgabe;
}
function checkBlacklist(name) {
   var _name = "";
   if (blacklist.length > 0) {
       for(var i = 0; i < blacklist.length; i++) {
           if (name.indexOf(blacklist[i]) != -1) {
               // Zeichenketten, die in der Blacklist stehen, aus dem Namen löschen
               _name = name.substring(0, name.indexOf(blacklist[i]));
           }
       }
       if(_name === "") {
           return name;
       } else {
           return _name;
       }
   } else return (name);
}
function schreibeZaehlerstand(geraet, zeitraum) { 
   var idKumuliert =    instanz + pfad + geraet + '.Zaehlerstand.kumuliert',
       idZaehlerstand = instanz + pfad + geraet + '.Zaehlerstand.' + zeitraum;
   // Zählerstand für übergebene Zeitraum und das Gerät in Wh auslesen und in kWh speichern (also durch 1000)
   if(KumulierterWertIstBereitsInKilo)
       setState(idZaehlerstand, parseFloat( (getState(idKumuliert).val).toFixed(AnzahlKommastellenZaehlerstand)) );  
   else
       setState(idZaehlerstand, parseFloat( (getState(idKumuliert).val / 1000).toFixed(AnzahlKommastellenZaehlerstand)) ); 
   if (logging) log('Zählerstände für das Gerät ' + geraet + ' (' + zeitraum + ') in Objekten gespeichert');
}
function rotateVerbrauchUndKosten(geraet, zeitraum, anzahl) {
   // Verbrauch
   if(anzahl > 0) {
       for(var i = anzahl; i >= 0; i--) {
           var j = i;
           j++;
           if(getObject(instanz + pfad + geraet + '.Verbrauch._' + zeitraum + '.' + zeitraum + '_' + j)) {
               if(i === 0)
                   setState(instanz + pfad + geraet + '.Verbrauch._' + zeitraum + '.' + zeitraum + '_' + j, getState(instanz + pfad + geraet + '.Verbrauch.' + zeitraum).val);
               else
                   setState(instanz + pfad + geraet + '.Verbrauch._' + zeitraum + '.' + zeitraum + '_' + j, getState(instanz + pfad + geraet + '.Verbrauch._' + zeitraum + '.' + zeitraum + '_' + i).val);
           }
       }
   }
   // Kosten
   if(anzahl > 0) {
       for(var i = anzahl; i >= 0; i--) {
           var j = i;
           j++;
           if(getObject(instanz + pfad + geraet + '.Kosten._' + zeitraum + '.' + zeitraum + '_' + j)) {
               if(i === 0)
                   setState(instanz + pfad + geraet + '.Kosten._' + zeitraum + '.' + zeitraum + '_' + j, getState(instanz + pfad + geraet + '.Kosten.' + zeitraum).val);
               else
                   setState(instanz + pfad + geraet + '.Kosten._' + zeitraum + '.' + zeitraum + '_' + j, getState(instanz + pfad + geraet + '.Kosten._' + zeitraum + '.' + zeitraum + '_' + i).val);
           }
       }
   }
}
function resetVerbrauchUndKosten(geraet, zeitraum) {
   // Reset der Stromkosten für den übergebenen Zeitraum
   // Reset des Stromverbrauchs für den übergebenen Zeitraum 
   setState(instanz + pfad + geraet + '.Kosten.' + zeitraum, 0);     
   setState(instanz + pfad + geraet + '.Verbrauch.' + zeitraum, 0);
   if (logging) log('Stromkosten und Stromverbrauch für das Gerät ' + geraet + ' (' + zeitraum + ') zurückgesetzt');
} 
function berechneVerbrauchUndKosten(geraet, zaehler, preis, grundpreis) {                      
   // bei jedem eingehenden Wert pro Gerät
   if(preis === 0) {
       var message0 = 'Achtung!' + '.\n'
                   + 'Es wurde noch kein Arbeitspreis angegeben.' + '\n' 
                   + 'Ohne Arbeitspreis kann das Skript keine Berechnungen durchführen.' + '\n'
                   + 'Diese Information ist zwingend notwendig!';
       log(message0, 'error');
   } else {
       var _grundpreis = 0;
       if(enable_Grundpreis_einberechnen) {
           _grundpreis = grundpreis * 12 / 365;
           _grundpreis = parseFloat(_grundpreis.toFixed(3));
       }
       var grundpreis_tag      = _grundpreis;
       var grundpreis_woche    = _grundpreis * 7;
       var grundpreis_monat    = _grundpreis * 30; 
       var grundpreis_quartal  = _grundpreis * 90;
       var grundpreis_jahr     = _grundpreis * 365;
       // Tag [Verbrauchskosten = (Zähler_ist - Zähler_Tagesbeginn) * Preis ] --- zaehler muss immer größer sein als Tages, Wochen, etc.-Wert
       setState(instanz + pfad + geraet + '.Kosten.Tag',        parseFloat( (((zaehler - getState(instanz + pfad + geraet + '.Zaehlerstand.Tag').val) * preis) + grundpreis_tag).toFixed(AnzahlKommastellenKosten) ) );  // Kosten an diesem Tag in €
       // Woche
       setState(instanz + pfad + geraet + '.Kosten.Woche',      parseFloat( (((zaehler - getState(instanz + pfad + geraet + '.Zaehlerstand.Woche').val) * preis) + grundpreis_woche).toFixed(AnzahlKommastellenKosten) ) );
       // Monat
       setState(instanz + pfad + geraet + '.Kosten.Monat',      parseFloat( (((zaehler - getState(instanz + pfad + geraet + '.Zaehlerstand.Monat').val) * preis) + grundpreis_monat).toFixed(AnzahlKommastellenKosten) ) );
       // Quartal
       setState(instanz + pfad + geraet + '.Kosten.Quartal',    parseFloat( (((zaehler - getState(instanz + pfad + geraet + '.Zaehlerstand.Quartal').val) * preis) + grundpreis_quartal).toFixed(AnzahlKommastellenKosten) ) );
       // Jahr
       setState(instanz + pfad + geraet + '.Kosten.Jahr',       parseFloat( (((zaehler - getState(instanz + pfad + geraet + '.Zaehlerstand.Jahr').val) * preis) + grundpreis_jahr).toFixed(AnzahlKommastellenKosten) ) );  
       if (logging) log('Stromkosten (' + geraet + ') aktualisiert');
   }
   // Verbrauch berechnen
   // Tag [Verbrauchskosten = (Zähler_ist - Zähler_Tagesbeginn) * Preis ] --- zaehler muss immer größer sein als Tages, Wochen, etc.-Wert
   setState(instanz + pfad + geraet + '.Verbrauch.Tag',     parseFloat(   (zaehler - getState(instanz + pfad + geraet + '.Zaehlerstand.Tag').val).toFixed(AnzahlKommastellenVerbrauch) ) );           // Verbrauch an diesem Tag in kWh
   // Woche
   setState(instanz + pfad + geraet + '.Verbrauch.Woche',   parseFloat(   (zaehler - getState(instanz + pfad + geraet + '.Zaehlerstand.Woche').val).toFixed(AnzahlKommastellenVerbrauch) ) );
   // Monat
   setState(instanz + pfad + geraet + '.Verbrauch.Monat',   parseFloat(   (zaehler - getState(instanz + pfad + geraet + '.Zaehlerstand.Monat').val).toFixed(AnzahlKommastellenVerbrauch) ) );
   // Quartal
   setState(instanz + pfad + geraet + '.Verbrauch.Quartal', parseFloat(   (zaehler - getState(instanz + pfad + geraet + '.Zaehlerstand.Quartal').val).toFixed(AnzahlKommastellenVerbrauch) ) );
   // Jahr
   setState(instanz + pfad + geraet + '.Verbrauch.Jahr',    parseFloat(   (zaehler - getState(instanz + pfad + geraet + '.Zaehlerstand.Jahr').val).toFixed(AnzahlKommastellenVerbrauch) ) );
   if (logging) log('Stromverbrauch (' + geraet + ') aktualisiert');
}
function erstelleStates (geraet, _unit, _unit_kilo) {
   var states2Create = [
      [pfad + geraet + '.Zaehlerstand.kumuliert', {'name':'Kumulierter Zählerstand ('   + geraet + ')', 'type':'number', 'unit': _unit,      'read':true, 'write':true, 'min':0, 'def':0 }],
      [pfad + geraet + '.Zaehlerstand.Tag',       {'name':'Zählerstand Tagesbeginn ('   + geraet + ')', 'type':'number', 'unit': _unit_kilo, 'read':true, 'write':true, 'min':0,  'def':0 }],
      [pfad + geraet + '.Zaehlerstand.Woche',     {'name':'Zählerstand Wochenbeginn ('  + geraet + ')', 'type':'number', 'unit': _unit_kilo, 'read':true, 'write':true, 'min':0,  'def':0 }],
      [pfad + geraet + '.Zaehlerstand.Monat',     {'name':'Zählerstand Monatsbeginn ('  + geraet + ')', 'type':'number', 'unit': _unit_kilo, 'read':true, 'write':true, 'min':0,  'def':0 }],
      [pfad + geraet + '.Zaehlerstand.Quartal',   {'name':'Zählerstand Quartalbeginn (' + geraet + ')', 'type':'number', 'unit': _unit_kilo, 'read':true, 'write':true, 'min':0,  'def':0 }],
      [pfad + geraet + '.Zaehlerstand.Jahr',      {'name':'Zählerstand Jahressbeginn (' + geraet + ')', 'type':'number', 'unit': _unit_kilo, 'read':true, 'write':true, 'min':0,  'def':0 }],
      [pfad + geraet + '.Verbrauch.Tag',       {'name':'Verbrauch seit Tagesbeginn ('   + geraet + ')', 'type':'number', 'unit': _unit_kilo, 'read':true, 'write':true, 'min':0,  'def':0 }],
      [pfad + geraet + '.Verbrauch.Woche',     {'name':'Verbrauch seit Wochenbeginn ('  + geraet + ')', 'type':'number', 'unit': _unit_kilo, 'read':true, 'write':true, 'min':0,  'def':0 }],
      [pfad + geraet + '.Verbrauch.Monat',     {'name':'Verbrauch seit Monatsbeginn ('  + geraet + ')', 'type':'number', 'unit': _unit_kilo, 'read':true, 'write':true, 'min':0,  'def':0 }],
      [pfad + geraet + '.Verbrauch.Quartal',   {'name':'Verbrauch seit Quartalbeginn (' + geraet + ')', 'type':'number', 'unit': _unit_kilo, 'read':true, 'write':true, 'min':0,  'def':0 }],
      [pfad + geraet + '.Verbrauch.Jahr',      {'name':'Verbrauch seit Jahressbeginn (' + geraet + ')', 'type':'number', 'unit': _unit_kilo, 'read':true, 'write':true, 'min':0,  'def':0 }],
      [pfad + geraet + '.Kosten.Tag',       {'name':'Stromkosten heute ('               + geraet + ')', 'type':'number', 'unit': '€',        'read':true, 'write':true, 'min':0,  'def':0 }],
      [pfad + geraet + '.Kosten.Woche',     {'name':'Stromkosten Woche ('               + geraet + ')', 'type':'number', 'unit': '€',        'read':true, 'write':true, 'min':0,  'def':0 }],
      [pfad + geraet + '.Kosten.Monat',     {'name':'Stromkosten Monat ('               + geraet + ')', 'type':'number', 'unit': '€',        'read':true, 'write':true, 'min':0,  'def':0 }],
      [pfad + geraet + '.Kosten.Quartal',   {'name':'Stromkosten Quartal ('             + geraet + ')', 'type':'number', 'unit': '€',        'read':true, 'write':true, 'min':0,  'def':0 }],
      [pfad + geraet + '.Kosten.Jahr',      {'name':'Stromkosten Jahr ('                + geraet + ')', 'type':'number', 'unit': '€',        'read':true, 'write':true, 'min':0,  'def':0 }]
   ]; 
   createUserStates('0_userdata.0', false, states2Create);
   // Speichern der Werte in zusätzlichen Variablen
   if(Tag_Anzahl_Werte_in_der_Vergangenheit > 0) {
       for(var i = 1; i <= Tag_Anzahl_Werte_in_der_Vergangenheit; i++) {
           states2Create = [
             [pfad + geraet + '.Verbrauch._Tag.Tag_' + i, {'name':'Verbrauch vor '   + i + ' Tag(en) ('  + geraet + ')', 'type':'number', 'unit': _unit_kilo, 'read':true, 'write':true, 'min':0, 'def':0 }],
             [pfad + geraet + '.Kosten._Tag.Tag_' + i,    {'name':'Stromkosten vor ' + i + ' Tag(en) ('  + geraet + ')', 'type':'number', 'unit': '€'       , 'read':true, 'write':true, 'min':0,  'def':0 }]
           ];
           createUserStates('0_userdata.0', false, states2Create);
       }
   }
   if(Woche_Anzahl_Werte_in_der_Vergangenheit > 0) {
       for(var i = 1; i <= Woche_Anzahl_Werte_in_der_Vergangenheit; i++) {
           let states2Create = [
             [pfad + geraet + '.Verbrauch._Woche.Woche_' + i, {'name':'Verbrauch vor '  + i + ' Woche(n) ('  + geraet + ')' , 'type':'number', 'unit': _unit_kilo, 'read':true, 'write':true, 'min':0, 'def':0 }],
             [pfad + geraet + '.Kosten._Woche.Woche_'    + i, {'name':'Stromkosten vor ' + i + ' Woche(n) ('  + geraet + ')', 'type':'number', 'unit': '€'       , 'read':true, 'write':true, 'min':0,  'def':0 }]
           ];
           createUserStates('0_userdata.0', false, states2Create);
       }
   }
   if(Monat_Anzahl_Werte_in_der_Vergangenheit > 0) {
       for(var i = 1; i <= Monat_Anzahl_Werte_in_der_Vergangenheit; i++) {
           states2Create = [
             [pfad + geraet + '.Verbrauch._Monat.Monat_' + i, {'name':'Verbrauch vor '  + i + ' Monat(en) ('  + geraet + ')', 'type':'number', 'unit': _unit_kilo, 'read':true, 'write':true, 'min':0, 'def':0 }],
             [pfad + geraet + '.Kosten._Monat.Monat_' + i,    {'name':'Stromkosten vor ' + i + ' Monat(en) (' + geraet + ')', 'type':'number', 'unit': '€'       , 'read':true, 'write':true, 'min':0,  'def':0 }]
           ];
           createUserStates('0_userdata.0', false, states2Create);
       }
   }
   if(Quartal_Anzahl_Werte_in_der_Vergangenheit > 0) {
       for(var i = 1; i <= Quartal_Anzahl_Werte_in_der_Vergangenheit; i++) {
           states2Create = [
             [pfad + geraet + '.Verbrauch._Quartal.Quartal_' + i, {'name':'Verbrauch vor '  + i + ' Quartal(en) ('  + geraet + ')', 'type':'number', 'unit': _unit_kilo, 'read':true, 'write':true, 'min':0, 'def':0 }],
             [pfad + geraet + '.Kosten._Quartal.Quartal_' + i,    {'name':'Stromkosten vor ' + i + ' Quartal(en) (' + geraet + ')', 'type':'number', 'unit': '€'       , 'read':true, 'write':true, 'min':0,  'def':0 }]
           ];
           createUserStates('0_userdata.0', false, states2Create);
      }
   }
   if(Jahr_Anzahl_Werte_in_der_Vergangenheit > 0) {
       for(var i = 1; i <= Jahr_Anzahl_Werte_in_der_Vergangenheit; i++) {
           states2Create = [
             [pfad + geraet + '.Verbrauch._Jahr.Jahr_' + i, {'name':'Verbrauch vor '  + i + ' Jahr(en) ('  + geraet + ')', 'type':'number', 'unit': _unit_kilo, 'read':true, 'write':true, 'min':0, 'def':0 }],
             [pfad + geraet + '.Kosten._Jahr.Jahr_' + i,    {'name':'Stromkosten vor ' + i + ' Jahr(en) (' + geraet + ')', 'type':'number', 'unit': '€'       , 'read':true, 'write':true, 'min':0,  'def':0 }]
           ];
           createUserStates('0_userdata.0', false, states2Create);
       }
   }
   // Neustart von CCU oder Gerät erkannt
   createUserStates('0_userdata.0', false, [pfad + geraet + '.config.NeustartErkanntAlterWert',{'name':'Neustart erkannt alter Wert', 'type':'boolean', 'read':true, 'write':true, 'def':false}]);
   // Gerät hat eigenen Strompreis
   if(enable_unterschiedlichePreise) {
       states2Create = [
           [pfad + geraet + '.eigenerPreis.aktuell.Arbeitspreis', {'name':'Strompreis - aktueller Arbeitspreis ab Datum (brutto)','type':'number', 'unit': '€/' + _unit_kilo, 'read':true, 'write':true, 'min':0, 'def':0 }],
           [pfad + geraet + '.eigenerPreis.aktuell.Grundpreis'  , {'name':'Strompreis - aktueller Grundpreis ab Datum (brutto)',  'type':'number', 'unit': '€/Monat'        , 'read':true, 'write':true, 'min':0,  'def':0 }],
           [pfad + geraet + '.eigenerPreis.neu.Arbeitspreis'    , {'name':'Strompreis - neuer Arbeitspreis ab Datum (brutto)',    'type':'number', 'unit': '€/' + _unit_kilo, 'read':true, 'write':true, 'min':0,  'def':0 }],
           [pfad + geraet + '.eigenerPreis.neu.Grundpreis'      , {'name':'Strompreis - neuer Grundpreis ab Datum (brutto)',      'type':'number', 'unit': '€/Monat'        , 'read':true, 'write':true, 'min':0,  'def':0 }]];
       createUserStates('0_userdata.0', false, states2Create);
       createUserStates('0_userdata.0', false, [pfad + geraet + '.eigenerPreis.neu.Datum', {'name':'Strompreis und Grundpreis ab folgendem Datum zur Berechnung heranziehen (Beispiel 01.01.2019)','type':'string','read':true, 'write':true, 'def':'01.01.1979' }]);
       createUserStates('0_userdata.0', false, [pfad + geraet + '.eigenerPreis.neu.PreisaenderungDurchgefuehrt', {'name':'Preisänderung durchgeführt','type':'boolean','read':true, 'write':true,'def':false }]);
   }
   // history bei allen Datenpunkten aktivieren
   if(enable_history) {
       enableHistory(geraet, 'Tag');
       enableHistory(geraet, 'Woche');
       enableHistory(geraet, 'Monat');
       enableHistory(geraet, 'Quartal');
       enableHistory(geraet, 'Jahr');
   }
   if (logging) log('States in der Instanz ' + instanz + pfad + ' erstellt');   
}
function enableHistory(geraet, zeitraum) {
   if(instance_history !== '') {
       sendTo(instance_history, 'enableHistory', {
           id: instanz + pfad + geraet + '.Kosten.' + zeitraum,
           options: {
               changesOnly:  true,
               debounce:     0,
               retention:    31536000,
               maxLength:    3,
               changesMinDelta: 0.5
           }
       }, function (result) {
           if (result.error) {
               if (logging) log("Fehler beim Aktivieren von History: " + result.error);
           }
       });
       sendTo(instance_history, 'enableHistory', {
           id: instanz + pfad + geraet + '.Verbrauch.' + zeitraum,
           options: {
               changesOnly:  true,
               debounce:     0,
               retention:    31536000,
               maxLength:    3,
               changesMinDelta: 0.5
           }
       }, function (result) {
           if (result.error) {
               if (logging) log("Fehler beim Aktivieren von History: " + result.error);
           }
       });
       sendTo(instance_history, 'enableHistory', {
           id: instanz + pfad + geraet + '.Zaehlerstand.' + zeitraum,
           options: {
               changesOnly:  true,
               debounce:     0,
               retention:    31536000,
               maxLength:    3,
               changesMinDelta: 0.5
           }
       }, function (result) {
           if (result.error) {
               if (logging) log("Fehler beim Aktivieren von History: " + result.error);
           }
       });
   }
}
function pruefePreisaenderung(geraet) {
   var _Datum = "";
   var _PreisaenderungDurchgefuehrt = "";
   var _Arbeitspreis = "";
   var _Grundpreis = "";
   var _ArbeitspreisNeu = "";
   var _GrundpreisNeu = "";
   if(typeof geraet === "undefined") {
        // Default Arbeitspreis ändern
       _Datum                          = instanz + pfad + 'Allgemein.Stromkosten.neu.Datum';
       _PreisaenderungDurchgefuehrt    = instanz + pfad + 'Allgemein.Stromkosten.neu.PreisaenderungDurchgefuehrt';
       _Arbeitspreis       = instanz + pfad + 'Allgemein.Stromkosten.aktuell.Arbeitspreis';
       _Grundpreis         = instanz + pfad + 'Allgemein.Stromkosten.aktuell.Grundpreis';
       _ArbeitspreisNeu    = instanz + pfad + 'Allgemein.Stromkosten.neu.Arbeitspreis';
       _GrundpreisNeu      = instanz + pfad + 'Allgemein.Stromkosten.neu.Grundpreis';
   } else {
       // Arbeitspreis für Gerät ändern
       _Datum                          = instanz + pfad + geraet + '.eigenerPreis.neu.Datum';
       _PreisaenderungDurchgefuehrt    = instanz + pfad + geraet + '.eigenerPreis.neu.PreisaenderungDurchgefuehrt';
       _Arbeitspreis       = instanz + pfad + geraet + '.eigenerPreis.aktuell.Arbeitspreis';
       _Grundpreis         = instanz + pfad + geraet + '.eigenerPreis.aktuell.Grundpreis';
       _ArbeitspreisNeu    = instanz + pfad + geraet + '.eigenerPreis.neu.Arbeitspreis';
       _GrundpreisNeu      = instanz + pfad + geraet + '.eigenerPreis.neu.Grundpreis';
   }
   if(getObject(_Datum)) {
       var date = getState(_Datum).val;
       var Datum_Tag;
       var Datum_Monat;
       var Datum_Jahr;
       try {
           var Datum = date.match(/\d{2}(\.|-)\d{2}(\.|-)\d{4}/g).toString();
           Datum_Tag = Datum.split(".")[0];
           Datum_Monat = Datum.split(".")[1];
           Datum_Jahr = Datum.split(".")[2];
       } catch (err) {
           console.log("Fehler beim Auslesen des Datums. Eventuell falsche Syntax? " + date + " (Error:" + err + ")");
       }
       var newdate = new Date(Datum_Monat + " " + Datum_Tag + " " + Datum_Jahr);
       var today = new Date();
       today.setHours(0,0,0,0);
       if(today.getTime() === newdate.getTime()) {
           if(!getState(_PreisaenderungDurchgefuehrt).val) {
               setState(_PreisaenderungDurchgefuehrt, true);
               var alterArbeitspreis = getState(_Arbeitspreis).val;
               var alterGrundpreis = getState(_Grundpreis).val;
               var neuerArbeitspreis = getState(_ArbeitspreisNeu).val;
               var neuerGrundpreis = getState(_GrundpreisNeu).val;
               setState(_Arbeitspreis, neuerArbeitspreis);
               setState(_Grundpreis, neuerGrundpreis);
               var message =  'Preisänderung für ' + geraet + ' wurde durchgeführt:' + '\n'
                           + 'alter Arbeitspreis:' + alterArbeitspreis + '.\n'
                           + 'alter Grundpeis:' + alterGrundpreis + '.\n'
                           + 'neuer Arbeitspreis:' + neuerArbeitspreis + '.\n'
                           + 'neuer Grundpreis:' + neuerGrundpreis;
               send_message(message);
           }
       } else if(today.getTime() > newdate.getTime()) {
           // Variable zurücksetzen
           setState(_PreisaenderungDurchgefuehrt, false);
       }
   }
}
//----------------------------------------------------------------------------//
//========================================================================================================================================================================
/**
* Create states under 0_userdata.0 or javascript.x
* Current Version:     https://github.com/Mic-M/iobroker.createUserStates
* Support:             https://forum.iobroker.net/topic/26839/
* Autor:               Mic (ioBroker) | Mic-M (github)
* Version:             1.0 (17 January 2020)
* Example:
* -----------------------------------------------
  let statesToCreate = [
      ['Test.Test1', {'name':'Test 1', 'type':'string', 'read':true, 'write':true, 'role':'info', 'def':'Hello' }],
      ['Test.Test2', {'name':'Test 2', 'type':'string', 'read':true, 'write':true, 'role':'info', 'def':'Hello' }],
  ];
  createUserStates('0_userdata.0', false, statesToCreate);
* -----------------------------------------------
* PLEASE NOTE: Per https://github.com/ioBroker/ioBroker.javascript/issues/474, the used function setObject() 
*              executes the callback PRIOR to completing the state creation. Therefore, we use a setTimeout and counter. 
* -----------------------------------------------
* @param {string} where          Where to create the state: e.g. '0_userdata.0' or 'javascript.x'.
* @param {boolean} force         Force state creation (overwrite), if state is existing.
* @param {array} statesToCreate  State(s) to create. single array or array of arrays
* @param {object} [callback]     Optional: a callback function -- This provided function will be executed after all states are created.
*/
function createUserStates(where, force, statesToCreate, callback = undefined) {
  const WARN = false; // Throws warning in log, if state is already existing and force=false. Default is false, so no warning in log, if state exists.
  const LOG_DEBUG = false; // To debug this function, set to true
  // Per issue #474 (https://github.com/ioBroker/ioBroker.javascript/issues/474), the used function setObject() executes the callback 
  // before the state is actual created. Therefore, we use a setTimeout and counter as a workaround.
  // Increase this to 100, if it is not working.
  const DELAY = 50; // Delay in milliseconds (ms)
  // Validate "where"
  if (where.endsWith('.')) where = where.slice(0, -1); // Remove trailing dot
  if ( (where.match(/^javascript.([0-9]|[1-9][0-9])$/) == null) && (where.match(/^0_userdata.0$/) == null) ) {
      log('This script does not support to create states under [' + where + ']', 'error');
      return;
  }
  // Prepare "statesToCreate" since we also allow a single state to create
  if(!Array.isArray(statesToCreate[0])) statesToCreate = [statesToCreate]; // wrap into array, if just one array and not inside an array
  let numStates = statesToCreate.length;
  let counter = -1;
  statesToCreate.forEach(function(param) {
      counter += 1;
      if (LOG_DEBUG) log ('[Debug] Currently processing following state: [' + param[0] + ']');
      // Clean
      let stateId = param[0];
      if (! stateId.startsWith(where)) stateId = where + '.' + stateId; // add where to beginning of string
      stateId = stateId.replace(/\.*\./g, '.'); // replace all multiple dots like '..', '...' with a single '.'
      const FULL_STATE_ID = stateId;
      if( ($(FULL_STATE_ID).length > 0) && (existsState(FULL_STATE_ID)) ) { // Workaround due to https://github.com/ioBroker/ioBroker.javascript/issues/478
          // State is existing.
          if (WARN && !force) log('State [' + FULL_STATE_ID + '] is already existing and will no longer be created.', 'warn');
          if (!WARN && LOG_DEBUG) log('[Debug] State [' + FULL_STATE_ID + '] is already existing. Option force (=overwrite) is set to [' + force + '].');
          if(!force) {
              // State exists and shall not be overwritten since force=false
              // So, we do not proceed.
              numStates--;
              if (numStates === 0) {
                  if (LOG_DEBUG) log('[Debug] All states successfully processed!');
                  if (typeof callback === 'function') { // execute if a function was provided to parameter callback
                      if (LOG_DEBUG) log('[Debug] An optional callback function was provided, which we are going to execute now.');
                      return callback();
                  }
              } else {
                  // We need to go out and continue with next element in loop.
                  return; // https://stackoverflow.com/questions/18452920/continue-in-cursor-foreach
              }
          } // if(!force)
      }
      /************
       * State is not existing or force = true, so we are continuing to create the state through setObject().
       ************/
      let obj = {};
      obj.type = 'state';
      obj.native = {};
      obj.common = param[1];
      setObject(FULL_STATE_ID, obj, function (err) {
          if (err) {
              log('Cannot write object for state [' + FULL_STATE_ID + ']: ' + err);
          } else {
              if (LOG_DEBUG) log('[Debug] Now we are creating new state [' + FULL_STATE_ID + ']')
              let init = null;
              if(param[1].def === undefined) {
                  if(param[1].type === 'number') init = 0;
                  if(param[1].type === 'boolean') init = false;
                  if(param[1].type === 'string') init = '';
              } else {
                  init = param[1].def;
              }
              setTimeout(function() {
                  setState(FULL_STATE_ID, init, true, function() {
                      if (LOG_DEBUG) log('[Debug] setState durchgeführt: ' + FULL_STATE_ID);
                      numStates--;
                      if (numStates === 0) {
                          if (LOG_DEBUG) log('[Debug] All states processed.');
                          if (typeof callback === 'function') { // execute if a function was provided to parameter callback
                              if (LOG_DEBUG) log('[Debug] Function to callback parameter was provided');
                              return callback();
                          }
                      }
                  });
              }, DELAY + (20 * counter) );
          }
      });
  });
}