helgeerbe / OpenDTU-OnBattery

Software for ESP32 to talk to Hoymiles Inverters and Victrons MPPT battery chargers (Ve.Direct)
GNU General Public License v2.0
292 stars 62 forks source link

[Request] Limit Battery Current of Victron MPPT via VE.DIRECT #470

Open JaMo523 opened 11 months ago

JaMo523 commented 11 months ago

Is your feature request related to a problem? Please describe.

Eingangsleistung der Photovoltaikmodule ist größer als der Inverter im (Full) Solar-Passthrough ans Netz übergeben kann. Die Batterie wird an Sonnentagen immer auf 100% geladen.

Describe the solution you'd like

Zusätzlich zu (Full) Solar-Passthrough könnte man den Batteriestrom vom Victron MPPT Laderegler über VE.DIRECT regeln. Das hat folgende Vorteile:

  1. Die Lebenszeit von dem gesamten System wird erhöht, weil Laderegler und Wechselrichter nicht beansprucht werden.
  2. Man kann mehr Photovoltaikmodule benutzen um auch bei schlechterem Wetter Strom zu erzeugen.

Describe alternatives you've considered

Keine alternative bekannt.

Additional context

image image https://www.victronenergy.com/upload/documents/BlueSolar-HEX-protocol.pdf

philippsandhaus commented 11 months ago

Interessant, genau an dieser Regelung des maximalen Ladestroms über das HEX-Protocol arbeite ich gerade, allerdings mit dem Ziel die Leistung eines AC-Laders auf Basis des zur Verfügung stehenden Überschusses zu beschränken. Die Notwendigkeit für einen MPPT-Lader erschliesst sich mir aber nicht wirklich. Aufgabe des MPPT-Ladereglers ist es doch gerade ein optimales Ladeprofil für die jeweilige Batteries unter Berücksichtigung des jeweiligen Ladezustands abzufahren. Wenn die Batterie voll ist, bzw. das Ladegerät in die Absoptionsphase übergeht wird der Strom nach und nach bis zur Ladeschlussspannung verringert. Wenn die Batterie voll ist, fliesst auch kein Strom mehr. Hier wird auch nichts unnötig "belastet" durch das Beschränken den maximalen Spannung.

Ich sehe also wirklich keinen Grund warum man den maximalen Strom eines MPPT-Laders beschränken sollte, die zur Verfügung stehende Energie (PV-Module) sollte ja möglichst ausgenutzt werden. Anders bei AC-Ladern, bei denen vermieden werden sollte, dass Energie aus dem Netz bezogen wird.

JaMo523 commented 11 months ago

Interessant, genau an dieser Regelung des maximalen Ladestroms über das HEX-Protocol arbeite ich gerade, allerdings mit dem Ziel die Leistung eines AC-Laders auf Basis des zur Verfügung stehenden Überschusses zu beschränken. Die Notwendigkeit für einen MPPT-Lader erschliesst sich mir aber nicht wirklich. Aufgabe des MPPT-Ladereglers ist es doch gerade ein optimales Ladeprofil für die jeweilige Batteries unter Berücksichtigung des jeweiligen Ladezustands abzufahren. Wenn die Batterie voll ist, bzw. das Ladegerät in die Absoptionsphase übergeht wird der Strom nach und nach bis zur Ladeschlussspannung verringert. Wenn die Batterie voll ist, fliesst auch kein Strom mehr. Hier wird auch nichts unnötig "belastet" durch das Beschränken den maximalen Spannung.

Ich sehe also wirklich keinen Grund warum man den maximalen Strom eines MPPT-Laders beschränken sollte, die zur Verfügung stehende Energie (PV-Module) sollte ja möglichst ausgenutzt werden. Anders bei AC-Ladern, bei denen vermieden werden sollte, dass Energie aus dem Netz bezogen wird.

Mir ist aufgefallen, dass der Victron MPPT Laderegler den Strom nicht herunter regelt. Die für Pylontech vorgeschriebenen Einstellungen sind auf dem MPPT Laderegler eingetragen.

