├── .env_sample ├── .gitignore ├── 1692266227129-ecoflow-connector_v112.js ├── README.md ├── const.js ├── ecoflow-connector_v115.txt ├── ecoflow.js ├── index.html ├── package-lock.json ├── package.json ├── server.js ├── utils.js └── yarn.lock /.env_sample: -------------------------------------------------------------------------------- 1 | NODE_ENV=development 2 | KEY_MAIL=ecoflow_mail 3 | KEY_URL=url_to_call 4 | KEY_PASSWORD=ecoflow_password 5 | KEY_POWERSTREAM_SN=powerstream_serial_number 6 | KEY_POWERSTREAM_SN2=powerstream_serial_number_2 7 | KEY_QUERY_AC=100 8 | KEY_QUERY_PRIO=0 or 1 9 | TOKEN=TOKEN_KEY 10 | TOKEN_VAL=TOKEN_VALUE 11 | TARGET=2 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | node_modules -------------------------------------------------------------------------------- /1692266227129-ecoflow-connector_v112.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ecoflow-connector.js 3 | * Version: 1.1.2 4 | * Release date: 17.08.2023 5 | * Autor: Waly_de 6 | * Forum: https://forum.iobroker.net/topic/66743/ecoflow-connector-script-zur-dynamischen-leistungsanpassung 7 | * 8 | * This JavaScript file establishes a simple connection between IOBroker and EcoFlow. 9 | * It automatically creates known states under 0_userdata. 10 | * 11 | * Please note that adjustments in the ConfigData section are required. Here, you need to enter your access credentials 12 | * used for the EcoFlow app, as well as the serial numbers of your devices. 13 | * 14 | * If you have a state that displays the current power consumption (SmartmeterID), please provide it as well. 15 | * This value will be used to dynamically adjust the Powerstream's feed-in power. 16 | * 17 | * Not all parameters of the Powerstream data are known yet. All known parameters will be automatically created as states. 18 | * By modifying the "protoSource" constant, newly discovered data will also be automatically created. 19 | * 20 | * The raw data of the interface is logged as a HEX string. 21 | * 22 | * Please exercise caution as this is the initial version of the script. Use it at your own risk! 23 | * 24 | * Requirements: 25 | * - Install protobuf. Simply add these libs to your javascript instance configuration (Zusätzliche NPM-Module) 26 | * - The "Paho MQTT Client" is also required. If not already installed, use the javascript instance configuration. 27 | * 28 | * 29 | * Note: It is encouraged to discover and publish missing data definitions to improve the script. 30 | * Suggestions, optimizations, and extensions are welcome at any time. 31 | * 32 | * Special thanks to all contributors for their valuable input and support. 33 | * 34 | * Unterstütze das Projekt 'ecoflow-connector' 35 | * Wenn dir das Script zur dynamischen Leistungsanpassung für den IObroker gefällt und du es nützlich findest, 36 | * ziehe bitte in Erwägung, eine kleine Spende via PayPal zu hinterlassen. 37 | * Jeder Beitrag hilft, das Projekt am Laufen zu halten und weitere Updates zu ermöglichen. 38 | * Danke für deine Unterstützung! 39 | * https://www.paypal.com/donate/?hosted_button_id=4J7JDDALF3N5L 40 | * 41 | * 42 | * Changelog: 43 | * ----------------------------------------------------------- 44 | * (0.4) 29.06.2023 45 | * Da der MQTT von ecoflow regelmäßig aufhört zu senden, vor allem, wenn die App genutzt und komplett geschlossen wird, 46 | * habe ich eine Überwachung der letzten ankommenden Nachrichten eingebaut. Kommt 5 Minuten lang nichts Neues vom PowerStream, 47 | * wird die Verbindung zum MQTT komplett neu aufgebaut. 48 | * 49 | * Ein Fehler bei der Erstellung der States wurde beseitigt. 50 | * ----------------------------------------------------------- 51 | * (0.5.2) 06.07.2023 52 | * State sumPV hinzugefügt (Summe aus PV1 und PV2) = Solar-Leistung gesamt. 53 | * Abweichung der PV Power von der App versucht zu kompensieren. 54 | * Neuen State RealPower zur besseren Ermittlung der Einspeiseleistung angelegt. Zeigt den Verbrauch im Haus ohne Einspeisung. 55 | * History für RealPower wird automatisch aktiviert. 56 | * Die Koordinaten werden aus den Systemeinstellungen ermittelt (sonst einfach selbst angeben). 57 | * Diverse kleine Anpassungen und Bugfixes. 58 | * Es werden jetzt mehrere PowerStreams berücksichtigt. In der Konfiguration muss das Flag "isPowerStream" gesetzt werden. 59 | * Gesteuert wird aber bisher nur der erste PowerStream. 60 | * Die protoSource so angepasst, dass in "Item" enthaltene unbekannte Daten auch als State angelegt werden. Bitte helft mit zu identifizieren, 61 | * was was ist. Dann können wir die Felder entsprechend benennen... 62 | * Parameter "subscribe" bei der Gerätekonfiguration hinzugefügt. Damit lässt sich der Empfang von MQTT Telegrammen für das Gerät abstellen. 63 | * 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. 64 | * Nach Sonnenuntergang wird jetzt weniger oft reconnectet, wenn keine Daten mehr kommen. 65 | * Reaktionszeiten für die Anpassung der Einspeisung wurden erhöht (30 Sekunden). 66 | * State "totalPV" für die derzeitige komplette PV Leistung hinzugefügt. 67 | * Funktion hinzugefügt, die die Einspeisung bei voller Batterie auf Maximum stellt. Ein- und Ausschaltprozent können mit battPozOn und battPozOff eingestellt werden. 68 | * ----------------------------------------------------------- 69 | * (0.6.1) 26.07.2023 70 | * ACHTUNG: Die Felddefinitionen für den Powerstream sind jetzt vollständig und an die von der Community ermittelten Daten angepasst. 71 | * Das bedeutet aber leider auch, dass alle States mit neuen Namen neu angelegt werden. Die vom Script generierten States bleiben erhalten (SerAC, totalPV, sumPV). 72 | * 73 | * 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. 74 | * Damit die States angelegt werden, müssen sie bei laufendem Script einmal in der App verändert werden. Möglich sind bisher: 75 | * Beep, slowChgPower, ACPower, DCPower, USBPower, minDsgSoc, maxChgSoc, curr12VMax, standByModeMins, lcdTimeMins, ACstandByMins, openOilSoc, closeOilSoc. 76 | * 77 | * 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:". 78 | * Wenn ich diesen Eintrag mit einer kurzen Beschreibung erhalte, was es ist, kann ich es auch einbauen. 79 | * 80 | * Die States werden hier angelegt: 0_userdata.0.ecoflow.app_XXXXXXXXXXXXXXXXXXX_XXXXXXXXXXXXXXX_thing_property_set.writeables 81 | * 82 | * (0.6.7) 31.07.2023 * 83 | * Writeables für Delta2 angelegt. 84 | * totalPV / 10 geändert, damit ein echter Wattwert angezeigt wird. 85 | * Anpassung für neues Datenformat nach diversen Updates von ecoflow. 86 | * Diverse Optimierungen und Bugfixes. 87 | * 88 | * (0.6.8) 03.08.2023 * 89 | * Neue Einstellung: "Regulation: false" zum Abstellen der Regulierung des PowerStreams (Read Only Modus). 90 | * Anpassung an neues Format. 91 | * Bugfixes. 92 | * 93 | * (1.0.0) 06.08.2023 * 94 | * - Neuer State "lowestValue" zeigt die Grundlage zur Berechnung der Einspeiseleistung an und repräsentiert den niedrigsten Wert des realen Verbrauchs in den 95 | * letzten mit "MinValueMin" eingestellten Minuten. 96 | * - Neue Einstellung MinValueAg: Art der Ermittlung des kleinsten Wertes 0 = Minimalwert, 1 = Durchschnittswert. 97 | * - Neues Feature: Wenn die volle Leistung (600w) in die Batterie geht, wird die Einspeiseleistung in Stufen erhöht, auch wenn dann 98 | * Leistung ins Netz geht, um möglichst das volle Potenzial der vorhandenen Solarenergie zu nutzen. 99 | * - Neue Einstellungen: lowBatLimitPozOn, lowBatLimitPozOff und lowBatLimit. Bei Unterschreiten der Batterieladung von "lowBatLimitPozOn" % ist die maximale Einspeiseleistung auf 100 | * "lowBatLimit" W limitiert, bis der Ladezustand wieder bei "lowBatLimitPozOff" ist. 101 | * - Neue Einstellungen: RegulationState. Frei wählbar. Wenn angegeben, kann mit diesem State die Regulation ein- und ausgeschaltet werden (Wird automatisch unterhalb 0_userdata.0.ecoflow angelegt). 102 | * - Neue Einstellungen: RegulationOffPower. Wird die Regulation per State abgestellt, wird die Einspeiseleistung des ersten Powerstreams auf diesen Wert gesetzt. (-1 = keine Änderung). 103 | * 104 | * (1.0.1) 07.08.2023 * 105 | * - Writeables auch für PowerStreams angelegt (SetDisplayBrightness, SetPrio (0=Stromversorgung, 1= Batterie ), SetBatLimitLow, SetBatLimitHigh, SetAC).Sie tauchen auf, wenn bei laufendem Script per App geändert wird. 106 | * - Verbesserung der Reguierung 107 | * 108 | * (1.0.2) 09.08.2023 * 109 | * - Unterstützung für SmartPlugs. Bisher ein Writeable: SwitchPlug mit den Werten 0= AUS und 1= AN 110 | * - Bugfix und Optimierungen 111 | * 112 | * (1.1.2) 17.08.2023 * 113 | * - Braking Changes bei der Konfiguration. Viele Daten sind jetzt zu den einzelnen PowerStream gewandert. 114 | * - Unterstützung der Steuerung von mehreren PowerStream in 2 Modes (Balance und Serial) 115 | * Balance: die PS werden nacheinander angesprochen, dabei versucht jeder Einzelne für sich den Bedarf zu decken. 116 | * Serial: Der Bedarf wird in der Reihenfolge der Konfiguration verteilt. Erst wenn der erste es nicht mehr schafft den Bedarf zu decken, wird der Nächste hinzugezogen 117 | * - Automatisches Wechseln in den Batterieprioritätsmodus. battOnSwitchPrio: true/false wenn battPozOn erreicht ist 118 | * - Festlegen des Gerätetyps bei der Konfiguration. Typ: Powerstrem:"PS"; DeltaMax:"DM"; DeltaMax2: "DM2"; SmartPlug: "SM"; Andere: "NA" 119 | * 120 | */ 121 | // Systemkoordinaten werden versucht zu ermitteln und als Default den Variablen zugeordnet. 122 | var latitude; 123 | var longitude; 124 | // Ermitteln des Standortes aus den Einstellungen. 125 | getStandortKoordinaten() 126 | 127 | /*************************************** 128 | ********** YOUR DATA HERE ************ 129 | ****************************************/ 130 | var ConfigData = { 131 | email: "your@mail.com", // Die App-Zugangsdaten von ecoFlow 132 | passwort: "yourAppPasswort", 133 | seriennummern: [ 134 | //############# Diesen Abschnitt für jedes einzelne Gerät anlegen ################ 135 | { 136 | seriennummer: "XXXXXXXXXXXXX", // Die Seriennummer des Gerätes 137 | name: "PowerStream", // beliebiger Namen 138 | subscribe: true, // "true": Alle Daten für dieses Gerät werden angefragt. "false": Es werden keine Statusdaten abgefragt 139 | typ: "PS", // Welches Gerät ist es: Powerstrem:"PS"; DeltaMax:"DM"; DeltaMax2: "DM2"; SmartPlug: "SM"; Andere: "NA" 140 | // Parameter an hier nur für PowerStream. 141 | regulation: true, // "True": Dieser PowerStream soll vom Script reguliert werden 142 | hasBat: true, // "true": Eine Batterie ist angeschlossen. Nur für PowerStream relevant. 143 | battPozOn: 99, battPozOff: 92, // Wenn die Batterie bei battPozOn ist, Einspeisung auf MaxPower. Bei BattPozOff Normalbetrieb 144 | battOnSwitchPrio: false, // Bei battPozOn wird in den Batterie-Prioritätsmodus gewechselt 145 | lowBatLimitPozOn: 15, lowBatLimitPozOff: 25,// Bei Unterschreiten der Batterieladung von "lowBatLimitPozOn" % ist die maximale Einspeiseleistung auf 146 | lowBatLimit: 150, // "lowBatLimit" limitiert, bis der Ladezustand wieder bei "lowBatLimitPozOff" ist 147 | }, 148 | //####################################################################### 149 | { 150 | seriennummer: "XXXXXXXXXXXXX", 151 | name: "DELTA Max", 152 | typ: "DM", 153 | subscribe: false, // "true": Alle Daten für dieses Gerät werden angefragt. "false": Es werden keine Statusdaten abgefragt 154 | }, 155 | //####################################################################### 156 | { 157 | seriennummer: "XXXXXXXXXXXXX", 158 | name: "SmartPlug 1", 159 | typ: "SM", 160 | subscribe: true, // "true": Alle Daten für dieses Gerät werden angefragt. "false": Es werden keine Statusdaten abgefragt 161 | }, 162 | //####################################################################### 163 | ], 164 | SmartmeterID: "sonoff.0.Stromzaehler1.MT175_P", // State, der den aktuellen Gesamtverbrauch in Watt anzeigt 165 | //**************************************** 166 | // Erweiterte Einstellungen: 167 | //**************************************** 168 | Regulation: true, // 'false' stellt das Setzen der Einspeiseleistung ab 169 | RegulationState: "", // Wenn angegeben, kann mit diesem State die Regulation ein- und ausgeschaltet werden (Wird automatisch unter 0_userdata.0.ecoflow angelegt) 170 | RegulationOffPower: -1, // Wird die Regulation per State abgestellt, wird die Einspeiseleistung des ersten Powerstreams auf diesen Wert gesetzt (-1 = keine Änderung) 171 | RegulationMultiPsMode: 0, // Wenn mehrere PS reguloert werden sollen. "balance" = 0 oder "serial" = 1 172 | BasePowerOffset: 30, // Wird vom aktuellen Verbrauch abgezogen, um die Einspeiseleistung zu berechnen 173 | MaxPower: 600, // Der höchstmögliche Wert in Watt für die Einspeiseleistung 174 | MinValueMin: 3, // Der Zeitraum in Minuten, aus dem der niedrigste Gesamtverbrauchswert geholt werden soll 175 | MinValueAg: 0, // Art der Ermittlung des kleinsten Wertes: 0 = Minimalwert, 1 = Durchschnittswert 176 | ReconnectMin: 30, // Zeit in Minuten, nach der die Anwendung neu gestartet wird, wenn keine neuen Daten eintreffen 177 | statesPrefix: "0_userdata.0.ecoflow", // Hier werden die ecoFlow States angelegt 178 | latitude: latitude, // Breitengrad des Standortes (wird automatisch eingesetzt) 179 | longitude: longitude, // Längengrad des Standortes (wird automatisch eingesetzt) 180 | Debug: false, 181 | PlotCmdID: 99999, 182 | }; 183 | //***************************************/ 184 | //***************************************/ 185 | 186 | const LogAllHeader = false //"HW" 187 | const protoSource2 = ` 188 | syntax = "proto3"; 189 | message Message { 190 | repeated Header header = 1 ; 191 | bytes payload = 2; 192 | } 193 | message Header { 194 | bytes pdata = 1 [proto3_optional = false]; 195 | int32 src = 2 [proto3_optional = true]; 196 | int32 dest = 3 [proto3_optional = true]; 197 | int32 d_src = 4 [proto3_optional = true]; 198 | int32 d_dest = 5 [proto3_optional = true]; 199 | int32 enc_type = 6 [proto3_optional = true]; 200 | int32 check_type = 7 [proto3_optional = true]; 201 | int32 cmd_func = 8 [proto3_optional = true]; 202 | int32 cmd_id = 9 [proto3_optional = true]; 203 | int32 data_len = 10 [proto3_optional = true]; 204 | int32 need_ack = 11 [proto3_optional = true]; 205 | int32 is_ack = 12 [proto3_optional = true]; 206 | int32 seq = 14 [proto3_optional = true]; 207 | int32 product_id = 15 [proto3_optional = true]; 208 | int32 version = 16 [proto3_optional = true]; 209 | int32 payload_ver = 17 [proto3_optional = true]; 210 | int32 time_snap = 18 [proto3_optional = true]; 211 | int32 is_rw_cmd = 19 [proto3_optional = true]; 212 | int32 is_queue = 20 [proto3_optional = true]; 213 | int32 ack_type = 21 [proto3_optional = true]; 214 | string code = 22 [proto3_optional = true]; 215 | string from = 23 [proto3_optional = true]; 216 | string module_sn = 24 [proto3_optional = true]; 217 | string device_sn = 25 [proto3_optional = true]; 218 | } 219 | message InverterHeartbeat { 220 | optional uint32 inv_err_code = 1; 221 | optional uint32 inv_warn_code = 3; 222 | optional uint32 pv1_err_code = 2; 223 | optional uint32 pv1_warn_code = 4; 224 | optional uint32 pv2_err_code = 5; 225 | optional uint32 pv2_warning_code = 6; 226 | optional uint32 bat_err_code = 7; 227 | optional uint32 bat_warning_code = 8; 228 | optional uint32 llc_err_code = 9; 229 | optional uint32 llc_warning_code = 10; 230 | optional uint32 pv1_statue = 11; 231 | optional uint32 pv2_statue = 12; 232 | optional uint32 bat_statue = 13; 233 | optional uint32 llc_statue = 14; 234 | optional uint32 inv_statue = 15; 235 | optional int32 pv1_input_volt = 16; 236 | optional int32 pv1_op_volt = 17; 237 | optional int32 pv1_input_cur = 18; 238 | optional int32 pv1_input_watts = 19; 239 | optional int32 pv1_temp = 20; 240 | optional int32 pv2_input_volt = 21; 241 | optional int32 pv2_op_volt = 22; 242 | optional int32 pv2_input_cur = 23; 243 | optional int32 pv2_input_watts = 24; 244 | optional int32 pv2_temp = 25; 245 | optional int32 bat_input_volt = 26; 246 | optional int32 bat_op_volt = 27; 247 | optional int32 bat_input_cur = 28; 248 | optional int32 bat_input_watts = 29; 249 | optional int32 bat_temp = 30; 250 | optional uint32 bat_soc = 31; 251 | optional int32 llc_input_volt = 32; 252 | optional int32 llc_op_volt = 33; 253 | optional int32 llc_temp = 34; 254 | optional int32 inv_input_volt = 35; 255 | optional int32 inv_op_volt = 36; 256 | optional int32 inv_output_cur = 37; 257 | optional int32 inv_output_watts = 38; 258 | optional int32 inv_temp = 39; 259 | optional int32 inv_freq = 40; 260 | optional int32 inv_dc_cur = 41; 261 | optional int32 bp_type = 42; 262 | optional int32 inv_relay_status = 43; 263 | optional int32 pv1_relay_status = 44; 264 | optional int32 pv2_relay_status = 45; 265 | optional uint32 install_country = 46; 266 | optional uint32 install_town = 47; 267 | optional uint32 permanent_watts = 48; 268 | optional uint32 dynamic_watts = 49; 269 | optional uint32 supply_priority = 50; 270 | optional uint32 lower_limit = 51; 271 | optional uint32 upper_limit = 52; 272 | optional uint32 inv_on_off = 53; 273 | optional uint32 wireless_err_code = 54; 274 | optional uint32 wireless_warn_code = 55; 275 | optional uint32 inv_brightness = 56; 276 | optional uint32 heartbeat_frequency = 57; 277 | optional uint32 rated_power = 58; 278 | } 279 | message InverterHeartbeat2 { 280 | int32 X_Unknown_1 = 1; 281 | int32 X_Unknown_2 = 2; 282 | int32 X_Unknown_3 = 3; 283 | int32 X_Unknown_4 = 4; 284 | int32 X_Unknown_5 = 5; 285 | int32 X_Unknown_6 = 6; 286 | int32 X_Unknown_7 = 7; 287 | int32 X_Unknown_8 = 8; 288 | int32 X_Unknown_9 = 9; 289 | int32 X_Unknown_10 = 10; 290 | int32 X_Unknown_11 = 11; 291 | int32 X_Unknown_12 = 12; 292 | int32 X_Unknown_13 = 13; 293 | int32 X_Unknown_14 = 14; 294 | int32 X_Unknown_15 = 15; 295 | int32 X_Unknown_16 = 16; 296 | int32 X_Unknown_17 = 17; 297 | int32 X_Unknown_18 = 18; 298 | int32 X_Unknown_19 = 19; 299 | int32 X_Unknown_20 = 20; 300 | int32 X_Unknown_21 = 21; 301 | int32 X_Unknown_22 = 22; 302 | int32 X_Unknown_23 = 23; 303 | int32 X_Unknown_24 = 24; 304 | int32 X_Unknown_25 = 25; 305 | int32 X_Unknown_26 = 26; 306 | int32 X_Unknown_27 = 27; 307 | int32 X_Unknown_28 = 28; 308 | int32 X_Unknown_29 = 29; 309 | int32 X_Unknown_30 = 30; 310 | int32 X_Unknown_31 = 31; 311 | int32 X_Unknown_32 = 32; 312 | int32 X_Unknown_33 = 33; 313 | int32 X_Unknown_34 = 34; 314 | int32 X_Unknown_35 = 35; 315 | int32 X_Unknown_36 = 36; 316 | int32 X_Unknown_37 = 37; 317 | int32 X_Unknown_38 = 38; 318 | int32 X_Unknown_39 = 39; 319 | int32 X_Unknown_40 = 40; 320 | int32 X_Unknown_41 = 41; 321 | int32 X_Unknown_42 = 42; 322 | int32 X_Unknown_43 = 43; 323 | int32 X_Unknown_44 = 44; 324 | int32 X_Unknown_45 = 45; 325 | int32 X_Unknown_46 = 46; 326 | int32 X_Unknown_47 = 47; 327 | int32 X_Unknown_48 = 48; 328 | int32 X_Unknown_49 = 49; 329 | int32 X_Unknown_50 = 50; 330 | int32 X_Unknown_51 = 51; 331 | int32 X_Unknown_52 = 52; 332 | } 333 | message setMessage { 334 | setHeader header = 1; 335 | } 336 | message setHeader { 337 | setValue pdata = 1 [proto3_optional = true]; 338 | int32 src = 2 [proto3_optional = true]; 339 | int32 dest = 3 [proto3_optional = true]; 340 | int32 d_src = 4 [proto3_optional = true]; 341 | int32 d_dest = 5 [proto3_optional = true]; 342 | int32 enc_type = 6 [proto3_optional = true]; 343 | int32 check_type = 7 [proto3_optional = true]; 344 | int32 cmd_func = 8 [proto3_optional = true]; 345 | int32 cmd_id = 9 [proto3_optional = true]; 346 | int32 data_len = 10 [proto3_optional = true]; 347 | int32 need_ack = 11 [proto3_optional = true]; 348 | int32 is_ack = 12 [proto3_optional = true]; 349 | int32 seq = 14 [proto3_optional = true]; 350 | int32 product_id = 15 [proto3_optional = true]; 351 | int32 version = 16 [proto3_optional = true]; 352 | int32 payload_ver = 17 [proto3_optional = true]; 353 | int32 time_snap = 18 [proto3_optional = true]; 354 | int32 is_rw_cmd = 19 [proto3_optional = true]; 355 | int32 is_queue = 20 [proto3_optional = true]; 356 | int32 ack_type = 21 [proto3_optional = true]; 357 | string code = 22 [proto3_optional = true]; 358 | string from = 23 [proto3_optional = true]; 359 | string module_sn = 24 [proto3_optional = true]; 360 | string device_sn = 25 [proto3_optional = true]; 361 | } 362 | message setValue { 363 | optional int32 value = 1; 364 | } 365 | message permanent_watts_pack { 366 | optional int32 permanent_watts = 1; 367 | } 368 | message supply_priority_pack { 369 | optional int32 supply_priority = 1; 370 | } 371 | message bat_lower_pack { 372 | optional int32 lower_limit = 1; 373 | } 374 | message bat_upper_pack { 375 | optional int32 upper_limit = 1; 376 | } 377 | message PowerItem { 378 | optional uint32 timestamp = 1; 379 | optional sint32 timezone = 2; 380 | optional uint32 inv_to_grid_power = 3; 381 | optional uint32 inv_to_plug_power = 4; 382 | optional int32 battery_power = 5; 383 | optional uint32 pv1_output_power = 6; 384 | optional uint32 pv2_output_power = 7; 385 | } 386 | message PowerPack { 387 | optional uint32 sys_seq = 1; 388 | repeated PowerItem sys_power_stream = 2; 389 | //repeated plug_heartbeat_pack sys_power_stream = 2; 390 | } 391 | message PowerAckPack { 392 | optional uint32 sys_seq = 1; 393 | } 394 | message node_massage { 395 | optional string sn = 1; 396 | optional bytes mac = 2; 397 | } 398 | message mesh_child_node_info { 399 | optional uint32 topology_type = 1; 400 | optional uint32 mesh_protocol = 2; 401 | optional uint32 max_sub_device_num = 3; 402 | optional bytes parent_mac_id = 4; 403 | optional bytes mesh_id = 5; 404 | repeated node_massage sub_device_list = 6; 405 | } 406 | message EnergyItem { 407 | optional uint32 timestamp = 1; 408 | optional uint32 watth_type = 2; 409 | repeated uint32 watth = 3; 410 | } 411 | message EnergyTotalReport { 412 | optional uint32 watth_seq = 1; 413 | optional EnergyItem watth_item = 2; 414 | } 415 | message BatchEnergyTotalReport { 416 | optional uint32 watth_seq = 1; 417 | repeated EnergyItem watth_item = 2; 418 | } 419 | message EnergyTotalReportAck { 420 | optional uint32 result = 1; 421 | optional uint32 watth_seq = 2; 422 | optional uint32 watth_type = 3; 423 | } 424 | message EventRecordItem { 425 | optional uint32 timestamp = 1; 426 | optional uint32 sys_ms = 2; 427 | optional uint32 event_no = 3; 428 | repeated float event_detail = 4; 429 | } 430 | message EventRecordReport { 431 | optional uint32 event_ver = 1; 432 | optional uint32 event_seq = 2; 433 | repeated EventRecordItem event_item = 3; 434 | } 435 | message EventInfoReportAck { 436 | optional uint32 result = 1; 437 | optional uint32 event_seq = 2; 438 | optional uint32 event_item_num = 3; 439 | } 440 | message ProductNameSet { 441 | optional string name = 1; 442 | } 443 | message ProductNameSetAck { 444 | optional uint32 result = 1; 445 | } 446 | message ProductNameGet {} 447 | message ProductNameGetAck { 448 | optional string name = 3; 449 | } 450 | message RTCTimeGet {} 451 | message RTCTimeGetAck { 452 | optional uint32 timestamp = 1; 453 | optional int32 timezone = 2; 454 | } 455 | message RTCTimeSet { 456 | optional uint32 timestamp = 1; 457 | optional int32 timezone = 2 [(nanopb).default = 0]; 458 | } 459 | message RTCTimeSetAck { 460 | optional uint32 result = 1; 461 | } 462 | message country_town_message { 463 | optional uint32 country = 1; 464 | optional uint32 town = 2; 465 | } 466 | message time_task_config { 467 | optional uint32 task_index = 1; 468 | optional time_range_strategy time_range = 2; 469 | optional uint32 type = 3; 470 | } 471 | message time_task_delet { 472 | optional uint32 task_index = 1; 473 | } 474 | message time_task_config_post { 475 | optional time_task_config task1 = 1; 476 | optional time_task_config task2 = 2; 477 | optional time_task_config task3 = 3; 478 | optional time_task_config task4 = 4; 479 | optional time_task_config task5 = 5; 480 | optional time_task_config task6 = 6; 481 | optional time_task_config task7 = 7; 482 | optional time_task_config task8 = 8; 483 | optional time_task_config task9 = 9; 484 | optional time_task_config task10 = 10; 485 | optional time_task_config task11 = 11; 486 | } 487 | message time_task_config_ack { 488 | optional uint32 task_info = 1; 489 | } 490 | message rtc_data { 491 | optional int32 week = 1 [(nanopb).default = 0]; 492 | optional int32 sec = 2 [(nanopb).default = 0]; 493 | optional int32 min = 3 [(nanopb).default = 0]; 494 | optional int32 hour = 4 [(nanopb).default = 0]; 495 | optional int32 day = 5 [(nanopb).default = 0]; 496 | optional int32 month = 6 [(nanopb).default = 0]; 497 | optional int32 year = 7 [(nanopb).default = 0]; 498 | } 499 | message time_range_strategy { 500 | optional bool is_config = 1; 501 | optional bool is_enable = 2; 502 | optional int32 time_mode = 3; 503 | optional int32 time_data = 4; 504 | optional rtc_data start_time = 5; 505 | optional rtc_data stop_time = 6; 506 | } 507 | message plug_ack_message { 508 | optional uint32 ack = 1; 509 | } 510 | message plug_heartbeat_pack { 511 | optional uint32 err_code = 1 [(nanopb).default = 0]; 512 | optional uint32 warn_code = 2 [(nanopb).default = 0]; 513 | optional uint32 country = 3 [(nanopb).default = 0]; 514 | optional uint32 town = 4 [(nanopb).default = 0]; 515 | optional int32 max_cur = 5 [(nanopb).default = 0]; 516 | optional int32 temp = 6 [(nanopb).default = 0]; 517 | optional int32 freq = 7 [(nanopb).default = 0]; 518 | optional int32 current = 8 [(nanopb).default = 0]; 519 | optional int32 volt = 9 [(nanopb).default = 0]; 520 | optional int32 watts = 10 [(nanopb).default = 0]; 521 | optional bool switch = 11 [(nanopb).default = false]; 522 | optional int32 brightness = 12 [(nanopb).default = 0]; 523 | optional int32 max_watts = 13 [(nanopb).default = 0]; 524 | optional int32 heartbeat_frequency = 14 [(nanopb).default = 0]; 525 | optional int32 mesh_enable = 15 [(nanopb).default = 0]; 526 | } 527 | message plug_switch_message { 528 | optional uint32 plug_switch = 1; 529 | } 530 | message brightness_pack { 531 | optional int32 brightness = 1 [(nanopb).default = 0]; 532 | } 533 | message max_cur_pack { 534 | optional int32 max_cur = 1 [(nanopb).default = 0]; 535 | } 536 | message max_watts_pack { 537 | optional int32 max_watts = 1 [(nanopb).default = 0]; 538 | } 539 | message mesh_ctrl_pack { 540 | optional uint32 mesh_enable = 1 [(nanopb).default = 0]; 541 | } 542 | message ret_pack { 543 | optional bool ret_sta = 1 [(nanopb).default = false]; 544 | } 545 | enum CmdFunction { 546 | Unknown = 0; 547 | PermanentWattsPack = 129; 548 | SupplyPriorityPack = 130; 549 | } 550 | `; 551 | const globalState = {}; 552 | const writeables = [ 553 | { id: 38, name: 'Beep', ValueName: 'enabled', Typ: 'DM' }, 554 | { id: 69, name: 'slowChgPower', ValueName: 'slowChgPower', Typ: 'DM' }, 555 | { id: 66, name: 'ACPower', ValueName: 'enabled', Typ: 'DM' }, 556 | { id: 81, name: 'DCPower', ValueName: 'enabled', Typ: 'DM' }, 557 | { id: 34, name: 'USBPower', ValueName: 'enabled', Typ: 'DM' }, 558 | { id: 51, name: 'minDsgSoc', ValueName: 'minDsgSoc', Typ: 'DM' }, 559 | { id: 49, name: 'maxChgSoc', ValueName: 'maxChgSoc', Typ: 'DM' }, 560 | { id: 71, name: 'curr12VMax', ValueName: 'currMa', Typ: 'DM' }, 561 | { id: 33, name: 'standByModeMins', ValueName: 'standByMode', Typ: 'DM' }, 562 | { id: 49, name: 'lcdTimeMins', ValueName: 'lcdTime', Typ: 'DM' }, 563 | { id: 153, name: 'ACstandByMins', ValueName: 'standByMins', Typ: 'DM' }, 564 | { id: 52, name: 'openOilSoc', ValueName: 'openOilSoc', Typ: 'DM' }, 565 | { id: 53, name: 'closeOilSoc', ValueName: 'closeOilSoc', Typ: 'DM' }, 566 | { id: 0, name: 'acChgCfg_D2', ValueName: 'chgWatts', Typ: 'D2', MT: 5, AddParam: '{"chgWatts":600,"chgPauseFlag":255}' }, 567 | { id: 0, name: 'dcOutCfg_D2', ValueName: 'enabled', Typ: 'D2', MT: 1 }, 568 | { id: 0, name: 'quietMode_D2', ValueName: 'enabled', Typ: 'D2', MT: 5 }, 569 | { id: 0, name: 'dcChgCfg_D2', ValueName: 'dcChgCfg', Typ: 'D2', MT: 5 }, 570 | { id: 1, name: 'InverterHeartbeat', Typ: 'PS', Templet: 'InverterHeartbeat', Writable: false, cmdFunc: 20 }, 571 | { id: 1, name: 'plug_heartbeat_pack', Typ: 'PLUG', Templet: 'plug_heartbeat_pack', Writable: false, cmdFunc: 2 }, 572 | { id: 4, name: 'InverterHeartbeat2', Typ: 'PS', Templet: 'InverterHeartbeat2', Writable: false, cmdFunc: 20 }, 573 | { id: 11, name: 'Ping', Typ: 'PS', Templet: 'setValue', Writable: false, cmdFunc: 32 }, 574 | { id: 32, name: 'Ignor', Typ: 'PS', Templet: '', Writable: false, Ignor: true, cmdFunc: 254 }, 575 | { id: 134, name: 'Ignor', Typ: 'PS', Templet: '', Writable: false, Ignor: true, cmdFunc: 20 }, 576 | { id: 135, name: 'SetDisplayBrightness', Typ: 'PS', Templet: 'setValue', Writable: true, ValueName: 'value', Ignor: false, cmdFunc: 20 }, 577 | { id: 135, name: 'Ignor', Typ: 'Plug', Templet: '', Writable: false, ValueName: '', Ignor: true, cmdFunc: 2 }, 578 | { id: 136, name: 'PowerPack', Typ: 'PS', Templet: 'PowerPack', Writable: false, cmdFunc: 20 }, 579 | { id: 138, name: 'PowerPack', Typ: 'PS', Templet: 'PowerPack', Writable: false, cmdFunc: 20 }, 580 | { id: 130, name: 'SetPrio', Typ: 'PS', Templet: 'setValue', Writable: true, ValueName: 'value', cmdFunc: 20 }, 581 | { id: 132, name: 'SetBatLimitLow', Typ: 'PS', Templet: 'setValue', Writable: true, ValueName: 'value', cmdFunc: 20 }, 582 | { id: 133, name: 'SetBatLimitHigh', Typ: 'PS', Templet: 'setValue', Writable: true, ValueName: 'value', cmdFunc: 20 }, 583 | { id: 129, name: 'SetAC', Typ: 'PS', Templet: 'setValue', Writable: true, ValueName: 'value', cmdFunc: 20 }, 584 | { id: 129, name: 'SwitchPlug', Typ: 'Plug', Templet: 'setValue', Writable: true, ValueName: 'value', cmdFunc: 2 }, 585 | ]; 586 | 587 | const musterGetPS = 588 | { 589 | "header": { 590 | "src": 32, 591 | "dest": 32, 592 | "seq": 1651831507, 593 | "OS": "ios" 594 | } 595 | } 596 | 597 | const musterSetAC = { 598 | header: { 599 | pdata: { 600 | value: 1300, 601 | }, 602 | src: 32, 603 | dest: 53, 604 | dSrc: 1, 605 | dDest: 1, 606 | checkType: 3, 607 | cmdFunc: 20, 608 | cmdId: 129, 609 | dataLen: 3, 610 | needAck: 1, 611 | seq: 1651831507, 612 | version: 19, 613 | payloadVer: 1, 614 | from: 'ios', 615 | deviceSn: 'ABCxxxxxxx123' 616 | } 617 | }; 618 | 619 | const musterSetAC2 = { 620 | header: { 621 | pdata: { 622 | value: 17477, 623 | }, 624 | src: 32, 625 | dest: 53, 626 | dSrc: 1, 627 | dDest: 1, 628 | checkType: 3, 629 | cmdFunc: 32, 630 | cmdId: 11, 631 | dataLen: 4, 632 | needAck: 1, 633 | seq: 1651831507, 634 | version: 19, 635 | payloadVer: 1, 636 | from: 'ios', 637 | deviceSn: 'ABCxxxxxxx123' 638 | } 639 | }; 640 | 641 | const musterslowChgPower = { 642 | "from": "iOS", 643 | "operateType": "TCP", 644 | "id": "816376009", 645 | "lang": "de-de", 646 | "params": 647 | { 648 | "id": 69, 649 | }, 650 | "version": "1.0" 651 | }; 652 | 653 | const musterDelta2 = { 654 | "from": "Android", 655 | "id": "458115693", 656 | "moduleType": 5, 657 | "operateType": "acChgCfg", 658 | "params": 659 | { 660 | } 661 | , "version": "1.0" 662 | } 663 | 664 | 665 | // @ts-ignore 666 | const mqtt = require('mqtt'); 667 | const https = require('https'); 668 | // @ts-ignore 669 | const protobuf = require("protobufjs"); 670 | // Verbindungsstatus speichern 671 | let isMqttConnected = false; 672 | 673 | const mqttDaten = { 674 | UserID: '', 675 | User: '', 676 | Passwort: '', 677 | URL: '', 678 | Port: '', 679 | protocol: '', 680 | clientID: '' 681 | } 682 | 683 | //Die erste PowerStream ermitteln 684 | let firstPsSn = ConfigData.seriennummern[0].seriennummer; 685 | let firstPsSnIndex = -1 686 | GetNextAsn() 687 | //log("firstPsSn: " + GetNextAsn()) 688 | function GetNextAsn() { 689 | if (ConfigData.RegulationMultiPsMode == 1) { 690 | //return firstPsSn 691 | firstPsSnIndex = -1 692 | } 693 | var length = ConfigData.seriennummern.length; 694 | for (var j = 0; j < length; j++) { 695 | var i = (firstPsSnIndex + j + 1) % length; 696 | if (ConfigData.seriennummern[i].typ == "PS" && ConfigData.seriennummern[i].regulation) { 697 | firstPsSn = ConfigData.seriennummern[i].seriennummer; 698 | firstPsSnIndex = i 699 | break; 700 | } 701 | } 702 | //log("GetNextAsn:" + firstPsSn + " Index:" + firstPsSnIndex) 703 | return firstPsSn 704 | } 705 | /*======================================================= 706 | ========= Timer ============ 707 | =======================================================*/ 708 | //jede x Sekunden 709 | var intervalID = setInterval(function () { 710 | if (true || istTag()) { 711 | CheckforReconnect(function () { 712 | SetBasePower(GetNextAsn()); 713 | }); 714 | } else { 715 | ////SetBasePower(firstPsSn); 716 | } 717 | }, 15 * 1000); 718 | 719 | // Hartbeat der App simmulieren 720 | var intervalID3 = setInterval(function () { 721 | if (isMqttConnected) { 722 | for (var i = 0; i < ConfigData.seriennummern.length; i++) { 723 | if (ConfigData.seriennummern[i].typ == "PS") { 724 | setmusterGetPS(ConfigData.seriennummern[i].seriennummer); 725 | } 726 | } 727 | } 728 | }, 32 * 1000); 729 | 730 | /* 731 | var intervalID2 = setInterval(function () { 732 | getLowestValue(ConfigData.statesPrefix + ".RealPower", 2) 733 | .then(lowestValue => { 734 | log( "lowestValue:" + lowestValue)// 735 | }) 736 | .catch((error) => { 737 | console.warn('Fehler beim Abrufen des niedrigsten Werts:', error); 738 | }); 739 | }, 2 * 1000); 740 | //*/ 741 | 742 | 743 | 744 | // @ts-ignore 745 | await getEcoFlowMqttData(ConfigData.email, ConfigData.passwort) 746 | async function getEcoFlowMqttData(email, password) { 747 | const options = { 748 | hostname: 'api.ecoflow.com', 749 | path: '/auth/login', 750 | method: 'POST', 751 | headers: { 752 | 'Host': 'api.ecoflow.com', 753 | 'lang': 'de-de', 754 | 'platform': 'android', 755 | 'sysversion': '11', 756 | 'version': '4.1.2.02', 757 | 'phonemodel': 'SM-X200', 758 | 'content-type': 'application/json', 759 | 'user-agent': 'okhttp/3.14.9' 760 | } 761 | }; 762 | 763 | const data = { 764 | appVersion: "4.1.2.02", 765 | email: email, 766 | os: "android", 767 | osVersion: "30", 768 | password: Buffer.from(password).toString('base64'), 769 | scene: "IOT_APP", 770 | userType: "ECOFLOW" 771 | }; 772 | 773 | function httpsRequest(options, data) { 774 | return new Promise((resolve, reject) => { 775 | const req = https.request(options, res => { 776 | let data = ''; 777 | res.on('data', chunk => { 778 | data += chunk; 779 | }); 780 | res.on('end', () => { 781 | resolve(data); 782 | }); 783 | }); 784 | 785 | req.on('error', error => { 786 | reject(error); 787 | }); 788 | 789 | if (data) { 790 | req.write(JSON.stringify(data)); 791 | } 792 | 793 | req.end(); 794 | }); 795 | } 796 | 797 | function uuidv4() { 798 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { 799 | var r = Math.random() * 16 | 0, 800 | v = c === 'x' ? r : (r & 0x3 | 0x8); 801 | return v.toString(16); 802 | }); 803 | } 804 | 805 | let response = await httpsRequest(options, data); 806 | try { 807 | let token = JSON.parse(response).data.token; 808 | let userid = JSON.parse(response).data.user.userId; 809 | } catch (error) { 810 | log(response) // 811 | throw new Error("Ein Fehler bei der Ermittlung der Zugangsdaten ist aufgetreten. Bitte prüfe die Zugangsdaten."); 812 | } 813 | 814 | let token = JSON.parse(response).data.token; 815 | let userid = JSON.parse(response).data.user.userId; 816 | 817 | options.path = `/iot-auth/app/certification?userId=${userid}`; 818 | options.method = 'GET'; 819 | options.headers.authorization = `Bearer ${token}`; 820 | response = await httpsRequest(options); 821 | try { 822 | mqttDaten.Passwort = JSON.parse(response).data.certificatePassword 823 | mqttDaten.Port = JSON.parse(response).data.port 824 | mqttDaten.UserID = userid 825 | mqttDaten.User = JSON.parse(response).data.certificateAccount 826 | mqttDaten.URL = JSON.parse(response).data.url 827 | mqttDaten.protocol = JSON.parse(response).data.protocol 828 | mqttDaten.clientID = "ANDROID_" + uuidv4() + "_" + userid 829 | } catch (error) { 830 | log(response)// 831 | throw new Error("Ein Fehler bei der Ermittlung der Zugangsdaten ist aufgetreten. Bitte prüfe die Zugangsdaten."); 832 | } 833 | /* 834 | log("UserID: " + userid); // 835 | log("User: " + JSON.parse(response).data.certificateAccount); // 836 | log("Passwort: " + JSON.parse(response).data.certificatePassword); // 837 | log("URL: " + JSON.parse(response).data.url); // 838 | log("Port: " + JSON.parse(response).data.port); // 839 | log("protocol: " + JSON.parse(response).data.protocol); // 840 | log("clientID: ANDROID_" + uuidv4() + "_" + userid); // 841 | */ 842 | 843 | } 844 | 845 | // @ts-ignore 846 | await createMyState("LastTopic") 847 | //################ MQTT Verbindung ################## 848 | function setupMQTTConnection() { 849 | //log("Neue MQTT Verbindung startet") 850 | // Verbindung herstellen 851 | const options = { 852 | port: mqttDaten.Port, 853 | clientId: mqttDaten.clientID, 854 | username: mqttDaten.User, 855 | password: mqttDaten.Passwort, 856 | protocol: mqttDaten.protocol 857 | }; 858 | 859 | const client = mqtt.connect("mqtt://" + mqttDaten.URL, options); 860 | 861 | // Event-Handler für Verbindungsaufbau 862 | client.on('connect', function () { 863 | console.log('Verbunden mit dem Ecoflow MQTT-Broker')// 864 | SubscribeEco(); 865 | for (var i = 0; i < ConfigData.seriennummern.length; i++) { 866 | if (ConfigData.seriennummern[i].typ == "PS") { 867 | setmusterGetPS(ConfigData.seriennummern[i].seriennummer); 868 | } 869 | } 870 | isMqttConnected = true 871 | 872 | }); 873 | 874 | // Auf Nachricht empfangen Ereignis reagieren 875 | client.on('message', async function (topic, message) { 876 | //log("Incomming Massage: " + topic) 877 | //log("Incomming Massage: " + message.length) 878 | if (message.length == 0) return 879 | //return 880 | var jsonMessage = "" 881 | const mqState = topic.replace(/^\//, '').replace(/\//g, '_') 882 | //log(mqState) 883 | await createMyState(mqState + ".RAW") 884 | setState(ConfigData.statesPrefix + ".LastTopic", topic) 885 | try { 886 | jsonMessage = JSON.parse(message); 887 | if (false || ConfigData.Debug) log('JSON-Nachricht empfangen:' + topic + ':' + JSON.stringify(jsonMessage)) // 888 | if (!pruefeID(jsonMessage, mqState)) { 889 | return 890 | } 891 | setState(ConfigData.statesPrefix + '.' + mqState + ".RAW", JSON.stringify(jsonMessage)) 892 | generateAndSyncSub("data", jsonMessage, false, ConfigData.statesPrefix + '.' + mqState) 893 | } catch (error) { 894 | if (error.name != "SyntaxError") log(error.stack, "warn") // 895 | //if (topic.indexOf("/set") !== -1) log('Binäre Nachricht empfangen:' + topic + ':' + message.toString('hex')); 896 | if (ConfigData.Debug) log('Binäre Nachricht empfangen:' + topic + ':' + message.toString('hex'));// 897 | await createMyState(mqState + ".RAW_HEX") 898 | setState(ConfigData.statesPrefix + '.' + mqState + ".RAW_HEX", message.toString('hex')) 899 | const messagedecoded = decodeAndPrint(message.toString('hex'), mqState) 900 | //log(messagedecoded) 901 | if (ConfigData.Debug) log('Decodierte Nachricht:' + messagedecoded) // 902 | setState(ConfigData.statesPrefix + '.' + mqState + ".RAW", messagedecoded) 903 | generateAndSyncSub("", JSON.parse(messagedecoded), false, ConfigData.statesPrefix + '.' + mqState) 904 | } 905 | }); 906 | 907 | // Event-Handler für getrennte Verbindung 908 | client.on('close', () => { 909 | //console.log("MQTT-Client ist getrennt"); 910 | isMqttConnected = false; 911 | }); 912 | 913 | // Callback für Fehler 914 | client.on('error', function (error) { 915 | log('Fehler bei der Ecoflow MQTT-Verbindung:' + error, 'warn'); // 916 | }); 917 | 918 | client.on('reconnect', function () { 919 | console.log('Reconnecting to Ecoflow MQTT broker...'); // 920 | }); 921 | // Weitere Event-Handler hier... 922 | return client; 923 | } 924 | 925 | function SubscribeEco() { 926 | ConfigData.seriennummern.forEach(item => { 927 | client.subscribe('/app/' + mqttDaten.UserID + '/' + item.seriennummer + '/thing/property/set'); 928 | client.subscribe('/app/' + mqttDaten.UserID + '/' + item.seriennummer + '/thing/property/get'); 929 | if (item.subscribe) { 930 | client.subscribe('/app/device/property/' + item.seriennummer); 931 | } 932 | }); 933 | } 934 | 935 | function findWriteableByID(id) { 936 | const foundItem = writeables.find((item) => item.id === id); 937 | return foundItem || null; 938 | } 939 | function findWriteableByValueName(id) { 940 | //log("suche nach writeable: " + id) 941 | const foundItem = writeables.find((item) => item.name === id); 942 | return foundItem || null; 943 | } 944 | 945 | //pruefeID(JSON.parse('{"params":{"slowChgWatts":300,"fastChgWatts":255,"chgPauseFlag":0},"from":"iOS","lang":"de-de","id":"25581985","modelSn":"RXXXXXXXXXXXXX","modelType":3,"operateType":"acChgCfg","version":"1.0"}'), 'app_1584583134200832001_HW51ZOH4SF541661_thing_property_set') 946 | function pruefeID(json, mqState) { 947 | //log(mqState + " : " + JSON.stringify(json)) 948 | if (mqState.includes("thing_property_set")) { 949 | if ('params' in json && 'id' in json.params) { // ('params' in json && 'id' in json.params) (Delta Max) 950 | const Ignores = [40, 72, 68]; 951 | const writeables = [69]; 952 | if (Ignores.includes(json.params.id)) { 953 | //log("Ignore: "+ JSON.stringify(json)) 954 | return false; 955 | } else if (mqState.includes("thing_property_set")) { 956 | const suchwriteable = findWriteableByID(json.params.id) 957 | if (suchwriteable) { 958 | //log("Schreibbar: " + JSON.stringify(json)) 959 | //log("Schreibbardaten: " + JSON.stringify(suchwriteable)) 960 | //log("wert: " + JSON.stringify(json.params[suchwriteable.ValueName])) 961 | createMyState(mqState + ".writeables." + suchwriteable.name + "", json.params[suchwriteable.ValueName].toString()) 962 | setState(ConfigData.statesPrefix + "." + mqState + ".writeables." + suchwriteable.name, json.params[suchwriteable.ValueName].toString(), true) 963 | } else { 964 | log("Unbekannter Set Befehl: " + JSON.stringify(json)) // 965 | log("Adresse: " + mqState) // 966 | } 967 | return true; 968 | } 969 | } else if ('params' in json && 'moduleType' in json && 'operateType' in json) { // Delta2 970 | let suchwriteable 971 | suchwriteable = findWriteableByValueName(json.operateType + "_D2") 972 | /* 973 | for (const paramName in json.params) { 974 | suchwriteable = findWriteableByValueName(paramName) 975 | if(suchwriteable) break 976 | } 977 | */ 978 | if (suchwriteable) { 979 | //log("Schreibbar: " + JSON.stringify(json)) 980 | //log("Schreibbardaten: " + JSON.stringify(suchwriteable)) 981 | //log("wert: " + JSON.stringify(json.params[suchwriteable.ValueName])) 982 | createMyState(mqState + ".writeables." + suchwriteable.name, JSON.stringify(json.params[suchwriteable.ValueName].toString())) 983 | setState(ConfigData.statesPrefix + "." + mqState + ".writeables." + suchwriteable.name, JSON.stringify(json.params[suchwriteable.ValueName].toString()), true) 984 | } else { 985 | log("Unbekannter Delta2 Set Befehl: " + JSON.stringify(json)) // 986 | log("Adresse: " + mqState) // 987 | } 988 | return true; 989 | } else { // ('params' in json && 'id' in json.params) (Delta Max) 990 | log("pruefeID: nix gefunden") // 991 | return true; 992 | } 993 | } //"thing_property_set" 994 | return true; 995 | } 996 | 997 | 998 | // Verbindung herstellen 999 | let client = setupMQTTConnection(); 1000 | 1001 | // Funktion zum Trennen und Neuaufbau der Verbindung 1002 | function reconnect() { 1003 | client.end(); // Verbindung trennen 1004 | setTimeout(function () { 1005 | client = setupMQTTConnection(); // Neue Verbindung herstellen 1006 | //log("Ecoflow neuverbindung"); 1007 | }, 2000); // Wartezeit 1008 | } 1009 | 1010 | // close connection if script stopped 1011 | onStop(function (callback) { 1012 | if (client) { 1013 | // close connection 1014 | client.end(); 1015 | log("Ecoflow MQTT-Client beendet") // 1016 | clearInterval(intervalID); 1017 | //clearInterval(intervalID2); 1018 | clearInterval(intervalID3); 1019 | } 1020 | callback(); 1021 | }, 2000); 1022 | 1023 | function CheckforReconnect(callback) { 1024 | //log("CheckforReconnect") 1025 | //return 1026 | let wartezeit = 15 1027 | //bis eine Stunde nach Sonneuntergang kurze Reconnects dann 15 min. 1028 | if (istTag(60)) wartezeit = 1 1029 | if (getState(ConfigData.statesPrefix + ".LastTopic")?.ts < Date.now() - ConfigData.ReconnectMin * 60 * 1000) { 1030 | console.log("Der letzte Eintrag ist älter als " + ConfigData.ReconnectMin + " Minuten. Versuche Neustart."); // 1031 | setState(ConfigData.statesPrefix + ".LastTopic", "Last Action Restart:" + new Date().toLocaleString()) 1032 | runScript(); 1033 | return; 1034 | // Wenn letzte Powerstream-Meldung älter als min ist, reconnecte 1035 | } else if (getState(ConfigData.statesPrefix + '.app_device_property_' + firstPsSn + '.RAW')?.ts < Date.now() - (wartezeit * 60 * 1000)) { 1036 | //log("Reconnect zu Ecoflow MQTT für PowerStream - Daten") 1037 | //.ts Updaten 1038 | const oldvalue = getState(ConfigData.statesPrefix + '.app_device_property_' + firstPsSn + '.RAW').val 1039 | setState(ConfigData.statesPrefix + '.app_device_property_' + firstPsSn + '.RAW', oldvalue) 1040 | reconnect(); 1041 | return; 1042 | //runScript(); 1043 | } else { 1044 | callback(); 1045 | } 1046 | } 1047 | 1048 | 1049 | //const hextest = "0a0a1020182070f9f0b6830b" 1050 | function decodeAndPrint(hexString, mqState = "") { 1051 | if (typeof hexString !== 'string' || !hexString) { 1052 | log('Ungültiger hexString: "' + hexString + '"'); // 1053 | return "{}"; 1054 | } 1055 | const root = protobuf.parse(protoSource2).root; 1056 | const PowerMessage = root.lookupType("Message"); 1057 | //const message = PowerMessage.decode(Buffer.from(hexString, "hex")); 1058 | let message = {} 1059 | try { 1060 | message = PowerMessage.decode(Buffer.from(hexString, "hex")); 1061 | } catch (error) { 1062 | log('Fehler beim Decodieren:' + error.message); // 1063 | //log('hexString: "' + hexString +'"'); 1064 | //log('buffer: ' + Buffer.from(hexString, "hex")) 1065 | //log('PowerMessage: ' + PowerMessage) 1066 | return "{}"; 1067 | } 1068 | let logflag = false 1069 | if (LogAllHeader) { 1070 | // @ts-ignore 1071 | for (let element of LogAllHeader.split(",")) { 1072 | if (mqState.includes(element)) { 1073 | //log("Ganze Nachricht :" + mqState + " : " + JSON.stringify(convconvertBuffersForLog(message))); 1074 | logflag = true 1075 | } 1076 | } 1077 | } 1078 | let Rueckgabe = {} 1079 | if (Array.isArray(message.header)) { 1080 | //log("Nachricht Anzahl :" + message.header.length); 1081 | let outputObject = {} 1082 | for (let i = 0; i < message.header.length; i++) { 1083 | const header = message.header[i]; 1084 | if (!header.cmdId) header.cmdId = 0; if (!header.cmdFunc) header.cmdFunc = 0 1085 | let matchedEntry = writeables.find((entry) => entry.id == header.cmdId && entry.cmdFunc == header.cmdFunc) || { id: header.cmdId, name: 'nichtDefiniert', Typ: 'PS', Templet: 'nichtDefiniert', Writable: false, Ignor: false }; 1086 | if (matchedEntry.Templet == "nichtDefiniert") { 1087 | //matchedEntry = writeables.find((entry) => entry.id == header.cmdId && entry.Typ == "PS") || { id: header.cmdId, name: 'nichtDefiniert', Typ: 'PS', Templet: 'nichtDefiniert', Writable: false, Ignor: false }; 1088 | } 1089 | //log(JSON.stringify(matchedEntry)) 1090 | const MessageType = matchedEntry.Templet 1091 | //const MessageType = messageIDTypes[header.cmdId] || "nichtDefiniert" 1092 | //log("MessageType: " + MessageType) 1093 | if (logflag && header.cmdId > -1) log("Serial:" + header.deviceSn + " cmdId:" + header.cmdId + " cmdFunc:" + header.cmdFunc + " Pdata Hex: " + header.pdata.toString('hex')) // 1094 | if (logflag && (header.cmdId) == (ConfigData.PlotCmdID)) { 1095 | log("--------------------------------------------")// 1096 | log("topic: " + mqState)// 1097 | log("Definition: " + JSON.stringify(matchedEntry))// 1098 | log("cmdId: " + header.cmdId)// 1099 | log("RAW: " + hexString)// 1100 | log("Nachricht: " + JSON.stringify(convconvertBuffersForLog(message)))// 1101 | log("Header: " + i + " von(" + message.header.length + ") " + JSON.stringify(convconvertBuffersForLog(header)))// 1102 | //log("Pdata Hex: " + header.pdata.toString('hex')) 1103 | log("--------------------------------------------")// 1104 | } 1105 | if (matchedEntry.Ignor) { 1106 | //log("Ignor: " + JSON.stringify(matchedEntry)) 1107 | //log(hexString) 1108 | //log(JSON.stringify(convconvertBuffersForLog(message))) 1109 | continue; 1110 | } 1111 | if (MessageType == "nichtDefiniert") { 1112 | if (header.cmdId != 0 && header.cmdId) { 1113 | console.warn('Nicht definierter cmd_func-Wert:' + header.cmdId + " cmdFunc:" + header.cmdFunc); 1114 | log("hexString: " + hexString)// 1115 | } 1116 | continue; 1117 | } else { 1118 | const PdataMessage = root.lookupType(MessageType); 1119 | const pdata = PdataMessage.decode(header.pdata); 1120 | const pdataObject = PdataMessage.toObject(pdata, { 1121 | longs: Number, // Konvertiere Long-Werte in Zahlen (optional) 1122 | enums: String, // Konvertiere Enum-Werte in Strings (optional) 1123 | bytes: Buffer, // Konvertiere Bytes in Buffer (optional) 1124 | }); 1125 | outputObject[MessageType] = pdataObject 1126 | if (matchedEntry.Writable && mqState.includes("thing_property_set")) { 1127 | //log("Wert = " + JSON.stringify(outputObject) + " topic:"+ mqState) 1128 | const setvalue = pdataObject[matchedEntry.ValueName] || 0 1129 | setStateNE(ConfigData.statesPrefix + "." + mqState + ".writeables." + matchedEntry.name, setvalue.toString(), true) 1130 | } 1131 | 1132 | if (logflag && header.cmdId == ConfigData.PlotCmdID) { 1133 | log("outputObject: " + JSON.stringify(convconvertBuffersForLog(outputObject))) // 1134 | log("--------------------------------------------") // 1135 | } 1136 | 1137 | Rueckgabe.data = outputObject; 1138 | continue; 1139 | } 1140 | }; 1141 | } else { 1142 | // Das Ergebnis ist eine einzelne Message 1143 | log("Es wurde eine einzelne Message dekodiert:" + JSON.stringify(convconvertBuffersForLog(message))); // 1144 | return JSON.stringify(Rueckgabe); 1145 | } 1146 | //log("DURCHLAUF:" + JSON.stringify(Rueckgabe)) 1147 | return JSON.stringify(Rueckgabe) 1148 | } 1149 | 1150 | function convconvertBuffersForLog(obj, hash = new WeakMap()) { // 1151 | if (obj == null || typeof obj !== 'object') return obj; 1152 | //if (Buffer.isBuffer(obj)) return Buffer.from(obj); //Original Buffer 1153 | if (Buffer.isBuffer(obj)) return obj.toString('hex'); //Hexstring 1154 | if (hash.has(obj)) return hash.get(obj); 1155 | let copy = Array.isArray(obj) ? [] : {}; 1156 | hash.set(obj, copy); 1157 | for (let key in obj) { 1158 | if (obj.hasOwnProperty(key)) { 1159 | copy[key] = convconvertBuffersForLog(obj[key], hash); // 1160 | } 1161 | } 1162 | return copy; 1163 | } 1164 | 1165 | function convertBuffersToHexStrings(obj) { 1166 | for (let key in obj) { 1167 | //if (key == "pdata") log("pdata gefunden. typ: " + typeof obj[key]) 1168 | if (obj[key] instanceof Buffer) { 1169 | obj[key] = obj[key].toString('hex'); 1170 | } else if (typeof obj[key] === 'object') { 1171 | convertBuffersToHexStrings(obj[key]); 1172 | } 1173 | } 1174 | } 1175 | 1176 | function SendProto(protomsg, topic) { 1177 | //return 1178 | const root = protobuf.parse(protoSource2).root; 1179 | const PowerMessage = root.lookupType("setMessage"); 1180 | const message = PowerMessage.create(JSON.parse(protomsg)); 1181 | const messageBuffer = PowerMessage.encode(message).finish(); 1182 | //log("Modifizierter Hex-String:" + Buffer.from(messageBuffer).toString("hex")); 1183 | //log("topic:" + topic); 1184 | client.publish(topic, messageBuffer, { qos: 1 }, function (error) { 1185 | if (error) { 1186 | console.error('Fehler beim Veröffentlichen der MQTT-Nachricht:', error); 1187 | } else { 1188 | if (ConfigData.Debug) log('Die MQTT-Nachricht wurde erfolgreich veröffentlicht.'); // 1189 | } 1190 | }); 1191 | } 1192 | 1193 | function SendJSON(protomsg, topic) { 1194 | //log("topic:" + topic); 1195 | client.publish(topic, protomsg, { qos: 1 }, function (error) { 1196 | if (error) { 1197 | console.error('Fehler beim Veröffentlichen der MQTT-Nachricht:', error); 1198 | } else { 1199 | if (ConfigData.Debug) log('Die MQTT-Nachricht wurde erfolgreich veröffentlicht.'); // 1200 | } 1201 | }); 1202 | } 1203 | 1204 | function getStandortKoordinaten() { 1205 | var obj = getObject('system.config'); 1206 | if (obj) { 1207 | latitude = obj.common.latitude; 1208 | longitude = obj.common.longitude; 1209 | } else { 1210 | console.error('Fehler beim Abrufen der Einstellungen'); 1211 | } 1212 | } 1213 | 1214 | //Anmeldenachrichten der APP 1215 | function setmusterGetPS(asn) { 1216 | let updatedMusterSetAC = JSON.parse(JSON.stringify(musterGetPS)); 1217 | updatedMusterSetAC.header.seq = Date.now() 1218 | //log(JSON.stringify(updatedMusterSetAC)); 1219 | SendProto(JSON.stringify(updatedMusterSetAC), '/app/' + mqttDaten.UserID + '/' + asn + '/thing/property/get'); 1220 | SendProto(JSON.stringify(updatedMusterSetAC), '/app/' + mqttDaten.UserID + '/' + asn + '/thing/property/get'); 1221 | SendProto(JSON.stringify(updatedMusterSetAC), '/app/' + mqttDaten.UserID + '/' + asn + '/thing/property/get'); 1222 | // @ts-ignore 1223 | updatedMusterSetAC = JSON.parse(JSON.stringify(musterSetAC2)); 1224 | updatedMusterSetAC.header.seq = Date.now() 1225 | updatedMusterSetAC.header.deviceSn = asn 1226 | //log(JSON.stringify(updatedMusterSetAC)); 1227 | SendProto(JSON.stringify(updatedMusterSetAC), '/app/' + mqttDaten.UserID + '/' + asn + '/thing/property/set'); 1228 | } 1229 | 1230 | function generateAndSyncSub(id, JElements, sub = false, preset = "0_userdata.0") { 1231 | if (!JElements || typeof JElements !== 'object') { 1232 | log('Ungültige JElements übergeben!'); // 1233 | return; 1234 | } 1235 | for (var JElement in JElements) { 1236 | var AktVal; 1237 | if (typeof JElements[JElement] === "object") { 1238 | if (id === "") { 1239 | generateAndSyncSub(JElement, JElements[JElement], true, preset); 1240 | } else { 1241 | generateAndSyncSub(id + "." + JElement, JElements[JElement], true, preset); 1242 | } 1243 | 1244 | //generateAndSyncSub(id + "." + JElement, JElements[JElement], true, preset); 1245 | } else { 1246 | try { 1247 | if (isState2(preset + "." + id + "." + JElement)) AktVal = getState(preset + "." + id + "." + JElement).val; else AktVal = null 1248 | } catch (e) { 1249 | log("Fehler: " + e); // 1250 | } 1251 | if (AktVal == null) { 1252 | createState(preset + "." + id + "." + JElement, JElements[JElement], false); 1253 | AktVal = JElements[JElement]; 1254 | } 1255 | if (AktVal != JElements[JElement]) { 1256 | if (isState2(preset + "." + id + "." + JElement)) { 1257 | setState(preset + "." + id + "." + JElement, JElements[JElement]); 1258 | } 1259 | } 1260 | } 1261 | } 1262 | } 1263 | 1264 | 1265 | /* Checks if a a given state or part of state is existing. 1266 | * This is a workaround, as getObject() or getState() throw warnings in the log. 1267 | * Set strict to true if the state shall match exactly. If it is false, it will add a wildcard * to the end. 1268 | * See: https://forum.iobroker.net/topic/11354/ 1269 | * @param {string} strStatePath Input string of state, like 'javascript.0.switches.Osram.Bedroom' 1270 | * @param {boolean} [strict=false] Optional: if true, it will work strict, if false, it will add a wildcard * to the end of the string 1271 | * @return {boolean} true if state exists, false if not */ 1272 | function isState2(strStatePath, strict = true) { 1273 | let mSelector; 1274 | if (strict) { 1275 | mSelector = $(strStatePath); 1276 | } else { 1277 | mSelector = $(strStatePath + "*"); 1278 | } 1279 | if (mSelector.length > 0) { 1280 | return true; 1281 | } else { 1282 | return false; 1283 | } 1284 | } 1285 | 1286 | //--------- Prüft übergebne zeiträume und Tage und gibt True zurück wenn innerhalb 1287 | //log("return: " + CheckTime2("22:00","11:00",[0,1,2,3,4,5,6],getDateObject("06 Nov 2018 08:30:00 GMT+0100"))); 1288 | function CheckTime2(Startzeit, Endzeit, Wochentage, d = new Date()) { 1289 | var locStartDate = getDateObject(formatDate(d, "MM DD YYYY " + Startzeit)); 1290 | var locEndDate = getDateObject(formatDate(d, "MM DD YYYY " + Endzeit)); 1291 | var LocOriginal = getDateObject(formatDate(d, "MM DD YYYY hh:mm:ss")); 1292 | Wochentage = Wochentage.map(function (x) { 1293 | return parseInt(x, 10); 1294 | }); 1295 | if (locStartDate.getTime() > locEndDate.getTime()) { 1296 | if (LocOriginal.getTime() >= locStartDate.getTime() && LocOriginal.getTime() <= getDateObject(formatDate(d, "MM DD YYYY 23:59:59")).getTime()) { 1297 | d.setDate(d.getDate() + 1); 1298 | locEndDate = getDateObject(formatDate(d, "MM DD YYYY " + Endzeit)); 1299 | } else { 1300 | d.setDate(d.getDate() - 1); 1301 | locStartDate = getDateObject(formatDate(d, "MM DD YYYY " + Startzeit)); 1302 | } 1303 | } 1304 | var n = getDateObject(locStartDate).getDay(); 1305 | if (Wochentage.includes(n) && LocOriginal.getTime() >= getDateObject(locStartDate).getTime() && LocOriginal.getTime() <= getDateObject(locEndDate).getTime()) { return true } else { return false } 1306 | } 1307 | 1308 | function SunTimes(time = 0) { 1309 | // @ts-ignore 1310 | const SunCalc = require('suncalc'); 1311 | const date = new Date(); 1312 | // Berechnung von Sonnenaufgang und Sonnenuntergang 1313 | const sunTimes = SunCalc.getTimes(date, ConfigData.latitude, ConfigData.longitude); 1314 | const sunrise = sunTimes.sunrise.getHours() + ':' + sunTimes.sunrise.getMinutes(); 1315 | const sunset = sunTimes.sunset.getHours() + ':' + sunTimes.sunset.getMinutes(); 1316 | if (time == 0) { 1317 | return sunrise 1318 | } else { 1319 | return sunset 1320 | } 1321 | } 1322 | 1323 | function istTag(offsetMin = 0) { 1324 | //log("Ist Tag?: " + CheckTime2(SunTimes(0).toString(), addMinutesToTime(SunTimes(1).toString(),offsetMin), [0, 1, 2, 3, 4, 5, 6], new Date())); 1325 | return CheckTime2(SunTimes(0).toString(), addMinutesToTime(SunTimes(1).toString(), offsetMin), [0, 1, 2, 3, 4, 5, 6]) 1326 | } 1327 | 1328 | function addMinutesToTime(time, minutesToAdd) { 1329 | var parts = time.split(":"); 1330 | var hours = parseInt(parts[0]); 1331 | var minutes = parseInt(parts[1]); 1332 | var totalMinutes = hours * 60 + minutes + minutesToAdd; 1333 | var newHours = Math.floor(totalMinutes / 60) % 24; 1334 | var newMinutes = totalMinutes % 60; 1335 | var newTime = newHours.toString().padStart(2, "0") + ":" + newMinutes.toString().padStart(2, "0"); 1336 | return newTime; 1337 | } 1338 | //############ Funktionen zum Setzen von Werten 1339 | for (var i = 0; i < ConfigData.seriennummern.length; i++) { 1340 | if (ConfigData.seriennummern[i].typ == "PS") { 1341 | const asn = ConfigData.seriennummern[i].seriennummer 1342 | //log(asn) 1343 | // @ts-ignore 1344 | await createMyState('app_' + mqttDaten.UserID + '_' + asn + '_thing_property_set.setAC') 1345 | on({ id: ConfigData.statesPrefix + '.app_' + mqttDaten.UserID + '_' + asn + '_thing_property_set.setAC', change: "any", ack: false }, function (obj) { 1346 | setAC(asn, Number(obj.state.val)) 1347 | setState(obj.id, obj.state.val, true); 1348 | }); 1349 | 1350 | //Powersumme bilden und schreiben data.InverterHeartbeat.pv1InputWatts 1351 | on({ id: new RegExp(ConfigData.statesPrefix + '.app_device_property_' + asn + '.data.InverterHeartbeat.pv.InputWatts'), change: "any" }, function (obj) { 1352 | let state1 = ConfigData.statesPrefix + '.app_device_property_' + asn + '.data.InverterHeartbeat.pv1InputWatts'; 1353 | let state2 = ConfigData.statesPrefix + '.app_device_property_' + asn + '.data.InverterHeartbeat.pv2InputWatts'; 1354 | //let korstate = ConfigData.statesPrefix + '.app_device_property_' + asn + '.data.InverterHeartbeat.X_Unknown_5'; 1355 | let sumState = ConfigData.statesPrefix + '.app_device_property_' + asn + '.data.InverterHeartbeat.sumPV'; 1356 | 1357 | if (existsState(state1) || existsState(state2)) { 1358 | let pv1InputWatts = 0, pv2InputWatts = 0 1359 | if (existsState(state1)) pv1InputWatts = GetValAkt(state1, 30).val 1360 | if (existsState(state2)) pv2InputWatts = GetValAkt(state2, 30).val 1361 | //let sum = GetValAkt(state1, 30).val + GetValAkt(state2, 30).val - (getState(korstate).val * 20); 1362 | let sum = (pv1InputWatts + pv2InputWatts) * (0.93); 1363 | if (!existsState(sumState)) { 1364 | createState(sumState, sum); 1365 | } else { 1366 | setState(sumState, sum); 1367 | //log("Summe gesetzt für "+asn+": "+ sum) 1368 | } 1369 | } 1370 | }); 1371 | } 1372 | } 1373 | const idRegex = new RegExp(ConfigData.statesPrefix + '\.app_[A-Za-z0-9_]+_thing_property_set\\.writeables\\..*'); 1374 | on({ id: idRegex, change: "any", ack: false }, function (obj) { 1375 | const idParts = obj.id.split('.'); 1376 | const lastPart = idParts[idParts.length - 1]; 1377 | const matchedEntry = writeables.find((entry) => entry.name === lastPart); 1378 | //const matchedEntry = writeables.find((entry) => entry.name === lastPart && entry.typ === "PS"); 1379 | if (matchedEntry) { 1380 | //log("Write Event: " + obj.id + " val: " + obj.state.val + " | Matched Entry: " + JSON.stringify(matchedEntry)); 1381 | const asn = obj.id.match(/.*?\.app_.*?_(.*?)_thing_property_set.*/)[1]; 1382 | let updatedMuster 1383 | if (matchedEntry.Typ == "DM") { 1384 | updatedMuster = JSON.parse(JSON.stringify(musterslowChgPower));; 1385 | updatedMuster.id = Date.now().toString() 1386 | updatedMuster.params.id = matchedEntry.id 1387 | updatedMuster.params[matchedEntry.ValueName] = Number(obj.state.val) 1388 | SendJSON(JSON.stringify(updatedMuster), '/app/' + mqttDaten.UserID + '/' + asn + '/thing/property/set'); 1389 | } else if (matchedEntry.Typ == "D2") { 1390 | updatedMuster = JSON.parse(JSON.stringify(musterDelta2));; 1391 | updatedMuster.id = Date.now().toString() 1392 | updatedMuster.moduleType = Number(matchedEntry.MT) 1393 | updatedMuster.operateType = matchedEntry.name.replace("_D2", "") 1394 | 1395 | if (matchedEntry.AddParam) updatedMuster.params = JSON.parse(matchedEntry.AddParam); 1396 | //updatedMuster.params.chgPauseFlag = 255 1397 | updatedMuster.params[matchedEntry.ValueName] = Number(obj.state.val) 1398 | SendJSON(JSON.stringify(updatedMuster), '/app/' + mqttDaten.UserID + '/' + asn + '/thing/property/set'); 1399 | } else if (matchedEntry.Typ == "PS" || matchedEntry.Typ == "Plug") { 1400 | updatedMuster = JSON.parse(JSON.stringify(musterSetAC)); 1401 | if (Number(obj.state.val) <= -1) { 1402 | delete updatedMuster.item.meta; 1403 | delete updatedMuster.item.ValByte; 1404 | } 1405 | else { 1406 | updatedMuster.header.pdata[matchedEntry.ValueName] = Number(obj.state.val) 1407 | updatedMuster.header.dataLen = getVarintByteSize(Number(obj.state.val)) 1408 | } 1409 | updatedMuster.header.cmdId = matchedEntry.id 1410 | updatedMuster.header.cmdFunc = matchedEntry.cmdFunc || 20 1411 | updatedMuster.header.seq = Date.now() 1412 | updatedMuster.header.deviceSn = asn 1413 | //log(JSON.stringify(updatedMuster)) 1414 | SendProto(JSON.stringify(updatedMuster), '/app/' + mqttDaten.UserID + '/' + asn + '/thing/property/set'); 1415 | } else { 1416 | log("Write Event Unbekannter Typ: (" + matchedEntry.Typ + ") " + obj.id + " val: " + obj.state.val + ""); // 1417 | } 1418 | //log("Gefunden und gesendet:Topic: " + '/app/' + mqttDaten.UserID + '/' + asn + '/thing/property/set' + " Daten:" + JSON.stringify(updatedMuster)) 1419 | //SendJSON(JSON.stringify(updatedMuster), '/app/' + mqttDaten.UserID + '/' + asn + '/thing/property/set'); 1420 | //delete updatedMuster.params[matchedEntry.ValueName] 1421 | } else { 1422 | log("Write Event: " + obj.id + " val: " + obj.state.val + " | No matching entry found."); // 1423 | } 1424 | setState(obj.id, obj.state.val, true); 1425 | }); 1426 | 1427 | //State für die gesamte PV-Leistung 'totalPV' erstellen und beschreiben 1428 | on({ id: new RegExp(ConfigData.statesPrefix + '\.app_device_property_[A-Za-z0-9]{13,17}\.data\.InverterHeartbeat\.sumPV'), change: "any" }, function (obj) { 1429 | //log("sumpv Evemnt:" + obj.id + " val: " + obj.state.val) 1430 | let totalPV = 0 1431 | for (var i = 0; i < ConfigData.seriennummern.length; i++) { 1432 | if (ConfigData.seriennummern[i].typ == "PS") { 1433 | const asn = ConfigData.seriennummern[i].seriennummer 1434 | if (isState2(ConfigData.statesPrefix + '.app_device_property_' + asn + '.data.InverterHeartbeat.sumPV')) { 1435 | totalPV = totalPV + GetValAkt(ConfigData.statesPrefix + '.app_device_property_' + asn + '.data.InverterHeartbeat.sumPV', 60).val 1436 | } 1437 | } 1438 | } 1439 | totalPV = totalPV / 10 1440 | 1441 | let totalPVState = ConfigData.statesPrefix + '.totalPV'; 1442 | if (!existsState(totalPVState)) { 1443 | createState(totalPVState, Number(totalPV.toFixed(0))); 1444 | } else { 1445 | setState(totalPVState, Number(totalPV.toFixed(0))); 1446 | //log("Summe gesetzt für "+asn+": "+ sum) 1447 | } 1448 | }) 1449 | 1450 | //Regulation State 1451 | if (ConfigData.RegulationState != "") { 1452 | let eventid = ConfigData.statesPrefix + '.' + ConfigData.RegulationState 1453 | ConfigData.Regulation = Boolean(getStateCr(eventid, ConfigData.Regulation, true).val) 1454 | on({ id: eventid, change: "any", ack: false }, function (obj) { 1455 | let name = obj.id.split('.').pop(); 1456 | //log(name + ":" + obj.state.val) 1457 | 1458 | if (ConfigData.RegulationOffPower >= 0 && !obj.state.val) { 1459 | setAC(firstPsSn, ConfigData.RegulationOffPower * 10) 1460 | GlobalObj[firstPsSn].OldNewValue = 0 1461 | } 1462 | ConfigData.Regulation = Boolean(obj.state.val) 1463 | }) 1464 | } 1465 | 1466 | // Einstellen der Einspeiseleistung 1467 | function setAC(asn, Value) { 1468 | if (!ConfigData.Regulation) { 1469 | //return 1470 | } 1471 | let updatedMusterSetAC = musterSetAC; 1472 | if (Value <= -1) { 1473 | delete updatedMusterSetAC.item.meta; 1474 | delete updatedMusterSetAC.item.ValByte; 1475 | } 1476 | else { 1477 | updatedMusterSetAC.header.pdata.value = Value 1478 | updatedMusterSetAC.header.dataLen = getVarintByteSize(Value) 1479 | } 1480 | updatedMusterSetAC.header.seq = Date.now() 1481 | updatedMusterSetAC.header.deviceSn = asn 1482 | //log(JSON.stringify(updatedMusterSetAC)) 1483 | setState(ConfigData.statesPrefix + '.app_' + mqttDaten.UserID + '_' + asn + '_thing_property_set.setAC', Value.toString(), true) 1484 | SendProto(JSON.stringify(updatedMusterSetAC), '/app/' + mqttDaten.UserID + '/' + asn + '/thing/property/set'); 1485 | } 1486 | 1487 | // ########### Grundbedarf/Einspeiseleistung steuern 1488 | // Den niedrigsten oder durchschnittlichen Wert vom Gesamtverbrauch der letzten x Minuten ermitteln 1489 | function getLowestValue(id, minuten = 120) { 1490 | const now = Date.now(); 1491 | const range = minuten * 60 * 1000; 1492 | return new Promise((resolve, reject) => { 1493 | sendTo('history.0', 'getHistory', { 1494 | id: id, 1495 | options: { 1496 | start: now - range, 1497 | end: now, 1498 | aggregate: 'none', 1499 | ignoreNull: true 1500 | } 1501 | }, (result) => { 1502 | if (result.error) { 1503 | log("getLowestValue-error: " + result.error);// 1504 | reject(result.error); 1505 | } else if (result.result && result.result.length > 0) { 1506 | let lowestValue = result.result[0].val; 1507 | let totalValue = result.result[0].val; 1508 | if (ConfigData.MinValueAg === 0) { 1509 | for (let i = 1; i < result.result.length; i++) { 1510 | if (result.result[i].val < lowestValue) { 1511 | lowestValue = result.result[i].val; 1512 | } 1513 | } 1514 | } else if (ConfigData.MinValueAg === 1) { 1515 | for (let i = 1; i < result.result.length; i++) { 1516 | totalValue += result.result[i].val; 1517 | } 1518 | lowestValue = totalValue / result.result.length; // Durchschnittswert 1519 | } 1520 | let Dauer = ((Date.now() - now) / 1000) 1521 | if (Dauer > 1) log("getLowestValue-Duration: " + ((Date.now() - now) / 1000) + "s") // 1522 | setStateNE(ConfigData.statesPrefix + ".lowestValue", Math.floor(Number(lowestValue))); 1523 | resolve(Math.floor(Number(lowestValue))); 1524 | } else { 1525 | reject(new Error('No data')); 1526 | } 1527 | }); 1528 | }); 1529 | } 1530 | 1531 | //Einspeiseleistung berechnen und bei Änderung setzen 1532 | // Initialisiere die Globalen variablen 1533 | const GlobalObj = {} 1534 | for (var i = 0; i < ConfigData.seriennummern.length; i++) { 1535 | const sn = ConfigData.seriennummern[i].seriennummer; 1536 | if (!GlobalObj[sn]) { 1537 | GlobalObj[sn] = {}; 1538 | } 1539 | if (ConfigData.seriennummern[i].typ == "PS") { 1540 | GlobalObj[sn].OldNewValue = -1 1541 | GlobalObj[sn].FullPower = false 1542 | GlobalObj[sn].zusatzpower = 0 1543 | GlobalObj[sn].TempMaxPower = ConfigData.MaxPower 1544 | } 1545 | } 1546 | //var OldNewValue = -1 1547 | //var FullPower = false 1548 | //let zusatzpower = 0 1549 | //let TempMaxPower = ConfigData.MaxPower 1550 | const ZUSATZPOWER_INCREMENT = 20 1551 | const BAT_MAX_OFFSET = 60 1552 | function SetBasePower(asn, needPowerFromOthersRek = null) { 1553 | //log("SetBasePower " + asn) 1554 | //return 1555 | if (isState2(ConfigData.SmartmeterID) && ConfigData.Regulation) { 1556 | let skip = false 1557 | const ToHomeId = ConfigData.statesPrefix + ".app_device_property_" + asn + ".data.InverterHeartbeat.invOutputWatts" 1558 | const batstate = Number(GetValAkt(ConfigData.statesPrefix + ".app_device_property_" + asn + ".data.InverterHeartbeat.batSoc", 600).val) 1559 | const invOutputWatts = Number(GetValAkt(ToHomeId, 90, false).val.toFixed(0)) / 10 1560 | const PrioMode = getStateCr(ConfigData.statesPrefix + '.app_' + mqttDaten.UserID + '_' + asn + '_thing_property_set.writeables.SetPrio', "0", true).val == "1" ? true : false 1561 | let LeiststungsGap = ((GlobalObj[asn].OldNewValue) - invOutputWatts) 1562 | let BatPower = Number(GetValAkt(ConfigData.statesPrefix + ".app_device_property_" + asn + ".data.InverterHeartbeat.batInputWatts", 600).val / 10) 1563 | var foundItem = ConfigData.seriennummern.find(item => item.seriennummer === asn); 1564 | if (foundItem.hasBat) { 1565 | if (batstate >= foundItem.battPozOn && !GlobalObj[asn].FullPower && !PrioMode) { 1566 | if (foundItem.battOnSwitchPrio) { 1567 | if (BatPower < 30) { 1568 | setStateNE(ConfigData.statesPrefix + '.app_' + mqttDaten.UserID + '_' + asn + '_thing_property_set.writeables.SetPrio', "1", false) 1569 | GlobalObj[asn].OldNewValue = -1 1570 | if (true || ConfigData.Debug) log("Batterie ist bei " + foundItem.battPozOn + "%: Schalte auf Batterie Prioritätsmodus.") 1571 | skip = true 1572 | } 1573 | } else { 1574 | GlobalObj[asn].FullPower = true 1575 | setAC(asn, (Math.floor(ConfigData.MaxPower) * 10)) 1576 | GlobalObj[asn].OldNewValue = (Math.floor(ConfigData.MaxPower)) 1577 | if (true || ConfigData.Debug) log("Batterie ist bei " + foundItem.battPozOn + "%: Einspeisung auf Maximum.") 1578 | skip = true 1579 | } 1580 | getLowestValue(ConfigData.statesPrefix + ".RealPower", ConfigData.MinValueMin) 1581 | //return 1582 | } else if ((batstate > foundItem.battPozOff || batstate == 0) && (GlobalObj[asn].FullPower || PrioMode)) { 1583 | getLowestValue(ConfigData.statesPrefix + ".RealPower", ConfigData.MinValueMin) 1584 | if (foundItem.battOnSwitchPrio) { 1585 | // Hier noch Regeln einbinden zum frühzeitigen beeneden des Priomode 1586 | // Weiter unten nach der Bedarfsermittlung 1587 | 1588 | } else { 1589 | skip = true 1590 | } 1591 | //return 1592 | } else if (GlobalObj[asn].FullPower || PrioMode) { 1593 | GlobalObj[asn].FullPower = false 1594 | if (true || ConfigData.Debug) log("Batterie runter auf " + foundItem.battPozOff + "%: Normalbetrieb.") 1595 | if (foundItem.battOnSwitchPrio) { 1596 | setStateNE(ConfigData.statesPrefix + '.app_' + mqttDaten.UserID + '_' + asn + '_thing_property_set.writeables.SetPrio', "0", false) 1597 | } 1598 | } 1599 | if (!skip) { 1600 | const batInputWatts = ConfigData.statesPrefix + ".app_device_property_" + asn + ".data.InverterHeartbeat.batInputWatts" 1601 | if (Number(GetValAkt(batInputWatts, 60).val) <= ((ConfigData.MaxPower - BAT_MAX_OFFSET) * -10)) { 1602 | if (GlobalObj[asn].zusatzpower < 300) { 1603 | if (GlobalObj[asn].zusatzpower == 0) { GlobalObj[asn].zusatzpower = invOutputWatts } 1604 | GlobalObj[asn].zusatzpower += ZUSATZPOWER_INCREMENT 1605 | log("Maximalleistung geht in die Batterie. Stelle zusätzlich Einspeisung auf " + GlobalObj[asn].zusatzpower + " W") 1606 | setAC(asn, ((GlobalObj[asn].zusatzpower) * 10)); 1607 | GlobalObj[asn].OldNewValue = (GlobalObj[asn].zusatzpower) 1608 | } 1609 | getLowestValue(ConfigData.statesPrefix + ".RealPower", ConfigData.MinValueMin) 1610 | skip = true 1611 | //return 1612 | } else { 1613 | if (Number(GetValAkt(batInputWatts, 60).val) >= ((ConfigData.MaxPower - 200) * -10) && GlobalObj[asn].zusatzpower > 0) { 1614 | GlobalObj[asn].zusatzpower = 0 1615 | log("-Zusatzpower aus !") 1616 | 1617 | } else { 1618 | GlobalObj[asn].zusatzpower = GlobalObj[asn].zusatzpower - ZUSATZPOWER_INCREMENT 1619 | if (GlobalObj[asn].zusatzpower > 0) { 1620 | log("Maximalleistung geht in die Batterie. Stelle zusätzlich Einspeisung auf " + GlobalObj[asn].zusatzpower + " W") 1621 | setAC(asn, ((GlobalObj[asn].zusatzpower) * 10)); 1622 | GlobalObj[asn].OldNewValue = (GlobalObj[asn].zusatzpower) 1623 | getLowestValue(ConfigData.statesPrefix + ".RealPower", ConfigData.MinValueMin) 1624 | skip = true 1625 | //return 1626 | } else { 1627 | if (GlobalObj[asn].zusatzpower > -ZUSATZPOWER_INCREMENT && GlobalObj[asn].zusatzpower <= 0) log("Zusatzpower aus !") 1628 | GlobalObj[asn].zusatzpower = 0 1629 | } 1630 | } 1631 | } 1632 | } 1633 | if (!skip) { 1634 | if (batstate < foundItem.lowBatLimitPozOn) { 1635 | if (GlobalObj[asn].TempMaxPower == ConfigData.MaxPower) log("Batteriestand unter Limit:" + foundItem.lowBatLimitPozOn + "% (" + batstate + "%). Limitiere Einspeiseleistung auf: " + foundItem.lowBatLimit + "W") 1636 | GlobalObj[asn].TempMaxPower = foundItem.lowBatLimit 1637 | } else { 1638 | if (GlobalObj[asn].TempMaxPower == foundItem.lowBatLimit && batstate >= foundItem.lowBatLimitPozOff) { 1639 | log("Batteriestand ist jetzt über Limit:" + foundItem.lowBatLimitPozOff + "% (" + batstate + "%). Maximale Einspeisung wieder bei: " + ConfigData.MaxPower + "W") 1640 | GlobalObj[asn].TempMaxPower = ConfigData.MaxPower 1641 | } 1642 | } 1643 | } 1644 | } else { //hasbat 1645 | if (!GlobalObj[asn].FullPower) { 1646 | GlobalObj[asn].FullPower = true 1647 | if (true || ConfigData.Debug) log("PowerStream " + asn + " hat keine Batterie konfiguriert. Einspeisung auf Maximum. (" + ConfigData.MaxPower + ")") 1648 | setAC(asn, (Math.floor(ConfigData.MaxPower) * 10)) 1649 | } 1650 | GlobalObj[asn].OldNewValue = (Math.floor(ConfigData.MaxPower)) 1651 | getLowestValue(ConfigData.statesPrefix + ".RealPower", ConfigData.MinValueMin) 1652 | skip = true 1653 | //return 1654 | }//hasbat 1655 | //log ("regelung GlobalObj[asn].OldNewValue:" + GlobalObj[asn].OldNewValue) 1656 | getLowestValue(ConfigData.statesPrefix + ".RealPower", ConfigData.MinValueMin) 1657 | .then(lowestValue => { 1658 | //log("SetBasePower lowestValue " + lowestValue) 1659 | if (lowestValue != 0) { 1660 | //var invOutputWatts = 0 1661 | //const ToHomeId = ConfigData.statesPrefix + ".app_device_property_" + asn + ".data.InverterHeartbeat.invOutputWatts" 1662 | if (isState2(ToHomeId)) { 1663 | //invOutputWatts = Number(GetValAkt(ToHomeId,90).val) / 10 1664 | let NewValue 1665 | if (needPowerFromOthersRek !== null) { 1666 | NewValue = needPowerFromOthersRek 1667 | } else { 1668 | NewValue = (lowestValue - ConfigData.BasePowerOffset) 1669 | } 1670 | //Einspeisung der andernen Powerstream feststellen und abziehen: 1671 | let otherPS = 0 1672 | for (var i = 0; i < ConfigData.seriennummern.length; i++) { 1673 | if (ConfigData.seriennummern[i].typ == "PS" && ConfigData.seriennummern[i].seriennummer != asn) { 1674 | const asn = ConfigData.seriennummern[i].seriennummer 1675 | otherPS = otherPS + Number(GetValAkt(ConfigData.statesPrefix + '.app_device_property_' + asn + '.data.InverterHeartbeat.invOutputWatts', 90).val) 1676 | //log("otherPS: " + otherPS / 10) 1677 | } 1678 | } 1679 | //log("-- LeiststungsGap " + LeiststungsGap.toFixed(1) + " OldNewValue:" + GlobalObj[asn].OldNewValue + " invOutputWatts:" + invOutputWatts) 1680 | 1681 | if (foundItem.battOnSwitchPrio) { 1682 | // Hier noch Regeln einbinden zum frühzeitigen beeneden des Priomode 1683 | if (PrioMode) { 1684 | if (LeiststungsGap > 30) { 1685 | log("Priomode ist an. LeiststungsGap " + LeiststungsGap.toFixed(1) + " OldNewValue:" + GlobalObj[asn].OldNewValue + " invOutputWatts:" + invOutputWatts) 1686 | log("Schalte den jetzt Priomode aus") 1687 | setStateNE(ConfigData.statesPrefix + '.app_' + mqttDaten.UserID + '_' + asn + '_thing_property_set.writeables.SetPrio', "0", false) 1688 | } 1689 | } 1690 | } else { 1691 | //skip = true 1692 | } 1693 | 1694 | //****** 1695 | //const powerValue = needPowerFromOthersRek !== null ? needPowerFromOthersRek : 0; 1696 | if (ConfigData.RegulationMultiPsMode == 1) { 1697 | let calcValue = NewValue 1698 | let myMaxPower = GlobalObj[asn].TempMaxPower 1699 | 1700 | //log("LeiststungsGap " + LeiststungsGap + " OldNewValue:" + GlobalObj[asn].OldNewValue) 1701 | if (LeiststungsGap > 10) { 1702 | myMaxPower = invOutputWatts 1703 | } 1704 | let needPowerFromOthers = calcValue - myMaxPower 1705 | //log("needPowerFromOthers " + needPowerFromOthers + " myMaxPower:" + myMaxPower) 1706 | serialRegulation(asn, needPowerFromOthers) 1707 | if (needPowerFromOthers > 0) { 1708 | NewValue = GlobalObj[asn].TempMaxPower 1709 | } 1710 | } else { 1711 | NewValue = Number((NewValue - (otherPS / 10)).toFixed(0)) 1712 | if (NewValue > GlobalObj[asn].TempMaxPower) NewValue = GlobalObj[asn].TempMaxPower 1713 | } 1714 | //****** 1715 | 1716 | if (skip) return 1717 | 1718 | if (NewValue < 0) NewValue = 0 1719 | //log("Newval:" + NewValue) 1720 | //log("LowVal in " + ConfigData.MinValueMin + " Minuten: " + lowestValue + " W, Andere: " + (otherPS / 10) + " W, Offset " + ConfigData.BasePowerOffset + "W, neu: " + NewValue + " W"); 1721 | if (false || ConfigData.Debug) { 1722 | log("Tiefster Wert der letzten " + ConfigData.MinValueMin + " Minuten: " + lowestValue + " W");// 1723 | log("Summe der Anderen PS: " + (otherPS / 10) + " W");// 1724 | log("Rest ist: " + Math.floor((lowestValue) - (otherPS / 10)) + " W");// 1725 | log("Offset von: " + ConfigData.BasePowerOffset + " W abziehen = " + (Math.floor((lowestValue) - (otherPS / 10)) - ConfigData.BasePowerOffset) + " W Neuer Einspeisewert")// 1726 | log("Neuer Wert unter Berücksichtigung der Limits: " + NewValue + " W")// 1727 | log("Einspeisung aktuell: " + invOutputWatts + " W")// 1728 | log("===================================================") // 1729 | } 1730 | //setAC(asn,10) 1731 | if (GlobalObj[asn].OldNewValue != NewValue) { 1732 | setAC(asn, (Math.floor(NewValue) * 10)) 1733 | if (false || ConfigData.Debug) log("Änderung für Einspeisung gesendet: " + Math.floor(NewValue) + " W") // 1734 | } 1735 | GlobalObj[asn].OldNewValue = NewValue 1736 | } 1737 | } 1738 | }) 1739 | .catch(error => { 1740 | log("Fehler beim Abrufen des niedrigsten Werts: " + error); // 1741 | }); 1742 | } 1743 | } 1744 | 1745 | function serialRegulation(asn, PowerNeeded) { 1746 | var foundIndex = ConfigData.seriennummern.findIndex(item => item.seriennummer === asn) || 0; 1747 | for (var i = foundIndex + 1; i < ConfigData.seriennummern.length; i++) { 1748 | if (ConfigData.seriennummern[i].typ == "PS" && ConfigData.seriennummern[i].regulation && ConfigData.seriennummern[i].seriennummer != asn) { 1749 | //log("Rekursivaufruf an :" + ConfigData.seriennummern[i].seriennummer + " für Restleistung:" + PowerNeeded) 1750 | SetBasePower(ConfigData.seriennummern[i].seriennummer, PowerNeeded) 1751 | break; 1752 | } 1753 | } 1754 | } 1755 | 1756 | 1757 | function serialRegulationalt(asn, PowerNeeded) { 1758 | for (var y = 0; y < ConfigData.seriennummern.length; y++) { 1759 | var i = (firstPsSnIndex + y + 1) % ConfigData.seriennummern.length; 1760 | if (ConfigData.seriennummern[i].typ == "PS" && ConfigData.seriennummern[i].regulation && ConfigData.seriennummern[i].seriennummer != asn) { 1761 | const ToHomeId = ConfigData.statesPrefix + ".app_device_property_" + ConfigData.seriennummern[i].seriennummer + ".data.InverterHeartbeat.invOutputWatts" 1762 | const permanentWatts = Number(GetValAkt(ConfigData.statesPrefix + ".app_device_property_" + ConfigData.seriennummern[i].seriennummer + ".data.InverterHeartbeat.permanentWatts", 90, false).val.toFixed(0)) / 10 1763 | const invOutputWatts = Number(GetValAkt(ToHomeId, 90, false).val.toFixed(0)) / 10 1764 | //let newsetVal = (Math.floor(invOutputWatts + PowerNeeded) * 10) 1765 | let newsetVal = (Math.floor(PowerNeeded) * 10) 1766 | if (newsetVal < 0) newsetVal = 0 1767 | if (newsetVal == 0 && permanentWatts == 0) { 1768 | 1769 | } else { 1770 | setAC(ConfigData.seriennummern[i].seriennummer, newsetVal) 1771 | } 1772 | GlobalObj[ConfigData.seriennummern[i].seriennummer].OldNewValue = newsetVal 1773 | //setAC(ConfigData.seriennummern[i].seriennummer, (Math.floor( PowerNeeded) * 10)) 1774 | //firstPsSn = ConfigData.seriennummern[i].seriennummer; 1775 | //firstPsSnIndex = i 1776 | break; 1777 | } 1778 | } 1779 | } 1780 | 1781 | 1782 | function GetValAkt(id, minuten = 15, reset = true) { 1783 | if (isState2(id)) { 1784 | const state = getState(id) 1785 | if (state.ts > Date.now() - minuten * 60 * 1000) { 1786 | return state 1787 | } else { 1788 | if (typeof state.val === 'number') { 1789 | if (reset && state.val != 0) setState(id, 0, true) 1790 | } else { 1791 | if (reset && state.val != "0") setState(id, "0", true) 1792 | } 1793 | state.val = 0 1794 | return state 1795 | } 1796 | } else { 1797 | //log("Kein State: " + id + "lege an.") 1798 | createState(id, "0", false) 1799 | //return getState(id) 1800 | 1801 | const leerstate = {} 1802 | leerstate.val = "0" 1803 | leerstate.ts = Date.now() 1804 | return leerstate 1805 | } 1806 | } 1807 | 1808 | 1809 | async function createMyState(name, value = undefined) { 1810 | const stateName = ConfigData.statesPrefix + '.' + name; 1811 | if (!globalState[stateName]) { 1812 | const state = { 1813 | name: name.split('.').pop(), 1814 | role: 'state', 1815 | //type: 'string', // 'number', 'boolean', usw. 1816 | read: true, 1817 | write: true, 1818 | }; 1819 | // @ts-ignore 1820 | globalState[stateName] = state; 1821 | log(globalState); 1822 | // Wenn der optionale Parameter value übergeben wurde, schreibe den Wert in den State 1823 | } 1824 | } 1825 | 1826 | 1827 | function getStateCr(id, initValue, ack = false, common = {}, native = {}) { 1828 | if (!isState2(id)) { 1829 | let valueType = typeof initValue; 1830 | let name = id.split('.').pop(); 1831 | if (Object.keys(common).length === 0) { 1832 | common = { 1833 | name: name, 1834 | type: valueType, 1835 | role: 'state', 1836 | read: true, 1837 | write: true, 1838 | }; 1839 | } 1840 | createState(id, initValue, false, common, native) 1841 | //log("getStateCr: " + id) 1842 | const leerstate = {} 1843 | leerstate.val = initValue 1844 | leerstate.ts = Date.now() 1845 | return leerstate 1846 | } else { 1847 | return getState(id); 1848 | } 1849 | } 1850 | 1851 | function setStateNE(id, value, ack = false, common = {}, native = {}) { 1852 | existsState(id, function (err, exists) { 1853 | if (!exists) { 1854 | let valueType = typeof value; 1855 | let name = id.split('.').pop(); 1856 | if (Object.keys(common).length === 0) { 1857 | common = { 1858 | name: name, 1859 | type: valueType, 1860 | role: 'state', 1861 | read: true, 1862 | write: true, 1863 | }; 1864 | } 1865 | createState(id, value, false, common, native, function () { 1866 | setState(id, value, ack); 1867 | }); 1868 | } else { 1869 | setState(id, value, ack); 1870 | } 1871 | }); 1872 | } 1873 | 1874 | // State RealPower anlegen wenn noch nicht vorhanden und History aktivieren 1875 | if (isState2(ConfigData.SmartmeterID)) { 1876 | if (!isState2(ConfigData.statesPrefix + ".RealPower")) { 1877 | const stateObject = { 1878 | name: "RealPower", 1879 | role: "state", 1880 | type: "number", 1881 | read: true, 1882 | write: true, 1883 | custom: { 1884 | "history.0": { 1885 | enabled: true, 1886 | aliasId: "", 1887 | debounceTime: 0, 1888 | blockTime: 0, 1889 | changesOnly: false, 1890 | changesRelogInterval: 0, 1891 | changesMinDelta: 0, 1892 | ignoreBelowNumber: "", 1893 | disableSkippedValueLogging: true, 1894 | retention: 86400, 1895 | customRetentionDuration: 365, 1896 | maxLength: 960, 1897 | enableDebugLogs: false, 1898 | debounce: 1000 1899 | } 1900 | } 1901 | }; 1902 | createState(ConfigData.statesPrefix + ".RealPower", stateObject, function () { 1903 | //* 1904 | const stateId = ConfigData.statesPrefix + ".RealPower"; // Hier den ID des States angeben 1905 | // Aktiviere die History-Funktion für den State 1906 | const historyOptions = { 1907 | id: stateId, 1908 | options: { 1909 | enabled: true // Setze den Wert auf true, um die History zu aktivieren 1910 | } 1911 | }; 1912 | 1913 | sendTo("history.0", "enableHistory", historyOptions, (result) => { 1914 | if (result.error) { 1915 | log("Fehler beim Aktivieren der History für " + stateId + ": " + result.error);// 1916 | } else { 1917 | log("History für " + stateId + " erfolgreich aktiviert");// 1918 | } 1919 | }); 1920 | // 1921 | }); 1922 | 1923 | } 1924 | //Wert für den realen Verbrauch. Wird alle 5 Sekunden gesetzt, wenn sich die Werte vom Smartmeter ändern 1925 | let WorkInProz = false 1926 | const SECONDS_DELAY = 5; 1927 | const DIVISION_FACTOR = 10; 1928 | const TOLERANCE_PERIOD_FACTOR = 2; 1929 | let LastRealPower = 0 1930 | on({ id: ConfigData.SmartmeterID, change: "any" }, function (obj) { 1931 | if (!WorkInProz) { 1932 | WorkInProz = true; 1933 | setTimeout(function () { 1934 | const Hausstrom = Number(getState(ConfigData.SmartmeterID).val); 1935 | let Einspeisung = 0; 1936 | 1937 | for (const item of ConfigData.seriennummern) { 1938 | if (item.typ == "PS") { 1939 | const asn = item.seriennummer; 1940 | const LastACset = getState(ConfigData.statesPrefix + '.app_' + mqttDaten.UserID + '_' + asn + '_thing_property_set.setAC').ts; 1941 | const invOutputWattsState = GetValAkt(ConfigData.statesPrefix + ".app_device_property_" + asn + ".data.InverterHeartbeat.invOutputWatts", 50, true); 1942 | const invOutputWatts = Number(invOutputWattsState.val) / DIVISION_FACTOR; 1943 | const lastOutset = invOutputWattsState.ts; 1944 | 1945 | if ((Number(lastOutset) < Number(LastACset)) && invOutputWatts !== 0) { 1946 | const lastRealset = getState(ConfigData.statesPrefix + ".RealPower").ts; 1947 | if (Number(lastRealset) > Date.now() - ((ConfigData.MinValueMin * 1000 * 60) / TOLERANCE_PERIOD_FACTOR)) { 1948 | //log("RealPower Set Warte auf aktuelle Daten von: " + asn + " lezter: " + new Date(lastOutset).toLocaleTimeString('de-DE') + " / ACset: " + new Date(LastACset).toLocaleTimeString('de-DE')); 1949 | WorkInProz = false; 1950 | return; 1951 | } else { 1952 | //log("Überspringe ab jetzt warten auf Daten von: " + asn + " und setzte Wert für Einspeisung auf 0 ") 1953 | //setState(ConfigData.statesPrefix + ".app_device_property_" + asn + ".data.InverterHeartbeat.invOutputWatts", "0") 1954 | Einspeisung += invOutputWatts; 1955 | } 1956 | } else { 1957 | 1958 | Einspeisung += invOutputWatts; 1959 | } 1960 | } 1961 | } 1962 | const RealPower = Number((Hausstrom + Einspeisung).toFixed(0)) 1963 | if (RealPower + 100 < LastRealPower) { 1964 | //log("PeakSkip Delta: " + (LastRealPower - RealPower) ) 1965 | } else { 1966 | setState(ConfigData.statesPrefix + ".RealPower", RealPower); 1967 | } 1968 | LastRealPower = RealPower 1969 | WorkInProz = false; 1970 | }, SECONDS_DELAY * 1000); 1971 | } 1972 | }); 1973 | } 1974 | function getVarintByteSize(number) { 1975 | let byteSize = 0; 1976 | while (number >= 128) { 1977 | byteSize++; 1978 | number >>= 7; // Rechtsschiebeoperation um 7 Bits 1979 | } 1980 | byteSize++; byteSize++; 1981 | return byteSize; 1982 | } 1983 | 1984 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ecoflow-powerstream-nodejs 2 | API to change the powestream outoput value 3 | 4 | Based on work from https://forum.iobroker.net/topic/66743/ecoflow-connector-script-zur-dynamischen-leistungsanpassung 5 | 6 | As I don't have an IOBroker I set up a node.js server to modify the output from the PowerStram by Ecoflow. 7 | 8 | Not supported by Ecoflow, use it on your own rsponsability. 9 | 10 | PowerStream 11 | - setAc - modifies the output power 12 | - setPrio - modifes the mode 0 = prio AC, 1 = prio storage 13 | 14 | Call exemple (simply compose your correct URL and call it) 15 | - SetPrio AC and watts to 100 : 16 | https://YOUR_URL/KEY_URL?TOKEN_KEY=TOKEN_VALUE&KEY1=100&KEY2=0&TARGET=2 17 | 18 | - SetPrio storage and watts to 500 : 19 | https://YOUR_URL/KEY_URL?TOKEN_KEY=TOKEN_VALUE&KEY1=500&KEY2=1 20 | 21 | Home atomation usage: 22 | - In configuration.xml: 23 | input_text: 24 | pws_out_value: 25 | name: Value for PowerStream Output 26 | pws_mode_value: 27 | name: Value for PowerStream Mode (0/1) 28 | 29 | rest_command: 30 | pws_cmd: 31 | url: "https://YOUR_URL/KEY_URL?TOKEN_KEY=TOKEN_VALUE&KE1={{ states('input_text.pws_out_value') }}&KEY2={{ states('input_text.pws_mode_value') }}" 32 | verify_ssl: false 33 | 34 | 35 | - automation script 1 - press buton set PWS to 120 without modifyng the mode 36 | alias: Btn PWS 120 37 | description: "" 38 | trigger: 39 | - platform: state 40 | entity_id: 41 | - input_button.btn_powserstream_120 42 | condition: [] 43 | action: 44 | - service: input_text.set_value 45 | data: 46 | value: "120" 47 | target: 48 | entity_id: input_text.pws_out_value 49 | - service: rest_command.pws_cmd 50 | data: {} 51 | mode: single 52 | 53 | automation script 2 - 20h30 set PWS 120 and mode to priority AC 54 | alias: Time 20h30 => PWS 120 55 | description: "" 56 | trigger: 57 | - platform: time 58 | at: "20:30:00" 59 | condition: [] 60 | action: 61 | - service: automation.trigger 62 | data: 63 | skip_condition: true 64 | target: 65 | entity_id: automation.pws_100 66 | - device_id: f54d73723718d4b17d9ee13e36627877 67 | domain: mobile_app 68 | type: notify 69 | message: PWS => 120 70 | - delay: 71 | hours: 0 72 | minutes: 1 73 | seconds: 0 74 | milliseconds: 0 75 | - service: automation.trigger 76 | data: 77 | skip_condition: true 78 | target: 79 | entity_id: automation.pws_100 80 | mode: single 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /const.js: -------------------------------------------------------------------------------- 1 | const protoSource2 = ` 2 | syntax = "proto3"; 3 | message Message { 4 | repeated Header header = 1 ; 5 | bytes payload = 2; 6 | } 7 | message Header { 8 | bytes pdata = 1 [proto3_optional = false]; 9 | int32 src = 2 [proto3_optional = true]; 10 | int32 dest = 3 [proto3_optional = true]; 11 | int32 d_src = 4 [proto3_optional = true]; 12 | int32 d_dest = 5 [proto3_optional = true]; 13 | int32 enc_type = 6 [proto3_optional = true]; 14 | int32 check_type = 7 [proto3_optional = true]; 15 | int32 cmd_func = 8 [proto3_optional = true]; 16 | int32 cmd_id = 9 [proto3_optional = true]; 17 | int32 data_len = 10 [proto3_optional = true]; 18 | int32 need_ack = 11 [proto3_optional = true]; 19 | int32 is_ack = 12 [proto3_optional = true]; 20 | int32 seq = 14 [proto3_optional = true]; 21 | int32 product_id = 15 [proto3_optional = true]; 22 | int32 version = 16 [proto3_optional = true]; 23 | int32 payload_ver = 17 [proto3_optional = true]; 24 | int32 time_snap = 18 [proto3_optional = true]; 25 | int32 is_rw_cmd = 19 [proto3_optional = true]; 26 | int32 is_queue = 20 [proto3_optional = true]; 27 | int32 ack_type = 21 [proto3_optional = true]; 28 | string code = 22 [proto3_optional = true]; 29 | string from = 23 [proto3_optional = true]; 30 | string module_sn = 24 [proto3_optional = true]; 31 | string device_sn = 25 [proto3_optional = true]; 32 | } 33 | message InverterHeartbeat { 34 | optional uint32 inv_err_code = 1; 35 | optional uint32 inv_warn_code = 3; 36 | optional uint32 pv1_err_code = 2; 37 | optional uint32 pv1_warn_code = 4; 38 | optional uint32 pv2_err_code = 5; 39 | optional uint32 pv2_warning_code = 6; 40 | optional uint32 bat_err_code = 7; 41 | optional uint32 bat_warning_code = 8; 42 | optional uint32 llc_err_code = 9; 43 | optional uint32 llc_warning_code = 10; 44 | optional uint32 pv1_statue = 11; 45 | optional uint32 pv2_statue = 12; 46 | optional uint32 bat_statue = 13; 47 | optional uint32 llc_statue = 14; 48 | optional uint32 inv_statue = 15; 49 | optional int32 pv1_input_volt = 16; 50 | optional int32 pv1_op_volt = 17; 51 | optional int32 pv1_input_cur = 18; 52 | optional int32 pv1_input_watts = 19; 53 | optional int32 pv1_temp = 20; 54 | optional int32 pv2_input_volt = 21; 55 | optional int32 pv2_op_volt = 22; 56 | optional int32 pv2_input_cur = 23; 57 | optional int32 pv2_input_watts = 24; 58 | optional int32 pv2_temp = 25; 59 | optional int32 bat_input_volt = 26; 60 | optional int32 bat_op_volt = 27; 61 | optional int32 bat_input_cur = 28; 62 | optional int32 bat_input_watts = 29; 63 | optional int32 bat_temp = 30; 64 | optional uint32 bat_soc = 31; 65 | optional int32 llc_input_volt = 32; 66 | optional int32 llc_op_volt = 33; 67 | optional int32 llc_temp = 34; 68 | optional int32 inv_input_volt = 35; 69 | optional int32 inv_op_volt = 36; 70 | optional int32 inv_output_cur = 37; 71 | optional int32 inv_output_watts = 38; 72 | optional int32 inv_temp = 39; 73 | optional int32 inv_freq = 40; 74 | optional int32 inv_dc_cur = 41; 75 | optional int32 bp_type = 42; 76 | optional int32 inv_relay_status = 43; 77 | optional int32 pv1_relay_status = 44; 78 | optional int32 pv2_relay_status = 45; 79 | optional uint32 install_country = 46; 80 | optional uint32 install_town = 47; 81 | optional uint32 permanent_watts = 48; 82 | optional uint32 dynamic_watts = 49; 83 | optional uint32 supply_priority = 50; 84 | optional uint32 lower_limit = 51; 85 | optional uint32 upper_limit = 52; 86 | optional uint32 inv_on_off = 53; 87 | optional uint32 wireless_err_code = 54; 88 | optional uint32 wireless_warn_code = 55; 89 | optional uint32 inv_brightness = 56; 90 | optional uint32 heartbeat_frequency = 57; 91 | optional uint32 rated_power = 58; 92 | } 93 | message InverterHeartbeat2 { 94 | int32 X_Unknown_1 = 1; 95 | int32 X_Unknown_2 = 2; 96 | int32 X_Unknown_3 = 3; 97 | int32 X_Unknown_4 = 4; 98 | int32 X_Unknown_5 = 5; 99 | int32 X_Unknown_6 = 6; 100 | int32 X_Unknown_7 = 7; 101 | int32 X_Unknown_8 = 8; 102 | int32 X_Unknown_9 = 9; 103 | int32 X_Unknown_10 = 10; 104 | int32 X_Unknown_11 = 11; 105 | int32 X_Unknown_12 = 12; 106 | int32 X_Unknown_13 = 13; 107 | int32 X_Unknown_14 = 14; 108 | int32 X_Unknown_15 = 15; 109 | int32 X_Unknown_16 = 16; 110 | int32 X_Unknown_17 = 17; 111 | int32 X_Unknown_18 = 18; 112 | int32 X_Unknown_19 = 19; 113 | int32 X_Unknown_20 = 20; 114 | int32 X_Unknown_21 = 21; 115 | int32 X_Unknown_22 = 22; 116 | int32 X_Unknown_23 = 23; 117 | int32 X_Unknown_24 = 24; 118 | int32 X_Unknown_25 = 25; 119 | int32 X_Unknown_26 = 26; 120 | int32 X_Unknown_27 = 27; 121 | int32 X_Unknown_28 = 28; 122 | int32 X_Unknown_29 = 29; 123 | int32 X_Unknown_30 = 30; 124 | int32 X_Unknown_31 = 31; 125 | int32 X_Unknown_32 = 32; 126 | int32 X_Unknown_33 = 33; 127 | int32 X_Unknown_34 = 34; 128 | int32 X_Unknown_35 = 35; 129 | int32 X_Unknown_36 = 36; 130 | int32 X_Unknown_37 = 37; 131 | int32 X_Unknown_38 = 38; 132 | int32 X_Unknown_39 = 39; 133 | int32 X_Unknown_40 = 40; 134 | int32 X_Unknown_41 = 41; 135 | int32 X_Unknown_42 = 42; 136 | int32 X_Unknown_43 = 43; 137 | int32 X_Unknown_44 = 44; 138 | int32 X_Unknown_45 = 45; 139 | int32 X_Unknown_46 = 46; 140 | int32 X_Unknown_47 = 47; 141 | int32 X_Unknown_48 = 48; 142 | int32 X_Unknown_49 = 49; 143 | int32 X_Unknown_50 = 50; 144 | int32 X_Unknown_51 = 51; 145 | int32 X_Unknown_52 = 52; 146 | } 147 | message setMessage { 148 | setHeader header = 1; 149 | } 150 | message setHeader { 151 | setValue pdata = 1 [proto3_optional = true]; 152 | int32 src = 2 [proto3_optional = true]; 153 | int32 dest = 3 [proto3_optional = true]; 154 | int32 d_src = 4 [proto3_optional = true]; 155 | int32 d_dest = 5 [proto3_optional = true]; 156 | int32 enc_type = 6 [proto3_optional = true]; 157 | int32 check_type = 7 [proto3_optional = true]; 158 | int32 cmd_func = 8 [proto3_optional = true]; 159 | int32 cmd_id = 9 [proto3_optional = true]; 160 | int32 data_len = 10 [proto3_optional = true]; 161 | int32 need_ack = 11 [proto3_optional = true]; 162 | int32 is_ack = 12 [proto3_optional = true]; 163 | int32 seq = 14 [proto3_optional = true]; 164 | int32 product_id = 15 [proto3_optional = true]; 165 | int32 version = 16 [proto3_optional = true]; 166 | int32 payload_ver = 17 [proto3_optional = true]; 167 | int32 time_snap = 18 [proto3_optional = true]; 168 | int32 is_rw_cmd = 19 [proto3_optional = true]; 169 | int32 is_queue = 20 [proto3_optional = true]; 170 | int32 ack_type = 21 [proto3_optional = true]; 171 | string code = 22 [proto3_optional = true]; 172 | string from = 23 [proto3_optional = true]; 173 | string module_sn = 24 [proto3_optional = true]; 174 | string device_sn = 25 [proto3_optional = true]; 175 | } 176 | message setValue { 177 | optional int32 value = 1; 178 | } 179 | message permanent_watts_pack { 180 | optional int32 permanent_watts = 1; 181 | } 182 | message supply_priority_pack { 183 | optional int32 supply_priority = 1; 184 | } 185 | message bat_lower_pack { 186 | optional int32 lower_limit = 1; 187 | } 188 | message bat_upper_pack { 189 | optional int32 upper_limit = 1; 190 | } 191 | message PowerItem { 192 | optional uint32 timestamp = 1; 193 | optional sint32 timezone = 2; 194 | optional uint32 inv_to_grid_power = 3; 195 | optional uint32 inv_to_plug_power = 4; 196 | optional int32 battery_power = 5; 197 | optional uint32 pv1_output_power = 6; 198 | optional uint32 pv2_output_power = 7; 199 | } 200 | message PowerPack { 201 | optional uint32 sys_seq = 1; 202 | repeated PowerItem sys_power_stream = 2; 203 | //repeated plug_heartbeat_pack sys_power_stream = 2; 204 | } 205 | message PowerAckPack { 206 | optional uint32 sys_seq = 1; 207 | } 208 | message node_massage { 209 | optional string sn = 1; 210 | optional bytes mac = 2; 211 | } 212 | message mesh_child_node_info { 213 | optional uint32 topology_type = 1; 214 | optional uint32 mesh_protocol = 2; 215 | optional uint32 max_sub_device_num = 3; 216 | optional bytes parent_mac_id = 4; 217 | optional bytes mesh_id = 5; 218 | repeated node_massage sub_device_list = 6; 219 | } 220 | message EnergyItem { 221 | optional uint32 timestamp = 1; 222 | optional uint32 watth_type = 2; 223 | repeated uint32 watth = 3; 224 | } 225 | message EnergyTotalReport { 226 | optional uint32 watth_seq = 1; 227 | optional EnergyItem watth_item = 2; 228 | } 229 | message BatchEnergyTotalReport { 230 | optional uint32 watth_seq = 1; 231 | repeated EnergyItem watth_item = 2; 232 | } 233 | message EnergyTotalReportAck { 234 | optional uint32 result = 1; 235 | optional uint32 watth_seq = 2; 236 | optional uint32 watth_type = 3; 237 | } 238 | message EventRecordItem { 239 | optional uint32 timestamp = 1; 240 | optional uint32 sys_ms = 2; 241 | optional uint32 event_no = 3; 242 | repeated float event_detail = 4; 243 | } 244 | message EventRecordReport { 245 | optional uint32 event_ver = 1; 246 | optional uint32 event_seq = 2; 247 | repeated EventRecordItem event_item = 3; 248 | } 249 | message EventInfoReportAck { 250 | optional uint32 result = 1; 251 | optional uint32 event_seq = 2; 252 | optional uint32 event_item_num = 3; 253 | } 254 | message ProductNameSet { 255 | optional string name = 1; 256 | } 257 | message ProductNameSetAck { 258 | optional uint32 result = 1; 259 | } 260 | message ProductNameGet {} 261 | message ProductNameGetAck { 262 | optional string name = 3; 263 | } 264 | message RTCTimeGet {} 265 | message RTCTimeGetAck { 266 | optional uint32 timestamp = 1; 267 | optional int32 timezone = 2; 268 | } 269 | message RTCTimeSet { 270 | optional uint32 timestamp = 1; 271 | optional int32 timezone = 2 [(nanopb).default = 0]; 272 | } 273 | message RTCTimeSetAck { 274 | optional uint32 result = 1; 275 | } 276 | message country_town_message { 277 | optional uint32 country = 1; 278 | optional uint32 town = 2; 279 | } 280 | message time_task_config { 281 | optional uint32 task_index = 1; 282 | optional time_range_strategy time_range = 2; 283 | optional uint32 type = 3; 284 | } 285 | message time_task_delet { 286 | optional uint32 task_index = 1; 287 | } 288 | message time_task_config_post { 289 | optional time_task_config task1 = 1; 290 | optional time_task_config task2 = 2; 291 | optional time_task_config task3 = 3; 292 | optional time_task_config task4 = 4; 293 | optional time_task_config task5 = 5; 294 | optional time_task_config task6 = 6; 295 | optional time_task_config task7 = 7; 296 | optional time_task_config task8 = 8; 297 | optional time_task_config task9 = 9; 298 | optional time_task_config task10 = 10; 299 | optional time_task_config task11 = 11; 300 | } 301 | message time_task_config_ack { 302 | optional uint32 task_info = 1; 303 | } 304 | message rtc_data { 305 | optional int32 week = 1 [(nanopb).default = 0]; 306 | optional int32 sec = 2 [(nanopb).default = 0]; 307 | optional int32 min = 3 [(nanopb).default = 0]; 308 | optional int32 hour = 4 [(nanopb).default = 0]; 309 | optional int32 day = 5 [(nanopb).default = 0]; 310 | optional int32 month = 6 [(nanopb).default = 0]; 311 | optional int32 year = 7 [(nanopb).default = 0]; 312 | } 313 | message time_range_strategy { 314 | optional bool is_config = 1; 315 | optional bool is_enable = 2; 316 | optional int32 time_mode = 3; 317 | optional int32 time_data = 4; 318 | optional rtc_data start_time = 5; 319 | optional rtc_data stop_time = 6; 320 | } 321 | message plug_ack_message { 322 | optional uint32 ack = 1; 323 | } 324 | message plug_heartbeat_pack { 325 | optional uint32 err_code = 1 [(nanopb).default = 0]; 326 | optional uint32 warn_code = 2 [(nanopb).default = 0]; 327 | optional uint32 country = 3 [(nanopb).default = 0]; 328 | optional uint32 town = 4 [(nanopb).default = 0]; 329 | optional int32 max_cur = 5 [(nanopb).default = 0]; 330 | optional int32 temp = 6 [(nanopb).default = 0]; 331 | optional int32 freq = 7 [(nanopb).default = 0]; 332 | optional int32 current = 8 [(nanopb).default = 0]; 333 | optional int32 volt = 9 [(nanopb).default = 0]; 334 | optional int32 watts = 10 [(nanopb).default = 0]; 335 | optional bool switch = 11 [(nanopb).default = false]; 336 | optional int32 brightness = 12 [(nanopb).default = 0]; 337 | optional int32 max_watts = 13 [(nanopb).default = 0]; 338 | optional int32 heartbeat_frequency = 14 [(nanopb).default = 0]; 339 | optional int32 mesh_enable = 15 [(nanopb).default = 0]; 340 | } 341 | message plug_switch_message { 342 | optional uint32 plug_switch = 1; 343 | } 344 | message brightness_pack { 345 | optional int32 brightness = 1 [(nanopb).default = 0]; 346 | } 347 | message max_cur_pack { 348 | optional int32 max_cur = 1 [(nanopb).default = 0]; 349 | } 350 | message max_watts_pack { 351 | optional int32 max_watts = 1 [(nanopb).default = 0]; 352 | } 353 | message mesh_ctrl_pack { 354 | optional uint32 mesh_enable = 1 [(nanopb).default = 0]; 355 | } 356 | message ret_pack { 357 | optional bool ret_sta = 1 [(nanopb).default = false]; 358 | } 359 | enum CmdFunction { 360 | Unknown = 0; 361 | PermanentWattsPack = 129; 362 | SupplyPriorityPack = 130; 363 | } 364 | `; 365 | 366 | const musterSetAC = { 367 | header: { 368 | pdata: { 369 | value: 1300, 370 | }, 371 | src: 32, 372 | dest: 53, 373 | dSrc: 1, 374 | dDest: 1, 375 | checkType: 3, 376 | cmdFunc: 20, 377 | cmdId: 129, 378 | dataLen: 3, 379 | needAck: 1, 380 | seq: 1651831507, 381 | version: 19, 382 | payloadVer: 1, 383 | from: 'ios', 384 | deviceSn: 'ABCxxxxxxx123' 385 | } 386 | }; 387 | 388 | const musterDELTA2MAX = { 389 | "params": 390 | { 391 | }, 392 | "from": "iOS", 393 | "lang": "en-en", 394 | "id": "873106536", 395 | "moduleSn": "XXXXXXXXXXXXXXXX", 396 | "moduleType": 1, 397 | "operateType": "quietCfg", 398 | "version": "1.0" 399 | } 400 | 401 | const writeables = [ 402 | //PowerStream 403 | { id: 1, name: 'InverterHeartbeat', Typ: 'PS', Templet: 'InverterHeartbeat', writeable: false, cmdFunc: 20 }, 404 | { id: 4, name: 'InverterHeartbeat2', Typ: 'PS', Templet: 'InverterHeartbeat2', writeable: false, cmdFunc: 20 }, 405 | { id: 11, name: 'Ping', Typ: 'PS', Templet: 'setValue', writeable: false, cmdFunc: 32 }, 406 | { id: 32, name: 'PowerPack_32', Typ: 'PS', Templet: 'PowerPack32', writeable: false, Ignor: true, cmdFunc: 254 }, 407 | { id: 134, name: 'Ignor', Typ: 'PS', Templet: '', writeable: false, Ignor: true, cmdFunc: 20 }, 408 | { id: 135, name: 'SetDisplayBrightness', Typ: 'PS', Templet: 'setValue', writeable: true, ValueName: 'value', Ignor: false, cmdFunc: 20 }, 409 | { id: 136, name: 'PowerPack_136', Typ: 'PS', Templet: 'PowerPack136', writeable: false, cmdFunc: 20 }, 410 | { id: 138, name: 'PowerPack_138', Typ: 'PS', Templet: 'PowerPack138', writeable: false, cmdFunc: 20 }, 411 | { id: 130, name: 'SetPrio', Typ: 'PS', Templet: 'setValue', writeable: true, ValueName: 'value', cmdFunc: 20 }, 412 | { id: 132, name: 'SetBatLimitLow', Typ: 'PS', Templet: 'setValue', writeable: true, ValueName: 'value', cmdFunc: 20 }, 413 | { id: 133, name: 'SetBatLimitHigh', Typ: 'PS', Templet: 'setValue', writeable: true, ValueName: 'value', cmdFunc: 20 }, 414 | { id: 129, name: 'SetAC', Typ: 'PS', Templet: 'setValue', writeable: true, ValueName: 'value', cmdFunc: 20 }, 415 | //Delta Max 416 | { id: 38, name: 'Beep', ValueName: 'enabled', Typ: 'DM' }, 417 | { id: 69, name: 'slowChgPower', ValueName: 'slowChgPower', Typ: 'DM' }, 418 | { id: 66, name: 'ACPower', ValueName: 'enabled', Typ: 'DM' }, 419 | { id: 66, name: 'xboost', ValueName: 'xboost', Typ: 'DM' }, 420 | { id: 81, name: 'DCPower', ValueName: 'enabled', Typ: 'DM' }, 421 | { id: 34, name: 'USBPower', ValueName: 'enabled', Typ: 'DM' }, 422 | { id: 51, name: 'minDsgSoc', ValueName: 'minDsgSoc', Typ: 'DM' }, 423 | { id: 49, name: 'maxChgSoc', ValueName: 'maxChgSoc', Typ: 'DM' }, 424 | { id: 71, name: 'curr12VMax', ValueName: 'currMa', Typ: 'DM' }, 425 | { id: 33, name: 'standByModeMins', ValueName: 'standByMode', Typ: 'DM' }, 426 | { id: 49, name: 'lcdTimeMins', ValueName: 'lcdTime', Typ: 'DM' }, 427 | { id: 153, name: 'ACstandByMins', ValueName: 'standByMins', Typ: 'DM' }, 428 | { id: 52, name: 'openOilSoc', ValueName: 'openOilSoc', Typ: 'DM' }, 429 | { id: 53, name: 'closeOilSoc', ValueName: 'closeOilSoc', Typ: 'DM' }, 430 | //Delta 2 431 | { id: 0, name: 'acChgCfg_D2', ValueName: 'chgWatts', Typ: 'D2', MT: 5, AddParam: '{"chgWatts":600,"chgPauseFlag":255}' }, 432 | { id: 0, name: 'acOutCfg_D2', ValueName: 'enabled', Typ: 'D2', MT: 3 }, 433 | { id: 0, name: 'dcOutCfg_D2', ValueName: 'enabled', Typ: 'D2', MT: 1 }, 434 | { id: 0, name: 'quietMode_D2', ValueName: 'enabled', Typ: 'D2', MT: 5 }, 435 | { id: 0, name: 'dcChgCfg_D2', ValueName: 'dcChgCfg', Typ: 'D2', MT: 5 }, 436 | //Delta 2 Max 437 | { id: 0, name: 'quietCfg', ValueName: 'enabled', Typ: 'D2M', MT: 1, OT: 'quietCfg' }, // 0 = Piepen ein, 1 = Piepen aus 438 | { id: 0, name: 'xboost', ValueName: 'xboost', Typ: 'D2M', MT: 3, OT: 'acOutCfg', AddParam: '{"enabled":255,"out_freq":255,"out_voltage":4294967295,"xboost":0}' }, // 0 ist aus und 1 ist ein (default = 255) 439 | { id: 0, name: 'ACenabled', ValueName: 'enabled', Typ: 'D2M', MT: 3, OT: 'acOutCfg', AddParam: '{"enabled":0,"out_freq":255,"out_voltage":4294967295,"xboost":255}' }, // 0 ist aus und 1 ist ein (default = 255) 440 | // xboost und ACenabled funktionieren zwar, aber beim Setzen in der App des einen Parameters wird der andere Parameter in iobroker auf 255 gesetzt, was aber keine Auswirkungen hat 441 | // 442 | { id: 0, name: 'maxChgSoc', ValueName: 'maxChgSoc', Typ: 'D2M', MT: 2, OT: 'upsConfig' }, // Ladegrenzwert -> dasselbe wie SetBatLimitHigh im PS 443 | { id: 0, name: 'minDsgSoc', ValueName: 'minDsgSoc', Typ: 'D2M', MT: 2, OT: 'dsgCfg' }, // Entladegrenzwert -> dasselbe wie SetBatLimitLow im PS 444 | // 445 | { id: 0, name: 'bpPowerSoc', ValueName: 'bpPowerSoc', Typ: 'D2M', MT: 1, OT: 'watthConfig', AddParam: '{"bpPowerSoc":12,"minChgSoc":0,"isConfig":0,"minDsgSoc":0}' }, // Backup-Reserve Sicherung in % 446 | { id: 0, name: 'bpPowerEnable', ValueName: 'isConfig', Typ: 'D2M', MT: 1, OT: 'watthConfig', AddParam: '{"bpPowerSoc":12,"minChgSoc":0,"isConfig":0,"minDsgSoc":0}' }, // Enable Backup-Reserve 447 | // 448 | { id: 0, name: 'slowChgWatts', ValueName: 'slowChgWatts', Typ: 'D2M', MT: 3, OT: 'acChgCfg', AddParam: '{"fastChgWatts":200, "slowChgWatts":255,"chgPauseFlag":0}' }, // Objekt angelegt, schreibbar 449 | { id: 0, name: 'chgPauseFlag', ValueName: 'chgPauseFlag', Typ: 'D2M', MT: 3, OT: 'acChgCfg', AddParam: '{"fastChgWatts":200, "slowChgWatts":255,"chgPauseFlag":0}' }, // TODO: chgPauseFlag testen, ob dann die Ladung pausiert 450 | // 451 | { id: 0, name: 'dcChgCfg', ValueName: 'dcChgCfg', Typ: 'D2M', MT: 5, OT: 'dcChgCfg', AddParam: '{"dcChgCfg":0, "dcChgCfg2":0}' }, // Objekt angelegt, schreibbar, aber beide Eingänge werden gleichzeitig mit dem selben Wert geändert 452 | { id: 0, name: 'dcChgCfg2', ValueName: 'dcChgCfg2', Typ: 'D2M', MT: 5, OT: 'dcChgCfg', AddParam: '{"dcChgCfg":0, "dcChgCfg2":0}' }, // Hier genauso und die Werte sind 8000 (8A), 6000 (6A) und 4000 (4A) 453 | // 454 | { id: 0, name: 'USB', ValueName: 'enabled', Typ: 'D2M', MT: 1, OT: 'dcOutCfg' }, // USB ein/aus 455 | { id: 0, name: '12VDC', ValueName: 'enabled', Typ: 'D2M', MT: 5, OT: 'mpptCar' }, // 12V Auto ein/aus 456 | { id: 0, name: 'smartgenClose', ValueName: 'closeOilSoc', Typ: 'D2M', MT: 2, OT: 'closeOilSoc' }, // Automatisches Einschalten des Smart Generators in % 457 | { id: 0, name: 'smartgenOpen', ValueName: 'openOilSoc', Typ: 'D2M', MT: 2, OT: 'openOilSoc' }, // Automatisches Ausschalten des Smart Generators in % 458 | { id: 0, name: 'standbyTime', ValueName: 'standbyMin', Typ: 'D2M', MT: 1, OT: 'standbyTime' }, // Geräte-Zeitüberschreitung (in min) 0 => Nie 459 | { id: 0, name: 'lcdTime', ValueName: 'delayOff', Typ: 'D2M', MT: 1, OT: 'lcdCfg', AddParam: '{"brighLevel":255}' }, // Bildschirm-Zeitüberschreitung (in Sekunden) 0 => Nie 460 | { id: 0, name: 'setRtcTime', Ignor: true, ValueName: 'sec', Typ: 'D2M', MT: 2, OT: 'setRtcTime', AddParam: '{"sec":4,"min": 13, "week": 3, "month": 9,"hour": 11,"year": 2023, "day":6}' }, // Ignorieren 461 | // Smart Plugs 462 | { id: 2, name: 'PowerPack_2', Typ: 'SM', Templet: 'PowerPack2', writeable: false, ValueName: '', Ignor: false, cmdFunc: 2 }, 463 | { id: 11, name: 'Ping', Typ: 'SM', Templet: 'setValue', writeable: false, cmdFunc: 32 }, 464 | { id: 32, name: 'PowerPack_32', Typ: 'SM', Templet: 'PowerPack32', writeable: false, ValueName: '', Ignor: false, cmdFunc: 254 }, 465 | { id: 133, name: 'PowerPack_133', Typ: 'SM', Templet: 'PowerPack133', writeable: false, ValueName: '', Ignor: true, cmdFunc: 2 }, 466 | { id: 135, name: 'PowerPack_135', Typ: 'SM', Templet: 'PowerPack135', writeable: false, ValueName: '', Ignor: true, cmdFunc: 2 }, 467 | { id: 1, name: 'plug_heartbeat_pack', Typ: 'SM', Templet: 'plug_heartbeat_pack', writeable: false, cmdFunc: 2 }, 468 | { id: 129, name: 'SwitchPlug', Typ: 'SM', Templet: 'setValue', writeable: true, ValueName: 'value', cmdFunc: 2 }, 469 | ]; 470 | 471 | module.exports = { 472 | protoSource2, 473 | musterSetAC, 474 | writeables, 475 | musterDELTA2MAX 476 | } -------------------------------------------------------------------------------- /ecoflow.js: -------------------------------------------------------------------------------- 1 | var https = require('https'); 2 | var protobuf=require("protobufjs"); 3 | var mqtt=require("mqtt"); 4 | const {log} = require("./utils"); 5 | const {protoSource2, musterSetAC, writeables} = require ("./const.js"); 6 | 7 | function httpsRequest(options, data) { 8 | return new Promise((resolve, reject) => { 9 | const req = https.request(options, res => { 10 | let data = ''; 11 | res.on('data', chunk => { 12 | data += chunk; 13 | }); 14 | res.on('end', () => { 15 | resolve(data); 16 | }); 17 | }); 18 | 19 | req.on('error', error => { 20 | log("ERROR https request",error); 21 | reject(error); 22 | }); 23 | 24 | if (data) { 25 | req.write(JSON.stringify(data)); 26 | } 27 | 28 | req.end(); 29 | }); 30 | } 31 | 32 | async function getEcoFlowMqttData(email, password) { 33 | const options = { 34 | hostname: 'api.ecoflow.com', 35 | path: '/auth/login', 36 | method: 'POST', 37 | rejectUnauthorized: false, 38 | headers: { 39 | 'Host': 'api.ecoflow.com', 40 | 'lang': 'de-de', 41 | 'platform': 'android', 42 | 'sysversion': '11', 43 | 'version': '4.1.2.02', 44 | 'phonemodel': 'SM-X200', 45 | 'content-type': 'application/json', 46 | 'user-agent': 'okhttp/3.14.9' 47 | } 48 | }; 49 | 50 | const mqttDaten = {}; 51 | const data = { 52 | appVersion: "4.1.2.02", 53 | email: email, 54 | os: "android", 55 | osVersion: "30", 56 | password: Buffer.from(password).toString('base64'), 57 | scene: "IOT_APP", 58 | userType: "ECOFLOW" 59 | }; 60 | 61 | function uuidv4() { 62 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { 63 | var r = Math.random() * 16 | 0, 64 | v = c === 'x' ? r : (r & 0x3 | 0x8); 65 | return v.toString(16); 66 | }); 67 | } 68 | 69 | let token, userid; 70 | try { 71 | let response = await httpsRequest(options, data); 72 | let responseData = JSON.parse(response); 73 | token = responseData.data.token; 74 | userid = responseData.data.user.userId; 75 | } catch (error) { 76 | //throw new Error("Une erreur s'est produite:",error); 77 | log("ERROR to connect Ecoflow MQTT broker", response); 78 | return null; 79 | } 80 | 81 | if (!token) return "ERROR"; 82 | options.path = `/iot-auth/app/certification?userId=${userid}`; 83 | options.method = 'GET'; 84 | options.headers.authorization = `Bearer ${token}`; 85 | try { 86 | let response = await httpsRequest(options); 87 | response = JSON.parse(response); 88 | mqttDaten.Passwort = response.data.certificatePassword; 89 | mqttDaten.Port = response.data.port; 90 | mqttDaten.UserID = userid; 91 | mqttDaten.User = response.data.certificateAccount; 92 | mqttDaten.URL = response.data.url 93 | mqttDaten.protocol = response.data.protocol; 94 | mqttDaten.clientID = "ANDROID_" + uuidv4() + "_" + userid 95 | } catch (error) { 96 | log("ERROR An error has occurred while determining the access data. Please check the access data", mqttDaten); 97 | return null; 98 | //throw new Error("Une erreur s'est produite lors de la détermination des données d'accès. Veuillez vérifier les données d'accès."); 99 | } 100 | return mqttDaten; 101 | } 102 | 103 | //################ MQTT Verbindung ################## 104 | async function setupMQTTConnection(mqttDaten) { 105 | //console.log("mqttDaten",mqttDaten); 106 | //console.log("Nouvelle connexion MQTT : " + mqttDaten) 107 | // Verbindung herstellen 108 | const options = { 109 | port: mqttDaten.Port, 110 | clientId: mqttDaten.clientID, 111 | username: mqttDaten.User, 112 | password: mqttDaten.Passwort, 113 | protocol: mqttDaten.protocol, 114 | rejectUnauthorized: false, 115 | }; 116 | const client = await mqtt.connect("mqtt://" + mqttDaten.URL, options); 117 | // Event-Handler für Verbindungsaufbau 118 | client.mqttDaten = mqttDaten; 119 | // Event-Handler für getrennte Verbindung 120 | client.on('close', () => { 121 | log("Ecoflow MQTT broker is deconnected"); 122 | //console.log("Le client MQTT est déconnecté"); 123 | //isMqttConnected = false; 124 | }); 125 | 126 | // Callback für Fehler 127 | client.on('error', function (error) { 128 | log('Error with the Ecoflow MQTT broker connection ' + error); 129 | }); 130 | 131 | 132 | client.on('reconnect', function () { 133 | log('Reconnecting to Ecoflow MQTT broker...',mqttDaten.URL, mqttDaten.Port, mqttDaten.protocol); 134 | // don't need to reconnect 135 | client.end(); 136 | }); 137 | 138 | client.on('message', (topic, payload) => { 139 | log('Received Message to Ecoflow MQTT broker', topic, payload.toString()); 140 | }); 141 | 142 | // Weitere Event-Handler hier... 143 | return client; 144 | } 145 | 146 | function setAC(client,asn, Value) { 147 | log("set Ac => " + Value/10 + " Watts"); 148 | let updatedMusterSetAC = musterSetAC; 149 | if (Value <= -1) { 150 | delete updatedMusterSetAC.item.meta; 151 | delete updatedMusterSetAC.item.ValByte; 152 | } 153 | else { 154 | updatedMusterSetAC.header.pdata.value = Value 155 | updatedMusterSetAC.header.dataLen = getVarintByteSize(Value) 156 | } 157 | updatedMusterSetAC.header.seq = Date.now() 158 | updatedMusterSetAC.header.deviceSn = asn 159 | //log(JSON.stringify(updatedMusterSetAC)) 160 | //setState(ConfigData.statesPrefix + '.app_' + mqttDaten.UserID + '_' + asn + '_thing_property_set.setAC', Value.toString(), true) 161 | log('send SetAc to Ecoflow MQTT broker for ' + asn); 162 | SendProto(client,JSON.stringify(updatedMusterSetAC), '/app/' + client.mqttDaten.UserID + '/' + asn + '/thing/property/set'); 163 | } 164 | 165 | //setStateNE(ConfigData.statesPrefix + '.app_' + mqttDaten.UserID + '_' + asn + '_thing_property_set.writeables.SetPrio', "1", false) 166 | //{ id: 130, name: 'SetPrio', Typ: 'PS', Templet: 'setValue', Writable: true, ValueName: 'value', cmdFunc: 20 }, 167 | function setPrio(_client, _asn, _value) { 168 | const lastPart = "SetPrio"; 169 | const matchedEntry = writeables.find((entry) => entry.name === lastPart); 170 | log("setPrio => "+_value); 171 | if (matchedEntry) { 172 | if (matchedEntry.Typ == "PS") { 173 | updatedMuster = JSON.parse(JSON.stringify(musterSetAC)); 174 | if (Number(_value) <= -1) { 175 | delete updatedMuster.item.meta; 176 | delete updatedMuster.item.ValByte; 177 | } 178 | else { 179 | updatedMuster.header.pdata[matchedEntry.ValueName] = Number(_value) 180 | updatedMuster.header.dataLen = getVarintByteSize(Number(_value)) 181 | } 182 | updatedMuster.header.cmdId = matchedEntry.id 183 | updatedMuster.header.cmdFunc = matchedEntry.cmdFunc || 20 184 | updatedMuster.header.seq = Date.now() 185 | updatedMuster.header.deviceSn = _asn 186 | //log(JSON.stringify(updatedMuster)) 187 | log('send SetPrio to Ecoflow MQTT broker for ' + _asn); 188 | SendProto(_client, JSON.stringify(updatedMuster), '/app/' + _client.mqttDaten.UserID + '/' + _asn + '/thing/property/set'); 189 | } 190 | } 191 | } 192 | 193 | function DM2() { 194 | const lastPart = "SetPrio"; 195 | const matchedEntry = writeables.find((entry) => entry.name === lastPart); 196 | if (matchedEntry.Typ == "D2M") { 197 | updatedMuster = JSON.parse(JSON.stringify(musterDELTA2MAX));; 198 | updatedMuster.id = Date.now().toString() 199 | updatedMuster.moduleSn = asn 200 | updatedMuster.moduleType = Number(matchedEntry.MT) 201 | updatedMuster.operateType = matchedEntry.OT 202 | 203 | if (matchedEntry.AddParam) { 204 | updatedMuster.params = JSON.parse(matchedEntry.AddParam); 205 | let suchwriteables = writeables.filter((item) => item.OT === matchedEntry.OT && item.Typ == matchedEntry.Typ && updatedMuster.params.hasOwnProperty(item.ValueName) && item.name != matchedEntry.name); 206 | if (suchwriteables.length > 0) { 207 | suchwriteables.forEach((suchwriteable) => { 208 | const addval = getStateCr(obj.id.replace(lastPart, suchwriteable.name), updatedMuster.params[suchwriteable.ValueName]).val 209 | //log("Adparam: " + suchwriteable.name + " Wert aus State:" + addval + " initvalue:" + updatedMuster.params[suchwriteable.ValueName]) 210 | updatedMuster.params[suchwriteable.ValueName] = addval 211 | }) 212 | } 213 | } 214 | //updatedMuster.params.chgPauseFlag = 255 215 | updatedMuster.params[matchedEntry.ValueName] = Number(obj.state.val) 216 | SendJSON(JSON.stringify(updatedMuster), '/app/' + mqttDaten.UserID + '/' + asn + '/thing/property/set'); 217 | } 218 | } 219 | 220 | function SendJSON(protomsg, topic) { 221 | //log("topic:" + topic); 222 | client.publish(topic, protomsg, { qos: 1 }, function (error) { 223 | if (error) { 224 | console.error('Error while publishing MQTT:', error); 225 | } else { 226 | if (ConfigData.Debug) log('Die MQTT-Nachricht wurde erfolgreich veröffentlicht.'); // 227 | } 228 | }); 229 | } 230 | 231 | function SendProto(client, protomsg, topic) { 232 | //return 233 | const root = protobuf.parse(protoSource2).root; 234 | const PowerMessage = root.lookupType("setMessage"); 235 | const message = PowerMessage.create(JSON.parse(protomsg)); 236 | const messageBuffer = PowerMessage.encode(message).finish(); 237 | //console.log("protomsg",protomsg); 238 | //console.log("message",message); 239 | 240 | //console.log("messageBuffer",messageBuffer.toString()); 241 | //log("Modifizierter Hex-String:" + Buffer.from(messageBuffer).toString("hex")); 242 | //log("topic:" + topic); 243 | client.publish(topic, messageBuffer, { qos: 1 }, function (error) { 244 | if (error) { 245 | log('Error when publishing the Ecoflow MQTT message:', error); 246 | } else { 247 | log('The MQTT message has been successfully published'); 248 | } 249 | }); 250 | } 251 | 252 | function getVarintByteSize(number) { 253 | let byteSize = 0; 254 | while (number >= 128) { 255 | byteSize++; 256 | number >>= 7; // Rechtsschiebeoperation um 7 Bits 257 | } 258 | byteSize++; byteSize++; 259 | return byteSize; 260 | } 261 | 262 | module.exports = { 263 | getEcoFlowMqttData, 264 | setupMQTTConnection, 265 | setAC, 266 | setPrio 267 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | Hi, hello ! -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ecoflow_node", 3 | "lockfileVersion": 2, 4 | "requires": true, 5 | "packages": {} 6 | } 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ecoflow-nodejs", 3 | "scripts": { 4 | "start": "node server.js" 5 | }, 6 | "dependencies": { 7 | "dotenv": "^16.3.1", 8 | "express": "^4.18.2", 9 | "https": "^1.0.0", 10 | "mqtt": "^5.0.3", 11 | "protobufjs": "^7.2.4" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | 2 | var path = require('path'); 3 | var express = require ('express'); 4 | var dotenv = require ('dotenv'); 5 | dotenv.config(); 6 | const port = 8000; 7 | const key = "/?"+process.env.KEY_QUERY+"=" 8 | const app = express(); 9 | const url = process.env.KEY_URL; 10 | const {log} = require("./utils"); 11 | let mqttDaten = {}; 12 | 13 | app.get('/'+url, (req, res) => { 14 | if (process.env.TOKEN && req.query[process.env.TOKEN] && (req.query[process.env.TOKEN] = process.env.TOKEN_VAL)) { 15 | let v = req.query[process.env.KEY_QUERY_AC]; 16 | let v2 = req.query[process.env.KEY_QUERY_PRIO]; 17 | let v3 = req.query[process.env.TARGET]; 18 | //console.log(v,v2); 19 | if (process.env.KEY_PASSWORD && process.env.KEY_MAIL && process.env.KEY_POWERSTREAM_SN && ( v || v2)) { 20 | //changeWatt(v*1, process.env.KEY_PASSWORD, process.env.KEY_MAIL, process.env.KEY_POWERSTREAM_SN); 21 | if (req.query[process.env.KEY_QUERY_AC] || req.query[process.env.KEY_QUERY_PRIO]) { 22 | const {getEcoFlowMqttData, setupMQTTConnection, setAC, setPrio} = require(path.resolve( __dirname, "./ecoflow.js" ) ); 23 | mqttDaten = getEcoFlowMqttData(process.env.KEY_MAIL, process.env.KEY_PASSWORD) 24 | .then(mqttDaten => { 25 | if (mqttDaten) { 26 | log('recevied datas from Ecoflow MQTT broker', mqttDaten) 27 | setupMQTTConnection(mqttDaten) 28 | .then (client => { 29 | client.on('connect', function () { 30 | log('connected to Ecoflow MQTT broker') 31 | //console.log('Connecté au courtier Ecoflow MQTT'); 32 | client.subscribe(['#'], () => { 33 | log('Subscribe to Ecoflow MQTT topic #') 34 | }) 35 | 36 | if (v && v*1>=0) { 37 | if (v3==2) { 38 | setAC(client, process.env.KEY_POWERSTREAM_SN2,v*10); 39 | } 40 | else { 41 | setAC(client, process.env.KEY_POWERSTREAM_SN,v*10); 42 | } 43 | } 44 | else { 45 | log(process.env.KEY_QUERY_AC + ' must be grater than 0') 46 | } 47 | if (v2 && (v2*1===0 || v2*1===1)) { 48 | if (v3==2) { 49 | setPrio(client, process.env.KEY_POWERSTREAM_SN2,v2); 50 | } 51 | else { 52 | setPrio(client, process.env.KEY_POWERSTREAM_SN,v2); 53 | } 54 | } 55 | else { 56 | log(process.env.KEY_POWERSTREAM_SN + ' must be 0 or 1') 57 | } 58 | setTimeout(() => { 59 | log('disconnect to Ecoflow MQTT broker') 60 | client.end(); 61 | }, "3000"); 62 | //isMqttConnected = true 63 | }) 64 | }) 65 | .catch(); 66 | } 67 | else { 68 | log('not connected to Ecoflow MQTT broker') 69 | res.send('not connected to Ecoflow MQTT broker'); 70 | } 71 | 72 | }) 73 | .catch(); 74 | } 75 | else { 76 | log(process.env.KEY_QUERY_AC + ' or ' + process.env.KEY_QUERY_AC + ' are mandatory') 77 | res.send(process.env.KEY_QUERY_AC + ' or ' + process.env.KEY_QUERY_AC + ' are mandatory') 78 | } 79 | } 80 | else { 81 | log(process.env.KEY_PASSWORD + ' are mandatory') 82 | res.send(process.env.KEY_PASSWORD + ' are mandatory') 83 | } 84 | } 85 | else { 86 | log(process.env.TOKEN + ' are mandatory') 87 | res.send(process.env.TOKEN + ' are mandatory') 88 | } 89 | }); 90 | 91 | app.use((req, res) => {res.status(404).send('Not found!')}); 92 | 93 | var server = app.listen(port, () => { 94 | var host = server.address().address; 95 | var port = server.address().port; 96 | 97 | log("Starting app listening at port " + port) 98 | }); 99 | -------------------------------------------------------------------------------- /utils.js: -------------------------------------------------------------------------------- 1 | function log() { 2 | var delta = new Date().toLocaleString('fr-FR'); 3 | var message = delta + ': ' + arguments[0]; 4 | 5 | var args = []; 6 | if (arguments.length>1) { 7 | for(var i = 0; i < arguments.length; i++) { 8 | args.push(arguments[i]); 9 | } 10 | console.log(message, args); 11 | } else { 12 | console.log(message); 13 | } 14 | } 15 | 16 | module.exports = { 17 | log 18 | }; -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": 6 | version "1.1.2" 7 | resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" 8 | integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== 9 | 10 | "@protobufjs/base64@^1.1.2": 11 | version "1.1.2" 12 | resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" 13 | integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== 14 | 15 | "@protobufjs/codegen@^2.0.4": 16 | version "2.0.4" 17 | resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" 18 | integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== 19 | 20 | "@protobufjs/eventemitter@^1.1.0": 21 | version "1.1.0" 22 | resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" 23 | integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== 24 | 25 | "@protobufjs/fetch@^1.1.0": 26 | version "1.1.0" 27 | resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" 28 | integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== 29 | dependencies: 30 | "@protobufjs/aspromise" "^1.1.1" 31 | "@protobufjs/inquire" "^1.1.0" 32 | 33 | "@protobufjs/float@^1.0.2": 34 | version "1.0.2" 35 | resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" 36 | integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== 37 | 38 | "@protobufjs/inquire@^1.1.0": 39 | version "1.1.0" 40 | resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" 41 | integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== 42 | 43 | "@protobufjs/path@^1.1.2": 44 | version "1.1.2" 45 | resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" 46 | integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== 47 | 48 | "@protobufjs/pool@^1.1.0": 49 | version "1.1.0" 50 | resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" 51 | integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== 52 | 53 | "@protobufjs/utf8@^1.1.0": 54 | version "1.1.0" 55 | resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" 56 | integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== 57 | 58 | "@types/node@>=13.7.0": 59 | version "20.5.1" 60 | resolved "https://registry.yarnpkg.com/@types/node/-/node-20.5.1.tgz#178d58ee7e4834152b0e8b4d30cbfab578b9bb30" 61 | integrity sha512-4tT2UrL5LBqDwoed9wZ6N3umC4Yhz3W3FloMmiiG4JwmUJWpie0c7lcnUNd4gtMKuDEO4wRVS8B6Xa0uMRsMKg== 62 | 63 | abort-controller@^3.0.0: 64 | version "3.0.0" 65 | resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" 66 | integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== 67 | dependencies: 68 | event-target-shim "^5.0.0" 69 | 70 | accepts@~1.3.8: 71 | version "1.3.8" 72 | resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" 73 | integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== 74 | dependencies: 75 | mime-types "~2.1.34" 76 | negotiator "0.6.3" 77 | 78 | array-flatten@1.1.1: 79 | version "1.1.1" 80 | resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" 81 | integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== 82 | 83 | balanced-match@^1.0.0: 84 | version "1.0.2" 85 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" 86 | integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== 87 | 88 | base64-js@^1.3.1: 89 | version "1.5.1" 90 | resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" 91 | integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== 92 | 93 | bl@^5.0.0: 94 | version "5.1.0" 95 | resolved "https://registry.yarnpkg.com/bl/-/bl-5.1.0.tgz#183715f678c7188ecef9fe475d90209400624273" 96 | integrity sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ== 97 | dependencies: 98 | buffer "^6.0.3" 99 | inherits "^2.0.4" 100 | readable-stream "^3.4.0" 101 | 102 | body-parser@1.20.1: 103 | version "1.20.1" 104 | resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" 105 | integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw== 106 | dependencies: 107 | bytes "3.1.2" 108 | content-type "~1.0.4" 109 | debug "2.6.9" 110 | depd "2.0.0" 111 | destroy "1.2.0" 112 | http-errors "2.0.0" 113 | iconv-lite "0.4.24" 114 | on-finished "2.4.1" 115 | qs "6.11.0" 116 | raw-body "2.5.1" 117 | type-is "~1.6.18" 118 | unpipe "1.0.0" 119 | 120 | brace-expansion@^2.0.1: 121 | version "2.0.1" 122 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" 123 | integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== 124 | dependencies: 125 | balanced-match "^1.0.0" 126 | 127 | buffer-from@^1.0.0: 128 | version "1.1.2" 129 | resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" 130 | integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== 131 | 132 | buffer@^6.0.3: 133 | version "6.0.3" 134 | resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" 135 | integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== 136 | dependencies: 137 | base64-js "^1.3.1" 138 | ieee754 "^1.2.1" 139 | 140 | bytes@3.1.2: 141 | version "3.1.2" 142 | resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" 143 | integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== 144 | 145 | call-bind@^1.0.0: 146 | version "1.0.2" 147 | resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" 148 | integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== 149 | dependencies: 150 | function-bind "^1.1.1" 151 | get-intrinsic "^1.0.2" 152 | 153 | commist@^3.2.0: 154 | version "3.2.0" 155 | resolved "https://registry.yarnpkg.com/commist/-/commist-3.2.0.tgz#da9c8e5f245ac21510badc4b10c46b5bcc9b56cd" 156 | integrity sha512-4PIMoPniho+LqXmpS5d3NuGYncG6XWlkBSVGiWycL22dd42OYdUGil2CWuzklaJoNxyxUSpO4MKIBU94viWNAw== 157 | 158 | concat-stream@^2.0.0: 159 | version "2.0.0" 160 | resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-2.0.0.tgz#414cf5af790a48c60ab9be4527d56d5e41133cb1" 161 | integrity sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A== 162 | dependencies: 163 | buffer-from "^1.0.0" 164 | inherits "^2.0.3" 165 | readable-stream "^3.0.2" 166 | typedarray "^0.0.6" 167 | 168 | content-disposition@0.5.4: 169 | version "0.5.4" 170 | resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" 171 | integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== 172 | dependencies: 173 | safe-buffer "5.2.1" 174 | 175 | content-type@~1.0.4: 176 | version "1.0.5" 177 | resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" 178 | integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== 179 | 180 | cookie-signature@1.0.6: 181 | version "1.0.6" 182 | resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" 183 | integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== 184 | 185 | cookie@0.5.0: 186 | version "0.5.0" 187 | resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" 188 | integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== 189 | 190 | debug@2.6.9: 191 | version "2.6.9" 192 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" 193 | integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== 194 | dependencies: 195 | ms "2.0.0" 196 | 197 | debug@^4.1.1, debug@^4.3.1, debug@^4.3.4: 198 | version "4.3.4" 199 | resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" 200 | integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== 201 | dependencies: 202 | ms "2.1.2" 203 | 204 | depd@2.0.0: 205 | version "2.0.0" 206 | resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" 207 | integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== 208 | 209 | destroy@1.2.0: 210 | version "1.2.0" 211 | resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" 212 | integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== 213 | 214 | dotenv@^16.3.1: 215 | version "16.3.1" 216 | resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e" 217 | integrity sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ== 218 | 219 | duplexify@^4.1.2: 220 | version "4.1.2" 221 | resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-4.1.2.tgz#18b4f8d28289132fa0b9573c898d9f903f81c7b0" 222 | integrity sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw== 223 | dependencies: 224 | end-of-stream "^1.4.1" 225 | inherits "^2.0.3" 226 | readable-stream "^3.1.1" 227 | stream-shift "^1.0.0" 228 | 229 | ee-first@1.1.1: 230 | version "1.1.1" 231 | resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" 232 | integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== 233 | 234 | encodeurl@~1.0.2: 235 | version "1.0.2" 236 | resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" 237 | integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== 238 | 239 | end-of-stream@^1.4.1: 240 | version "1.4.4" 241 | resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" 242 | integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== 243 | dependencies: 244 | once "^1.4.0" 245 | 246 | escape-html@~1.0.3: 247 | version "1.0.3" 248 | resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" 249 | integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== 250 | 251 | etag@~1.8.1: 252 | version "1.8.1" 253 | resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" 254 | integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== 255 | 256 | event-target-shim@^5.0.0: 257 | version "5.0.1" 258 | resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" 259 | integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== 260 | 261 | events@^3.3.0: 262 | version "3.3.0" 263 | resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" 264 | integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== 265 | 266 | express@^4.18.2: 267 | version "4.18.2" 268 | resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" 269 | integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ== 270 | dependencies: 271 | accepts "~1.3.8" 272 | array-flatten "1.1.1" 273 | body-parser "1.20.1" 274 | content-disposition "0.5.4" 275 | content-type "~1.0.4" 276 | cookie "0.5.0" 277 | cookie-signature "1.0.6" 278 | debug "2.6.9" 279 | depd "2.0.0" 280 | encodeurl "~1.0.2" 281 | escape-html "~1.0.3" 282 | etag "~1.8.1" 283 | finalhandler "1.2.0" 284 | fresh "0.5.2" 285 | http-errors "2.0.0" 286 | merge-descriptors "1.0.1" 287 | methods "~1.1.2" 288 | on-finished "2.4.1" 289 | parseurl "~1.3.3" 290 | path-to-regexp "0.1.7" 291 | proxy-addr "~2.0.7" 292 | qs "6.11.0" 293 | range-parser "~1.2.1" 294 | safe-buffer "5.2.1" 295 | send "0.18.0" 296 | serve-static "1.15.0" 297 | setprototypeof "1.2.0" 298 | statuses "2.0.1" 299 | type-is "~1.6.18" 300 | utils-merge "1.0.1" 301 | vary "~1.1.2" 302 | 303 | finalhandler@1.2.0: 304 | version "1.2.0" 305 | resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" 306 | integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== 307 | dependencies: 308 | debug "2.6.9" 309 | encodeurl "~1.0.2" 310 | escape-html "~1.0.3" 311 | on-finished "2.4.1" 312 | parseurl "~1.3.3" 313 | statuses "2.0.1" 314 | unpipe "~1.0.0" 315 | 316 | forwarded@0.2.0: 317 | version "0.2.0" 318 | resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" 319 | integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== 320 | 321 | fresh@0.5.2: 322 | version "0.5.2" 323 | resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" 324 | integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== 325 | 326 | fs.realpath@^1.0.0: 327 | version "1.0.0" 328 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 329 | integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== 330 | 331 | function-bind@^1.1.1: 332 | version "1.1.1" 333 | resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" 334 | integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== 335 | 336 | get-intrinsic@^1.0.2: 337 | version "1.2.1" 338 | resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82" 339 | integrity sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw== 340 | dependencies: 341 | function-bind "^1.1.1" 342 | has "^1.0.3" 343 | has-proto "^1.0.1" 344 | has-symbols "^1.0.3" 345 | 346 | glob@^8.0.0: 347 | version "8.1.0" 348 | resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" 349 | integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== 350 | dependencies: 351 | fs.realpath "^1.0.0" 352 | inflight "^1.0.4" 353 | inherits "2" 354 | minimatch "^5.0.1" 355 | once "^1.3.0" 356 | 357 | has-proto@^1.0.1: 358 | version "1.0.1" 359 | resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" 360 | integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== 361 | 362 | has-symbols@^1.0.3: 363 | version "1.0.3" 364 | resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" 365 | integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== 366 | 367 | has@^1.0.3: 368 | version "1.0.3" 369 | resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" 370 | integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== 371 | dependencies: 372 | function-bind "^1.1.1" 373 | 374 | help-me@^4.2.0: 375 | version "4.2.0" 376 | resolved "https://registry.yarnpkg.com/help-me/-/help-me-4.2.0.tgz#50712bfd799ff1854ae1d312c36eafcea85b0563" 377 | integrity sha512-TAOnTB8Tz5Dw8penUuzHVrKNKlCIbwwbHnXraNJxPwf8LRtE2HlM84RYuezMFcwOJmoYOCWVDyJ8TQGxn9PgxA== 378 | dependencies: 379 | glob "^8.0.0" 380 | readable-stream "^3.6.0" 381 | 382 | http-errors@2.0.0: 383 | version "2.0.0" 384 | resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" 385 | integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== 386 | dependencies: 387 | depd "2.0.0" 388 | inherits "2.0.4" 389 | setprototypeof "1.2.0" 390 | statuses "2.0.1" 391 | toidentifier "1.0.1" 392 | 393 | https@^1.0.0: 394 | version "1.0.0" 395 | resolved "https://registry.yarnpkg.com/https/-/https-1.0.0.tgz#3c37c7ae1a8eeb966904a2ad1e975a194b7ed3a4" 396 | integrity sha512-4EC57ddXrkaF0x83Oj8sM6SLQHAWXw90Skqu2M4AEWENZ3F02dFJE/GARA8igO79tcgYqGrD7ae4f5L3um2lgg== 397 | 398 | iconv-lite@0.4.24: 399 | version "0.4.24" 400 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" 401 | integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== 402 | dependencies: 403 | safer-buffer ">= 2.1.2 < 3" 404 | 405 | ieee754@^1.2.1: 406 | version "1.2.1" 407 | resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" 408 | integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== 409 | 410 | inflight@^1.0.4: 411 | version "1.0.6" 412 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 413 | integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== 414 | dependencies: 415 | once "^1.3.0" 416 | wrappy "1" 417 | 418 | inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4: 419 | version "2.0.4" 420 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 421 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 422 | 423 | ipaddr.js@1.9.1: 424 | version "1.9.1" 425 | resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" 426 | integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== 427 | 428 | js-sdsl@4.3.0: 429 | version "4.3.0" 430 | resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.3.0.tgz#aeefe32a451f7af88425b11fdb5f58c90ae1d711" 431 | integrity sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ== 432 | 433 | long@^5.0.0: 434 | version "5.2.3" 435 | resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1" 436 | integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q== 437 | 438 | lru-cache@^7.18.3: 439 | version "7.18.3" 440 | resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.18.3.tgz#f793896e0fd0e954a59dfdd82f0773808df6aa89" 441 | integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA== 442 | 443 | media-typer@0.3.0: 444 | version "0.3.0" 445 | resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" 446 | integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== 447 | 448 | merge-descriptors@1.0.1: 449 | version "1.0.1" 450 | resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" 451 | integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== 452 | 453 | methods@~1.1.2: 454 | version "1.1.2" 455 | resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" 456 | integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== 457 | 458 | mime-db@1.52.0: 459 | version "1.52.0" 460 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" 461 | integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== 462 | 463 | mime-types@~2.1.24, mime-types@~2.1.34: 464 | version "2.1.35" 465 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" 466 | integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== 467 | dependencies: 468 | mime-db "1.52.0" 469 | 470 | mime@1.6.0: 471 | version "1.6.0" 472 | resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" 473 | integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== 474 | 475 | minimatch@^5.0.1: 476 | version "5.1.6" 477 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" 478 | integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== 479 | dependencies: 480 | brace-expansion "^2.0.1" 481 | 482 | minimist@^1.2.8: 483 | version "1.2.8" 484 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" 485 | integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== 486 | 487 | mqtt-packet@^8.2.0: 488 | version "8.2.0" 489 | resolved "https://registry.yarnpkg.com/mqtt-packet/-/mqtt-packet-8.2.0.tgz#07cfdf72771f2c17c5689183162d1e4ad613438f" 490 | integrity sha512-21Vo7XdRXUw2qhdTfk8GeOl2jtb8Dkwd4dKxn/epvf37mxTxHodvBJoozTPZGVwh57JXlsh2ChsaxMsAfqxp+A== 491 | dependencies: 492 | bl "^5.0.0" 493 | debug "^4.1.1" 494 | process-nextick-args "^2.0.1" 495 | 496 | mqtt@^5.0.3: 497 | version "5.0.3" 498 | resolved "https://registry.yarnpkg.com/mqtt/-/mqtt-5.0.3.tgz#dd64ba92079bb592ba4caa729de8ed243b58a53d" 499 | integrity sha512-XyCzdCNFm4XXBUV7HQPd1qXYdu7GC/H+wXr+RfaztwZ72/c3sD8yRivOBdh8iKWHc+EGawSeDIvXCnvEykcJVA== 500 | dependencies: 501 | commist "^3.2.0" 502 | concat-stream "^2.0.0" 503 | debug "^4.3.4" 504 | duplexify "^4.1.2" 505 | help-me "^4.2.0" 506 | lru-cache "^7.18.3" 507 | minimist "^1.2.8" 508 | mqtt-packet "^8.2.0" 509 | number-allocator "^1.0.14" 510 | readable-stream "^4.4.2" 511 | reinterval "^1.1.0" 512 | rfdc "^1.3.0" 513 | split2 "^4.2.0" 514 | ws "^8.13.0" 515 | 516 | ms@2.0.0: 517 | version "2.0.0" 518 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 519 | integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== 520 | 521 | ms@2.1.2: 522 | version "2.1.2" 523 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 524 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== 525 | 526 | ms@2.1.3: 527 | version "2.1.3" 528 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" 529 | integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== 530 | 531 | negotiator@0.6.3: 532 | version "0.6.3" 533 | resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" 534 | integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== 535 | 536 | number-allocator@^1.0.14: 537 | version "1.0.14" 538 | resolved "https://registry.yarnpkg.com/number-allocator/-/number-allocator-1.0.14.tgz#1f2e32855498a7740dcc8c78bed54592d930ee4d" 539 | integrity sha512-OrL44UTVAvkKdOdRQZIJpLkAdjXGTRda052sN4sO77bKEzYYqWKMBjQvrJFzqygI99gL6Z4u2xctPW1tB8ErvA== 540 | dependencies: 541 | debug "^4.3.1" 542 | js-sdsl "4.3.0" 543 | 544 | object-inspect@^1.9.0: 545 | version "1.12.3" 546 | resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" 547 | integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== 548 | 549 | on-finished@2.4.1: 550 | version "2.4.1" 551 | resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" 552 | integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== 553 | dependencies: 554 | ee-first "1.1.1" 555 | 556 | once@^1.3.0, once@^1.4.0: 557 | version "1.4.0" 558 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 559 | integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== 560 | dependencies: 561 | wrappy "1" 562 | 563 | parseurl@~1.3.3: 564 | version "1.3.3" 565 | resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" 566 | integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== 567 | 568 | path-to-regexp@0.1.7: 569 | version "0.1.7" 570 | resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" 571 | integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== 572 | 573 | process-nextick-args@^2.0.1: 574 | version "2.0.1" 575 | resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" 576 | integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== 577 | 578 | process@^0.11.10: 579 | version "0.11.10" 580 | resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" 581 | integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== 582 | 583 | protobufjs@^7.2.4: 584 | version "7.2.4" 585 | resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.2.4.tgz#3fc1ec0cdc89dd91aef9ba6037ba07408485c3ae" 586 | integrity sha512-AT+RJgD2sH8phPmCf7OUZR8xGdcJRga4+1cOaXJ64hvcSkVhNcRHOwIxUatPH15+nj59WAGTDv3LSGZPEQbJaQ== 587 | dependencies: 588 | "@protobufjs/aspromise" "^1.1.2" 589 | "@protobufjs/base64" "^1.1.2" 590 | "@protobufjs/codegen" "^2.0.4" 591 | "@protobufjs/eventemitter" "^1.1.0" 592 | "@protobufjs/fetch" "^1.1.0" 593 | "@protobufjs/float" "^1.0.2" 594 | "@protobufjs/inquire" "^1.1.0" 595 | "@protobufjs/path" "^1.1.2" 596 | "@protobufjs/pool" "^1.1.0" 597 | "@protobufjs/utf8" "^1.1.0" 598 | "@types/node" ">=13.7.0" 599 | long "^5.0.0" 600 | 601 | proxy-addr@~2.0.7: 602 | version "2.0.7" 603 | resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" 604 | integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== 605 | dependencies: 606 | forwarded "0.2.0" 607 | ipaddr.js "1.9.1" 608 | 609 | qs@6.11.0: 610 | version "6.11.0" 611 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" 612 | integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== 613 | dependencies: 614 | side-channel "^1.0.4" 615 | 616 | range-parser@~1.2.1: 617 | version "1.2.1" 618 | resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" 619 | integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== 620 | 621 | raw-body@2.5.1: 622 | version "2.5.1" 623 | resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857" 624 | integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== 625 | dependencies: 626 | bytes "3.1.2" 627 | http-errors "2.0.0" 628 | iconv-lite "0.4.24" 629 | unpipe "1.0.0" 630 | 631 | readable-stream@^3.0.2, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: 632 | version "3.6.2" 633 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" 634 | integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== 635 | dependencies: 636 | inherits "^2.0.3" 637 | string_decoder "^1.1.1" 638 | util-deprecate "^1.0.1" 639 | 640 | readable-stream@^4.4.2: 641 | version "4.4.2" 642 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.4.2.tgz#e6aced27ad3b9d726d8308515b9a1b98dc1b9d13" 643 | integrity sha512-Lk/fICSyIhodxy1IDK2HazkeGjSmezAWX2egdtJnYhtzKEsBPJowlI6F6LPb5tqIQILrMbx22S5o3GuJavPusA== 644 | dependencies: 645 | abort-controller "^3.0.0" 646 | buffer "^6.0.3" 647 | events "^3.3.0" 648 | process "^0.11.10" 649 | string_decoder "^1.3.0" 650 | 651 | reinterval@^1.1.0: 652 | version "1.1.0" 653 | resolved "https://registry.yarnpkg.com/reinterval/-/reinterval-1.1.0.tgz#3361ecfa3ca6c18283380dd0bb9546f390f5ece7" 654 | integrity sha512-QIRet3SYrGp0HUHO88jVskiG6seqUGC5iAG7AwI/BV4ypGcuqk9Du6YQBUOUqm9c8pw1eyLoIaONifRua1lsEQ== 655 | 656 | rfdc@^1.3.0: 657 | version "1.3.0" 658 | resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" 659 | integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== 660 | 661 | safe-buffer@5.2.1, safe-buffer@~5.2.0: 662 | version "5.2.1" 663 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" 664 | integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== 665 | 666 | "safer-buffer@>= 2.1.2 < 3": 667 | version "2.1.2" 668 | resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" 669 | integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== 670 | 671 | send@0.18.0: 672 | version "0.18.0" 673 | resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" 674 | integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== 675 | dependencies: 676 | debug "2.6.9" 677 | depd "2.0.0" 678 | destroy "1.2.0" 679 | encodeurl "~1.0.2" 680 | escape-html "~1.0.3" 681 | etag "~1.8.1" 682 | fresh "0.5.2" 683 | http-errors "2.0.0" 684 | mime "1.6.0" 685 | ms "2.1.3" 686 | on-finished "2.4.1" 687 | range-parser "~1.2.1" 688 | statuses "2.0.1" 689 | 690 | serve-static@1.15.0: 691 | version "1.15.0" 692 | resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" 693 | integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== 694 | dependencies: 695 | encodeurl "~1.0.2" 696 | escape-html "~1.0.3" 697 | parseurl "~1.3.3" 698 | send "0.18.0" 699 | 700 | setprototypeof@1.2.0: 701 | version "1.2.0" 702 | resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" 703 | integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== 704 | 705 | side-channel@^1.0.4: 706 | version "1.0.4" 707 | resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" 708 | integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== 709 | dependencies: 710 | call-bind "^1.0.0" 711 | get-intrinsic "^1.0.2" 712 | object-inspect "^1.9.0" 713 | 714 | split2@^4.2.0: 715 | version "4.2.0" 716 | resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" 717 | integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== 718 | 719 | statuses@2.0.1: 720 | version "2.0.1" 721 | resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" 722 | integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== 723 | 724 | stream-shift@^1.0.0: 725 | version "1.0.1" 726 | resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" 727 | integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== 728 | 729 | string_decoder@^1.1.1, string_decoder@^1.3.0: 730 | version "1.3.0" 731 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" 732 | integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== 733 | dependencies: 734 | safe-buffer "~5.2.0" 735 | 736 | toidentifier@1.0.1: 737 | version "1.0.1" 738 | resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" 739 | integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== 740 | 741 | type-is@~1.6.18: 742 | version "1.6.18" 743 | resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" 744 | integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== 745 | dependencies: 746 | media-typer "0.3.0" 747 | mime-types "~2.1.24" 748 | 749 | typedarray@^0.0.6: 750 | version "0.0.6" 751 | resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" 752 | integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== 753 | 754 | unpipe@1.0.0, unpipe@~1.0.0: 755 | version "1.0.0" 756 | resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" 757 | integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== 758 | 759 | util-deprecate@^1.0.1: 760 | version "1.0.2" 761 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 762 | integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== 763 | 764 | utils-merge@1.0.1: 765 | version "1.0.1" 766 | resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" 767 | integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== 768 | 769 | vary@~1.1.2: 770 | version "1.1.2" 771 | resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" 772 | integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== 773 | 774 | wrappy@1: 775 | version "1.0.2" 776 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 777 | integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== 778 | 779 | ws@^8.13.0: 780 | version "8.13.0" 781 | resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0" 782 | integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA== 783 | --------------------------------------------------------------------------------