NEWS
ecoflow-connector-Script zur dynamischen Leistungsanpassung
-
@walle-0 Das ist eine interessante Idee... Könnte ich in meiner Configuration auch brauchen... Hast Du damit schon gearbeitet? Kann man den Wert auch auf 0 setzten? Zieht die Delta dann noch irgendwie Strom? Wozu nutz Du das? Einspeisen kannst Du ja nicht mit der Delta.
-
@walle-0 Ist das die Einstellung für die Ladegeschwindigkeit per AC? Sowas geht zumindest grundsätzlich mit MQTT. Ich habe das mit meiner Delta 2 eine Weile gemacht um je nach Überschuss alles zur Verfügung stehende abzugreifen. Die Watt-Zahl wurde permanent angepasst (zwischen 200 und 2000). Leider ist mir irgendwann der AC Eingang zerschossen. Ich weiß nicht ob es damit zu tun hatte? Dass die Elektronik eine permanente Anpassung des Wertes nicht vertragen kann?
-
@waly_de Aktuell mache ich die Einstellungen über die APP und funktioniert seit 1,5 Monaten soweit gut.
Den Wert kann man auf minimal 200W einstellen. Ja sie bezieht beim Laden die 200W plus den Verbrauch der angeschlossenen Geräte.
Da ich die PV am Dach installiert habe, kann ich für gezielte Endverbraucher (Kühlschrank, einzellne Räume) nur die 230V zum laden benutzen. Meine PV ist nicht Blackout fähig.
Die Delta Pro und Delta Pro 2 hängen in Serie zum jeweiligen Gerät / Raum, damit dieser bei einem Stromausfall mit Strom versorgt ist und fungieren sozusagen als USV.Um den Wert auf 0 zu bringen, schalte ich aktuell einen SONOFF POW320D um die Ecoflows vom Netz zu trennen.
Unterschiede zw. den beiden sind:
-
Ecoflow Delta Pro max. Ladeleistung = 2900W (Einstellbar), ich persönlich stelle es aber nie über 2000W um das Gerät zu schonen. (IOBroker Variable = inv.CfgSlowChgWatts => Dieser Wert ändert sich auf den in der APP eingestellten Wert)
-
Ecoflow Delta 2 max. Ladeleistung = 1200W (Einstellbar), ich gehe aber auch hier nie über 1000W. (IOBroker Variable = mppt.CfgChgWatts => Dieser Wert ändert sich auf den in der APP eingestellten Wert)
-
-
@mattenausohz Ja das ist der Wert, der sich bei mir immer ändert je nachdem was ich in der APP einstelle.
Bisher hatte ich noch keine Probleme das hier irgendetwas defekt wurde. -
Post nicht beachten. Anliegen hat sich geklärt<
-
@mattenausohz
Beim Smartplug ist bei einer proto Nachricht die dritte Stelle kein string, wie auf der erwähnten Decoder Seite angezeigt wird. Es ist ein Array von uint32 mit 24 Einträgen. Jede Stelle ist eine Stunde Watt type 5 ist watth und watt type 6 ist die Zeit, wo er an war in dieser Stunde in Minuten.
Also mann muss bei dieser Nachricht nur die Einträge von dem Array bei watt type 5 addieren. Dann erhält mann die watth von dem Tag.
Sehr wahrscheinlich ist das beim powerstream auch so.
Hier die Proto:syntax = "proto3"; message EnergyItem { optional uint32 timestamp = 1; optional uint32 watth_type = 2; repeated uint32 watth = 3; //diese Stelle } message BatchEnergyTotalReport { optional uint32 watth_seq = 1; repeated EnergyItem watth_item = 2; }
-
Hab das Script auch mal bei mir intigriert und läuft auf anhieb.
Ich habe den Datenpunkt von Tibber Lesekopf bei den Parameter angegeben. Tasächlich regelt er der PS dynamisch. Macht also was es soll. Sehr schön und vielen Dank
Jetzt schaue ich mal was noch so möglich ist. -
@walle-0
Ich arbeite gerade daran. Das script ist wesentlich verändert und legt auch neue States an, weil ich nun die richtigen Definitionen verwende.Anbei das Script. Bitte als BETA betrachten. und vorsichtig sein:
/** * ecoflow-connector.js * Version: 0.6.1 * Release date: 26.07.2023 * Autor: Waly_de * Forum: https://forum.iobroker.net/topic/66743/ecoflow-connector-script-zur-dynamischen-leistungsanpassung * * * This JavaScript file establishes a simple connection between IOBroker and EcoFlow. * It automatically creates known states under 0_userdata. * * Please note that adjustments in the ConfigData section are required. Here, you need to enter your access credentials * used for the EcoFlow app, as well as the serial numbers of your devices. * * If you have a state that displays the current power consumption (SmartmeterID), please provide it as well. * This value will be used to dynamically adjust the Powerstream's feed-in power. * * Not all parameters of the Powerstream data are known yet. All known parameters will be automatically created as states. * By modifying the "protoSource" constant, newly discovered data will also be automatically created. * * The raw data of the interface is logged as a HEX string. * * Please exercise caution as this is the initial version of the script. Use it at your own risk! * * Requirements: * - Install protobuf using the command "npm install protobufjs" from the terminal console. * - The "Paho MQTT Client" is also required. If not already installed, use the command "npm install mqtt". * You can also simply add this libs to your javascript instance configuration (Zusätzliche NPM-Module) * * Note: It is encouraged to discover and publish missing data definitions to improve the script. * Suggestions, optimizations, and extensions are welcome at any time. * * Special thanks to all contributors for their valuable input and support. * * Changelog: * ----------------------------------------------------------- * (0.4) 29.06.2023 * Da der MQTT von ecoflow regelmäßig aufhört zu senden, vor allem, wenn die App genutzt und komplett geschlossen wird, * habe ich eine Überwachung der letzten ankommenden Nachrichten eingebaut. Kommt 5 Minuten lang nichts neues vom PowerStream, * Wird die Verbindung zum MQTT kompett neu aufgebaut. * * Ein Fehler bei der Erstellung der States wurde beseitigt * ----------------------------------------------------------- * (0.5.2) 06.07.2023 * State sumPV hinzugefügt (Summe aus PV1 und PV2) = Solar-Leistung gesamt * Abweichung der PV Power von der App versucht zu kompensieren * Neuen State RealPower zur besseren Ermittlung der Einspeiseleistung angelegt. Zeigt den Verbrauch im Haus ohne Einspeisung * History für RealPower wird automatisch aktiviert * Die koordinaten werden aus den Systemeistellungen ermittelt ( Sonst einfach selbst angeben ) * Diverse kleine Anpassungen und Bugfixes * Es werden jetzt mehrere PowerStream berücksichtigt. In der Konfiguration muss das Flag "isPowerStream" gesetzt werden * Gesteuert wird aber bisher nur die erste PowerStream * die protoSource so angepasst, dass in "Item" enthaltene unbekannte Daten auch als State angelegt werden. Bitte helft mit zu identifizieren * was was ist. Dann können wir die Felder entsprechend benennen... * Parameter "subscribe" bei der Gerätekonfiguration hinzugefügt. Damit lässt sich der Empfang von MQTT Telegrammen für das Gerät abstellen * Mein Delta Max hatte derart viel gesendet, dass der Raspi nicht mehr in der Lage war alles zu verarbeiten. Für die Steuerung braucht man die Daten der Batterie nicht. * Nach Sonnenuntergang wird jetzt weniger oft reconnectet, wenn keine Daten mehr kommen. * Reaktionszeiten für die Anpassung der Einspeisung wurde erhöht (30 sekunden) * State "totalPV" für die derzeitige komplett PV Leistung hinzugefügt * Funtion hinzugefügt, die die Einspeisung bei voller Batterie auf Maximum stellt. Ein und Ausschaltprozent können mit battPozOn und battPozOff eingestellt werden. * ----------------------------------------------------------- * (0.6.1) 26.07.2023 * ACHTUNG: Die Felddefinitionen für den Powerstream sind jetzt vollständig und an die von der Community ermittelten Daten angepasst. * Das bedeutet aber leider auch, dass alle States mit neuen Namen neu angelegt werden. Die vom Script generierten States bleiben erhalten (SerAC, totalPV, sumPV). * * Für die Delta Max sind nun einige States auch zum Schreiben verfügbar. Die Delta muss dazu nicht unbedingt auf "subscribe: true" gestellt werden. * Damit die States angelegt werden, müssen sie bei laufendem Script einmal in der App verändert werden. Möglich sind bisher: * Beep, slowChgPower, ACPower, DCPower, USBPower, minDsgSoc, maxChgSoc, curr12VMax, standByModeMins, lcdTimeMins, ACstandByMins, openOilSoc, closeOilSoc. * * Ob diese States auch so bei anderen Deltas funktionieren, kann ich nicht sagen. Wenn nicht, solltet Ihr im Log einen Eintrag finden: "Unbekannter Set Befehl: ". * Wenn ich diesen Eintrag mit einer kurzen Beschreibung erhalte, was es ist, kann ich es auch einbauen. * * Die States werden hier angelegt: 0_userdata.0.ecoflow.app_XXXXXXXXXXXXXXXXXXX_XXXXXXXXXXXXXXX_thing_property_set.writeables * * */ // SystemKoordinaten werden versucht zu ermittelm und als Default den Variablen zugeteilt var latitude; var longitude; // ermitteln des Standortes aus den Settings getStandortKoordinaten() /*************************************** ********** YOUR DATA HERE ************ ****************************************/ var ConfigData = { email: "your@mail.com", //Die App-Zugangsdaten von ecoFlow passwort: "yourAppPasswort!", seriennummern: [ { seriennummer: "XXXXXXXXXXXXX", name: "PowerStream", isPowerStream: true, subscribe: true }, { seriennummer: "XXXXXXXXXXXXX", name: "DELTA Max", isPowerStream: false, subscribe: false } ], SmartmeterID: "sonoff.0.Stromzaehler1.MT175_P", //State, das den aktuellen Gesamtverbrauch in Watt enthält BasePowerOffset: 50, //wird vom aktuellen Verbrauch abgezogen um die Einspeiseleistung zu berechnen MaxPower: 600, //Der höchst mögliche wert in Watt für die Einspeiseleistung MinValueMin: 3, //Der Zeitraum in Minuten, aus dem der letzte Gesamtverbrauchs-Minimalwert geholt werden soll ReconnectMin: 30, //Zeit in Minuten, nach der die Anwendung neu gestartet wird, wenn keine neuen Daten eintreffen statesPrefix: "0_userdata.0.ecoflow", //Hier werden die ecoFlow States angelegt latitude: latitude, //Breitengrad des Standortes (wird automatisch eingesetzt) longitude: longitude, //Längengrad des Standortes (wird automatisch eingesetzt) battPozOn: 98, battPozOff:93, //Wenn die Batterie bei battPozOn ist, Einspeisung auf MaxPower. Bei BattPozOff Normalbetieb Debug: false }; //***************************************/ //***************************************/ const messageTypes = { 20: 'InverterHeartbeat', // 254: 'BatchEnergyTotalReport', // Weitere cmd_func-Werte und zugehörige Nachrichtentypen }; const messageIDTypes = { 1: 'InverterHeartbeat', 11: 'setValue', 136: 'PowerPack', 138: 'PowerPack', 129: 'setValue', // Weitere cmd_func-Werte und zugehörige Nachrichtentypen }; const protoSource2 = ` syntax = "proto3"; message Message { Header header = 1; bytes payload = 2; } message Header { bytes pdata = 1 [proto3_optional = true]; int32 src = 2 [proto3_optional = true]; int32 dest = 3 [proto3_optional = true]; int32 d_src = 4 [proto3_optional = true]; int32 d_dest = 5 [proto3_optional = true]; int32 enc_type = 6 [proto3_optional = true]; int32 check_type = 7 [proto3_optional = true]; int32 cmd_func = 8 [proto3_optional = true]; int32 cmd_id = 9 [proto3_optional = true]; int32 data_len = 10 [proto3_optional = true]; int32 need_ack = 11 [proto3_optional = true]; int32 is_ack = 12 [proto3_optional = true]; int32 seq = 14 [proto3_optional = true]; int32 product_id = 15 [proto3_optional = true]; int32 version = 16 [proto3_optional = true]; int32 payload_ver = 17 [proto3_optional = true]; int32 time_snap = 18 [proto3_optional = true]; int32 is_rw_cmd = 19 [proto3_optional = true]; int32 is_queue = 20 [proto3_optional = true]; int32 ack_type = 21 [proto3_optional = true]; string code = 22 [proto3_optional = true]; string from = 23 [proto3_optional = true]; string module_sn = 24 [proto3_optional = true]; string device_sn = 25 [proto3_optional = true]; } message InverterHeartbeat { optional uint32 inv_err_code = 1; optional uint32 inv_warn_code = 3; optional uint32 pv1_err_code = 2; optional uint32 pv1_warn_code = 4; optional uint32 pv2_err_code = 5; optional uint32 pv2_warning_code = 6; optional uint32 bat_err_code = 7; optional uint32 bat_warning_code = 8; optional uint32 llc_err_code = 9; optional uint32 llc_warning_code = 10; optional uint32 pv1_statue = 11; optional uint32 pv2_statue = 12; optional uint32 bat_statue = 13; optional uint32 llc_statue = 14; optional uint32 inv_statue = 15; optional int32 pv1_input_volt = 16; optional int32 pv1_op_volt = 17; optional int32 pv1_input_cur = 18; optional int32 pv1_input_watts = 19; optional int32 pv1_temp = 20; optional int32 pv2_input_volt = 21; optional int32 pv2_op_volt = 22; optional int32 pv2_input_cur = 23; optional int32 pv2_input_watts = 24; optional int32 pv2_temp = 25; optional int32 bat_input_volt = 26; optional int32 bat_op_volt = 27; optional int32 bat_input_cur = 28; optional int32 bat_input_watts = 29; optional int32 bat_temp = 30; optional uint32 bat_soc = 31; optional int32 llc_input_volt = 32; optional int32 llc_op_volt = 33; optional int32 llc_temp = 34; optional int32 inv_input_volt = 35; optional int32 inv_op_volt = 36; optional int32 inv_output_cur = 37; optional int32 inv_output_watts = 38; optional int32 inv_temp = 39; optional int32 inv_freq = 40; optional int32 inv_dc_cur = 41; optional int32 bp_type = 42; optional int32 inv_relay_status = 43; optional int32 pv1_relay_status = 44; optional int32 pv2_relay_status = 45; optional uint32 install_country = 46; optional uint32 install_town = 47; optional uint32 permanent_watts = 48; optional uint32 dynamic_watts = 49; optional uint32 supply_priority = 50; optional uint32 lower_limit = 51; optional uint32 upper_limit = 52; optional uint32 inv_on_off = 53; optional uint32 wireless_err_code = 54; optional uint32 wireless_warn_code = 55; optional uint32 inv_brightness = 56; optional uint32 heartbeat_frequency = 57; optional uint32 rated_power = 58; } message setMessage { setHeader header = 1; } message setHeader { setValue pdata = 1 [proto3_optional = true]; int32 src = 2 [proto3_optional = true]; int32 dest = 3 [proto3_optional = true]; int32 d_src = 4 [proto3_optional = true]; int32 d_dest = 5 [proto3_optional = true]; int32 enc_type = 6 [proto3_optional = true]; int32 check_type = 7 [proto3_optional = true]; int32 cmd_func = 8 [proto3_optional = true]; int32 cmd_id = 9 [proto3_optional = true]; int32 data_len = 10 [proto3_optional = true]; int32 need_ack = 11 [proto3_optional = true]; int32 is_ack = 12 [proto3_optional = true]; int32 seq = 14 [proto3_optional = true]; int32 product_id = 15 [proto3_optional = true]; int32 version = 16 [proto3_optional = true]; int32 payload_ver = 17 [proto3_optional = true]; int32 time_snap = 18 [proto3_optional = true]; int32 is_rw_cmd = 19 [proto3_optional = true]; int32 is_queue = 20 [proto3_optional = true]; int32 ack_type = 21 [proto3_optional = true]; string code = 22 [proto3_optional = true]; string from = 23 [proto3_optional = true]; string module_sn = 24 [proto3_optional = true]; string device_sn = 25 [proto3_optional = true]; } message setValue { optional int32 value = 1; } message permanent_watts_pack { optional int32 permanent_watts = 1; } message supply_priority_pack { optional int32 supply_priority = 1; } message bat_lower_pack { optional int32 lower_limit = 1; } message bat_upper_pack { optional int32 upper_limit = 1; } message PowerItem { optional uint32 timestamp = 1; optional sint32 timezone = 2; optional uint32 inv_to_grid_power = 3; optional uint32 inv_to_plug_power = 4; optional int32 battery_power = 5; optional uint32 pv1_output_power = 6; optional uint32 pv2_output_power = 7; } message PowerPack { optional uint32 sys_seq = 1; repeated PowerItem sys_power_stream = 2; } message PowerAckPack { optional uint32 sys_seq = 1; } message node_massage { optional string sn = 1; optional bytes mac = 2; } message mesh_child_node_info { optional uint32 topology_type = 1; optional uint32 mesh_protocol = 2; optional uint32 max_sub_device_num = 3; optional bytes parent_mac_id = 4; optional bytes mesh_id = 5; repeated node_massage sub_device_list = 6; } message EnergyItem { optional uint32 timestamp = 1; optional uint32 watth_type = 2; repeated uint32 watth = 3; } message EnergyTotalReport { optional uint32 watth_seq = 1; optional EnergyItem watth_item = 2; } message BatchEnergyTotalReport { optional uint32 watth_seq = 1; repeated EnergyItem watth_item = 2; } message EnergyTotalReportAck { optional uint32 result = 1; optional uint32 watth_seq = 2; optional uint32 watth_type = 3; } message EventRecordItem { optional uint32 timestamp = 1; optional uint32 sys_ms = 2; optional uint32 event_no = 3; repeated float event_detail = 4; } message EventRecordReport { optional uint32 event_ver = 1; optional uint32 event_seq = 2; repeated EventRecordItem event_item = 3; } message EventInfoReportAck { optional uint32 result = 1; optional uint32 event_seq = 2; optional uint32 event_item_num = 3; } message ProductNameSet { optional string name = 1; } message ProductNameSetAck { optional uint32 result = 1; } message ProductNameGet {} message ProductNameGetAck { optional string name = 3; } message RTCTimeGet {} message RTCTimeGetAck { optional uint32 timestamp = 1; optional int32 timezone = 2; } message RTCTimeSet { optional uint32 timestamp = 1; optional int32 timezone = 2 [(nanopb).default = 0]; } message RTCTimeSetAck { optional uint32 result = 1; } message country_town_message { optional uint32 country = 1; optional uint32 town = 2; } message time_task_config { optional uint32 task_index = 1; optional time_range_strategy time_range = 2; optional uint32 type = 3; } message time_task_delet { optional uint32 task_index = 1; } message time_task_config_post { optional time_task_config task1 = 1; optional time_task_config task2 = 2; optional time_task_config task3 = 3; optional time_task_config task4 = 4; optional time_task_config task5 = 5; optional time_task_config task6 = 6; optional time_task_config task7 = 7; optional time_task_config task8 = 8; optional time_task_config task9 = 9; optional time_task_config task10 = 10; optional time_task_config task11 = 11; } message time_task_config_ack { optional uint32 task_info = 1; } message rtc_data { optional int32 week = 1 [(nanopb).default = 0]; optional int32 sec = 2 [(nanopb).default = 0]; optional int32 min = 3 [(nanopb).default = 0]; optional int32 hour = 4 [(nanopb).default = 0]; optional int32 day = 5 [(nanopb).default = 0]; optional int32 month = 6 [(nanopb).default = 0]; optional int32 year = 7 [(nanopb).default = 0]; } message time_range_strategy { optional bool is_config = 1; optional bool is_enable = 2; optional int32 time_mode = 3; optional int32 time_data = 4; optional rtc_data start_time = 5; optional rtc_data stop_time = 6; } message plug_ack_message { optional uint32 ack = 1; } message plug_heartbeat_pack { optional uint32 err_code = 1 [(nanopb).default = 0]; optional uint32 warn_code = 2 [(nanopb).default = 0]; optional uint32 country = 3 [(nanopb).default = 0]; optional uint32 town = 4 [(nanopb).default = 0]; optional int32 max_cur = 5 [(nanopb).default = 0]; optional int32 temp = 6 [(nanopb).default = 0]; optional int32 freq = 7 [(nanopb).default = 0]; optional int32 current = 8 [(nanopb).default = 0]; optional int32 volt = 9 [(nanopb).default = 0]; optional int32 watts = 10 [(nanopb).default = 0]; optional bool switch = 11 [(nanopb).default = false]; optional int32 brightness = 12 [(nanopb).default = 0]; optional int32 max_watts = 13 [(nanopb).default = 0]; optional int32 heartbeat_frequency = 14 [(nanopb).default = 0]; optional int32 mesh_enable = 15 [(nanopb).default = 0]; } message plug_switch_message { optional uint32 plug_switch = 1; } message brightness_pack { optional int32 brightness = 1 [(nanopb).default = 0]; } message max_cur_pack { optional int32 max_cur = 1 [(nanopb).default = 0]; } message max_watts_pack { optional int32 max_watts = 1 [(nanopb).default = 0]; } message mesh_ctrl_pack { optional uint32 mesh_enable = 1 [(nanopb).default = 0]; } message ret_pack { optional bool ret_sta = 1 [(nanopb).default = false]; } enum CmdFunction { Unknown = 0; PermanentWattsPack = 129; SupplyPriorityPack = 130; } `; const protoSource = ` syntax = "proto3"; message PowerItem { optional Meta meta = 1; optional uint32 src = 2; optional uint32 dest = 3; optional uint32 unknown1 = 4; optional uint32 unknown2 = 5; optional uint32 unknown3 = 6; optional uint32 unknown4 = 7; optional uint32 cmdFunc = 8; optional CmdFunction cmdId = 9; optional uint32 ValByte = 10; optional uint32 needAck = 11; uint64 timestamp = 14; optional uint32 unknown6 = 16; //3 Byte? optional uint32 unknown7 = 17; //3 Byte? optional string OS = 23; optional string serialNumber = 25; } message PowerMessage { PowerItem item = 1; } message Meta { optional int32 value = 1; optional int32 X_Unknown_3 = 3; optional int32 X_Unknown_4 = 4; optional int32 X_Unknown_5 = 5; optional int32 X_Unknown_6 = 6; optional int32 X_Unknown_7 = 7; optional int32 X_Unknown_8 = 8; optional int32 X_Unknown_9 = 9; optional int32 plugPower = 10; optional int32 X_Unknown_11 = 11; optional int32 X_Unknown_12 = 12; optional int32 X_Unknown_13 = 13; optional int32 M_Unknown7 = 14; // bei Prio-änderung gesehen optional int32 M_Unknown8 = 15; //bei Prio-änderung gesehen optional int32 M_Unknown10 = 16; optional int32 X_Unknown_17 = 17; optional int32 X_Unknown_18 = 18; optional int32 PV1_Power = 19; optional int32 X_Unknown_20 = 20; optional int32 M_Unknown1 = 21; optional int32 X_Unknown_22 = 22; optional int32 X_Unknown_23 = 23; optional int32 PV2_Power = 24; optional int32 X_Unknown_25 = 25; optional int32 X_Unknown_26 = 26; optional int32 X_Unknown_27 = 27; optional int32 X_Unknown_28 = 28; optional int32 From_Bat_Power = 29; optional int32 X_Unknown_30 = 30; optional int32 Batt_Poz = 31; optional int32 X_Unknown_32 = 32; optional int32 M_Unknown2 = 33; optional int32 X_Unknown_34 = 34; optional int32 M_Unknown3 = 35; optional int32 X_Unknown_36 = 36; optional int32 M_Unknown6 = 37; optional int32 ToHome_Power = 38; optional int32 X_Unknown_39 = 39; optional int32 X_Unknown_40 = 40; optional int32 X_Unknown_41 = 41; optional int32 X_Unknown_42 = 42; optional int32 M_Unknown9 = 43; //bei prio setzen gesehen optional int32 X_Unknown_44 = 44; optional int32 X_Unknown_45 = 45; optional int32 X_Unknown_46 = 46; optional int32 X_Unknown_47 = 47; optional int32 Needed_Power = 48; optional int32 X_Unknown_49 = 49; optional int32 X_Unknown_50 = 50; optional int32 X_Unknown_51 = 51; optional int32 X_Unknown_52 = 52; optional int32 X_Unknown_53 = 53; optional int32 X_Unknown_54 = 54; optional int32 X_Unknown_55 = 55; optional int32 X_Unknown_56 = 56; optional int32 X_Unknown_57 = 57; optional int32 X_Unknown_58 = 58; optional int32 M_Unknown4 = 59; optional int32 Bat_Minutes = 60; } enum CmdFunction { Unknown = 0; PermanentWattsPack = 129; SupplyPriorityPack = 130; } `; const writeables = [ { id: 38, name: 'Beep', ValueName: 'enabled' }, { id: 69, name: 'slowChgPower', ValueName: 'slowChgPower' }, { id: 66, name: 'ACPower', ValueName: 'enabled' }, { id: 81, name: 'DCPower', ValueName: 'enabled' }, { id: 34, name: 'USBPower', ValueName: 'enabled' }, { id: 51, name: 'minDsgSoc', ValueName: 'minDsgSoc' }, { id: 49, name: 'maxChgSoc', ValueName: 'maxChgSoc' }, { id: 71, name: 'curr12VMax', ValueName: 'currMa' }, { id: 33, name: 'standByModeMins', ValueName: 'standByMode' }, { id: 49, name: 'lcdTimeMins', ValueName: 'lcdTime' }, { id: 153, name: 'ACstandByMins', ValueName: 'standByMins' }, { id: 52, name: 'openOilSoc', ValueName: 'openOilSoc' }, { id: 53, name: 'closeOilSoc', ValueName: 'closeOilSoc' } ]; const musterGetPS = { "header": { "src": 32, "dest": 32, "seq": 1651831507, "OS": "ios" } } const musterSetAC = { header: { pdata: { value: 1300, }, src: 32, dest: 53, dSrc: 1, dDest: 1, checkType: 3, cmdFunc: 20, cmdId: 129, dataLen: 3, needAck: 1, seq: 1651831507, version: 19, payloadVer: 1, from: 'ios', deviceSn: 'ABCxxxxxxx123' } }; const musterSetAC2 = { header: { pdata: { value: 17477, }, src: 32, dest: 53, dSrc: 1, dDest: 1, checkType: 3, cmdFunc: 32, cmdId: 11, dataLen: 4, needAck: 1, seq: 1651831507, version: 19, payloadVer: 1, from: 'ios', deviceSn: 'ABCxxxxxxx123' } }; const musterslowChgPower = { "from": "iOS", "operateType": "TCP", "id": "816376009", "lang": "de-de", "params": { "id": 69, }, "version": "1.0" }; // @ts-ignore const mqtt = require('mqtt'); const https = require('https'); // @ts-ignore const protobuf = require("protobufjs"); const mqttDaten = { UserID: '', User: '', Passwort: '', URL: '', Port: '', protocol: '', clientID: '' } //Die erste PowerStream ermitteln let firstPsSn = ""; for (var i = 0; i < ConfigData.seriennummern.length; i++) { if (ConfigData.seriennummern[i].isPowerStream) { firstPsSn = ConfigData.seriennummern[i].seriennummer; break; } } /*======================================================= ========= Timer ============ =======================================================*/ //jede x Sekunden schedule('*/30 * * * * *', function () { //log("-timer CheckforReconnect") // Nur am Tag ständig prüfen... (Macht das Sinn? Vorerst deaktiviert) if (true || istTag()) { CheckforReconnect(function () { SetBasePower(firstPsSn); }); } else { ////SetBasePower(firstPsSn); } }); // @ts-ignore await getEcoFlowMqttData(ConfigData.email, ConfigData.passwort) async function getEcoFlowMqttData(email, password) { const options = { hostname: 'api.ecoflow.com', path: '/auth/login', method: 'POST', headers: { 'Host': 'api.ecoflow.com', 'lang': 'de-de', 'platform': 'android', 'sysversion': '11', 'version': '4.1.2.02', 'phonemodel': 'SM-X200', 'content-type': 'application/json', 'user-agent': 'okhttp/3.14.9' } }; const data = { appVersion: "4.1.2.02", email: email, os: "android", osVersion: "30", password: Buffer.from(password).toString('base64'), scene: "IOT_APP", userType: "ECOFLOW" }; function httpsRequest(options, data) { return new Promise((resolve, reject) => { const req = https.request(options, res => { let data = ''; res.on('data', chunk => { data += chunk; }); res.on('end', () => { resolve(data); }); }); req.on('error', error => { reject(error); }); if (data) { req.write(JSON.stringify(data)); } req.end(); }); } function uuidv4() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { var r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); } let response = await httpsRequest(options, data); try { let token = JSON.parse(response).data.token; let userid = JSON.parse(response).data.user.userId; } catch (error) { log(response) throw new Error("Ein Fehler bei der Ermittlung der Zugangsdaten ist aufgetreten. Bitte prüfe die Zugangsdaten."); } let token = JSON.parse(response).data.token; let userid = JSON.parse(response).data.user.userId; options.path = `/iot-auth/app/certification?userId=${userid}`; options.method = 'GET'; options.headers.authorization = `Bearer ${token}`; response = await httpsRequest(options); try { mqttDaten.Passwort = JSON.parse(response).data.certificatePassword mqttDaten.Port = JSON.parse(response).data.port mqttDaten.UserID = userid mqttDaten.User = JSON.parse(response).data.certificateAccount mqttDaten.URL = JSON.parse(response).data.url mqttDaten.protocol = JSON.parse(response).data.protocol mqttDaten.clientID = "ANDROID_" + uuidv4() + "_" + userid } catch (error) { log(response) throw new Error("Ein Fehler bei der Ermittlung der Zugangsdaten ist aufgetreten. Bitte prüfe die Zugangsdaten."); } /* console.log("UserID: " + userid); console.log("User: " + JSON.parse(response).data.certificateAccount); console.log("Passwort: " + JSON.parse(response).data.certificatePassword); console.log("URL: " + JSON.parse(response).data.url); console.log("Port: " + JSON.parse(response).data.port); console.log("protocol: " + JSON.parse(response).data.protocol); console.log("clientID: ANDROID_" + uuidv4() + "_" + userid); */ } // @ts-ignore await createMyState("LastTopic") //################ MQTT Verbindung ################## function setupMQTTConnection() { //log("Neue MQTT Verbindung startet") // Verbindung herstellen const options = { port: mqttDaten.Port, clientId: mqttDaten.clientID, username: mqttDaten.User, password: mqttDaten.Passwort, protocol: mqttDaten.protocol }; const client = mqtt.connect("mqtt://" + mqttDaten.URL, options); // Event-Handler für Verbindungsaufbau client.on('connect', function () { console.log('Verbunden mit dem Ecoflow MQTT-Broker'); SubscribeEco(); for (var i = 0; i < ConfigData.seriennummern.length; i++) { if (ConfigData.seriennummern[i].isPowerStream) { setmusterGetPS(ConfigData.seriennummern[i].seriennummer); } } }); function SubscribeEco() { ConfigData.seriennummern.forEach(item => { client.subscribe('/app/' + mqttDaten.UserID + '/' + item.seriennummer + '/thing/property/set'); client.subscribe('/app/' + mqttDaten.UserID + '/' + item.seriennummer + '/thing/property/get'); if (item.subscribe) { client.subscribe('/app/device/property/' + item.seriennummer); } }); } // Auf Nachricht empfangen Ereignis reagieren client.on('message', async function (topic, message) { //log("Incomming Massage: " + topic) //log("Incomming Massage: " + message.length) if (message.length == 0) return //return var jsonMessage = "" const mqState = topic.replace(/^\//, '').replace(/\//g, '_') await createMyState(mqState + ".RAW") setState(ConfigData.statesPrefix + ".LastTopic", topic) try { jsonMessage = JSON.parse(message); if (!pruefeID(jsonMessage, mqState)) { return } if (false || ConfigData.Debug) log('JSON-Nachricht empfangen:' + topic + ':' + JSON.stringify(jsonMessage)); setState(ConfigData.statesPrefix + '.' + mqState + ".RAW", JSON.stringify(jsonMessage)) generateAndSyncSub("data", jsonMessage, false, ConfigData.statesPrefix + '.' + mqState) } catch (error) { //if (topic.indexOf("/set") !== -1) log('Binäre Nachricht empfangen:' + topic + ':' + message.toString('hex')); if (ConfigData.Debug) log('Binäre Nachricht empfangen:' + topic + ':' + message.toString('hex')); await createMyState(mqState + ".RAW_HEX") setState(ConfigData.statesPrefix + '.' + mqState + ".RAW_HEX", message.toString('hex')) if (ConfigData.Debug) log('Decodierte Nachricht:' + decodeAndPrint(message.toString('hex'))) const messagedecoded = decodeAndPrint(message.toString('hex')) setState(ConfigData.statesPrefix + '.' + mqState + ".RAW", messagedecoded) generateAndSyncSub("data", JSON.parse(messagedecoded), false, ConfigData.statesPrefix + '.' + mqState) } }); // Callback für Fehler client.on('error', function (error) { log('Fehler bei der Ecoflow MQTT-Verbindung:' + error, 'warn'); }); client.on('reconnect', function () { console.log('Reconnecting to Ecoflow MQTT broker...'); }); // Weitere Event-Handler hier... return client; } function findWriteableByID(id) { const foundItem = writeables.find((item) => item.id === id); return foundItem || null; } function pruefeID(json, mqState) { if ('params' in json && 'id' in json.params) { const Ignores = [40, 72, 68]; const writeables = [69]; if (Ignores.includes(json.params.id)) { //log("Ignore: "+ JSON.stringify(json)) return false; } else if (mqState.includes("thing_property_set")) { const suchwritable = findWriteableByID(json.params.id) if (suchwritable) { //log("Schreibbar: " + JSON.stringify(json)) //log("Schreibbardaten: " + JSON.stringify(suchwritable)) //log("wert: " + JSON.stringify(json.params[suchwritable.ValueName])) createMyState(mqState + ".writeables." + suchwritable.name + "", json.params[suchwritable.ValueName].toString()) setState(ConfigData.statesPrefix + "." + mqState + ".writeables." + suchwritable.name, json.params[suchwritable.ValueName].toString(), true) } else { log("Unbekannter Set Befehl: " + JSON.stringify(json)) log("Adresse: " + mqState) } return true; } } else { return true; } } // Verbindung herstellen let client = setupMQTTConnection(); // Funktion zum Trennen und Neuaufbau der Verbindung function reconnect() { client.end(); // Verbindung trennen setTimeout(function () { client = setupMQTTConnection(); // Neue Verbindung herstellen //log("Ecoflow neuverbindung"); }, 2000); // Wartezeit } // close connection if script stopped onStop(function (callback) { if (client) { // close connection client.end(); log("Ecoflow MQTT-Client beendet") } callback(); }, 2000); function CheckforReconnect(callback) { //log("CheckforReconnect") //return let wartezeit = 15 //bis eine Stunde nach Sonneuntergang kurze Reconnects dann 15 min. if (istTag(60)) wartezeit = 1 if (getState(ConfigData.statesPrefix + ".LastTopic")?.ts < Date.now() - ConfigData.ReconnectMin * 60 * 1000) { console.log("Der letzte Eintrag ist älter als " + ConfigData.ReconnectMin + " Minuten. Versuche Neustart."); setState(ConfigData.statesPrefix + ".LastTopic", "Last Action Restart:" + new Date().toLocaleString()) runScript(); return; // Wenn letzte Powerstream-Meldung älter als <wartezeit> min ist, reconnecte } else if (getState(ConfigData.statesPrefix + '.app_device_property_' + firstPsSn + '.RAW_HEX')?.ts < Date.now() - (wartezeit * 60 * 1000)) { log("Reconnect zu Ecoflow MQTT für PowerStream - Daten") //.ts Updaten const oldvalue = getState(ConfigData.statesPrefix + '.app_device_property_' + firstPsSn + '.RAW_HEX').val setState(ConfigData.statesPrefix + '.app_device_property_' + firstPsSn + '.RAW_HEX', oldvalue) reconnect(); return; //runScript(); } else { callback(); } } async function createMyState(name, value = undefined) { const stateName = ConfigData.statesPrefix + '.' + name; if (!(await existsObjectAsync(stateName))) { const state = { name: name.split('.').pop(), role: 'state', type: 'string', // 'number', 'boolean', usw. read: true, write: true, }; // @ts-ignore await createStateAsync(stateName, value, false, state); // Wenn der optionale Parameter value übergeben wurde, schreibe den Wert in den State } } async function createMyStatealt(name) { if (!(await existsObjectAsync(ConfigData.statesPrefix + '.' + name))) { await createStateAsync(ConfigData.statesPrefix + '.' + name, { name: name.split('.').pop(), role: 'state', type: 'string', read: true, write: true }); } } function decodeAndPrint(hexString) { const root = protobuf.parse(protoSource2).root; const PowerMessage = root.lookupType("Message"); const message = PowerMessage.decode(Buffer.from(hexString, "hex")); const object = PowerMessage.toObject(message, { defaults: false }); //log(message.header.cmdId) //log("Hex:" + hexString) //log(JSON.stringify(object)) const MessageType = messageIDTypes[message.header.cmdId] //log (MessageType) if (message.header.cmdId == 32) { //log(hexString) //log(JSON.stringify(object)) return "{}" } if (message.header.cmdId == 999) { log("--------------------------------------------") log("cmdId: " + message.header.cmdId) log(hexString) log(JSON.stringify(object)) log("--------------------------------------------") } if (!MessageType) { if (message.header.cmdId != 0) { console.warn('Ungültiger cmd_func-Wert:' + message.header.cmdId); log(hexString) } return "{}"; } else { const PdataMessage = root.lookupType(MessageType); const pdata = PdataMessage.decode(message.header.pdata); const pdataObject = PdataMessage.toObject(pdata, { longs: Number, // Konvertiere Long-Werte in Zahlen (optional) enums: String, // Konvertiere Enum-Werte in Strings (optional) bytes: Buffer, // Konvertiere Bytes in Buffer (optional) }); const outputObject = { [MessageType]: pdataObject }; //log(JSON.stringify(outputObject)) return JSON.stringify(outputObject); } // return "" } function SendProto(protomsg, topic) { //return const root = protobuf.parse(protoSource2).root; const PowerMessage = root.lookupType("setMessage"); const message = PowerMessage.create(JSON.parse(protomsg)); const messageBuffer = PowerMessage.encode(message).finish(); //log("Modifizierter Hex-String:" + Buffer.from(messageBuffer).toString("hex")); //log("topic:" + topic); client.publish(topic, messageBuffer, { qos: 1 }, function (error) { if (error) { console.error('Fehler beim Veröffentlichen der MQTT-Nachricht:', error); } else { if (ConfigData.Debug) log('Die MQTT-Nachricht wurde erfolgreich veröffentlicht.'); } }); } function SendJSON(protomsg, topic) { client.publish(topic, protomsg, { qos: 1 }, function (error) { if (error) { console.error('Fehler beim Veröffentlichen der MQTT-Nachricht:', error); } else { if (ConfigData.Debug) log('Die MQTT-Nachricht wurde erfolgreich veröffentlicht.'); } }); } function getStandortKoordinaten() { var obj = getObject('system.config'); if (obj) { latitude = obj.common.latitude; longitude = obj.common.longitude; } else { console.error('Fehler beim Abrufen der Einstellungen'); } } //Anmeldenachrichten der APP function setmusterGetPS(asn) { let updatedMusterSetAC = (musterGetPS); updatedMusterSetAC.header.seq = Date.now() //log(JSON.stringify(updatedMusterSetAC)); SendProto(JSON.stringify(updatedMusterSetAC), '/app/' + mqttDaten.UserID + '/' + asn + '/thing/property/get'); SendProto(JSON.stringify(updatedMusterSetAC), '/app/' + mqttDaten.UserID + '/' + asn + '/thing/property/get'); SendProto(JSON.stringify(updatedMusterSetAC), '/app/' + mqttDaten.UserID + '/' + asn + '/thing/property/get'); // @ts-ignore updatedMusterSetAC = (musterSetAC2); updatedMusterSetAC.header.seq = Date.now() updatedMusterSetAC.header.deviceSn = asn //log(JSON.stringify(updatedMusterSetAC)); SendProto(JSON.stringify(updatedMusterSetAC), '/app/' + mqttDaten.UserID + '/' + asn + '/thing/property/set'); } function generateAndSyncSub(id, JElements, sub = false, preset = "0_userdata.0") { if (!JElements || typeof JElements !== 'object') { log('Ungültige JElements übergeben!'); return; } for (var JElement in JElements) { var AktVal; if (typeof JElements[JElement] === "object") { generateAndSyncSub(id + "." + JElement, JElements[JElement], true, preset); } else { try { if (isState2(preset + "." + id + "." + JElement)) AktVal = getState(preset + "." + id + "." + JElement).val; else AktVal = null } catch (e) { log("Fehler: " + e); } if (AktVal == null) { createState(preset + "." + id + "." + JElement, JElements[JElement], false); AktVal = JElements[JElement]; } if (AktVal != JElements[JElement]) { if (isState2(preset + "." + id + "." + JElement)) { setState(preset + "." + id + "." + JElement, JElements[JElement]); } } } } } /* Checks if a a given state or part of state is existing. * This is a workaround, as getObject() or getState() throw warnings in the log. * Set strict to true if the state shall match exactly. If it is false, it will add a wildcard * to the end. * See: https://forum.iobroker.net/topic/11354/ * @param {string} strStatePath Input string of state, like 'javascript.0.switches.Osram.Bedroom' * @param {boolean} [strict=false] Optional: if true, it will work strict, if false, it will add a wildcard * to the end of the string * @return {boolean} true if state exists, false if not */ function isState2(strStatePath, strict = true) { let mSelector; if (strict) { mSelector = $(strStatePath); } else { mSelector = $(strStatePath + "*"); } if (mSelector.length > 0) { return true; } else { return false; } } //--------- Prüft übergebne zeiträume und Tage und gibt True zurück wenn innerhalb //log("return: " + CheckTime2("22:00","11:00",[0,1,2,3,4,5,6],getDateObject("06 Nov 2018 08:30:00 GMT+0100"))); function CheckTime2(Startzeit, Endzeit, Wochentage, d = new Date()) { var locStartDate = getDateObject(formatDate(d, "MM DD YYYY " + Startzeit)); var locEndDate = getDateObject(formatDate(d, "MM DD YYYY " + Endzeit)); var LocOriginal = getDateObject(formatDate(d, "MM DD YYYY hh:mm:ss")); Wochentage = Wochentage.map(function (x) { return parseInt(x, 10); }); if (locStartDate.getTime() > locEndDate.getTime()) { if (LocOriginal.getTime() >= locStartDate.getTime() && LocOriginal.getTime() <= getDateObject(formatDate(d, "MM DD YYYY 23:59:59")).getTime()) { d.setDate(d.getDate() + 1); locEndDate = getDateObject(formatDate(d, "MM DD YYYY " + Endzeit)); } else { d.setDate(d.getDate() - 1); locStartDate = getDateObject(formatDate(d, "MM DD YYYY " + Startzeit)); } } var n = getDateObject(locStartDate).getDay(); if (Wochentage.includes(n) && LocOriginal.getTime() >= getDateObject(locStartDate).getTime() && LocOriginal.getTime() <= getDateObject(locEndDate).getTime()) { return true } else { return false } } function SunTimes(time = 0) { // @ts-ignore const SunCalc = require('suncalc'); const date = new Date(); // Berechnung von Sonnenaufgang und Sonnenuntergang const sunTimes = SunCalc.getTimes(date, ConfigData.latitude, ConfigData.longitude); const sunrise = sunTimes.sunrise.getHours() + ':' + sunTimes.sunrise.getMinutes(); const sunset = sunTimes.sunset.getHours() + ':' + sunTimes.sunset.getMinutes(); if (time == 0) { return sunrise } else { return sunset } } function istTag(offsetMin = 0) { //log("Ist Tag?: " + CheckTime2(SunTimes(0).toString(), addMinutesToTime(SunTimes(1).toString(),offsetMin), [0, 1, 2, 3, 4, 5, 6], new Date())); return CheckTime2(SunTimes(0).toString(), addMinutesToTime(SunTimes(1).toString(), offsetMin), [0, 1, 2, 3, 4, 5, 6]) } function addMinutesToTime(time, minutesToAdd) { var parts = time.split(":"); var hours = parseInt(parts[0]); var minutes = parseInt(parts[1]); var totalMinutes = hours * 60 + minutes + minutesToAdd; var newHours = Math.floor(totalMinutes / 60) % 24; var newMinutes = totalMinutes % 60; var newTime = newHours.toString().padStart(2, "0") + ":" + newMinutes.toString().padStart(2, "0"); return newTime; } //############ Funktionen zum Setzen von Werten for (var i = 0; i < ConfigData.seriennummern.length; i++) { if (ConfigData.seriennummern[i].isPowerStream) { const asn = ConfigData.seriennummern[i].seriennummer //log(asn) // @ts-ignore await createMyState('app_' + mqttDaten.UserID + '_' + asn + '_thing_property_set.setAC') on({ id: ConfigData.statesPrefix + '.app_' + mqttDaten.UserID + '_' + asn + '_thing_property_set.setAC', change: "any", ack: false }, function (obj) { setAC(asn, Number(obj.state.val)) setState(obj.id, obj.state.val, true); }); //Powersumme bilden und schreiben data.InverterHeartbeat.pv1InputWatts on({ id: new RegExp(ConfigData.statesPrefix + '.app_device_property_' + asn + '.data.InverterHeartbeat.pv.InputWatts'), change: "any" }, function (obj) { let state1 = ConfigData.statesPrefix + '.app_device_property_' + asn + '.data.InverterHeartbeat.pv1InputWatts'; let state2 = ConfigData.statesPrefix + '.app_device_property_' + asn + '.data.InverterHeartbeat.pv2InputWatts'; //let korstate = ConfigData.statesPrefix + '.app_device_property_' + asn + '.data.InverterHeartbeat.X_Unknown_5'; let sumState = ConfigData.statesPrefix + '.app_device_property_' + asn + '.data.InverterHeartbeat.sumPV'; if (existsState(state1) || existsState(state2)) { let pv1InputWatts = 0, pv2InputWatts = 0 if (existsState(state1)) pv1InputWatts = GetValAkt(state1, 30).val if (existsState(state2)) pv2InputWatts = GetValAkt(state2, 30).val //let sum = GetValAkt(state1, 30).val + GetValAkt(state2, 30).val - (getState(korstate).val * 20); let sum = (pv1InputWatts + pv2InputWatts) * (0.93); if (!existsState(sumState)) { createState(sumState, sum); } else { setState(sumState, sum); //log("Summe gesetzt für "+asn+": "+ sum) } } }); } } const idRegex = new RegExp(ConfigData.statesPrefix + '\.app_[A-Za-z0-9_]+_thing_property_set\\.writeables\\..*'); on({ id: idRegex, change: "any", ack: false }, function (obj) { const idParts = obj.id.split('.'); const lastPart = idParts[idParts.length - 1]; const matchedEntry = writeables.find((entry) => entry.name === lastPart); if (matchedEntry) { //log("Write Event: " + obj.id + " val: " + obj.state.val + " | Matched Entry: " + JSON.stringify(matchedEntry)); let updatedMuster = musterslowChgPower; updatedMuster.id = Date.now().toString() updatedMuster.params.id = matchedEntry.id updatedMuster.params[matchedEntry.ValueName] = Number(obj.state.val) const asn = obj.id.match(/.*?\.app_.*?_(.*?)_thing_property_set.*/)[1]; //log("Topic: " + '/app/' + mqttDaten.UserID + '/' + asn + '/thing/property/set' + " Daten:" + JSON.stringify(updatedMuster)) SendJSON(JSON.stringify(updatedMuster), '/app/' + mqttDaten.UserID + '/' + asn + '/thing/property/set'); delete updatedMuster.params[matchedEntry.ValueName] } else { log("Write Event: " + obj.id + " val: " + obj.state.val + " | No matching entry found."); } setState(obj.id, obj.state.val, true); }); //State für die gesamte PV-Leistung 'totalPV' erstellen und beschreiben on({ id: new RegExp(ConfigData.statesPrefix + '\.app_device_property_[A-Za-z0-9]{13,17}\.data\.InverterHeartbeat\.sumPV'), change: "any" }, function (obj) { //log("sumpv Evemnt:" + obj.id + " val: " + obj.state.val) let totalPV = 0 for (var i = 0; i < ConfigData.seriennummern.length; i++) { if (ConfigData.seriennummern[i].isPowerStream) { const asn = ConfigData.seriennummern[i].seriennummer if (isState2(ConfigData.statesPrefix + '.app_device_property_' + asn + '.data.InverterHeartbeat.sumPV')) { totalPV = totalPV + GetValAkt(ConfigData.statesPrefix + '.app_device_property_' + asn + '.data.InverterHeartbeat.sumPV', 20).val } } } let totalPVState = ConfigData.statesPrefix + '.totalPV'; if (!existsState(totalPVState)) { createState(totalPVState, totalPV); } else { setState(totalPVState, totalPV); //log("Summe gesetzt für "+asn+": "+ sum) } }) // Einstellen der Einspeiseleistung function setAC(asn, Value) { let updatedMusterSetAC = musterSetAC; if (Value <= -1) { delete updatedMusterSetAC.item.meta; delete updatedMusterSetAC.item.ValByte; } else { updatedMusterSetAC.header.pdata.value = Value updatedMusterSetAC.header.dataLen = getVarintByteSize(Value) } updatedMusterSetAC.header.seq = Date.now() updatedMusterSetAC.header.deviceSn = asn //log(JSON.stringify(updatedMusterSetAC)) SendProto(JSON.stringify(updatedMusterSetAC), '/app/' + mqttDaten.UserID + '/' + asn + '/thing/property/set'); } // ########### Grundbedarf/Einspeiseleistung steuern // Den niedrigeste Wert vom Gesamtverbrauch der letzten x Minuten ermitteln function getLowestValue(id, minuten = 120) { //log("getLowestValue-Aufruf für:" + id) const now = Date.now(); const range = minuten * 60 * 1000; return new Promise((resolve, reject) => { sendTo('history.0', 'getHistory', { id: id, options: { start: now - range, end: now, aggregate: 'none', ignoreNull: true } }, (result) => { if (result.error) { log("getLowestValue-fehler: " + result.error) reject(result.error); } else if (result.result && result.result.length > 0) { let lowestValue = result.result[0].val; for (let i = 1; i < result.result.length; i++) { if (result.result[i].val < lowestValue) { lowestValue = result.result[i].val; } //log(result.result[i].val ) } resolve(Math.floor(Number(lowestValue))); } else { reject(new Error('No data')); } let Dauer = ((Date.now() - now) / 1000) if (Dauer > 1) log("getLowestValue-Dauer: " + ((Date.now() - now) / 1000) + "s") }); }); } //Einspeiseleistung berechnen und bei Änderung setzen var OldNewValue = 0 var FullPower = false function SetBasePower(asn) { //log("SetBasePower") //return if (isState2(ConfigData.SmartmeterID)) { const batstate = ConfigData.statesPrefix + ".app_device_property_" + asn + ".data.InverterHeartbeat.batSoc" if (Number(GetValAkt(batstate, 60).val) >= ConfigData.battPozOn && !FullPower) { FullPower = true setAC(asn, (Math.floor(ConfigData.MaxPower) * 10)) if (true || ConfigData.Debug) log("Batterie ist bei " + ConfigData.battPozOn + "%: Einspeisung auf Maximum.") return } else if ((Number(GetValAkt(batstate, 60).val) > ConfigData.battPozOff || (Number(GetValAkt(batstate, 60).val) == 0)) && FullPower) { return } else if (FullPower) { FullPower = false if (true || ConfigData.Debug) log("Batterie runter auf " + ConfigData.battPozOff + "%: Normalbetieb.") } getLowestValue(ConfigData.statesPrefix + ".RealPower", ConfigData.MinValueMin) .then(lowestValue => { //log("SetBasePower lowestValue " + lowestValue) if (lowestValue != 0) { var AndereVerbraucher = 0 const ToHomeId = ConfigData.statesPrefix + ".app_device_property_" + asn + ".data.InverterHeartbeat.invOutputWatts" if (isState2(ToHomeId)) { AndereVerbraucher = Number(GetValAkt(ToHomeId).val) / 10 var NewValue = (lowestValue - ConfigData.BasePowerOffset) //Einspeisung der andernen Powerstream feststellen und abziehen: let otherPS = 0 for (var i = 0; i < ConfigData.seriennummern.length; i++) { if (ConfigData.seriennummern[i].isPowerStream && ConfigData.seriennummern[i].seriennummer != asn) { const asn = ConfigData.seriennummern[i].seriennummer otherPS = otherPS + Number(GetValAkt(ConfigData.statesPrefix + '.app_device_property_' + asn + '.data.InverterHeartbeat.invOutputWatts').val) //log("otherPS: " + otherPS / 10) } } NewValue = Number((NewValue - (otherPS / 10)).toFixed(0)) if (NewValue > ConfigData.MaxPower) NewValue = ConfigData.MaxPower //log("Newval:"+ NewValue) if (NewValue < 0) NewValue = 0 //log("LowVal in " + ConfigData.MinValueMin + " Minuten: " + lowestValue + " W, Andere: " + (otherPS / 10) + " W, Offset " + ConfigData.BasePowerOffset + "W, neu: " + NewValue + " W"); if (false || ConfigData.Debug) { log("Tiefster Wert der letzten " + ConfigData.MinValueMin + " Minuten: " + lowestValue + " W"); log("Summe der Anderen PS: " + (otherPS / 10) + " W"); log("Rest ist: " + Math.floor((lowestValue) - (otherPS / 10)) + " W"); log("Offset von: " + ConfigData.BasePowerOffset + " W abziehen = " + (Math.floor((lowestValue) - (otherPS / 10)) - ConfigData.BasePowerOffset) + " W Neuer Einspeisewert") log("Neuer Wert unter Berücksichtigung der Limits: " + NewValue + " W") log("Einspeisung aktuell: " + AndereVerbraucher + " W") log("===================================================") } //setAC(asn,10) if (OldNewValue != NewValue) { setAC(asn, (Math.floor(NewValue) * 10)) if (false || ConfigData.Debug) log("Änderung für Einspeisung gesendet" + Math.floor(NewValue) * 10) } OldNewValue = NewValue } } }) .catch(error => { log("Fehler beim Abrufen des niedrigsten Werts: " + error); }); } } function GetValAkt(id, minuten = 15, reset = true) { const state = getState(id) if (state.ts > Date.now() - minuten * 60 * 1000) { return state } else { if (reset || state.val != 0) setState(id, 0) state.val = 0 return state } } // State RealPower anlegen wenn noch nicht vorhanden und History aktivieren if (isState2(ConfigData.SmartmeterID)) { if (!isState2(ConfigData.statesPrefix + ".RealPower")) { const stateObject = { name: "RealPower", role: "state", type: "number", read: true, write: true, custom: { "history.0": { enabled: true, aliasId: "", debounceTime: 0, blockTime: 0, changesOnly: true, changesRelogInterval: 0, changesMinDelta: 0, ignoreBelowNumber: "", disableSkippedValueLogging: true, retention: 86400, customRetentionDuration: 365, maxLength: 960, enableDebugLogs: false, debounce: 1000 } } }; createState(ConfigData.statesPrefix + ".RealPower", stateObject, function () { //* const stateId = ConfigData.statesPrefix + ".RealPower"; // Hier den ID des States angeben // Aktiviere die History-Funktion für den State const historyOptions = { id: stateId, options: { enabled: true // Setze den Wert auf true, um die History zu aktivieren } }; sendTo("history.0", "enableHistory", historyOptions, (result) => { if (result.error) { log("Fehler beim Aktivieren der History für " + stateId + ": " + result.error); } else { log("History für " + stateId + " erfolgreich aktiviert"); } }); // }); } //Wert für den realen Verbrauch. Wird alle 5 Sekunden gesetzt, wenn sich die Werte vom Smartmeter ändern let WorkInProz = false on({ id: ConfigData.SmartmeterID, change: "any" }, function (obj) { //log("SmartmeterID ereignis!") if (!WorkInProz) { WorkInProz = true setTimeout(function () { const Hausstrom = Number(getState(ConfigData.SmartmeterID).val); let Einspeisung = 0 for (var i = 0; i < ConfigData.seriennummern.length; i++) { if (ConfigData.seriennummern[i].isPowerStream) { const asn = ConfigData.seriennummern[i].seriennummer //Nur Werte berücksichtigen, die nicht älter als 60 Minuten sind if (getState(ConfigData.statesPrefix + '.app_device_property_' + asn + '.data.InverterHeartbeat.invOutputWatts')?.ts > Date.now() - 60 * 60 * 1000) { Einspeisung = Einspeisung + (Number(getState(ConfigData.statesPrefix + ".app_device_property_" + asn + ".data.InverterHeartbeat.invOutputWatts").val.toFixed(0)) / 10); //log("Einspeisung: " + Einspeisung) } } } setState(ConfigData.statesPrefix + ".RealPower", Number((Hausstrom + Einspeisung).toFixed(0))); //log("Neu gesetzte Realpower: " + Number((Hausstrom + Einspeisung).toFixed(0))); WorkInProz = false; }, 5000); } }); } function getVarintByteSize(number) { let byteSize = 0; while (number >= 128) { byteSize++; number >>= 7; // Rechtsschiebeoperation um 7 Bits } byteSize++; byteSize++; return byteSize; }
-
@waly_de
Ich habe das script soeben auf den neuesten Stand gebracht.Für die Ecoflow Delta Pro funktioniert das einstellen tadellos. Ich stelle es in hunderter Schritten (Wie in der APP ein, da es möglich sein könnte das hier Intern auf andere Wiederstände etc. umgeschaltet wird) über das Blockly ein.
Läuft bei mir aktuell stabil => Werde morgen mehr berichten.Bei der Ecoflow Delta 2 ist der writeable Wert = "CfgChgWatts".
Super Arbeit!
-
@walle-0 prima
Stell bitte bei laufendem Script mal den Wert vom Delta ein und sieh nach, ob im Log Sowas wie "Unbekannter Set Befehl: xxxxxx " steht.... wenn ja schick mir mal den ganzen Eintrag bitte.
-
@waly_de
Leider kommt hier keine Meldung.
Er schreibt mir zwar den Wert in params im IOBroker um, aber nicht in der APPEs erscheinen für die Delta 2 im selben script keine "writeables".
Kann es sein das ich hier eine 2. Javascript instanz brauche? -
@walle-0 nein, ich vermute das das Datenformat zum setzen anders aussieht...
Ich bauche die RAW-Daten um das zu sehen... und zwar das, was nach dem Umstellen in der App in:
0_userdata.0.ecoflow.app_xxxxxxxxxxxxx_xxxxxxxxxxxxxx_thing_property_set.RAW geschrieben wird. Wenn da viel reingeschrieben wird, schick ich dir mal eine Anpassung für das Script, das alles logt, was da rein kommt. -
@walle-0 Kannst du auch selber machen... Suche die Zeile:
if (false || ConfigData.Debug) log('JSON-Nachricht empfangen:' + topic + ':' + JSON.stringify(jsonMessage));
und mach aus dem false ein true :
if (true || ConfigData.Debug) log('JSON-Nachricht empfangen:' + topic + ':' + JSON.stringify(jsonMessage));
dann sollte alles ins Log geschrieben werden.
-
@waly_de
Danke erstmalHier die RAW Daten:
{"from":"Android","id":"588625445","moduleType":2,"operateType":"setRtcTime","params":{"sec":58,"min":0,"week":3,"month":7,"hour":18,"year":2023,"day":26},"version":"1.0"}Das werde ich gleich mal umstellen.
-
@walle-0 Ja das sieht anders aus Das ist setRtcTime gewesen?
Also ich brauch dann den Eintrag der kommt, wenn du CfgChgWatts machst ... -
Untenstehend ist der Log:
script.js.Ecoflow.EcoFlow_MQTT_Laden: JSON-Nachricht empfangen:/app/1668913565281660930/XXXXXXXXXXXXXXXX/thing/property/set:{"from":"Android","id":"458115693","moduleType":5,"operateType":"acChgCfg","params":{"chgWatts":900,"chgPauseFlag":255},"version":"1.0"}Das ist der Log wenn ich bei der Delta 2 den Wert ändere.
kannst du hier was rauslesen?
-
@walle-0 Ja, das sieht gut aus... kannst du bitte noch ein paar andere Werte ändern und mir die entsprechenden RAWs Senden?
und kannst du damit irgendwas anfangen? chgPauseFlag:255 ... was ist eine chgPause? und kann man diesen wert irgendwo einstellen oder sehen ?
-
@waly_de: ich hätte ebenfalls Interesse: Habe eine Delta 2 Max und eine Powerstream. Möchte dynamisch über AC die Delta 2 max laden ( PV Überschuss mit Smartmeter) und via Powerstream dynamisch zurückspeisen. Wäre toll wenn es hierzu ein fertiges skript geben würde.
Danke schon mal für eure tolle Arbeit. das erste Skript (Powerstream dynamisch einspeisen) hat bei mir mit Anpassungen funktioniert
-
Ja kann ich. Meinst du den Wert zum laden?
script.js.Ecoflow.EcoFlow_MQTT_Laden: JSON-Nachricht empfangen:/app/1668913565281660930/XXXXXXXXXXXXXXXX/thing/property/set:{"from":"Android","id":"597271039","moduleType":5,"operateType":"acChgCfg","params":{"chgWatts":800,"chgPauseFlag":255},"version":"1.0"}
script.js.Ecoflow.EcoFlow_MQTT_Laden: JSON-Nachricht empfangen:/app/1668913565281660930/XXXXXXXXXXXXXXXX/thing/property/set:{"from":"Android","id":"123801081","moduleType":5,"operateType":"acChgCfg","params":{"chgWatts":600,"chgPauseFlag":255},"version":"1.0"}
Mit dem Wert chgPauseFlag:255 kann ich nichts anfangen, der Wert bleibt bei mir immer auf 255
Das was ich noch sehe ist das bei der Delta 2 keine writeables erstellt werden.
-
@walle-0 nein, ich meinte andere Parameter... AC Ein/Aus, USB Ein/Aus oder auch Ladelimits ... irgend was anderes eben, damit ich sehen kann wie sich die Nachrichten unterscheiden