Ich verstehe deine Argumentation aber woher weiß der Victron Laderegler welche Kapazität die Batterien haben? Davon Abhängig ist ja auch der Ladestrom. Das kann man nicht einstellen und woher weiß der Victron welcher Strom zur Last und welcher zur Batterie geht, wenn Last und Batterie an dem gleichen Ausgang angeschlossen sind? Die Pylontech gibt auch einen maximalen Ladestrom vor. Es gibt von Victron auch eine Lösung um die Pylontech über CAN einzubinden, dann werden die Daten vermutlich auch verwendet.

Ich glaube nicht das die Batterie von dem Laderegler zerstört wird. Wenn man seine Batterie aber zwischen 20 und 80% betreiben möchte, dann kommt man um die Limitierung des Ladestroms nicht herum.

Wie bereits bei der Eröffnung geschrieben, geht es um Fälle bei denen die Leistung der PV-Module wesentlich größer ist als die Leistung des Wechselrichters. Bei Sonnentagen kann man die erzeugte Leistung gar nicht verbrauchen. Der (Full) Solar-Passthrough kann dann ein weiters Laden nicht unterbinden. Außerdem hat der (Full) Solar-Passthrough vermutlich negative Auswirkungen auf die Lebenszeit vom Wechselrichter.

Ich programmiere auch gerade an dem HEX-Protocol. Kenn mich aber in C++ nicht aus.

schlimmchen commented 11 months ago

Der Laderegler wechselt von "Alles reinpumpen was geht" (Bulk) zu "Regle auf eine feste Spannung" (Absorption). Im Bulk Zustand gibt die Batterie die Spannung vor, die den Strom "schluckt". Im Absorption Zustand stellt sich der Strom von selbst ein, weil die Spannung fest ist. Wenn der Strom dann klein geworden ist, gehts in den Zustand Floating. Dazu muss der Laderegler nichts über die Batterie wissen außer der Zielspannung.

Deinen Vorschlag finde ich gut. Man kann zwar den Laderegler so einstellen, dass er den Akku nur auf 80% bringt (Spannung begrenzen), aber wenn man dann den Wechselrichter aufdreht weil man Energie braucht, dann kommt die erstmal aus der Batterie, bis der Laderegler begreift dass er wieder in Bulk wechseln sollte. Das kann man vermeiden wenn man den Laderegler drosselt und bei Bedarf aktiv aufdreht.

Klingt aber andererseits nach viel Arbeit für einen Sonderfall...

JaMo523 commented 11 months ago

Klingt aber andererseits nach viel Arbeit für einen Sonderfall...

Das HEX Protokoll ist eigentlich ziemlich einfach. Habe es mal quick und dirty bei mir reinprogrammiert und begrenze jetzt den Strom im (Full) Solar-Passthrough auf 10A. Die ~500W werden dann vom Wechselrichter ans Netz übergeben. Sobald der (Full) Solar-Passthrough aus ist wird das maxBatteryCurrentLimit wieder auf 45A geändert.

Man könnte den Power Limiter um eine Einstellung erweitern, bei der man dann den Batteriestrom bei aktivem (Full) Solar-Passthrough angeben kann und einmal bei nicht aktivem (Full) Solar-Passtrough.

Ich habe schon gelesen, dass ihr nicht noch mehr Einstellungen für den Power Limiter haben wollt, weil es für die meisten so schon kompliziert ist. Da könnte man auch "erweiterte Einstellungen" hinzufügen.

@philippsandhaus wie weit bist du denn mit dem HEX Protokoll? Ich habe jetzt mal nur das maxBatteryCurrentLimit als "SET-Command" programmiert, bei dem die Checksumme von einer Funktion berechnet wird. Das funktioniert auch soweit. Ich wollte eventuell noch den "GET-Command" hinzufügen um zu prüfen, ob die Einstellungen übernommen wurden.

philippsandhaus commented 11 months ago

Ich bin immer noch nicht ganz überzeugt, ob eine solche Drosselung wirklich nötig ist. Wie @schlimmchen schon schreibt, kann man den Ladestand der Batterie gut auf z.B. 80% begrenzen indem man die Absorptionsspannung im MPPT-Laderegler einfach runtersetzt. Den kurzen "Schluck" aus der Batterie wenn der Wechselrichter anspringt und der Laderegler noch nicht hinterherkommt sehe ich nicht als problematisch. Aber ich will das jetzt nicht unnötig schlecht machen, vielleicht sehe ich das ja auch falsch ;-) Technisch auf jeden Fall interessant.

