peterS.: JavaScript

Beitrag lesen

Eine Abstraktion, die sowohl generisch genug, als auch wartbar/skalierbar und sicher ist, würde höchstwahrscheinlich klassenbasiert sein, wobei zusätzlich noch auf Vererbung und Datenkapselung zurückgegriffen werden wird.

Die Verwendung von bspw. ... getState('sun2000.0.collected.chargeDischargePower').val ... zeigt, dass solche Entitäten schon im System existieren. Die eigentliche Aufgabe scheint demzufolge eher darin zu bestehen, eine flexible, sichere und skalierbare Abstraktion und Implementierung für ein reines Mapping zur Datanausgabe zu erschaffen.

Das folgende Code-Beispiel benutzt Features des JavaScript Klassensyntax, um das eigentliche Zieltemplate zu erstellen; die PVA-Klasse zur Erschaffung von PVA-Instanzen.

Diese Klasse nutzt die Technik der Aggregation, um die Abstraktion einer Photovoltaikanlage (PVA) vollständig abzubilden.

Und um zu gewährleisten, dass die zu aggregierenden Entitäten, wie z.b. Batterie, Panele, Wechselrichter oder Haus, einerseits öffentlich zugänglich, aber auch schreibgeschützt sind, werden private Felder mit den dazugehörigen getter Methoden implementiert.

Jede Entität wird ebenfalls durch ihre eigene Klassenabstraktion abgebildet. Auch hier wird überall mit (schreib)geschützten Eigenschaften gearbeitet. Darüber hinaus beerben diese Entitäten jeweils einunddieselbe BrokerEntity-Basisklasse.

Über die Datensicherheit hinaus gewährleistet dieser zweiteilige Ansatz Skalier- und Wartbarkeit solcher Entitäten; aber warum?

Dreh- und Angelpunkt ist die schon oben erwähnte getStatus-Function der IO-Broker API. Der erwartete Parameter beschreibt den keypath in der Baumstruktur eines Objekts welcher zu einem Objektwert führt.

Die Basisklasse verwaltet die keypath-Wurzel und die getStatus-Funktion, welche bei der Instantiierung sowohl der PVA-Klasse als auch von konkreten PVA-Entitäten in den Konstruktor gereicht werden, wohingegen die Implementierung einer PVA-Entität sich für jede Entität-spezifische Eigenschaft nur um die korrekte super delegation vom Basisklassen-getStatus kümmern muss. An genau dieser Stelle wird/ist das Mapping über den richtig zusammengestellten keypath abgeschlossen/beendet.

Nehmen wir an, dass ein PVA-Instanz jetzt folgendermassen erzeugt wurde ... const pva = new PVA('sun2000', 0, getStatus); ... dann lassen sich alle Werte folgendermassen erreichen ...

pva.keypath;
pva.name;
pva.idx;


pva.battery.keypath;

pva.battery.soc;
pva.battery.soc.val;

pva.battery.chargeDischargePower;
pva.battery.chargeDischargePower.val;


pva.panels.keypath;

pva.panels.inputPower;
pva.panels.inputPower.val;


pva.meter.keypath;

pva.meter.activePower;
pva.meter.activePower.val;


pva.house.keypath;

pva.house.consumption;
pva.house.consumption.val;

Da jeder Wert unmittelbar aufgerufen werden kann, ist die ursprünglich angedachte/vorgesehene Methode DatenEinlesen hinfällig. Sinnvoll hingegen ist es, eine eigene valueOf-Methode zu implementieren. Dort hat man die Kontrolle über die Ausgabe einer selbst definierten Datenstruktur.

Der folgende Beispielcode dient sowohl zur Veranschaulichung der obigen Prosa als auch als "Proof of Concept". Ein kleiner Test ist ebenfalls dabei. Man kann den gesamten Code in jede beliebige JavaScript-Konsole kippen und ausführen ...

class BrokerEntity {

  #keypath;
  #getStatus

  constructor(keypath, getStatus) {
    this.#keypath = keypath;
    this.#getStatus = getStatus;
  }
  get keypath() {
    return this.#keypath;
  }

  getStatus(keypath) {
    return this.#getStatus(keypath);
  }
}

class PvaBattery extends BrokerEntity {

  constructor(...args) {
    super(...args);
  }
  get keypath() {
    return `${ super.keypath }.collected`;
  }

  get soc() {
    return super.getStatus(`${ this.keypath }.SOC`);
  }
  get chargeDischargePower() {
    return super.getStatus(`${ this.keypath }.chargeDischargePower`);
  }
}
class PvaPanels extends BrokerEntity {

  constructor(...args) {
    super(...args);
  }
  get keypath() {
    return `${ super.keypath }.collected`;
  }

  get inputPower() {
    return super.getStatus(`${ this.keypath }.inputPower`);
  }
}
class PvaMeter extends BrokerEntity {

  constructor(...args) {
    super(...args);
  }
  get keypath() {
    return `${ super.keypath }.meter`;
  }

  get activePower() {
    return super.getStatus(`${ this.keypath }.activePower`);
  }
}
class PvaHouse extends BrokerEntity {

  constructor(...args) {
    super(...args);
  }
  get keypath() {
    return `${ super.keypath }.collected`;
  }

  get consumption() {
    return super.getStatus(`${ this.keypath }.houseConsumption`);
  }
}

class PVA {

  #name;
  #idx;

  #battery;
  #panels;
  #meter;
  #house;

  constructor(name, idx, getStatus) {
    const keypath = `${ name }.${ idx }`;

    this.#name = name;
    this.#idx = idx;

    this.#battery = new PvaBattery(keypath, getStatus);
    this.#panels = new PvaPanels(keypath, getStatus);
    this.#meter = new PvaMeter(keypath, getStatus);
    this.#house = new PvaHouse(keypath, getStatus);
  }
  get keypath() {
    return `${ this.name }.${ this.idx }`;
  }
  get name() { return this.#name }
  get idx() { return this.#idx; }

  get battery() { return this.#battery; }
  get panels() { return this.#panels; }
  get meter() { return this.#meter; }
  get house() { return this.#house; }

  valueOf() {
    return {

      batterie: {
        status: this.battery.soc.val,
        ladung_entladung: this.battery.chargeDischargePower.val,
      },
      panele: {
        aktuelle_leistung: this.panels.inputPower.val,
      },
      wechselrichter: {
        netzbezug_einspeisung: this.meter.activePower.val,
      },
      haus: {
        verbrauch: this.house.consumption.val,
      }
    }
  }
}

/*
 *  test case of the above implemented architecture.
 */
const pvaStatusMock = new Map([
  [ 'sun2000.0.collected.SOC', { val: '80%' } ],
  [ 'sun2000.0.collected.chargeDischargePower', { val: '2.5 / 2.3 kW' } ],
  [ 'sun2000.0.collected.inputPower', { val: '2 kWp' } ],
  [ 'sun2000.0.meter.activePower', { val: '0.7 kWh' } ],
  [ 'sun2000.0.collected.houseConsumption', { val: '0.95 kWh' } ],
]);
function getStatusMock(keypath) {
  return pvaStatusMock.get(keypath);
}

const pva = new PVA('sun2000', 0, getStatusMock);

console.log('pva meta-data ...', {
  name: pva.name,
  idx: pva.idx,
  keypath: pva.keypath,
});
console.log('current pva status ...', pva.valueOf());