wie weit bist du denn mit dem HEX Protokoll?

Da bin ich dann noch nicht viel weiter als Du denke ich. Ich habe die Möglichkeit reingehackt, mir wurde aber dann klar, dass auch für das weitere Testen erst einmal ein paar Umbauten/Abstraktionen notwendig sind, an denen ich gerade arbeite. Das wären für mich:

  1. Abstrahieren einer AC-Lader-Komponente analog zur Batterie-Komponente. Im Moment gibt es nur den Huawei als AC-Lader. Hier wäre aus Nutzersicht eine Auswahlmöglichkeit zwischen Huawei und VIctron im Einstellungsmenü sinnvoll mit entsprechender Abstraktion im Code.
  2. Erweitern des VEDirectFramehandlers und das allgemeine Handling von HEX-Nachrichten (Senden und Empfangen). Ich hatte zunächst geplant, die konkreten Commands in einer eigenen davon abgeleiteten Klasse VEDirectBlueSmartControllerzu implementieren, aber vielleicht macht es dann Sinn, eine Zwischenebene mit einer abstrakten Klassen wie z.B. VEDirectChargerControllereinzuführen von der dann VEDirectBlueSmartController und VEDirectMpptControllererben. Hier könnte dann ein Großteil des Hex-Protokolls (die eigentlichen Commands) implementiert werden, da denke ich vieles auf beide zutrifft.

Ich habe jetzt mal nur das maxBatteryCurrentLimit als "SET-Command" programmiert, bei dem die Checksumme von einer Funktion berechnet wird. Das funktioniert auch soweit. Ich wollte eventuell noch den "GET-Command" hinzufügen um zu prüfen, ob die Einstellungen übernommen wurden.

Magst Du den Code teilen bzw. vielleicht auch selber den Teil entsprechend erweitern in VEDirectFrameHandler? Ziel wäre ich denke ich dieses soweit zu abstrahieren, dass das ganze leicht um zusätzliche SET-Commands erweitert werden kann. Eine weitere Anwendung wäre z.B. das Abrufen von historischen Daten, um sie, ähnlich wie in der Victron App, perspektivisch im Webinterface anzeigen zu können.

Generell: Laut Doku und auch laut meinen Tests sollte das Gerät auf ein SET-Command irgendwann mit der gleichen Nachricht antworten. Das wäre für mich dann die Bestätigung, dass das Kommando angekommen ist. Das ist dann auch eine schöne generelle Lösung für alle Arten von Commands. Falls eine Antwort innerhalb eines bestimmten Zeitraums nicht kommt, kann man das Kommando ja noch einige Male erneut schicken, bevor man mit einem Fehler aufgibt. Ich glaube nicht, dass man den Wert noch gesondert über ein GET-Kommando abrufen muss, aber müsste man natürlich testen.

JaMo523 commented 11 months ago

Ich bin immer noch nicht ganz überzeugt, ob eine solche Drosselung wirklich nötig ist. Wie @schlimmchen schon schreibt, kann man den Ladestand der Batterie gut auf z.B. 80% begrenzen indem man die Absorptionsspannung im MPPT-Laderegler einfach runtersetzt. Den kurzen "Schluck" aus der Batterie wenn der Wechselrichter anspringt und der Laderegler noch nicht hinterherkommt sehe ich nicht als problematisch. Aber ich will das jetzt nicht unnötig schlecht machen, vielleicht sehe ich das ja auch falsch ;-) Technisch auf jeden Fall interessant.

Die Batterie durch einstellen der Spannung genau auf 80% zu laden wird nicht so gut funktionieren, da die Temperaturschwankungen dann nicht kompensiert werden.

Gestern hat mein System die 80% gehalten. Ich habe aber auch schon gelesen, dass die Pylontech ab und zu auf 95-100% geladen werden soll. Da könnte man auch noch eine Option einbauen, dass ein Laden auf 100% nach x Tagen durchgeführt wird.

Ich kenne mich mit Klassen und vererben noch nicht so gut aus. Habe schon C programmiert aber noch nicht C++.

Konstanten:

// hex commands
enum VeDirectHexCommands {
    ENTER_BOOT = 0,
    PING = 1,
    APP_VERSION =2,
    PRODUCT_ID = 4,
    RESTART = 6,
    GET = 7,
    SET = 8,
    ASYNC = 10
};

// hex ids
enum VeDirectHexIds {
    BATTERY_MAXIMUM_CURRENT = 0xEDF0,
};

enum VeDirectFlags {
    DEFAULT_FLAG0 = 0,
};

// this is used to convert uint8_t hex into hex string 
static char HEX_MAP[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};

berechnen des Check Wertes:

uint8_t VeDirectFrameHandler::veDirectHexChecksum(String command)
{
    char buffer[2];
    bool convert = false;
    uint8_t sum=0;

    for(char& c : command) {
        if (!convert){
            if (c != ':'){
                buffer[0] = c;
            }
            else {
                buffer[0] = '0';
            }
            convert = true;
        }
        else {
            // convert
            buffer[1] = c;
            sum+= strtoul (buffer, NULL, 16);
            convert = false;
        }
    }
    // calculate victron check value 
    // sum of command + flag + value + check = 0x55
    return (0x55 - sum);
}

umwanden uint nach hex String:

/*
 * hexToVeDirectStrHex
 * This functions convert hex to VeDirect hex strings.
 */
String VeDirectFrameHandler::uint4toVeDirectHexStr(uint8_t value)
{
    return (String(HEX_MAP[(value & 0x0F)]));
}

String VeDirectFrameHandler::uint8toVeDirectHexStr(uint8_t value)
{
    return (String(HEX_MAP[(value >> 4)]) + String(HEX_MAP[(value & 0x0F)]));
}

String VeDirectFrameHandler::uint16toVeDirectHexStr(uint16_t value)
{
    // victron use little endian
    return (String(HEX_MAP[((value & 0xF0)>> 4)]) + String(HEX_MAP[(value & 0x0F)]) + String(HEX_MAP[((value)>> 12)]) + String(HEX_MAP[((value & 0xF00)>> 8)]));
}

Befehl um uint16 Wert zu setzen:

/*
 * veDirectHexSetUint16
 * This function uses set command to set uint16 value.
 */
void VeDirectFrameHandler::veDirectHexSetUint16(uint16_t veDirectId, uint8_t veDirectFlag, uint16_t value)
{
    // set command
    String txData = ":" + uint4toVeDirectHexStr(SET);
    // add id
    txData += uint16toVeDirectHexStr (veDirectId);
    // add flags
    txData+=uint8toVeDirectHexStr(veDirectFlag);
    // add current
    txData+=uint16toVeDirectHexStr(value);
    // add checksum
    txData+=uint8toVeDirectHexStr(veDirectHexChecksum(txData));
    // send data to Victron
    VedirectSerial.print(txData+"\n");
    _msgOut->println("[VE.Direct] send hex command: " + txData);
}

Batterieladestrom Limit setzen:

/*
 * setBatteryCurrentLimit
 * This function sets battery current limit. Don't call it in a loop because 
 * Victron is writing battery current limit in non volatile memory.
 */
void VeDirectFrameHandler::setBatteryCurrentLimit(uint16_t batteryCurrentLimit)
{
    // scale current (0.1 A)
    uint16_t veBatCurrentLimit = batteryCurrentLimit * 10;
    // prase command :8[F0ED]00[6400][0C]\n
    veDirectHexSetUint16(BATTERY_MAXIMUM_CURRENT,DEFAULT_FLAG0,veBatCurrentLimit);
    // log message
    _msgOut->printf("[VE.Direct] Set Victron batteryMaximumCurrent to %dA.\r\n",batteryCurrentLimit);
}
philippsandhaus commented 11 months ago

Die Batterie durch einstellen der Spannung genau auf 80% zu laden wird nicht so gut funktionieren, da die Temperaturschwankungen dann nicht kompensiert werden.

Genau dafür gibt es in den Einstellungen der Victron App eine Option (Temperaturkompensation).

Gestern hat mein System die 80% gehalten. Ich habe aber auch schon gelesen, dass die Pylontech ab und zu auf 95-100% geladen werden soll. Da könnte man auch noch eine Option einbauen, dass ein Laden auf 100% nach x Tagen durchgeführt wird.

Kann man natürlich alles machen, das macht das Management aber noch komplexer. Vom Gefühl her würde ich möglichst viel des Batteriemanagements den Victron-Komponenten überlassen. Es scheint ja auch generell eine sehr gute Unterstützung von Victron für Pylontech zu geben. Aber das ist sicher auch eine Philosophiefrage.

Vielen Dank für die Code-Teile, das bildet schon sehr viel ab! Wenn für dich ok würde ich mich daran bedienen und in meinen Code einbauen.

JaMo523 commented 11 months ago

Ich sehe es eher umgekehrt. Die DTU hat viel mehr Informationen und kann deshalb viel besser entscheiden.

Klar ist der Victron Mppt kann auch alleine die Batterie managen, aber selbst Victron bietet ja BMS, Shunts, Controller usw. an um ein Vollständiges System aufzubauen.

Ich sehe die DTU als Controller.

Den Code kannst du gerne verwenden. Ich teste momentan noch ein bisschen und stelle dann ein pull request.

schlimmchen commented 11 months ago

Gestern habe ich mich mit zwei anderen studierten Informatikern über diesen Post unterhalten und einen Online-Compiler bemüht und wir sind zum Schluss gekommen, dass es keinen Unterschied gibt zwischen diesen beiden return-Statements. Und auch ohne Klammer dürfte es kein anderes Verhalten ergeben.

Was funktioniert da denn nicht wie erwartet, wenn du nur eine Klammer nimmst? Das würde mich in der Tat interessiern. Meckert der Compiler?

philippsandhaus commented 11 months ago

Zumindest bei mir bemerke ich keinen Unterschied ob ich eine oder zwei Klammern nehme. Kompiliert beides ohne Probleme, hab jetzt aber nicht gestenstet, ob sich das Ergebnis ändert.

@JaMo523: Vorschlag von meiner Seite: Ich könnte die Erweiterungen in den Klassen vom VDirectFrameHandler einbinden und das erst einmal als PR aufbereiten. Du könntest dann deine Erweiterungen im DPL zum Reduzieren des Ladestroms als darauf aufbauenden PR umsetzen. Das sollte dann ja auf deiner mit minimalen bis gar keinen Änderungen möglich sein und wir kommen uns nicht in die Quere.

philippsandhaus commented 11 months ago

@JaMo523 Noch eine Frage: Ich sehe gerade, dass Du 0xEDF0 (Battery Maximum Current) als Register nutzt. Es gibt aber auch 0xEDDF (Charger Maximum Current). Ich meine, das hatte ich bei meinen ersten Tests genutzt. Weisst Du, was der Unterschied ist?

JaMo523 commented 11 months ago

Gestern habe ich mich mit zwei anderen studierten Informatikern über diesen Post unterhalten und einen Online-Compiler bemüht und wir sind zum Schluss gekommen, dass es keinen Unterschied gibt zwischen diesen beiden return-Statements. Und auch ohne Klammer dürfte es kein anderes Verhalten ergeben.

Was funktioniert da denn nicht wie erwartet, wenn du nur eine Klammer nimmst? Das würde mich in der Tat interessiern. Meckert der Compiler?

Ich glaube es war gestern schon spät. Es kommt das selbe raus mit oder ohne Klammer. Habe von Hand die Checksumme errechnet und dabei vermutlich einen Fehler gemacht. Ich habe den Post gelöscht um Fehlinformationen zu vermeiden.

@philippsandhaus Dein Vorschlag ist gut, dass können wir so machen.

Bei 0xEDF0 (Battery Maximum Current) ist angegeben, dass es im non-volatile Speicher gespeichert wird. Man sieht dann auch in der App unter den Batterieeinstellungen, dass das Limit gesetzt ist. Deshalb habe ich es benutzt.

alexseuf commented 7 months ago

Wenn von einem (Pylontech) Batterie BMS ein (dynamischer) charge voltage (Soll)werte übertragen wird, sollte dieser auch als Sollwert an den MPPT durchgereicht werden. und als Charge current für den MPPT sollte der vom BMS übergbene max. Charge current + dem vom hoymiles aktuell bezogener String current(summe) übergeben werden, wenn kein solar pasthrough aktiviert ist. So macht das Victron ja auch mit seinem Ladespannungsgrenze (CVL), eine Ladestrombegrenzung (CCL).

hoschiking commented 7 months ago

Darf ich mal fragen wie es mit dem Request weiter geht? Ich bin stark an einer dynamischen Strombegrenzung eine Victron MPPT interessiert, ich suche nach einer solchen Lösung schon lange. Ich missbrauche einen MPPT als AC-Charger (an einem Meanwell Netzteil) um den PV-Überschuß der großen Dachanalge in den Speicher zu laden. Da ich mit 24V arbeite, ist die implementierte Huawei-Lösung nicht möglich. Im Prinzip so, wie es @philippsandhaus beschrieben hat. Ich kann zwar nicht programmieren, aber ich würde mich als Tester anbieten. Fürs erste müßte der Code nicht unbedingt in OpenDTU implementiert werden, für Testzwecke könnte man eine dedizierten ESP nehmen. Aufs Feedback würde ich mich freuen.

dkeck commented 4 months ago

Ich mache das gleiche wie hoschiking. Habe mehrere HLGs im Einsatz. Auf der Suche nach einer Lösung hier gelandet. Bitte auch das Register 0x2015 nutzbar machen für zyklisches Schreiben falls es diese Funktion ins Projekt schafft. Grund: Warnung in Doku nicht zyklisch in 0xEDF0 zu schreiben.

Der Rest nur als Referenz und Zusammenfassung für Interessierte, die so wie ich bei Null anfangen und es auch mit der Hand durchspielen wollen. Prüfsumme wird gegen 0x55 gerechnet. Strom ist ein Vielfaches von 100mA. 10A-Standardbeispiel aus Doku auf S.27. Register 0x2015 siehe Seite 26 in der Doku. Doku: https://www.victronenergy.com/upload/documents/BlueSolar-HEX-protocol.pdf

Symbolik: A) >>HexCommandAsBytes<< B) [[StringCommandAsChars]] C) <LF> = LineFeed

10A-Limit in festen Speicher schreiben = Register 0xEDF0 >>3a 38 46 30 45 44 30 30 36 34 30 30 30 43 0a<< [[:8F0ED0064000C<LF>]] Prüfsumme: 0x55-0x08-0xF0-0xED-0x00-0x64-0x00 = FFFFFFFFFFFFFE0C = 0C (Überlauf bedeutungslos) 10A = 100*100mA => 100 = 0x64

1A-Limit in festen Speicher schreiben = Register 0xEDF0 >>3a 38 46 30 45 44 30 30 30 41 30 30 36 36 0a<< [[:8F0ED000A0066<LF>]] Prüfsumme: 0x55-0x08-0xF0-0xED-0x00-0x0A-0x00 = FFFFFFFFFFFFFE66 = 66 (Überlauf bedeutungslos) 1A = 10*100mA => 10 = 0xA

2A-Limit in flüchtigen Speicher schreiben = Register 0x2015 >>3a 38 31 35 32 30 30 30 31 34 30 30 30 34 0a<< [[:8152000140004<LF>]] Prüfsumme: 0x55-0x08-0x15-0x20-0x00-0x14-0x00 = 4 (kein Überlauf da Summe kleiner als 55) 2A = 20*100mA => 20 = 0x14 Register 0x2015 wird nach 1 Minute auf den Wert von 0xEDF0 geschrieben; muss somit zyklisch wiederholt werden

    public static void Main(string[] args){
      byte[] b = new byte[] {0x8,0xF0,0xED,0x0,0xA,0x0}; //1A-Beispiel
      int chk = 0;
      for(int i = 0; i < b.Length; i++){
         chk += b[i];
      }
      Console.WriteLine ((0x55-chk).ToString("X")); //FFFFFE66 - nur das letzte Byte ist relevant: 0x66
    }

Add1: Es lässt sich sauber auf 1 Nachkommastelle regeln, was mit Victrons Apps (Windows, Android) nicht möglich ist, was aber irrelevant ist, da diese ins falsche Register schreiben. 0xEDF0 funktioniert dabei stets als obere Schranke für 0x2015. Beispiel 8.5A: >>3A 38 31 35 32 30 30 30 35 35 30 30 43 33 0A<< [[:81520005500C3<LF>]]

Zur Entlastung sollten alle Kombinationen fix abgespeichert werden, statt diese immer wieder zu berechnen. Würde z.B. für einen MPPT100/20 bedeuten, dass sich eine Tabelle mit 200 Einträgen ergibt.