From f9ada5d850d3e7b1a3f244f3760fe2974a9a3109 Mon Sep 17 00:00:00 2001 From: LKuemmel <76958050+LKuemmel@users.noreply.github.com> Date: Wed, 24 Jul 2024 14:23:41 +0200 Subject: [PATCH] Merge master into Beta (#1783) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * soc BMW: fix env issue in main.sh * MQTT: Allow other devices to use the branch other/# * Bump aiohttp from 3.9.2 to 3.9.4 Bumps [aiohttp](https://github.com/aio-libs/aiohttp) from 3.9.2 to 3.9.4. - [Release notes](https://github.com/aio-libs/aiohttp/releases) - [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst) - [Commits](https://github.com/aio-libs/aiohttp/compare/v3.9.2...v3.9.4) --- updated-dependencies: - dependency-name: aiohttp dependency-type: direct:production ... Signed-off-by: dependabot[bot] * trap failing api in fetch_soc * use also surplus in mode scheduled charging submode instant charging * req.get_http_session implemented * long line trncated * indent fixed * immediately switch off in case of submode change and switch off threshold reached * pv phases automatic: directly start with max phases * backup file before writing * Fix livegraph date display and pricelist scale granularity * Prevent division by zero error in day graph * polestar api:cleaned and exceptions added * blank line removed * fix charged range display * Increment version number in mosquitto.acl as requested by benderl * build ui * Update powergraph.js Index zu kurz, Verbrauche2 erscheint als LP3 * build UI * remove spinning wheel from graph * add registry entry for os error none in case of timed out network connection * fix client handler fault state * fix year graph * fix template deletion * Update version 2.1.4 * Update pycarwings2.py Two changes 1st re line 75: Nissan changed the Base URL of its Carwings API. Since May 20, 2024 the old URL used by pycarwings2 in soc_leaf doesn't work anymore. 2nd re line 121: At least on a Windows 10 PC with Python 3.12 for test purpose, the additional parameter 'headers={"User-Agent": ""}' is needed to run pycarwings2.py successfully. The need for this parameter within OpenWB itself has tbc. For more details see posts on https://forum.openwb.de/viewtopic.php?p=109761#p109761 ff * update ramdisk settings * Update pycarwings2.py 2nd change removed, because it is not needed on the OpenWB software platform. * Update version 2.1.5-Alpha.1 * Wiki: Fehlerbehandlung im Kontextmanager (#1616) * Wiki: Fehlerbehandlung im Kontextmanager * zusammenfassen für Geräte und SoC * struktur * Update docs/Neues Modul programmieren.md Co-authored-by: benderl --------- Co-authored-by: benderl * Gerätenamen (#1619) * Gerätenamen * SMA Sunny Home Manager 2.0, Energy Meter * build UI * Boersenmodul (#1568) * #Boersenstrompreise allgemein energycharts mit Grundpreisoption * #Laenderauswahl fix * #Boersenmodul v1 * #removed unused import * #removed topics, cleaned code * #removed whitespace flake8 * #use req * # change variable serve_price to surchar_price * #requested changes * # requested change, cleaned code * SOC-Module BMW - via bimmer_connected, supporting calculate_soc (#1610) * SOC-Module BMW - via bimmer_connected, supporting calculate_soc * changes as requested * removed obsolete imports * Rabot dynamischer Tarif (#1655) * Grundstruktur ohne Anpassung * Anapssung Rabot * fix Umrechnungsfaktor * fix flake8 + added try except for get_raw_prices * fix pv yields: yields for inverters added in the current month or year (#1654) * fix pv yields: yields for inverters added in the current month or year * consider first day when no monthly logfile exists * fix timestamp text (#1646) * fix timestamp text * review * "Neu" statt "Standard-" (#1641) * "Neu" statt "Standard-" * cp: phase_1 must be 1/2/3 * fix hierarchy validation (#1629) * fix hierarchy validation * reuse text * Fix pip SSL-error in github action * build UI * fix missing logfiles * improve ui text * build UI * Hausverbrauch bei Hausverbrauchszählern (#1640) * draft * Hausverbrauch bei Hausverbrauchszählern * Smart-hello Vehicle SoC (#1653) * initial version * flake8 * LoginHello error handling * handle empty VIN * remove unused functions * add sessioncache to config * formatting and cleanup * flake8 * soc timestamp * add retry logic * no calc_while_charging * recommendations from review * review * Boersenmodul (#1665) * #Boersenstrompreise allgemein energycharts mit Grundpreisoption * #Laenderauswahl fix * #Boersenmodul v1 * #removed unused import * #removed topics, cleaned code * #removed whitespace flake8 * #use req * # change variable serve_price to surchar_price * #requested changes * # requested change, cleaned code * Fix Börsenmodul Zeit * remove unused import * Flake8 Removed unused import * fix UTC Zeit +02:00 ud +01:00 * Feature shelly pro3em (#1670) * add shelly as battery and counter * add Shelly 3EM * minor changes to modules * improve readability * disable PSA modul (#1672) * disable PSA modul * flake8 * Update packages/modules/vehicles/psa/api.py Co-authored-by: LKuemmel <76958050+LKuemmel@users.noreply.github.com> * flake8 --------- Co-authored-by: LKuemmel <76958050+LKuemmel@users.noreply.github.com> * fix counters in daily log * fix Errorhandling Backup vor Update (#1631) * Wechseln des Entwickungszweiges liefert StackTrace * Aenderung Error logging * fix phases in case of no hw phase switch (#1612) * fix phases in case of no hw phase switch * check hw phase switch first * fix simcount nan (#1657) * fix simcount nan * fix * typo * typo * new module mtec * fix log grid attribute * skip if error doesn't occur * colors theme: update packages (#1684) * update packages * update dist package * Elwa2.... (#2843) * Elwa2.... Elwa2 anpaasungen für 1.9 und 2.0, Gui für 2.0 kommt noch separat. * typeo... * typo * Metadata bat all (#1635) * changed_values_handler improve test fix fix test * Metadata for bat_all * clean up * fixes * remove obsolet metadata * remove obsolet topic (#1621) * remove obsolet topic * update file version * fix path backup_before_update (#1623) Co-authored-by: benderl * fix build cards theme workflow * Revert "remove obsolet topic (#1621)" This reverts commit 7c7b5e8de113198b19ea6473bf71111e42f35444. * Remove obsolet (#1689) * remove obsolet topic * update file version * Build Display Theme: Cards * update display theme * add features to display theme * reduce parameters for theme * add binary version * fix python linter problem * fix python linter problem * fix smarthome display * build UI * improve log file sizes (#1659) * improve log file sizes * use more rotating files * Update deye (#1691) * fix power factors and registers * change ModbusDataType fo frequency * Wiki (#1658) * Create manual-workflow.yml * clean branch * Create Huawei-Smartlogger.md * Add files via upload * Update Huawei-Smartlogger.md * Update _Sidebar.md * Update _Sidebar.md * Update _Sidebar.md * Update docs/Huawei-Smartlogger.md Co-authored-by: LKuemmel <76958050+LKuemmel@users.noreply.github.com> * Update docs/Huawei-Smartlogger.md Co-authored-by: LKuemmel <76958050+LKuemmel@users.noreply.github.com> * Update docs/Huawei-Smartlogger.md Co-authored-by: LKuemmel <76958050+LKuemmel@users.noreply.github.com> * Update docs/Huawei-Smartlogger.md * Update docs/Huawei-Smartlogger.md * Update docs/Huawei-Smartlogger.md --------- Co-authored-by: LKuemmel <76958050+LKuemmel@users.noreply.github.com> * Phasenumschaltung (#1644) * Phasenumschaltung Zielladen mit submode * New topic setdata & update_config * Anpassung Kommentar * topic von phase_switch_delay geändert und von Klasse PVCharging in ChargeModeConfig migriert * Flake8 line too long * fix pytest auto_phase_switch_test.py * added upgrade_datastore_44 * update_config merge * altes topic * build UI * Neues Konzept ID-Tag (#1652) * disable_after_unplug migriert von ev.py nach chargepoint topic verschoben in setdata * removed rfid_enabled * changed definition set manual_lock active * changed access to disable_after_unplug * new concept disable_after_unplug * flake8 * Anpassung wiki Ladung nur nach Freischaltung * Korrektur Rechtsschreibung * Korrektur Rechtsschreibung * Korrektur Rechtsschreibung * Anapssung Text wiki * Korrektur Rechtschreibung * added datastore in update_conf (not tested) * resolve datastore merge conflict * flake8 whitespace after merge conflict * flake8 whitespace * flake8 whitespace * Update packages/control/chargepoint/chargepoint_template.py Co-authored-by: LKuemmel <76958050+LKuemmel@users.noreply.github.com> * Update packages/control/ev.py --------- Co-authored-by: LKuemmel <76958050+LKuemmel@users.noreply.github.com> * build UI (#1697) * fix phases to use if MAX_FAILED_PHASE_SWITCHES is reached (#1698) * improve text, wiki: fix visibility and content (#1700) * improve text * wiki: fix visibility and content * fix wiki (#1701) * add timeout for display unlock * add timeout for display unlock * hook for uncaught exceptions (#1703) * hook for uncaught exceptions log stdout and stderr fix * flake8 * Revert "hook for uncaught exceptions (#1703)" (#1704) This reverts commit f4c41060cfca548a592ca1077ea17661b0ffd885. * Hook uncaught exceptions (#1705) * hook for uncaught exceptions log stdout and stderr fix * flake8 * fix import error * Anpassung Ziel-SoC/Limit-SoC (#1662) * Anpassung Ziel-SoC/Limit-SoC * Anpassung if-statement -> pytest * fix text * flake8 line too long * build UI (#1706) * Wiki: Add Content (#1452) * Wiki: Fehlerhafte Links korrigiert und Inhalte ergänzt * Wiki: Ergänzungen und neue Seite Fehlersuche * Wiki: MQTT Doku als erster Entwurf * Wiki: Formatierungen an MQTT-Doc * Wiki: Korrekturen zu Kommentaren #1 * Apply suggestions from code review Co-authored-by: LKuemmel <76958050+LKuemmel@users.noreply.github.com> * Update docs/Ladeprofile.md Co-authored-by: LKuemmel <76958050+LKuemmel@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: LKuemmel <76958050+LKuemmel@users.noreply.github.com> * Wiki: Fehler in MQTT Doku * Wiki: Warnung hinzugefügt * Wiki: Erkentnisse zu den Darstellungen eingefügt * Wiki: Menü Anpassung * Apply suggestions from code review Co-authored-by: LKuemmel <76958050+LKuemmel@users.noreply.github.com> --------- Co-authored-by: ChristianK Co-authored-by: LKuemmel <76958050+LKuemmel@users.noreply.github.com> * fix update_conf datastore_46 (#1702) * Aufteilung datastore_46 in 46 und 47 * fix update_config * improve default value max_current_single_phase = 16 (#1707) * improve default value max_current_single_phase = 16 * fix test * fix PHASE_SWITCH_DELAY_EXPIRED and no phase switch required (#1709) * Update _Sidebar.md (#1708) Typo in der Formatierung des Menüs * fix double log entries in child logs (#1710) * Wiki Hausverbrauchs-Zähler (#1711) * Wiki Hausverbrauchs-Zähler * fix * fix-alpha_ess-counter-bug (#1713) * headers for chargelog in download-link (#1714) * Metadata chargepoint all (#1634) * Metadata for chargepoint_all * fix * clean up * fix * Extend charge log (#1651) * draft * fix * csv: match key to row, parse power sources * clean up * flake8 * fix * fixes * fix * fix * clean up log (#1717) * clean up log * fix get phases chargemode * Anpassung Wiki Ladung nur nach Freischaltung (#1718) * build UI (#1719) * Goodwe v1 1 (#1715) * add version and firmware information * add alternative registers for battery and counter * fix breaking changes * solve breaking changes * fix breaking changes, add comments * metadata for ev (#1716) * Colors theme: Fixes and Improvements (#1712) * increase font size in badges * fix livegraph SOC * fix chargelog energy source (#1720) * handle errors during configurable initialization - RSE (#1458) * handle errors during configurable initialisation * fix * fix * handle errors during configurable initialisation - backup clouds (#1630) * handle errors during configurable initialisation - backup clouds * fix * Implementierung Einrichtungsassistent automatisches Aufrufen beim Starten Rename of InstallWizard to InstallAssistantDone datastore_upgrade Einrichtungsassistent Rename of InstallWizard to InstallAssistantDone Update update_config.py * fix * build UI (#1723) * fix visibility assistant after update (#1724) * build UI (#1729) * Goodwe v1 1 (#1727) * add version and firmware information * add alternative registers for battery and counter * fix breaking changes * solve breaking changes * fix breaking changes, add comments * fix SimCounter * remove duplicate lines * fix badge font color (#1728) * handle errors during configurable initialization - electronic tariffs (#1632) * handle errors during configurable initialization - electronic tariffs * docs * energycharts * fix daygraph * fix visibility of bat on dashboard (#1731) * fix satellite modbus error (#1732) * fix satellite modbus error * mock contextmanager * detect and fix invalid cached vehicle (#1673) * fix pv yield for deleted inverters (#1737) * Smart260 (#1739) * samrthome 2.0 startup Nur für openwb 2.0 übernehmen. Neu wird beim smarthome startup der letzte manual / automatisch Mode aus mqtt übenommen. flake8 Anpassungen * blank removed * flake8 ... * Goodwe v1 1 (#1738) * add version and firmware information * add alternative registers for battery and counter * fix breaking changes * solve breaking changes * fix breaking changes, add comments * fix SimCounter * remove duplicate lines * add component ID for SimCounter * correct pv total power reg for v1.1 * adjust line length * fix and remove obsolet topics (#1736) * fix and remove obsolet topics * fix * update valid topics (#1740) * show ripple control receiver config in log (#1741) * Use create_device for byd, huawei smartlogger, carlo gavazzi, good_we, kostal piko, janitza (#1742) * BYD, CarloGavazzi, GoodWe * sample * huawei smartlogger * kostal piko * janitza * build * hotfix * fix samba default path * naming (#1749) * increase evse error tolerance (#1750) * Goodwe v1 1 (#1745) * add version and firmware information * add alternative registers for battery and counter * fix breaking changes * solve breaking changes * fix breaking changes, add comments * fix SimCounter * remove duplicate lines * add component ID for SimCounter * correct pv total power reg for v1.1 * adjust line length * change ModbusDataType for power and power factors * adjust inverter total power * fix goodwe inverter power, remove unused device id * fix pin validation * add switch for soc limit in PV mode * add upper limit for autarchy in energy graph * fix CP colors in month graph and remove calculation of autarchy * add different factors for deye three phase inverters (#1735) * add different factors for deye three phase inverters * fix deye breaking changes * remove factor selection * handle device_type internally * remove topic device_type * read device_type just once * add enum device_type for better readability * Ticket (#1726) * draft * ticket * remove comment * delete old script * fixes * secondary * fix * show primary/secondary mode * fix * add fox_ess device (#1755) * mqtt, powerdog, saxpower, siemens, lg (#1754) * mqtt, powerdog, saxpower, siemens * lg * build UI (#1756) * Fix charge log (#1757) * fix chargelog * fix rebase * test * test * fix * f * fix * fix * test * running test only local * undo * read energy counter from inverter, remove simcount (#1758) * Fix charge log-bugfix (#1763) * fix * typo * Revert "Fix charge log-bugfix (#1763)" (#1764) This reverts commit 71b3fe09a5deccbfe27d02964a7e9193e77c1ae1. * Revert "Fix charge log (#1757)" (#1765) This reverts commit ea7aac7a0d90c440703acf76a8186199a97c8135. * Improve Wiki (#1766) * integrate energy flow view from simple interface * fix vehicle settings header * enlarge some elements * update vulnerable dependencies * update fontawesome packages * update other dependencies except eslint * linting * upgrade eslint * fix linting errors ans warnings * migrate simple display theme * tune energy flow view * add reboot and shutdown buttons for secondaries * cleanup display wrapper * tweak flow elements * Revert "add reboot and shutdown buttons for secondaries" This reverts commit eb832089231cd7688b4a3f72e9385d1dfc12bd68. * build settings ui * Build Display Theme: Cards * improve ticket (#1769) * metadata (#1760) * metadata all * fix test * improve * new connection on exception (#1771) * detect broken evse and broken meter (#1759) * Update update_config.py * rename consumption counter (#1768) * fix skipped datastore version (#1773) * fix daily yield topic (#1776) * inital http-api * enable http api apache site * minor changes * http-api send msg added * inital http-api * enable http api apache site * minor changes * http api topic * returns in json * command * cleanup after rebase and fix * refactor api v1 * remove unsecure access to api * refactor apache configuration in atreboot.sh * command * migrate command to regular topic update * build settings ui * fix calculation of charge costs including one hour change (#1781) * fix sma error handling (#1782) * fix sma error handling * dc power NaN * ticket: catch request timeout (#1779) * Update version 2.1.5-Beta.1 --------- Signed-off-by: dependabot[bot] Co-authored-by: benderl Co-authored-by: rleidner Co-authored-by: DerHerrW Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Philipp Kainz Co-authored-by: PK Co-authored-by: Claus Hagen Co-authored-by: DerHerrW <129861137+DerHerrW@users.noreply.github.com> Co-authored-by: Lutz Bender Co-authored-by: Penny <89247538+hhoefling@users.noreply.github.com> Co-authored-by: mekrapp <158028484+mekrapp@users.noreply.github.com> Co-authored-by: pama87 <163103468+pama87@users.noreply.github.com> Co-authored-by: rleidner <89418596+rleidner@users.noreply.github.com> Co-authored-by: Martin Rinas Co-authored-by: ndrsnhs <156670705+ndrsnhs@users.noreply.github.com> Co-authored-by: ndrsnhs Co-authored-by: okaegi <72255431+okaegi@users.noreply.github.com> Co-authored-by: LKuemmel Co-authored-by: Sebastian Reinhold <44172078+sreinhold95@users.noreply.github.com> Co-authored-by: Christian <76468662+Pendragon77@users.noreply.github.com> Co-authored-by: ChristianK Co-authored-by: pama87 Co-authored-by: kevinwieland --- .../workflows/build_display_theme_cards.yml | 2 +- data/config/apache/http-api-ssl.conf | 141 + data/config/mosquitto/mosquitto.acl | 4 +- data/config/mosquitto/openwb_local.conf | 3 +- data/config/ramdisk_config.txt | 5 + docs/Anzeige-Steuerung.md | 7 + docs/Fehlersuche.md | 43 + "docs/Hausverbrauchs-Z\303\244hler.md" | 6 +- docs/Huawei-Smartlogger.md | 17 + docs/HuaweiSmartloggerKomponenten.PNG | Bin 0 -> 106407 bytes docs/HuaweiSmartloggerLogischeAdressen.PNG | Bin 0 -> 110174 bytes docs/HuaweiSmartloggerModBusTCP.PNG | Bin 0 -> 87438 bytes docs/Ladeprofile.md | 3 + docs/Ladung nur nach Freischaltung.md | 78 +- docs/MQTT.md | 71 + "docs/Neues Ger\303\244t programmieren.md" | 23 - docs/Neues Modul programmieren.md | 39 + docs/Neues Soc-Modul programmieren.md | 12 - docs/Tutorial | 0 docs/Typische-Anwendungsfaelle.md | 29 + docs/Wiki-Eintrag erstellen.md | 8 +- docs/Zaehler.md | 91 + "docs/Z\303\244hler.md" | 93 - docs/_Sidebar.md | 14 +- docs/pictures/Anwendungsfaelle_minStrom.jpg | Bin 0 -> 37093 bytes docs/pictures/Anwendungsfaelle_zielladen.jpg | Bin 0 -> 67535 bytes docs/pictures/Fehlersuche_DebugLog.jpg | Bin 0 -> 55059 bytes docs/pictures/Fehlersuche_Main-Log.jpg | Bin 0 -> 8924 bytes docs/pictures/MQTT_Konsole_PV-Laden.jpg | Bin 0 -> 77540 bytes docs/pictures/MQTT_Konsole_Sofortladen.jpg | Bin 0 -> 86001 bytes docs/pictures/Wiki-Eintrag erstellen_Pull.jpg | Bin 65001 -> 0 bytes ...rk.png => Wiki-Eintrag_erstellen_Fork.png} | Bin docs/pictures/Wiki-Eintrag_erstellen_Pull.jpg | Bin 0 -> 59553 bytes .../sample_electricity_tariff/tariff.py | 2 +- docs/samples/sample_modbus/device.py | 2 +- openwb-install.sh | 4 +- package-lock.json | 6 + packages/conftest.py | 40 + .../control/algorithm/additional_current.py | 9 +- packages/control/algorithm/algorithm.py | 4 +- packages/control/algorithm/common.py | 9 +- .../control/algorithm/filter_chargepoints.py | 14 - .../algorithm/filter_chargepoints_test.py | 25 - .../algorithm/integration_test/conftest.py | 1 + packages/control/algorithm/min_current.py | 4 +- .../control/algorithm/surplus_controlled.py | 32 +- .../algorithm/surplus_controlled_test.py | 34 +- packages/control/auto_phase_switch_test.py | 32 +- packages/control/bat.py | 20 +- packages/control/bat_all.py | 32 +- packages/control/chargelog/chargelog.py | 74 +- packages/control/chargelog/chargelog_test.py | 154 + packages/control/chargepoint/chargepoint.py | 69 +- .../control/chargepoint/chargepoint_all.py | 14 +- .../control/chargepoint/chargepoint_data.py | 9 +- .../chargepoint/chargepoint_template.py | 19 +- .../control/chargepoint/control_parameter.py | 56 +- .../control/chargepoint/get_phases_test.py | 7 +- packages/control/counter.py | 80 +- packages/control/counter_all.py | 141 +- .../control/counter_home_consumption_test.py | 26 +- packages/control/ev.py | 156 +- packages/control/ev_charge_template_test.py | 12 +- packages/control/general.py | 141 +- packages/control/hierarchy_test.py | 18 +- packages/control/prepare.py | 2 + packages/control/process.py | 2 - packages/dataclass_utils/_dataclass_asdict.py | 1 - packages/helpermodules/abstract_plans.py | 6 +- .../helpermodules/changed_values_handler.py | 114 +- .../changed_values_handler_test.py | 27 +- packages/helpermodules/command.py | 199 +- packages/helpermodules/create_debug.py | 279 + .../data_migration/data_migration.py | 16 +- packages/helpermodules/exceptions/os.py | 2 +- packages/helpermodules/graph.py | 6 +- .../helpermodules/hardware_configuration.py | 10 +- packages/helpermodules/logger.py | 24 +- .../measurement_logging/process_log.py | 76 +- .../measurement_logging/update_yields.py | 68 +- .../measurement_logging/update_yields_test.py | 42 +- .../measurement_logging/write_log.py | 17 +- .../measurement_logging/write_log_test.py | 6 +- packages/helpermodules/parse_send_debug.py | 74 - packages/helpermodules/setdata.py | 24 +- packages/helpermodules/subdata.py | 53 +- packages/helpermodules/system.py | 19 +- packages/helpermodules/timecheck.py | 13 +- packages/helpermodules/timecheck_test.py | 15 +- packages/helpermodules/update_config.py | 202 +- packages/helpermodules/update_config_test.py | 4 +- .../helpermodules/utils/json_file_handler.py | 49 + .../utils/json_file_handler_test.py | 34 + packages/helpermodules/utils/run_command.py | 23 + packages/main.py | 64 +- .../backup_clouds/nextcloud/backup_cloud.py | 5 +- .../modules/backup_clouds/nfs/backup_cloud.py | 3 +- .../backup_clouds/onedrive/backup_cloud.py | 3 +- .../backup_clouds/samba/backup_cloud.py | 3 +- .../modules/backup_clouds/samba/config.py | 3 +- .../chargepoint_module.py | 82 +- packages/modules/common/component_state.py | 1 + packages/modules/common/component_type.py | 1 + .../common/configurable_backup_cloud.py | 15 +- .../configurable_ripple_control_receiver.py | 11 +- .../modules/common/configurable_tariff.py | 31 +- packages/modules/common/hardware_check.py | 39 +- .../modules/common/hardware_check_context.py | 13 - .../modules/common/hardware_check_test.py | 26 +- packages/modules/common/simcount/_simcount.py | 14 +- .../common/simcount/_simcounter_store.py | 12 + packages/modules/common/store/_api.py | 10 +- packages/modules/common/store/_counter.py | 16 +- packages/modules/devices/alpha_ess/counter.py | 21 +- packages/modules/devices/byd/bat.py | 3 +- packages/modules/devices/byd/device.py | 91 +- .../modules/devices/carlo_gavazzi/counter.py | 3 +- .../modules/devices/carlo_gavazzi/device.py | 100 +- packages/modules/devices/deye/bat.py | 30 +- packages/modules/devices/deye/config.py | 4 +- packages/modules/devices/deye/counter.py | 34 +- packages/modules/devices/deye/device.py | 17 +- packages/modules/devices/deye/device_type.py | 8 +- packages/modules/devices/deye/inverter.py | 20 +- packages/modules/devices/fox_ess/bat.py | 39 + packages/modules/devices/fox_ess/config.py | 72 + packages/modules/devices/fox_ess/counter.py | 36 + packages/modules/devices/fox_ess/device.py | 48 + packages/modules/devices/fox_ess/inverter.py | 35 + packages/modules/devices/good_we/bat.py | 10 +- packages/modules/devices/good_we/config.py | 11 +- packages/modules/devices/good_we/counter.py | 47 +- packages/modules/devices/good_we/device.py | 122 +- packages/modules/devices/good_we/inverter.py | 10 +- packages/modules/devices/good_we/version.py | 6 + packages/modules/devices/huawei/config.py | 2 +- .../devices/huawei_smartlogger/config.py | 2 +- .../devices/huawei_smartlogger/device.py | 122 +- packages/modules/devices/janitza/device.py | 100 +- .../modules/devices/kostal_piko/device.py | 138 +- packages/modules/devices/lg/device.py | 193 +- packages/modules/devices/lg/lg_test.py | 22 +- packages/modules/devices/mqtt/device.py | 68 +- packages/modules/devices/mtec/bat.py | 45 + packages/modules/devices/mtec/config.py | 73 + packages/modules/devices/mtec/counter.py | 34 + packages/modules/devices/mtec/device.py | 48 + packages/modules/devices/mtec/inverter.py | 35 + packages/modules/devices/opendtu/config.py | 4 +- .../modules/devices/openwb_evu_kit/counter.py | 2 + .../modules/devices/openwb_flex/config.py | 2 +- .../modules/devices/openwb_flex/counter.py | 5 +- .../modules/devices/openwb_flex/versions.py | 4 +- packages/modules/devices/powerdog/device.py | 126 +- packages/modules/devices/qcells/device.py | 9 +- packages/modules/devices/saxpower/device.py | 100 +- packages/modules/devices/shelly/bat.py | 59 + packages/modules/devices/shelly/config.py | 32 + packages/modules/devices/shelly/counter.py | 72 + packages/modules/devices/shelly/device.py | 16 +- packages/modules/devices/siemens/device.py | 112 +- packages/modules/devices/sma_shm/config.py | 6 +- .../modules/devices/sma_sunny_boy/counter.py | 3 +- .../modules/devices/sma_sunny_boy/device.py | 14 +- .../modules/devices/sma_sunny_boy/inverter.py | 3 + .../modules/display_themes/cards/config.py | 7 + .../display_themes/cards/source/.eslintrc.cjs | 14 - .../cards/source/eslint.config.js | 13 + .../cards/source/package-lock.json | 2901 +++++----- .../display_themes/cards/source/package.json | 46 +- .../cards/source/public/icons/icon_Data.md | 3 + .../cards/source/public/icons/owbBattery.svg | 3 + .../source/public/icons/owbChargePoint.svg | 3 + .../cards/source/public/icons/owbGrid.svg | 3 + .../cards/source/public/icons/owbHouse.svg | 1 + .../cards/source/public/icons/owbPV.svg | 3 + .../cards/source/public/icons/owbVehicle.svg | 39 + .../display_themes/cards/source/src/App.vue | 62 +- .../src/components/ChargePointCodeButton.vue | 20 +- .../src/components/ChargePointLockButton.vue | 8 +- .../src/components/ChargePointPlugBadge.vue | 13 +- .../ChargePoints/ChargePointCard.vue | 309 ++ .../ChargePoints/ManualSocInput.vue | 31 +- .../ChargePoints/SimpleChargePointCard.vue | 313 ++ .../source/src/components/CodeInputModal.vue | 40 +- .../src/components/DashBoard/BatteryCard.vue | 18 +- .../components/DashBoard/ChargePointsCard.vue | 27 +- .../src/components/DashBoard/FlowCard.vue | 872 +++ .../src/components/DashBoard/GridCard.vue | 11 +- .../src/components/DashBoard/HomeCard.vue | 9 +- .../src/components/DashBoard/InverterCard.vue | 14 +- .../source/src/components/DashBoardCard.vue | 8 +- .../cards/source/src/components/DateTime.vue | 16 +- .../src/components/ExtendedNumberInput.vue | 25 +- .../source/src/components/LockNavItem.vue | 35 +- .../cards/source/src/components/NavBar.vue | 24 +- .../cards/source/src/components/NavItem.vue | 2 +- .../cards/source/src/components/NumberPad.vue | 103 +- .../cards/source/src/components/SparkLine.vue | 24 +- .../src/components/Status/RebootButton.vue | 40 +- .../src/components/Status/ReloadButton.vue | 17 +- .../src/components/Status/ShutdownButton.vue | 42 +- .../cards/source/src/router/index.js | 5 + .../cards/source/src/stores/mqtt.js | 157 +- .../source/src/views/ChargePointsView.vue | 417 +- .../cards/source/src/views/DashBoardView.vue | 14 +- .../cards/source/src/views/EnergyFlowView.vue | 19 + .../cards/source/src/views/StatusView.vue | 61 +- .../cards/source/src/views/WelcomeView.vue | 23 +- .../assets/ChargePointPlugBadge-6FxVjZH4.js | 1 - .../assets/ChargePointPlugBadge-CP9QLc8z.js | 1 + .../assets/ChargePointPlugBadge-DqNX7D_z.css | 1 + .../assets/ChargePointPlugBadge-rhc0EdNG.css | 1 - .../web/assets/ChargePointsView-DWVNNV7U.js | 1 + .../web/assets/ChargePointsView-DvlDrERC.css | 1 + .../web/assets/ChargePointsView-TjZNhXcr.js | 1 - .../web/assets/ChargePointsView-anjjZlvZ.css | 1 - .../web/assets/DashBoardCard-ChX0nYSF.js | 1 + .../web/assets/DashBoardCard-DKbC8x2S.css | 1 + .../web/assets/DashBoardCard-JoQ8MVPZ.css | 1 - .../web/assets/DashBoardCard-qEoaavxr.js | 1 - ...-0x0RbA.css => DashBoardView-CrVFeUF-.css} | 2 +- .../web/assets/DashBoardView-Mm5SqgY_.js | 1 + .../web/assets/DashBoardView-QZWzjOit.js | 1 - .../web/assets/EnergyFlowView-CdniTPDx.js | 1 + .../web/assets/EnergyFlowView-VrFiHW_2.css | 1 + .../cards/web/assets/StatusView-Bb96bsZ0.js | 1 + .../cards/web/assets/StatusView-_Im55Cq_.js | 1 - .../cards/web/assets/index-Dlb0hnJU.js | 2 + .../cards/web/assets/index-Gvrm5_Z6.css | 1 + .../cards/web/assets/index-Jo0qhDRH.js | 7 - .../cards/web/assets/index-fiBjfcgV.css | 1 - .../cards/web/assets/vendor-DcFd2nmT.js | 29 + .../web/assets/vendor-fortawesome-1k8kfR6t.js | 762 +++ .../web/assets/vendor-fortawesome-S-qqChZo.js | 762 --- .../web/assets/vendor-inkline-B7Agb5gs.css | 1 + .../web/assets/vendor-inkline-Dxj-ZgXa.css | 1 - ...F2zxVbzD.js => vendor-inkline-GZyOLkvD.js} | 2 +- .../cards/web/assets/vendor-xCcLvHvG.js | 29 - .../cards/web/icons/icon_Data.md | 3 + .../cards/web/icons/owbBattery.svg | 3 + .../cards/web/icons/owbChargePoint.svg | 3 + .../cards/web/icons/owbGrid.svg | 3 + .../cards/web/icons/owbHouse.svg | 1 + .../display_themes/cards/web/icons/owbPV.svg | 3 + .../cards/web/icons/owbVehicle.svg | 39 + .../display_themes/cards/web/index.html | 12 +- .../modules/display_themes/colors/config.py | 31 + .../display_themes/colors/source/.eslintrc | 24 + .../display_themes/colors/source/.gitignore | 30 + .../colors/source/.prettierignore | 2 + .../colors/source/.prettierrc.json} | 3 + .../colors/source/.vscode/extensions.json | 7 + .../display_themes/colors/source/README.md | 39 + .../display_themes/colors/source/env.d.ts | 1 + .../display_themes/colors/source/index.html | 12 + .../colors/source/package-lock.json | 4764 +++++++++++++++++ .../display_themes/colors/source/package.json | 44 + .../colors/source/public/favicon.ico | Bin 0 -> 4286 bytes .../display_themes/colors/source/src/App.vue | 28 + .../colors/source/src/assets/base.css | 86 + .../colors/source/src/assets/css/style.css | 316 ++ .../fontawesome-free-6.0.0-web/LICENSE.txt | 165 + .../fontawesome-free-6.0.0-web/css/._all.css | Bin 0 -> 4096 bytes .../css/._all.min.css | Bin 0 -> 4096 bytes .../css/._fontawesome.css | Bin 0 -> 4096 bytes .../css/._fontawesome.min.css | Bin 0 -> 4096 bytes .../css/._regular.css | Bin 0 -> 4096 bytes .../css/._regular.min.css | Bin 0 -> 4096 bytes .../css/._solid.css | Bin 0 -> 4096 bytes .../css/._solid.min.css | Bin 0 -> 4096 bytes .../css/._svg-with-js.css | Bin 0 -> 4096 bytes .../css/._svg-with-js.min.css | Bin 0 -> 4096 bytes .../css/._v4-font-face.css | Bin 0 -> 4096 bytes .../css/._v4-font-face.min.css | Bin 0 -> 4096 bytes .../css/._v4-shims.css | Bin 0 -> 4096 bytes .../css/._v4-shims.min.css | Bin 0 -> 4096 bytes .../css/._v5-font-face.css | Bin 0 -> 4096 bytes .../css/._v5-font-face.min.css | Bin 0 -> 4096 bytes .../css/fontawesome.min.css | 4587 ++++++++++++++++ .../css/solid.min.css | 23 + .../webfonts/fa-solid-900.ttf} | Bin .../webfonts/fa-solid-900.woff2} | Bin .../colors/source/src/assets/js/helpers.ts | 125 + .../colors/source/src/assets/js/model.ts | 167 + .../colors/source/src/assets/js/mqttClient.ts | 134 + .../source/src/assets/js/processMessages.ts | 278 + .../source/src/assets/js/sendMessages.ts | 98 + .../source/src/assets/js/themeConfig.ts | 504 ++ .../colors/source/src/assets/js/types.ts | 97 + .../colors/source/src/assets/logo.svg | 1 + .../colors/source/src/assets/main.css | 35 + .../src/components/batteryList/model.ts | 45 + .../components/batteryList/processMessages.ts | 69 + .../chargePointList/CPChargePoint.vue | 341 ++ .../cpConfig/CPChargeConfig.vue | 129 + .../cpConfig/CPChargeConfigPanel.vue | 220 + .../cpConfig/CPConfigInstant.vue | 95 + .../chargePointList/cpConfig/CPConfigPv.vue | 149 + .../cpConfig/CPConfigScheduled.vue | 102 + .../cpConfig/CPConfigTimed.vue | 102 + .../cpConfig/CPConfigVehicle.vue | 66 + .../src/components/chargePointList/model.ts | 445 ++ .../chargePointList/processMessages.ts | 267 + .../src/components/counterList/model.ts | 44 + .../src/components/energyMeter/EMBarGraph.vue | 95 + .../src/components/energyMeter/EMLabels.vue | 90 + .../src/components/energyMeter/EMYAxis.vue | 48 + .../src/components/energyMeter/EmBar.vue | 76 + .../src/components/energyMeter/EmLabel.vue | 124 + .../src/components/energyMeter/EmPopup.vue | 143 + .../src/components/energyMeter/EmPopups.vue | 87 + .../components/energyMeter/EnergyMeter.vue | 167 + .../src/components/icons/IconCommunity.vue | 12 + .../components/icons/IconDocumentation.vue | 12 + .../src/components/icons/IconEcosystem.vue | 12 + .../src/components/icons/IconSupport.vue | 12 + .../src/components/icons/IconTooling.vue | 19 + .../components/powerGraph/PGSourceGraph.vue | 237 + .../components/powerGraph/PGUsageGraph.vue | 291 + .../src/components/powerGraph/PGXAxis.vue | 173 + .../src/components/powerGraph/PgSelector.vue | 261 + .../src/components/powerGraph/PgSoc.vue | 139 + .../src/components/powerGraph/PgSocAxis.vue | 38 + .../src/components/powerGraph/PowerGraph.vue | 162 + .../source/src/components/powerGraph/model.ts | 553 ++ .../powerGraph/processDayGraphData.ts | 171 + .../powerGraph/processLiveGraphData.ts | 144 + .../powerGraph/processMonthYearGraphData.ts | 186 + .../src/components/powerMeter/PMLabel.vue | 64 + .../src/components/powerMeter/PMSourceArc.vue | 75 + .../src/components/powerMeter/PMUsageArc.vue | 71 + .../src/components/powerMeter/PowerMeter.vue | 272 + .../src/components/priceChart/PriceChart.vue | 248 + .../source/src/components/priceChart/model.ts | 19 + .../components/priceChart/processMessages.ts | 24 + .../src/components/shared/BatterySymbol.vue | 31 + .../src/components/shared/ConfigItem.vue | 88 + .../src/components/shared/DateInput.vue | 209 + .../src/components/shared/DisplayButton.vue | 35 + .../src/components/shared/FormatWatt.vue | 15 + .../src/components/shared/FormatWattH.vue | 17 + .../source/src/components/shared/InfoItem.vue | 70 + .../src/components/shared/ModalComponent.vue | 99 + .../src/components/shared/NumberPad.vue | 77 + .../src/components/shared/PadButton.vue | 49 + .../src/components/shared/RadioBarInput.vue | 82 + .../src/components/shared/RadioInput.vue | 110 + .../src/components/shared/RangeInput.vue | 85 + .../src/components/shared/SwitchInput.vue | 37 + .../source/src/components/shared/WBWidget.vue | 56 + .../src/components/shared/WbSubwidget.vue | 86 + .../src/components/shared/WbWidgetFlex.vue | 43 + .../source/src/components/smartHome/model.ts | 49 + .../components/smartHome/processMessages.ts | 161 + .../src/components/statusPage/StatusPage.vue | 25 + .../display_themes/colors/source/src/main.ts | 9 + .../colors/source/src/views/DisplayTheme.vue | 93 + .../colors/source/src/views/NavigationBar.vue | 145 + .../colors/source/tsconfig.json | 23 + .../colors/source/tsconfig.node.json | 19 + .../colors/source/tsconfig.vite-config.json | 9 + .../colors/source/vite.config.ts | 49 + .../web/assets/fa-solid-900-CG7ny7S5.ttf | Bin 0 -> 303480 bytes .../web/assets/fa-solid-900-DByUvYfa.woff2 | Bin 0 -> 126828 bytes .../colors/web/assets/index-BvQvEVyL.css | 9 + .../colors/web/assets/index-DmVcCKQc.js | 6 + .../colors/web/assets/vendor-B-lkTxtL.js | 35 + .../display_themes/colors/web/favicon.ico | Bin 0 -> 4286 bytes .../display_themes/colors/web/index.html | 14 + .../electricity_tariffs/awattar/tariff.py | 3 +- .../energycharts/__init__.py | 0 .../energycharts/config.py | 15 + .../energycharts/tariff.py | 40 + .../electricity_tariffs/rabot/__init__.py | 0 .../electricity_tariffs/rabot/config.py | 31 + .../electricity_tariffs/rabot/tariff.py | 90 + .../electricity_tariffs/tibber/tariff.py | 3 +- .../electricity_tariffs/voltego/tariff.py | 4 +- .../chargepoint_module.py | 4 +- .../internal_chargepoint_handler/clients.py | 4 +- packages/modules/loadvars.py | 6 +- .../dimm_kit/ripple_control_receiver.py | 58 +- .../gpio/ripple_control_receiver.py | 3 +- packages/modules/smarthome/acthor/watt.py | 19 +- packages/modules/vehicles/bmwbc/__init__.py | 0 packages/modules/vehicles/bmwbc/api.py | 139 + packages/modules/vehicles/bmwbc/config.py | 23 + packages/modules/vehicles/bmwbc/soc.py | 45 + packages/modules/vehicles/evcc/api.py | 27 +- packages/modules/vehicles/polestar/api.py | 33 +- packages/modules/vehicles/psa/api.py | 112 +- packages/modules/vehicles/psa/config.py | 2 +- .../modules/vehicles/smarthello/__init__.py | 0 packages/modules/vehicles/smarthello/api.py | 363 ++ .../modules/vehicles/smarthello/config.py | 23 + packages/modules/vehicles/smarthello/soc.py | 23 + .../web_themes/colors/source/package.json | 61 +- .../web_themes/colors/source/shims-mqtt.d.ts | 4 + .../colors/source/src/assets/css/style.css | 4 +- .../colors/source/src/assets/js/helpers.ts | 2 +- .../colors/source/src/assets/js/model.ts | 16 +- .../colors/source/src/assets/js/mqttClient.ts | 22 +- .../source/src/assets/js/themeConfig.ts | 2 + .../colors/source/src/assets/js/types.ts | 6 + .../src/components/batteryList/BLBattery.vue | 15 +- .../components/batteryList/BatteryList.vue | 33 +- .../src/components/buttonBar/BBSelect.vue | 4 +- .../chargePointList/CPChargePoint.vue | 27 +- .../chargePointList/ChargePointList.vue | 4 - .../cpConfig/CPChargeConfig.vue | 17 + .../chargePointList/cpConfig/CPConfigPv.vue | 36 +- .../cpSimpleList/CPSListItem.vue | 2 +- .../cpSimpleList/CPSimpleList.vue | 6 +- .../cpSimpleList/CpSimpleList2.vue | 13 +- .../chargePointList/processMessages.ts | 2 +- .../src/components/counterList/ClCounter.vue | 26 +- .../components/counterList/CounterList.vue | 2 +- .../components/energyMeter/EnergyMeter.vue | 8 +- .../components/inverterList/IlInverter.vue | 13 +- .../components/inverterList/InverterList.vue | 10 +- .../components/powerGraph/PGSourceGraph.vue | 4 +- .../src/components/powerGraph/PgSelector.vue | 38 +- .../src/components/powerGraph/PgToolTips.vue | 6 +- .../src/components/powerGraph/PowerGraph.vue | 12 +- .../source/src/components/powerGraph/model.ts | 48 +- .../powerGraph/processLiveGraphData.ts | 30 +- .../powerGraph/processMonthYearGraphData.ts | 63 +- .../src/components/powerMeter/PMLabel.vue | 12 +- .../priceChart/GlobalPriceChart.vue | 46 +- .../src/components/shared/BatterySymbol.vue | 8 +- .../source/src/components/shared/WBWidget.vue | 4 +- .../source/src/components/shared/WbBadge.vue | 29 + .../src/components/shared/WbSubwidget.vue | 6 +- .../src/components/shared/WbWidgetFlex.vue | 23 +- .../src/components/smartHome/SHListItem.vue | 70 +- .../components/smartHome/SmartHomeList.vue | 12 +- .../components/vehicleList/VehicleList.vue | 2 +- .../src/components/vehicleList/VlVehicle.vue | 2 +- .../web_themes/colors/source/src/main.ts | 1 - .../colors/source/src/router/index.js | 20 - .../colors/source/src/views/ColorsTheme.vue | 2 +- .../colors/source/src/views/ThemeSettings.vue | 4 +- .../colors/source/tsconfig.app.json | 14 + .../web_themes/colors/source/tsconfig.json | 26 +- .../colors/source/tsconfig.node.json | 19 + .../web_themes/colors/source/vite.config.ts | 5 +- .../web/assets/fa-solid-900-CG7ny7S5.ttf | Bin 0 -> 303480 bytes .../web/assets/fa-solid-900-DByUvYfa.woff2 | Bin 0 -> 126828 bytes .../colors/web/assets/index-1385170e.js | 6 - .../colors/web/assets/index-CB5xMAMt.js | 6 + .../colors/web/assets/index-RsvlNgnt.css | 9 + .../colors/web/assets/index-d82a5d32.css | 9 - .../colors/web/assets/vendor-978a58bb.js | 53 - .../colors/web/assets/vendor-DXWWyQ_6.js | 62 + .../modules/web_themes/colors/web/index.html | 7 +- packages/smarthome/smartcommon.py | 66 +- requirements.txt | 3 +- runs/atreboot.sh | 70 +- runs/http-api/v1/index.php | 105 + runs/send_debug.sh | 76 - runs/setup_apache2.sh | 108 + web/display/index.html | 4 +- web/display/processAllMqttMsg.js | 10 +- web/display/themes/colors | 1 + ...eLog-41982ab8.js => ChargeLog-722afad2.js} | 2 +- .../ChargePointInstallation-09031c4e.js | 1 - .../ChargePointInstallation-b2835c0f.js | 1 + web/settings/assets/Chart-21159b5b.js | 1 + web/settings/assets/Chart-ca210cf7.js | 1 - web/settings/assets/CloudConfig-3ed9b804.js | 1 - web/settings/assets/CloudConfig-ea9e35a2.js | 1 + .../assets/DataManagement-703fd7ce.js | 1 - .../assets/DataManagement-9d482bbc.js | 1 + web/settings/assets/DebugConfig-00788e5e.js | 2 - web/settings/assets/DebugConfig-47800e80.js | 2 + .../assets/GeneralChargeConfig-00e2f8a5.js | 1 + .../assets/GeneralChargeConfig-e9abdba5.js | 1 - web/settings/assets/GeneralConfig-bc48aa27.js | 1 - web/settings/assets/GeneralConfig-c69ebde0.js | 1 + .../assets/HardwareInstallation-364c2ab4.js | 1 - .../assets/HardwareInstallation-5af5fbe9.js | 1 + .../assets/InstallAssistant-eda2851f.js | 1 + .../assets/InstallAssistantStep0-a21fa735.js | 1 + .../assets/InstallAssistantStep1-d2a360b9.js | 1 + .../assets/InstallAssistantStep10-c89e1c63.js | 1 + .../assets/InstallAssistantStep2-ffa141aa.js | 1 + .../assets/InstallAssistantStep3-98547931.js | 1 + .../assets/InstallAssistantStep4-40a75ed5.js | 1 + .../assets/InstallAssistantStep5-44af41e7.js | 1 + .../assets/InstallAssistantStep6-7a4708f6.js | 1 + .../assets/InstallAssistantStep7-8e85a8ba.js | 1 + .../assets/InstallAssistantStep8-35eac618.js | 1 + .../assets/InstallAssistantStep9-8722f60d.js | 1 + .../InstallAssistantStepTemplate-61e7a445.js | 1 + .../InstallAssistantStepTemplate-ff479f80.css | 1 + .../assets/InstantChargeConfig-36c3cfa2.js | 1 + .../assets/InstantChargeConfig-eb18cfa6.js | 1 - web/settings/assets/LegalSettings-22fa337f.js | 1 + web/settings/assets/LegalSettings-d586b175.js | 1 - .../assets/LoadManagementConfig-85a943c7.js | 1 + .../assets/LoadManagementConfig-d6cf0978.js | 1 - .../assets/MqttBridgeConfig-35e0451a.js | 1 - .../assets/MqttBridgeConfig-d9dbce27.js | 1 + ...4ae3.js => OpenwbSortableList-83baffc2.js} | 2 +- .../assets/OptionalComponents-582c94d5.js | 2 - .../assets/OptionalComponents-edc6afdc.js | 2 + .../assets/PVChargeConfig-25f62f25.js | 1 - .../assets/PVChargeConfig-fb1a202e.js | 1 + .../assets/ScheduledChargeConfig-a26da09e.js | 1 - .../assets/ScheduledChargeConfig-b97a9b65.js | 1 + ...{Status-76c877c4.js => Status-6ea46c60.js} | 2 +- web/settings/assets/Support-4da61633.js | 1 + web/settings/assets/Support-f5fb936e.js | 1 - web/settings/assets/System-55768eb3.js | 1 + web/settings/assets/System-c2faaea4.js | 1 - web/settings/assets/TestingStore-46b74900.js | 1 - web/settings/assets/TestingStore-55b29644.js | 1 + .../assets/TimeChargeConfig-55c91052.js | 1 - .../assets/TimeChargeConfig-835e9962.js | 1 + web/settings/assets/VehicleConfig-0d88d084.js | 1 - web/settings/assets/VehicleConfig-2146c20d.js | 1 + web/settings/assets/backup_cloud-86d98afb.js | 1 - ...d-348c40e4.js => backup_cloud-b9529fad.js} | 2 +- ...d-2fa5514b.js => backup_cloud-c615e61f.js} | 2 +- ...d-3b0c57b5.js => backup_cloud-e6067096.js} | 2 +- web/settings/assets/backup_cloud-f63441e9.js | 1 + .../{bat-caa3579c.js => bat-0b9b3e59.js} | 2 +- .../{bat-17f90f05.js => bat-0d13c6cf.js} | 2 +- .../{bat-078827ba.js => bat-0fad5685.js} | 2 +- .../{bat-4716250e.js => bat-245b28b2.js} | 2 +- .../{bat-3de07a54.js => bat-529544eb.js} | 2 +- .../{bat-d1bb19e5.js => bat-5319970c.js} | 2 +- .../{bat-427b895a.js => bat-5e9c56af.js} | 2 +- web/settings/assets/bat-602e434a.js | 1 + .../{bat-23446e35.js => bat-68f9ad88.js} | 2 +- .../{bat-8cd34132.js => bat-702b5b7d.js} | 2 +- .../{bat-29444946.js => bat-70c49f50.js} | 2 +- .../{bat-991a2da4.js => bat-7115b6b2.js} | 2 +- .../{bat-6a57da9d.js => bat-73dd1a1b.js} | 2 +- .../{bat-73611913.js => bat-79eb8c84.js} | 2 +- .../{bat-28ed9b26.js => bat-7f759d21.js} | 2 +- .../{bat-1ea26945.js => bat-827eee78.js} | 2 +- .../{bat-896fc8aa.js => bat-82bdf780.js} | 2 +- .../{bat-fdf9bf78.js => bat-879dbf08.js} | 2 +- .../{bat-ad90e9ee.js => bat-895996e9.js} | 2 +- .../{bat-004af275.js => bat-8cbbbe5c.js} | 2 +- .../{bat-b7e731be.js => bat-8e2f16ba.js} | 2 +- .../{bat-b2a2d3f2.js => bat-94aace3d.js} | 2 +- .../{bat-80db94ff.js => bat-9574b509.js} | 2 +- .../{bat-fdd27b23.js => bat-af0b1c39.js} | 2 +- .../{bat-4a6a7c43.js => bat-b4ccfcff.js} | 2 +- web/settings/assets/bat-b587d2a1.js | 1 - web/settings/assets/bat-b8c193c6.js | 1 + .../{bat-0c0b59d8.js => bat-bb395a88.js} | 2 +- .../{bat-80b6905b.js => bat-bd9fa2cb.js} | 2 +- web/settings/assets/bat-c0813514.js | 1 - .../{bat-68b35b74.js => bat-c147ff7e.js} | 2 +- .../{bat-71cd93ee.js => bat-c8262add.js} | 2 +- web/settings/assets/bat-d162819e.js | 1 + .../{bat-6326eecb.js => bat-d5d41762.js} | 2 +- .../{bat-fefef2e9.js => bat-da0cdc25.js} | 2 +- web/settings/assets/bat-e5059d98.js | 1 + web/settings/assets/bat-e8c7f71b.js | 1 + .../{bat-4392d8e4.js => bat-f3f8f533.js} | 2 +- .../{bat-0614e258.js => bat-ffc676b7.js} | 2 +- .../{bat-9a1cb5c1.js => bat-ffee8776.js} | 2 +- ...at_api-30d440c8.js => bat_api-ff0494f9.js} | 2 +- ...bus-5abb62d6.js => bat_modbus-19ae8233.js} | 2 +- ...29e697.js => bat_smart_energy-0f1332a9.js} | 2 +- ...nt-0566cc01.js => chargePoint-1b2b6046.js} | 2 +- ...nt-e717ad09.js => chargePoint-40d383a8.js} | 2 +- ...nt-0b9f9c90.js => chargePoint-4ec3642e.js} | 2 +- ...nt-50edf03b.js => chargePoint-5d9108db.js} | 2 +- ...nt-27dcccca.js => chargePoint-8e001c83.js} | 2 +- ...nt-1ef07072.js => chargePoint-c9d77c2a.js} | 2 +- ...mands-b16cfbca.js => commands-653dec4f.js} | 2 +- .../assets/consumption_counter-21c31eb0.js | 1 + .../assets/consumption_counter-5dce3617.js | 1 - ...ounter-85e4278f.js => counter-02524703.js} | 2 +- ...ounter-c98aa2dc.js => counter-045722ed.js} | 2 +- ...ounter-89cef159.js => counter-08adb306.js} | 2 +- ...ounter-7071f23c.js => counter-0a6841a2.js} | 2 +- ...ounter-e2630477.js => counter-0d97eeba.js} | 2 +- web/settings/assets/counter-12e587b8.js | 1 + ...ounter-778b4337.js => counter-1487841f.js} | 2 +- ...ounter-f45165f1.js => counter-1b2d57ae.js} | 2 +- ...ounter-d8e01f1e.js => counter-1cee7877.js} | 2 +- ...ounter-a252bc48.js => counter-1d1f787e.js} | 2 +- ...ounter-86d5cc78.js => counter-223061cc.js} | 2 +- ...ounter-9dc912f8.js => counter-24314bba.js} | 2 +- web/settings/assets/counter-2753f6f8.js | 1 + web/settings/assets/counter-2a5fb302.js | 1 + ...ounter-727f13a8.js => counter-2ba74204.js} | 2 +- ...ounter-dc0d92e4.js => counter-3018fe00.js} | 2 +- ...ounter-8eb01542.js => counter-349928ea.js} | 2 +- web/settings/assets/counter-354979d7.js | 1 - ...ounter-8dc1093e.js => counter-3adbb6cf.js} | 2 +- web/settings/assets/counter-4dd61d66.js | 1 + ...ounter-3e3b4646.js => counter-525ffd50.js} | 2 +- ...ounter-4d89398e.js => counter-560cce16.js} | 2 +- web/settings/assets/counter-5bd73746.js | 1 - ...ounter-951a835d.js => counter-5e5d845c.js} | 2 +- ...ounter-dc0290a5.js => counter-60457116.js} | 2 +- ...ounter-d9c2b225.js => counter-62b5d0c6.js} | 2 +- ...ounter-93fefb5d.js => counter-66c51558.js} | 2 +- ...ounter-e24e0897.js => counter-688947fd.js} | 2 +- ...ounter-9fa93c7a.js => counter-6ec5fe05.js} | 2 +- ...ounter-0e83fdf2.js => counter-7ae2f188.js} | 2 +- ...ounter-63366dbc.js => counter-81bbf3a9.js} | 2 +- web/settings/assets/counter-85a387e7.js | 1 + ...ounter-31875047.js => counter-8e3b5675.js} | 2 +- ...ounter-8cc2b538.js => counter-973013db.js} | 2 +- web/settings/assets/counter-9c0a2d69.js | 1 + web/settings/assets/counter-a04670ec.js | 1 + ...ounter-24785cb2.js => counter-a699cc16.js} | 2 +- ...ounter-ba87c672.js => counter-ae412bbb.js} | 2 +- ...ounter-2caa83da.js => counter-aecfef3f.js} | 2 +- ...ounter-5e598793.js => counter-b2c31625.js} | 2 +- ...ounter-b00adb82.js => counter-b9a2d196.js} | 2 +- ...ounter-1353cd87.js => counter-be679886.js} | 2 +- ...ounter-99aae1f4.js => counter-c382fe07.js} | 2 +- ...ounter-cabc879f.js => counter-c4022d6d.js} | 2 +- ...ounter-e949dc78.js => counter-c500661a.js} | 2 +- web/settings/assets/counter-c93ee66d.js | 1 - web/settings/assets/counter-d4d8ab4d.js | 1 - ...ounter-c56a24bd.js => counter-e14d11c9.js} | 2 +- ...ounter-8babe15d.js => counter-e5a551c2.js} | 2 +- web/settings/assets/counter-e6dea4a1.js | 1 + web/settings/assets/counter-ed091fbe.js | 1 - ...ounter-16a12798.js => counter-eef9fbde.js} | 2 +- ...ounter-b9eae16e.js => counter-f4ede2ab.js} | 2 +- ...ounter-c31d3c2d.js => counter-f5bdf5b7.js} | 2 +- web/settings/assets/counter-f7a4cb2f.js | 1 + ..._s0-c93542b2.js => counter_s0-8776160e.js} | 2 +- ..._sm-0a095ddb.js => counter_sm-9779fb75.js} | 2 +- ...{device-5a899a6c.js => device-0083b877.js} | 2 +- ...{device-8176ba3b.js => device-024a1916.js} | 2 +- ...{device-3a0a95ea.js => device-0381dde8.js} | 2 +- ...{device-7b3f38c2.js => device-03f42ff3.js} | 2 +- ...{device-eb55288a.js => device-04cd5fc1.js} | 2 +- ...{device-5dacd4ed.js => device-0780972d.js} | 2 +- ...{device-fe43a3a0.js => device-08a28a0a.js} | 2 +- ...{device-3de8b661.js => device-0c29a881.js} | 2 +- ...{device-b18ccf96.js => device-16376bb4.js} | 2 +- ...{device-fae80432.js => device-17cc93f5.js} | 2 +- ...{device-65b723d1.js => device-1dc353b4.js} | 2 +- ...{device-becdaae3.js => device-23840023.js} | 2 +- ...{device-591eb549.js => device-25e63451.js} | 2 +- ...{device-6470e0bb.js => device-26757aeb.js} | 2 +- ...{device-eb08de6d.js => device-29504c9a.js} | 2 +- web/settings/assets/device-297790d4.js | 1 - ...{device-fecbff51.js => device-2f3fabdc.js} | 2 +- ...{device-e9b3840b.js => device-309ffadc.js} | 2 +- ...{device-1da1c02e.js => device-31b159f9.js} | 2 +- ...{device-5ac87fdd.js => device-370d2d3b.js} | 2 +- ...{device-450208c0.js => device-3963e5da.js} | 2 +- web/settings/assets/device-43209216.js | 1 - web/settings/assets/device-471a07ab.js | 1 + ...{device-8375d21a.js => device-4a5c725a.js} | 2 +- ...{device-53dc31db.js => device-4f45d53d.js} | 2 +- ...{device-cb8b26ed.js => device-500e5018.js} | 2 +- ...{device-b3c52c36.js => device-52ddf35f.js} | 2 +- ...{device-168f30ca.js => device-5637bb4a.js} | 2 +- ...{device-747bfa75.js => device-59de8958.js} | 2 +- ...{device-19d7d953.js => device-5a1071a1.js} | 2 +- ...{device-fd8370a3.js => device-6626c768.js} | 2 +- ...{device-b42da65f.js => device-6c8cf01b.js} | 2 +- ...{device-c12bd44e.js => device-6d155fbc.js} | 2 +- web/settings/assets/device-720eb4cb.js | 1 + ...{device-4af5ac8f.js => device-730be313.js} | 2 +- ...{device-f037488c.js => device-77bcb56a.js} | 2 +- ...{device-2fffc73f.js => device-79c0c109.js} | 2 +- ...{device-8d96d63b.js => device-7b5e366a.js} | 2 +- web/settings/assets/device-80543318.js | 1 - ...{device-f9fac383.js => device-87eec55b.js} | 2 +- ...{device-4519dc95.js => device-889c26e6.js} | 2 +- ...{device-a781dd9f.js => device-8941f395.js} | 2 +- ...{device-7e746f01.js => device-8f942b3e.js} | 2 +- ...{device-fb080c72.js => device-9848f60c.js} | 2 +- ...{device-39a18394.js => device-a01f0692.js} | 2 +- ...{device-3a4a9885.js => device-a2663b8f.js} | 2 +- ...{device-af058452.js => device-a83462a8.js} | 2 +- ...{device-958c98ed.js => device-ac3bc820.js} | 2 +- ...{device-db271319.js => device-ad3020fa.js} | 2 +- web/settings/assets/device-b0b72ec4.js | 1 + ...{device-3b0bdf64.js => device-b6d28a9b.js} | 2 +- ...{device-5ba21463.js => device-bdc24b1b.js} | 2 +- ...{device-0cd45d58.js => device-c12c5098.js} | 2 +- ...{device-225eb0a9.js => device-c882a99d.js} | 2 +- web/settings/assets/device-cb4bf319.js | 1 + ...{device-420ad191.js => device-cf2d027e.js} | 2 +- web/settings/assets/device-d904f113.js | 1 + web/settings/assets/device-d9dc3bf5.js | 1 + ...{device-96b42d7a.js => device-da6133e0.js} | 2 +- web/settings/assets/device-e0515fb9.js | 1 + ...{device-ef440406.js => device-e37865e7.js} | 2 +- ...{device-f8cc6d79.js => device-e3958f31.js} | 2 +- ...{device-33a63868.js => device-e965df80.js} | 2 +- ...{device-03854029.js => device-e9b80205.js} | 2 +- web/settings/assets/device-f172e832.js | 1 - ...{device-98243109.js => device-f248d2b7.js} | 2 +- web/settings/assets/device-f34d002a.js | 1 + web/settings/assets/device-f9e4faac.js | 1 - ...{device-7330dbf4.js => device-fc0d2464.js} | 2 +- ...{device-ed53f77f.js => device-fde3c802.js} | 2 +- web/settings/assets/displayTheme-0f1ce346.js | 1 + web/settings/assets/displayTheme-30096b05.js | 1 - .../assets/electricity_tariff-1a39d75f.js | 1 + ...0fd5.js => electricity_tariff-2f2bda3c.js} | 2 +- ...e44c.js => electricity_tariff-440299b5.js} | 2 +- .../assets/electricity_tariff-7a249832.js | 1 + ...18a6.js => electricity_tariff-ff5ad3ad.js} | 2 +- ...dc747.js => external_inverter-351fbf76.js} | 2 +- .../assets/external_inverter-6d24a89f.js | 1 + ...da41b.js => external_inverter-ad1ea734.js} | 2 +- .../assets/external_inverter-c72c8a10.js | 1 - web/settings/assets/index-d084f6bd.js | 1 + web/settings/assets/index-d5bc6d44.js | 1 - ...erter-8728d3e0.js => inverter-03b48a6c.js} | 2 +- ...erter-1c9d8a02.js => inverter-0518b9c4.js} | 2 +- ...erter-91d3a655.js => inverter-07445706.js} | 2 +- ...erter-c966de6c.js => inverter-0a398559.js} | 2 +- ...erter-d441061a.js => inverter-0cb29727.js} | 2 +- ...erter-469c35df.js => inverter-0cb645b6.js} | 2 +- ...erter-e2480bca.js => inverter-0fc52861.js} | 2 +- web/settings/assets/inverter-13cc5687.js | 1 + ...erter-66c8e5e6.js => inverter-1d3f4df2.js} | 2 +- ...erter-ac40e842.js => inverter-1def4e9b.js} | 2 +- ...erter-c3b1b600.js => inverter-25c2a95f.js} | 2 +- ...erter-e15ddd14.js => inverter-290bf9e6.js} | 2 +- ...erter-b37fb584.js => inverter-2ef7a50f.js} | 2 +- ...erter-dfc55df7.js => inverter-30e941c7.js} | 2 +- ...erter-643f7cf4.js => inverter-3c2411f5.js} | 2 +- ...erter-f55c59cd.js => inverter-44f1ec26.js} | 2 +- web/settings/assets/inverter-50c21455.js | 1 - web/settings/assets/inverter-52398948.js | 1 - ...erter-318e7901.js => inverter-5476d4c0.js} | 2 +- ...erter-3aae24f8.js => inverter-56673e8b.js} | 2 +- ...erter-9732490a.js => inverter-5a6f70ef.js} | 2 +- ...erter-5b451c75.js => inverter-5eff776c.js} | 2 +- ...erter-2ad2b236.js => inverter-6099d12f.js} | 2 +- ...erter-8a407cb7.js => inverter-6232c685.js} | 2 +- ...erter-1989d0b6.js => inverter-6383f684.js} | 2 +- web/settings/assets/inverter-63d272fb.js | 1 + ...erter-e221132d.js => inverter-6b9571c6.js} | 2 +- ...erter-abea47d7.js => inverter-7d5686ca.js} | 2 +- web/settings/assets/inverter-7e9d0c38.js | 1 - ...erter-d8dec1b6.js => inverter-8174966c.js} | 2 +- ...erter-d7dea42e.js => inverter-8e01a109.js} | 2 +- ...erter-5a418d51.js => inverter-91ab3bcb.js} | 2 +- ...erter-13512c6a.js => inverter-92732c4f.js} | 2 +- ...erter-01de9df1.js => inverter-a1ec827b.js} | 2 +- ...erter-1b94372e.js => inverter-a2ac853c.js} | 2 +- ...erter-c5d45c65.js => inverter-aadffff7.js} | 2 +- ...erter-de64dbcb.js => inverter-ae7fa269.js} | 2 +- web/settings/assets/inverter-b4208566.js | 1 + ...erter-92d95012.js => inverter-b492477c.js} | 2 +- ...erter-1991d27a.js => inverter-b5a17f56.js} | 2 +- ...erter-5d155af2.js => inverter-b7d8947f.js} | 2 +- ...erter-daca8a0a.js => inverter-b82a23a1.js} | 2 +- ...erter-2bfcc388.js => inverter-bcb5d470.js} | 2 +- ...erter-fe835151.js => inverter-c036e5dd.js} | 2 +- ...erter-63c2a9d6.js => inverter-cd80a37d.js} | 2 +- ...erter-d4dc1157.js => inverter-d5417159.js} | 2 +- ...erter-82c0d3d4.js => inverter-d5c1d2ce.js} | 2 +- web/settings/assets/inverter-d9845157.js | 1 + ...erter-1a56f664.js => inverter-db998d6f.js} | 2 +- ...erter-6b7e698c.js => inverter-e08e67af.js} | 2 +- web/settings/assets/inverter-e6e2509b.js | 1 + ...erter-8720288d.js => inverter-eb0d44a6.js} | 2 +- ...erter-a5cd751f.js => inverter-f1bb5f21.js} | 2 +- ...erter-291f8d33.js => inverter-f755785a.js} | 2 +- ...erter-8911e4cb.js => inverter-f9ec521f.js} | 2 +- ...erter-e87230d9.js => inverter-faf10f31.js} | 2 +- ...8ef8.js => inverter_secondary-2d6a7628.js} | 2 +- ...js => ripple_control_receiver-0fec0f83.js} | 2 +- ...js => ripple_control_receiver-a6a97d4e.js} | 2 +- ...ehicle-a56ffc15.js => vehicle-05135c16.js} | 2 +- ...ehicle-4b4e98b0.js => vehicle-0f184c8b.js} | 2 +- ...ehicle-abd3908a.js => vehicle-1b88fc5e.js} | 2 +- ...ehicle-0aac4e2a.js => vehicle-1d9e5107.js} | 2 +- web/settings/assets/vehicle-25136235.js | 1 + ...ehicle-3dbed7e0.js => vehicle-2742b8e8.js} | 2 +- web/settings/assets/vehicle-295d8f60.js | 1 - ...ehicle-de59f8dc.js => vehicle-2a287ab7.js} | 2 +- ...ehicle-f15518b8.js => vehicle-32ab6e9b.js} | 2 +- ...ehicle-04a6570f.js => vehicle-35042811.js} | 2 +- web/settings/assets/vehicle-4569ab36.js | 1 + ...ehicle-cd111671.js => vehicle-591dc321.js} | 2 +- ...ehicle-1df7269f.js => vehicle-653b73be.js} | 2 +- ...ehicle-e685fd4b.js => vehicle-7c7a06a1.js} | 2 +- ...ehicle-067609bb.js => vehicle-80836d40.js} | 2 +- ...ehicle-bfbabfd0.js => vehicle-ab1e8b2c.js} | 2 +- ...ehicle-23efacf4.js => vehicle-c1302901.js} | 2 +- web/settings/assets/vehicle-e3f6e0f8.js | 1 + ...s-e5457936.js => vendor-axios-dc7988e3.js} | 2 +- web/settings/assets/vendor-b3afda6d.js | 135 + ...3ddddf.js => vendor-bootstrap-37731caa.js} | 2 +- ...4d79ce0b.js => vendor-chartjs-2789d664.js} | 2 +- web/settings/assets/vendor-d624aab7.js | 135 - ...fc1e.js => vendor-fortawesome-b013cb5c.js} | 6 +- ...-b0321e08.js => vendor-jquery-2371184a.js} | 2 +- ...cb1ea.js => vendor-sortablejs-806a0b5c.js} | 2 +- web/settings/downloadChargeLog.php | 100 +- web/settings/img/openWB_logo_light.png | Bin 0 -> 9603 bytes web/settings/index.html | 14 +- .../legacy_smart_home/smarthomeconfig.php | 5 +- web/version | 2 +- 809 files changed, 30181 insertions(+), 6232 deletions(-) create mode 100644 data/config/apache/http-api-ssl.conf create mode 100644 data/config/ramdisk_config.txt create mode 100644 docs/Anzeige-Steuerung.md create mode 100644 docs/Fehlersuche.md create mode 100644 docs/Huawei-Smartlogger.md create mode 100644 docs/HuaweiSmartloggerKomponenten.PNG create mode 100644 docs/HuaweiSmartloggerLogischeAdressen.PNG create mode 100644 docs/HuaweiSmartloggerModBusTCP.PNG create mode 100644 docs/MQTT.md delete mode 100644 "docs/Neues Ger\303\244t programmieren.md" create mode 100644 docs/Neues Modul programmieren.md delete mode 100644 docs/Neues Soc-Modul programmieren.md create mode 100644 docs/Tutorial create mode 100644 docs/Typische-Anwendungsfaelle.md create mode 100644 docs/Zaehler.md delete mode 100644 "docs/Z\303\244hler.md" create mode 100644 docs/pictures/Anwendungsfaelle_minStrom.jpg create mode 100644 docs/pictures/Anwendungsfaelle_zielladen.jpg create mode 100644 docs/pictures/Fehlersuche_DebugLog.jpg create mode 100644 docs/pictures/Fehlersuche_Main-Log.jpg create mode 100644 docs/pictures/MQTT_Konsole_PV-Laden.jpg create mode 100644 docs/pictures/MQTT_Konsole_Sofortladen.jpg delete mode 100644 docs/pictures/Wiki-Eintrag erstellen_Pull.jpg rename docs/pictures/{Wiki-Eintrag erstellen_Fork.png => Wiki-Eintrag_erstellen_Fork.png} (100%) create mode 100644 docs/pictures/Wiki-Eintrag_erstellen_Pull.jpg create mode 100644 package-lock.json create mode 100644 packages/control/chargelog/chargelog_test.py create mode 100644 packages/helpermodules/create_debug.py delete mode 100644 packages/helpermodules/parse_send_debug.py create mode 100644 packages/helpermodules/utils/json_file_handler.py create mode 100644 packages/helpermodules/utils/json_file_handler_test.py create mode 100644 packages/helpermodules/utils/run_command.py delete mode 100644 packages/modules/common/hardware_check_context.py create mode 100644 packages/modules/devices/fox_ess/bat.py create mode 100644 packages/modules/devices/fox_ess/config.py create mode 100644 packages/modules/devices/fox_ess/counter.py create mode 100644 packages/modules/devices/fox_ess/device.py create mode 100644 packages/modules/devices/fox_ess/inverter.py create mode 100644 packages/modules/devices/good_we/version.py create mode 100644 packages/modules/devices/mtec/bat.py create mode 100644 packages/modules/devices/mtec/config.py create mode 100644 packages/modules/devices/mtec/counter.py create mode 100644 packages/modules/devices/mtec/device.py create mode 100644 packages/modules/devices/mtec/inverter.py create mode 100644 packages/modules/devices/shelly/bat.py create mode 100644 packages/modules/devices/shelly/counter.py delete mode 100644 packages/modules/display_themes/cards/source/.eslintrc.cjs create mode 100644 packages/modules/display_themes/cards/source/eslint.config.js create mode 100644 packages/modules/display_themes/cards/source/public/icons/icon_Data.md create mode 100644 packages/modules/display_themes/cards/source/public/icons/owbBattery.svg create mode 100644 packages/modules/display_themes/cards/source/public/icons/owbChargePoint.svg create mode 100644 packages/modules/display_themes/cards/source/public/icons/owbGrid.svg create mode 100644 packages/modules/display_themes/cards/source/public/icons/owbHouse.svg create mode 100644 packages/modules/display_themes/cards/source/public/icons/owbPV.svg create mode 100644 packages/modules/display_themes/cards/source/public/icons/owbVehicle.svg create mode 100644 packages/modules/display_themes/cards/source/src/components/ChargePoints/ChargePointCard.vue create mode 100644 packages/modules/display_themes/cards/source/src/components/ChargePoints/SimpleChargePointCard.vue create mode 100644 packages/modules/display_themes/cards/source/src/components/DashBoard/FlowCard.vue create mode 100644 packages/modules/display_themes/cards/source/src/views/EnergyFlowView.vue delete mode 100644 packages/modules/display_themes/cards/web/assets/ChargePointPlugBadge-6FxVjZH4.js create mode 100644 packages/modules/display_themes/cards/web/assets/ChargePointPlugBadge-CP9QLc8z.js create mode 100644 packages/modules/display_themes/cards/web/assets/ChargePointPlugBadge-DqNX7D_z.css delete mode 100644 packages/modules/display_themes/cards/web/assets/ChargePointPlugBadge-rhc0EdNG.css create mode 100644 packages/modules/display_themes/cards/web/assets/ChargePointsView-DWVNNV7U.js create mode 100644 packages/modules/display_themes/cards/web/assets/ChargePointsView-DvlDrERC.css delete mode 100644 packages/modules/display_themes/cards/web/assets/ChargePointsView-TjZNhXcr.js delete mode 100644 packages/modules/display_themes/cards/web/assets/ChargePointsView-anjjZlvZ.css create mode 100644 packages/modules/display_themes/cards/web/assets/DashBoardCard-ChX0nYSF.js create mode 100644 packages/modules/display_themes/cards/web/assets/DashBoardCard-DKbC8x2S.css delete mode 100644 packages/modules/display_themes/cards/web/assets/DashBoardCard-JoQ8MVPZ.css delete mode 100644 packages/modules/display_themes/cards/web/assets/DashBoardCard-qEoaavxr.js rename packages/modules/display_themes/cards/web/assets/{DashBoardView-7-0x0RbA.css => DashBoardView-CrVFeUF-.css} (53%) create mode 100644 packages/modules/display_themes/cards/web/assets/DashBoardView-Mm5SqgY_.js delete mode 100644 packages/modules/display_themes/cards/web/assets/DashBoardView-QZWzjOit.js create mode 100644 packages/modules/display_themes/cards/web/assets/EnergyFlowView-CdniTPDx.js create mode 100644 packages/modules/display_themes/cards/web/assets/EnergyFlowView-VrFiHW_2.css create mode 100644 packages/modules/display_themes/cards/web/assets/StatusView-Bb96bsZ0.js delete mode 100644 packages/modules/display_themes/cards/web/assets/StatusView-_Im55Cq_.js create mode 100644 packages/modules/display_themes/cards/web/assets/index-Dlb0hnJU.js create mode 100644 packages/modules/display_themes/cards/web/assets/index-Gvrm5_Z6.css delete mode 100644 packages/modules/display_themes/cards/web/assets/index-Jo0qhDRH.js delete mode 100644 packages/modules/display_themes/cards/web/assets/index-fiBjfcgV.css create mode 100644 packages/modules/display_themes/cards/web/assets/vendor-DcFd2nmT.js create mode 100644 packages/modules/display_themes/cards/web/assets/vendor-fortawesome-1k8kfR6t.js delete mode 100644 packages/modules/display_themes/cards/web/assets/vendor-fortawesome-S-qqChZo.js create mode 100644 packages/modules/display_themes/cards/web/assets/vendor-inkline-B7Agb5gs.css delete mode 100644 packages/modules/display_themes/cards/web/assets/vendor-inkline-Dxj-ZgXa.css rename packages/modules/display_themes/cards/web/assets/{vendor-inkline-F2zxVbzD.js => vendor-inkline-GZyOLkvD.js} (99%) delete mode 100644 packages/modules/display_themes/cards/web/assets/vendor-xCcLvHvG.js create mode 100644 packages/modules/display_themes/cards/web/icons/icon_Data.md create mode 100644 packages/modules/display_themes/cards/web/icons/owbBattery.svg create mode 100644 packages/modules/display_themes/cards/web/icons/owbChargePoint.svg create mode 100644 packages/modules/display_themes/cards/web/icons/owbGrid.svg create mode 100644 packages/modules/display_themes/cards/web/icons/owbHouse.svg create mode 100644 packages/modules/display_themes/cards/web/icons/owbPV.svg create mode 100644 packages/modules/display_themes/cards/web/icons/owbVehicle.svg create mode 100755 packages/modules/display_themes/colors/config.py create mode 100644 packages/modules/display_themes/colors/source/.eslintrc create mode 100644 packages/modules/display_themes/colors/source/.gitignore create mode 100755 packages/modules/display_themes/colors/source/.prettierignore rename packages/modules/{web_themes/colors/source/prettierrc.json => display_themes/colors/source/.prettierrc.json} (59%) mode change 100755 => 100644 create mode 100644 packages/modules/display_themes/colors/source/.vscode/extensions.json create mode 100644 packages/modules/display_themes/colors/source/README.md create mode 100644 packages/modules/display_themes/colors/source/env.d.ts create mode 100644 packages/modules/display_themes/colors/source/index.html create mode 100644 packages/modules/display_themes/colors/source/package-lock.json create mode 100644 packages/modules/display_themes/colors/source/package.json create mode 100644 packages/modules/display_themes/colors/source/public/favicon.ico create mode 100644 packages/modules/display_themes/colors/source/src/App.vue create mode 100644 packages/modules/display_themes/colors/source/src/assets/base.css create mode 100755 packages/modules/display_themes/colors/source/src/assets/css/style.css create mode 100755 packages/modules/display_themes/colors/source/src/assets/fonts/fontawesome-free-6.0.0-web/LICENSE.txt create mode 100755 packages/modules/display_themes/colors/source/src/assets/fonts/fontawesome-free-6.0.0-web/css/._all.css create mode 100755 packages/modules/display_themes/colors/source/src/assets/fonts/fontawesome-free-6.0.0-web/css/._all.min.css create mode 100755 packages/modules/display_themes/colors/source/src/assets/fonts/fontawesome-free-6.0.0-web/css/._fontawesome.css create mode 100755 packages/modules/display_themes/colors/source/src/assets/fonts/fontawesome-free-6.0.0-web/css/._fontawesome.min.css create mode 100755 packages/modules/display_themes/colors/source/src/assets/fonts/fontawesome-free-6.0.0-web/css/._regular.css create mode 100755 packages/modules/display_themes/colors/source/src/assets/fonts/fontawesome-free-6.0.0-web/css/._regular.min.css create mode 100755 packages/modules/display_themes/colors/source/src/assets/fonts/fontawesome-free-6.0.0-web/css/._solid.css create mode 100755 packages/modules/display_themes/colors/source/src/assets/fonts/fontawesome-free-6.0.0-web/css/._solid.min.css create mode 100755 packages/modules/display_themes/colors/source/src/assets/fonts/fontawesome-free-6.0.0-web/css/._svg-with-js.css create mode 100755 packages/modules/display_themes/colors/source/src/assets/fonts/fontawesome-free-6.0.0-web/css/._svg-with-js.min.css create mode 100755 packages/modules/display_themes/colors/source/src/assets/fonts/fontawesome-free-6.0.0-web/css/._v4-font-face.css create mode 100755 packages/modules/display_themes/colors/source/src/assets/fonts/fontawesome-free-6.0.0-web/css/._v4-font-face.min.css create mode 100755 packages/modules/display_themes/colors/source/src/assets/fonts/fontawesome-free-6.0.0-web/css/._v4-shims.css create mode 100755 packages/modules/display_themes/colors/source/src/assets/fonts/fontawesome-free-6.0.0-web/css/._v4-shims.min.css create mode 100755 packages/modules/display_themes/colors/source/src/assets/fonts/fontawesome-free-6.0.0-web/css/._v5-font-face.css create mode 100755 packages/modules/display_themes/colors/source/src/assets/fonts/fontawesome-free-6.0.0-web/css/._v5-font-face.min.css create mode 100755 packages/modules/display_themes/colors/source/src/assets/fonts/fontawesome-free-6.0.0-web/css/fontawesome.min.css create mode 100755 packages/modules/display_themes/colors/source/src/assets/fonts/fontawesome-free-6.0.0-web/css/solid.min.css rename packages/modules/{web_themes/colors/web/assets/fa-solid-900-1782156b.ttf => display_themes/colors/source/src/assets/fonts/fontawesome-free-6.0.0-web/webfonts/fa-solid-900.ttf} (100%) mode change 100644 => 100755 rename packages/modules/{web_themes/colors/web/assets/fa-solid-900-1b099f88.woff2 => display_themes/colors/source/src/assets/fonts/fontawesome-free-6.0.0-web/webfonts/fa-solid-900.woff2} (100%) mode change 100644 => 100755 create mode 100755 packages/modules/display_themes/colors/source/src/assets/js/helpers.ts create mode 100755 packages/modules/display_themes/colors/source/src/assets/js/model.ts create mode 100755 packages/modules/display_themes/colors/source/src/assets/js/mqttClient.ts create mode 100755 packages/modules/display_themes/colors/source/src/assets/js/processMessages.ts create mode 100755 packages/modules/display_themes/colors/source/src/assets/js/sendMessages.ts create mode 100755 packages/modules/display_themes/colors/source/src/assets/js/themeConfig.ts create mode 100755 packages/modules/display_themes/colors/source/src/assets/js/types.ts create mode 100644 packages/modules/display_themes/colors/source/src/assets/logo.svg create mode 100644 packages/modules/display_themes/colors/source/src/assets/main.css create mode 100755 packages/modules/display_themes/colors/source/src/components/batteryList/model.ts create mode 100755 packages/modules/display_themes/colors/source/src/components/batteryList/processMessages.ts create mode 100755 packages/modules/display_themes/colors/source/src/components/chargePointList/CPChargePoint.vue create mode 100755 packages/modules/display_themes/colors/source/src/components/chargePointList/cpConfig/CPChargeConfig.vue create mode 100755 packages/modules/display_themes/colors/source/src/components/chargePointList/cpConfig/CPChargeConfigPanel.vue create mode 100755 packages/modules/display_themes/colors/source/src/components/chargePointList/cpConfig/CPConfigInstant.vue create mode 100755 packages/modules/display_themes/colors/source/src/components/chargePointList/cpConfig/CPConfigPv.vue create mode 100755 packages/modules/display_themes/colors/source/src/components/chargePointList/cpConfig/CPConfigScheduled.vue create mode 100755 packages/modules/display_themes/colors/source/src/components/chargePointList/cpConfig/CPConfigTimed.vue create mode 100755 packages/modules/display_themes/colors/source/src/components/chargePointList/cpConfig/CPConfigVehicle.vue create mode 100755 packages/modules/display_themes/colors/source/src/components/chargePointList/model.ts create mode 100755 packages/modules/display_themes/colors/source/src/components/chargePointList/processMessages.ts create mode 100755 packages/modules/display_themes/colors/source/src/components/counterList/model.ts create mode 100755 packages/modules/display_themes/colors/source/src/components/energyMeter/EMBarGraph.vue create mode 100755 packages/modules/display_themes/colors/source/src/components/energyMeter/EMLabels.vue create mode 100755 packages/modules/display_themes/colors/source/src/components/energyMeter/EMYAxis.vue create mode 100755 packages/modules/display_themes/colors/source/src/components/energyMeter/EmBar.vue create mode 100755 packages/modules/display_themes/colors/source/src/components/energyMeter/EmLabel.vue create mode 100755 packages/modules/display_themes/colors/source/src/components/energyMeter/EmPopup.vue create mode 100755 packages/modules/display_themes/colors/source/src/components/energyMeter/EmPopups.vue create mode 100755 packages/modules/display_themes/colors/source/src/components/energyMeter/EnergyMeter.vue create mode 100644 packages/modules/display_themes/colors/source/src/components/icons/IconCommunity.vue create mode 100644 packages/modules/display_themes/colors/source/src/components/icons/IconDocumentation.vue create mode 100644 packages/modules/display_themes/colors/source/src/components/icons/IconEcosystem.vue create mode 100644 packages/modules/display_themes/colors/source/src/components/icons/IconSupport.vue create mode 100644 packages/modules/display_themes/colors/source/src/components/icons/IconTooling.vue create mode 100755 packages/modules/display_themes/colors/source/src/components/powerGraph/PGSourceGraph.vue create mode 100755 packages/modules/display_themes/colors/source/src/components/powerGraph/PGUsageGraph.vue create mode 100755 packages/modules/display_themes/colors/source/src/components/powerGraph/PGXAxis.vue create mode 100755 packages/modules/display_themes/colors/source/src/components/powerGraph/PgSelector.vue create mode 100755 packages/modules/display_themes/colors/source/src/components/powerGraph/PgSoc.vue create mode 100755 packages/modules/display_themes/colors/source/src/components/powerGraph/PgSocAxis.vue create mode 100755 packages/modules/display_themes/colors/source/src/components/powerGraph/PowerGraph.vue create mode 100755 packages/modules/display_themes/colors/source/src/components/powerGraph/model.ts create mode 100755 packages/modules/display_themes/colors/source/src/components/powerGraph/processDayGraphData.ts create mode 100755 packages/modules/display_themes/colors/source/src/components/powerGraph/processLiveGraphData.ts create mode 100755 packages/modules/display_themes/colors/source/src/components/powerGraph/processMonthYearGraphData.ts create mode 100755 packages/modules/display_themes/colors/source/src/components/powerMeter/PMLabel.vue create mode 100755 packages/modules/display_themes/colors/source/src/components/powerMeter/PMSourceArc.vue create mode 100755 packages/modules/display_themes/colors/source/src/components/powerMeter/PMUsageArc.vue create mode 100755 packages/modules/display_themes/colors/source/src/components/powerMeter/PowerMeter.vue create mode 100755 packages/modules/display_themes/colors/source/src/components/priceChart/PriceChart.vue create mode 100755 packages/modules/display_themes/colors/source/src/components/priceChart/model.ts create mode 100755 packages/modules/display_themes/colors/source/src/components/priceChart/processMessages.ts create mode 100755 packages/modules/display_themes/colors/source/src/components/shared/BatterySymbol.vue create mode 100755 packages/modules/display_themes/colors/source/src/components/shared/ConfigItem.vue create mode 100755 packages/modules/display_themes/colors/source/src/components/shared/DateInput.vue create mode 100755 packages/modules/display_themes/colors/source/src/components/shared/DisplayButton.vue create mode 100755 packages/modules/display_themes/colors/source/src/components/shared/FormatWatt.vue create mode 100755 packages/modules/display_themes/colors/source/src/components/shared/FormatWattH.vue create mode 100755 packages/modules/display_themes/colors/source/src/components/shared/InfoItem.vue create mode 100755 packages/modules/display_themes/colors/source/src/components/shared/ModalComponent.vue create mode 100755 packages/modules/display_themes/colors/source/src/components/shared/NumberPad.vue create mode 100755 packages/modules/display_themes/colors/source/src/components/shared/PadButton.vue create mode 100755 packages/modules/display_themes/colors/source/src/components/shared/RadioBarInput.vue create mode 100755 packages/modules/display_themes/colors/source/src/components/shared/RadioInput.vue create mode 100755 packages/modules/display_themes/colors/source/src/components/shared/RangeInput.vue create mode 100755 packages/modules/display_themes/colors/source/src/components/shared/SwitchInput.vue create mode 100755 packages/modules/display_themes/colors/source/src/components/shared/WBWidget.vue create mode 100755 packages/modules/display_themes/colors/source/src/components/shared/WbSubwidget.vue create mode 100755 packages/modules/display_themes/colors/source/src/components/shared/WbWidgetFlex.vue create mode 100755 packages/modules/display_themes/colors/source/src/components/smartHome/model.ts create mode 100755 packages/modules/display_themes/colors/source/src/components/smartHome/processMessages.ts create mode 100644 packages/modules/display_themes/colors/source/src/components/statusPage/StatusPage.vue create mode 100644 packages/modules/display_themes/colors/source/src/main.ts create mode 100644 packages/modules/display_themes/colors/source/src/views/DisplayTheme.vue create mode 100755 packages/modules/display_themes/colors/source/src/views/NavigationBar.vue create mode 100644 packages/modules/display_themes/colors/source/tsconfig.json create mode 100644 packages/modules/display_themes/colors/source/tsconfig.node.json create mode 100755 packages/modules/display_themes/colors/source/tsconfig.vite-config.json create mode 100644 packages/modules/display_themes/colors/source/vite.config.ts create mode 100644 packages/modules/display_themes/colors/web/assets/fa-solid-900-CG7ny7S5.ttf create mode 100644 packages/modules/display_themes/colors/web/assets/fa-solid-900-DByUvYfa.woff2 create mode 100644 packages/modules/display_themes/colors/web/assets/index-BvQvEVyL.css create mode 100644 packages/modules/display_themes/colors/web/assets/index-DmVcCKQc.js create mode 100644 packages/modules/display_themes/colors/web/assets/vendor-B-lkTxtL.js create mode 100644 packages/modules/display_themes/colors/web/favicon.ico create mode 100644 packages/modules/display_themes/colors/web/index.html create mode 100644 packages/modules/electricity_tariffs/energycharts/__init__.py create mode 100644 packages/modules/electricity_tariffs/energycharts/config.py create mode 100644 packages/modules/electricity_tariffs/energycharts/tariff.py create mode 100644 packages/modules/electricity_tariffs/rabot/__init__.py create mode 100644 packages/modules/electricity_tariffs/rabot/config.py create mode 100644 packages/modules/electricity_tariffs/rabot/tariff.py create mode 100644 packages/modules/vehicles/bmwbc/__init__.py create mode 100755 packages/modules/vehicles/bmwbc/api.py create mode 100755 packages/modules/vehicles/bmwbc/config.py create mode 100755 packages/modules/vehicles/bmwbc/soc.py create mode 100644 packages/modules/vehicles/smarthello/__init__.py create mode 100644 packages/modules/vehicles/smarthello/api.py create mode 100644 packages/modules/vehicles/smarthello/config.py create mode 100644 packages/modules/vehicles/smarthello/soc.py create mode 100644 packages/modules/web_themes/colors/source/shims-mqtt.d.ts create mode 100644 packages/modules/web_themes/colors/source/src/components/shared/WbBadge.vue delete mode 100755 packages/modules/web_themes/colors/source/src/router/index.js create mode 100644 packages/modules/web_themes/colors/source/tsconfig.app.json create mode 100644 packages/modules/web_themes/colors/source/tsconfig.node.json create mode 100644 packages/modules/web_themes/colors/web/assets/fa-solid-900-CG7ny7S5.ttf create mode 100644 packages/modules/web_themes/colors/web/assets/fa-solid-900-DByUvYfa.woff2 delete mode 100644 packages/modules/web_themes/colors/web/assets/index-1385170e.js create mode 100644 packages/modules/web_themes/colors/web/assets/index-CB5xMAMt.js create mode 100644 packages/modules/web_themes/colors/web/assets/index-RsvlNgnt.css delete mode 100644 packages/modules/web_themes/colors/web/assets/index-d82a5d32.css delete mode 100644 packages/modules/web_themes/colors/web/assets/vendor-978a58bb.js create mode 100644 packages/modules/web_themes/colors/web/assets/vendor-DXWWyQ_6.js create mode 100644 runs/http-api/v1/index.php delete mode 100755 runs/send_debug.sh create mode 100755 runs/setup_apache2.sh create mode 120000 web/display/themes/colors rename web/settings/assets/{ChargeLog-41982ab8.js => ChargeLog-722afad2.js} (58%) delete mode 100644 web/settings/assets/ChargePointInstallation-09031c4e.js create mode 100644 web/settings/assets/ChargePointInstallation-b2835c0f.js create mode 100644 web/settings/assets/Chart-21159b5b.js delete mode 100644 web/settings/assets/Chart-ca210cf7.js delete mode 100644 web/settings/assets/CloudConfig-3ed9b804.js create mode 100644 web/settings/assets/CloudConfig-ea9e35a2.js delete mode 100644 web/settings/assets/DataManagement-703fd7ce.js create mode 100644 web/settings/assets/DataManagement-9d482bbc.js delete mode 100644 web/settings/assets/DebugConfig-00788e5e.js create mode 100644 web/settings/assets/DebugConfig-47800e80.js create mode 100644 web/settings/assets/GeneralChargeConfig-00e2f8a5.js delete mode 100644 web/settings/assets/GeneralChargeConfig-e9abdba5.js delete mode 100644 web/settings/assets/GeneralConfig-bc48aa27.js create mode 100644 web/settings/assets/GeneralConfig-c69ebde0.js delete mode 100644 web/settings/assets/HardwareInstallation-364c2ab4.js create mode 100644 web/settings/assets/HardwareInstallation-5af5fbe9.js create mode 100644 web/settings/assets/InstallAssistant-eda2851f.js create mode 100644 web/settings/assets/InstallAssistantStep0-a21fa735.js create mode 100644 web/settings/assets/InstallAssistantStep1-d2a360b9.js create mode 100644 web/settings/assets/InstallAssistantStep10-c89e1c63.js create mode 100644 web/settings/assets/InstallAssistantStep2-ffa141aa.js create mode 100644 web/settings/assets/InstallAssistantStep3-98547931.js create mode 100644 web/settings/assets/InstallAssistantStep4-40a75ed5.js create mode 100644 web/settings/assets/InstallAssistantStep5-44af41e7.js create mode 100644 web/settings/assets/InstallAssistantStep6-7a4708f6.js create mode 100644 web/settings/assets/InstallAssistantStep7-8e85a8ba.js create mode 100644 web/settings/assets/InstallAssistantStep8-35eac618.js create mode 100644 web/settings/assets/InstallAssistantStep9-8722f60d.js create mode 100644 web/settings/assets/InstallAssistantStepTemplate-61e7a445.js create mode 100644 web/settings/assets/InstallAssistantStepTemplate-ff479f80.css create mode 100644 web/settings/assets/InstantChargeConfig-36c3cfa2.js delete mode 100644 web/settings/assets/InstantChargeConfig-eb18cfa6.js create mode 100644 web/settings/assets/LegalSettings-22fa337f.js delete mode 100644 web/settings/assets/LegalSettings-d586b175.js create mode 100644 web/settings/assets/LoadManagementConfig-85a943c7.js delete mode 100644 web/settings/assets/LoadManagementConfig-d6cf0978.js delete mode 100644 web/settings/assets/MqttBridgeConfig-35e0451a.js create mode 100644 web/settings/assets/MqttBridgeConfig-d9dbce27.js rename web/settings/assets/{OpenwbSortableList-3bc74ae3.js => OpenwbSortableList-83baffc2.js} (93%) delete mode 100644 web/settings/assets/OptionalComponents-582c94d5.js create mode 100644 web/settings/assets/OptionalComponents-edc6afdc.js delete mode 100644 web/settings/assets/PVChargeConfig-25f62f25.js create mode 100644 web/settings/assets/PVChargeConfig-fb1a202e.js delete mode 100644 web/settings/assets/ScheduledChargeConfig-a26da09e.js create mode 100644 web/settings/assets/ScheduledChargeConfig-b97a9b65.js rename web/settings/assets/{Status-76c877c4.js => Status-6ea46c60.js} (87%) create mode 100644 web/settings/assets/Support-4da61633.js delete mode 100644 web/settings/assets/Support-f5fb936e.js create mode 100644 web/settings/assets/System-55768eb3.js delete mode 100644 web/settings/assets/System-c2faaea4.js delete mode 100644 web/settings/assets/TestingStore-46b74900.js create mode 100644 web/settings/assets/TestingStore-55b29644.js delete mode 100644 web/settings/assets/TimeChargeConfig-55c91052.js create mode 100644 web/settings/assets/TimeChargeConfig-835e9962.js delete mode 100644 web/settings/assets/VehicleConfig-0d88d084.js create mode 100644 web/settings/assets/VehicleConfig-2146c20d.js delete mode 100644 web/settings/assets/backup_cloud-86d98afb.js rename web/settings/assets/{backup_cloud-348c40e4.js => backup_cloud-b9529fad.js} (95%) rename web/settings/assets/{backup_cloud-2fa5514b.js => backup_cloud-c615e61f.js} (84%) rename web/settings/assets/{backup_cloud-3b0c57b5.js => backup_cloud-e6067096.js} (98%) create mode 100644 web/settings/assets/backup_cloud-f63441e9.js rename web/settings/assets/{bat-caa3579c.js => bat-0b9b3e59.js} (86%) rename web/settings/assets/{bat-17f90f05.js => bat-0d13c6cf.js} (86%) rename web/settings/assets/{bat-078827ba.js => bat-0fad5685.js} (93%) rename web/settings/assets/{bat-4716250e.js => bat-245b28b2.js} (92%) rename web/settings/assets/{bat-3de07a54.js => bat-529544eb.js} (87%) rename web/settings/assets/{bat-d1bb19e5.js => bat-5319970c.js} (93%) rename web/settings/assets/{bat-427b895a.js => bat-5e9c56af.js} (88%) create mode 100644 web/settings/assets/bat-602e434a.js rename web/settings/assets/{bat-23446e35.js => bat-68f9ad88.js} (93%) rename web/settings/assets/{bat-8cd34132.js => bat-702b5b7d.js} (90%) rename web/settings/assets/{bat-29444946.js => bat-70c49f50.js} (86%) rename web/settings/assets/{bat-991a2da4.js => bat-7115b6b2.js} (93%) rename web/settings/assets/{bat-6a57da9d.js => bat-73dd1a1b.js} (87%) rename web/settings/assets/{bat-73611913.js => bat-79eb8c84.js} (90%) rename web/settings/assets/{bat-28ed9b26.js => bat-7f759d21.js} (93%) rename web/settings/assets/{bat-1ea26945.js => bat-827eee78.js} (92%) rename web/settings/assets/{bat-896fc8aa.js => bat-82bdf780.js} (88%) rename web/settings/assets/{bat-fdf9bf78.js => bat-879dbf08.js} (92%) rename web/settings/assets/{bat-ad90e9ee.js => bat-895996e9.js} (87%) rename web/settings/assets/{bat-004af275.js => bat-8cbbbe5c.js} (87%) rename web/settings/assets/{bat-b7e731be.js => bat-8e2f16ba.js} (93%) rename web/settings/assets/{bat-b2a2d3f2.js => bat-94aace3d.js} (93%) rename web/settings/assets/{bat-80db94ff.js => bat-9574b509.js} (93%) rename web/settings/assets/{bat-fdd27b23.js => bat-af0b1c39.js} (92%) rename web/settings/assets/{bat-4a6a7c43.js => bat-b4ccfcff.js} (87%) delete mode 100644 web/settings/assets/bat-b587d2a1.js create mode 100644 web/settings/assets/bat-b8c193c6.js rename web/settings/assets/{bat-0c0b59d8.js => bat-bb395a88.js} (88%) rename web/settings/assets/{bat-80b6905b.js => bat-bd9fa2cb.js} (93%) delete mode 100644 web/settings/assets/bat-c0813514.js rename web/settings/assets/{bat-68b35b74.js => bat-c147ff7e.js} (92%) rename web/settings/assets/{bat-71cd93ee.js => bat-c8262add.js} (88%) create mode 100644 web/settings/assets/bat-d162819e.js rename web/settings/assets/{bat-6326eecb.js => bat-d5d41762.js} (96%) rename web/settings/assets/{bat-fefef2e9.js => bat-da0cdc25.js} (92%) create mode 100644 web/settings/assets/bat-e5059d98.js create mode 100644 web/settings/assets/bat-e8c7f71b.js rename web/settings/assets/{bat-4392d8e4.js => bat-f3f8f533.js} (86%) rename web/settings/assets/{bat-0614e258.js => bat-ffc676b7.js} (86%) rename web/settings/assets/{bat-9a1cb5c1.js => bat-ffee8776.js} (94%) rename web/settings/assets/{bat_api-30d440c8.js => bat_api-ff0494f9.js} (93%) rename web/settings/assets/{bat_modbus-5abb62d6.js => bat_modbus-19ae8233.js} (93%) rename web/settings/assets/{bat_smart_energy-6b29e697.js => bat_smart_energy-0f1332a9.js} (88%) rename web/settings/assets/{chargePoint-0566cc01.js => chargePoint-1b2b6046.js} (95%) rename web/settings/assets/{chargePoint-e717ad09.js => chargePoint-40d383a8.js} (94%) rename web/settings/assets/{chargePoint-0b9f9c90.js => chargePoint-4ec3642e.js} (92%) rename web/settings/assets/{chargePoint-50edf03b.js => chargePoint-5d9108db.js} (98%) rename web/settings/assets/{chargePoint-27dcccca.js => chargePoint-8e001c83.js} (84%) rename web/settings/assets/{chargePoint-1ef07072.js => chargePoint-c9d77c2a.js} (95%) rename web/settings/assets/{commands-b16cfbca.js => commands-653dec4f.js} (96%) create mode 100644 web/settings/assets/consumption_counter-21c31eb0.js delete mode 100644 web/settings/assets/consumption_counter-5dce3617.js rename web/settings/assets/{counter-85e4278f.js => counter-02524703.js} (85%) rename web/settings/assets/{counter-c98aa2dc.js => counter-045722ed.js} (88%) rename web/settings/assets/{counter-89cef159.js => counter-08adb306.js} (87%) rename web/settings/assets/{counter-7071f23c.js => counter-0a6841a2.js} (88%) rename web/settings/assets/{counter-e2630477.js => counter-0d97eeba.js} (86%) create mode 100644 web/settings/assets/counter-12e587b8.js rename web/settings/assets/{counter-778b4337.js => counter-1487841f.js} (96%) rename web/settings/assets/{counter-f45165f1.js => counter-1b2d57ae.js} (86%) rename web/settings/assets/{counter-d8e01f1e.js => counter-1cee7877.js} (86%) rename web/settings/assets/{counter-a252bc48.js => counter-1d1f787e.js} (92%) rename web/settings/assets/{counter-86d5cc78.js => counter-223061cc.js} (90%) rename web/settings/assets/{counter-9dc912f8.js => counter-24314bba.js} (86%) create mode 100644 web/settings/assets/counter-2753f6f8.js create mode 100644 web/settings/assets/counter-2a5fb302.js rename web/settings/assets/{counter-727f13a8.js => counter-2ba74204.js} (91%) rename web/settings/assets/{counter-dc0d92e4.js => counter-3018fe00.js} (86%) rename web/settings/assets/{counter-8eb01542.js => counter-349928ea.js} (90%) delete mode 100644 web/settings/assets/counter-354979d7.js rename web/settings/assets/{counter-8dc1093e.js => counter-3adbb6cf.js} (86%) create mode 100644 web/settings/assets/counter-4dd61d66.js rename web/settings/assets/{counter-3e3b4646.js => counter-525ffd50.js} (89%) rename web/settings/assets/{counter-4d89398e.js => counter-560cce16.js} (87%) delete mode 100644 web/settings/assets/counter-5bd73746.js rename web/settings/assets/{counter-951a835d.js => counter-5e5d845c.js} (86%) rename web/settings/assets/{counter-dc0290a5.js => counter-60457116.js} (86%) rename web/settings/assets/{counter-d9c2b225.js => counter-62b5d0c6.js} (93%) rename web/settings/assets/{counter-93fefb5d.js => counter-66c51558.js} (88%) rename web/settings/assets/{counter-e24e0897.js => counter-688947fd.js} (86%) rename web/settings/assets/{counter-9fa93c7a.js => counter-6ec5fe05.js} (89%) rename web/settings/assets/{counter-0e83fdf2.js => counter-7ae2f188.js} (86%) rename web/settings/assets/{counter-63366dbc.js => counter-81bbf3a9.js} (86%) create mode 100644 web/settings/assets/counter-85a387e7.js rename web/settings/assets/{counter-31875047.js => counter-8e3b5675.js} (87%) rename web/settings/assets/{counter-8cc2b538.js => counter-973013db.js} (97%) create mode 100644 web/settings/assets/counter-9c0a2d69.js create mode 100644 web/settings/assets/counter-a04670ec.js rename web/settings/assets/{counter-24785cb2.js => counter-a699cc16.js} (85%) rename web/settings/assets/{counter-ba87c672.js => counter-ae412bbb.js} (85%) rename web/settings/assets/{counter-2caa83da.js => counter-aecfef3f.js} (86%) rename web/settings/assets/{counter-5e598793.js => counter-b2c31625.js} (86%) rename web/settings/assets/{counter-b00adb82.js => counter-b9a2d196.js} (94%) rename web/settings/assets/{counter-1353cd87.js => counter-be679886.js} (92%) rename web/settings/assets/{counter-99aae1f4.js => counter-c382fe07.js} (86%) rename web/settings/assets/{counter-cabc879f.js => counter-c4022d6d.js} (86%) rename web/settings/assets/{counter-e949dc78.js => counter-c500661a.js} (90%) delete mode 100644 web/settings/assets/counter-c93ee66d.js delete mode 100644 web/settings/assets/counter-d4d8ab4d.js rename web/settings/assets/{counter-c56a24bd.js => counter-e14d11c9.js} (86%) rename web/settings/assets/{counter-8babe15d.js => counter-e5a551c2.js} (86%) create mode 100644 web/settings/assets/counter-e6dea4a1.js delete mode 100644 web/settings/assets/counter-ed091fbe.js rename web/settings/assets/{counter-16a12798.js => counter-eef9fbde.js} (86%) rename web/settings/assets/{counter-b9eae16e.js => counter-f4ede2ab.js} (86%) rename web/settings/assets/{counter-c31d3c2d.js => counter-f5bdf5b7.js} (87%) create mode 100644 web/settings/assets/counter-f7a4cb2f.js rename web/settings/assets/{counter_s0-c93542b2.js => counter_s0-8776160e.js} (86%) rename web/settings/assets/{counter_sm-0a095ddb.js => counter_sm-9779fb75.js} (97%) rename web/settings/assets/{device-5a899a6c.js => device-0083b877.js} (90%) rename web/settings/assets/{device-8176ba3b.js => device-024a1916.js} (92%) rename web/settings/assets/{device-3a0a95ea.js => device-0381dde8.js} (91%) rename web/settings/assets/{device-7b3f38c2.js => device-03f42ff3.js} (89%) rename web/settings/assets/{device-eb55288a.js => device-04cd5fc1.js} (91%) rename web/settings/assets/{device-5dacd4ed.js => device-0780972d.js} (87%) rename web/settings/assets/{device-fe43a3a0.js => device-08a28a0a.js} (88%) rename web/settings/assets/{device-3de8b661.js => device-0c29a881.js} (91%) rename web/settings/assets/{device-b18ccf96.js => device-16376bb4.js} (89%) rename web/settings/assets/{device-fae80432.js => device-17cc93f5.js} (95%) rename web/settings/assets/{device-65b723d1.js => device-1dc353b4.js} (90%) rename web/settings/assets/{device-becdaae3.js => device-23840023.js} (93%) rename web/settings/assets/{device-591eb549.js => device-25e63451.js} (89%) rename web/settings/assets/{device-6470e0bb.js => device-26757aeb.js} (86%) rename web/settings/assets/{device-eb08de6d.js => device-29504c9a.js} (91%) delete mode 100644 web/settings/assets/device-297790d4.js rename web/settings/assets/{device-fecbff51.js => device-2f3fabdc.js} (92%) rename web/settings/assets/{device-e9b3840b.js => device-309ffadc.js} (92%) rename web/settings/assets/{device-1da1c02e.js => device-31b159f9.js} (89%) rename web/settings/assets/{device-5ac87fdd.js => device-370d2d3b.js} (89%) rename web/settings/assets/{device-450208c0.js => device-3963e5da.js} (91%) delete mode 100644 web/settings/assets/device-43209216.js create mode 100644 web/settings/assets/device-471a07ab.js rename web/settings/assets/{device-8375d21a.js => device-4a5c725a.js} (91%) rename web/settings/assets/{device-53dc31db.js => device-4f45d53d.js} (87%) rename web/settings/assets/{device-cb8b26ed.js => device-500e5018.js} (90%) rename web/settings/assets/{device-b3c52c36.js => device-52ddf35f.js} (93%) rename web/settings/assets/{device-168f30ca.js => device-5637bb4a.js} (87%) rename web/settings/assets/{device-747bfa75.js => device-59de8958.js} (94%) rename web/settings/assets/{device-19d7d953.js => device-5a1071a1.js} (92%) rename web/settings/assets/{device-fd8370a3.js => device-6626c768.js} (91%) rename web/settings/assets/{device-b42da65f.js => device-6c8cf01b.js} (95%) rename web/settings/assets/{device-c12bd44e.js => device-6d155fbc.js} (87%) create mode 100644 web/settings/assets/device-720eb4cb.js rename web/settings/assets/{device-4af5ac8f.js => device-730be313.js} (92%) rename web/settings/assets/{device-f037488c.js => device-77bcb56a.js} (96%) rename web/settings/assets/{device-2fffc73f.js => device-79c0c109.js} (91%) rename web/settings/assets/{device-8d96d63b.js => device-7b5e366a.js} (92%) delete mode 100644 web/settings/assets/device-80543318.js rename web/settings/assets/{device-f9fac383.js => device-87eec55b.js} (92%) rename web/settings/assets/{device-4519dc95.js => device-889c26e6.js} (89%) rename web/settings/assets/{device-a781dd9f.js => device-8941f395.js} (91%) rename web/settings/assets/{device-7e746f01.js => device-8f942b3e.js} (90%) rename web/settings/assets/{device-fb080c72.js => device-9848f60c.js} (91%) rename web/settings/assets/{device-39a18394.js => device-a01f0692.js} (91%) rename web/settings/assets/{device-3a4a9885.js => device-a2663b8f.js} (89%) rename web/settings/assets/{device-af058452.js => device-a83462a8.js} (91%) rename web/settings/assets/{device-958c98ed.js => device-ac3bc820.js} (94%) rename web/settings/assets/{device-db271319.js => device-ad3020fa.js} (90%) create mode 100644 web/settings/assets/device-b0b72ec4.js rename web/settings/assets/{device-3b0bdf64.js => device-b6d28a9b.js} (96%) rename web/settings/assets/{device-5ba21463.js => device-bdc24b1b.js} (93%) rename web/settings/assets/{device-0cd45d58.js => device-c12c5098.js} (89%) rename web/settings/assets/{device-225eb0a9.js => device-c882a99d.js} (89%) create mode 100644 web/settings/assets/device-cb4bf319.js rename web/settings/assets/{device-420ad191.js => device-cf2d027e.js} (94%) create mode 100644 web/settings/assets/device-d904f113.js create mode 100644 web/settings/assets/device-d9dc3bf5.js rename web/settings/assets/{device-96b42d7a.js => device-da6133e0.js} (89%) create mode 100644 web/settings/assets/device-e0515fb9.js rename web/settings/assets/{device-ef440406.js => device-e37865e7.js} (92%) rename web/settings/assets/{device-f8cc6d79.js => device-e3958f31.js} (85%) rename web/settings/assets/{device-33a63868.js => device-e965df80.js} (86%) rename web/settings/assets/{device-03854029.js => device-e9b80205.js} (90%) delete mode 100644 web/settings/assets/device-f172e832.js rename web/settings/assets/{device-98243109.js => device-f248d2b7.js} (89%) create mode 100644 web/settings/assets/device-f34d002a.js delete mode 100644 web/settings/assets/device-f9e4faac.js rename web/settings/assets/{device-7330dbf4.js => device-fc0d2464.js} (89%) rename web/settings/assets/{device-ed53f77f.js => device-fde3c802.js} (87%) create mode 100644 web/settings/assets/displayTheme-0f1ce346.js delete mode 100644 web/settings/assets/displayTheme-30096b05.js create mode 100644 web/settings/assets/electricity_tariff-1a39d75f.js rename web/settings/assets/{electricity_tariff-30070fd5.js => electricity_tariff-2f2bda3c.js} (95%) rename web/settings/assets/{electricity_tariff-e117e44c.js => electricity_tariff-440299b5.js} (94%) create mode 100644 web/settings/assets/electricity_tariff-7a249832.js rename web/settings/assets/{electricity_tariff-985f18a6.js => electricity_tariff-ff5ad3ad.js} (98%) rename web/settings/assets/{external_inverter-95adc747.js => external_inverter-351fbf76.js} (86%) create mode 100644 web/settings/assets/external_inverter-6d24a89f.js rename web/settings/assets/{external_inverter-029da41b.js => external_inverter-ad1ea734.js} (86%) delete mode 100644 web/settings/assets/external_inverter-c72c8a10.js create mode 100644 web/settings/assets/index-d084f6bd.js delete mode 100644 web/settings/assets/index-d5bc6d44.js rename web/settings/assets/{inverter-8728d3e0.js => inverter-03b48a6c.js} (90%) rename web/settings/assets/{inverter-1c9d8a02.js => inverter-0518b9c4.js} (87%) rename web/settings/assets/{inverter-91d3a655.js => inverter-07445706.js} (93%) rename web/settings/assets/{inverter-c966de6c.js => inverter-0a398559.js} (86%) rename web/settings/assets/{inverter-d441061a.js => inverter-0cb29727.js} (93%) rename web/settings/assets/{inverter-469c35df.js => inverter-0cb645b6.js} (93%) rename web/settings/assets/{inverter-e2480bca.js => inverter-0fc52861.js} (93%) create mode 100644 web/settings/assets/inverter-13cc5687.js rename web/settings/assets/{inverter-66c8e5e6.js => inverter-1d3f4df2.js} (93%) rename web/settings/assets/{inverter-ac40e842.js => inverter-1def4e9b.js} (88%) rename web/settings/assets/{inverter-c3b1b600.js => inverter-25c2a95f.js} (86%) rename web/settings/assets/{inverter-e15ddd14.js => inverter-290bf9e6.js} (93%) rename web/settings/assets/{inverter-b37fb584.js => inverter-2ef7a50f.js} (86%) rename web/settings/assets/{inverter-dfc55df7.js => inverter-30e941c7.js} (93%) rename web/settings/assets/{inverter-643f7cf4.js => inverter-3c2411f5.js} (89%) rename web/settings/assets/{inverter-f55c59cd.js => inverter-44f1ec26.js} (90%) delete mode 100644 web/settings/assets/inverter-50c21455.js delete mode 100644 web/settings/assets/inverter-52398948.js rename web/settings/assets/{inverter-318e7901.js => inverter-5476d4c0.js} (89%) rename web/settings/assets/{inverter-3aae24f8.js => inverter-56673e8b.js} (93%) rename web/settings/assets/{inverter-9732490a.js => inverter-5a6f70ef.js} (93%) rename web/settings/assets/{inverter-5b451c75.js => inverter-5eff776c.js} (93%) rename web/settings/assets/{inverter-2ad2b236.js => inverter-6099d12f.js} (93%) rename web/settings/assets/{inverter-8a407cb7.js => inverter-6232c685.js} (86%) rename web/settings/assets/{inverter-1989d0b6.js => inverter-6383f684.js} (94%) create mode 100644 web/settings/assets/inverter-63d272fb.js rename web/settings/assets/{inverter-e221132d.js => inverter-6b9571c6.js} (93%) rename web/settings/assets/{inverter-abea47d7.js => inverter-7d5686ca.js} (86%) delete mode 100644 web/settings/assets/inverter-7e9d0c38.js rename web/settings/assets/{inverter-d8dec1b6.js => inverter-8174966c.js} (86%) rename web/settings/assets/{inverter-d7dea42e.js => inverter-8e01a109.js} (86%) rename web/settings/assets/{inverter-5a418d51.js => inverter-91ab3bcb.js} (86%) rename web/settings/assets/{inverter-13512c6a.js => inverter-92732c4f.js} (93%) rename web/settings/assets/{inverter-01de9df1.js => inverter-a1ec827b.js} (94%) rename web/settings/assets/{inverter-1b94372e.js => inverter-a2ac853c.js} (93%) rename web/settings/assets/{inverter-c5d45c65.js => inverter-aadffff7.js} (88%) rename web/settings/assets/{inverter-de64dbcb.js => inverter-ae7fa269.js} (94%) create mode 100644 web/settings/assets/inverter-b4208566.js rename web/settings/assets/{inverter-92d95012.js => inverter-b492477c.js} (86%) rename web/settings/assets/{inverter-1991d27a.js => inverter-b5a17f56.js} (90%) rename web/settings/assets/{inverter-5d155af2.js => inverter-b7d8947f.js} (88%) rename web/settings/assets/{inverter-daca8a0a.js => inverter-b82a23a1.js} (96%) rename web/settings/assets/{inverter-2bfcc388.js => inverter-bcb5d470.js} (86%) rename web/settings/assets/{inverter-fe835151.js => inverter-c036e5dd.js} (92%) rename web/settings/assets/{inverter-63c2a9d6.js => inverter-cd80a37d.js} (86%) rename web/settings/assets/{inverter-d4dc1157.js => inverter-d5417159.js} (93%) rename web/settings/assets/{inverter-82c0d3d4.js => inverter-d5c1d2ce.js} (93%) create mode 100644 web/settings/assets/inverter-d9845157.js rename web/settings/assets/{inverter-1a56f664.js => inverter-db998d6f.js} (93%) rename web/settings/assets/{inverter-6b7e698c.js => inverter-e08e67af.js} (91%) create mode 100644 web/settings/assets/inverter-e6e2509b.js rename web/settings/assets/{inverter-8720288d.js => inverter-eb0d44a6.js} (89%) rename web/settings/assets/{inverter-a5cd751f.js => inverter-f1bb5f21.js} (93%) rename web/settings/assets/{inverter-291f8d33.js => inverter-f755785a.js} (91%) rename web/settings/assets/{inverter-8911e4cb.js => inverter-f9ec521f.js} (93%) rename web/settings/assets/{inverter-e87230d9.js => inverter-faf10f31.js} (90%) rename web/settings/assets/{inverter_secondary-96ad8ef8.js => inverter_secondary-2d6a7628.js} (93%) rename web/settings/assets/{ripple_control_receiver-8324a5fb.js => ripple_control_receiver-0fec0f83.js} (96%) rename web/settings/assets/{ripple_control_receiver-ea08502b.js => ripple_control_receiver-a6a97d4e.js} (92%) rename web/settings/assets/{vehicle-a56ffc15.js => vehicle-05135c16.js} (94%) rename web/settings/assets/{vehicle-4b4e98b0.js => vehicle-0f184c8b.js} (95%) rename web/settings/assets/{vehicle-abd3908a.js => vehicle-1b88fc5e.js} (95%) rename web/settings/assets/{vehicle-0aac4e2a.js => vehicle-1d9e5107.js} (97%) create mode 100644 web/settings/assets/vehicle-25136235.js rename web/settings/assets/{vehicle-3dbed7e0.js => vehicle-2742b8e8.js} (95%) delete mode 100644 web/settings/assets/vehicle-295d8f60.js rename web/settings/assets/{vehicle-de59f8dc.js => vehicle-2a287ab7.js} (95%) rename web/settings/assets/{vehicle-f15518b8.js => vehicle-32ab6e9b.js} (92%) rename web/settings/assets/{vehicle-04a6570f.js => vehicle-35042811.js} (93%) create mode 100644 web/settings/assets/vehicle-4569ab36.js rename web/settings/assets/{vehicle-cd111671.js => vehicle-591dc321.js} (96%) rename web/settings/assets/{vehicle-1df7269f.js => vehicle-653b73be.js} (96%) rename web/settings/assets/{vehicle-e685fd4b.js => vehicle-7c7a06a1.js} (97%) rename web/settings/assets/{vehicle-067609bb.js => vehicle-80836d40.js} (89%) rename web/settings/assets/{vehicle-bfbabfd0.js => vehicle-ab1e8b2c.js} (95%) rename web/settings/assets/{vehicle-23efacf4.js => vehicle-c1302901.js} (97%) create mode 100644 web/settings/assets/vehicle-e3f6e0f8.js rename web/settings/assets/{vendor-axios-e5457936.js => vendor-axios-dc7988e3.js} (99%) create mode 100644 web/settings/assets/vendor-b3afda6d.js rename web/settings/assets/{vendor-bootstrap-f73ddddf.js => vendor-bootstrap-37731caa.js} (99%) rename web/settings/assets/{vendor-chartjs-4d79ce0b.js => vendor-chartjs-2789d664.js} (99%) delete mode 100644 web/settings/assets/vendor-d624aab7.js rename web/settings/assets/{vendor-fortawesome-994bfc1e.js => vendor-fortawesome-b013cb5c.js} (65%) rename web/settings/assets/{vendor-jquery-b0321e08.js => vendor-jquery-2371184a.js} (99%) rename web/settings/assets/{vendor-sortablejs-c0fcb1ea.js => vendor-sortablejs-806a0b5c.js} (99%) create mode 100644 web/settings/img/openWB_logo_light.png diff --git a/.github/workflows/build_display_theme_cards.yml b/.github/workflows/build_display_theme_cards.yml index 292e120de9..2b63cb8214 100644 --- a/.github/workflows/build_display_theme_cards.yml +++ b/.github/workflows/build_display_theme_cards.yml @@ -31,7 +31,7 @@ jobs: - name: Commit and Push Changes run: | git config user.name "${{ github.actor }}" - git config.user.email "${{ github.actor }}@users.noreply.github.com" + git config user.email "${{ github.actor }}@users.noreply.github.com" git add packages/modules/display_themes/cards/web git commit -m "Build Display Theme: Cards" git push diff --git a/data/config/apache/http-api-ssl.conf b/data/config/apache/http-api-ssl.conf new file mode 100644 index 0000000000..2a2546da76 --- /dev/null +++ b/data/config/apache/http-api-ssl.conf @@ -0,0 +1,141 @@ +# openwb-version:1 + + Listen 8443 + + + ServerAdmin webmaster@localhost + + DocumentRoot /var/www/html/openWB/runs/http-api + + # Available loglevels: trace8, ..., trace1, debug, info, notice, warn, + # error, crit, alert, emerg. + # It is also possible to configure the loglevel for particular + # modules, e.g. + #LogLevel info ssl:warn + + ErrorLog ${APACHE_LOG_DIR}/api-ssl-error.log + CustomLog ${APACHE_LOG_DIR}/api-ssl-access.log combined + + # For most configuration files from conf-available/, which are + # enabled or disabled at a global level, it is possible to + # include a line for only one particular virtual host. For example the + # following line enables the CGI configuration for this host only + # after it has been globally disabled with "a2disconf". + #Include conf-available/serve-cgi-bin.conf + + # SSL Engine Switch: + # Enable/Disable SSL for this virtual host. + SSLEngine on + + # A self-signed (snakeoil) certificate can be created by installing + # the ssl-cert package. See + # /usr/share/doc/apache2/README.Debian.gz for more info. + # If both key and certificate are stored in the same file, only the + # SSLCertificateFile directive is needed. + SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem + SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key + + # Server Certificate Chain: + # Point SSLCertificateChainFile at a file containing the + # concatenation of PEM encoded CA certificates which form the + # certificate chain for the server certificate. Alternatively + # the referenced file can be the same as SSLCertificateFile + # when the CA certificates are directly appended to the server + # certificate for convinience. + #SSLCertificateChainFile /etc/apache2/ssl.crt/server-ca.crt + + # Certificate Authority (CA): + # Set the CA certificate verification path where to find CA + # certificates for client authentication or alternatively one + # huge file containing all of them (file must be PEM encoded) + # Note: Inside SSLCACertificatePath you need hash symlinks + # to point to the certificate files. Use the provided + # Makefile to update the hash symlinks after changes. + #SSLCACertificatePath /etc/ssl/certs/ + #SSLCACertificateFile /etc/apache2/ssl.crt/ca-bundle.crt + + # Certificate Revocation Lists (CRL): + # Set the CA revocation path where to find CA CRLs for client + # authentication or alternatively one huge file containing all + # of them (file must be PEM encoded) + # Note: Inside SSLCARevocationPath you need hash symlinks + # to point to the certificate files. Use the provided + # Makefile to update the hash symlinks after changes. + #SSLCARevocationPath /etc/apache2/ssl.crl/ + #SSLCARevocationFile /etc/apache2/ssl.crl/ca-bundle.crl + + # Client Authentication (Type): + # Client certificate verification type and depth. Types are + # none, optional, require and optional_no_ca. Depth is a + # number which specifies how deeply to verify the certificate + # issuer chain before deciding the certificate is not valid. + #SSLVerifyClient require + #SSLVerifyDepth 10 + + # SSL Engine Options: + # Set various options for the SSL engine. + # o FakeBasicAuth: + # Translate the client X.509 into a Basic Authorisation. This means that + # the standard Auth/DBMAuth methods can be used for access control. The + # user name is the `one line' version of the client's X.509 certificate. + # Note that no password is obtained from the user. Every entry in the user + # file needs this password: `xxj31ZMTZzkVA'. + # o ExportCertData: + # This exports two additional environment variables: SSL_CLIENT_CERT and + # SSL_SERVER_CERT. These contain the PEM-encoded certificates of the + # server (always existing) and the client (only existing when client + # authentication is used). This can be used to import the certificates + # into CGI scripts. + # o StdEnvVars: + # This exports the standard SSL/TLS related `SSL_*' environment variables. + # Per default this exportation is switched off for performance reasons, + # because the extraction step is an expensive operation and is usually + # useless for serving static content. So one usually enables the + # exportation for CGI and SSI requests only. + # o OptRenegotiate: + # This enables optimized SSL connection renegotiation handling when SSL + # directives are used in per-directory context. + #SSLOptions +FakeBasicAuth +ExportCertData +StrictRequire + + SSLOptions +StdEnvVars + + + SSLOptions +StdEnvVars + + + AllowOverride All + Require all granted + Options -Indexes + + # SSL Protocol Adjustments: + # The safe and default but still SSL/TLS standard compliant shutdown + # approach is that mod_ssl sends the close notify alert but doesn't wait for + # the close notify alert from client. When you need a different shutdown + # approach you can use one of the following variables: + # o ssl-unclean-shutdown: + # This forces an unclean shutdown when the connection is closed, i.e. no + # SSL close notify alert is send or allowed to received. This violates + # the SSL/TLS standard but is needed for some brain-dead browsers. Use + # this when you receive I/O errors because of the standard approach where + # mod_ssl sends the close notify alert. + # o ssl-accurate-shutdown: + # This forces an accurate shutdown when the connection is closed, i.e. a + # SSL close notify alert is send and mod_ssl waits for the close notify + # alert of the client. This is 100% SSL/TLS standard compliant, but in + # practice often causes hanging connections with brain-dead browsers. Use + # this only for browsers where you know that their SSL implementation + # works correctly. + # Notice: Most problems of broken clients are also related to the HTTP + # keep-alive facility, so you usually additionally want to disable + # keep-alive for those clients, too. Use variable "nokeepalive" for this. + # Similarly, one has to force some clients to use HTTP/1.0 to workaround + # their broken HTTP/1.1 implementation. Use variables "downgrade-1.0" and + # "force-response-1.0" for this. + # BrowserMatch "MSIE [2-6]" \ + # nokeepalive ssl-unclean-shutdown \ + # downgrade-1.0 force-response-1.0 + + + + +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/data/config/mosquitto/mosquitto.acl b/data/config/mosquitto/mosquitto.acl index 80f2cb4777..c99f151a50 100644 --- a/data/config/mosquitto/mosquitto.acl +++ b/data/config/mosquitto/mosquitto.acl @@ -1,4 +1,4 @@ -# openwb-version:1 +# openwb-version:2 # allow publishing set topics topic write openWB/set/# # allow clearing system messages @@ -9,3 +9,5 @@ pattern write openWB/command/%c/messages/# topic read openWB/# # allow read access for remote support topics topic read openWB-remote/# +# allow brach "others" for devices other than openWB +topic readwrite others/# diff --git a/data/config/mosquitto/openwb_local.conf b/data/config/mosquitto/openwb_local.conf index 37201d3772..8909e9280f 100644 --- a/data/config/mosquitto/openwb_local.conf +++ b/data/config/mosquitto/openwb_local.conf @@ -1,4 +1,4 @@ -# openwb-version:13 +# openwb-version:14 listener 1886 localhost allow_anonymous true @@ -25,7 +25,6 @@ topic openWB/chargepoint/+/set/phases_to_use out 2 topic openWB/chargepoint/+/set/manual_lock out 2 topic openWB/chargepoint/+/set/autolock_state out 2 topic openWB/chargepoint/+/set/rfid out 2 -topic openWB/chargepoint/+/set/change_ev_permitted out 2 topic openWB/chargepoint/+/get/# out 2 topic openWB/chargepoint/+/config/# out 2 topic openWB/chargepoint/template/# out 2 diff --git a/data/config/ramdisk_config.txt b/data/config/ramdisk_config.txt new file mode 100644 index 0000000000..ed9fe59afc --- /dev/null +++ b/data/config/ramdisk_config.txt @@ -0,0 +1,5 @@ +# openwb - begin +# openwb-version:1 +# Do not edit this section! We need begin/end and version for proper updates! +tmpfs /var/www/html/openWB/ramdisk tmpfs nodev,nosuid,size=48M 0 0 +# openwb - end diff --git a/docs/Anzeige-Steuerung.md b/docs/Anzeige-Steuerung.md new file mode 100644 index 0000000000..4d60949873 --- /dev/null +++ b/docs/Anzeige-Steuerung.md @@ -0,0 +1,7 @@ +Die Kontrolle der openWB geschieht über einen Webbrowser. Aufruf geschieht über Eingabe der IP-Adresse der openWB. + +## Startseite +Die hier angezeigten Leistungen werden direkt aus den Leistungsdaten, welche vom Zähler übertragen werden, übernommen. + +## Auswertungen - Diagramme +In der Auswertung (und für's Langzeit-Logging) werden 5min-Intervalle der Zählerstände (kWh-Differenz/5min = kW) verwendet. \ No newline at end of file diff --git a/docs/Fehlersuche.md b/docs/Fehlersuche.md new file mode 100644 index 0000000000..96c5ab26e0 --- /dev/null +++ b/docs/Fehlersuche.md @@ -0,0 +1,43 @@ +# Fehlersuche +Es kann immer mal passieren, dass etwas nicht wie gedacht funktioniert. Das kann an einem Fehler im Programmcode, an einem Hardwaredefekt oder an einer fehlerhaften oder nicht zu den Gegebenheiten passenden Konfiguration liegen. + +Wenn nun eine Funktion nicht wie erwartet ausgeführt wird oder plötzlich ein Fehler auftritt, ist die erste Frage: +> Habe ich vor Kurzem etwas verändert? + +Dies betrifft ebenso Änderungen der Einstellungen im Fahrzeug, Wechselrichter (Softwareupdate?) oder der Hauselektrik. Unter Umständen kann das Problem so schon gelöst werden. + + +## Wo bekomme ich Hilfe? +### Hardwaresupport +Mit Problemen bei Inbetriebnahme / Anschluss oder Hardwareproblemen mit openWB-Hardware bitte direkt über die Support-Funktion unter System -> Support an openWB wenden (Notfalls auch per Mail an support@openwb.de). +Im Forum kann durchaus mal etwas untergehen. Das führt zu Frust und soll nicht sein. + +### Forum +Im [Forum](https://forum.openwb.de/index.php) findet man folgende Hilfestellung für Hilfesuchende. Die hier erbetene Herangehensweise dient dazu, den Fehler mit dem für alle niedrigsten Aufwand zu beheben. + +> Bitte keine Mehrfach-Meldung per Mail, Support-Ticket und Forum. +Das spart auf unserer Seite Supportzeit und bringt erfahrungsgemäß keine Beschleunigung des Vorgangs. +Bitte bei Problemen immer einen Logauszug posten: + +> Dazu unter System->Fehlersuche das Debuglevel auf Details stellen und mindestens zwei komplette Durchläufe von ``# *** Start***`` bis ``# ***Start***`` aus dem Main-Log kopieren, während das Problem auftritt. Sensible Daten wie Benutzernamen und Kennwörter unkenntlich machen. +Logauszüge bitte als Codeblock posten (Schaltfläche "" über dem Editor-Fenster). +Bei Problemen mit dem internen Ladepunkt zusätzlich einen Auszug aus dem Log des internen Ladepunkts, bei Problemen mit dem Soc aus dem Soc-Log posten. +Bei Problemen mit dem UI/Darstellung bitte ein Theme verwenden, das von openWB gepflegt wird (wird bei der Themeauswahl angezeigt). + +> Screenshots ersetzen keinen Logauszug! +Für Beiträge wie "Funktion XY geht nicht mehr! Woran kann das liegen?" ohne Logs gibt es von uns keine Hilfestellung. + +Formuliert Eure Frage freundlich, beschreibt was ihr tun wolltet und was anstelle dessen passiert ist. Weiterhin ist die verwendete Version von OpenWB wichtig. Diese findet ihr unter _Einstellungen -> System -> System_ im Feld _Versionsinformationen / Aktualisierungen_. +Da es schon Wechselwirkungen mit anderen Smarthome-Systemen gegeben hat, erwähnt ggf. weitere im Heimnetzwerk laufende SmartHome-Systeme. + +### Log-Erstellung +In der Standard-Einstellung des Logs werden nur Warnungen & Fehler erfasst. Außerdem wird bei einem Neustart der openWB der Fehlerlog gelöscht. + +Um aussagekräftige Logs zu erzeugen, müssen Log-Dateien im Debug-Modus erstellt werden. Hierzu ist folgende Schaltfläche zu aktivieren: + +![Debug-Einstellung](pictures/Fehlersuche_DebugLog.jpg) + +Aufgrund des detaillierten Loggings, ist die Dauer der Aufnahme ca. auf die letzten zwei Stunden begrenzt. Beachtet also, dass ihr sich der Fehler innerhalb des aufgezeichneten Abschnitts befindet. +Dann ist in den meisten Fällen das Main.log, aufzuklappen und mit der grünen Schaltfläche zu aktualisieren. Der entsprechende Auszug kann nun in eine Textdatei oder direkt in die Nachricht im Forum kopiert werden. + +![Main-Log](pictures/Fehlersuche_Main-Log.jpg) \ No newline at end of file diff --git "a/docs/Hausverbrauchs-Z\303\244hler.md" "b/docs/Hausverbrauchs-Z\303\244hler.md" index fb07aaa605..c5ddc5582a 100644 --- "a/docs/Hausverbrauchs-Z\303\244hler.md" +++ "b/docs/Hausverbrauchs-Z\303\244hler.md" @@ -1,4 +1,8 @@ -Einige Zähler, wie zB Solar-Log und Kostal Plenticore, werden im Hausverbrauchs-Zweig und nicht am EVU-Punkt installiert. Die für die Reglung erforderlichen Werte des EVU-Punkts werden mit einem virtuellen Zähler ermittelt. Dazu ein Virtuelles Gerät mit einem virtuellen Zähler anlegen. Die Komponenten müssen in der Hierarchie wie in den Abbildungen angeordnet werden: +Es gibt zwei mögliche Einbaupositionen für Zähler: EVU-Punkt und Hausverbrauchs-Zweig. +Ist der Zähler am EVU-Punkt installiert, misst er am EVU-Punkt (EVU=Elektrizitätsversorgungsunternehmen) Bezug und Einspeisung ins öffentliche Netz. Der Hausverbrauch wird dann aus den Werten der Ladepunkte, Wechselrichter und Speicher berechnet. +Ist der Zähler im Hausverbrauchs-Zweig installiert, misst er die Leistung der Ladepunkte und den Hausverbrauch. Bezug und Einspeisung ins öffentliche Netz werden dann aus den Werten des Zählers, Wechselrichter und Speicher berechnet. Dazu gibt es in openWB einen virtuellen Zähler. Dieser addiert die Werte aller in der Struktur dahinter angeordneten Komponenten. + +Zunächst ein Virtuelles Gerät mit einem virtuellen Zähler anlegen. Die Komponenten müssen in der Hierarchie wie in den Abbildungen unten angeordnet werden. In den Einstellungen für das Lastmanagement beim Punkt `Hausverbrauch` den Hausverbrauchs-Zähler auswählen. Der Hausverbrauch ist die Leistung des ausgewählten Zählers abzüglich der Ladeleistung. Misst der Zähler den Hausverbrauch, ergibt sich folgende Anordnung: diff --git a/docs/Huawei-Smartlogger.md b/docs/Huawei-Smartlogger.md new file mode 100644 index 0000000000..bbf082c46e --- /dev/null +++ b/docs/Huawei-Smartlogger.md @@ -0,0 +1,17 @@ +Im Smartlogger3000a müssen folgende Einstellungen festgelegt werden: + +1. Zunächst unter Einstell.-> Bef.-Param. -> Modbus TCP + Folgende Einstellungen festlegen: + Leitungseinstellungen: Akt.(Unbegrenzt) + Addressmodus: Logische Addresse + Logger-Addresse: z.B.4 (Muss eine freie ModBus ID sein, logische Addresse 2tes Bild.) + Schnelle Planung: Aktivieren + ![Huawei Smartlogger ModBusTCP](HuaweiSmartloggerModBusTCP.PNG) +2. Unter Wartung->Geräte-Mgmt.-> Geräte Liste + kann man jetzt die logische Adresse der einzelnen Geräte ablesen. Diese wird dann in der openWB in der Einstellung ModbusID eingetragen. + ![HuaweiSmartloggerLogischeAdressen](HuaweiSmartloggerLogischeAdressen.PNG) +4. in den Einstellungen der openWB das Modul Huawei Smartlogger auswählen. +5. Jetzt muss man die IP des Smartloggers und den Port 502 eintragen, außer dieser wurde geändert. +6. Jetzt die passenden Komponenten hinzufügen und die jeweilige ModbusID eintragen. +7. Zum Schluss auf Speichern drücken und unter dem Lastmanagement die passende Anordnung wählen. + ![Huawei Smartlogger Komponenten](HuaweiSmartloggerKomponenten.PNG) diff --git a/docs/HuaweiSmartloggerKomponenten.PNG b/docs/HuaweiSmartloggerKomponenten.PNG new file mode 100644 index 0000000000000000000000000000000000000000..2acc5154231c0751f1e220ced85340a0ba79ca0a GIT binary patch literal 106407 zcmeFZ2~d;S*FH$MZL6SQw+hPIHY%&2g6vDXMg>7xga{!G8WGuK4O@b?EG-C#vaccn zvO^F8BoGAx$`%nJKteD@QX z(`j41dipOFrJa>(SMyG9HMEOSJ|HFwtZUvk{JV~f1#F) z*jt#2(7TJ+yMAq7oP<5nl!V=ugk8UWd7(f(YN|lp;)(j2MMXres6vjKYeIZ}h8$hL zuCh{zbq-PaztM%~7jNR-I5>suOW0}Q0A4vS-pLei1X_$h%}49h?23AxNYnbwqjag)lV zM&z|n`5iI{#rL`jUxr?G`ip703e}r^4$U-%UvzG~GH4udz|nTMmZeglKPLW*5!4=k zHqtAEa;!I4l!LU{jRl98INI#45&3M`J5x_So9W0rWPa9+{GqNklI8$x@;rOF`yZ9h zlT&!_%TunWV*SW%@hDMzE#g+!-C!xKU4p_^T$qpb&}{&9>x3=TW4l8xQA#`)m2=US zyb@p0(QAes*3xq3xavgJ4(of5A(tzv0y@m~j0xyQ$g;U!{WS;bQM<9X@u z8QJg?4S}-h$;l+(TZd2Kq5wJpDVPQgsO(mylBk`Up{Q!7Koe%|kjYT)k&OHc|DTX3#Ol_?cNOL2!>Xyj}Mr}uKb9y4g@EwrhvY@1B8BkcW(@+4$G#AlSb zz7}4#d~sCsTfngSmnkuiUIc&;U0Yfv?;ZrJ&=e$apkA*`wPbAR6$#CpF=U{pJt!d9 zt}gPPw`)S8S}HcKV;a1h3fv`6gleGf{UZ~%)YyD$)da}D1FRRGO6I|!TlOD}s*OHI-8 z(r$bv##)+Q??ff8wX<}pTW6Xg`~ayKqEKcQd8V#3#({1U0+W% zFf}7UnHK z=nRl(t==oQ%`&MlcnrpA8FrZ>-kwn_a6|b0jC*5Xle zZ%Y9C?T|5r=P{*Wh@aIUTAuPA1_pprTS%P7B0?3CbCcSS8r)L>-j$ZF|i&yK$9qS!jfOC*Fn_h+wWliOZ4uUxfEziL9Md0u2s(%Cgq zq)AtaMMm7StD%kbMt}-}%TzfU^s)EHd*%>9AmzZ9XZ!#;8s_-d#==p|H10BN6#G!| zXG58zdIzHBk}oSM8x}u9Pp@zv?xaztZr9_Oc+hk2?brdmj9@nv%YFw*OOnw#2pAI_8t*5FluPnde&|`ifCl%jY$kq zyMQPCgM4lz5$PM82k|*`0`(A4F9>60Qh(~Qa+^a-9JbZklv?iE@Xk66o!D)5;}h)S zhyNJjE}*1cHh86*UR7Z<+Pb9$TcYSqb&zDqf($Q0<_oF>yRcKq*o$9Kb|IwGnc9Igxfn#tJBSfQ~r(&savW@#$DM$y`3RnD6M)vUF@mY?E zAnYdeMQsY!y=51`kcydomsVN9)EleO%<8EysLn^(VEpiRn1;C{a&&cbPP4?Ucu`I1 z)4rr^rB*Evj!>1KU3%CJXms}r0&u^eka{Vio;*X^BHCYvD6yyC(4ebbv^)MHz(0R} zg(lXYEH-kri*i;qdZbxChZ9!3lQgOlL5(*V1s$mLD(f%FuN`lAg7hE5HE|jf-pgMc z)|ZMm9^mQGr17lcSDdROh@geZ|2n1iUJ#!&cXL4=W%20RU@jbYy4L90c;SOYge}xm zemS>Ma?*tz5~|^79Po!xAQZa4{+3yBRyj)D>-th1jJ!DD!S-?#jm~W>)xH><4HDa8 zc5sd}=p~AZce-^rw3Wz~YN@#Bjw&iveh;`3m_v}FA9flG>ZOmePu8cIdivp8rZy!+ z?^us+VLCIW{)Zg1X5kh9AUcQCR1un?X{>N#yfGamUon|r==`7dLLus%>F1_i52s1Y zZtb{mhDz+>v`j8k4cxN|SoG;>y;Lb}Ab|A})^dcWHYpP3x%mkIyTIPaT_~Mp6Yot{ z8gyYxy>Lb!-7Ut=(iHfg4Rj#tOS0mC$QqZl4@@m%fP~#a`KHjiTWQn6nUEt7u{Sx> zMko;M=Xex;^W*SiXG``ca!grcC_pdlIO^h?n_ayPw?lA+RgHR}+%nRlhn{goepY^r zB90KeaLzf~;_Vj~^!}QA)Tn#u)fkr18bRG4XFKZqCeRI>+_unMqNGk4BdMq~qjd{I zEE)&G6_#A`0}{i^hW*HM747J;#AK2?LsSAjKm3XbIs#>1AHp#E=01!<{o@~bk~(^; z1VOGhr&X`17*^VJ<2d+;TBrovBGxu4y)_M$Q zKK-1>TnY!8J7B&hg*(6IeDl9t{+u|zJohc_Q~LMI&(3~JK8;R%$()W@ea$~9+n#>T zAnzo8OF=D#&s}fzLDK(cS?K=;iSYmBE*w#T%xf<5bII@S<%&}I!`@MLCiNDlH$ExO zy*zJ$sw4bUe-DrUSXd3L6$MY|h!uRQlZ2GO6thc%zs!gb!z{h&l#u*w;{!AaUILS3 zgnz||r^>I`)NFbFCHXxu3h_~o(S(J$PekAUcjKcz*4DB@feAtfx4u91rJ>%q>$3m+ zDSz2<>8C%x`*fK~!8u^O!GYS((|saa=1ow%%PGs)jmMP{+wz`k!YEomt*XT@`TR8T zTn@(-17yG2|J~Enl&U43$8J2SL}L5KIDP{L7k~NNU4;FHWzHts39!awSHa}`+#ual z3=_iVIMWMqqrw|Q4x^!d8WAj#`yoCQt?ndMbicoy&_=j2T2%Jn{h7~0`wOVP(S_JI zY?OkisbE#NrRQ-Gq6s4=#ZV;Kp>ZW0k0vr3mA@87XTG(6egA#HCXC-l`??P3A_dmb z@jS!>2>)SECm-8?5I2ZfnJmtY0K=ICa?hCHRy}u4k<0~{$U8ML70Jo93SQs$b#a%1 znH!pGE%kM|R&Ut#*#Yq#a=4?86YYIcnOKH9&E!fjA*qef&iR*Rd(fngT%77X{blh# zQQvThMCXQ8hTxAi(-2UE-x5Td=c;>>kw9|_tev9~%iKvVnASNq{KFyq3R!m08g?3u zANCGM>L>XREasbsJy@}a_~eunroOod`c`Xh*wpIt1htEG)EGA&M)u~2RSM|WlKT_1 z=8t-3!;WXhrs#S5$d?N5H~ZoJz3zf(WGm6kv}bv;u1zrv4CIhIr&15Zb!aF+WT{7c z-6s~0UH)NzW9|cJDY2AX0a;hIs9e#Qp+R?B%ZIG?6y+3WRrIz6tHv-L zk7t`({Lq9(HDeiXmOs>)yIjsHa@^YyT$y2UNHO5@*HV1L{m*p|gbTI|lL#s2r9&FQ zou{liAkQ-_rY(OoR9`+lx+PSXifBUa?}l?w$^92yDCkMbu{KDt@SY!j;6YbOjUxVV~KkAq{6u#^&k6Rw8L-SDywidc??lT>C zrxxEbDAxS(@4a%2l4ol{POX-y*0S*i8rB2aR^Scu$Y%w4#Uh)lb3B2 z-!4NyJS5cM)XIb|2BhEP^3L1Ty%}6PtrL6RG0M@guEgzG&c>1^X5x|XqnWMjeRu`k z4ObjLXCzYisii~5&LZ^@S0u}&0>@35$>GE*>ZN!~0sPBC9wfCvlzkuKK0ed<7U$?~ zrYC>BA8y~)=29QivH?9QW^n$J_w`VnQ}sTJ*iJXAV;-34lWq4xO61iJ`T%`>_LUYU z7JN6++1m}D>hVQ2as%ILQe2$TZ%wbm;at1nre~<7)Hs3{mv&o?1MSCww|m!@~k@P1VOr;};r88^21O_fHsl;fRdFdE-b>sIF)y76v0Uj{HW{9m-6khGSR>Y~*@ zusoS_qS%M*Q@hCD7DPE4{yHc9VuhEObXAz3nnk< zfc({nT+Q+>rKjp%r~&&rO*<0qrTGOQ_l>D2Cgh+4=>*N{D{4NALa%Dl-DXjp=(;s1 zC^$eDi`(qkY#C4uH20C)m0F;z;XdGxc(Jjl>ysA}Td<~QDf(Kg_xrJ?5hc45PFwV} zbWsAxC+*Hlw^Rxy`*Jwm6Mb{NIK=&@GMbS{1np3%dOS%~H>aezvN`=BLN6E;^VJRC zKg{K2cGP6s?I#*7J??Z%@uM7@pdhYXR5bez=p9tLVoC5bw{6t#mBwrLCY}(FTu$(~MY~I> z)8%V0OaF+j<=_y)rnw85V-;C9n)fA_aB{qV)BzL@d|BJ$`4oDqyuV4j-t`OY`ClPE zo5z(}G{L=I1pk9*<}hihG5QHzBZPK1wkFAp5gXkSInzkFM5sQ^XOB5;V@&irg{z@a ztV9!lWcXp?N^s!i)>(zHa?a6Ab|rZ-&f}jbLO4f36c00ShFb<6riWp%+X+P(f06(LFq|%y*X#oOCZ)OS(j!gF)NWW7NoYvrlm#Ibgd5XiSkseZ4!X|dlU*LG{F0{oe82K&aNPD;IbLw%WTzqwV+#B!L# zN!7x7OlY$rINopr`k;cJb_opOY2uvxLjv0?K55e>gm!_3Dd~xNoqC0VD$-qf`rXd= z6N(-}m*>yqRRnLZy`g4OeaDfuH`QT*F7!lgfR44uixH1UOX8pVu5Lc zbYdaGrTxfC{*~|kNFVk~Wr)wW)To}bNmVc>>t8`p#&`}?zUbWSYvOsKvGe`z$Uke7 zNqNr=0k3uVAA&0MepzxLw_9TggN~BkVcL%Dag4ZMB>8E+OCCrgb_}%w9X<6|$D&%S z>B`NO0qhJViqen)W`6+tC(rnCYADERlh}k|jfl)Clb;*Iye2*dsC-3Qt5YPDe(Ee* zlssT)YNg*3rga|T<5=kEI9TF#NW}-^tNw57r1jp^Sjq~cWNO-cBNh%oeAsr5XRs2( zPvOX(qKj`RU6i#xZ+3+OrQv*+pnQMA!NalE#KxUB#2`>8kmwMo+mBS&D9>D5@g*7< zm=5=tf>p>R9X|fH^+s;jrA={z*{91WF}(^^SNj{*nh_v*#2%Wb%_(B z#zp~#*r!X&!*j6y138|48Rxm*?JT7b~&x37g#=8{Hs=M%+#g`*(^> z(y*{@9)0ql>~ErQ*{*htp1K4!+U-=?IQsBuZFU6*^E*8Gn1`QYEU38xQNQqf`fSwU zR(k45j_zY!CsA*y)B@L>W?2#&7zm;dR<=~)vbHm31n#Yuz6Si)h#pf=Q_7kL33Fqo z|L`FFR?+c^EB~cut8e9Q4wza0F?DlGQJk=}Q2%uLw6)GTN`iP+Y3UBk(_oAYo zYBau;O=+Cg89j9jfDuJDbHJ@QlXQ^aO=To9{C0ugb+5%5OsUC*hCd%)maG)>=FU0{ zFP&M7v?+zN(q5bkK}CB0tagkzR!d4kJ=QyG2j*rOQ_KM)>ttyp>hK>B9q1HS@g(~h zqFzsP4VZ!A#i|ctQ_L>QZY01yYOV3V^NVtu`m}geev&xWb4pspKSymb?-nt;qZ~9s z*ng8h4UxFqNOpH?`4MC#i`c?DCahQ0a*f{yJPWWMcuTZw-WxN@78oo^k>hSmp)`p> zHTHX==Samc5VC2|)tI!})x!HaxBtFRLIw(ebsehfRZwz&x9#a=34$SYhCb^xwZgfrb3wa=i0;B zsM}0ej3^@+_0B;!$zc6`H!!PHF@znxACs`OmPnp!jhnPIZ6XyG9nJS$!AfXE2yE>o z7(B{_diJQPXli4;5nPQJR1$=;jBp<_mhqD6b?1_nkS(ZCfFED#Ya)?|{WuWrV_AnU znWzm=D9!RR*{Ac*wV22UL-rPqDNolT+~JNk9xh+HB((UuDfgCS(vX*V(-*(G%jxUH zhTsL8I#)k!s%TCUqCTjZ^53$->7?kzy@xCu0!+W+{GWe5&d3et$qdW)?(~v{Rj-Aq z>JqwN^Zl+zv0e7Z*2BvT1?$<+@HQ~&lx{kVub$}W5J(tCLy7v3?KmL8AAM<0eN2TZ1nlZlDgm%|{ zACjL>dP!m8s?`sQ^Vj}GVgBm|c*LZ`9-ZJ4V&33%lR+ zqv4)*so--=UoVsDxLgU$W&P0u2Yatwc8n^SddB}@w}NkAjz6A#*2FMnu$agFZ69ALM-9f{Yak%<@o=T%Gt$%_6cj9Tx-2@B#if}|n~e*Rt1jGn|9ANg_g6${N>-xZHvQm&B-ke>os>6 zK$C=g6g06>5r5aPkTrVyc8xFlRo*j8l>|U*wqC6Kv8U&EmI^32lYK9}XZbag`cYJ2 zJY6X&#jg<_CxaMGHz?s9LNxWs(DOuJM0*avIy=v8B!(N1^XD=84_)@paf|NGq$U7m=4r_;zv!u}=qoeV=yCHAhtcR~*zE6Gg( zmo{`9C6oiKz&cbld1|jE1Lx~&n{_n4Mf>j0 z!YrN*Vp1__jcu9?;jYOMac{vwh#r)@H1}?p*~9qoy$c+=%(eBaQT7URE?2!=bZ~t; zwSAVzzTzwkU`Y!@(AT*?a^DHXJ^#`+G;-zsg24>ZyIv&)rz)*DXt_|sxn z<;B^z{n&M_k-ezTx#=MBaUbD+&{scvteo=9c4F#ktx3;R6JX(23bQ;-(@G^ z;il0#xe1Rjo7BW4ux9nrK|hO_xynN36BW>VO$0-X$T4pDn7kPUhFR>lQ&kAf+sBq# z(#;hEKTl(wV2>+cW%vfNqwGsGL}k-nuj34lv|dk><(aPX*pUQ5sPkmc;2B=iz8ic` zANk)8sE+|m*$q`}R$z3x!j9~_PNu8eGq3OeTYwMx~YVMyVJ5w5PfzA zal)&4Zy-^sNN{pxaAF3rO*u9;_%^F}&LXh?KtKxC6qhnPW>U1a@7R74XcW@v@_b$D zrkC={u;u>DQw=|SD>mP-wW)Mm+r33`43z^qDg<4T_ipbY^SMSGs__|ez3+z)ELE!W z{(WFzmepViNPLXz@4gSSsEc{6L1WBff=g;GC<$~#S4xGt{B41*W;AAuX}@yQuQ9qM zUZJ~Gqr$RTi0;CnntwmkElJq)SdRS7Ce z@a3L8VTT_uyZRpjrmyw8%4Yz1KWVb1sVgbvUI&y;oDy0+r_0nX0Mt_=;pS zQsBB`#axceD=2l$*_IpAKgwFVy?7%KsC^XTvlR)STbhIkCSl>y3ndAyAcKzX!MTt6ubk|fm8*K8%7(7<(}$hIgTp@&aM1t+bDuYf0E=MEq;Dcu zFO}1Ts;`PPHKN|WI@f+FF~ZJ@XsXGWs*auKu#^Svh7>?3dEje~4m8r=D1r+7AwZ*> z@bG%YGWUwuEiEKwdDjTU2QA=0qjz~DHM|YR?4iIZB20sdR*!E`GxB*pw}PyUzGirp z`9<9mn~zPx9yO*FGPpgFt(I3Ral=}i5#)VgD17gp1(~m}L3B}edhzCHH>}Q9ik2ffi z?@ul{w7hEhN25YLr-Pe4CPj}!0(t&=j@2gAc%xAhRUmNtrAnU^Z0<@nPFo-+WXheE z*&XmoHiNsB9BHv@mjHBJLhQ{-C#7xog}yqMeU1Z?ESW(4j5~;x!A#BQbirc7j56sk zIU$^=|7Sf&vLmm!EC2E7JrJK7t|zI`MR~e?v8N+=A-pbj%t48d4oJF9MrcSi%jxJ+ z%eA{-{C7;ea#T;fRCiXqqwy-y&zWI7-x!crvddF{Uyhr6@d)Ta3dpFc!1hwb*l!j| zqTT2@v>#M(;#bQVk3Bt^n0|+6ft>?Fp`sE6U3OmM1OejF@Upkfr8XEQw2Xt3WaT#8 zZcQ&**<)U0Q&y~MET?;HfN>o!B@DkFws<2cqEPO6ybCH^kE6o%DAh$ouN?l0ctVYw zt|PZsDg)2jt_9j#xM~*8jGaOJb5JeTdf^iD6PeQYau!h^X5(E^qOPINj92iNIK#^y zE$_$4fnzo;Ut*N`92aF!)Y6kIoZ~tj}011omfQyr-%X*@Y z)9sAf^Aj>#P3?C0jT%wv130!&o3U^$wtjSM!A(nGR%-p|!P!4%kJW*zN77g%TC{O* zZIysbqDk3G;;!+dj4iS-_Z+VF3-i3`S{sNDJQz+Vr(w;lo5QL<$4~KxW0vf##1nww zv&<0gd=hrvs13Y2f@~#=HEs4nsne|=*`;D5^BTlXU4=$v(rYw)b;q_b(Cn)#ePaUL zT#qR~fn1Y`AXg^~1?n$Lvqtn{oRKZ|qUPcxB zWx-4fVok1#lt=2d`HKoOpMN?jy#{t)F-A)c;xo|qf}Qvh)(au}t8#sq^D_NOfGT>T zC2D&oB}xnD<>iEzjxkWkSj{5^hZj{lN+45c`MD1O`U1BV@9+R^E-wKrW=C=()-`Sa zVIO~j_`Jq>X2Qik)Dco!_=n5$^6|&%mD6by;?yWDqkqY`!%Lw_IM4ADe5MTU1W}eA z;_9Ars7PHSX);zlmPuFy=k@}(A5r=29tZIWV&hsnC;`<* zxacSRkGauWSm#EWUM{fIhW`m8^l%jrez7KfZg}$8TETIrpy0deK{oc)eqp42LvnsD zW5MW*Au1)+@*)TFG z2$;LH54V5RaV&0QarlfREuz-Va5@#cgWdPSQWjn`AIxu<43|*5%S0^dD)9T~@=5)_ zx{|hq3jo9Zo4Tlv47w<{^~2m9!e%O?sp81^M*dLEL~D^D)tTP{w#9wG^1wHZqt*b= z-sSsBt9tJYSQ3{VzJF%qMV+ld1zg*R6&QHNA9Bc$I+t;q(JR$5KuO&1ox2@H_fD=X z6)Io<-9k7mCE>BHlmaUsy`ubZOM|5N4Zwb(NZD2I3Q;Ram?&0r?hf*jC(BFJ#l1}x zgLQj5kmcn`TL&ezf7d*Psjf_spa$lPz&uwMgvV+qT ze@y=4Y`!q~{PqSTXR=yc8JV6swjH^EG4m1_p#9i3D}&*yj)5dnk2CdtbDVFbP{-Lm zW;1sG4C7P}Q=BDpivvK72gGq!kOE`0mE#KnW~(cI+MWDrlH0MU5*zLgUE!1{J`x_S zkv#uh`A}~yF5W#IM@}E#(=jeTwom6epsCq=NzS>|Sf(`!#kjdAH%_Ptb=6B$coh;* z$P4Nr=LiR|FhM=Vv3dPDGZX7S?mRnM?lxGbfP|7%yC_i=Y*45jt(yLt9=f#28DHGX zQ+ES8Dq{&9NgHA;)#xL8vON zg>&S7}az3xrF-57M*XPB_AHZ>@4ubClA*5UjWnhYy_SIL{Q5qga< zl}ViWA5X05;Xrt1p!bCI*)PwFh5LyId?z`DJ1(&+sPp8N@aH8nMQG5#PY zRR;n%Bp9T&=xScK6PoA2oo_MtpJI*JJV4Qm$!M!uJ>ON9E`@zF>FIObQo4}=?hc#hIV7Q|n6P+r$Zzqg z&SsGS>;Iv4L`41@No9fI-qH)7itF6q+|ax(8hMQg&iTcW0yVNyrQ>t; zOGPT5DX*3WvC3#>B_@&QE~LJ`vcxYBH2N@a=xZ04yx@m_R&kL#>2 z;LFj^@A+aO-iZ9qmcjp6;D2R-SN^G3Aw$3l{{!v)YWO#c>#X-y&6vZ^g5;T!Vx6$2 ztf9+j{w8PUUH9SR<|W^bs+KaTo}TtloG>A%5N4Mi897vV$D5gjofYy!?;6n5CT$b3 z;PL>RyJFML`vXHY>S~ix;Nh=QNQHxDgTtB^FR2wHm z?HK0Ntsbvbclvw+SzV|G{k%I!p@_d>-!5NIFG(VMvWfc! zo5C*!eGJ9dm`Ps{o=FF}BGjVlVTWc`FHr-{R#|=Xa^)ScmA}Y~zsL3s_df8_|DB%# z4z(Mqn(CYDzWbOw-R2?asMe4y+Nt8w)N ze;$tn+b3hyPls-?OtF;RDpgaOvI#OTdPK-(Tn;lKVGa>d%yW976Cr78GalI}-$OHk zh4yz3r)Vc{0cxz2V~ZRn+%^sBnlQYt?|Kk}s6VK%^u!UJjK@X)9$xQz3tC@u8+DM! zxml2e#g>ikUe3yhXg>`^Ki#e#OKq7l&TN^UT-t>A*)p}ysHZPJorF(D1ikPAOBAt; zuPXn`xxUn1#XHwT$h_Pb5(W;M87|;k=tzm1HWXHCDD1#BWrKcsiQaHY@Y5u0r%RM- zDprwjUwE1ptM)RTzVHzSHJ0hUVYk${dzoaHR+s2TXcqQ-XkvjHrdTSI`zp1D1~?BS z`iHPUaly@{EP#qJ%@s5hpo;Rkvvvg5tEP=m3ArL^TF)|16Y}_$8bX>{R((5GAvbrn zgQhVdY^YPnsZB!{6lTh{xg%$`awM|1Nip$MwibC#c$~h;y@vX?BY|p=MI63LFIgL< zzZX~(2UCI5UpQTpX+~?x8bZsN<8KIl%&xb ziBiHZTiiFP3sck2`_6iBx6Ya&%`)cUGc`1Cu!O6GG2!_2_v^a{hT;*sPE9KDT|ya; z2`y2Shd1KNoG?d?RwT`NrV zPLHl~jouBrli$F|a}E4fP~2*OZ9&nI@(ZESm!?DAgHG7idf0{68+!EMFUz(tvhhxtlOv4T7We&!Orj;=KCVOIXrrjj*hPo{mt;qu>c zn?`F#y+`eqLTjE(Ge5L9R0-Z}8DZoP&~zgK%h^D#`0Gw)NTr(*H5IW5)%DPrK`-^XIRQHwb*D9bwJ3Z zaE<5l+G+hx{KDlx&H3OflDJl)82P>jp}C^EiNSf43ES_1i^>Eqy%$r&k;k%RLBPqOwd+ZWIjOm?kuZu+FN*J-FQo2L_p+J=6Si^8s|^${D>yeH19A?M{uXsPh?TeL zuewq_cWJ;G^CY}W&VWp-7L0UF3RB~`ayuz*(F2v)NvLvd<;$_-TP!H-OI@G+NKr#5 z7!?ae?g)<4MoiN?T3<1TJxjWqcjoMSao9l+6hvPTGbSwM$n~%7k8B#VgGP2VLsewe zyv@{{GHaA_cSwj2(L2`9!CtntNipqe_I*;rtb16l6>BU-s=ZxBfp1CKqiZ4Vgr6wOB?=(drO4T5zE{!#~-`IZ8{h#D3l7JrO#kxWabnK3Tg?Zw#>lq5OOg$%h z`J8gAU@$##}f4p|DR&DJLh(LWs# zo=^RZV)aXrZrWh`=rJ&>mvh{C;HB@8E+IQu{m$ZE@538zz}kY_&Che>8f{f(L`sYd zioqopUVnCoJ%)JzE6GYPqb=BQ*7=D_Ze@2w&or4=NBgTXHq*Xj@r6BO;baEy}nz&s(TU3L`}xWcrVSBz?SY{-%7-ZM42oP-CT zx#GQTiwwmv9D>Irw86(cKE1V_?ZU;L-;8K= zo)1h~%{c+E*VRrSj`cN+P8&)Sa31~{KSX!pAxK_FB(0< z_+*|?lz)+1dL|W{O?}Fsc2walaOX_pv{N}#RY+~=6Pylj<8a#PNNNKv^qAl-2=`iL zhmXyJip!@EtTSVm;j4%3Q0f)(*a9=ZkEwdcTJp(%<6p2tdg>y3e!Ccxf7wHfAhD;; zr?o=U-NVpA_NK=s`V^W(P{ej;70XUMD}8Qgx+|x?a%sG3#U$8eGCZPagk^2+>_Bn? z?;)^G8!JX$XC&Os4?R8XK11Rfzms*oekd!RF&oyb>a?qGV0=rW=nIjSjnsPR?i3+u{u~aI?#d#eJ`y&9Az}Z?z^ywe|hA8W*HB zV?26fhWVtSDNQI3kVJxd`mU;;ou1-AZLs4wXlRU*W_5Bgm5HNDW(Bw5 z8JR2lgo({x$G+-(e7K6?&Qqhm35x@g=7VHI27c|u0-b>-ZP|gLq0ogu#vRo?`U)X0 zd^mK!T_B@Hf4AsBY@){b%^oJ*R<4Z!V1u0&;2yd={i+`9iN8O{^29O0(z}{*bvLJc z=LB0`mTszwLOg9&%h?-e@d?oC2O8TsZt?t`w$NvBvOS%}lRKM)f(~25&Mw10I=il* zbEDSYa^gBdO}|wT_I~)LlimFh!_5dAx}z*V3%OnMc}a@rKng6I>Yk&D9|-HO+2wY+ z%VB7W*qKokENJL;C*Q)l*SPaKey_a^3O4c5ovfKY-Vox-;!F{ntMYx^(M*7VTb@1IEo3S8coFVwburOhll;$0?L9~WzpO$OgRzivh@DV$YR zXyE0^k?o7^jZv(-bT>*LW{W|We(3aHL1iW88s3kkE2)!x3Xw@jHitmvs1+`bWVx4d zDp;=W@DWWxJEtbh)EGdwp?`eP`n-r&wf_tx&v?ctZTPn`P(LQ?u>E)~Z0UV-4vR2{ zn@Vd5edbK}Wl(|MeOh5MeauY>9B9-vsv(oWVWSSq&W!xpEr4lvV`1fZciZ{*@10D` zC*I)B5TVM`T%j}Vm_?hGk}!U!;y-wK;TEVhH!n@egEw&u4SP`}YenTPAu0KDNG!L` zIGc(HAqW z5ldK&`{js~1q1$s%9yaA1U{XI0(Al$FcYfcr$$|Q5~-DVlYE+Xf5^q|Wl;1$W%}*l z8*_E}i6sifb>K||c~6Dn3|CsWglb*o(MZqWP}q{!SoAFq35jsX+vmT#w?tJg0`d1! zkk#2nY2M6`y77Bwd2U9e-c8PyOrB%4k=Rh->3et|E@#nkDAY1=_*`$cp;Rwj#%Q5u z+A|JETS)kDb}qOICb&Z_m~X15@f_I5v*xUE&6~nC*rg;*Loc!*1BpYfEU%eRIgxwc zN(vNaYqwI9;mJ-Go)cM(e16Nkzh;xJpx~C0Ow8D$ieCTl^Q%@%Qf)P%y0vh9wD+Mb zlkg@ez#o@d9-|q|PCOP&FEu$1XL07V^yorwkCSMT>vH7m{PTx6yX~IKt?yPfo1_d{_tw2z za`I3hTUoGZD95%J;Kyb!jL#i^@yAr>$lQmDckC#wiHSJCwncPq*X9t#(vZMjkSM2y z88bOGoHAN3*(-0nbJ?ija2X24dOc3^yP)LJ8nykdu0Ll)n_HXyti-=eRYmPWNdWd; z*>v=-WWz?8+#M5g;{~2pEShgC7!$JQ1zz0PND*QiaR(f?>n#UN^2C7YZx3G)6DRWul*qo&V|4Xq6hUY=qe=D&Pa zt8q2lED$adaBODEVB&0Wuw?y3x4n(=ROwvS@X{MX^G`9AydB{5ZLUoGB{!ab+!e_k zL9^5-n7npBH_?yGEL~BojlofuS5*Xu-&K8>bq)ne-#-X9iD@Oo1vo8@*39em1Pn6# zWbZV8BjJao{@MHt3OL8+hhRilyKQ0cZA2~5eC*rVckF#KK-gHmGp;+mS88$hw#^=F zZXv7->-(y&){zO;Y%)9*jdSm+>X?T0s&x&=OvT`!xL`1c{`5D+i6!tGPvG zp#T`PLH7~fwuB-zmDUMS5C*ZuirfWGiEECj&y$d$*QbqH$cKw%$iBmjORXfa37S}C zWQCk%!TupbM#F+pU?5y`VEJz+%G**Oy;p7Tf~kDE41ct(2H!0JKE`=7s1o$(qyz5>48cn6%{rmng;FmJ_GjPEq0H9M* zD!pu^x?ODSco~e-VM>#(d=+!to=ZN#_JjX2@fR-e`PZOwEI8C5s9aLPoqm(Q>7I#c zl`^`c5$Tz&FmB7Ue*(jRB2Quh`Wh+?F;+gJN$m$`6Fo-&$Dm%;>}zRJ7C()0?x7+D zAR9Q}5Qlv->oMTTRAh3*Q^6~V=X^3VBw8Izo|+0mA+0bV8A!7Dm}`pdiUs_nORuEt zd8-1C&9&=$$!c@dbC3@>*tO1d0E3C){`Xs!Z^5Q~@+o!5TfEvz25ImGI28uJZ7sW_ zfXi~CGXH+L6U{c*2LTfKN4;|u9v=7aWYhZ(z6hVQJ^JYU`=w1|6k74zNU4$LZM>^D zYe79M^HEhi$D>2?0J#>-{ml%Yu;th0We1@<+ayo)5%W4~ICLJI=ww>J;y}Lc&7@BHF4{5nN;e)Z^&JnnMgni$GR{7WPmOYwZ`>inm%fl$6xpPM+7or!ydfb+!6aaC z$}v#mO<*Sgv3omr-h%u(^GZpSTF)Z)k0UnSl4%G)?n_A@btTWEJfi$`7Qqc2(*$0W!wQSpn&>gT6Oy{hI z>D2pO7~D>Ae=*m%8fnk1e-1u=<|NMethGe7T(y0_bIgS_X#vCwJm-R z2xANkJ{d3oVyl)(n^8^oY2D({IF(EAeVby(-&v#8DrHN&!iEX%f&qRh;TCQ#j~u8z zPz`eITq$h?J)iWW_%%P^Es9?`ulVJp-wyeq`Z!*#urZqGG;GWvOTGe4s-4o>?__-M z^uJclG44z>{bl8_)#~GA$N$V#Xrx807|&hB$~?~L z1*yUY;$^WJ=93SLt$#}xa0s8O*54adTR>?dMViEPS+oRPS8PR7U^_}?o(@<^_tLI` zK4?Y-pN_wy3N;RctnxnO$Wt{itYPVEID(I$Eq$QM^R4C)cDeli6N1UPsRw_IaDSLw z1_#OPNX#QX$lciK5O>-XI)?95%;lzodMH6aIz{Ienn0pu|FJV93D$4o3|ab`j}-OgM&za(>N<#N=Re zctazR39iK82N=&R&)X=7E;MCdMWiw}w9LE{2$@ahMe+ba%BwLSKLM5fF_Ud`V97`* z^zyi}DYxwWM42u0si$Z>#_v&GzZK`sYck^jG`VcF1Nq84IFc!DJ3N=SUedEu1Q67swNt;c_S5;(O z^NZP3R1lG2ezDKdX_6|qLhZ4`Eu9KESGf`|#gGG6A=5QO?TCC+ALp=2*Y&6(PB)hlaGN#8a1uZ!!_*-<0XQkkXw-`pSu-*hb2RbO zZkpG><_UJD5*VT@AJ8^ z#T}zP5&gmUqDF~}%bInQ89Y+=0wY)SVhcPos2Dd@t1CsY%j3QY`8`;aGOVa~u0)bn#DLZ8OZC}o>MP?~y^a-?I9s5o@IZ?zrC%_%YCfia4s~6y1;^R{}skDY= zB(&4ZPUBiy!s<}ZiEI3hfiC%t)$ChS3a0PF(#*Qe?1O+Bud2X_#XoCOmI5C(csDyL z+$y1k`5g3k%g~vx)U~D{_VDCm%@@lA3e1hEO#46^w zlD@x9h%vMhGOpfiEcI3$X)!|FN@aREhZR)2yR)kz-cmXjcVjS_b2W<@u(FXpz;up~ zl$|JBN_amZWw(Wn1GY@`QrBo%)}t}$+K|2tLdD;|j|~^yogJO53%23$OL2D)PM~ny z^fvCF`yU(jWTXs9R*7q0E}F9sHsy#|qsP_GK4<6+ZtlO()S$U+WQ8bvM`>V8s+rJ5 zBEqiC{3L!lqt5>H{kdR^=R5=53zNq8-!0pti(!WS*Bo!jh}7aqb3SJ};O@EApKRb( zp^GTJCiF(brn`Xt;wU>|!05X62lz_Vt(NRN4EZ0oo=#~6*C2Bvcb`$up@clI+O%<* z`^(dMx96HQPibKYN%~{inz$*Tn*bXaQw3!2N=fP_F&-;jw_Wp8qlRit*W6W@R72MV zb#B{sho8AMc^=nhqQp)+9INVSbN;)+N8*LhJ9(pN&jrC7{m@{0`(afi)Cu45`r@VD?1s;mC5%r@Q-o6-_ z-l#SUi9DhQMZ+A7t>8Z1i(XnOYEy-NAMacHiGiz)A;WX}dZ&&GV2qd>qEx{58(2eD z&$D%oAT7l%(knBtqI(ac@tOkZ1>IHnV7SAF%mj88d<}Yg~j!c6p49gb@@`*-b;*(>^}4wwNsfi`nAFj=QRj%s!rd65-a6AL3`b`~eHIH80~% z8#{Tqc4Gl0BCD}4N7XL>v!{7HNoDV7lJsbLf8&R@!6*EUmc?QhIJQ!jlE^c?^2x^ zG8gpg`({ICKE#Aox?K0yoF1hYL0kor5gqRt0|=jlDkgQ{#ghReoN<2c^^y{{5ByO~ zW;aDo0QMGW`y7eMbR5+@qk zFX?LW7LQTAF!XQkXn7MtnZaoq_G0mJs1<4vKeU^1f89-(2<97wK%U1skC{b=Y!e>| z(3@YK=HmSQ0v^AQb>Clc8ruuFC6hN*s`I*u4g;2z6AfJJ=Y%jyhZDM3=BOXxc2U&a zswi8B*(@^_WoR^zJlxd%7}Q8nlJZ?U5dyYSN-&p%IB%b)%x90^f-e_EoUjWsw3@}{ z*uDnhaldSv@uda}8J%N*c(m7JoZgA>^OMHIr;llV+MPl*moBS6Gjhv0eBkA#rrC>n zP5cS^>rwBl>DEgBmA4CvSHdcyF4G4YTuWST-xU1qTmdB@KO`hcE5SVNOntB)8e-V7!rlJlK`&!q{Mh51rM911umDSJtpZO{uPk9j;iBTTIEUAhsg%D)E;5jEqVM>b zwiZWHW~%$@dA5ETeO0i{v(JQ!;`DY4j81L0raHP=qaH+DY5TE=Fjv#qrR29OX5o@r z#Nm)eyG=3Yo-W26SScCypE%rW$(b!9RYa-Dfyk32_-`~u zI)0o?We8Mpl8lc1v_|#VU`bk@i;da%u1E`UPdOjR@0sROoChr#9ruvCLgttd z*-{_HcHlhb1a(;h@8ib_*rab5g_qNnR|p#S;~APwdvB~usyTT~DL3TkTic^s ze9Rv92aq_^QwI#~wDz1mI{P>eSU88Hck{&*aS&xEjsc1Vo@Uk#y>*06;teFq^`+}T z?((b$J6zi_%%haXL#l`v5kJx$*a?Rb>4Xz;9B(Pr10ys-7)N$Hpz2+RDr zcB7h}=b?eXe~q-3DsaZNVUyu2hmJ1POpj*@a6lO~FHcubVyYXz9bGGkD^(S9sPfCz zF!vphWSJHwH%WGZTb!B*4qMwoAaCm@8YpGagbNoaBHri)nWK$|{6GdP==LPuihWJ! zHN)jvj}9kKLgLmMF1r&SMQ{+w69>xzeU-<&Bae%T!VcTdd%il$s5{@!|1~4$hQisI zJMUAbR^K>kRhMWbKj(n4MG3H=^}g3f?)}3&x}#pGDl)d(aZ@9+$0O#f$xu?>r^mD- zq1aI~ZO(knkK^jN(!lDp}>UT#7)`01RLxnFQ+LwuAn9$~XSf|AS2c1Mse zg+ODMgMnaIs8IN zY%6>wmF!ihLy&47p1PE_sfjeTomMnwd$v?Y$y3s1V|0sLX`i0pWb+i)d_zn2eA+72 zTuoMHkR$7t>FNMcY{z@%rkEjd7h!GM!q}MpMNXjCr6U$^*_|MKn;x>jFSLpr>pu}y z+Q@^QBUMqEXWxsTv{PM}i$GZF6WEl|Xa$V#nAOa)CN8Z2PV@57Q*u&{r5VJFhMSz} za<}dqtn%I#-?=+?#_V{@k9EsqPXmC$t_ld8lj74*Ws96pY0*LHB`5Y>q!4 zW&#`>T}^vf5eGEFbVD(;ts_Wlp1tPSwFW2yPq5U&od$YGNru+Essn+sl)|NDoo>Rd zA24auNxQJ`c-^KA`d_y$MQXc5E;5tTmUzpEvVG^CTLqlyUmV?o z*0JH&y(=-gcchb(m^v%76ln%&_c$?^Q?30B$hGBr<-yo%UmGosg_b!=l_|Pa?ioB& zJG+`C-6a(9zN)9$dl{1D^JO7foqsj0(^P8GK9|>kHcM7{e`-O^y)o7;45gR?@Bg3gzqSnQeL+03D-|#)IL(SM99FG`jvu5NWsz zLhTwb(ZL{boZo8?0X^K}XG6vu$|C2|Zj>F(qF%D2Y;M9C&WH}qjLDk*0vBEJMQ)lm zSCc+cHM_u?$mkXix=QG}O=XAYhhl=yGwYjNH+A1G7|N|%&c-yYs^IhPLC-4j{b?)9 z{wtT}4ArPi_W{b}%}GVBkEuiGa$lIL+k8D=k*T-diB|Om!KB zbV^2i?B$ajamC8<626LpO9iy;tGoe&vV@_<#8iJx!O8Oj@vZwyW@tga1Hox(a+~uQ z3gwxcO}dR8||AEVps)bk;O7zmh|K=n>iAmcO-cpU?x|MpDk;M9eBBr zFaMG8N~X=Qsmz1nENz!;Ay@hCFWM``r}_U{&{+z4d+D#9loy{A{%L5XvGu__gGFa%C%YN#m1pAP@gDxUFD;C}S*le(>-ZPBu<79NC)PU?OwQwwkU}D6Nr|U_GjD zrTS8#0wksdU^iNBl%`J+cc|UgZ;?w%AUvo^a1VHwLP;2~F24k{@4z!CXKQ5IE2`yr z=U<(453qV^9UQduA!9aBjj7WPAq-!1COp4aTNkM!%|DN48n~v_`dpa1Q+AJO?%@&P z=aTxCUpTkgAYCdQg;&WJDM7RoCjTlNlK{HZ*xM>cuX(G*L=?ZGej4f}IvcbQ-tn6W zVhCe3gvVQPv;Bmw?s564pNY8-AjD0>7`x{4&y6VR+#FFb6xxPVZnkQ2VoU-;sG{<-n$u%RdANV-}L*TLoYx+(aF5aq+g zZ4_m}xJdFQ?uP(gAL(wxZ#1?Z^6i|<#X;`7zM-BNb^VDYmfj%r4dBFB-X>fN5hEw}l4FXnyzfxT1>MA^uXeOaEQt6US z99zmcRYZ^Ms&%%VT2a`wPFimFQtN@IRghXW>2Ab<@kpo1w*3$_l}0@>XQ3foSl)RZ zylIY+(eKqPt&3xnId>TW&A}z-GbhB+b4594Qcb$>(X|-Q7=h+n{1%J14E5gV6_N6^`sHv;PM zw3*V%PmfU;!;Q%*wU=bB9wS&CXz{U`(2oqytwB#Lc`FrSSgQ-;q%n<%>ub>pfuGRl6%M*p|o+{FL>M^n0SaOV(NCDY@;9UEQJNjU9r@q#;JlJW@ozC`Om7Vj#!7_#e z#9!%tF*Raa^D3va*{~}dTK+sQxPq?D1~b{|7(L|EurqFa1dtW58-(=j_pEMoxTvye zD{TUY^s`WcCO6*2G)qAkL4#?2>e0_xcIA~BzN3cp*Dnym&Da$=OPv`VD#IpV(Fl*t zJHVG28tj)Oigz*T-K~@>7zV5gT7CKa^XP7ms~9FL_CgmPMeCwumUR*90AxylUN1uK z(|E`r5klQzuOv8o1TFT|bHriquk}l%OTP5qr;CH3^SpNm-U=~S3o6chMTl)Wu6da+ z+gHHYUxM%hkyo9O@f9(V3lITN#d`FkhB|C^sI;>2qSF0_OZ-vUhwb=8DI{kZ=NJ-w zE6Oa}yqz!Wi8sU!c!0b{Xi*R(#mZM1X+3DU!U5U5&^#0@BWc0!+9Me6;H%;vm-5o; z?Ux_##ZjeZC?|bihc~hs^Ylhi^X2Ngf$FM7$iizN{cfnV;R*C}E!Cehswii!5IDsD zbAKOGq5VN}Z=~aWf#%jhe?s@Hsr>$73bLAt z*~yM?{yA3mhZnzm@hUFXcOH>ao3`I5lKB@QMErX2#99#;q8HI@_Twdg73tB-gCVUE z(=&^slTAMdEri#E26@b+qZ*rkXboh*#sJDmlmc39TV5z!wG)jz05;PFlB2a*2MoX^ zlMY=2DKf7&JaP>=tS4BS1#)(VYEeU6!rfNl2CKErS6CEnM6z1~nQp0_$E{P7*_>Ab z1gW(FN74r$VWeQOriXG3G{GzR1Ssv9%gvv@2B*zU6-Wa}{Aij#`oxA)DS8hxFgIjv z0Ib*B@`9?!inn0wDu90??s$Flb?hT&&T6aI>m64&JepX^@%Oi*aNk(i1T*9OYh0T3 zVG$mKM;B>#00J5=un~zY{jtTgx&H*;cyy&d&4b^D>dDAJ6RE`82&CSBThvKs zXR&Vlp5L-+{X?7(ueuit$E(!Rsb-7ajSXtv4tA0C=(R=7J6TOc3l?E$O;J?8`Q`?2 zv=6@~ETsoxt`K(pTIXiy)QdMSiG9WC+c+=)^=3~zl5RD&!{l6rnluEapLS;tt#e_D z!kMDkcc^Z-a^UN(zf?OgxjbVNb2kj^rYE;Bv|Dy4?3Z*!v5c+1)m0OsnmK4OJE@Vu zV23Eb5dO<|GL=youcWr>!ClE>M4+5qcV0h8wR6=}T-Y-k_2t<7Gn4TDXx069BXH&6 zSWTFEIzH%7I)eRvQ9Msz`ghyk*@R?1z0#k*Bt;r)h0y}mtGUf)z=)!GQcm{BrS>R) zeyt-89hYUojw`DZecgbE8A z_ErZ{?4kms$Gq&n8pMVd2g9koiBmgTEPk3j@wX=29mBm&p}g36CqVP-?}XaV8xEU- z^FwW8(5k@ZStuNL2cTXA?u`d@tiC7~rkp7|Sfpf@7AB6zGur1-SWtxtUS0O~mAHgIvD&ZKg)~VQ@f@YQ4+NTFIte1&on3&S!G`1-LAL z1spQyxhqo?HW=~2Hss9>f0cP;4Ix5~_@Jl{ViRfjt+$LiueqV{+67Yy$qU4H^0%E< zXO#5~4_aN+%D+!Vt)>aK^G8aSLFsC)V3s+)p1LmnzAsGypE&`^qLApaINAQ6;g3 z0i0#R)I3H;m;LE1+~VMvmu+8>Zy~1x&`>D+9pL`8fu^b$Z(ie{;TbN2J)nDXTb;D6 zSw|ar#-Z*4T2y?idpL{GyiYz1cqb#7bshL*>q`(BbZEZCDM*0ZZ+2i#MCd_dft%~< z6g8oRQGN#y#Nk{X#2-?=4>Y0_UzOZgx&)NlL;wmbmd0j@+CKCui@y6x+59ZTWC@}~ zgJ{AhEQW;Gv`sE{nY6iwD{|@aABco7$p-!}pKilKcET6X2#`?`VxUCi7p8nzQ z;^@xa+)kbz1#%OC!@J?eQvS+?~ z;9u#VhEk6r*P7T!mxqtw{KGZtvq-aVTa&dcP7Y7kwenCD6Ez*W% z$P#O_$>|5r;&n|2S&q5@YV+gwPc26A@|yJaARhsrMCsCcNhisz?IS>obbIEr@`vRPOMqBL zwbN_W5$!A1Q|itGG2{~&fmHP1kWz}=C{f=nXrwmM`E-D+I@S3ZM}Kf~V-cyqisnu9 zJ?TzQ1vH1Ic;M}5ogh{Fi%L-(z{mdO`y5cvsqhE=!En)*y(s*CLu731(T7de>_P=a z+!7<`O9FITFd}JMm2pb^E@HT%H{Zmtc~2Ao*?aMBy_;H4RbYIa zZ0rM^8il%fa(V3Egq^@r0-S`H?{!{%@T(h9AzK_AB>V)$>w&W2A)jELa%GzVLJe|cECeo8y#>RCxz#NnMkL%Y^%xe-I#DE70TCb(3 zx+ftID}P;t($rX^CN|TsmRBtOfUPlGs%q~w4jZlp`9du**wAUJgq?&IU~|TV`OS6% zk{2c{g$Ic9I{Q$x-Py`06IypPrFidg>6?kp!jbyq4|dUp-m3{6dUD-GxK7!$1Hv+$ zn3#BtW#y)7`vUcS3Z=q8t@!OSM2kKpAH^T`Y1L7z0v8>C3a3>7s zhbt}CfL)2{$fyf}3=79pb&4AG4^Yv;vqjs03 z42v{l?sa$X8;$h+05mN};wFBefP5u;G6E$i@fx2-n}M%8lcI}m(?N|G&WS0ZP(ZUT zoRf%8JZmnYm82pjf_A*Tu~hoKg+!YmUJ&Y)rceqKsfb<`TF@PR#YSeMQ1_EV6GFZH-4};m58bJpMS0q z+X30WFdBfpaqVNRyh)4&J)}S?9;R1+RBlCAwl>|#(m%vc`p0^hECb7mWKu;ulzm}9 z4|CU?)gBa4?rN0x`!Iwb#!Ifq4umq9AKI%~2%OS(n~Q6V=@+gXl~+WG{Is0kbcwW~ z5PZP_PzJnFH;d&G!-NyNk1DzB6qD`b)x~H-7eTyP8FCT&5hqj!6a?V(=PW6}yWdQ_ zuznYi|72eJhX@a@fU^>X8q!d)a1Q{^IR=DL7oc2KdMkM4zc0jr%hslyEv#BGl{klAuL>Fbb;JCQ8ZFCzEezsQqtG}i&BW&Jw;ln z^k+S{tQ|GV1flgFOOD36>s#f&AON+&wMMYY_yk$+9q#nlITD#3Nh+(io6GkY+F%_3 z>^~fh)?N#c-%S>P!Gq~E73Vf0F4bZ1#w1N}B4Py>I(C{Qf*gw!sst$m-^Qme0>}Zu zC&Q}3&IA+;Ho%<1o>3q``(g{t-N2`FxV|YGt{E)~bNh+1wpuC2>Ee27@XJF!Ld72C z(W~#rq{=x3swlaUGI`}_nxcNcgEL3G!*C-ZYL}i~&f@HH>TWaj|cM@;I=7wFK^#+(j^v zj#uo?yDQ*7@(?u4RasO^S+vp(cp066dFesDZvi`A^bWCdcd-P z!+@_XX27Qi81Mf<5B&dk1cH4J5_sjrZM3n~;1xCDbZ@wlb65Yj`Hf#dE|(~0mwFW- zyL|c0@#V-hi`bxiasCGvxl4e+lo24r|EI3jL;N5I8LcHJ5Ff&?-Q;HC;gt>l#Purj zA5TjPi1^ohK&=P(uW)yO0GVO9$MA2GW^i%~9``AnaqCw;R#D7=B;C4pS!qNIj~jqQ zj?(0qf;QPF0Jj3jOOgKtTwbgFF9Yt|I|{YP$y;;Zq_57n?K`|c@rzTkop{@4@b~49 zfaBY>``v&1f6&vwHX`x=wNXe(z_Re%kAiJdg4I5fph}?CobOk>>kG7M$BqZ?DK!|u zI0e!-N`r3sf`nE@sc~nQ7X|zRfxb6u94`hFEaxdfzcHK6oN){LC|E9nH`k?_M8`+a zoMJ?Yyo{^jmjHLTW~X%>QLr;mhld{%#q0 z34^OPB=gk%w`taRZbwX$l?gXvAS!m0PCL&%XWoWgLB4eq-jBZ-HhCl)h1+OYb# zY)P6P?JQ;0}lBkxgGpLaJ)9T!(c)YfoAC@3WV-wQCW&-QdA`SMj{DsY|^U z1~kQ8a`F&MoAkW=u*CmFv=&Fqq5VI#)4k(;JBrFFO+wO_dzoJMtF{Iqxl7fS+|UwM z_!yS@eH79pmqFu(u;_OGKw2M%3At8FrmbsSE0tSDmS0@-Kk(fyeTYyb|lIefIS~rRSgxS$(|GD{sHR>1Ws={Y5n!P47vle90p}71F zwD^rk^in1Lq(gX*D`^-<;y%W0Sn>HG{u-Q3$X1Ov1O=WA-7Qbl0~b-v!od zAq54A@edx>Y%^FP7=NXiAEg3Px=UJ;@kdo1t+=OW9>w5*OrB8#$cld}JG5d64<{8k z4BY+duZ&LN!k46m96Q%If7pnN=Rhz+>T;1&VMHN-%zo42UT7O)v-%CpHF^Zqar&%e z2bnzC^0D1L0CXpCa%VV#eTGGV;uFWrHkUNmYFzBhCb41IyQ{T8SNF39A06OO_l@t) zWq7AaV}zzXA@_Rio?-|7qeUtDDpK2~PPL_^NnCJX95y`hl_vR`e1kjt+*{4w`dv>$ z*`Q1%fO2SA(p7bRkL~!zvhH(~z_X^}v9&Y$K$7{RKYhtX70)=!SUp)C8*kd+3t_)2 zkZiIKftm`)pVXRCnZQHsbGG41DRj8|16){BiLXCs6a--v!Lj=ODkVk0S5JTZQ2SrO zplcn?=hRN1jug9+vAY30ukEI9^y-%8C=!r0`U-_>&46<07LNQ-{4=qk+Y2gZS28L^coA9F#FV{9ktbdheS_Den=krm*_wV z*L3p4%#2w--)CYeN#v8*L*lQWEqJ{9=5a!=zsgQ1n95X$Iu*Q-A=%t5cyKwLe~#r$`u*daR`KmQ zHXB9cSkwe9UkUZhoekk#rl%m`N7IkUP{Acx+GoG9f{!0#ues(IQT_lxPf28e{S6sC zcje*EjxqaD7;uz)erbn#_NN_c$%nsSN>iqQb3wI?g8gT8@est(C=xLWiDE#O$wL=aPW=c1yqxWJ(%!GSn1dVw^AFRJlzKOfH?#@v=mGC_km10;0& zh}fNV7T~hfT0a07-hp(%>o#TJv8Ki*G9rsb_Rz9tag?;rOl#o-qQrxUzIu=4{o*tgle&9jPg!4P!c|J_@PM!r6ahF449Iqqg1jD4|AB1KvLsv325x zX*zg#a_2guBpJ}2I63V>QZ0fb9kmTbIC};b$)BEgST&78$F>I2qK?8Qa{NDh%^F_8AqFt10(+=om4nJDxk9c=S=Ys@peG z`RiA10E8L89J}x^@Blf|e2f`UJ{p~=dbskaqp?B2S4?(pB}f%{M>Xmso>H=UgdJ>|UDe@N*w7ruT~{aEe8Vml()8CuxdeHck1x90^zR=gXqAykfh z1a`+mE!$MY5UQ(UGwo9o4k1#Wrp%1}&O#(sRErAR8dYG#=Qqe!M@z zC2<()A5sn{^#DS7fFl`eS#)diul%N(Yqh)AJ-`$;6n6yii)0LEuOINSqykt0Uyohe z{AXO6dhlIO6Dpk+AX&A}3sn4Oe2==r0TB=?rhdDa`FQ--74%bw9G^wN^1KcRcgw{f zdrI0swJPFFTd14`Q=j-5>TbKK;}8#kTmJ*QosPb%f|6p#K2m6|1lA=bd$kN$sQ=FR zd8!gJ+m6&}0yWk5S-(=aV1+9Mzw+}+@z1FR2%T%w3O!n_+K(ZHEw8YE##( zK%pTN>#L(sYaisf&m7{pD>{eKj>mFm>&~s)r}B0|{moPy!oKlvk3U|ERGM`j2mWMo zUbuz?*th1pC+DpHzXFAZpd+ISporzxZK)oYpt)zOV$^Q~2-QN6BL!ee*Adui>m%dr{9V0(62S^gNOq7&Nk<@!}B!uY9uXNb3`@ZyQCoIx|QeYnd2Xh`pcj@l}}W4RiX(Z@bj5OYn{z?C~g?2i*5`P4J3+ zs5>=A`NGg$BUeYij7W_%&RhGWeD{R#?}BW(9ry^v(FXlwYNLZ=v#DeZCaz<9a*pS| zobhiP!*6r_>#=wNPtFbQXNTa&5TAiO6pjSQqR7Y1Rnp#lKSm_11#A9x;}Nr|?GHnC z?xYkccEjHvf8+LDIPHBNa4$I+y7p!*#L0qK-{_XlszeK$<%+=LG{`5cga1cI8s z-c4raO*FKI728UrrhoV0KgVz;&R`&-iMHxT)L>sYJUk(**L~VFy-mBsqT`oG|65$* z{EpFMhapoy6p5|9c9$`Ahx{Layq9ocA%{l_Ms~z6>I)vkTS?Ss8%5k->Um}Q--N2i zLBcVil=#@d!H^$-cx|vxm_7 zG3`~CPzDpf-w_DT+n2NseGScJD4Cw)ZcE=aicST)IIwJ-lYSy~h8oOxXY$zar#3Qk zsX6{c%tOR-cl>SP;H9#NbZn?Y`e9=!zdnL47`wQo7b$-eG z#jQeW>+z?Fk`Im)Zk~11{N$o}Vd5z1uCc0Uu{gol+h3tzdUH&2heLF3gWEOh6FyN{ z1`iUBJh)4CJ_Aqh>DK*oan{9@o={djAq)K?yUtE&|L z!;6FeHZAAAmLq>Y)_(MFJm|y!#)CF`_^rB9c*B%~U+cq#f8B8O+_#*R@bCY(K0wM< zCX&za=S@-l*^P2B_y(@k-wXBeT<(Vw$mRj)>OpQHeFWb7Z5Uoutj>EtMUthU_DB{# zrmKnd2+to0PG(IA^Wuj|eHhhfFFC|+g#U6lLyNU;z;0gO&Ght@Xo;08v^X>y4k-8o zS*>&RA&nM$+sfe=0p#%4y3@0AKnMdU=;#0DVDdS|CqdrPDu?(Kuu?)_S5MogC~FbH zn`%q^X*T>bvbb__#)PI(;tU3;Enky+`&%AXVU^%=N8nttx(9H0a5+(_=`qNlXy(fBl;dk{M?!guBJisS}sy+?{D z0=Gqv6qStMk66a=7{kW`smB_e}>0)kPuh5=-YlrN6CiN=B8Wrl<*QCdy1X^joca zfX^)QvP3-Q3u#t$&#Opm0P$1}eZ!tsAU7`?Gy!bA@7h`XYW(X2=)QuJgrvyO$)9H7 zq-noTab8xfw!j17V4q;a2K0F+uFo7nn9#oofJwjIGtPa{yC#MAyrZ|#e`4N#u=d!v zhgm4U3qB+|2gudj)Qts}g=Q^WkJ&HAN(=>Jp>%YjTb4V0sc5mEMm@5PbMwbwy7!cUVfu4XfhldVu?GPvY!4}J&3ub(dr}_iA*$$+fFOn%m za?&5#KkgJsP9+&iAO-PuO!lxINpc8d>HC|_sKsu)be!Wp$K5~%5qRtO02=b^NXi1G zRwCs^y`P5Iv)EAjd2O9~HE(#3*&Kpy+eYY$eq;pzka5~lPJW@`#&V4jYW9vPqVQ_y ze$5lK@*|zzL`gAK`{xblr5Amdh`8Ox2e!X?B4^tC zshV&+!*-LJ8}p|p5?&S(6r|cXOoMqu5Lub}T6~$t3(zbi+#VAw6fN@KlYW}bLyjxC z!Z0<7Ja>)Ngq9AZRCo8J0o|%ut0Z?%%LAcDyRr&DYN8uv&mF-%R}vP zq}Sje7v@?6@KRKXgoU&jD+x7+6p@kk<`Kl$#aIrpbHPRtg&6~S=r0V;SB^vlPgGXp zcc{YlV8d*<=e4z~vxD)eMO_1LP@~rS@!SxYbHigSYi59Qb}ufPliZX^Wor{|cLnVK zNE=ET@?TUgYEu%j0N(&r=Ku@vkUhjxn=kNO*rmSgPG{nG;JC}xO-yVDD|h!ZA}_uu zAdRy?jAjbv#hd(it?FG!BwY(9y&}lNZ>#1|pIAHxN?G^^)4NUX@;-VwJHIlv6}B9d zhOc+404EXFMXo2!KA(TwBE`Io-gq4nsRzb55JH({3-S??C7m(J)fK)Y$XxqlRyD{Y z(wz%VAn(&PlH=i-7T&&tfna;bPQJ8?;}!)Rwla96z@zT`OglF8?IOZM)7#e=mDf}@ zSA?(ZAhWn8vRfpfKD@@6aMxk?oIqw}Is>bH)oT!$zqs1LY^ojVUqmd37&cAL6M8(` z$tmdTeK1y1(~MO?9)+AZL2diYN_>M2?M+?9)_7n0WAz-XPm)ZnZ(bnYJ49C}%@Fw+ ztw>t8vD4V=a^&`IesBmg^SpaNnIb=yDB{O^8eC$!R_kwRVgp8eL8JqW!>>nEFXjm_col5VN4_V62&fh*H<}o6VegL?9yBwR5O6b&rp1m(3c9MBI4v?2kVq}*$jsmbdI^6n z>XrO`5ulK}6^VbWk4x1jO~Z%A&aN?Yt@YjMtgqa3AtP7{CbNYCFoL~EgA??sE+Heh zcDI;Dyg60%Y=>_r?fRYsyQ|GDm;XqWM90g_aDF9`?F+7r^Qhy$R+<%$Z&SeSj&V1; zv1M4*wd1Fd!x9BuBNH02BTLv&Gi`Hjpqx z%$9N@n=>DOE2y2U26?cjg}^__S{(@S5R6fRf4(FvdniP&+g}JV+v(BaVMj)5Lk>%~ zmq<3atJ-&pzwwbfkmhu{Mpu34dMsy8Jbij%Y6eqayaondW{JjILIE(a#&^KLdsoIs zVj8>0{K-S(BUTo|i3CLB#E5V7i#ULx%~o2v2k@EP;pSuC-en68cCbF0-UBPgZ|gmR zN{bI8Ns-QjVxVxX2;R`VpA%f+ey`*jpY9)Ksx5H~D1aU1F@~O_MGj4DHayg3E)ie7 zpU?w(R1FM^?y(@p!oz26Mtuz211ONct*2NwG6;NJn-70`B-dd{`JeL%%%RBuEgFb^sGzCI1q54kM|JIF4n zX@G|Xw|pv1XQN=NkTpX)kHB*D=)oN!;rZ{yRO=8AoyTRqzY4UmI7LvznFxAV zSnjeekSbgAmT`tZPz-mLe0)s~B_asB7^R6V&TLE86t2HUqvV*qH8R>xK=l7G)bC>= z7pb*P+bsCxDVtG$4go)`^n79&C5Twu(eYn5RSOBXHkJn%Y6oSKLq|mdo$W&2%~3QV z`>XVE^4=r-YXvNCx8{{v|6|ZUm%C4KmLKQAaR-8(hQSk}`&d!;1{dDdQ}2ZLTjRtiJ-FNt3Aoop}>UR!yV535^1G>gBm-3PM)BbT}?Q)_+>n+BSM6g)<9{6T&gK9#to9;V+X zOUXmhylWgr1AL!UV6|`KMf03G94GFUL`VHFpeEZ0l0{dTc>;bC4F2a9YEZ50cR3^~Jrcq6$ z+tzS-JY_>8WdkUoDGzq^Dy<|yAu1&xD1DEJ5RhJa5`q>2B?u{%Mua3P()WE6NGuSr zh$I3+XcHlZhyg+pNC+YDZLBI!o%f#mLJZr7F)|`_v-dih; zT>5QAEj=F?&p#>GjXh_Q7VQ@pyFNmjxe-A4Jdx8QNr>t00gOC`UBW@BYD)NWwoAB+ zysjs6YB0BpxM7rD69q8u&|Z@+E)rK;!gG7rdp>B1<|49-J{TPID*U|a^t4PRy^wCx z6|*(WN=_WnypP#ygI|NqY;mWrS&qD~Z=C^7*9iQ>D^w_cfAxyW%P+}!MakC!@w!KD z&$~jqXw1Sb<__n;?P7!YNA+V^H)McLGsImQ*)Dv(4A1?L^p_}dhS^`#cDkV`@+s>@ zU|%=UStfDj$JPST-aGMDFVqp=6>e{%>;F=vHdL$Q6wslH=Q?!dAuuhW&)hI~G4Kcr zg3uN!e*<7(EieAfizri1sKZgyj9{exBd?Qf4>N=^T^Yt?v%g2cS!l@flJ)aK`; z1ux*{ekJb~5AJRWNmasadHddNNB{oBG~9*Dg<+yXU_dkjD8c@TrhI{U1OT98(h=WT zf5AiR3lMTTfLxt;2=Q)B1b9$~vM&RFjJhgway#M;e{R5tDjg2vw`SUnjyL^|)Z8yY zXMP!N-rR-x3v6|IVAgX$X==m39l=WS3s?OELZV&nxWry{Q_?3b=?450i}`N{wYD$t z|0$~WKl=kPo76Wy6(YZmMP0Zs>Hsv~zpSedZ-4?G`1x-I9sg(Fp6zJ+|KEZC${lc~ z`m&3{mLHN*NmT|7GdFlv8g$0wLF*}Eh$}h8CO5sIXVOF|nnWE4@pjelblI~TGr8YS zZ;mx-HMX%gV+fIxIyeu?EU$M$P)`%9Zne7 zO>={24Qy2xP)&>T2RO;fGqH~o#Z@iauFR1P|bFFZ=D~3)TkqBrzB7EBk^7&e4G}xYQfcj7t2p@o8KD8 zauERRf{C3d&VV1!7q#ayqhQOpK*{m^1F!mA)}eGrphp9PqB16N{>pq(k{!H zslfX{0@pzQv9*I}eHo=kQcG7dkoCiomY3JqVY=8dfA&CbJ7AWmXRk@&oLEiJm`)jbni!79Fhj&ppWr$$eGy&qDR^h8XKFIpiE+zFd>HtcU-rAJU4zIGgU-d z+>_5mdCT3;{V@+07cnG8JtInS&E|1f zv(Pa4y!t2E{-J9GPLUu?89Q(KOdYXj#l0rVZtNVON!pp9r7oTJp=i>n>ppe*nxXId zTy=(kdAEM;=m_^*tV)MNc=adFwJwh`)hDvRg<_^zKNuJ}fOn{*Gxdnxh*P6AEppTn zeS|S&^e}JMyr^Sg8vBdwzG?B3d}*Z;bxmd-beEsq7!Mno3bLzv{G@(u=FCfV+-h`e z+$UdKIqn=2+J#CD*xfrvxAxp?4wIqv&xQwRZH=0`z!_caWezCN1n+Ldv-gpUVQqZ- z;A;k9u=T4 zrZ!xWW{qcut%)ZKgQn~!;%?X&xOm=4gihqL`)AuTBAzI1tTH>7N5%8yFjcu-Zbize z1OkP$=yn*f$g+{`qRjea^%xm)-`)*dABK6?6#$|;J$Bq=!UEhlXiVGW+vK|KZ}&{y zv;ELMdhX@CI8{huR!W7eOxKJNvYFhI4Ge?rYI7u8sqMEypbG z(H_@5t`BKfUO=IwWK$(bvrXDFV->=#=0%he>J0mklMUVgl!WonX#&Ar5IT1tE%$Z^&Hg8%a;48Y{ zgjwVp1K((7?6cRx=Bws-haZWI^@1IC#c;J}>;pV&ETEe<7=80==alqIt`($)Ml0;4 zIB}mAM|iM$dNq3J5g2-Wa*vw$%+-03s^&qp+#-kBsAt$5VHKl%Gf2k1N@~eV{G?|> zx#-+PtFKe+=34g4jJ{lC&=SN+*@!Ho>81B!L=ZNc+=U0Jc(t^`H9q#O7o>%Ag{mzAmT^zm&mKRKJH>mEDtzL;kTR{%oGA4RWmxu*dyD5e~ zAPv5bniFH(e@NEaY0M4BDQ&{PO&)$3okQ!kc|5__z2vOOgP1#%rzBVnpVOq~Q1YdO zn75dcrcJ|W-^K~Q`Gvc4G^GlIzn67F##^-1?o=Q*wfb#LJB8!$*z6u(6#w>Egr;r| zl)dtyD%>{GFRSNZ6fG8_T6d;XPjB>!G_@u|Ka;^_+s%xvE+C~EO$o@Zn75|PV=1?^ zwsyhE35Of|8j1zS6B7uKC-Ps9ZC-a#o^+OsM~aCTDdm$TV9tEEq-AWEwyrFQmY5Q>6gIX|{;c)Gg4eP*(M?PmW7$)=pAKip5tuUS_M^{=w}x>^-m z3b_s55i^?u@S`HbMl(i}m=TS_Rm=>9n+t+Mr^rSoS>lxPMb6D`@2$fW?8pY<>0H^C^>q>^e0MSM_R7;V5Qp3wsV#oRMXF!s^Mb=G*q z@3SD)W6L@uV9EtgrYBZgpE$c`F#sFqxvi|Yh`;k|l6{0JZAi5vscRIsDMFcxa45n=`s4CQ%sD8h*oxOl{9S$UgNOF*qBHQm0CsgjusZ zEqpc;<7WJLcf>Lnv4LX|I`!VdAcJYHpQE~2N+q}4#8R?FW>xG~sJNZuLn4p5*|h!~6Xe#YYfoeY=l1Y}LY`(CibEB~*F)VNfxpW{{6$RnHZ zX%I_4S#l@gS9NKS{%l#CZU0LMh^HQjxcb0Vh8p_731L|EyiE0|IZ89oS%zQmsKF5A z38uob$cxEok{O-jwZ4ITl?XM&%9Q`c_ZA5z71l-i)q-2Uyp+Tq1P1o&%Utff+&<$z z9pR(-VQ?DwGZe;PckNoceSbYx5dV@6)W;q1#X5<(HXd$IoJOz5op$%8h5Gt+5%*`h zYvA!9IYRO*zM|gS&u>j=Uw7Age#m|6r?!7|640ELiZ{JLr^M&!zO4nTQgy`u#ekbM zyP0LIw0Plcnf&gex(AC-i-E;4yYFx(-8EE>G`t$$$(C#%kgKU7`Y&p2YJ0P^4LetK>uy5VTLak?(nOejYHnL ze8!3uk^}ud80CaR2XQt=h}*3~7N`LTu`8>V7wbuIoehE90X+WWID~Ap81Jb(kG1DB z${6R$Z4dM6Y#3=-eG#QG4J2Dk25-?f%m>0zP9cCJBY(3oRj_eFU9IO8s2l|k_rGcY zT_{q1l@Gn;uXvYQvjF7ete9e6eO2RBWv654UEG6|!PR}C$MQFG#oYEHI zZ7dJ37QWp#z(xZIAGY^7ce)XN=|OQmQ0lylvwAw409oJdc@S&YOZ4!8C=79y+3ql!J@f%x|o^>&rJ^L)y5xHUII0Ehc zjd8cP5gP@-zU?Q$po<#?@_S2ggHXYQXG%=56WvhdBBb!reC2^EQr~hzUgKQdhV~=W zj)fpNl`~WRv`cVPHPycLc{^>u-kf@rl=SF*W>Db9%2(qvqa0mY>&oniQ@}WtbDG zbpNXAZQQagjU59n`VnHZP`0xuxu~Us{sb_Y0uO_jCKe01KjTioY?)05m%FD^U9fkr2H7x_-ia2`&2J&|Ns z(1{~H<7M)b<8q#8)&s^YmODc)udnH_y7iU>w&bg5ac$s5fWPlRzCicwL`zFbiIGs> z7(-{wpVJK{wX2^%Uov86O&M`AsB!h@4O9~;+#5bukGgLz$fkcx;Xr-6A4kkgtp@$g zzM3D8eE$Hd|H)N0cxrg9(wFM3YGPi5x^aL^>txN}4Kv)T)XgH**EX$W)t&)g9T|-1PHpi%FnrF> z(g)q@@I>X((D*M?HGdD(g3%0`YA#M~*}sq5JHC;rUwSjR^077E_|uz>&&pkerbWWJ zaY0yP?S6b=1LBi@HOkj5plv(`BE~GhUG!-p!p2!GKe#Zk4kJgLcA<~Id^k{(UF<9D zoRaTPUu)*AoYLG0#?aBLOp&D-@x|a_GKXl+2hkkX6W(RF1|`(o*wm~{9UA4LW;F3z zz@BHp46ak|L(2|F?|L*;R={nb1U+GTSU1*$%K;wHH@jCCus_6|QzX@&_V0V%TJY&e z+a}EcBDq?prnZI}d55k}AQ}%@6`YjWZPi^KcLIS_is!vB1;;Omk?>btO>}T>8_2DS z8v?muPqeHt%Ym?VG=~B`4Tg<$7>B_@gx}iWc}{G%oA2_>uyeHyR=4WxbNR|zE%`&m;nfMf)vY_7c>34jUqfHA)_vt-{_bdjAU-j6MaE&;@I@I=&5 z)7?eld4BGv8vBj!Lt5j5oVe?OyOCP=2xQi3$j?jT{vLIMMV+R{ENQ!WBWNv3CukLG z`DU=hlC%R5+(=PJ#LqxCtG%1~TMvBg7rugvN5wgNz&`BK{uvY)5eIS^D%aogO? z8!h{VBcy{w+CF(TwoUpQzGYKC=xi8;ssusa*lxP7X1aw6PNz{AZl>{i5)VS*xiX?<`oxzCHA8tW`ncwi(1r@0NZh`)5xZJkn{b>y9{%WyuCI` z)y9S;5M21qFcPE9{e^D*bEwb9fv{Q2Y5u%R&NLoWjYdhCdriygg|enk55lg@-}4>& zCF8JJZq1c$#=x<~3~Lt)Q2eB7=2d{Y^|MzK?ZU{latE+vR?g*V!kF^|2X(F*^2s8a zM<&)ACu|A#D~7FSN4I{a43;-#hE`#)F|GHJSi7kmtoydlDNTy13zdhEs-0*plHu^n z7@R7cqL#znDV^iS9y)YVoVr)0Mk7+wbZ`xqhw)Y>AEAzh-q|>-YuTciTjk9b6^O|5#hI#g}R z363WWANN(h4E8XI?@xYnw^|vK_OwJbkXnPC0ukWZEY|)p&q-}$8&leE_#zA@0X3eagIWLEcZ3% zGa7HW=)JFz4Cichw4RoG21)5%U-&x%kn>KvBus-#nVIRs^S5gSPBo1txZ4?tYdm0H(`kcTHbPVz@DIn#x>>1hV2xVmVqkfh%1SdqZN?RlKCRK`WQ&^BtJc^z++#+%h zpKw@oA29}Bg7)1b`jp2wYRJ4ZV$P(kUcJ@NN)&c}5-RfYVmQpZdULeuRVz~=*Va9? z5ucuo2ho}9C0)e1MlG$A9MqZiEl-G8>+K2IH0n}=p`^fSYTiiAUQK&I;rUZ~IJRtY zB(G23p_*#Q{QP;IjOz?^8P3yE1NO%RmC#RU>WVLA5iL*wLqem9->%PXuc{j{$SLigr^)Pn`Eal9QzJhy}XA0M?3v9Xq?|Q z3oUdJaAMv(D0|w`LWh>U}F;$J(J@hn;?* zD4nMR9tXc@1XaGVjgqS>g(~}zW$9f~CFsWL?ug~Jyt^jn^HZ{|C8y0P4}PguI{GEK zr0nemJYJjnTXJm<<_B)PQc*bS4D12WjIa}VO;B1>&9Cp6f!{l%zwZGQXYro0oLh8z z=ioBGqi8wljHMvLL9si?fuEEX{j9H8M3G4k!+K4wZ4+<yQsJQpvs_81L7(ytJu52wHEq{Y$Cr)* zW%LR9q|o?UT9xmQE@+syfYlmLBD{7_}gHYqTn#duArMpa~Gun zV*)DRU0$ZBzipON4^w2qbJmGk%DFvi4??i#LUIy7!- z)A&7%R~@agae?j69Z^pb<96L)#<%n}K=74*Z772b@a*7l;gr(huZribtKZgUkAyTT zY{JVx^M)pWYskW%lEOAS}7i7X69d*n?o4#o*@iyzP}7TO47p*kalPPrF9JPA$*t;n=~hw@_*ETM^-A zu6z+}Sm@%UmRs#y+BN5ZHJ7-oFZCyO$tw^v(1znL=u89(l4Kb3K zl2AA{qMw5MEzPudDiOsiCFkE)!PQoJ-%w87%6V2rOt}Bck;Ab!WO{jO` zT8*kEy9ZScw~J2>sgxG-8kwGcO(f4V*Og)KDQ#+6oJj)Olt3q@u$M>{jE46yh1)*9qFW*YQ?kKz#>dWOR9vHR6~!UfD)kUjeA!Cs? zF2=eVJ^U7SK5gN=0WUs0^*!hIi(3>8rqyGL@X%R>o9Cn8?>UjHx zl5$oTQ8MDR$<4_RQNcn%I<28RPSsaVDn3*k$%;7?M5Tu_MDv<-a8VdvWx%OH!a}$J zz_U8|%8)jcUPgL6nf9vntGjlC)|~-~i93&OtZ|D%UIr$b675#MjS_^k8ko3k8y|ZM z-C1S%2lQU#rs^B@Fyy);FCQK)Mk|l3Z;e&qjGWf4*MGPJg3y-6ICbc)C{~mrl)N;$%kO*~4WfU>+?)QwF24R9Dzo-%XPV zY^G*V@b!6P$zXo$p9^#n)fLt5I&XZS4oLWVAV!g!?ZzfuQ8_;SHQMVdmidg`THK)r zhCw0|so$5REm`48QVJeEx#vjhTO}RHe7i{tMoQ?#GA@c{Md=bsc){=!nZzigVtD*4 znt$wE^86Fv{5%n5Y?)uO-HA2#g5Qb_kgt&vF3$E2vELq~x#1fnV7Tx~yT@M_=B1~^ z!T?r~?sdfw8)jFa129D8rFjVXF2n{qd-jF4jTE@Q%m^n~_UJdE?#!$Ye3RNHYI_l*v#aO8$793g_-_w(+ahYXg$J;i9HM1bxus&{9Gw{KkO=ZyAzdIx z$c~x?)QrbSGj8Cn( zC74v(>X7Meq1IQv^l+8IBsy^IC+eSV!Sa{ab`WU1_OjUcx#;o9M8uC*pihR3!HaD5 z&csE0NpYYdkl4Ox6zp-{`%m=$+uFQeO~g__UrxPv+#V^xPjbi?fs0jBTYX+6#U&#X z|M*0Mm-|@PeV%9n(foY`-rFQ(y_1ow)Z~(8qS{}Q3$`YLJP(IjiL9FY4EzXMKWW6Id8-g7_hRn<-IAI&5IwQ5sR;&_)(Vff`qoDenEC|fBApnnR8uW? z@tEpWf&9sV5+K2ZVTi%X8ZdE!lyA7@7T+(tsyqnY=46s@Q!+p3R(6q^?I zgHV$79xcs#V0wl!Jut&j^da;UqB^g(uJzz$;YxOLpmh)2JAxb$gSj2Fn6o=cI`g~! zdT$cE^^CvrP!)$$h*nOA<$!GD_vR9y@Wal|mkMq0jd2KIEjbAGkU8_`u#Gj+xkX`F zS4nVB+}sUQfi9MchnrnTf1YhDs|#X!LS;&V<8Yv5NYb&&7^jyB<-O%ADyi`~$9wDx zICUO+zVlcL=o!OMl$2g^s|<73fjCUCoFO(P*Ww$K%o$v5^fri3_(0mIWiZ=#k@N4{ zRUY9jkHNhTZ@LHSqRfX8xE+`zUVRI*iDPFSDkdcLs$fVZaf8|O4cANrI`0L63|1&? zT>DdGHA;J5qz#P-E!#%z`K+OhbEO5&&xx?SwSvW{EBub(P4!CulI)y~tT%#o&8?*o zm|kir*JVk<9Lm703}1~wfLDyB7rswky*dFc)1IR96W18eP9u{0nro8Q-;CC7*<%8` zrJlraRuSBDT(*jY8A6UXx9{+HdnW@N2uslUc&IPC+~i;k1%4cu*%{`q3>vMwpM;M^ zdJHZuGKWIMut<+1x9^v8;*0+hPYcyyb~a|yic8k>o`ir>CtaH&E1D@Qp^LU#6Per9 zMs~&IkCv5!nMoXCQia>D~`N0{N)<-b<#(c7@&ng#eK= z7_eIgBIm|W?Hz|?YP|NAFsT0M_CRi6g5HX>-mXU5b??&)yi!s3S<9ZN>=aOUwPL&9_CW;2l51JhuZW)BK|F%`Xr)PzxU?jxy>MpI@&V6nN)ABWS#9`cs*{IE#@l z>7Us}f!tGi;P*|DfNQ0NpwI1dYmw`h1Ce%w%Bcmsmfss+aRw*ocvBXu{GbKYaatf%d1OiStU+Kv$+vwq^NXp6&Zfp#-~c2sGJN-nwIE8 zpE5RItv^dN*LIt|=GWK#n>c`pRijjU8x@g+RPidNj`4JC6#{zd@QUC_ALI;Nban!Y z_{7c5rS&jBHutbjAX5ggo?-yV*+{%~#y@YIg}u%8In7bDQj55KLKxZkY_c0OLrkwx=FRpshT9tF&fm#;!OwP@Q z-m|{19hO>&5Mw@XT!lkh318~_7PQI+Le02YG=7T2|8~)?)2V~BD5zqufyWbN#n(4@ z&fm3&ZIq*|4j3OySQS_`#*~+@qg z7*p@vjlK0HtDX{LDpj2lr!qsK&R_zmzN-lGXEmd^LOBURyy3Kp6pR54Y)V|yt@SFu zBRcSe15IDbm|NS4BG?dIQT0(>{nhn92W)3YWnX0%3oSzcxLN8VC*lD^I;RcZCD2p54xc=Vk z*}Bxjw#ucPU5ap%Gw}@Z#e3$2q(ns6tR4zaLM%8^(P|r|14E+17cCz%ir&=4G=w8d z8X&rE35(+#Bs!2T*)lH7+dA#m4$>sy4>TLe*+}y)8H9hfXdN8+IRhyEb86Wwy3|8U z2Zsxc$Keg|nse6EO@BZX^HA&*xr*Zg*;v;few3+C-UZFEY_0*IQxQ zJWrpK-Qs4Rr9LelGeLMn@%4;P>;6u^GHdQ(p2y7cIPoE17;&y6uvlp9CdUn*KgGOl z`abIKwvf-jkjn@;Q4^nNL?2`1gI(y%w?$xp8O*#ob`GHtS^uioSAgV&!w)5Nzm4s+2T!vbPb!&Q|dD1K1T? zpWp`5kfwNMv-^~35iIp5g_-L!Z|Zwu7~KRqDTd6?JyY3Nk%JuYb@7gqrjo?XEpm6g zLGBuO;Eg_vLmXMuHrWt}(Oq&RC!t_!$$Di?8aU!uV(yP?xOWZRL$uIFvCg$4TRNa( z-8`c5tz3vG{b%0#vkh0lzElvj`?t^*+%R9hZ6vf10gD@j?tYp=%naxmF7Asm3sl;Bb|T5$7M=I7C6Ey=E@GDNFMCEMbahGe6V&pD!dwwnZ$d0?G-k#iag8> znrqxP?8lB^N=W0Es}&VC@_v3wJKdlg1OsML#?U(qnYq;g2gn+Nb+t`vh1$xciGXSy zQX_-i&ni^Ic8XdM;HNq%3}-wk923_9a0Y>~$jz5P-M5`8;ah~AK(DJi=L)x4K~sz= zn9jcF749)+gj)>O*zPWtdvg095)JEhjMXqDw#Dpc+i6udkXGFWh-wS5fzwl=Cv&p3 zlGfqT78A5_ZbGCkVX-k_YA`R=F;ZstKfMFPnc^QThDFC)0QgzSb^fA*Q2aJ}R_fs_ zlJGG|GF3AhZ_QVGd_$}o0ec>w1gB;p_2fvA^BVig>s5k2O+0f(RLyh`*>1-#hIvm( zXez@Jqy-E(_{?{H7Ch`Yq^U?5r2LTkh;vPMyG3+7yCw`=}7Ch@B5K-ws}?*n0qW~C>Fg$ z3S%{PHq>ebEwqz<$wdWfaAIH^0W7ni?lm!+&*IGm$u z(OFtF7o?W%sBfWWsJ?vx@pcpU_3|pgA0v3V3iokY;hZ9{H6oHFC+^qmFM@mdD;>9g z*d;n?f-=v>q%>?nD?5!p#Z?i@O5La~YNNCS{m=S1Z6ck}khi=t(?xt?so&jGh?;-j zaJ2JkduZ%!%>pcX+O3KvD2DY=6iFcf zU<*LyxN(kLb1y=Co2ULs9jb>1L|?NO)OYJ82jjs?n$&r#x+R5t^k*3FF2}k!x-geHhwMACaE6CdCJj>&c^|pp1Fh^V4$K$TO)WZqN8D_d4Vjl zFRq-6ty~-90J!i%hBwPd*=i*I?3M&wB{8KPKglz?g>}{m(ONtm2{g>NyGz{norT_8 zT@|qDvj?MbYnKe-cowbKo|xE>NGa3EIpz?B`e`w)g9P=t*dj}Gx0~{=Zt{80UcOCk z@9aR&YqO%~<&yvXgm09ij>(rTkKnA6Ynn{8Q}O<}V!2gQ41 z-xKqUYlheU;VF_YvMZgA$F3;X+sM_da`Net-9Aq>Gn9^>R6}m|H>fN?hSqjaR^3I3 z+tCfw@vv=iedE<^5Z8&ak0Ayd(YI^6dt1fbctXH7I0cI79E?B>*9|zSOql32tW3X= z)%V|rHm@kQi8Cz8bSuw>cuhot`quR3l9V0H-!K9;P9a2A2K-BiTewkbduF8CtqvzT zqg?eaEEA$&d-_Uz+%F63=O@B+4(gv$w@0@(GDKAcCBrGOv5hF&z-Q&R+@Pr#h`ak6 zyXe_^B#bO`i;Xfz?2@nFjZ{`@}e$&$54H&vAo=}V(9iH9$eKs z51`2vfK~(#%;WH|69E&w{8~=s{-W0z_d>HRm-puMbdncBZ@y|m=a(xE^ADBh37sBM zCjA~ptyt(KX6uYHLW|(3WgDAkajR=6mVH4n&Sn?>6v9+GiYRglUzpS$}W> ze`$!HoYt`aV_XM}QT&@VV~`HMxeAWQcN&+*R#pF0$9PkgVhUQ#Wu*+)NUd+YUY(xI zW@3x@>je*w`uTZ{EchRp=U7UaJDk|&YNIZpbHy|OjUHkgESlXmn^o7IA z;PNu2)s-acj0u4{--;^CSY3HW@}YIk8yXtO@t#~#&2^)xr-RBh>mHq4s{Lh4lNrCj zX$n)<`nBRLFw^28~Bi*nG~@$L5>T+;xT{n9Z{7L10PK8)6)z6XR$e@!OO7U>U- zYJ@|yvXQY`58N=M-u@80tTo>7dSBZbD#81x{H>)QJ#n6_J8><~66}M~ynT+!h(7fU z@+-QdRY4}pv9m7;ksmCE^#Wf_s9N=Z2d@LH`!KA^t4*3NgOF^!3?ZJs7pc_f`I~gi z2f4_$B>#(qXTM@l&3wu!k>_X6eL$lU$p0c1QKyBd9Xp%>x5cZweZv9st{F?Z*wB|q z(P;wd_4use30AUx+j$6$fCutr=VEUadT1q^aZ#QA!nO!r#`4BvWOL$=+gcj2>pg80 zJE#U#+zDhdl~2B`x%rLUx+^VTv&!3Ff>lji>$Q_u+@7UJEv`FlJZn+-O%3TKX#s!5(2kTc!xeUrlDE`tug2Y0i2}f?LmCX5bZK zG~cEsWyJZNpkfX3@#|MGoJec5{sEw%zmykJp+NO#_n#+6ye5qeYvBgtwm>XXatab* zasyU@k46(ieK=fqDAPRL1OM~>z0?zlt}x}CEDY?g;MqW+jd zj!ZA{S5w!H7wf6!`y>yCei0+7S@zV;u|u-Ng?LnMoN~w;rp1=`&*rFA_2H-7hz+Yl zndcAR{DQQCc+Hr?Ix9G*C~W0@)97F@-Pg8f5Yt8>G3;gph0v~%x!t8P6OO^pLedB# zfKN&pg2z6lQQnJr}f zj7r24?J}yY9P8J%UVbTRabBrJ1dwW`@+d-^Aj6s;5HBeQM@kezYNX0NE5a`L;a6`5 zIt#UwWk5_Eg(Vg-b8}|syX^W^Qy3x?62c1dF&cLU4(cgZ!;rfSgBkQ>9^AQ>IXCqx44X*HC z**@pGRHJcxIl_zCc1=)9Aw!ruEsalirK;?^=hLGsO6zr(F^|#gi3&897&lxGV&Vjl z5$@PR1XUn9WWpDA{n}fP*Tta%r<^>unra*DMnWryD)mB)Dz1tFyQI9+#Aa`@Q#0J? zF!}Lfc+fGN$+%%PRXBm}6TGIC9! z;a11&@e@q9>#s7N6*5ivb4y%7qTQP{}8nLbscb&=7bcC zjco2XsJ)Q7{~P5s>#|>51NJt;ah?0ivcqNCUUi?;YDkgKkb1S!rmKV|;#eqVi`gY5 zhG%Gk6TF=h{|2(mQE0b~ezwc;!k@eo3AHdlSt(d?aAUiavTcnQdahE0J%y6>@*CFC z2fs;h7fXF=PA8@Kh%>tBOi@nVQFkLphz5+6f=!R8DvX`oKgXbS9+43iShO;--g-T) zfw+G6eM=n z3d3FX+H>N&yWD#0(ln>V^@+q}lm}sUTc%}vF)ucFvzifiGh%EjdggsB!P1+(E(j}) z6un*wytvc;x0*J{C)cKLD$D3J5td%6XYx^ zz-W);`8mH04S{=gL0h(w>i zbBrCIw8-o@?q@cl8$W4&=fN)-ySwHQ=HKr;H2qWx3nCmgm)&WFECQJLldo8b+TJX7 zU?*Xbo&TlaIz(NBMod2@aaFvM1v-~)sQSG*?yj*bGX znnA?jt?gZQh zcok>(wz2V+B^%Sr{vBEG*ylrCWe=D=N zcwB_pg22PL(XAU4WVe^+rrIC6w@2&{%T7x7m@UTpOaeD}Xq~Sc?3>e7#=>z@7rj@C zyv+vRW{)rb=C!rhZn?qU_Aqa8`S~AzA(xV*CFMJpcc?VnL&$ zFIx%6&RDM4TEHL+wiPG_`W%fI2LDFk0pRcZYxK`%%k=bgYziZ?Nk-Lw-0HvTZ2=a_ zuIYccN+Tbw$vgbJfBUO^-#`%1a?teEm#B?2UZ6fP@v|`q2MdKS8&kve+}~~<@+b(F z>;?uy$GsH)9@BjvDwNoW+-a z=pKa+_YLl!C3TJ07!4oJHQ7fcY;GSQ>xExcG61PLm1FuH(Z&)L>A6JtQ&~>{cfLyC zjesJ-faiI>PU_Xp;3@*(-zPjS)2o)SP?`sK*>Gv)oYYFy((C&or%;Pl`HLi8-`{TA zjeUodp~QY)02(vZOdhV0v9R=I|7_boyTlH1YnKgiH&FDX;L4)#85seIYFNoTsY8HG z#F>h?g;RO{E*)^Sj#U^;jP^x;!I|_|{RGKEQZN*2p9)E9T77Zj_^8?sMeX#)dS=h^S1yCTR&*w$c?Krs^2WDiZwl??OaP=SAN$_z67`mZ zIkyRPzvs5_On%qW`1o=pw{FCj-E1W6YM%1}3Y4M1MKnHd8td!p%eJL8H8*1-{9rU) zxthnh6PQULR{;3p%&R`C>0JJc@U7r^t$nX{`T+}TY z<9L%VS{l#P(SQF_?Q=@yloqi$ZM5I+T4MWmJiU;~T4kO?1`vV_oMJO-fm)tm`7uHKd8}O`O*P#N7$rAnZfbR))+WAz_>Z=$ zXPQfIZ?hPMbMjjX2a$$LF_2K#;NBVRVHfGH_y{p(pu50 zV|D|RZuDpZpu5%aIza^IrT(ro_v5Fwe(z7ZxcXM3%4#=><#(>kdV?)D^(4Tp`b@!= z#%9b&LO&99;fulMzeu{wo;PiStH@VEq@x^fH(OEwxmE$3*P^h~ngBm8zj?mHhT)2p zWw?IJ9X{D^b7VStF?P&RzSl~q^Iqh+FbZd`jDQ#uNVjV)aQct2l>&%OvRO%WGWc>) zm(|Tgk*x5a~ zfEXpC+Q8S~8>!SLz&3oaoBi|5#;Ms^>aOXN#sA57w!f;$C`m)8bc_V010+eL{-%PB z>2!x#7GxQoq%1Ijp{xVSP08!vyq=1$*PH_tz z^Gni*)qp*7@E3dLPTCIg7klPFzzSG*W#WrHGucpa=4sDjVbt#Io{I$i?m7fLwCD_g zZHK3qd~2gswj=htf7&x6@V8wGsQ=kD0srNiNb8FU?6J(=Mf%@bU#RcfW@_cjB?{rO zK|EV}*+U>C?E%fw^z=MK&k21H6U^9;w>MjUs_2_lsfDGU(MmVr0~JTpY&bwe4yNFq zJ8vLPs@<%p=3?Ph35_9j>bPw^(`Mfj3m&D$Ba!@orqm;5F7BsF^$%G~?|an^1x_GD zNkR3x+a?S0pDYcBZ+#hiKJ?9Q0etqUHz5IKfSvAyc?a=cXJ5^|p7ZyyvGYHUjYBxG zc4MN&8`tD)E>5fF=_mRgojL`XBbM#-j^GMz0Ou4t92m2T5kq3@mMxg!fEMTDo|!-- z3+Vb=c^?MY=)*Nl{d7MuU-r;=$A^#y%qH7!5uI%KK={Ojv2A=UAYH*{C4*!$0lm(a zVh2*|j^%eQ%=%c}viPO&KV*&mD(+ORd8$Sac`8IYAB@bkS)D2Y{AAdEj_-Tl21!mT zQuj9-J(Vs%28m71sRH)N)L5LxxX-qnlsIy{Ho=?+2X+;qp~2k3K`b#rZ9 z_U7}SDHk3>s%P@=Z)pOY_|M_A{-623#MY^E}KS?_;;1sza=2X=Ml#qbSDwsObfT=^HJ6-Atm1*VZ_snSH&xka1ZrLKT_0zOiuwV4oKX5t8+#t{x~ z>IDc78&4gBBh|Bh`>|AO;@mtqJfP|`Q|kBM4Q|{X;nFyQQx=9UPgP^srVbgG9XED~ zi0f_raY!3|kb&Qqw`;$0iI7ADkh>etd){L5@d-CKJU`%7E`1u|MsHfth*aR3&tM{7 z@MHI~cdoe~AM%q(qXHj;XGz43$=lfbz%ye3?r=ZAjQ;KuZe*eS;{VtnjFa`xeDh6q z+^=VUw)y|Wfc|F=;x~_Dal#b^0nC-|M2;3tzATH8pp1TnMEX(c@{F zwPw`gAnOf@4^`rQglupNgxfD{@c#nf0=gnfnNM&8wPO%pVWQzrnV(jq*G0N*4^cyy zN-1zo`$}Y9-Ab?JLpa|AeOefizyi`W?_g8&-DemU0BIJ3REQO@L^^jvW@em!X~o~p z2y^_>;O{*Zz=Y2j)B~-8|Jog>d%U7Ov0pvSz~CbU`L7-J%+d9$zrJco6~TZ-o8oJn z)~nhjQ_LD_FkmWq0SG-RQah!DPOAdSx>ERhRRfg{WVb3*35mi4OdO8Ld&z5UdkNT1 z`37<#@YP4@tN8MiXGew01cP0X!fu6V@sROOtf166JYm&&QLh+kDnI(y=Dzubw0QQc zorr^6=tQqnYodX&^+jD$Nw@xatV*}PpMR9|J4P@s)nY;5mMpv`@cDj>A$kiN#sg8? z68VlO%#{7Z+rs@qX++%0?Une%X~m|L?$s9}sM)98^BJ;v_VAI#-ZzUfEw=O75%~`5 zk>%Wh79Nl>eC~E)Jb6cUu}Qd3etx8%2sQL+Ykpa^eJS=$JZ1|yHMO#&VgF*mJ1I;= z2%OvS@Fo2&*+n|`prmQv@%F%c0EGKV5q#mNolv4Q*8kztH+N~hALEl{QeX68zx#yO z%K_!M78~tnC-DC`xi*ujg5^a&4ky|>&X=T73Kdu2lTuaU#(q88G^J50(3QQDqU(*^ zTO8w(w?SVL>BztFjQs_#al+7z)g5^_vNLfZ0+=mj(x~>Yv>KA}zBbppEsolfCtUvV z@w;~=_N#%27`}p_p#FKuLFA=76XmCxQEUG=J;(CGXQnNn`Zf;3{b{;p=a3JIThHZf zP^SK4SOy!j0nm_JKhls}pxWjq_W$D`UN$a`VX}SjK52Q@7AvOQq+X<4QR{`7e_Y=8 z*@9H}=8YA?9aN2E5C9x<&ja0Ob-I#B=c3k+IOP8jiua-&fSuHdf;sb%B5_iV8!9xr zdI^JMJL)Vy)>F5oedHnk$Fef?(Or#y`qcPvKfs!tUE0l&6URYBWRZ;hN67KNzqkXX z&W}FkT|9RyXbk`&=jSBQY_v-+!bBWR(eVNVOY5cEaFOenSf}iW42hu`B7H59DIego z{j(gV%_Jo%HwnCiPbzLU;L zHH$V8z`xVxrbAAp7@~2Q)z@vEBSrF64__i*5s}3*?uDP1tbBX}iTw0yByyhSlCTuOoTg7e53c1PJp(*ch zN&}7ab{wPvQ#pck_D9MUx4QIi@B%+h@;ur03@09dZqI5Uyx)badd5#HZ^xkUKW;v8d2hwC}8%cRMKy+N#k;P z>{Vi)-#Wn_f(5sbhG9)M>bJ!Ijw~)>iPSju`_jouW8PsP>sq>cvgg+_tzr=> zxpp@ZS@LjmCHBi0oTWjDatZA?y0KW>>Dq9otVY$+Q+ zkm(0*hkjPBG%5#>ak3)->XMu6L>A{-Rz&u-8;*uih>Bgx-N8x!5O5_uBZZ5o!o#0n*INJZTY9sBBK3 z-_2YPp?0HT;y{E-K*|>!Z{&v%M+8SRxaLe6x`*(LKPluwnmZ?FV><8_Cg+fZz&)=* zHJ!f(k3MCIjXz{A#9-aaq(<3~T zQ6~&>omPn4ks{EHN>>OkD}pC_ADzo zno*FJSE-v|t)Af3r}ZH3r9T*ibFm*&Ji^@v(3DR?OE3JTYAzcB!Gq=Qx}u(6=(x5$ z0bH!XEpuBJ=eo9$;+V&Z7_ND3sL&I|TM|CpQ&Fs9j$yRU&EAs`qO|2;1x>IJlQQlL zh2Z`8(dt!tr?rbcq{$-bi``^>KM@lQ9{CQ`EIOw}nx_=HpD?JhHR4nmtjx3ux>#-O`lRWnAns_slw2M&M8)fXDG`C;As zhkbx_=)u^7-+m3?y%5SNUryToZYRHPjtE7 zsa_Ahh^mxEnAlu|?dVAH45!gA!43-=Xus0~GYToSETV#JV-qZFpi6{ZVD6acX z(*6cc*5N8bl5{FUMG@eOfXbX2E0dS66Q>dU1+nPUSSrZA zWicQJAoAh?lKdTv$~cE2G_FnKmrN@lVxxu7$ndWT&3Q-f>q@XG>sBGJ>vk^ci8hwO z_52Nva^xR(CO%K*r7}1&^hmw*<~e>JWEyxHKBT5t3A)xG+yON;Vh3uDmDMdzzqYf= zm<&PiF9e%TG0lU<#=&3l$_rn&K^oG1Zi-Xa%J^{r)412n+Ci@E7^tnrQ^qU+z$i;O zTFHGjMt~mK>RCo%B{us3@Vf_bS_7smU2$-f|6aHy(SB4z?F=-4imF|f8j8O_xIU^# z4Fs(3zm)jB!q+RpDPxh+@HOOxixv<#$udu>mD+?S0JkpM$iG%FALhD#;1fStbn=u# zNfQrHcRp;K*PZVh0Uf^?x&k1CfgL?wYeAJEt{8D!7D$u4yGhhCE(uaEb4sz$y-Xs{9gTrk7WgXm@Az-yq+kt_ADr5N$aw@hdDq!3#o_D-o}8-I2#)+Qn0 zLW{~d0GGU+PAO7#HtsY=+hfkRZCodNPUucf)Y@IRB__fpixvJ&lz_ZvRig;y(GFiz zP2eu*Ug;8e?Og3rj)pA-{59oWdkF9^ym^jWAefcdf745-KQ5}@Rj7WVE25ZxvZo2D&OmO?Lm^d+ig7BaM;!XwT50g7X+xZv6e%5H7<~GaNhtzz z6pc-e+fXit6HKi{htR@-qfikQ8SC+tt;ka>^|LPd8x=Xrxt8UNjSesv*p`)2S&PC? z=qA|G)V($^xgTw+6>`3ch7vw_Pv3@ar!dd-fBBQ{eq=+u3 z{g3uZHmcgs({=q(7U#_8h3H321o_1ot+U6HCmBkBie$pcI`wFAVaQ}a!k@a5=l;XY zk^cN}2ZCM@!x>A|iOP;-IVFETt8_d6UP3OjKtNSgh)v`)UE*fs98Nf+p5WA{YijtH z5%Ap?MECEn);hCJmR)o5b}AJP6ADya6{23cYTTjOX?I<;`0Jqf=?feF$I7MvYFERM z-N+Bp1U5`1ZcywXaxegyQPgTXWBTQM`{Hl)hqN1mFv}XSQq`;ZddLTHa<&E$|1^}} z8@W^r2|F6I5LZ0V%sWZA=+JHsmuXpxD~5E%7-_ZTddhty9gEcF-`q*TG$!=udU)uu zkucun%!GQ>+Vp>E?jN2?W(s)T3?C9q7z-&L+qsGq0o^cqaRMjs+VEDy zH{3be@!iD#(w(z8B46TUbc8K(9K%IRq8_05VD%xPHcV~9k4#XC^b^6e|I_hJ+Iat& z;FpKO%QgfhxGil}W-{k?j;@()i*nCb#@?5AdsQy-+!P|48k2m+=2oY{ggqOAevaw6 zGy9}bh=W{=uBvT|QcvbT&Wy>%vVN3`h2RDZCv(I1UwAPED^tZ-?^KY@K4fH{ygH2B zTNHC45As)t@Ne#~RVBSch1#4MQl*B+M*=~F^bJ08e-#;QafDB6i&Sfbuv9ua_cL9b?YBj?3D=sNVdjx!JWd}G>mY6u2MzJ>;G^muW8@Tj}h=qF` z69IZ-%l~COhIRd&aDfl;fL>kYVOTi}i{$y3Yz>WQOosp@)EB6YOEY&dWfgWQII%O9 z{QgnyempV#j}mkC=s!;J@2{SIK0Ci?S3tj{1^(0C|MAEBUzxC9w)oj=-G6Q9-|9&d z!U2W#U7wZIjvb-D1F23kkAT>1$X{X2(a)8>8)44>f6;kGuk?TR-NHvc@xqJF5$X~e zB8ds*mVG+#u9OjM-JpKB%i#l}82Bg^fQzrOmibCm-yHecU*$wc@8 ziIzDlkY}ov1J)m}C!(b^*9JLFk$3E1$k)fe9-Mm7x8Nx1*Hu z)OK<@3YZhr-PQ4k|qG=#mb>6hMohM(=UAMwi75(qGvCB8d{4C zR-YZ+3qX1egj4w081~N&+keVzb&(cQWhozy{h*l4y^pYyY%w|U7D&>vLorr3yJPSc zH-~E};FX$r*7E4ke9rG6$QH^vc<5fMaIM5opU1jd;pd9AeQ2Z)bCK2nQYzhnLZn1r&oK=sW3_GwNc&*k zdj)c*F(fR=(>bZkXp(r9sI^Q>%v6M^9Scz660MRvU@ec5nHfoKz8yuK16nd8M-6&p znt*opTQ+h;!l1m1x`T2}{aW~sMO)nC0Iv5;o++Wn&3x4^`n5uwKD?rvei#^UE0Z7Jw!Wr}SJ2X;jSgyx- zPQKFsmfE98_I56Vrr(TJJ6VDO@-t@*{Bs$aphO+t#Fau5sIw6Oa;djL_3~Ay3-`G` z6^9d6C^9-a!m+)jiiuIAl`5XwS~AfTG}VkT;G2xBgL%koqs*VuJCV#=sM)%A6>QP)Y= zfQ-#tG#0KSw{F6y_!`!@H8oKtK}dMWdubNou+B$BX<+yz3;5X+O~o3Ph-ic$7$`(? zme#=RG^gjkj9jWI_fz4af85wAGk;4hzEV!njss96`ITPMh|yiSRSQtAg#86S)hE)B-`X*9A=7Y~k>(^0jJ5BPeHjXTZ5;a&`^ zJvlJBI~l(Ch|2Hs9vsb(wurtu;Dqr4f^XbxfD0WtyoT;!w^ox(=<{BBbU3m@)TD(9 z)Kx|YRFzQqqo9N+(ol9zUlLK%uqThXpa!Hn+gXr_Ds3Z)0IGabuh*Xd7Zm_JBZoZ2 zdsTq-%U-?Z$Dt2VHY+C+iBDF+k-*sQ_v6n${?Fw%*Bbx5EcUT{!sti%KrF2E{J4b-rwCEi! zE)E9G3oarIX`45IGPO4&=bjWBHa;SP_yQ2?RZ{}jnMKiDB+w{O6t}n&LpR=1wrr%i zNU?1x|5LQqLk(008QaRkg>jxS@-3xd-s>yH(DX(*jIfFr z--aWlMt&%^mc}gJex?-3k!l_wVD)*-R==mP(pn&e;osF7K;sUOnVHP>qYtO~DG{Ix z0JK08au^p8_a8Er8U+d(S5fcv#w}swomv1DY*J8pCljIH=RSf_hON9%UmwZ?36{jet^YTv)PKw3BJGwrU$F2%AfGyReZ!cHQ!;z3S#trv-AuCYiHb0)cXheWVEu zphsR6SX|64cP1Ru5gyhQp+?JWL%3o7^+^4n zb71?^fh|#A%4?ikJio*^46Z~{PS?JrhyDJ|v;AfVY4}K1*eQ7{xwwf{+3-KV9!(rtyX}dtJX|i$`cq7B zpT&gs9iRXepf}==0(DHiKa0{{J|;Z=care`Ycp^epF&iKSibiqv+q^C2@u)d5G=X> zUZ4M-f&bANU^J*U?UpoJ;x5$faR&lWS%tal;ppbuBSRM$!wb{?NGys+-zYp*GbRe)H+QtIN2CMxBxO$)B;089^?VO&oD6Lj$Tw$l4KoQV;VDPmkFXq9%5^U8l{&92 zn0)`ai;R=oHZqS}jwB@j?TQ1aFNeHbk$Md$BLH-7mRgfWwv?F0HLzxc$p4I9nF&ao z-O^SH$Jy3~Ukji%IW+klcJA_JB7wuQKS(&_=j&;7Mrt6W1X%{_zY+n@+yWH3CjEu> zsx7bZdT(F=Ulc(^7qSlRQl5>m00Bha$=3C(^2ZC2L6dzaU5_=u$mlXvw+zy2eSpw7 zm%fdxec&Xoxnkv1z*K#8IM~@B^1v*t;2}MkD3u~AIGkT$aKTJCF4;xAO`h3=P~k(+ zAx$ohQ}~78OC7$&lv_NPVxVBCDqO?aFzE2C(G~tSAdmdyUq$*GIN#6((gYD`t@FZ# z`xbTo)5tbmn+_kO7(J{7kJL*dRPZb9S6XW_8Y!qOH6s1j*8q>M`;_Y4RG>Q~5QG}3 zB>S*M*yA_OhS(eifB5)-dtD&Vv_yTh>;d1s2qHy?GMiv4{&-)h@sN_A`y+soY7J-T z10Ysr=8O9Fn;X+WtU+|=^GH?Qdsarz%iiz8S@PykNEE(IUMjzgtxr3>{}idzaiA=kg7Ij1yU&+E_Rl1nP&~Q~ zoSGNQg?0N~1DZ~n9a`LZtOtxRcckj)Q7ntQp#npFSncettE}k}DmNbJWRkesZtAP% z8TycM`DKZOL)Ow&JLe=Y4ozALzSqEpTL1&>W)`EySJc@vsu%~fQain)F_34XyV#&_ zl5a*CtsXe5~G|-lp0L5g)g3Y*M4u|(jtFw?fm49WHq38Q*qmh&o#Un40z|_ zB{a5h!mwedB<8VSc6_>tzQycTMUwae(~Nfl>fnq+O0sSdBJrT{Fjs3@75gHpIPkq+ zC;$wN5N|2t^QTV_I)&e@tTlGe()+<26Q-e>62Nn;MR_*)rDc+1CD2p(kB-{`Z?`## zXbRwK)=wNeX%S6NRF8@qn0I5*$NbpMr*#|YZ#ng-fU9ZXNkzCxAUI>RM<#!qYx41( z&p@Tys)BlT2wFxhVzdyts7jhK>upn>^4Y4{fMPbhK(4Za5W4L^X?xxb(2uKhzw6P4 zy!SE2fLK+rs1tus9U6m=%Atc`gIGK(=j73>iJ57TdrO7&K=T!@sjtPa&dGP;PXWpH ztwa6O)l1a%zbxU$T+>aQyYfNxHtP%K>eyJk-85%CwtoVM_%&yX-9{7&;O?U<{kuAA&LKrtDm-lHtr&Bn%|+nW7O-0)B0OM@9PA4H{Wg=@qtO+;3)J3% z-)&BC44*HWD4_O-qv{vJ0YbaIE>fkT&J~ zs&&Cs&s^8gDtAam#|=YSxIX$JqzOIhn+@>9M*H-L?>hQhd93rgVDAiHphf6}JHC20 zA(6rvy*UF9u_m5SRlg<4^pZNm2#7qIq1?%^o$e4$v!;}k>9i@WJFJDy(nBwJp3t7a zQU-poHn%o>zRJ0*90flpr;LXY;a4Urt4$gu0|)#GZ0El0&fkjIFZf>6m&Fz}5M!W` z@HVmn;m_(tfS=WwN%wq8A8wu`ZEifJVBToC8ggioA)Q(ly?!a%dQq*(>0!!HSb%U5 zfU~|i|18%KW8kR!omVgUCxH=splQw#)W%+IPBtRnzioWFvyEHKvw7SZM54tn>OHLO za;6$+F0H|zyBl?_04Tsmeh+)Q{KHp0F7ldMw~$)0=mjQy<%u4r+9pT3(JWWteJ-oI zAJ$fS*KR~L)9_9B`qe$L2WnCkc^vrne(blxa{%!*1d=XQR$2Ru1(ig1<+d)VX2~~d zo8RfWTZww&E0<_|L?>^cwY9!*f)_O3x)^R0DJtIPFW%I-&b3-i(7T@b(h}I>H<YSNEyDXUM4H(BPgnJiR5vi%zjZ$$EzlW5KSWHM>FO?M**eKWYKwH7)uQ79CVPtQ z+*>$xev6Tu^6N){qc9i15ynQs-GZZYQj2s{6Hd7(H$4C#T1_Z79d8xy!*j1wTD|t< z!``yF<+xg8U6O=_{MJ;8sY)C|webW>SN?W>w^M<^i7VkOTg;?HN%CiDZKnkXM(TL0 zj}p=}p2A{-KQ!7sb{|ELw=Bjpm9pmm)*3z5|Amw7l8p;tnF~fka3J?MmPH{dYhmef zK~~RuY$f%rVdr;?t=Fr9$vpHzZ}JOTs@g8!My?67@qs%n!LNYqKHG|0sWB!0Jnaw+ zPEK`2MD40_YLEx*ii=xngeG^bXP-bMyaRetdQ<)geF_9hU|SW%d=9%9C`d{;ruc*& zX$)e7ll}4Tn}&hdg6})Jrx%LbnND>Mb#T-~s6)dJ85mx!Q>a1?$WpJ^9TRvWtLjbtW^E%$y68Y%yjyv#Ie*{6Pol5DkZ@Mvks3PNfS!?OZwv&+D$*-mrOA!VEO;Gr~ffO1z$JWl?Dk5 zUJg4T;TXBQ>8fP9`u1L^03Z;10S1Ty;n3ak_;2^d|7A$&@~GV%+f3rrMYwhvKd;>N zvfhK-E41?l+7>2hN$xFK(Opt^tVk2Q_{DaMd~Ge;Bkv1(;1Z;XENg#2{=Deu#HD(? zWyRwPuq@Vl&Is;HOw5vvS2$b9bnh!%ZqS&AqVe>aQHQ$?Q03G}W zrf#L60ZXh7Uq+_blar>HONCzItahL?Zj6fedP#@bpkDL6;>neeGgs>@*F$V|Iy$yv zvXym?tGYa7z>eEL4H>e|irh0)tC-n$s)Ob9k}*OMv*V<$lBc4i4CljQZ*Rxd2Q&VRAt zkQUB3)q)0XoL~2+;TevYq=YEvCI<&!`}_*;tTN?Gv_fU;?3{a9(^i7*PfJ+s1NSdv z5r&6kBwc1AuG;Ba+--_1{Drdtm_9SHl4_7nug`ZOdmWBQoe>zY0l4|L79-BI^SvdM zB-@wcFjMum3BTRIJ}tDd{z-M2WeRs-kv7oX}X>kda%QyWWGX={z$M2fc4#aflKYl;=Q4I|AIQ zx=5ew9R(=qco)38*0Ca#8ADZRQ`f@r*2-EwbXl!5hOEEQXp$V1J@i;_JV2c4KcE}f zl%7KV{jH7e{qt-uk%^p$vkzx_XSqH>x!su!c)Mbz$8Rkz9~X5k!3nzX{*{cso)?)I54>;ed%;!ft%6@Ausy z;OZmDQdHANhy7%1!IYX^VzC7a1*0g!WU>&07V=cuLI-bWVN9WWAHh^#f#NpJez&|v z+CSL9J2zSpQdx-%^8LC9Y5ms<@S z4{nQfhOlW@@$2|d+gQ+<^C-c$E&$iar8pv&9G zWU`0WZBYDhdGnz94=!68hSghjER@rG?Dm_xO}7#k*6y<@o>|pqZgnO$&{dKtz z-vBoqtnsTD`7`u+@CtkQ$;E0JMzRkJbjB)zTIEEYec@IxG}>R|(^5J4{^h&UU}ImH z{`s;v569ou>&B?RTul#tI^@rbV^L2Z8m$Zdk=b6Mni!R8zLLip=jFGU`nbXZ(v#H4 z4KmptzAOv;sPs{AO6SBrw@KdE;xUlWo9``|U*rKCav9ist)28Yxr(Z|=QO`raE#ct>DGw_`ZVfkGXN`NikP=-T2Xb03Xx`>6 z3PQt_GSvEO%&JnOU54sWRh8SZzKCpHhvLLvRd+>XrXrOXYTKsHobs+uxSFFpF<*(j&gLpl4LL9NQnk5RL7 zGzoEss)+8PftZ>O1Y(E0g!mY)Gh4Dv8MKHF-12$>(w1ZH;8M)!1fE|6~)N;ht)|{N&yRFV<8JxpLYw zL!sAX6dU=bGTfeX#)HQlM*)>*dK#O%S`L!TXLGOENF9zZ&4_~{bkvp+itX+ht=%EJ zu1%efRDCm=&<2%`8Pna3+ZC}3jH)jhv&^nN)f#G-_iW%>d}5%9^U-;U!WN(xjC@<- zEdKgnd{#pXOGAqzHB>+Wiw+c3W7QZ#z@tq6H4Zb-ye}#WwZ8BxoL`Ti827NM2Kipr_xeiGX zo(`m(W2D5XPkw)|@tWe`z?x(uPh)1_%&W-(Q_A|Vft`W-q=!hCL=i7CcL!vLw5Qr|Ky^^h1Ar3@v}MfQ2WV= zpP>&Kw!&(5tQ6@jq*2p^5`FXa1!gziiP}E#+HY0XBDxObXl|Z9)}_-QT}d>BdNQt5 zopW`VA%(D>gw>gqc5@E&y^1rgIRVNt>K;FpuZLg!&X2M-y8UfsWg+v*`x3vI6-M^- zNJ<0;SsN?}1iHNl%IDq+#CP$@N#=tw@wLBe`tyc0!-mW*LIc}PX{Ql|xzRJ&nRDw{ zS;b+;LhP80x~p8DAkjZ#|;L0?PSRAfO!cF5$buBFwJY}rdy^EPyRMbN>N%P)+=O&rR0&G=243FQJXt&A>7s%uzhWuOp!AV?IFH< zUOvhR&MOS5vgE~wC94^vAU>(4wIo9jgFu7HNTi%`o|QELY?%P0r@ zsITUi&=tKnA)bGRCpcv7J0in-roBVjKFqd|Q=L@E;Z)2`-ltwfvVNk)#10DzDtS{q~UyK1KCVtS%58ZVwZ|}fxokdOT?YXk3UCHtuo?wPALcUDGK~V-@>>W7) z=>F-rq2TIC=kOcD98Xr4AF2Y3Vd6238JKkX)C_x|mRb~#O{$QXy&qpeKH#lmxb8C8}=w%K<4?I`7zh7Fn8=8T6BBca{e(G zKNfIfLKEN_Ys6ly`2$}uc8B-;3Lae@$zs93nvsao_M@341J zpJ~g)Uv1z-STkD-)>c}_Hru8|KTCS-E7!(wa0!C+rgx=G!W-v;lkD1ZmmWINF3CUG z;;MOEqIu$9%@Y$hklbA1*#f=BsdIvqM@4g8bTbOQNUU8-hV0&9Vz9#yiwX)Q&zNnw zoDND5i;3&Jk)z{6DobtJ#oV(!&Z%W6g=|X?se)cq|1&8!xVvZjS;;;}Bio>QWfh3M z)XLFH=iGE%M&9RzJhft+X7SrAEeWeOOr2_6mv&o4$Zw5~Be&qU z$GVB?6Fbk)u#d{^G%Bi{uX&@*{YS7lO<7oP(s>zM2!A9m-=ZdnJ#tdCr*6VeT4VZy zSj`vDoV-*z-bZPyc-)WocVU4UBTuJvu#@{5qrFG$5LC+&p4;=`+M5MWxMuH${kw4j zH}<-@VEjRQ9R1wuO?6(1!Tr#(1ml1-js8hDm;P3~p2g-__q7Bo`7-BT#Q3bvwI<1a zf@C%9U85Yd4Emcht1s5K8y(NjuN)R=*ALd{cGwf`1CE^ zZ2o!LX6$JeZa4w-alc@hjpeiOvL}rgGE03%BD(`ZFh{Vt#a%+g+LBD!d_k z{RG}Pz1Jx!l^UU=u;%;VwGCpq2%(7P#opvq`;YVwx|iH8uRWpRN$6_`&1D~bV#drP zuf^XnrL)qnIHvN){lvyp?Mo>Bmn!CmeM^s@l z!7Ufq1`6)B{VVXffUuw8XwzvkxO|`d*u}aRJ{BWJRl)Z!M}#V;;!~iAC&QiZ;-~jv z(o70zt_$O-1|w_ZAnoYLl5U@#b%M51eDnK} z7KnXegI9wdWtZ=Lu-O&)ohvf(x$gOC$PQ1*vd|x$>Mq?&#rjr1X^MMLvuU#Zwl!?p zD3-qPI^c}OY5nuj=4oN1ba>M6udkLExy+aPuFehT`F1S68@M8R_FwsLPnVw}q=Tqp zooxj}GsQX@>7cGut0Iut(JJR7${OpFx$N)Weh3}6)pPAI4ZwSvk6fZTzXdlUO(?o% zzF{M(V?14F^op}?jb*5>okNWCjriQ~aX;E`JaOZedo%Pq;wGJz)&A7WHCVB$w?pm| z2z$*N9V3Em(H`R-;|)rwsZZhc<0ciy{hj)!s(3@SuRfBijyOQC(I^N4Cl61^z zYUx3p>i3Md=P+CIp_!cTAI0}ywmpLwcQ^%-w4sn@fC%ol+K4R${Slaj2acX#+vPdZ z(eich%Arhli5^6@XKZ_BHCTpJuf=a>CkuY&FB&1Oo#=N;-K?%bl8kE` zNKc(^QKg>?YD&E+d?)+EW^=UmPaarH4XoPaqWxo>x;+DDI9Pn=XSy?xeA4CjZmu@m z#__G>y7|#!p812G#$)bkQACF^=IcCnb&mJ(>)$!1W{xj-9`r=szk=03pM)@r@UU5m z7sCsYZ9ifGuC}<^eCl3Z`u z4)|KALbbeo%Ge9A3VY@$MxhT~hLMGwovhf`X?)yP4bL|T8H}gJ`##XSwoj#Hab}s* z8nobV75dD~CrsW=B;h|(;=?Ekt0T9tZIg?Z7^VwcY5i-5H?sy056|@Q68%ISX^h9Q|ZMd#fPCD_@A`W z!V(_#=%_xhsK^Ml-|K?7pFWucXDmK6``p2=ov@ENB=V^R6<)sD$NcF33?z97ZD z9e(m@F*L)Y-zCgpoO5|+$~;uc-9mmFdiXb5>-VEgT;30)!0x3z4f^_+Q;kONzq?YV zg~`{GzvYXX{?%z}S8C_u6n%u4aHMwa{>Us0k5H~!dl#}_E+q0PwCyJIiik%c$ zy!>HR{b+I}r9QyWIZPj8V;mZ8lMR{F^o2+6W}S$+Hi`5m@oIcqRKUMW7>*x9lorJ# zkRFtdUd+HwXrAojBTU76E1|MHYI8xD#gI{uDv_I>8IS?nt_Uq2|6_R6yjrc{0{+yr z&vpyDN;5}~F z1S!}v`+N?G5cHnmLY15WjHD~}XAg$CUS|B{Bo#a25#-!QP!KzM?$UO%Tjjbp%XJ~& z%qF>lEHB=bht8aRX6A zJDX0BsxV7*Zt~55wg#kJ^$>I|F*JPA6q-6st45KsjYg_oPTJn-N%hPRdvyezD=Ahn zzO2}Ii~6-NfEoX;CMwqmc7d-Q9zQXzbj5~+wjc?^M-sob6#=4rDa~6>%-`8H4F@9xT9mH7q!!9`ON(LZ|%|t$63tTdC%BNA6r{FWk z(FajA3A~isR=Ij*adx+B?8eXi-pzS+YrJw4T{c%^l|nG@jd2;0%H_K;S01tm2tCHh z@S!@i(PZnZ)^PO%q>9Upkd>REnV=W0+{hiMzC`}oN?VRe(2)5h#hBY*iKa0$)|0jG zMa~S{b(|0qq)BxnPDLJt>&^B3Ds&HHs(Fn#4MlFpc>g3^t?|L|_AtEQVPZZcnf5o8 zr`BT~^SZI$-=h08uf%#Mk|L~Y&{HR7_ct0mO&QnCEzzG0)R}^D!#oT7tR45;=c*Y$ z*Nq}R*V+=dYfBt!({uBEkr$5-f7EmOKgc6*$T+7ABw%}pt`+X}+;FzUEJ)Kwk(#lcNRO+o>(ugk`12MF{=rbIm3sOHl6vGHT^ zp$&lX`gqNE%f5aI2UpZD6fj2NN}9Z*v1w`xgB#G^UK#2&xH$WGVeK{1kIHwVj(0y5 z>AZAA3og{-JY~Dp$@f!PP}0rDyCV`oxyi=3yA%nwKLg(vh2>s|2(na}BYVgPy(r;k zyxcQ{xy##);7aOCB_; z9+uNE3tJOQd$r_bC0Q1Pd28Dr#Kt++KT(gdb)q|J%&eq%xGtWEanS289M|o^ncY>6 zVAXGVh-Cv`@on~+JKBg-U75iF;`5O4^20^hU^pa+la8l&bqU`xUOa-kA9L!LpN_;? zUjdH*5sEUkz=ROm;+t;n&m?mzCUFtAxDrS0G;g<++e9z10)q4DtfvT!L6J@BTvX7H zFS_+v+zaXKt3y`oQ}aALJ*}G$uu=eLL>WQk=Yu?;P%g#m>< zDV`bB_)^?!GX&+r7RsgVA?YCFhh!NaeJZ&(RvV;akNELeL)kNf0Ob@7-(rb|@yWp% zAE}t)>L1!kdHDR6PFK)9UocLg$HQS1H-?(HUfVsp=z9I~J~e`%JB3RfX|(B-F72Gx z^J;e|PUd!-6d?2vMgbL1xan-Jz4I1T6SUv(J)e;oUWezHPHMx!mVHNE3Hm9O#jV&A z2jpf(Qfy33QxoBXNvSt^_=9U%JrILh&(wNJgZC%+myeC&oAyd-`7V4Yf!udEnnJ+r zYwAjrfl{jSWnCC~zZj+bq6^(?_^eN)zJRmTPzi^xp(xj9jPI1pNYsX@=AV0$EbC_l zsgs?Vz{PQ$fU`7+KFmNqg0|sWq?Sar8; z;BDtw#+|EWf^oSg;YVf%jlku+ z4@R4^b{Q2x+rkLp>T@aO^p|}*5(_-%-G@`g@&LC5lUE6Wc45IavNQgT=*K*p`p1z4 zaOK>D*iMlQj>&68i;FcdF5pyV#s!Z;S?^kWZtdhKrU6n&BDMJ7dS+^iA|R4xzkRma zBEK0t)}rw|UMCFY0h&`0dv&f*{*lq8Z7aUc67CiX%)qglxpz0jBa-939qZQB+{=!% z$+|R^J6UIvbKwzm?KHOCWj%MwWu`(4qw+}}^$V4pVs=m7o8=4Y-ZWfoyy&SYi{DlC z#G5n@n~xs3t!iJ`zN@pb8d`AO=u8!N`_t03{qtkp2BD^zSeuUu&@C*T*5B`6Fnjcw z>L&FR$*sQ?De`YQ9mZAJxih>>Y#hJyo1j^rOBTZ?AXm~;3qrot)wq>%PYl;wh-dr2=BA>=_y?I; zJ!)#xYrg+_WpY*EqSsx!HD_+>H(jInX+*yd-{IX>@UiM&hnNYu4zT3zdIJD$;N*O) z?_HB+b(b<^potPI>HfZnMNPDeZrw`Cw$~@*I6Dk$r1-3Q24w1)6F@v*vRs%Us zx5AY6Vg+Vb()Qvp4&$dF{;1A56kD$@7v){4?LET3q8sjCF;Yw{_nCTh^>!33I?1)f z+)%@U>)eYCI%2P`A5T%{xMs>@?)aRp5udsK{2br^l%rGWl=299b^Ac+xR(#Z*%+Q~ zNf9JA)x!FX1GsWB=6CbK8rCuUHYJ9`dY)A+=@$Cz|HbB+MW!n#$e^I@J|NY_l<^B_ zK3RAE_QB<;IsKM|XjH-*ab(Q+AKJf&Bs`lL9}*x)WnFm_nddLC@TuGfDC2L%I8Ml`$zDB6xC;QFt$G5)ej*p|V6~$mS8D{w9SR|p!P(QRTGaIiu zwhbhmH6uO|lR1kezzQ;Yr*b1NzExOiwS51Q$E|LgSEXbjFdT2zQ+gPy*~bVHx2q~Q zu_d{uENZ{gReRUAmO@-vOnS{(?-L~3%BVQP*ks;P+ND$M`x;=W`H;Lqf!vUF<@-7{h4{;)!t=$+cL`cUL@MgLX-+CnzB3g%;6%(0Hbmau2xAdBug29@pNL8#1&Pc5uA)e5)&cH=nJM{-vUi{(rUi zo^efO(cZ9*GosRF6al4KN09)c13^O3VboCstPqMoKt!d4ozzrrCCS{02ghjW$kLzEFO#Bx(AZh`;(g=HzMy{YS4@{W znWmUzV5p+Cd&1kVTl>%`D}8 zi&`5Zv81$gUjf}l$KzTlqjEH3{WO_cm6{#s;)~kMX4fCrN?p6xHE#T8{HZ&10tlY1 zXVq{LnVw;v+9gOSSUZh-2@cz(X9rj)3pD(~5;y3HMnBhaKEyRG+T;0wPg}HqdmxG% zhi?XJ2j!^x(LV!yC@Y7zhMyq}=P1|Q__?Rvq+}>CdRXHV`v*fRwJ_S&jrXwHvL&k4 zmwLK>@OfEG%DO!*-I*DA*xCny<%w5^K76p4jW$n*`>&-JK5}$Zx`xmR6rC!|9#l`s z>ae{t#DDp61x-NQzu<%j1Utt@nrL@bJ2(F_jG9WDMPP?3`fFjP){`u*2Iy5BZ(|lk@>Z_^_ZL+$e8=Ic&=E1JiGTJ8+T9q8}jPQi!(&t^< zHQ1#1Dn>(eMRjVxDaum&`I9=TnS%8Uy-F)1l2C(m`#vp}_aqbA!dR;eMQjByG>C5O z!~*ziwx<=lahe-nb$PbG6|;9?sfYBV|)VgVhKn?j~Qk8x(!y;n&6fl?qv}3 zcS70~!}aqAUtZqPe&OX`c=ODcmla8?^IdE@qa9XuDC2zk&j%WNlT=&tyCZy$dI}oK zEa^%&+=AY|JBiXrd~{(zch_yhlbiYom#_NY?E_=nGA1gxx^*$TgAPvS(|!5oi{P3? zq*I%3t$bN+9Ss(^d-gZlwm(s{?@cPTJ;^Yu@|C7-Z^hqJE@)B_^v}?mC9trWgF8?1 zyqO!Y%`?2(TJgt4Ls8V00iDSYW5J)g(`VXVe^9?dQ-wDTt9PmH>m1E!ohE0ziX9BC zuFiC=D~}oQ^;LpvPXk-k{%AD( zjuBY9r6dZ}SAV9G9zXM8rk(k=eQ@akmC`bQDA;1(6#M$FP$VMlE$AWMzlVCF|Q@r5DxP3!hssU;$v;lAhWstGO=*8V+-bJ^Zk%Vli3*;2vql+ zwriA@M#AxjRgz0(Dx33bReDo_n+=t&O$JiHHJ7KIYJx8zYOQbDmZ-BZ!M_aw9)CI zC-h1XZ{HM(PmP@lC_m3R(B1_}9F>e))*)g|w2fNo$DpqeVM8misHmNhf|jaDfZ-f% zK+fe}etEDzVR&G0xGA;qpUv(Dbu=d$ZfUD4+Rrz;GFNx*0^Lj098mA91O`hZAtfnx zV^yJZwPyRR{|Twgt{$z)NB>FH_K^M1cso8i_o8Znl>GL(N!8FH@Cf478ZIC9OWo;u ziPPw=(8I0hseC;%V z+Q7YV_yR+}L}PCTG0ms(J{jiAn;ng%9LgRDO?zh9-O>I!DTe#U_?&M=3HtG3mPO!; zmMtExjrQKr5v!HFdXoqnS)XNqhJZGS#&yNQ!Tak2+(NP#|4y1yuqJD9Yg1|BtM$k3;10;7!8W=Vi z=c_|>tN2^H_i}eIGdq8b`GLfe`tkjI-<9V;hMErI6?=#MobQhZ?s;bsY3kXu^_4`J zsZZWtLN>lnO=ZLuxSH+$r>7*LnD_H;mDkH~W9Kgq9F-{OC6wco-7qZaY)PMl_CaAy^rc(A zL70II+`(9KL$B8p+}liI;*6hih6tU3`<$DIm%_GUbQq~ z)6sQwr}4-HAGlxpH8!V_d8+-pvu-L|sfmTrq)QmP{zPhC&>sPASgeAFz(~)LTK#sS zVK{*6@u&(|3P=t^)z4EWbkyDQvZuQe4iU5qZjD2{d*ybf>ZReysJPw(sU})Irs4IQ z?TX0)>Npo4R&EHRys@33xTazaHK?lbYnnSZ1d8sJrN5}K(6!`jYO)C+oGqlkHk}Sp z=!R0CFsB?Q;zpBs`OA@BKpfFkN24uszry16RhDZ12z}d;G0m?+f1CW5Z6m7R7<*39 z7+au+`3wD8yWVU_;*FGqbBu@#c#*IOGsXBX)PyHDy>qWaSY=tM=SD%@EprvA zImr~q83)@ikD$qVY8|UhI|W<^N=>sLfdSxtR2pukrd@sut~0jgL+CHJVMu zI#5qyn+KN=ZWc}_-PT>`G|R zRa)N*I~AaHuSSoOjsuE;2f74WZsa$b`WSs(QyJximqFxqiM^99IxFl2S#i*^=1-mD zGyA4&`XsY+Vq&e7f`eM6KE3in@EfX{G$wsAQ@zjdfWk~h{ip>}E5w*8SkyBB-8`6f z+_s`kpfEO{6ANd=RONP5#WkC&x;k4>Su7;jjp0+&s+c`i26fPCXTP2(EY97TvNLme z+}HEAsJcd+rIL|2+k5;rnbB*}+BUv4ybxdPaV)5PA1z!}d2{qsC`v-7R2}V&y=bjM zHd0Zkck4>3BdaA4Z(p+`yj-8q+)Zl_VYuG0ZA!IfNCL`EEyjX9IViScNFzf}#g8hT zUEt@0FqXyrZJiE*Y^O$}R|<&Do|BNScWv(%^|#NpRpSyy(XX;vir)(Fb4J&8_J?lK z!w21wNMHLFo2yJlh}<#iGq-%Je}sRJT6{q_dl6$j53E7za|7$pm%dSlf?v&le!kkG z-=ZyIm!~zOb=->chOla$>9^DH>Z;|5Oxmc>xEXa>P?cGLPaHU|;9tx^g;)Ih(QW~1i-@=$UP zQW|Q(2G9If>Bg5TA~MwLN|4@)JVg#;gJ5m=?&1F3JWg&l zn*CN~y>r7mVdPOoCh>YQuX2-PM5rS4*#Vs6SSNh{dXv+t4ctS|(*pfKo|bC#;z<3vaGPOE z!jF-?Re&+R2|Oo8PlRG)LqtZdYTdPc5HP#gC*l~<*>e!~r#)(b$`$g4M)u8x;A_K~!ApK0; zY*jyzZgI!4_l+&Q-;t76Ru;v({(Xr(ajB%i{>Y!PTil2#{ax|(vkpJ}UZGZ+4RS@_ zG0GI=D)q1ZzQIl}OGz|7MCf-O=O=8^t2zArU-ePZ@JP$02Y$f)cO#4vRup@+fXJ|S z8_oO60bAam5N;v{_H{nLHF#t&8&;~6#U@tKjIsTWo{zAtqku<|t+^L)5a5gp$a zPtZwA!j`>NOn~HAsW~*nS`CM-{t>9<=5+IKR88KT4=7=B?;p_s61;(g?QiB>e!S|N ziCH=J+PyyM#?>Fy_xvdD=LLKWPW&^@m1F3Mf3V|~QPXm%%LP4+`&k*dvoux}A0^a86!bi+F+7}|4}1@lH8Fjw-l8X+cNTiy z-}CK>Wf!=`kyaNCiSo8LPPWtcEUZb${B{4nQU5baC#b|87lSWnw4cn0+wje=ckwlD zH|0%siq&Ds4nn}Km+CpLZ&EC2tNw<+4JF2j)t8fbw)+We}WBNI>} zo2X_TC;l(Y!22#Y5H^sSN8Xp;u?a%hccn@nYu)(~*#-uK|j_&GEIrNq41ot%#gY@bm{9ltua`~C zp-~?6@D{+71V~U9(BSIfAtI}FlR7DUy*$+Ev#NG?Kw?QeSZ{*{_VS;0%;}=E_h+FE zr}SYVu@-iA0#YZ!5u2e)`yd$}FP??7Ijiy8OUCf#=?TKw(6+aZA8o`(mQMOL2(Usz zY4r~2!VpmDDfhEOPrvL}MIS&Ty6gnySi($BWvqnj*FH!f-iV|9IRCnK`&DjZHIc{V z;V$QLVjRCbn}+W2h64Wh4Q_^GLcOqkRe)uYj(V;rQ?C;JyyPv#i9qE)I+1oFx6o5E zYLy)#{lHb7cb>GCWWo_{I+#TT=4I(tU>eJhe6xx|z)vhu($fQKGTCJ9jf-a(Zo`cA z`l?wF4HZNacpIoG>`>NYhF)ZVq3fFywajOXtf(82QQio@k=L;>ql*S{Ic=bsPpj~lAgx>%dW8v=V z1hoc;3U%lM(~~igZswt;80G8Ae!z=k$T+z(meuCgsmSv$(8lRiFvpjt_`gZ?NSQJU z+k&Dm?LrCd?DZPAG|?ZGa;?|n=;h4D zR9k`QWeChbw{X^dpl_+fssKKgJG<2w9*+!;Kj=;MfI&q+T#dS>e(%)anJtn zAY;7#h`+z{$6OFLb`X`R;-O>*I}CNNLS`$996Xc-x}R`az)QoCbM7^6!miHdlXI5aKq3wEfqd2*PR;@b5K>gJ>A6blFI%aK81u)4r z_)P@?DSpRqbb{tfxU$&`y!lOlgBfWfBf8xtD8G&_AA*GhToCsVWKkt!!D2}Iz$Eg$ zpZ8TQ{$Fj^5}KV;5%n+70nFn}u+Sfntd6zcYVNe_^E4qKQi!;ZhRnHoF0KlX)OUZE zixS5R)B8w|^24H>`#mkUmVSCVceY!yxM>fm^5kjvn$SybU^N{zb&LtBag1D=jeN;N zgNJ9g8i0&`9qu<8H$D%_R#dPse5&C}e#-a+v~PB|s@C)(xz6BgkcQYdT#lxa2Ci9F z9s68JOWRwtMIEzDN0555uP#R~m8&ITL-u!`(A#47-b_s<~U#SgfeV_zTLukE%;zFvD>)xkFB9>_(y*a<40=;g%Y ztbQtBKbl)4EbYZ?mTQI`-)M#!-P7uZQT`XturHcmo=*4F?gj;IaUzJAof<4ZYrPy9 zQ!>p-B|U1S5$X-o-5zWYM?qtjvc>V%l2LOfs5Uu7<}O%txJ@yn>#1b91O5RBd`SK4 z(7wuKGJfek;m}sT+7a#x&zp|Ce)%jJemhHAtH(r9c0+CD!SI|r7cJn5$LSPLO$cZ< zzJZ3X(8y6yN@{0p{ZTztOebNR9|4jD6p_QjQKrAlrrWy-us#_+bJ^aZFm762lwjEK zB6bblq&O0)?WRgrCml6lP5P6pH`!6~doDPcfuz5P#mRBZvaWE;O&-!Ds&Gm!Upu~) zujK3VN4hU8$T3}gUSrDup`Q-5XzJs4urxfD^ZF=i z`AfqmA8dx4!OWd87j-t$t|D2rbJ=!7n*hV9!{XaPNml!IU#sl75H9T)SuVffMr6%L zk!^Ea+}CzD`l)LWOy>tlYly776bp}*gGudubtEF|B@l|eeS`@wS|8s$SkWBxrpNZ$ zs=mCd@y%Su5<=-#62+9nVjVoHZ(Y67_o%~1yh*!rd-W=F2x6NYpkeKZ~Kz4~)4Nzx1Ij z8%E~W;^AeDMHA80LwTow1eEVTX70NfzXlH&f>6uC_KB+gL_*RBz_3WWom*(Lv==mf zgzMue3|cD7)(2kap>HQHaJ4V)hdB)v!7)n-%tO=-5m~1OSrJClkSNpPqMJ1U?xtLC zVVGKd(+Yk*?!&<52=FC3f8M*sMc4)Cw)ntoX7d3+<1rveqv4Vm&N$0}#tEcrb)6^U zvgpT&H^S0FkaHpCD7=;t;^c0ShlS_&!-Ewb@ZUzjub2_fC+@z=UkC;)2u!5SaE#y* za-uuv;-bCQDy=R+)2W^t!dksJFtKS7BIBplQT9sb5hPS}4qy;J`SsiF6m>YN1TdGi z0%k1+rzfKhL$hjyB+J5NUwM>?3(Q%c<%oDV9q;orznAMbI9J7H2#4JVoP=G^k0ZuO z{;Elyk8>$;%#vhA^e%05!QfUvXS#llCuUarq{L>7sI4dzTv1dp_qL@ACe zxsgfnH%fvsh@I7`*x1ynn5K0BKv|dZHxhuw0JN;4K}hZeyF&yhz~0cawvW}_0ZMrz zDhCjxYFxi-z#4o=36RggN1sV7El48xcx5fq5)3z4oso^8n09GgU5^z%q8u=Szj_OD zJ9hPTQ~woBTr+d9H@%sAkjohlOQJRbhUdLKlYls$-u;cr)m#Nla@q;(I3o#$@;&?i zZb5hYsivM=TlxF{(>Y~!6Po;#9-=wgFF%e2{Xe@o6?Az#M)JTJ76Kz>sCsu{Rb_+`g-N>(BZFM+JB$hJTuS} z+t!3kIhU-S1by{WR$N@Ui~LUZ7_g$hq|?Y}Yq=S4s5xjU%>TND-B$;(M#?lMB~uA) z|NFVq{R9cbJ!RMFk*9X205qxSXh(W%>%y(t$R z$@E62$N#-x3t+MQ4|pS=g#Q<3VSi`xCmHi22g?vwGsZD1h}kMF`;OJ#y8D6E`cS^J z_YD6&8_gS><8Y*bQjL`X;Je>yZyR6Tvjc=31l)&}tB5$#>bYb^S*qB0|GvtZw^Op7fD76H>=Ft1zNa8Sw`GF(Re9bmgnD!7 zUP}_7dw@?L4w-G^!Ab3xj4sL^t~qrduX!2pUEfrYSiepK{cwL_+0KF`gJ%Cq`B!o8 zgWc{IjxI{C0tnIWFGIyUt*AOcr=0*i?&+`ijQlQIX#e9ZR2Ogg@TLh@t$nXk{WqW^$9B%l29=m6R)|9w@!`L8qjbqHR08~^`2 z1VFB&^BIKmf2*dyb(HVgwu*IMB;v}~I&uFqbN>b8=M)KNE!`!SP;8VkFwutMPi>Tc zU!AXg8nS6C#cfnLRnsM%_g?aplDKmToQ?!c6tnV?en|9aIC;WT@!vJ|`kE`yQ4_L< z3Shl^#2FN+zL;kt3zN!m4F3$Gukt3~lU7b~7}w17>t0_9NpISeJ)B0te*p*yv8WuDx0iqOxYwD5+`NawPJk5I?B zd&{i78&3gdm0X3%bq&jTS*{g53E*1ic5&e#wd?Ygpkg#N?;x4u|LkLb^8X^dJ zdg0bP`9K)fgV!;s{hrB^U6!wJ{L-4 zJo_P3T>M(|XS)@cKf20;$QpQ7vo5Ls-E2lUZF8_=1ChJmy*H*&+%MUUVF9 zC-a?&nbF^Q)awqbIy(?Hrd$L{ll#0hOdRFi%Gil*IchhmsY!;dR`r{~HtWSV+lV!Q zhPUw3Q9s8_7#PQ|!9-gRH)LS)$0c*wUF;?;db_87?85!vX$WTXa^!@nwX%&Y2_A3b zrUgb)9(toCXQ1M}82vY>*LT!a zN|Kq#T(2I6igW2oo~bw)Gcy*(MUitRlsJ&si32`SoO+t@T;C%8FjW4A?i~MjS)5Z& zA}t29xIurG!$ZQ`X{aPo{Tm8^@^y+UTSews%c0r@fy{Rzp`9s_m2hAms0m@BO{bSo z?19>TiX|o0P`ku2j3KtUm#qrEU_0_mX)?$HUG@2%g~}#PM)W!$^PQ?dP|l@GPb_sQ z(yj~2;@Gxru|dqd?XVDC%MD3;ev=QW_`7c=5)4vdj<*)n9q9xxbf)s}oP>j-`Wp@> z@s>@1V|Dp7Bo7v1x)(ivrDonmYT-ZRs*(hurWe2UQwN*Iv94WJyqUz-ol4oN+f}EW zq*$#%a!&k{YcqyZ-U;%^lP`nU$bU7!{u#V^%qvZP#CMN>x@gBR-xAqO3A9!-`b94| zhV5<6Po_2cDN(F8%84$)Pj_EwL`B2d2T=};4PXp%F93-983myDm2*QJHUpk6oFzez zS8mhgOB;)lm~-W+H#@UoLF>a-xGDEzOV>I2!=kJ}O+tRmKy_>>#AvW$Ceu^Nd2MA) zEWqqRi7uJXO?U0@pLhk4H3BSRQh{+oz3Mz=Y;8Z#H2SnXhjUTcZ(NX|rV32;FIaK&$scS5FUsO!u^0aiE#ygH$d=v}sFDgJSMuCm&9LFO1 zvmN&sffHPG=x1xHYVHW{*CHhD^YgB5)~xayMTE`d*!O}^1VvAk!ZAv(mq!l}3GCF1 z({3uyTt8D?3KFAlG1}8}n~Rtpi>R@N;*6#-%$6xO$pL=hr`eVd&ymJxBP^lpty6Pc z)49Nl!+pz|zn9OFSU0x&aHDZrlRKcr8Pk#-hl3t8t(vDB6EhiyH2LBq)uud-b=jV2 zVO}1y(?54y51IqvG;T@JLF-#eE^)wmRI8Cg9b@J|!(;#kl^zFrCY+UI$24d#iX0dU z%mKZ(0>YNAs_BCa$YV#IUX1Yr$M5uG=AD^i>4J3A*kmD+O=hSn@Hy*KBRo()Qyl^2 zTJ@3l9*=b46*V<1NTjwMegIs=kg-LwpT(1T-IThd+^DG;Oopu2;hSZnnq)v(M~|3y zlDZP)XuLHqe&?bc7K~XsIgp+JF}XSPQ?7~o_qTFUyBZcq%lngp!vP;M8vwgC#nDEIsH|9sW_mIITkUOE|?U<06_8J}>tF7!jPcdx7{4%`{gYZW@JbFDt z(p8%0TweRI&=AxlaYE{=CIXd2Qav-7ycoNIvfx@1>!Yzv8&xPaAB64*>xExGGH&;v zc4}3dXA6HxXI`$-Zba8wy!A#V5T}l(n_ym*)rBs+m}Qrr1t~T%9x00GSBlphjg?4a zkg=HRuBrCZPG+29%X5$nLq&k)o81Ss;8iYX10_SiTBFV&f^nzuO`UAY7-ias9buN0 z!Pqdi$gdxaDY}Em?DrgrhP>ZF+kme9#oRh_D`*yp-4RV4QDT2l!^ z1EpBSI9|WSGKp2hKp9O7PwK8}8JQ~@WOz!@HOsluecEkO&zp}^XHsxB8Y+@8n{kE` zv!{rm=!tqJLJMiMsC7aV;QMG^te`7MqJbm_ve1cc2^VIYYBDAPNZbR@*Gg)T|MDJ$O@MHx5F zV>mHpXum%zfz6g1yi-%~aHt~bdmbPjp z={PA(T=`hRnRM5>p)U~h89(xIC-XIR@j8E27;iZe17PSmgopaXZYcjeY2v^W z?P29bD2%(28fM7pKh$grP`J@ zx5Gh|(eH*)K){92V)0PFslQ)&QFDr2(?flJ<%i+EA0o~LTyT%EPCv3btkU3`m*tch z2t7xgYqiOu`N}h_>;pi!Q-rC-^vc2aoDUE_XKd*(L986vJj3;gj?|GBt``#Iwm&qpO zw>3B9oWM4xIrUWj1`>=!19@Wt2q4AA9bmWC%Wa&%^$J}Y9Qp=7k6R^{3Ljx+56W?7 zWO#V2`=KFaJHnhRPNgWFo(+ElwmnZ|$v5GqR~G`Ewlc2~JYuW*?4B!K8ckLh!|z)h zJY{h(Lw$bLD}MHHZ_Hp3?cIAV`g7M?Sw$WF9=Fg%F@f*r!)AWd<#Rlze|VZUSFz|q z>vzQJa3!6Jjp;)I`UVQ3si&{*(7tr;feIEFFPykyC7F59F}+F%ZB8 z;0{8u5hgmeV@q3LCU@XgH=9ZAVK8R)jX+JyWRwLCGvO)wt8#G;P8S6&oz=cv zMbK>INCKC*T(-rv zp0d3iWD5AZ?q_3jPvPhy9w$RubLB`@)Sr@LN|)QyX+92~yNhQkPnYg(Dd~PyYY5VX zqmu<9xnY9+p{0F&VjiFb$WCv1UkhM~Kuz7RJe$7XpjrB^&HRL@#_M=>T|;?*mDvS^ z){fXKaf~A43QpqBWAjiN2F!roT_jdJbJ1a{RSMfYYdFhEp_V=E zziiTd8YoWaC(EtBrEF>6UsjD*EqgqkM)+zD1h#5nP}Dc^!B4`<5A$WKw+EBsl6Fq* zzFFn;xiPpJx9I=8I8HqH(KIc%Uc*71q~&frw%@ZJXgkKA2tthJLe#oc9=|^DsRYzB z#!)R~cyGbo@l@)75Ul=MoS}i|pH#gPtF@D#_`Y1%o7s*5#Cv@CP}hRWhZk|hZ?7XB z0)eARzvEU4TQmbD3C%81^ZCKkXAeHiIuDJ3hzFfVr%=SQbfq!_)l+-acF~1NHj@th zwh7Hg1>JyB*MaDvr6t) z-dv_Q|BPz^F3Xd0lT*R3aq38zJ(#R0fpKei{vSEcO+Artz|zMD1m56=#<8xH0CXI! z$$|Lra<_?Re?{8rv;(xJmEvvyR>nH8>snQ?;jaaq3B>qo@0=?3gK~<7Toe61@QFiD z-*}OM)Q`R=Wz6|st9J(gPpxOPP}$Mi2w@0ImIeECLGh^EVO21LpxKpUt*c^5MUDIW zLx-L8d`Fz4q#<_tSgQJ4zW&r-kYQF7xhy`kx}CNCqHDoNzx<<9JR4Au(cZ~&s|r0E zknTpY8Jkil?Cstdo|-jIe|5IO(~?mib1c|RKgZU6OYWI=#2LpEFz&-Cws{ZaU1;6Y zl-wvW&Wmy5!PyS=CmoI+J0GZT3z25M(giRStJ+ z%dj}Y8VS)E2Mer~5mKgPZj>NC0yP(({ii0`=~UZIA{2xn2E1h>bQA(1AIIKZoz=_mUcTRUGQQcTGbPJQCF@@c*kp2QDSyrb7?OQVf#E^a0WjdWDu^e%d3(%#! z#>#EgZaM2yt+wQnvLqOw4AU=j)j0#3nxqAs{+tOn750cQNTW;6M`@ML0{Co&rI9N` zy#xtZyJpcPQ2v7bKKWePn}Lj4ig2IWX@$Tp;gx=V%_uZ|liG12i~H4XfI?s)>wwBv z?9Oz`4MSpiI(tr?a?c(adA0YkVp7tCCcq5{aB-ioUcPJ=Nc$6Mw36-H5PTr(5Jp~& z&<5m7x~e66x_BmvA9z-yM<3gJA|`T?KWpByTr-%(f3RF!0h+Bm?NN(2uN_02g}PUO zO2f;ojv-ww{h|+Dd~ke|auV6yhWv6@>=;;H^m=H+m*AKBZPs%nq~pvU#-&X9?Fd`i+(J`RdAQQZ6SIhj3FxRF`qT)i3$1< zbMeg047U%i$_1L6*V2vX@F;YMOD;e?0Lnewpn!71p_WL?L|8hnK)dVD(jprdC6Pt$ zRn+B>Xyj1SsbsTmJhEdk>>ey+(d8|PNZWDVqlSlFZq3K3-=Nu6v)bic} zwts4}wshU4jLqsA6ct@{b?|V^PSrrIh!9}=%I1o?P`OwhBau{Gp>2c)ge$mw(=O-w z;*^%GrW1fEu|gNoiQH4N_fkyZ`0L{+`~T(!)D^9oYM>J9UcSkM^)&@V}JbG=%B)&1UZ7J)Y2}VIJM^L_s z!zXXKMT#KXe)HEMbT6LiNCk>saj*_G8o72NZ}a>2a+8(T6dC@N`38ZrCcIa=7iPzv z@0+Of^TeHpT3(U5tM19U#c{4bPe>z}ncr!QWWAV0p3jI6A@&>yM_)Vquu;vZ6Rwz9 zHLFyg!k-LzwxaWZ?XmE)Sv0hWxfDJ9GWw%mqEmC-6Kq1c6d<)>1u3Oo;E#`r$n_-5 zw42RjWase5E#n876FBt22B99Wf33bPWA)R~IKa%aFhrG~RX(RD-UU(;(mT(2a?k}9 zjm`u;eoO=$Io(Uxko(EFS8Y40+-Uyrk{w1YnFWIU91Y6%fExpoW^x5NiWojj>ha6$ zf;|YNi$JdjKuWL1jq0fY+e}^~=#4ATSh?@5aYq6vm3+GJL)$CZ#Qd)Ved|+n*BfUy zZJoVP_q|75lcq;o{V(*$iU9BB)36YwOZ7)S(E*a>$@5X1YvyIuTHY;Z6?;nAJ@=DG zZ&VxLY?jQkVgvnj6EFK7h7QYsBA*@YG(S<$%kU#K#lfmxn_QQ=Sd#|nCDBrHaVjJU z=D6n)eI*MWQA9JsgdWmvO{kxUuO?m}JfHESIyox%c_F2^lyaVJ>yj(?A(kx(2;r51 zwA85_@XH8%C@f?u#_FLBsW z*?JIUlhuQg=s%O4{h+cPF%n128P$U}!fhaQ7@Xbc@FM<2<1#WDpwHcJGanXC!Pzux z^4Ujp&FS$`$>PCk68z<;WF&Hlhw0Vf6hJ6DOOZgig_-nOUZSE*Q(bm})5wras!ld` z-m!|kub4#6T+udA+`kYMew@fUne-NFrqeJm^hZT7FBwRrl@2l

|CpWBdlnVoQN; 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�CldkY(qrZ$Sj+2fDt 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 E@8N@5u)rIQD_J35Y4$k+j#!5!=aw8*h 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 literal 0 HcmV?d00001 diff --git a/docs/HuaweiSmartloggerModBusTCP.PNG b/docs/HuaweiSmartloggerModBusTCP.PNG new file mode 100644 index 0000000000000000000000000000000000000000..ae0b4e61c8f5481273fccaefb37df9cb83a111b9 GIT binary patch literal 87438 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 tI^%+;)i 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

Konfiguration -> Fahrzeuge -> Lade-Profile_ +Unter den Lade-Profilen werden die Einstellungen für das Ladeprofil verwaltet. Die Einstellungen auf der Hauptseite werden aus diesem Profil geladen und dorthin geschrieben. Ist nur ein Fahrzeug vorhanden, so wird in den meisten Fällen nur das Standard-Ladeprofil benötigt. Ausgenommen hiervon ist, wenn per RFID-Tag Ladevorgaben ausgewählt werden. + +In den fahrzeugspezifischen Einstellungen wird ein Ladeprofil einem Fahrzeug zugeordnet. Werden zwei Fahrzeuge geladen, empfiehlt es sich dazu ein zweites Ladeprofil anzulegen. \ No newline at end of file diff --git a/docs/Ladung nur nach Freischaltung.md b/docs/Ladung nur nach Freischaltung.md index 8741b9692f..2bb79e90c1 100644 --- a/docs/Ladung nur nach Freischaltung.md +++ b/docs/Ladung nur nach Freischaltung.md @@ -1,25 +1,67 @@ -Nach Abstecken des Fahrzeugs soll der Ladepunkt gesperrt werden und eine neue Ladung erst nach Freischalten durch: +Die openWB bietet die Möglichkeit den Ladepunkt gegen ungewollten Zugriff zu schützen. +Diese Option ist vor allem für öffentlich zugängliche Ladepunkte sinnvoll aber auch im privaten Bereich nützlich. +Dazu gibt es zwei grundlegende Konzepte: -- Eingeben einer PIN am openWB-Display (sofern mit Touchdisplay) oder -- Vorhalten eines RFID-Tags an der openWB mit RFID-Reader oder -- Direkt-tagging über den Ladestecker mit der openWB-Pro oder -- Auswahl eines Fahrzeugs im User Interface +#### **A** Nach Abstecken des Fahrzeugs wird der Ladepunkt gesperrt (Sperre nach Abstecken) +Ein neuer Ladevorgang erfolgt erst nach Freischalten durch: +- Eingeben einer **PIN** am openWB-Display (sofern mit Touchdisplay) +- Vorhalten eines **RFID-Tags** an der openWB mit RFID-Reader +- Fahrzeugerkennung über den **Ladestecker** mit der openWB-Pro +- händisches Entsperren des Ladepunktes im User Interface -gestartet werden. +#### **B** Nach Abstecken des Fahrzeugs wird auf das Standardfahrzeug (Standard nach Abstecken) mit Lademodus **Stop** gewechselt +Ein neuer Ladevorgang erfolgt erst durch: +- auswählen eines Fahrzeugs mit Lademodus Sofortladen, PV- oder Zielladen +- händischer Wechsel des Lademodus oder Fahrzeuges im User Interface +- vorhalten eines ID-Tags, welcher einem Fahrzeug zugeordnet ist -Hierzu ist folgendes zu konfigurieren: +#### Allgemeine Konfiguration +Wenn ID-Tags genutzt werden sollen, dann ist in der Navigationsbar unter **Einstellungen - Optionale Hardware** unter dem Punkt Identifikation von Fahrzeugen die Option **Identifikation aktivieren** auf **An** zustellen. -1. für jedes Fahrzeug mit Freischaltwunsch unter Einstellungen -> Konfiguration -> Fahrzeuge zusätzlich zum Standard-Fahrzeug **ein separates Fahrzeug** anlegen -2. unter Lade-Profile -> **Standard-Lade-Profil** (wird nur dem Standard-Fahrzeug zugeordnet) -> **Lademodus auf Stop** stellen -3. ein **neues Lade-Profil** für Fahrzeuge mit Freischaltwunsch anlegen (z.B. RFID-Lade-Profil) und dort -> **Standard nach Abstecken** aktivieren sowie bevorzugten Lademodus wählen -4. den Fahrzeugen mit Freischaltwunsch **das Lade-Profil** für Fahrzeuge mit Freischaltwunsch **zuweisen** (z.B. RFID-Lade-Profil) -5. **Speichern** nicht vergessen +**I.** Fahrzeuge +In der Navigationsbar auf Einstellungen klicken, dann den Reiter Konfiguration auswählen und **Fahrzeuge** aufrufen. Standardmäßig ist hier das **Standard-Fahrzeug** angelegt, welches die Option **Zugeordnete ID-Tags** beinhaltet. Hier müssen die ID-Tags eingetragen werden, welche ausschliesslich zur Zuordnung von Fahrzeugen verwendet werden. Hier zeigt sich auch die Stärke der openWB im Fahrzeugkonzept, welches die Möglichkeit bietet, verschiedene Fahrzeuge mit verschiedenen Fahrzeug- und Ladeprofilen über die ID-Kennung aufzurufen. -Wenn die Freischaltung mittels PIN, RFID oder MAC-Adresse erfolgen soll: +Hier muss für jedes Fahrzeug mit Freischaltwunsch ein **separates Fahrzeug** angelegt werden und die jeweiligen ID-Tags eingetragen werden, die zur Zuordnung genutzt werden. -- Einstellungen -> Optionale Hardware: **Identifikation aktivieren** + Speichern -- unter Konfiguration -> Fahrzeuge -> gewünschtes Fahrzeug -> Zugeordnete ID-Tags: dem jeweiligen Fahrzeug **den ID-Tag (PIN/RFID-Tag/MAC-Adresse) zuweisen** + Speichern -- unter Konfiguration -> Ladepunkte -> Ladepunkt-Profile -> im gewünschten Ladepunkt-Profil: **Freigabe durch ID-Tags aktiviere** + Speichern +Achtung: Lade-Profile müssen den Fahrzeugen unter Fahrzeuge zugeordnet werden! -Wenn die Tags an allen Ladepunkten genutzt werden dürfen, müssen die Tags nur bei den Fahrzeugen eingetragen werden. Wenn im Ladepunkt-Profil Tags eingetragen werden, können nur die eingetragenen Tags zur Fahrzeug-Zuordnung genutzt werden. Sind keine Tags eingetragen, wird nur die Zuordnung zum Fahrzeug geprüft. Durch die Begrenzung der Freischaltung auf bestimmte Tags, lassen sich zB Mitarbeiter- und Gäste-Parkplätze abbilden. -- unter Konfiguration -> Ladepunkte -> Ladepunkt-Profile -> im gewünschten Ladepunkt-Profil: **die gültigen ID-Tags zuordnen** + Speichern +**II.** Ladepunkte +In der Navigationsbar auf Einstellungen klicken, dann den Reiter Konfiguration auswählen und **Ladepunkte** aufrufen. +Standardmäßig ist hier das **Standard-Ladepunkt-Profil** angelegt, welches die Option **Sperre nach Abstecken** bietet. Wird diese Option aktiviert, dann ist das Feld **Zugeordnete ID-Tags** zugänglich. +Hier müssen die ID-Tags eingetragen werden, welche ausschliesslich zur Entsperrung des Ladepunktes verwendet werden. Sind mehrere Ladepunkte vorhanden (z.B. Duo oder mehrere ferngesteuerte openWBs) kann für jeden Ladepunkt ein eigenes Ladepunkt-Profil angelegt werden, wobei hier jeweils eine eigene ID-Kennung zur Freischaltung hinterlegbar ist. + +Achtung: Ladepunkt-Profile müssen den Ladepunkten unter Ladepunkte zugeordnet werden! + +##### Anmerkung: Bei allen Anpassungen der Einstellungen Speichern nicht vergessen! + +Zu **A. Sperre nach Abstecken** ist folgendes zu konfigurieren: +Die Option **Sperre nach Abstecken** ist im **Ladepunkt-Profil** bei Ladepunkte auswählbar und bewirkt, dass der Ladepunkt nach Abstecken eines Fahrzeugs gesperrt wird. Es gibt hier dann folgende zwei Möglichkeiten diesen Ladepunkt wieder zu entsperren: +1. die Option *Ladepunkt sperren* händisch im User Interface auf Nein setzen. Dadurch startet nach Anstecken eines Fahrzeugs das voreingestellte Fahrzeug den Ladevorgang. +2. ein ID-Tag vorhalten, der im Ladepunkt-Profil hinterlegt ist. Dadurch wird der Ladepunkt automatisch entsperrt und das voreingestellte Fahrzeug startet den Ladevorgang. Ist der ID-Tag, welcher im Ladepunkt-Profil hinterlegt wurde identisch mit einem ID-Tag, der einem Fahrzeug zugeordnet ist, dann findet hier auch direkt eine Zuordnung zu einem Fahrzeug statt. Dabei wird der Ladepunkt entsperrt und das dem ID-Tag zugeordnete Fahrzeug startet den Ladevorgang. +Wurden mehrere Fahrzeuge mit demselben ID-Tag angelegt, dann startet das Fahrzeug den Ladevorgang, welches auf der Liste der Fahrzeuge dem ID-Tag zuerst zugeordnet wurde. +Solange der Ladepunkt gesperrt ist, wird kein gültiger ausschliesslich einem Fahrzeug zugeordneter ID-Tag akzeptiert. +Nach Starten eines Ladevorgangs wird kein neuer ID-Tag akzeptiert. + +Anmerkung: Im Fall, dass zwei oder mehrere Fahrzeuge mit unterschiedlichen ID-Tags an demselben (gesperrten) Ladepunkt laden dürfen, müssen beide ID-Tags der Fahrzeuge auch im Ladepunkt-Profil hinterlegt werden. Dadurch wird erstens der Ladepunkt entsperrt und zweitens das dem Fahrzeug zugeordnete Lade-Profil ausgewählt, wodurch dann das damit verknüpfte Fahrzeug mit den dort hinterlegten Ladeeinstellungen den Ladevorgang startet. + +Zu **B. Standard nach Abstecken** ist folgendes zu konfigurieren: +Die Option **Standard nach Abstecken** ist im **Lade-Profil** bei Fahrzeuge auswählbar. Diese Option macht nur Sinn, wenn neben dem Standard-Lade-Profil mindestens ein weiteres Lade-Profil und mindestens ein weiteres Fahrzeug angelegt wurde. Dabei ist dem Standard-Fahrzeug das Standard-Lade-Profil und dem weiteren Fahrzeug das weitere Lade-Profil zuzuweisen. +Um diese Option sinnvoll zu nutzen, muss im Standard-Lade-Profil unter Allgemeine Optionen der aktive Lademodus auf **Stop** gestellt werden. +Weiterhin muss in einem anderen zu nutzenden Lade-Profil unter Allgemeine Optionen **Standard nach Abstecken** aktiviert werden. + +Insofern der Ladepunkt nicht gesperrt wurde, kann über einen ID-Tag der Ladevorgang für ein dem ID-Tag zugeordnetes Fahrzeug (entweder Standardfahrzeug oder ein anderes Fahrzeug mit einem Lade-Profil verschieden vom Standard-Lade-Profil) gestartet werden. Nach Abstecken wechselt die Auswahl dann auf Standardfahrzeug in den Lademodus **Stop** und der Ladepunkt startet keinen weiteren Ladevorgang bis die Auswahl entweder händisch über das User Interface oder automatisch per ID-Tag geändert wird. + +Startet ein Fahrzeug über einen ID-Tag den Ladevorgang und ist in diesem Fahrzeug Standard nach Abstecken aktiviert, dann wird nach Abstecken auf das Standardfahrzeug gewechselt, unabhängig davon, welches Fahrzeug vorher ausgewählt war. +Startet ein Fahrzeug über einen ID-Tag den Ladevorgang und ist in diesem Fahrzeug Standard nach Abstecken nicht aktiviert, dann wird nach Abstecken auf das letzte vorher ausgewählte Fahrzeug gewechselt. + +### Use Cases: + +#### Sperre nach Abstecken +Sperre nach Abstecken kann an einem Ladepunkt verwendet werden, welcher das Laden gegenüber fremden Zugriff sichert. Wird der ID-Tag nur zum Sperren/Entsperren des Ladepunktes verwendet, dann startet immer das ausgewählte Fahrzeug den Ladevorgang. Dies kann im privaten Bereich mit nur einem Fahrzeug sinnvoll sein, damit nur dieses Fahrzeug auch laden darf. Die Option ist aber auch für Ladeparks sinnvoll, bei denen die Ladepunkte nur für eine Gruppe von ID-Tags freischaltbar sind und dem ID-Tag zum Entsperren auch gleichzeitig zugeordnet sind. + +#### Standard nach Abstecken: +Standard nach Abstecken kann an einem Ladepunkt verwendet werden, welcher das Laden mehrere verschiedener Fahrzeuge ermöglichen soll. Werden mehrere Fahrzeuge mit verschiedenen Lade-Profilen und verschiedenen ID-Kennungen neben dem Standard-Fahrzeug angelegt, kann über die ID-Kennung zwischen den einzelnen Fahrzeugen gewechselt werden. Hier bietet sich beispielsweise ein privater Ladepunkt mit zwei Fahrzeugen an oder ein Ladepunkt in einer Firma mit verschiedenen Mitarbeitern. + +Standard nach Abstecken kann auch dazu verwendet werden, um beispielsweise zwischen zwei Fahrzeugen (und damit Fahrzeug-Profilen und Lade-Profilen) ohne ID-Tag zu wechseln, vor allem wenn nur eines der Fahrzeuge über die ID-Kennung zuverlässig erkannt wird. + +Stand 02. Juli 2024 \ No newline at end of file diff --git a/docs/MQTT.md b/docs/MQTT.md new file mode 100644 index 0000000000..5681b86447 --- /dev/null +++ b/docs/MQTT.md @@ -0,0 +1,71 @@ +# MQTT + +## Grundsätzliches +MQTT bedeutet: Message Queuing Telemetry Transport. Es handelt sich hierbei um ein M2M (Machine to Machine) Protokoll. +Für eine Kommunikation wird ein Broker (=Verwalter) benötigt, welcher die Nachrichten von den Sendern empfängt und an die Empfänger, welche sich für den Inhalt angemeldet haben, weiterleitet. Man spricht bei MQTT von publish und subscribe. Die Nachrichten werden in topics verschickt. + +openWB hat einen eigenen MQTT-Broker integriert, über den die Kommunikation läuft. Möchte man die Wallbox steuern oder Status-Nachrichten empfangen, sollte man sich als Client an diesem Broker anmelden. Der Broker läuft auf der IP der openWB unter Port 1883 ohne Nutzerauthentifizierung. + +## Zähler + +Als EVU-Zähler können auch Werte über MQTT empfangen werden. Die Integration ist im Abschnitt [Zähler](https://github.com/openWB/core/wiki/Zähler) beschrieben. + +## Smarthome + + +## Steuerbefehle + +Auf eigene Gefahr! Die folgenden Einstellungen und Kommunikationsmöglichkeiten sind nicht spezifiziert und nur von kundigen Nutzern mit entpsrechendem Fachwissen über die Konsequenzen durchzuführen. + +Bei den Steuerbefehlen ist # immer durch den entsprechenden Ladepunkt/Zähler/Fahrzeug zu ersetzen. Dies ist zwingend zu beachten, da ansonsten neue Fahrzeuge/Zähler etc. erstellt werden, wenn es nicht die ID eines Konfigurierten Gerätes ist. + +Lademodus auf "Sofortladen" +`openWB/set/vehicle/template/charge_template/#/chargemode/selected -> instant_charging` + +PV-Laden +`openWB/set/vehicle/template/charge_template/#/chargemode/selected -> pv_charging` + +"Minimal Stromstärke" im PV-Laden auf z.B. 6A +`openWB/set/vehicle/template/charge_template/#/chargemode/pv_charging/min_current -> 6` + +SoC-Limit auf z.B. 80% setzen +`openWB/set/vehicle/template/charge_template/#/chargemode/pv_charging/max_soc -> 80` + +Zielladen +`openWB/set/vehicle/template/charge_template/#/chargemode/selected -> scheduled_charging` + +Standby +`openWB/set/vehicle/template/charge_template/#/chargemode/selected -> standby` + +Stop +`openWB/set/vehicle/template/charge_template/#/chargemode/selected -> stop` + +_Work in Progress_ + +## Statusnachrichten + +Wo wird welcher nützliche Inhalt gesendet. + +Ladeprofil Status (verschachteltes JSON, muss entsprechend weiter decodiert werden...): +openWB/vehicle/template/charge_template/1 + +Setzen von min_Current für min+PV nachbauen: +`openWB/set/vehicle/template/charge_template/#/chargemode/pv_charging/min_current` + +Setzen des Lademodus: (Werte die zu senden sind: instant_charging, pv_charging, scheduled_charging, standby, stop) +`openWB/set/vehicle/template/charge_template/#/chargemode/selected` + +Ladepunkt sperren für Priosteuerung der LP: +`openWB/set/chargepoint/#/set/manual_lock` + +SoC Update triggern: +`openWB/set/vehicle/1#/get/force_soc_update` + +SoC im manuellen Modus setzen: +`openWB/set/vehicle/#/soc_module/calculated_soc_state/manual_soc` + +### Lademodus +Lademodus des angesteckten Auto wird in den LP geschrieben. Solange immer dasselbe Auto dran steckt ist das gleich, aber wenn Du ein anderes Auto ansteckst, bei mir z.b. ein Gastauto und Du nur den Lademodus deines normalen Auto ausliest und damit steuerst, ist der dortige Lademodus halt dann nicht der eigentliche des LP + + +_Work in Progress_ \ No newline at end of file diff --git "a/docs/Neues Ger\303\244t programmieren.md" "b/docs/Neues Ger\303\244t programmieren.md" deleted file mode 100644 index 777dcc8a70..0000000000 --- "a/docs/Neues Ger\303\244t programmieren.md" +++ /dev/null @@ -1,23 +0,0 @@ -Um die Programmierung neuer Geräte zu erleichtern, findet Ihr unter [docs/samples](https://github.com/openWB/core/tree/master/docs/samples?v30-12-2022) drei Muster: - -1. sample_modbus: Für Geräte, die per Modbus abgefragt werden. Dazu wird im Gerät ein Modbus-Client instanziiert, der dann an die Komponenten übergeben wird. -2. sample_request_per_component: Für Geräte, die per Http-Request abgefragt werden (lokal oder übers Internet) und bei denen jede Komponente eine eigene URL hat. -3. sample_request_per_device: Für Geräte, die per Http-Request abgefragt werden (lokal oder übers Internet) und bei denen alle Daten über eine URL abgefragt und dann je Komponente aus der Antwort geparst werden müssen. - -Die Muster sind nur als einheitlicher Ausgangspunkt zu verstehen! Es kann durchaus notwendig sein, Elemente der verschiedenen Muster zu kombinieren, weitere Einstellungs-Parameter hinzuzufügen oder bei einem Http-Request eine Authentifizierung durchzuführen. - -Nachdem Ihr das Muster, das am besten zu Eurem Gerät passt, ausgewählt habt, kopiert Ihr dieses in den _packages/modules/devies/\*Gerätename\*_-Ordner. Ordnername und Typ in config.py->Sample->type müssen identisch sein, damit das Gerät in der automatisch generierten Auswahlliste im UI angezeigt wird. -Wenn das Gerät nicht alle Komponenten unterstützt, löscht Ihr die nicht unterstützten Komponenten und die Referenzen darauf in config.py und device.py. -Wenn von der Komponente die Zählerstände für Import und Export gelesen werden können, können die Zeilen für simcount entfernt werden. - -Bei Hybrid-Systemen erfolgt die Verrechnung von Speicher-und PV-Leistung automatisiert, wenn Speicher und Wechselrichter in der Hierarchie wie [hier](https://github.com/openWB/core/wiki/Hybrid-System-aus-Wechselrichter-und-Speicher) beschrieben angeordnet sind. Wenn noch weitere spezifische Berechnungen erforderlich sind, müsst Ihr die Komponenten wie unter sample_request_per_device abfragen. Die update-Methode der Komponenten wird dann in eine get- und set-Methode aufgeteilt. Die get-Methode liefert den Component-State zurück, dieser wird in der update_components-Methode des Geräts verrechnet und dann die set-Methode der Komponente aufgerufen, die die store-Methode der Komponente aufruft. - -Das Speichern, Runden, Loggen und eine Plausibilitätsprüfung der Werte erfolgt zentral und muss daher nicht in jedem Modul implementiert werden. - -Wenn keine Einstellungsseiten in vue hinterlegt sind, sind die Einstellungen als json-Objekt editierbar. - -### Kompatibilität mit 1.9 - -Damit das Modul auch unter 1.9 lauffähig ist, müssen -wie bisher- die unter [docs/legacy](https://github.com/openWB/core/tree/master/docs/samples/legacy?v30-12-2022) angegebenen Ordner erstellt werden und das Modul im UI hinzugefügt werden. Außerdem muss in der device.py die read_legacy-Funktion so implementiert werden, dass anhand des übergebenen Komponenten-Typs das Update der entsprechenden Komponente getriggert wird. Beim zentralen Speichern der ausgelesenen Werte wird automatisch erkannt, ob diese in die ramdisk (1.9) oder in den Broker (2.x) geschrieben werden müssen. - -_Bei Fragen programmiert Ihr das Gerät vorerst, wie Ihr es versteht, und erstellt einen (Draft-)PR. Wir unterstützen Euch gerne per Review. diff --git a/docs/Neues Modul programmieren.md b/docs/Neues Modul programmieren.md new file mode 100644 index 0000000000..b856463bdc --- /dev/null +++ b/docs/Neues Modul programmieren.md @@ -0,0 +1,39 @@ +Um die Programmierung neuer Module zu erleichtern, findet Ihr unter [docs/samples](https://github.com/openWB/core/tree/master/docs/samples?v30-12-2022) Muster für neue Geräte, Fahrzeuge, Cloud-Sicherung und Stromanbieter für strompreisbasiertes Laden. +Die Muster sind nur als einheitlicher Ausgangspunkt zu verstehen! Es kann durchaus notwendig sein, Elemente der verschiedenen Muster zu kombinieren, weitere Einstellungs-Parameter hinzuzufügen oder bei einem Http-Request eine Authentifizierung durchzuführen. +Das Muster kopiert Ihr in den _packages/modules/\*Modul-Typ\*/\*Modul-Name\*_-Ordner. Ordnername und Typ in config.py->Sample->type müssen identisch sein, damit das Gerät in der automatisch generierten Auswahlliste im UI angezeigt wird. + +Wenn keine Einstellungsseiten in vue hinterlegt sind, sind die Einstellungen als json-Objekt editierbar. Muster für die Einstellungsseiten findet Ihr im Ordner [samples/samples_gui](https://github.com/openWB/core/tree/02b34ff216b0dfc83fdc56a53b63d52d5d9a79d2/docs/samples/samples_gui) + +Außer der modulspezifischen Abfrage erfolgt alles weitere zentral und muss daher nicht in jedem Modul implementiert werden: +* Speichern, Runden, Loggen und eine Plausibilitätsprüfung der Werte +* Prüfung, ob das Intervall zB zur SoC- oder Preis-Abfrage abgelaufen ist +* Behandlung von Exceptions + +Exceptions dürfen daher nur abgefangen werden, wenn sie +* behoben werden können. +* weitere Aktionen vorgenommen werden sollen. Danach mit `raise e` die Exception erneut werfen, damit sie weiterverarbeitet werden kann. + +Bei Modulen, die einen http-Request ausführen, get/post-Requests immer mit `req.get_http_session().get/post()` aus dem Ordner modules/common stellen. [get_http_session](https://github.com/openWB/core/blob/02b34ff216b0dfc83fdc56a53b63d52d5d9a79d2/packages/modules/common/req.py#L8) loggt die Antwort und prüft in einem Callback, ob ein Fehler aufgetreten ist und wirft eine Exception. Bei gängigen Fehlern wird diese in einen Text übersetzt, der auch für den Benutzer verständlich ist. + +Ein paar Hintergrund-Details, wie die Fehlerbehandlung umgesetzt ist: +Die update-Methode des Moduls wird immer mit dem [Kontextmanager](https://github.com/openWB/core/blob/02b34ff216b0dfc83fdc56a53b63d52d5d9a79d2/packages/modules/common/component_context.py#L11) aufgerufen. Dieser prüft nach dem Ende der Update-Methode, ob eine Exception aufgetreten ist und loggt diese und setzt die Topics `.../get/fault_state/` auf 2 und in `.../get/fault_str` den Text der Exception. fault_str wird dann im jeweiligen Modul auf der Status-Seite ausgegeben, um dem Benutzer eine Rückmeldung zu geben. + +### Neues Gerät programmieren +Für neue Geräte gibt es drei Muster: + +1. sample_modbus: Für Geräte, die per Modbus abgefragt werden. Dazu wird im Gerät ein Modbus-Client instanziiert, der dann an die Komponenten übergeben wird. +2. sample_request_per_component: Für Geräte, die per Http-Request abgefragt werden (lokal oder übers Internet) und bei denen jede Komponente eine eigene URL hat. +3. sample_request_per_device: Für Geräte, die per Http-Request abgefragt werden (lokal oder übers Internet) und bei denen alle Daten über eine URL abgefragt und dann je Komponente aus der Antwort geparst werden müssen. + +Wenn das Gerät nicht alle Komponenten unterstützt, löscht Ihr die nicht unterstützten Komponenten und die Referenzen darauf in config.py und device.py. +Wenn von der Komponente die Zählerstände für Import und Export gelesen werden können, können die Zeilen für simcount entfernt werden. + +Bei Hybrid-Systemen erfolgt die Verrechnung von Speicher-und PV-Leistung automatisiert, wenn Speicher und Wechselrichter in der Hierarchie wie [hier](https://github.com/openWB/core/wiki/Hybrid-System-aus-Wechselrichter-und-Speicher) beschrieben angeordnet sind. Wenn noch weitere spezifische Berechnungen erforderlich sind, müsst Ihr die Komponenten wie unter sample_request_per_device abfragen. Die update-Methode der Komponenten wird dann in eine get- und set-Methode aufgeteilt. Die get-Methode liefert den Component-State zurück, dieser wird in der update_components-Methode des Geräts verrechnet und dann die set-Methode der Komponente aufgerufen, die die store-Methode der Komponente aufruft. + +### Neues Fahrzeug programmieren +Beim Aufruf der _updater_-Funktion wird die Variable _vehicle_update_data_ übergeben. Darin sind aktuelle Daten aus der Regelung, wie zB Stecker-Status oder die geladene Energie seit Anstecken, enthalten, um Besonderheiten wie zB das Aufwecken des Fahrzeugs oder eine manuelle Berechnung während des Ladevorgangs umsetzen zu können. +Bei manchen Fahrzeugen kann der SoC nicht während der Ladung abgefragt werden. Damit dieser während der Ladung berechnet wird, muss in der soc.py bei der Instanziierung von `ConfigurableVehicle` der Parameter `calc_while_charging` auf `True` gesetzt werden. + +Nach dreimaliger fehlgeschlagener Abfrage wird der SoC auf 0% gesetzt, damit in jedem Fall geladen wird. + +_Bei Fragen programmiert Ihr das SoC-Modul vorerst, wie Ihr es versteht, und erstellt einen (Draft-)PR. Wir unterstützen Euch gerne per Review. diff --git a/docs/Neues Soc-Modul programmieren.md b/docs/Neues Soc-Modul programmieren.md deleted file mode 100644 index fc3dc74a30..0000000000 --- a/docs/Neues Soc-Modul programmieren.md +++ /dev/null @@ -1,12 +0,0 @@ -Um die Programmierung neuer SoC-Module zu erleichtern, findet Ihr unter [docs/samples](https://github.com/openWB/core/tree/master/docs/samples?v30-12-2022) ein Muster _sample_vehicle_. - -Die Muster sind nur als einheitlicher Ausgangspunkt zu verstehen! Es kann durchaus notwendig sein, weitere Einstellungs-Parameter hinzuzufügen oder bei einem Http-Request eine Authentifizierung durchzuführen. Beim Aufruf der _updater_-Funktion wird die Variable _vehicle_update_data_ übergeben. Darin sind aktuelle Daten aus der Regelung, wie zB Stecker-Status oder die geladene Energie seit Anstecken, enthalten, um Besonderheiten wie zB das Aufwecken des Fahrzeugs oder eine manuelle Berechnung während des Ladevorgangs umsetzen zu können. - -Das Muster kopiert Ihr in den _packages/modules/vehicles/\*Name\*_-Ordner. Ordnername und Typ in config.py->Sample->type müssen identisch sein, damit das Gerät in der automatisch generierten Auswahlliste im UI angezeigt wird. -Bei manchen Fahrzeugen kann der SoC nicht während der Ladung abgefragt werden. Damit dieser während der Ladung berechnet wird, muss in der soc.py bei der Instanziierung von `ConfigurableVehicle` der Parameter `calc_while_charging` auf `True` gesetzt werden. - -Das Speichern, Runden, Loggen und eine Plausibilitätsprüfung der Werte sowie die Prüfung, ob das Intervall zu SoC-Abfrage abgelaufen ist, erfolgt zentral und muss daher nicht in jedem Modul implementiert werden. - -Wenn keine Einstellungsseite in vue für das SoC-Modul hinterlegt ist, sind die Einstellungen als json-Objekt editierbar. - -_Bei Fragen programmiert Ihr das SoC-Modul vorerst, wie Ihr es versteht, und erstellt einen (Draft-)PR. Wir unterstützen Euch gerne per Review. diff --git a/docs/Tutorial b/docs/Tutorial new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/Typische-Anwendungsfaelle.md b/docs/Typische-Anwendungsfaelle.md new file mode 100644 index 0000000000..e24483be49 --- /dev/null +++ b/docs/Typische-Anwendungsfaelle.md @@ -0,0 +1,29 @@ +#Typische Anwendungsfälle +## Privater Haushalt, ein E-Auto und PV-Anlage +In diesem Szenario sind die Ziele meistens, das Auto morgens für den Weg zur Arbeit fahrbereit zu haben, aber bis dahin möglichst viel Energie aus der PV-Anlage zum Laden zu nutzen. +Hierfür ist die Funktion *Zielladen* zu nutzen. Auch, wenn es vom Namen her scheint, dass nur zu einem festen Zeitpunkt eine definierte Energiemenge in das EV geladen sein soll, wird dennoch bis zum Beginn dieses erzwingenen Ladevorgangs PV-Energie, sofern vorhanden, genutzt. + +![Zielladen](pictures/Anwendungsfaelle_zielladen.jpg) + +Einstellbar ist der Ziel SoC, der in vielen Fällen auf 80% eingestellt wird, da eine höhere Ladung den Akku des EV überproportional belastet. Dennoch sollte ab und zu der Akku auf 100% geladen und dort über kurze Zeit gehalten werden, damit die Zellen sich wieder balancieren können. +In dem oben gezeigten Beispiel ist der Ladestrom mit 13A eingestellt. Somit bleibt bei einem 3-phasigen 11kW-Lader noch Reserve, um die Stromstärke kurz vor Ende ggf. noch zu erhöhen. + +Als Zielzeit ist die Abfahrtzeit Abfahrt einzustellen. Die Regelung berechnet aus dem aktuellen SoC des Fahrzeugs, sowie aus den zwingend korrekt anzugebenden Maximalwerten der Ladeströme im Ladeprofil, den Zeitpunkt, an dem die Ladung starten muss. +Es empfielt sich den Ladestrom im Ladeprofil unter Zielladen etwas niedriger als die Möglichkeiten der Wallbox und des Fahrzeugs anzugeben, damit etwas Puffer vorhanden ist, falls das Auto zu spät angesteckt worden ist. + +Falls das EV durch eine Standheizung vor Fahrtbeginn vorgeheizt werden soll, kann hierfür ein Zeitslot mit _Laden nach Zeitplan_ konfiguriert werden. Zeitladen kann zusätzlich zum gewählten Lademodus, hier Zielladen, aktiviert werden. So wird der Akku durch die Standheizung nicht belastet, sondern der Strom kommt aus dem EVU-Netz. Hier kann dann ein minimaler Strom von z.B. 6A gewählt werden, da die Leistungsaufnahme für die Heizung meist nicht mehr als 1kW benötigt. + +###Außerplanmäßige Fahrt +Wird das Fahrzeug außer der Reihe benötigt und es soll kurzfristig viel Energie in den Akku geladen werden, ist die openWB auf der Startseite auf *Sofortladen* zu stellen. Hier ist es möglich, mit der maximal verfügbaren Leistung den Akku so schnell wie möglich aufzuladen. + +### Ausnutzen der PV-Anlage bei wechseldem Wetter +Insbesondere im Frühling und Herbst kann die PV-Leistung bei bewölktem Himmel stark schwanken. Um ein häufiges Beenden und Starten des Ladevorgangs zu vermeiden, kann bei dem Modus PV ein *Minimaler Dauerstrom* eingestellt werden. +Ist die Einstellung auf 0A, so wird ausschließlich mit solarem Überschuss geladen. Steigt die Netzeinspeisung über den in _Konfiguration->Ladeeinstellungen ->PV-Laden_ eingestellten Grenzwert, wird nach Ablauf der Einschaltverzögerung die Ladung gestartet. Mit der nächstgrößeren Einstellmöglichkeit 6A, wird (z.B. bei einphasigem Laden) kontinuierlich mit ~1,3kW geladen. Wird ins Netz eingespeist, wird die Ladeleistung hochgeregelt, sodass möglichst weder Strom eingespeist noch bezogen wird und, je nach Möglichkeiten des Fahrzeugs, auf 3-phasiges Laden umgeschaltet. + +![PV-Min](pictures/Anwendungsfaelle_minStrom.jpg) + +##Integration in Hausautomation +openWB eignet sich hervorragend zur Integration in eine bestehende Hausautomatins-Infrastruktur, da über den integrierten MQTT-Broker Befehle sowie Statusmeldungen ausgetauscht werden können. +### MQTT +Zum Debugging empfiehlt sich das Programm [MQTT-Explorer?](http://mqtt-explorer.com/). +Eine detailierte Erklärung ist auf der [MQTT-Seite](https://github.com/openWB/core/wiki/MQTT) zu finden. \ No newline at end of file diff --git a/docs/Wiki-Eintrag erstellen.md b/docs/Wiki-Eintrag erstellen.md index 7bec36028a..af529a2e6e 100644 --- a/docs/Wiki-Eintrag erstellen.md +++ b/docs/Wiki-Eintrag erstellen.md @@ -6,10 +6,12 @@ Der Name der Markdown-Datei ist der Titel der Wiki-Seite. Die Datei _Sidebar.md Wenn ihr euch am Wiki beteiligen wollt müsst ihr zunächst einen Github Account erstellen bzw. euch mit eurem anmelden. Dann geht ihr auf die [Projektseite](https://github.com/openWB/core) und erstellt einen Fork: -![Fork](pictures/Wiki-Eintrag erstellen_Fork.png) +![Fork](pictures/Wiki-Eintrag_erstellen_Fork.png) Dies ist nötig, da dem "normalen Mitarbeiter" das Projekt nicht gehört und man somit keine Schreibrechte im Projekt des OpenWB Accounts hat. Man erstellt also eine verknüfpte Kopie in seinem eigenen Account. Hier wird dann am besten ein Branch erstellt, den ihr sinnvoll benennt (z.B. Wiki oder ähnlich). In diesem Branch arbeitet ihr und ändert und ergänzt entsprechend euren Erfahrungen zu den Themen in denen ihr euch auskennt. Danach müsst ihr Änderungen mit *Commit* in die (lokale) Git-Umgebung übernehmen und mit *Push* zu Github übertragen. Dies beginnt ihr in eurem eigenen Branch und wählt im Menü oben Pull-Request aus und füllt die Felder mit einer Beschreibung was ihr gemacht habt. -![Pull](pictures/Wiki-Eintrag erstellen_Pull.png) -Für euren ersten Beitrag müsst ihr noch von einem Projektmitarbeiter freigeschaltet werden. Dies kann einige Zeit dauern. + +![Pull-Request](pictures/Wiki-Eintrag_erstellen_Pull.jpg) + +Für euren ersten Beitrag müsst ihr noch von einem Projektmitarbeiter freigeschaltet werden. Dies kann einige Zeit dauern. Eventuell werden auch noch Änderungen vorgeschlagen, die ihr dann diskutieren oder einfach annehmen könnt. diff --git a/docs/Zaehler.md b/docs/Zaehler.md new file mode 100644 index 0000000000..1abb80e036 --- /dev/null +++ b/docs/Zaehler.md @@ -0,0 +1,91 @@ + +# Zähler + +openWB benötigt zum erfolgreichen PV-Überschussladen die entsprechenden Zählerwerte am EVU-Punkt (EVU=Elektrizitätsversorgungsunternehmen), sprich dem Übergang ins öffentliche Netz. An dieser Stelle muss die Gesamtleistung saldierend erfasst werden. Für eine phasenbasierte Leistungsüberwachung sind auch die einzelnen Ströme und/oder Leistungen der drei Phasen notwendig. Bei einem Zähler im Hausverbrauchs-Zweig muss die Konfiguration wie [hier](https://github.com/openWB/core/wiki/Hausverbrauchs-Zähler) beschrieben erfolgen. + +Im einfachsten Fall geschieht dies durch Kauf und Einbau eines [EVU-Kits](##EVU-Kit). Sollten schon digital auslesbare Zähler vorhanden sein, so besteht die Möglichkeit diese Werte an openWB weiterzuleiten, auch mit Hilfe von Hausautomationsservern. + +Es gibt viele verschiedene Möglichkeiten, Zähler als auch Wechselrichter in das openWB-System einzufügen. Die Struktur der Zähler muss dann im [Lastmanagement](https://github.com/openWB/core/wiki/Lastmanagement-und-kaskadierte-Zähler) dem System bekanntgegeben werden. Hier können auch [virtuelle Zähler](##Virtuelle Zähler) hinzugefügt werden, welche openWB-intern die untergeordneten Zähler verrechnen. + + +## EVU-Kit + +Das [EVU-Kit (Link zum Shop)](https://openwb.de/shop/?product=openwb-evu-kit) ist die einfachste Art in der Software einen Zähler an die openWB zu integrieren. Der Zähler muss von einem Elektriker direkt hinter dem Zähler des EVU in den Zählerschrank integriert werden. Es gibt, je nach Kapazität des Hausanschlusses, verschiedene Messvarianten des Zählers, welche sich von der Integration unterscheiden. Dies betrifft aber nur die Arbeit des Elektrikers. +Der Zähler kommuniziert mit der openWB über Ethernet. Die Kits sind so vorkonfiguriert, dass sie von der openWB Software automatisch gefunden werden, wenn ein entsprechendes Gerät mit Zählerkomponente angelegt wird. Es gibt auch für Wechselrichter und Speicher entsprechende Kits. + +![EVU-Kit](pictures/EVU-PV-Speicher-Kit-689x1024.png) + +## MQTT + +openWB hat einen MQTT-Broker integriert, welcher unter Port 1883 (ohne Verschlüsselung) und Port 8883 (mit Verschlüsselung) erreichbar ist. Benutzerauthentifizierung ist deaktiviert und auch nicht aktivierbar. Ein Zähler, welcher die benötigten Daten liefert muss sich mit diesem Broker verbinden und dort die Werte unter den entsprechenden Topics publishen. + +Folgende Werte können dem MQTT-Zähler übergeben werden. Die ID ist individuell und wird beim Anlegen der MQTT-Komponente angezeigt. +Die folgenden Topics sind für einen reibungslosen Betrieb unbedingt erforderlich: +- **openWB/set/counter/id/get/power** + - **Beschreibung**: Bezugsleistung in Watt, Zahl mit oder ohne Nachkommastellen (Float, Integer) und einem Punkt als Dezimaltrennzeichen, positiv für Bezug, negativ für Einspeisung. + - **Beispiel**: `-123.45` + +- **openWB/set/counter/id/get/imported** + - **Beschreibung**: Bezogene Energie in Wh, Zahl mit oder ohne Nachkommastellen (Float, Integer) und einem Punkt als Dezimaltrennzeichen, nur positiv. + - **Beispiel**: `123.45` + +- **openWB/set/counter/id/get/exported** + - **Beschreibung**: Eingespeiste Energie in Wh, Zahl mit oder ohne Nachkommastellen (Float, Integer) und einem Punkt als Dezimaltrennzeichen, nur positiv. + - **Beispiel**: `123.45` + +Ströme je Phase sind für phasenbasiertes Lastmanagement unbedingt erforderlich, sonst erfolgt das Lastmanagement ausschließlich auf Basis der Gesamtleistung am EVU-Punkt: +- **openWB/set/counter/id/get/currents** + - **Beschreibung**: Array mit den Strömen je Phase in Ampere, mit Nachkommastellen (Float), positiv für Bezug, negativ für Einspeisung. + - **Beispiel**: `[1.2,2.3,-2.1]` + +Die Netzfrequenz, Spannungen, Leistungen und Leistungsfaktoren jeder Phase werden ausschließlich zu Anzeigezwecken verwendet: +- **openWB/set/counter/id/get/frequency** + - **Beschreibung**: Netzfrequenz in Hz, Zahl mit oder ohne Nachkommastellen (Float, Integer) und einem Punkt als Dezimaltrennzeichen. + - **Beispiel**: `50.12` + +- **openWB/set/counter/id/get/voltages** + - **Beschreibung**: Array mit den Spannungen je Phase in Volt, mit Nachkommastellen (Float). + - **Beispiel**: `[222.2,223.3,222.3]` + +- **openWB/set/counter/id/get/powers** + - **Beschreibung**: Array mit den Leistungen je Phase in Watt, mit Nachkommastellen (Float). + - **Beispiel**: `[12.3,23.4,-12.3]` + +- **openWB/set/counter/id/get/power_factors** + - **Beschreibung**: Array mit den Leistungsfaktoren je Phase, mit Nachkommastellen (Float), Wertebereich -1 bis 1. + - **Beispiel**: `[0.95,0.96,-0.95]` + +## Huawei Wechselrichter mit DTSU666-H 250A und SDongle + +Huawei Wechselrichter werden, in der Betriebsart mit Aufzeichnung des Hausverbraucht mit dem _DTSU666-H 250A_ Stromzähler direkt am EVU-Punkt betrieben. Die Kommunikation zwischen Zähler und Wechselrichter findet über RS485 statt. Sofern der Wechselrichter mit dem optionalen SmartDongle FE ausgestattet ist, können über diesen Daten des Wechselrichter ausgelesen werden. +Die Schnittstelle am Dongle ist Modbus-TCP. Dies muss mit Installer-Account am Wechselrichter auf "Unrestrictet" gestellt werden, damit die Daten extern abgerufen werden können. + +Der Huawei-Wechselrichter kann direkt über die openWB ausgelesen werden. +Eine weitere Möglichkeit des Datenabrufs wird im [openWB-Forum](https://openwb.de/forum/viewtopic.php?t=7029) entwickelt und ist auf [Github](https://github.com/AlexanderMetzger/huawei_openwb_bridge) sowie der [Homepage des Entwicklers](https://lebensraum-wohnraum.de/openwb-kommunikation-mit-dem-huawei-wechselrichter-sun-2000/) zu finden. Hierbei wird das Image auf die SD-karte eines Raspberry-Zero gespiegelt und der Raspberry mit dem Config-WLAN des Wechselrichters verbunden. Die Skripte ziehen sich die entsprechenden Werte in Echtzeit vom Wechselrichter und publishen diese auf die [MQTT](#MQTT) Schnittstelle der openWB. Der Zähler in der openWB muss dementsprechend als MQTT-Zähler eingerichtet sein. + +### Solaranzeige + +[Solaranzeige](https://solaranzeige.de) ist ebenfalls ein OpenSource Projekt, welches der Visualisierung, Speicherung und Weiterverarbeitung von PV-Daten dient. +Dieses Projekt unterstützt aktuell (Stand 2024-02) mehr Wechselrichter als openWB. Somit können hier mit etwas Aufwand existierende Wechselrichter eingebunden und die Daten weitergereicht werden, ohne Arbeiten am Zählerschrank durchzuführen zu lassen. +Die Software ist originär dafür vorgesehen auf einen Raspberry per Image installiert zu werden und nach wenigen Konfigurationsschritten lauffähig zu sein. Es gibt auch schon Portierungen für [Docker](https://github.com/DeBaschdi/docker.solaranzeige). +Solaranzeige kann mit vielen Wechselrichtern kommunizieren und auch teilweise die angeschlossenen Zähler auslesen. Eine zeitbasierte Datenbank (InfluxDb), Datenweitergabe über einen MQTT-Client sowie eine Visualisierung mit Grafana sind direkt integriert. Es kann aber auch bereits existierende Infrastruktur verwendet werden. +In dem Projekt wird (mit Stand von 2021) auch die Möglichkeit dokumentiert die Daten direkt an openWB weiterzuleiten. Dann kann jedoch kein weiterer MQTT-Broker bedient werden. +Alternativ können die Zählerwerte an eine Hausautomationsserver weitergegeben, dort ggf. vorzeichenkorrigiert und dann über einen zweiten MQTT-Client zur openWB geschickt werden. + +## Virtuelle Zähler + +Virtuelle Zähler sind, wie der Name schon sagt, nicht physikalisch vorhanden. Sie dienen in der Struktur des [Lastmanagement](https://github.com/openWB/core/wiki/Lastmanagement-und-kaskadierte-Zähler) dazu, Ströme und/oder Leistung zu begrenzen oder Werte aus untergeordneten einzelnen Zählern zu akkumulieren. + +## Timing +Das zyklische Senden bzw. Bereitstellen der Zählerwerte ist für eine funktionierende Regelung essentiell. Unter + +> Einstellungen - Allgemein - Hardware + +kann die Regelgeschwindigkeit ausgewählt werden. Es gibt die Intervalle: + - Normal + - Langsam (20s) + - Sehr langsam (60s) + +Hier muss eine Regelgeschwindigkeit entsprechend der Aktualisierungsrate der Zählerwerte angegeben werden, da ansonsten die Rückmeldung des Systems zu spät kommt und die openWB weiter versucht nachzuregeln. +Insbesondere bei der Verwendung von [Solaranzeige](### Solaranzeige) ist aufgrund der 20-30s dauerenden Aktualisierungsrate die Regelungsgeschwindigkeit anzupassen. + diff --git "a/docs/Z\303\244hler.md" "b/docs/Z\303\244hler.md" deleted file mode 100644 index c1fdeef89a..0000000000 --- "a/docs/Z\303\244hler.md" +++ /dev/null @@ -1,93 +0,0 @@ - -# Zähler - -OpenWB benötigt zum erfolgreichen PV-Überschussladen die entsprechenden Zählerwerte am EVU-Punkt (EVU=Elektrizitätsversorgungsunternehmen), sprich dem Übergang ins öffentliche Netz. An dieser Stelle muss die Gesamtleistung saldierend erfasst werden. Für eine phasenbasierte Leistungsüberwachung sind auch die einzelnen Ströme und/oder Leistungen der drei Phasen notwendig. - -Im einfachsten Fall geschieht dies durch Kauf und Einbau eines [EVU-Kits](##EVU-Kit). Sollten schon digital auslesbare Zähler vorhanden sein, so besteht die Möglichkeit diese Werte an OpenWB weiterzuleiten, auch mit Hilfe von Hausautomationsservern. - -Es gibt viele verschiedene Möglichkeiten, Zähler als auch Wechselrichter in das OpenWB-System einzufügen. Die Struktur der Zähler muss dann im [Lastmanagement](https://github.com/openWB/core/wiki/Lastmanagement-und-kaskadierte-Zähler) dem System bekanntgegeben werden. Hier können auch [virtuelle Zähler](##Virtuelle Zähler) hinzugefügt werden, welche OpenWB-intern die untergeordneten Zähler verrechnen. - - -## EVU-Kit - -Das [EVU-Kit (Link zum Shop)](https://openwb.de/shop/?product=openwb-evu-kit) ist die einfachste Art in der Software einen Zähler an die OpenWB zu integrieren. Der Zähler muss von einem Elektriker direkt hinter dem Zähler des EVU in den Zählerschrank integriert werden. Es gibt, je nach Kapazität des Hausanschlusses, verschiedene Messvarianten des Zählers, welche sich von der Integration unterscheiden. Dies betrifft aber nur die Arbeit des Elektrikers. -Der Zähler kommuniziert mit der OpenWB über Ethernet. Die Kits sind so vorkonfiguriert, dass sie von der openWB Software automatisch gefunden werden, wenn ein entsprechendes Gerät mit Zählerkomponente angelegt wird. Es gibt auch für Wechselrichter und Speicher entsprechende Kits. - -![EVU-Kit](pictures/EVU-PV-Speicher-Kit-689x1024.png) - -## MQTT - -OpenWB hat einen MQTT-Broker integriert, welcher unter Port 1883 (ohne Verschlüsselung) und Port 8883 (mit Verschlüsselung) erreichbar ist.Benutzerauthentifizierung ist deaktiviert und auch nicht aktivierbar. Ein Zähler, welcher die benötigten Daten liefert muss sich mit diesem Broker verbinden und dort die Werte unter den entsprechenden Topics publishen. - -Folgende Werte können dem MQTT-Zähler übergeben werden (Die Zahl in den Topics, hier "2", wird dynamisch erzeugt und ist den eigenen Gegebenheiten anzupassen. Wir ein neuer Zähler erstellt, wird diese Zahl inkrementiert): -``` -openWB/set/counter/2/get/power -Bezugsleistung in Watt, Zahl mit oder ohne Nachkommastellen (Float, Integer) und einem Punkt als Dezimaltrennzeichen, positiv Bezug, negativ Einspeisung -Beispiel: -123.45 - -openWB/set/counter/2/get/imported -Bezogene Energie in Wh, Zahl mit oder ohne Nachkommastellen (Float, Integer) und einem Punkt als Dezimaltrennzeichen, nur positiv -Beispiel: 123.45 - -openWB/set/counter/2/get/exported -Eingespeiste Energie in Wh, Zahl mit oder ohne Nachkommastellen (Float, Integer) und einem Punkt als Dezimaltrennzeichen, nur positiv -Beispiel: 123.45 - -openWB/set/counter/2/get/frequency -Netzfrequenz in Hz, Zahl mit oder ohne Nachkommastellen (Float, Integer) und einem Punkt als Dezimaltrennzeichen -Beispiel: 50.12 - -openWB/set/counter/2/get/currents -Array mit den Strömen je Phase in Ampere, mit Nachkommastellen (Float), positiv Bezug, negativ Einspeisung -Beispiel: [1.2,2.3,-2.1] - -openWB/set/counter/2/get/voltages -Array mit den Spannungen je Phase in Volt, mit Nachkommastellen (Float) -Beispiel: [222.2,223.3,222.3] - -openWB/set/counter/2/get/powers -Array mit den Leistungen je Phase in Watt, mit Nachkommastellen (Float) -Beispiel: [12.3,23.4,-12.3] - -openWB/set/counter/2/get/power_factors -Array mit den Leistungsfaktoren je Phase, mit Nachkommastellen (Float), Wertebereich -1 bis 1 -Beispiel: [0.95,0.96,-0.95] -``` - -### Optionale Werte -1. Phasenströme: _openWB/set/counter/2/get/voltages_: Werden nur benötigt, wenn in _Ladeeinstellungen - Übergreifendes_ die Begrenzung der Schieflast aktiviert ist. - - -## Huawei Wechselrichter mit DTSU666-H 250A und SDongle - -Huawei Wechselrichter werden, in der Betriebsart mit Aufzeichnung des Hausverbraucht mit dem _DTSU666-H 250A_ Stromzähler direkt am EVU-Punkt betrieben. Die Kommunikation zwischen Zähler und Wechselrichter findet über RS485 statt. Sofern der Wechselrichter mit dem optionalen SmartDongle FE ausgestattet ist, können über diesen Daten des Wechselrichter ausgelesen werden. -Die Schnittstelle am Dongle ist Modbus-TCP. Dies muss mit Installer-Account am Wechselrichter auf "Unrestrictet" gestellt werden, damit die Daten extern abgerufen werden können. - -Eine Möglichkeit des Datenabrufs wird im [OpenWB-Forum](https://openwb.de/forum/viewtopic.php?t=7029) entwickelt und ist auf [Github](https://github.com/AlexanderMetzger/huawei_openwb_bridge) sowie der [Homepage des Entwicklers](https://lebensraum-wohnraum.de/openwb-kommunikation-mit-dem-huawei-wechselrichter-sun-2000/) zu finden. Hierbei wird das Image auf die SD-karte eines Raspberry-Zero gespiegelt und der Raspberry mit dem Config-WLAN des Wechselrichters verbunden. Die Skripte ziehen sich die entsprechenden Werte in Echtzeit vom Wechselrichter und publishen diese auf die [MQTT](#MQTT) Schnittstelle der OpenWB. Der Zähler in der OpenWB muss dementsprechend als MQTT-Zähler eingerichtet sein. - -### Solaranzeige - -[Solaranzeige](https://solaranzeige.de) ist ebenfalls ein OpenSource Projekt, welches der Visualisierung, Speicherung und Weiterverarbeitung von PV-Daten dient. -Dieses Projekt unterstützt aktuell (Stand 2024-02) mehr Wechselrichter als OpenWB. Somit können hier mit etwas Aufwand existierende Wechselrichter eingebunden und die Daten weitergereicht werden, ohne Arbeiten am Zählerschrank durchzuführen zu lassen. -Die Software ist originär dafür vorgesehen auf einen Raspberry per Image installiert zu werden und nach wenigen Konfigurationsschritten lauffähig zu sein. Es gibt auch schon Portierungen für [Docker](https://github.com/DeBaschdi/docker.solaranzeige). -Solaranzeige kann mit vielen Wechselrichtern kommunizieren und auch teilweise die angeschlossenen Zähler auslesen. Eine zeitbasierte Datenbank (InfluxDb), Datenweitergabe über einen MQTT-Client sowie eine Visualisierung mit Grafana sind direkt integriert. Es kann aber auch bereits existierende Infrastruktur verwendet werden. -In dem Projekt wird (mit Stand von 2021) auch die Möglichkeit dokumentiert die Daten direkt an OpenWB weiterzuleiten. Dann kann jedoch kein weiterer MQTT-Broker bedient werden. -Alternativ können die Zählerwerte an eine Hausautomationsserver weitergegeben, dort ggf. vorzeichenkorrigiert und dann über einen zweiten MQTT-Client zur OpenWB geschickt werden. - -## Virtuelle Zähler - -Virtuelle Zähler sind, wie der Name schon sagt, nicht physikalisch vorhanden. Sie dienen in der Struktur des [Lastmanagement](https://github.com/openWB/core/wiki/Lastmanagement-und-kaskadierte-Zähler) dazu, Ströme und/oder Leistung zu begrenzen oder Werte aus untergeordneten einzelnen Zählern zu akkumulieren. - -## Timing -Das zyklische Senden bzw. Bereitstellen der Zählerwerte ist für eine funktionierende Regelung essentiell. Unter - -> Einstellungen - Allgemein - Hardware - -kann die Regelgeschwindigkeit ausgewählt werden. Es gibt die Intervalle: - - Normal - - Langsam (20s) - - Sehr langsam (60s) - -Hier muss eine Regelgeschwindigkeit entsprechend der Aktualisierungsrate der Zählerwerte angegeben werden, da ansonsten die Rückmeldung des Systems zu spät kommt und die OpenWB weiter versucht nachzuregeln. -Insbesondere bei der unter [Solaranzeige](### Solaranzeige) ist aufgrund der 20-30s dauerenden Aktualisierungsrate die Regelungsgeschwindigkeit anzupassen. - diff --git a/docs/_Sidebar.md b/docs/_Sidebar.md index 21f16c1d32..46a5e1bd78 100644 --- a/docs/_Sidebar.md +++ b/docs/_Sidebar.md @@ -4,19 +4,27 @@ * [Grundkonzept](https://github.com/openWB/core/wiki/Grundkonzept) * [Ladepunkte](https://github.com/openWB/core/wiki/Ladepunkte) * [Fahrzeuge](https://github.com/openWB/core/wiki/Fahrzeuge) -* [Zähler](https://github.com/openWB/core/wiki/Zaehler) +* Zähler + * [Grundsätzliches zu Zählern](https://github.com/openWB/core/wiki/Zaehler) * [Lastmanagement und kaskadierte Zähler](https://github.com/openWB/core/wiki/Lastmanagement-und-kaskadierte-Zähler) * [Hierarchie mit Hausverbrauchs-Zähler](https://github.com/openWB/core/wiki/Hausverbrauchs-Zähler) +* Oberfläche + * [Anzeige - Steuerung](https://github.com/openWB/core/wiki/Anzeige-Steuerung) * Szenarien + * [Typische Anwendungsfälle](https://github.com/openWB/core/wiki/Typische-Anwendungsfaelle) * [ID-Tag/Ladung nur nach Freischaltung](https://github.com/openWB/core/wiki/Ladung-nur-nach-Freischaltung) * [Hybrid-System aus Wechselrichter und Speicher](https://github.com/openWB/core/wiki/Hybrid-System-aus-Wechselrichter-und-Speicher) +* Integration in Heimautomation + * [MQTT](https://github.com/openWB/core/wiki/MQTT) * Mitarbeit am Projekt * [Wiki-Eintrag erstellen](https://github.com/openWB/core/wiki/Wiki-Eintrag_erstellen) * [Entwicklungsumgebung](https://github.com/openWB/core/wiki/Entwicklungsumgebung) - * [Neues Gerät programmieren](https://github.com/openWB/core/wiki/Neues-Gerät-programmieren) - * [Neues SoC-Modul programmieren](https://github.com/openWB/core/wiki/Neues-Soc-Modul-programmieren) + * [Neues Modul programmieren](https://github.com/openWB/core/wiki/Neues-Modul-programmieren) * [Einstellungs-Seite erstellen](https://github.com/openWB/core/wiki/Einstellungs-Seite-erstellen) * Konfiguration + * [Huawei Smartlogger 3000a](https://github.com/openWB/core/wiki/Huawei-Smartlogger) * [Cloud-Sicherung](https://github.com/openWB/core/wiki/Cloud-Sicherung) * [NextCloud](https://github.com/openWB/core/wiki/NextCloud-als-Sicherungs-Cloud-einrichten) * [Samba](https://github.com/openWB/core/wiki/Samba-als-Sicherung-einrichten) +* Sonstiges + * [Fehlersuche](https://github.com/openWB/core/wiki/Fehlersuche) diff --git a/docs/pictures/Anwendungsfaelle_minStrom.jpg b/docs/pictures/Anwendungsfaelle_minStrom.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9ec32a627c3700f34c65c5389ff8b4e41dc12bff GIT binary patch literal 37093 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 RbN|^e`Tw7L=l?SGe*mo?Lz@5q literal 0 HcmV?d00001 diff --git a/docs/pictures/Anwendungsfaelle_zielladen.jpg b/docs/pictures/Anwendungsfaelle_zielladen.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2fadb95194148f642ca65c4416130602a2331598 GIT binary patch literal 67535 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�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 ZL-{v(8b6{-tZu0pI)niLN;!wa{{d|bADjRH literal 0 HcmV?d00001 diff --git a/docs/pictures/Fehlersuche_Main-Log.jpg b/docs/pictures/Fehlersuche_Main-Log.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9f07623e51544a437b15e048ea558cfe925ac775 GIT binary patch literal 8924 zcmeHM2~bm6mwpIA7Td5JP&U~&Q3G*di3AA*hzJ-kAS$wBf{KU)1X>Lu7(@aD0fA-- zkO0yW76pO-4=o@FD2q+V4@hG~kjTCZnb`8zOm)vx&rD4@GgZ7-_1>%d-Fwb=&pqe6 zuioSJ^WFgxwn!8b5D)+W0saZ_*nlMgtu=O77>#W6B88`li3O0A+bva z0@)=aC#RsOp{%f5ZI7IsioS}Prk0Mbj=ZwLegp0O8rnMAUyTTC-@aW;L`+&tOj=t( zPC@&>eDMATNQwZNLbn73_5#}^1q3Apc)tL9_*D611-}yBJV4up1q6k*iSSW-2|!?* zAP6KRv_nV`B>J_3AV^@Fkfa<|>-&p=QTfCC$}P1gUnZUQgDF%fhxRyrm(TZdjTt2Tsy zAx|Tv(IY&-Act^w$Zrzg11l`78}Ip$s@#ZL4TNABv3DBIVy2*rhsQIW9SJ#4TI>fh z$N~?_Hy`i4TEBnPx3~8J@SkWA2SsdlKkGMnh3~51N0mPf6O#R-H{e$Nho}}sXkeh> zHy%#MBwU|&^^X?f(N>#?%{H;6^tC-99kk+21O-@}or8PbPp>lG^$nq^i0JX>_|aZIAThOM+QYjS}TL zA<}_9GQK?IK$FX{xmUza@(Z)$0aA^1i6ip4QW5mbz#hY(hBB@?Ula)nshVjsMBI_u z40=4(q>b8#b@*b7mLFY7Re+_k=$FPX%A^}=7ZeOW(~Ny1@AWQh?zcUcZd`r(#V=;6 zh0(*;$Qr&}{!=3>Ck8Dm(HQU!8w7=(tynuCdLtJ+r@q?oFarLmMR8}&J~R8LpF&pC zdq*EVo$~pUR{HJ!lMw$$3&rM3Bf9R5-JtV_SFg%$%s8HMO;?&dPz^;$P|V?T*d^sI z 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 literal 0 HcmV?d00001 diff --git a/docs/pictures/MQTT_Konsole_PV-Laden.jpg b/docs/pictures/MQTT_Konsole_PV-Laden.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d192967f5fdd1f417c39d3871a8e3b2e20c17e1d GIT binary patch literal 77540 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 literal 0 HcmV?d00001 diff --git a/docs/pictures/MQTT_Konsole_Sofortladen.jpg b/docs/pictures/MQTT_Konsole_Sofortladen.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b4cf3e3523bd96f9648eaf2b093390227caaf16c GIT binary patch literal 86001 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

7b^c#8 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

8p6NvWY!2ePn;Jya~BIASRt{HK1o}PC@ckQ9O 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

qDOOjmaRCjt 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 literal 0 HcmV?d00001 diff --git a/docs/pictures/Wiki-Eintrag erstellen_Pull.jpg b/docs/pictures/Wiki-Eintrag erstellen_Pull.jpg deleted file mode 100644 index c3e6c8ccb7dced0372d80a72d965a22125ec3487..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 65001 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¨icFO2ZY)aQ)X&94%ZcVCO8VcwOdeJ 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 F_zxh8XW{?= diff --git a/docs/pictures/Wiki-Eintrag erstellen_Fork.png b/docs/pictures/Wiki-Eintrag_erstellen_Fork.png similarity index 100% rename from docs/pictures/Wiki-Eintrag erstellen_Fork.png rename to docs/pictures/Wiki-Eintrag_erstellen_Fork.png diff --git a/docs/pictures/Wiki-Eintrag_erstellen_Pull.jpg b/docs/pictures/Wiki-Eintrag_erstellen_Pull.jpg new file mode 100644 index 0000000000000000000000000000000000000000..96d83ea5d8db3c3b8465b8cb545c38f569cb333b GIT binary patch literal 59553 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 literal 0 HcmV?d00001 diff --git a/docs/samples/sample_electricity_tariff/tariff.py b/docs/samples/sample_electricity_tariff/tariff.py index bebf1f5778..ea23689b6d 100644 --- a/docs/samples/sample_electricity_tariff/tariff.py +++ b/docs/samples/sample_electricity_tariff/tariff.py @@ -19,7 +19,7 @@ def fetch(config: SampleTariffConfiguration) -> None: def create_electricity_tariff(config: SampleTariff): def updater(): return fetch(config.configuration) - return ConfigurableTariff(config=config, component_updater=updater) + return updater device_descriptor = DeviceDescriptor(configuration_factory=SampleTariff) diff --git a/docs/samples/sample_modbus/device.py b/docs/samples/sample_modbus/device.py index 70b4372277..22b1957857 100644 --- a/docs/samples/sample_modbus/device.py +++ b/docs/samples/sample_modbus/device.py @@ -33,7 +33,7 @@ def update_components(components: Iterable[Union[SampleBat, SampleCounter, Sampl component.update(c) try: - client = ModbusTcpClient_(device_config.configuration.ip_address, port) + client = ModbusTcpClient_(device_config.configuration.ip_address, device_config.configuration.port) except Exception: log.exception("Fehler in create_device") return ConfigurableDevice( diff --git a/openwb-install.sh b/openwb-install.sh index 0de5f6de49..7933f35249 100755 --- a/openwb-install.sh +++ b/openwb-install.sh @@ -40,11 +40,11 @@ else fi echo -n "check for ramdisk... " -if grep -Fxq "tmpfs ${OPENWBBASEDIR}/ramdisk tmpfs nodev,nosuid,size=32M 0 0" /etc/fstab; then +if grep -Fq "tmpfs ${OPENWBBASEDIR}/ramdisk" /etc/fstab; then echo "ok" else mkdir -p "${OPENWBBASEDIR}/ramdisk" - echo "tmpfs ${OPENWBBASEDIR}/ramdisk tmpfs nodev,nosuid,size=32M 0 0" >> /etc/fstab + sudo tee -a "/etc/fstab" <"${OPENWBBASEDIR}/data/config/ramdisk_config.txt" >/dev/null mount -a echo "created" fi diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000000..9e36453c2a --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "core", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/packages/conftest.py b/packages/conftest.py index a2700ddc9d..11bb3faba2 100644 --- a/packages/conftest.py +++ b/packages/conftest.py @@ -134,3 +134,43 @@ def data_() -> None: imported=14000, exported=18000), config=Mock(spec=CounterConfig, max_currents=[32]*3), set=Mock(spec=CounterSet, raw_currents_left=[31]*3)))}) + + +def hierarchy_hc_counter() -> CounterAll: + # counter0 + # | + # - counter6 + # | + # - cp3 + # - inverter1 + # - bat2 + c = CounterAll() + c.data.get.hierarchy = [{"id": 0, "type": "counter", + "children": [ + {"id": 6, "type": "counter", + "children": [ + {"id": 3, "type": "cp", "children": []}]}, + {"id": 1, "type": "inverter", "children": []}, + {"id": 2, "type": "bat", "children": []}]}] + return c + + +@pytest.fixture() +def data_hc_counter_() -> None: + data.data_init(Mock()) + data.data.cp_data = { + "cp3": Mock(spec=Chargepoint, data=Mock(spec=ChargepointData, + config=Mock(spec=Config, phase_1=1), + get=Mock(spec=Get, currents=[30, 0, 0], power=6900, + daily_imported=10000, daily_exported=0, imported=56000), + set=Mock(spec=Set, loadmanagement_available=True)))} + data.data.pv_data.update({"pv1": Mock(spec=Pv, data=Mock( + spec=PvData, get=Mock(spec=PvGet, power=-10000, daily_exported=6000, exported=27000, currents=None)))}) + data.data.counter_data.update({ + "counter0": Mock(spec=Counter, data=Mock(spec=CounterData, get=Mock( + spec=CounterGet, currents=[40]*3, power=-2000, daily_imported=45000, daily_exported=3000))), + "counter6": Mock(spec=Counter, data=Mock(spec=CounterData, get=Mock( + spec=CounterGet, currents=[25, 10, 25], power=8000, daily_imported=20000, daily_exported=0, + imported=14000, exported=18000), + config=Mock(spec=CounterConfig, max_currents=[32]*3), + set=Mock(spec=CounterSet, raw_currents_left=[31]*3)))}) diff --git a/packages/control/algorithm/additional_current.py b/packages/control/algorithm/additional_current.py index 0203122ed8..4b5e6ddc1e 100644 --- a/packages/control/algorithm/additional_current.py +++ b/packages/control/algorithm/additional_current.py @@ -1,5 +1,4 @@ import logging -from typing import List from control.algorithm import common from control.loadmanagement import LimitingValue, Loadmanagement @@ -13,12 +12,14 @@ class AdditionalCurrent: + CONSIDERED_CHARGE_MODES = common.CHARGEMODES[0:8] + def __init__(self) -> None: pass - def set_additional_current(self, mode_range: List[int]) -> None: - common.reset_current_by_chargemode(common.CHARGEMODES[0:8]) - for mode_tuple, counter in common.mode_and_counter_generator(mode_range): + def set_additional_current(self) -> None: + common.reset_current_by_chargemode(self.CONSIDERED_CHARGE_MODES) + for mode_tuple, counter in common.mode_and_counter_generator(self.CONSIDERED_CHARGE_MODES): preferenced_chargepoints, preferenced_cps_without_set_current = get_preferenced_chargepoint_charging( get_chargepoints_by_mode_and_counter(mode_tuple, f"counter{counter.num}")) if preferenced_chargepoints: diff --git a/packages/control/algorithm/algorithm.py b/packages/control/algorithm/algorithm.py index 72d46f8360..d795583339 100644 --- a/packages/control/algorithm/algorithm.py +++ b/packages/control/algorithm/algorithm.py @@ -31,14 +31,14 @@ def calc_current(self) -> None: self.min_current.set_min_current() log.info("**Sollstrom setzen**") common.reset_current_to_target_current() - self.additional_current.set_additional_current([0, 8]) + self.additional_current.set_additional_current() counter.limit_raw_power_left_to_surplus(self.evu_counter.calc_raw_surplus()) self.surplus_controlled.check_switch_on() if self.evu_counter.data.set.surplus_power_left > 0: log.info("**PV-geführten Strom setzen**") common.reset_current_to_target_current() self.surplus_controlled.set_required_current_to_max() - self.surplus_controlled.set_surplus_current([6, 12]) + self.surplus_controlled.set_surplus_current() else: log.info("**Keine Leistung für PV-geführtes Laden übrig.**") self.no_current.set_no_current() diff --git a/packages/control/algorithm/common.py b/packages/control/algorithm/common.py index 280ce0780a..9df5b2b9c6 100644 --- a/packages/control/algorithm/common.py +++ b/packages/control/algorithm/common.py @@ -47,13 +47,8 @@ def reset_current_by_chargemode(mode_tuple: Tuple[Optional[str], str, bool]) -> cp.data.set.current = None -def mode_range_list_factory() -> List[int]: - return [0, -1] - - -def mode_and_counter_generator( - mode_range: List[int] = mode_range_list_factory()) -> Iterable[Tuple[Tuple[Optional[str], str, bool], Counter]]: - for mode_tuple in CHARGEMODES[mode_range[0]: mode_range[1]]: +def mode_and_counter_generator(chargemodes: List) -> Iterable[Tuple[Tuple[Optional[str], str, bool], Counter]]: + for mode_tuple in chargemodes: levels = data.data.counter_all_data.get_list_of_elements_per_level() for level in reversed(levels): for element in level: diff --git a/packages/control/algorithm/filter_chargepoints.py b/packages/control/algorithm/filter_chargepoints.py index a53971bd6f..c87a86fa62 100644 --- a/packages/control/algorithm/filter_chargepoints.py +++ b/packages/control/algorithm/filter_chargepoints.py @@ -3,7 +3,6 @@ from typing import List, Optional, Tuple from control import data -from control.algorithm import common from control.chargepoint.chargepoint import Chargepoint log = logging.getLogger(__name__) @@ -54,19 +53,6 @@ def get_preferenced_chargepoint_charging( return preferenced_chargepoints_with_set_current, preferenced_chargepoints_without_set_current -def get_chargepoints_pv_charging() -> List[Chargepoint]: - chargepoints: List[Chargepoint] = [] - for mode in common.CHARGEMODES[8: 12]: - chargepoints.extend(get_chargepoints_by_mode(mode)) - return chargepoints - - -def get_chargepoints_surplus_controlled() -> List[Chargepoint]: - chargepoints: List[Chargepoint] = [] - for mode in common.CHARGEMODES[6: 12]: - chargepoints.extend(get_chargepoints_by_mode(mode)) - return chargepoints - # tested diff --git a/packages/control/algorithm/filter_chargepoints_test.py b/packages/control/algorithm/filter_chargepoints_test.py index 3debfd7340..f278afcf4f 100644 --- a/packages/control/algorithm/filter_chargepoints_test.py +++ b/packages/control/algorithm/filter_chargepoints_test.py @@ -163,28 +163,3 @@ def test_get_chargepoints_by_mode_and_counter(chargepoints_of_counter: List[str] # assertion assert valid_chargepoints == expected_chargepoints - - -@pytest.mark.parametrize( - "submode_1, submode_2, expected_chargepoints", - [ - pytest.param(Chargemode.PV_CHARGING, Chargemode.PV_CHARGING, [mock_cp2, mock_cp1]), - pytest.param(Chargemode.SCHEDULED_CHARGING, Chargemode.PV_CHARGING, [mock_cp2]), - pytest.param(Chargemode.SCHEDULED_CHARGING, Chargemode.INSTANT_CHARGING, []), - ]) -def test_get_chargepoints_submode_pv_charging(submode_1: Chargemode, - submode_2: Chargemode, - expected_chargepoints: List[Chargepoint]): - # setup - def setup_cp(cp: Chargepoint, submode: str) -> Chargepoint: - cp.data.set.charging_ev = Ev(0) - cp.data.control_parameter.submode = submode - return cp - data.data.cp_data = {"cp1": setup_cp(mock_cp1, submode_1), - "cp2": setup_cp(mock_cp2, submode_2)} - - # evaluation - chargepoints = filter_chargepoints.get_chargepoints_pv_charging() - - # assertion - assert chargepoints == expected_chargepoints diff --git a/packages/control/algorithm/integration_test/conftest.py b/packages/control/algorithm/integration_test/conftest.py index 98923f300e..ac2f44d16f 100644 --- a/packages/control/algorithm/integration_test/conftest.py +++ b/packages/control/algorithm/integration_test/conftest.py @@ -26,6 +26,7 @@ def data_() -> None: data.data.cp_data[f"cp{i}"].data.config.phase_1 = i-2 data.data.cp_data[f"cp{i}"].data.set.charging_ev = i data.data.cp_data[f"cp{i}"].data.set.charging_ev_data = Ev(i) + data.data.cp_data[f"cp{i}"].data.set.charging_ev_data.ev_template.data.max_current_single_phase = 32 data.data.cp_data[f"cp{i}"].data.get.plug_state = True data.data.cp_data[f"cp{i}"].data.set.plug_time = f"12/01/2022, 15:0{i}:11" data.data.cp_data[f"cp{i}"].data.set.charging_ev_data.ev_template.data.nominal_difference = 2 diff --git a/packages/control/algorithm/min_current.py b/packages/control/algorithm/min_current.py index f373789180..ac5af0f213 100644 --- a/packages/control/algorithm/min_current.py +++ b/packages/control/algorithm/min_current.py @@ -8,11 +8,13 @@ class MinCurrent: + CONSIDERED_CHARGE_MODES = common.CHARGEMODES[0:-1] + def __init__(self) -> None: pass def set_min_current(self) -> None: - for mode_tuple, counter in common.mode_and_counter_generator(): + for mode_tuple, counter in common.mode_and_counter_generator(self.CONSIDERED_CHARGE_MODES): preferenced_chargepoints = get_chargepoints_by_mode_and_counter(mode_tuple, f"counter{counter.num}") if preferenced_chargepoints: log.info(f"Mode-Tuple {mode_tuple[0]} - {mode_tuple[1]} - {mode_tuple[2]}, Zähler {counter.num}") diff --git a/packages/control/algorithm/surplus_controlled.py b/packages/control/algorithm/surplus_controlled.py index 9a7e7ca5d4..85c8fcda98 100644 --- a/packages/control/algorithm/surplus_controlled.py +++ b/packages/control/algorithm/surplus_controlled.py @@ -6,22 +6,25 @@ from control.loadmanagement import LimitingValue, Loadmanagement from control.counter import Counter from control.chargepoint.chargepoint import Chargepoint -from control.algorithm.filter_chargepoints import (get_chargepoints_by_mode_and_counter, - get_preferenced_chargepoint_charging, get_chargepoints_pv_charging, - get_chargepoints_surplus_controlled) +from control.algorithm.filter_chargepoints import (get_chargepoints_by_mode, get_chargepoints_by_mode_and_counter, + get_preferenced_chargepoint_charging) from control.chargepoint.chargepoint_state import ChargepointState, CHARGING_STATES from modules.common.utils.component_parser import get_component_name_by_id log = logging.getLogger(__name__) +CONSIDERED_CHARGE_MODES = common.CHARGEMODES[0:2] + common.CHARGEMODES[6:12] +CONSIDERED_CHARGE_MODES_PV = common.CHARGEMODES[8:12] + class SurplusControlled: + def __init__(self) -> None: pass - def set_surplus_current(self, mode_range) -> None: - common.reset_current_by_chargemode(common.CHARGEMODES[6:12]) - for mode_tuple, counter in common.mode_and_counter_generator(mode_range): + def set_surplus_current(self) -> None: + common.reset_current_by_chargemode(CONSIDERED_CHARGE_MODES) + for mode_tuple, counter in common.mode_and_counter_generator(CONSIDERED_CHARGE_MODES): preferenced_chargepoints, preferenced_cps_without_set_current = get_preferenced_chargepoint_charging( get_chargepoints_by_mode_and_counter(mode_tuple, f"counter{counter.num}")) cp_with_feed_in, cp_without_feed_in = self.filter_by_feed_in_limit(preferenced_chargepoints) @@ -127,13 +130,14 @@ def check_submode_pv_charging(self) -> None: def phase_switch_necessary() -> bool: return cp.cp_ev_chargemode_support_phase_switch() and cp.data.get.phases_in_use != 1 control_parameter = cp.data.control_parameter - if cp.data.set.charging_ev_data.chargemode_changed: + if cp.data.set.charging_ev_data.chargemode_changed or cp.data.set.charging_ev_data.submode_changed: if control_parameter.state == ChargepointState.CHARGING_ALLOWED: if (cp.data.set.charging_ev_data.ev_template.data.prevent_charge_stop is False and phase_switch_necessary() is False): threshold = evu_counter.calc_switch_off_threshold(cp)[0] if evu_counter.calc_raw_surplus() - cp.data.set.required_power < threshold: control_parameter.required_currents = [0]*3 + control_parameter.state = ChargepointState.NO_CHARGING_ALLOWED else: control_parameter.required_currents = [0]*3 else: @@ -168,3 +172,17 @@ def set_required_current_to_max(self) -> None: control_parameter.required_currents = [max_current if required_currents[i] != 0 else 0 for i in range(3)] control_parameter.required_current = max_current + + +def get_chargepoints_pv_charging() -> List[Chargepoint]: + chargepoints: List[Chargepoint] = [] + for mode in CONSIDERED_CHARGE_MODES_PV: + chargepoints.extend(get_chargepoints_by_mode(mode)) + return chargepoints + + +def get_chargepoints_surplus_controlled() -> List[Chargepoint]: + chargepoints: List[Chargepoint] = [] + for mode in CONSIDERED_CHARGE_MODES: + chargepoints.extend(get_chargepoints_by_mode(mode)) + return chargepoints diff --git a/packages/control/algorithm/surplus_controlled_test.py b/packages/control/algorithm/surplus_controlled_test.py index 006727fb4e..a364bdd460 100644 --- a/packages/control/algorithm/surplus_controlled_test.py +++ b/packages/control/algorithm/surplus_controlled_test.py @@ -2,8 +2,10 @@ from unittest.mock import Mock import pytest +from control import data from control.algorithm import surplus_controlled -from control.algorithm.surplus_controlled import SurplusControlled +from control.algorithm.surplus_controlled import SurplusControlled, get_chargepoints_pv_charging +from control.chargemode import Chargemode from control.chargepoint.chargepoint import Chargepoint, ChargepointData from control.chargepoint.chargepoint_data import Get, Set from control.chargepoint.control_parameter import ControlParameter @@ -70,8 +72,8 @@ def test_limit_adjust_current(new_current: float, expected_current: float, monke @pytest.mark.parametrize("phases, required_currents, expected_currents", [ - pytest.param(1, [10, 0, 0], [32, 0, 0]), - pytest.param(1, [0, 15, 0], [0, 32, 0]), + pytest.param(1, [10, 0, 0], [16, 0, 0]), + pytest.param(1, [0, 15, 0], [0, 16, 0]), pytest.param(3, [10]*3, [16]*3), ]) def test_set_required_current_to_max(phases: int, @@ -115,3 +117,29 @@ def test_add_unused_evse_current(evse_current: float, limited_current: float, ex # evaluation assert current == expected_current + + +@pytest.mark.parametrize( + "submode_1, submode_2, expected_chargepoints", + [ + pytest.param(Chargemode.PV_CHARGING, Chargemode.PV_CHARGING, [mock_cp1, mock_cp2]), + pytest.param(Chargemode.INSTANT_CHARGING, Chargemode.PV_CHARGING, [mock_cp2]), + pytest.param(Chargemode.INSTANT_CHARGING, Chargemode.INSTANT_CHARGING, []), + ]) +def test_get_chargepoints_submode_pv_charging(submode_1: Chargemode, + submode_2: Chargemode, + expected_chargepoints: List[Chargepoint]): + # setup + def setup_cp(cp: Chargepoint, submode: str) -> Chargepoint: + cp.data.set.charging_ev = Ev(0) + cp.data.control_parameter.chargemode = Chargemode.PV_CHARGING + cp.data.control_parameter.submode = submode + return cp + data.data.cp_data = {"cp1": setup_cp(mock_cp1, submode_1), + "cp2": setup_cp(mock_cp2, submode_2)} + + # evaluation + chargepoints = get_chargepoints_pv_charging() + + # assertion + assert chargepoints == expected_chargepoints diff --git a/packages/control/auto_phase_switch_test.py b/packages/control/auto_phase_switch_test.py index 97aa74456c..c206d33087 100644 --- a/packages/control/auto_phase_switch_test.py +++ b/packages/control/auto_phase_switch_test.py @@ -35,7 +35,6 @@ def __init__(self, phases_to_use: int, required_current: float, evu_surplus: int, - reserved_evu_overhang: int, get_currents: List[float], get_power: float, state: ChargepointState, @@ -50,7 +49,6 @@ def __init__(self, self.phases_to_use = phases_to_use self.required_current = required_current self.available_power = evu_surplus - self.reserved_evu_overhang = reserved_evu_overhang self.get_currents = get_currents self.get_power = get_power self.state = state @@ -63,60 +61,60 @@ def __init__(self, cases = [ Params("1to3, enough power, start timer", max_current_single_phase=16, timestamp_auto_phase_switch=None, - phases_to_use=1, required_current=6, evu_surplus=-800, reserved_evu_overhang=0, get_currents=[15.6, 0, 0], + phases_to_use=1, required_current=6, evu_surplus=800, get_currents=[15.6, 0, 0], get_power=3450, state=ChargepointState.CHARGING_ALLOWED, expected_phases_to_use=1, expected_current=6, - expected_message=Ev.PHASE_SWITCH_DELAY_TEXT.format("Umschaltung von 1 auf 3", "7 Min. 0 Sek."), + expected_message=Ev.PHASE_SWITCH_DELAY_TEXT.format("Umschaltung von 1 auf 3", "7 Min."), expected_timestamp_auto_phase_switch=1652683252.0, expected_state=ChargepointState.PHASE_SWITCH_DELAY), Params("1to3, not enough power, start timer", max_current_single_phase=16, timestamp_auto_phase_switch=None, - phases_to_use=1, required_current=6, evu_surplus=-300, reserved_evu_overhang=0, get_currents=[15.6, 0, 0], + phases_to_use=1, required_current=6, evu_surplus=300, get_currents=[15.6, 0, 0], get_power=3450, state=ChargepointState.CHARGING_ALLOWED, expected_phases_to_use=1, expected_current=6, expected_state=ChargepointState.CHARGING_ALLOWED), Params("1to3, enough power, timer not expired", max_current_single_phase=16, timestamp_auto_phase_switch=1652682952.0, phases_to_use=1, required_current=6, - evu_surplus=-1200, reserved_evu_overhang=460, get_currents=[15.6, 0, 0], get_power=3450, + evu_surplus=1460, get_currents=[15.6, 0, 0], get_power=3450, state=ChargepointState.PHASE_SWITCH_DELAY, expected_phases_to_use=1, expected_current=6, - expected_message=Ev.PHASE_SWITCH_DELAY_TEXT.format("Umschaltung von 1 auf 3", "2 Min. 0 Sek."), + expected_message=Ev.PHASE_SWITCH_DELAY_TEXT.format("Umschaltung von 1 auf 3", "2 Min."), expected_timestamp_auto_phase_switch=1652683252.0, expected_state=ChargepointState.PHASE_SWITCH_DELAY), Params("1to3, not enough power, timer not expired", max_current_single_phase=16, timestamp_auto_phase_switch=1652682952.0, phases_to_use=1, required_current=6, - evu_surplus=0, reserved_evu_overhang=460, get_currents=[15.6, 0, 0], get_power=3450, + evu_surplus=460, get_currents=[15.6, 0, 0], get_power=3450, state=ChargepointState.PHASE_SWITCH_DELAY, expected_phases_to_use=1, expected_current=6, expected_message=f"Verzögerung für die Umschaltung von 1 auf 3 Phasen abgebrochen{Ev.NOT_ENOUGH_POWER}", expected_timestamp_auto_phase_switch=1652683252.0, expected_state=ChargepointState.CHARGING_ALLOWED), Params("1to3, enough power, timer expired", max_current_single_phase=16, timestamp_auto_phase_switch=1652682772.0, phases_to_use=1, required_current=6, - evu_surplus=-1200, reserved_evu_overhang=460, get_currents=[15.6, 0, 0], get_power=3450, + evu_surplus=1640, get_currents=[15.6, 0, 0], get_power=3450, state=ChargepointState.PHASE_SWITCH_DELAY, expected_phases_to_use=3, expected_current=6, expected_state=ChargepointState.PHASE_SWITCH_DELAY_EXPIRED), Params("3to1, not enough power, start timer", max_current_single_phase=16, timestamp_auto_phase_switch=None, - phases_to_use=3, required_current=6, evu_surplus=0, reserved_evu_overhang=0, + phases_to_use=3, required_current=6, evu_surplus=0, get_currents=[4.5, 4.4, 5.8], get_power=3381, state=ChargepointState.CHARGING_ALLOWED, expected_phases_to_use=3, expected_current=6, - expected_message="Umschaltung von 3 auf 1 Phasen in 9 Min. 0 Sek..", + expected_message="Umschaltung von 3 auf 1 Phasen in 9 Min..", expected_timestamp_auto_phase_switch=1652683252.0, expected_state=ChargepointState.PHASE_SWITCH_DELAY), Params("3to1, not enough power, timer not expired", max_current_single_phase=16, timestamp_auto_phase_switch=1652682952.0, - phases_to_use=3, required_current=6, evu_surplus=0, reserved_evu_overhang=-460, + phases_to_use=3, required_current=6, evu_surplus=-460, get_currents=[4.5, 4.4, 5.8], get_power=3381, state=ChargepointState.PHASE_SWITCH_DELAY, expected_phases_to_use=3, expected_current=6, - expected_message="Umschaltung von 3 auf 1 Phasen in 4 Min. 0 Sek..", + expected_message="Umschaltung von 3 auf 1 Phasen in 4 Min..", expected_timestamp_auto_phase_switch=1652683252.0, expected_state=ChargepointState.PHASE_SWITCH_DELAY), Params("3to1, enough power, timer not expired", max_current_single_phase=16, timestamp_auto_phase_switch=1652682952.0, phases_to_use=3, required_current=6, - evu_surplus=-860, reserved_evu_overhang=0, get_currents=[4.5, 4.4, 5.8], + evu_surplus=860, get_currents=[4.5, 4.4, 5.8], get_power=3381, state=ChargepointState.PHASE_SWITCH_DELAY, expected_phases_to_use=3, expected_current=6, expected_message=f"Verzögerung für die Umschaltung von 3 auf 1 Phasen abgebrochen{Ev.ENOUGH_POWER}", expected_timestamp_auto_phase_switch=1652683252.0, expected_state=ChargepointState.CHARGING_ALLOWED), Params("3to1, not enough power, timer expired", max_current_single_phase=16, timestamp_auto_phase_switch=1652682592.0, phases_to_use=3, required_current=6, - evu_surplus=0, reserved_evu_overhang=-460, get_currents=[4.5, 4.4, 5.8], + evu_surplus=-460, get_currents=[4.5, 4.4, 5.8], get_power=3381, state=ChargepointState.PHASE_SWITCH_DELAY, expected_phases_to_use=1, expected_current=16, expected_state=ChargepointState.PHASE_SWITCH_DELAY_EXPIRED), ] @@ -126,12 +124,12 @@ def __init__(self, def test_auto_phase_switch(monkeypatch, vehicle: Ev, params: Params): # setup mock_evu = Mock(spec=Counter, data=Mock(spec=CounterData, - set=Mock(spec=Set, reserved_surplus=params.reserved_evu_overhang, + set=Mock(spec=Set, reserved_surplus=0, released_surplus=0))) mock_get_evu_counter = Mock(name="power_for_bat_charging", return_value=mock_evu) monkeypatch.setattr(data.data.counter_all_data, "get_evu_counter", mock_get_evu_counter) mock_evu_counter_surplus = Mock(return_value=params.available_power) - monkeypatch.setattr(mock_evu, "calc_surplus", mock_evu_counter_surplus) + monkeypatch.setattr(mock_evu, "get_usable_surplus", mock_evu_counter_surplus) vehicle.ev_template.data.max_current_single_phase = params.max_current_single_phase control_parameter = ControlParameter() diff --git a/packages/control/bat.py b/packages/control/bat.py index 9fb4529b87..0458e3e588 100644 --- a/packages/control/bat.py +++ b/packages/control/bat.py @@ -3,21 +3,23 @@ from typing import List from dataclass_utils.factories import currents_list_factory +from helpermodules.constants import NO_ERROR log = logging.getLogger(__name__) @dataclass class Get: - currents: List[float] = field(default_factory=currents_list_factory) - soc: float = 0 - daily_exported: float = 0 - daily_imported: float = 0 - imported: float = 0 - exported: float = 0 - fault_state: int = 0 - fault_str: str = "" - power: float = 0 + currents: List[float] = field(default_factory=currents_list_factory, metadata={ + "topic": "get/currents"}) + soc: float = field(default=0, metadata={"topic": "get/soc"}) + daily_exported: float = field(default=0, metadata={"topic": "get/daily_exported"}) + daily_imported: float = field(default=0, metadata={"topic": "get/daily_imported"}) + imported: float = field(default=0, metadata={"topic": "get/imported"}) + exported: float = field(default=0, metadata={"topic": "get/exported"}) + fault_state: int = field(default=0, metadata={"topic": "get/fault_state"}) + fault_str: str = field(default=NO_ERROR, metadata={"topic": "get/fault_str"}) + power: float = field(default=0, metadata={"topic": "get/power"}) def get_factory() -> Get: diff --git a/packages/control/bat_all.py b/packages/control/bat_all.py index 0afc0726de..a6b9e28edf 100644 --- a/packages/control/bat_all.py +++ b/packages/control/bat_all.py @@ -24,7 +24,6 @@ from control import data from control.bat import Bat from helpermodules.constants import NO_ERROR -from helpermodules.pub import Pub from modules.common.fault_state import FaultStateLevel log = logging.getLogger(__name__) @@ -38,7 +37,7 @@ class BatConsiderationMode(Enum): @dataclass class Config: - configured: bool = False + configured: bool = field(default=False, metadata={"topic": "config/configured"}) def config_factory() -> Config: @@ -47,14 +46,14 @@ def config_factory() -> Config: @dataclass class Get: - soc: float = field(default=0, metadata={"topic": "get/soc", "mutable_by_algorithm": True}) - daily_exported: float = field(default=0, metadata={"topic": "get/daily_exported", "mutable_by_algorithm": True}) - daily_imported: float = field(default=0, metadata={"topic": "get/daily_imported", "mutable_by_algorithm": True}) - fault_str: str = field(default=NO_ERROR, metadata={"topic": "get/fault_str", "mutable_by_algorithm": True}) - fault_state: int = field(default=0, metadata={"topic": "get/fault_state", "mutable_by_algorithm": True}) - imported: float = field(default=0, metadata={"topic": "get/imported", "mutable_by_algorithm": True}) - exported: float = field(default=0, metadata={"topic": "get/exported", "mutable_by_algorithm": True}) - power: float = field(default=0, metadata={"topic": "get/power", "mutable_by_algorithm": True}) + soc: float = field(default=0, metadata={"topic": "get/soc"}) + daily_exported: float = field(default=0, metadata={"topic": "get/daily_exported"}) + daily_imported: float = field(default=0, metadata={"topic": "get/daily_imported"}) + fault_str: str = field(default=NO_ERROR, metadata={"topic": "get/fault_str"}) + fault_state: int = field(default=0, metadata={"topic": "get/fault_state"}) + imported: float = field(default=0, metadata={"topic": "get/imported"}) + exported: float = field(default=0, metadata={"topic": "get/exported"}) + power: float = field(default=0, metadata={"topic": "get/power"}) def get_factory() -> Get: @@ -63,8 +62,9 @@ def get_factory() -> Get: @dataclass class Set: - charging_power_left: float = 0 - regulate_up: bool = False + charging_power_left: float = field( + default=0, metadata={"topic": "set/charging_power_left"}) + regulate_up: bool = field(default=False, metadata={"topic": "set/regulate_up"}) def set_factory() -> Set: @@ -90,7 +90,6 @@ def calc_power_for_all_components(self): try: if len(data.data.bat_data) >= 1: self.data.config.configured = True - Pub().pub("openWB/set/bat/config/configured", self.data.config.configured) # Summe für alle konfigurierten Speicher bilden exported = 0 imported = 0 @@ -128,7 +127,6 @@ def calc_power_for_all_components(self): self.data.get.soc = 0 else: self.data.config.configured = False - Pub().pub("openWB/set/bat/config/configured", self.data.config.configured) except Exception: log.exception("Fehler im Bat-Modul") @@ -148,10 +146,6 @@ def _max_bat_power_hybrid_system(self, battery: Bat) -> float: else: battery.data.get.fault_state = FaultStateLevel.ERROR.value battery.data.get.fault_str = self.ERROR_CONFIG_MAX_AC_OUT - Pub().pub(f"openWB/set/bat/{battery.num}/get/fault_state", - battery.data.get.fault_state) - Pub().pub(f"openWB/set/bat/{battery.num}/get/fault_str", - battery.data.get.fault_str) raise ValueError(self.ERROR_CONFIG_MAX_AC_OUT) else: # Kein Hybrid-WR @@ -193,8 +187,6 @@ def setup_bat(self): else: self.data.set.charging_power_left = 0 self.data.get.power = 0 - Pub().pub("openWB/set/bat/set/charging_power_left", self.data.set.charging_power_left) - Pub().pub("openWB/set/bat/set/regulate_up", self.data.set.regulate_up) except Exception: log.exception("Fehler im Bat-Modul") diff --git a/packages/control/chargelog/chargelog.py b/packages/control/chargelog/chargelog.py index 59ec3a66e6..6e026a49b7 100644 --- a/packages/control/chargelog/chargelog.py +++ b/packages/control/chargelog/chargelog.py @@ -7,19 +7,56 @@ from control import data from dataclass_utils import asdict -from helpermodules.measurement_logging.process_log import CalculationType, analyse_percentage, process_entry +from helpermodules.measurement_logging.process_log import (CalculationType, analyse_percentage, + get_log_from_date_until_now, process_entry) from helpermodules.measurement_logging.write_log import LegacySmartHomeLogData, LogType, create_entry from helpermodules.pub import Pub from helpermodules import timecheck +from helpermodules.utils.json_file_handler import write_and_check # alte Daten: Startzeitpunkt der Ladung, Endzeitpunkt, Geladene Reichweite, Energie, Leistung, Ladedauer, LP-Nummer, # Lademodus, ID-Tag -# json-Objekt: {"chargepoint": {"id": 1, "name": "Hof", "rfid": 1234}, -# "vehicle": { "id": 1, "name":"Model 3", "chargemode": "pv_charging", "prio": True }, -# "time": { "begin":"27.05.2021 07:43", "end": "27.05.2021 07:50", "time_charged": "1:34", -# "data": {"range_charged": 34, "imported_since_mode_switch": 3400, "imported_since_plugged": 5000, -# "power": 110000, "costs": 3,42} }} - +# json-Objekt: new_entry = { +# "chargepoint": +# { +# "id": 22, +# "name": "LP 22", +# "serial_number": "0123456," +# "imported_at_start": 1000, +# "imported_at_end": 2000, +# }, +# "vehicle": +# { +# "id": 1, +# "name": "Auto", +# "chargemode": instant_charging, +# "prio": False, +# "rfid": "123" +# "soc_at_start": 50, +# "soc_at_end": 60, +# "range_at_start": 100, +# "range_at_end": 125, +# }, +# "time": +# { +# "begin": "01.02.2024 15:00:00", +# "end": "01.02.2024 16:00:00", +# "time_charged": "1:00" +# }, +# "data": +# { +# "range_charged": 100, +# "imported_since_mode_switch": 1000, +# "imported_since_plugged": 1000, +# "power": 1000, +# "costs": 0.95, +# "energy_source": { +# "grid": 0.25, +# "pv": 0.25, +# "bat": 0.5, +# "cp": 0} +# } +# } log = logging.getLogger("chargelog") @@ -48,6 +85,9 @@ def collect_data(chargepoint): if chargepoint.data.get.charge_state: if log_data.timestamp_start_charging is None: log_data.timestamp_start_charging = timecheck.create_timestamp() + if charging_ev.soc_module: + log_data.range_at_start = charging_ev.data.get.range + log_data.soc_at_start = charging_ev.data.get.soc if chargepoint.data.control_parameter.submode == "time_charging": log_data.chargemode_log_entry = "time_charging" else: @@ -157,6 +197,8 @@ def _create_entry(chargepoint, charging_ev, immediately: bool = True): if duration > 0: power = get_value_or_default(lambda: round(log_data.imported_since_mode_switch / duration, 2)) calculate_charge_cost(chargepoint, True) + energy_source = get_value_or_default(lambda: analyse_percentage(get_log_from_date_until_now( + log_data.timestamp_start_charging)["totals"])["energy_source"]) costs = round(log_data.costs, 2) new_entry = { "chargepoint": @@ -173,7 +215,11 @@ def _create_entry(chargepoint, charging_ev, immediately: bool = True): "name": get_value_or_default(lambda: _get_ev_name(log_data.ev)), "chargemode": get_value_or_default(lambda: log_data.chargemode_log_entry), "prio": get_value_or_default(lambda: log_data.prio), - "rfid": get_value_or_default(lambda: log_data.rfid) + "rfid": get_value_or_default(lambda: log_data.rfid), + "soc_at_start": get_value_or_default(lambda: log_data.soc_at_start), + "soc_at_end": get_value_or_default(lambda: charging_ev.data.get.soc), + "range_at_start": get_value_or_default(lambda: log_data.range_at_start), + "range_at_end": get_value_or_default(lambda: charging_ev.data.get.range), }, "time": { @@ -189,7 +235,8 @@ def _create_entry(chargepoint, charging_ev, immediately: bool = True): "imported_since_mode_switch": log_data.imported_since_mode_switch, "imported_since_plugged": log_data.imported_since_plugged, "power": power, - "costs": costs + "costs": costs, + "power_source": energy_source } } return new_entry @@ -210,8 +257,7 @@ def write_new_entry(new_entry): # content = json.load(jsonFile) content = [] content.append(new_entry) - with open(filepath, "w", encoding="utf-8") as json_file: - json.dump(content, json_file) + write_and_check(filepath, content) log.debug(f"Neuer Ladelog-Eintrag: {new_entry}") @@ -403,7 +449,11 @@ def get_reference_time(cp, reference_position): elif reference_position == ReferenceTime.MIDDLE: return timecheck.create_timestamp() - 3540 elif reference_position == ReferenceTime.END: - return timecheck.create_unix_timestamp_current_full_hour() + 60 + # Wenn der Ladevorgang erst innerhalb der letzten Stunde gestartet wurde. + if timecheck.create_unix_timestamp_current_full_hour() <= cp.data.set.log.timestamp_start_charging: + return cp.data.set.log.timestamp_start_charging + else: + return timecheck.create_unix_timestamp_current_full_hour() + 60 else: raise TypeError(f"Unbekannter Referenz-Zeitpunkt {reference_position}") diff --git a/packages/control/chargelog/chargelog_test.py b/packages/control/chargelog/chargelog_test.py new file mode 100644 index 0000000000..1ab700af43 --- /dev/null +++ b/packages/control/chargelog/chargelog_test.py @@ -0,0 +1,154 @@ + +import datetime +from unittest.mock import Mock + +import pytest + +from control import data +from control.chargelog import chargelog +from control.chargelog.chargelog import calculate_charge_cost +from control.chargepoint.chargepoint import Chargepoint +from helpermodules import timecheck +from test_utils.test_environment import running_on_github + + +def mock_daily_log_with_charging(date: str, num_of_intervalls, monkeypatch): + """erzeugt ein daily_log, im ersten Eintrag gibt es keine Änderung, danach wird bis inklusive dem letzten Beitrag + geladen""" + bat_exported = pv_exported = cp_imported = counter_imported = 2000 + date = datetime.datetime.strptime(date, "%m/%d/%Y, %H:%M") + daily_log = {"entries": []} + for i in range(0, num_of_intervalls): + if i != 0: + bat_exported += 1000 + pv_exported += 500 + cp_imported += 2000 + counter_imported += 500 + daily_log["entries"].append({'bat': {'all': {'exported': bat_exported, 'imported': 2000, 'soc': 100}, + 'bat2': {'exported': bat_exported, 'imported': 2000, 'soc': 100}}, + 'counter': {'counter0': {'exported': 2000, + 'grid': True, + 'imported': counter_imported}}, + 'cp': {'all': {'exported': 0, 'imported': cp_imported}, + 'cp4': {'exported': 0, 'imported': cp_imported}}, + 'date': date.strftime("%H:%M"), + 'ev': {'ev0': {'soc': None}}, + 'hc': {'all': {'imported': 0}}, + 'pv': {'all': {'exported': pv_exported}, 'pv1': {'exported': pv_exported}}, + 'sh': {}, + 'timestamp': date.timestamp()}) + date += datetime.timedelta(minutes=5) + mock_todays_daily_log = Mock(return_value=daily_log) + monkeypatch.setattr(chargelog, "get_todays_daily_log", mock_todays_daily_log) + return daily_log + + +@pytest.fixture() +def mock_data() -> None: + data.data_init(Mock()) + data.data.optional_data.et_module = None + + +def mock_create_entry_reference_end(clock, daily_log, monkeypatch): + current_log = daily_log["entries"][-1] + current_log["cp"]["all"]["imported"] += 500 + current_log["cp"]["cp4"]["imported"] += 500 + current_log["counter"]["counter0"]["imported"] += 500 + current_log["date"] = clock + current_log["timestamp"] = datetime.datetime.strptime(f"05/16/2022, {clock}", "%m/%d/%Y, %H:%M").timestamp() + mock_create_entry = Mock(return_value=current_log) + monkeypatch.setattr(chargelog, "create_entry", mock_create_entry) + + +def init_cp(charged_energy, costs, start_hour, start_minute=47): + cp = Chargepoint(4, None) + cp.data.set.log.imported_since_plugged = cp.data.set.log.imported_since_mode_switch = charged_energy + cp.data.set.log.timestamp_start_charging = datetime.datetime(2022, 5, 16, start_hour, start_minute).timestamp() + cp.data.get.imported = charged_energy + 2000 + cp.data.set.log.costs = costs + return cp + + +def test_calc_charge_cost_no_hour_change_reference_end(mock_data, monkeypatch): + cp = init_cp(6500, 0, 10, start_minute=27) + daily_log = mock_daily_log_with_charging("05/16/2022, 10:25", 4, monkeypatch) + mock_create_entry_reference_end("10:42", daily_log, monkeypatch) + + calculate_charge_cost(cp, True) + + assert cp.data.set.log.costs == 1.425 + + +def test_calc_charge_cost_first_hour_change_reference_begin(mock_data, monkeypatch): + cp = init_cp(6000, 0, 7) + daily_log = mock_daily_log_with_charging("05/16/2022, 07:45", 4, monkeypatch) + current_log = daily_log["entries"][-1] + current_log["date"] = "08:00" + current_log["timestamp"] = datetime.datetime.strptime("05/16/2022, 08:00", "%m/%d/%Y, %H:%M").timestamp() + mock_create_entry = Mock(return_value=current_log) + monkeypatch.setattr(chargelog, "create_entry", mock_create_entry) + + calculate_charge_cost(cp, False) + + assert cp.data.set.log.costs == 1.275 + + +def test_calc_charge_cost_first_hour_change_reference_begin_day_change(mock_data, monkeypatch): + cp = init_cp(6000, 0, 23) + daily_log = mock_daily_log_with_charging("05/16/2022, 23:45", 4, monkeypatch) + current_log = daily_log["entries"][-1] + current_log["date"] = "00:00" + current_log["timestamp"] = datetime.datetime.strptime("05/17/2022, 00:00", "%m/%d/%Y, %H:%M").timestamp() + mock_create_entry = Mock(return_value=current_log) + monkeypatch.setattr(chargelog, "create_entry", mock_create_entry) + mock_today_timestamp = Mock(return_value=1652738421) + monkeypatch.setattr(timecheck, "create_timestamp", mock_today_timestamp) + + calculate_charge_cost(cp, False) + + assert cp.data.set.log.costs == 1.275 + + +def test_calc_charge_cost_one_hour_change_reference_end(mock_data, monkeypatch): + if running_on_github(): + # ToDo Zeitzonen berücksichtigen, damit Tests auf Github laufen + return + cp = init_cp(22500, 1.275, 7) + daily_log = mock_daily_log_with_charging("05/16/2022, 07:45", 12, monkeypatch) + mock_create_entry_reference_end("08:40", daily_log, monkeypatch) + + calculate_charge_cost(cp, True) + + assert cp.data.set.log.costs == 4.8248999999999995 + + +def test_calc_charge_cost_two_hour_change_reference_middle(mock_data, monkeypatch): + if running_on_github(): + # ToDo Zeitzonen berücksichtigen, damit Tests auf Github laufen + return + cp = init_cp(22500, 1.275, 6) + daily_log = mock_daily_log_with_charging("05/16/2022, 06:45", 16, monkeypatch) + current_log = daily_log["entries"][-1] + current_log["date"] = "08:00" + current_log["timestamp"] = datetime.datetime(2022, 5, 16, 8).timestamp() + mock_create_entry = Mock(return_value=current_log) + monkeypatch.setattr(chargelog, "create_entry", mock_create_entry) + mock_today_timestamp = Mock(return_value=1652680801) + monkeypatch.setattr(timecheck, "create_timestamp", mock_today_timestamp) + + calculate_charge_cost(cp, False) + + assert cp.data.set.log.costs == 6.375 + + +def test_calc_charge_cost_two_hour_change_reference_end(mock_data, monkeypatch): + if running_on_github(): + # ToDo Zeitzonen berücksichtigen, damit Tests auf Github laufen + return + cp = init_cp(46500, 6.375, 6) + daily_log = mock_daily_log_with_charging("05/16/2022, 06:45", 24, monkeypatch) + mock_create_entry_reference_end("08:40", daily_log, monkeypatch) + + calculate_charge_cost(cp, True) + + assert cp.data.set.log.costs == 9.924900000000001 diff --git a/packages/control/chargepoint/chargepoint.py b/packages/control/chargepoint/chargepoint.py index c2b25b1922..69e6178ea6 100644 --- a/packages/control/chargepoint/chargepoint.py +++ b/packages/control/chargepoint/chargepoint.py @@ -41,7 +41,7 @@ def get_chargepoint_config_default() -> dict: return { - "name": "Standard-Ladepunkt", + "name": "neuer Ladepunkt", "type": None, "ev": 0, "template": 0, @@ -101,8 +101,8 @@ def _is_grid_protection_inactive(self) -> Tuple[bool, Optional[str]]: general_data.grid_protection_random_stop): state = False message = "Ladepunkt gesperrt, da der Netzschutz aktiv ist." - Pub().pub("openWB/set/general/grid_protection_timestamp", None) - Pub().pub("openWB/set/general/grid_protection_random_stop", 0) + general_data.grid_protection_timestamp = None + general_data.grid_protection_random_stop = 0 else: state = False message = "Ladepunkt gesperrt, da der Netzschutz aktiv ist." @@ -149,8 +149,7 @@ def _is_autolock_inactive(self) -> Tuple[bool, Optional[str]]: state = True else: # Darf Autolock durch Tag überschrieben werden? - if (data.data.optional_data.data.rfid.active and - self.template.data.rfid_enabling): + if data.data.optional_data.data.rfid.active: if self.data.get.rfid is None and self.data.set.rfid is None: state = False message = ("Keine Ladung, da der Ladepunkt durch Autolock gesperrt ist und erst per ID-Tag " @@ -164,13 +163,15 @@ def _is_autolock_inactive(self) -> Tuple[bool, Optional[str]]: return state, message def _is_manual_lock_inactive(self) -> Tuple[bool, Optional[str]]: - if (self.data.set.manual_lock is False or - (self.template.data.rfid_enabling and - (self.data.get.rfid is not None or self.data.set.rfid is not None))): - if self.data.set.manual_lock: + if self.data.set.manual_lock and self.template.data.disable_after_unplug or self.data.set.manual_lock is False: + if ((self.data.get.rfid or self.data.set.rfid) in self.template.data.valid_tags + or self.data.set.manual_lock is False): Pub().pub(f"openWB/set/chargepoint/{self.num}/set/manual_lock", False) - charging_possible = True - message = None + charging_possible = True + message = None + else: + charging_possible = False + message = "Ladepunkt gesperrt, da kein zum Ladepunkt passender ID-Tag gefunden wurde." else: charging_possible = False message = "Keine Ladung, da der Ladepunkt gesperrt wurde." @@ -223,8 +224,7 @@ def _process_charge_stop(self) -> None: self.data.config.ev = 0 Pub().pub("openWB/set/chargepoint/"+str(self.num)+"/config/ev", 0) # Ladepunkt nach Abstecken sperren - if data.data.ev_data[ - "ev"+str(self.data.set.charging_ev_prev)].charge_template.data.disable_after_unplug: + if self.template.data.disable_after_unplug: self.data.set.manual_lock = True Pub().pub("openWB/set/chargepoint/"+str(self.num)+"/set/manual_lock", True) # Ev wurde noch nicht aktualisiert. @@ -458,11 +458,11 @@ def initiate_phase_switch(self): Pub().pub("openWB/set/chargepoint/"+str(self.num)+"/set/phases_to_use", self.data.control_parameter.phases) self.data.set.phases_to_use = self.data.control_parameter.phases - if self._is_phase_switch_required(): - # Wenn die Umschaltverzögerung aktiv ist, darf nicht umgeschaltet werden. - if (self.data.control_parameter.state != ChargepointState.PERFORMING_PHASE_SWITCH and - self.data.control_parameter.state != ChargepointState.WAIT_FOR_USING_PHASES): - if self.cp_ev_support_phase_switch(): + if self.cp_ev_support_phase_switch(): + if self._is_phase_switch_required(): + # Wenn die Umschaltverzögerung aktiv ist, darf nicht umgeschaltet werden. + if (self.data.control_parameter.state != ChargepointState.PERFORMING_PHASE_SWITCH and + self.data.control_parameter.state != ChargepointState.WAIT_FOR_USING_PHASES): log.debug( f"Lp {self.num}: Ladung aktiv halten " f"{charging_ev.ev_template.data.keep_charge_active_duration}s") @@ -490,13 +490,16 @@ def initiate_phase_switch(self): self.data.set.phases_to_use = self.data.control_parameter.phases self.data.control_parameter.state = ChargepointState.PERFORMING_PHASE_SWITCH else: - log.error( - "Phasenumschaltung an Ladepunkt" + str(self.num) + - " nicht möglich, da der Ladepunkt keine Phasenumschaltung unterstützt.") - else: - log.error("Phasenumschaltung an Ladepunkt" + str(self.num) + - " nicht möglich, da gerade eine Umschaltung im Gange ist.") - + log.error("Phasenumschaltung an Ladepunkt" + str(self.num) + + " nicht möglich, da gerade eine Umschaltung im Gange ist.") + elif self.data.control_parameter.state == ChargepointState.PHASE_SWITCH_DELAY_EXPIRED: + # Wenn keine Phasenumschaltung durchgeführt wird, Status auf CHARGING_ALLOWED setzen, sonst + # bleibt PHASE_SWITCH_DELAY_EXPIRED stehen. + self.data.control_parameter.state = ChargepointState.CHARGING_ALLOWED + else: + log.error( + "Phasenumschaltung an Ladepunkt" + str(self.num) + + " nicht möglich, da der Ladepunkt keine Phasenumschaltung unterstützt.") except Exception: log.exception("Fehler in der Ladepunkt-Klasse von "+str(self.num)) @@ -508,9 +511,13 @@ def get_phases_by_selected_chargemode(self) -> int: mode = "time_charging" else: mode = charging_ev.charge_template.data.chargemode.selected - chargemode = data.data.general_data.get_phases_chargemode(mode) + chargemode = data.data.general_data.get_phases_chargemode(mode, self.data.control_parameter.submode) - if chargemode is None: + if (chargemode is None or + (self.data.config.auto_phase_switch_hw is False and self.data.get.charge_state) or + self.data.control_parameter.failed_phase_switches > self.MAX_FAILED_PHASE_SWITCHES): + # Wenn keine Umschaltung verbaut ist, die Phasenzahl nehmen, mit der geladen wird. Damit werden zB auch + # einphasige EV an dreiphasigen openWBs korrekt berücksichtigt. phases = self.data.get.phases_in_use elif (chargemode == 0 and (self.data.set.phases_to_use == self.data.get.phases_in_use or self.data.get.phases_in_use == 0)): @@ -638,6 +645,7 @@ def update(self, ev_list: Dict[str, Ev]) -> None: required_current = self.check_min_max_current( required_current, self.data.control_parameter.phases) charging_ev.set_chargemode_changed(self.data.control_parameter, submode) + charging_ev.set_submode_changed(self.data.control_parameter, submode) self.set_control_parameter(submode, required_current) self.set_required_currents(required_current) @@ -773,11 +781,14 @@ def _pub_connected_vehicle(self, vehicle: Ev): def cp_ev_chargemode_support_phase_switch(self) -> bool: control_parameter = self.data.control_parameter pv_auto_switch = (control_parameter.chargemode == Chargemode.PV_CHARGING and - data.data.general_data.get_phases_chargemode(Chargemode.PV_CHARGING.value) == 0) + data.data.general_data.get_phases_chargemode( + Chargemode.PV_CHARGING.value, + control_parameter.submode) == 0) scheduled_auto_switch = ( control_parameter.chargemode == Chargemode.SCHEDULED_CHARGING and control_parameter.submode == Chargemode.PV_CHARGING and - data.data.general_data.get_phases_chargemode(Chargemode.SCHEDULED_CHARGING.value) == 0) + data.data.general_data.get_phases_chargemode(Chargemode.SCHEDULED_CHARGING.value, + control_parameter.submode) == 0) return (self.cp_ev_support_phase_switch() and self.data.get.charge_state and (pv_auto_switch or scheduled_auto_switch) and diff --git a/packages/control/chargepoint/chargepoint_all.py b/packages/control/chargepoint/chargepoint_all.py index 5f1600c18f..d2fd2e5075 100644 --- a/packages/control/chargepoint/chargepoint_all.py +++ b/packages/control/chargepoint/chargepoint_all.py @@ -3,7 +3,6 @@ from control import data from control.chargepoint.chargepoint_state import ChargepointState -from helpermodules.pub import Pub log = logging.getLogger(__name__) @@ -11,11 +10,11 @@ @dataclass class AllGet: - daily_imported: float = 0 - daily_exported: float = 0 - power: float = 0 - imported: float = 0 - exported: float = 0 + daily_imported: float = field(default=0, metadata={"topic": "get/daily_imported"}) + daily_exported: float = field(default=0, metadata={"topic": "get/daily_exported"}) + power: float = field(default=0, metadata={"topic": "get/power"}) + imported: float = field(default=0, metadata={"topic": "get/imported"}) + exported: float = field(default=0, metadata={"topic": "get/exported"}) def all_get_factory() -> AllGet: @@ -80,10 +79,7 @@ def get_cp_sum(self): except Exception: log.exception("Fehler in der allgemeinen Ladepunkt-Klasse für Ladepunkt "+cp) self.data.get.power = power - Pub().pub("openWB/set/chargepoint/get/power", power) self.data.get.imported = imported - Pub().pub("openWB/set/chargepoint/get/imported", imported) self.data.get.exported = exported - Pub().pub("openWB/set/chargepoint/get/exported", exported) except Exception: log.exception("Fehler in der allgemeinen Ladepunkt-Klasse") diff --git a/packages/control/chargepoint/chargepoint_data.py b/packages/control/chargepoint/chargepoint_data.py index 0388566936..ca4fdf1728 100644 --- a/packages/control/chargepoint/chargepoint_data.py +++ b/packages/control/chargepoint/chargepoint_data.py @@ -77,6 +77,10 @@ class Log: prio: bool = False rfid: Optional[str] = None serial_number: Optional[str] = None + soc_at_start: Optional[int] = None + soc_at_end: Optional[int] = None + range_at_start: Optional[float] = None + range_at_end: Optional[float] = None def connected_vehicle_factory() -> ConnectedVehicle: @@ -118,7 +122,6 @@ def log_factory() -> Log: @dataclass class Set: - change_ev_permitted: bool = False charging_ev: int = -1 charging_ev_prev: int = -1 current: float = 0 @@ -139,11 +142,11 @@ class Set: class Config: configuration: Dict = field(default_factory=empty_dict_factory) ev: int = 0 - name: str = "Standard-Ladepunkt" + name: str = "neuer Ladepunkt" type: Optional[str] = None template: int = 0 connected_phases: int = 3 - phase_1: int = 0 + phase_1: int = 1 auto_phase_switch_hw: bool = False control_pilot_interruption_hw: bool = False id: int = 0 diff --git a/packages/control/chargepoint/chargepoint_template.py b/packages/control/chargepoint/chargepoint_template.py index 736a0e6f48..c2591de2a1 100644 --- a/packages/control/chargepoint/chargepoint_template.py +++ b/packages/control/chargepoint/chargepoint_template.py @@ -36,21 +36,26 @@ def autolock_factory(): @dataclass class CpTemplateData: - autolock: Autolock = field(default_factory=autolock_factory) + autolock: Autolock = field(default_factory=autolock_factory, metadata={"topic": ""}) id: int = 0 max_current_multi_phases: int = 32 max_current_single_phase: int = 32 - name: str = "Standard Ladepunkt-Profil" - rfid_enabling: bool = False + name: str = "neues Ladepunkt-Profil" + disable_after_unplug: bool = False valid_tags: List = field(default_factory=empty_list_factory) +def cp_template_data_factory() -> CpTemplateData: + return CpTemplateData() + + +@dataclass class CpTemplate: """ Profil für einen Ladepunkt. """ - def __init__(self): - self.data: CpTemplateData = CpTemplateData() + data: CpTemplateData = field(default_factory=cp_template_data_factory, metadata={ + "topic": ""}) def is_locked_by_autolock(self, charge_state: bool) -> bool: if self.data.autolock.active: @@ -90,11 +95,11 @@ def get_ev(self, rfid: str, vehicle_id: str, assigned_ev: int) -> int: if data.data.optional_data.data.rfid.active and (rfid is not None or vehicle_id is not None): vehicle = ev_module.get_ev_to_rfid(rfid, vehicle_id) if vehicle is None: - if self.data.rfid_enabling: + if len(self.data.valid_tags) == 0: num = -1 message = ( f"Keine Ladung, da dem ID-Tag {rfid} kein Fahrzeug-Profil zugeordnet werden kann. Eine " - "Freischaltung ist nur mit gültigen ID-Tag möglich.") + "Freischaltung ist nur mit gültigem ID-Tag möglich.") else: num = assigned_ev else: diff --git a/packages/control/chargepoint/control_parameter.py b/packages/control/chargepoint/control_parameter.py index fc151b9e7b..de8daa2ed4 100644 --- a/packages/control/chargepoint/control_parameter.py +++ b/packages/control/chargepoint/control_parameter.py @@ -9,50 +9,28 @@ @dataclass class ControlParameter: - chargemode: Chargemode_enum = field( - default=Chargemode_enum.STOP, - metadata={"topic": "control_parameter/chargemode", "mutable_by_algorithm": True}) - current_plan: Optional[str] = field( - default=None, - metadata={"topic": "control_parameter/current_plan", "mutable_by_algorithm": True}) - failed_phase_switches: int = field( - default=0, - metadata={"topic": "control_parameter/failed_phase_switches", "mutable_by_algorithm": True}) + chargemode: Chargemode_enum = field(default=Chargemode_enum.STOP, metadata={ + "topic": "control_parameter/chargemode"}) + current_plan: Optional[str] = field(default=None, metadata={"topic": "control_parameter/current_plan"}) + failed_phase_switches: int = field(default=0, metadata={"topic": "control_parameter/failed_phase_switches"}) imported_at_plan_start: Optional[float] = field( - default=None, - metadata={"topic": "control_parameter/imported_at_plan_start", "mutable_by_algorithm": True}) + default=None, metadata={"topic": "control_parameter/imported_at_plan_start"}) imported_instant_charging: Optional[float] = field( - default=None, - metadata={"topic": "control_parameter/imported_instant_charging", "mutable_by_algorithm": True}) - limit: Optional[LimitingValue] = field( - default=None, - metadata={"topic": "control_parameter/limit", "mutable_by_algorithm": True}) - phases: int = field( - default=0, - metadata={"topic": "control_parameter/phases", "mutable_by_algorithm": True}) - prio: bool = field( - default=False, - metadata={"topic": "control_parameter/prio", "mutable_by_algorithm": True}) - required_current: float = field( - default=0, - metadata={"topic": "control_parameter/required_current", "mutable_by_algorithm": True}) - required_currents: List[float] = field( - default_factory=currents_list_factory) - state: ChargepointState = field( - default=ChargepointState.NO_CHARGING_ALLOWED, - metadata={"topic": "control_parameter/state", "mutable_by_algorithm": True}) - submode: Chargemode_enum = field( - default=Chargemode_enum.STOP, - metadata={"topic": "control_parameter/submode", "mutable_by_algorithm": True}) + default=None, metadata={"topic": "control_parameter/imported_instant_charging"}) + limit: Optional[LimitingValue] = field(default=None, metadata={"topic": "control_parameter/limit"}) + phases: int = field(default=0, metadata={"topic": "control_parameter/phases"}) + prio: bool = field(default=False, metadata={"topic": "control_parameter/prio"}) + required_current: float = field(default=0, metadata={"topic": "control_parameter/required_current"}) + required_currents: List[float] = field(default_factory=currents_list_factory) + state: ChargepointState = field(default=ChargepointState.NO_CHARGING_ALLOWED, + metadata={"topic": "control_parameter/state"}) + submode: Chargemode_enum = field(default=Chargemode_enum.STOP, metadata={"topic": "control_parameter/submode"}) timestamp_auto_phase_switch: Optional[float] = field( - default=None, - metadata={"topic": "control_parameter/timestamp_auto_phase_switch", "mutable_by_algorithm": True}) + default=None, metadata={"topic": "control_parameter/timestamp_auto_phase_switch"}) timestamp_perform_phase_switch: Optional[float] = field( - default=None, - metadata={"topic": "control_parameter/timestamp_perform_phase_switch", "mutable_by_algorithm": True}) + default=None, metadata={"topic": "control_parameter/timestamp_perform_phase_switch"}) timestamp_switch_on_off: Optional[float] = field( - default=None, - metadata={"topic": "control_parameter/timestamp_switch_on_off", "mutable_by_algorithm": True}) + default=None, metadata={"topic": "control_parameter/timestamp_switch_on_off"}) def control_parameter_factory() -> ControlParameter: diff --git a/packages/control/chargepoint/get_phases_test.py b/packages/control/chargepoint/get_phases_test.py index 1b4f7360f7..4f8f5ce47d 100644 --- a/packages/control/chargepoint/get_phases_test.py +++ b/packages/control/chargepoint/get_phases_test.py @@ -52,10 +52,13 @@ def __init__(self, cases = [ Params("continue using 3", connected_phases=3, auto_phase_switch_hw=False, prevent_phase_switch=True, chargemode_phases=3, phases_in_use=3, imported_since_plugged=0, - expected_phases=3), + expected_phases=3, charge_state=True), + Params("continue using 1, one phase car", connected_phases=3, auto_phase_switch_hw=False, + prevent_phase_switch=True, chargemode_phases=3, phases_in_use=1, imported_since_plugged=0, + expected_phases=1, charge_state=True), Params("continue using 1", connected_phases=1, auto_phase_switch_hw=False, prevent_phase_switch=True, chargemode_phases=1, phases_in_use=1, imported_since_plugged=0, - expected_phases=1), + expected_phases=1, charge_state=True), Params("don't change during phase switch", connected_phases=3, auto_phase_switch_hw=True, prevent_phase_switch=False, chargemode_phases=0, phases_in_use=1, imported_since_plugged=0, expected_phases=1, timestamp_perform_phase_switch="2022/05/11, 15:00:02"), diff --git a/packages/control/counter.py b/packages/control/counter.py index 6cf4252960..580b052dff 100644 --- a/packages/control/counter.py +++ b/packages/control/counter.py @@ -7,13 +7,14 @@ from typing import List, Tuple from control import data +from control.chargemode import Chargemode from control.ev import Ev from control.chargepoint.chargepoint import Chargepoint from control.chargepoint.chargepoint_state import ChargepointState from dataclass_utils.factories import currents_list_factory, voltages_list_factory from helpermodules import timecheck +from helpermodules.constants import NO_ERROR from helpermodules.phase_mapping import convert_cp_currents_to_evu_currents -from helpermodules.pub import Pub from modules.common.fault_state import FaultStateLevel log = logging.getLogger(__name__) @@ -32,8 +33,9 @@ class ControlRangeState(Enum): @dataclass class Config: - max_currents: List[float] = field(default_factory=currents_list_factory) - max_total_power: float = 0 + max_currents: List[float] = field(default_factory=currents_list_factory, metadata={ + "topic": "get/max_currents"}) + max_total_power: float = field(default=0, metadata={"topic": "get/max_total_power"}) def config_factory() -> Config: @@ -42,19 +44,22 @@ def config_factory() -> Config: @dataclass class Get: - powers: List[float] = field(default_factory=currents_list_factory) - currents: List[float] = field(default_factory=currents_list_factory) - voltages: List[float] = field(default_factory=voltages_list_factory) - power_factors: List[float] = field(default_factory=currents_list_factory) - unbalanced_load: float = 0 - frequency: float = 0 - daily_exported: float = 0 - daily_imported: float = 0 - imported: float = 0 - exported: float = 0 - fault_state: int = 0 - fault_str: str = "" - power: float = 0 + powers: List[float] = field(default_factory=currents_list_factory, metadata={ + "topic": "get/powers"}) + currents: List[float] = field(default_factory=currents_list_factory, metadata={ + "topic": "get/currents"}) + voltages: List[float] = field(default_factory=voltages_list_factory, metadata={ + "topic": "get/voltages"}) + power_factors: List[float] = field(default_factory=currents_list_factory, metadata={ + "topic": "get/power_factors"}) + frequency: float = field(default=0, metadata={"topic": "get/frequency"}) + daily_exported: float = field(default=0, metadata={"topic": "get/daily_exported"}) + daily_imported: float = field(default=0, metadata={"topic": "get/daily_imported"}) + imported: float = field(default=0, metadata={"topic": "get/imported"}) + exported: float = field(default=0, metadata={"topic": "get/exported"}) + fault_state: int = field(default=0, metadata={"topic": "get/fault_state"}) + fault_str: str = field(default=NO_ERROR, metadata={"topic": "get/fault_str"}) + power: float = field(default=0, metadata={"topic": "get/power"}) def get_factory() -> Get: @@ -63,13 +68,12 @@ def get_factory() -> Get: @dataclass class Set: - error_counter: int = 0 - reserved_surplus: float = 0 - released_surplus: float = 0 + error_counter: int = field(default=0, metadata={"topic": "set/error_counter"}) + reserved_surplus: float = field(default=0, metadata={"topic": "set/reserved_surplus"}) + released_surplus: float = field(default=0, metadata={"topic": "set/released_surplus"}) raw_power_left: float = 0 raw_currents_left: List[float] = field(default_factory=currents_list_factory) surplus_power_left: float = 0 - state_str: str = "" def set_factory() -> Set: @@ -121,7 +125,6 @@ def _set_loadmanagement_state(self) -> None: # auf True gesetzt werden. if loadmanagement_available is False: data.data.cp_data[cp].data.set.loadmanagement_available = loadmanagement_available - Pub().pub(f"openWB/set/counter/{self.num}/set/error_counter", self.data.set.error_counter) # tested @@ -183,25 +186,12 @@ def update_surplus_values_left(self, diffs) -> None: log.debug(f'Zähler {self.num}: {self.data.set.raw_currents_left}A verbleibende Ströme, ' f'{self.data.set.surplus_power_left}W verbleibender Überschuss') - def put_stats(self): - try: - if f'counter{self.num}' == data.data.counter_all_data.get_evu_counter_str(): - Pub().pub(f"openWB/set/counter/{self.num}/set/reserved_surplus", - self.data.set.reserved_surplus) - Pub().pub(f"openWB/set/counter/{self.num}/set/released_surplus", - self.data.set.released_surplus) - log.info(f'{self.data.set.reserved_surplus}W reservierte EVU-Leistung, ' - f'{self.data.set.released_surplus}W freigegebene EVU-Leistung') - except Exception: - log.exception("Fehler in der Zähler-Klasse von "+str(self.num)) - def calc_surplus(self): # reservierte Leistung wird nicht berücksichtigt, weil diese noch verwendet werden kann, bis die EV # eingeschaltet werden. Es darf bloß nicht für zu viele zB die Einschaltverzögerung gestartet werden. evu_counter = data.data.counter_all_data.get_evu_counter() bat_surplus = data.data.bat_all_data.power_for_bat_charging() surplus = evu_counter.data.get.power - bat_surplus - log.info(f"Überschuss zur PV-geführten Ladung: {surplus}W") return surplus def calc_raw_surplus(self): @@ -214,7 +204,6 @@ def calc_raw_surplus(self): max_power = evu_counter.data.config.max_total_power surplus = raw_power_left - max_power + bat_surplus + disengageable_smarthome_power ranged_surplus = surplus + self._control_range_offset() - log.info(f"Überschuss zur PV-geführten Ladung: {ranged_surplus}W") return ranged_surplus def get_control_range_state(self, feed_in_yield: int) -> ControlRangeState: @@ -249,11 +238,17 @@ def _control_range_offset(self): log.debug(f"Anpassen des Regelbereichs {range_offset}W") return range_offset + def get_usable_surplus(self, feed_in_yield: float) -> float: + # verbleibender EVU-Überschuss unter Berücksichtigung der Einspeisegrenze und Speicherleistung + return (-self.calc_surplus() - self.data.set.released_surplus + + self.data.set.reserved_surplus - feed_in_yield) + SWITCH_ON_FALLEN_BELOW = "Einschaltschwelle während der Einschaltverzögerung unterschritten." SWITCH_ON_WAITING = "Die Ladung wird gestartet, sobald in {} die Einschaltverzögerung abgelaufen ist." SWITCH_ON_NOT_EXCEEDED = ("Die Ladung kann nicht gestartet werden, da die Einschaltschwelle nicht erreicht " "wird.") SWITCH_ON_EXPIRED = "Einschaltschwelle für die Dauer der Einschaltverzögerung überschritten." + SWITCH_ON_MAX_PHASES = "Der Überschuss ist ausreichend, um direkt mit {} Phasen zu laden." def calc_switch_on_power(self, chargepoint: Chargepoint) -> Tuple[float, float]: surplus = self.data.set.surplus_power_left - self.data.set.reserved_surplus @@ -329,6 +324,19 @@ def switch_on_timer_expired(self, chargepoint: Chargepoint) -> None: self.data.set.reserved_surplus -= pv_config.switch_on_threshold*control_parameter.phases msg = self.SWITCH_ON_EXPIRED.format(pv_config.switch_on_threshold) control_parameter.state = ChargepointState.CHARGING_ALLOWED + + if chargepoint.data.set.charging_ev_data.charge_template.data.chargemode.pv_charging.feed_in_limit: + feed_in_yield = pv_config.feed_in_yield + else: + feed_in_yield = 0 + ev_template = chargepoint.data.set.charging_ev_data.ev_template + max_phases_power = ev_template.data.min_current * ev_template.data.max_phases * 230 + if (data.data.general_data.get_phases_chargemode(Chargemode.PV_CHARGING.value, + control_parameter.submode) == 0 and + chargepoint.cp_ev_support_phase_switch() and + self.get_usable_surplus(feed_in_yield) > max_phases_power): + control_parameter.phases = ev_template.data.max_phases + msg += self.SWITCH_ON_MAX_PHASES.format(ev_template.data.max_phases) chargepoint.set_state_and_log(msg) except Exception: log.exception("Fehler im allgemeinen PV-Modul") @@ -480,8 +488,6 @@ def reset_pv_data(self): """ setzt die Daten zurück, die über mehrere Regelzyklen genutzt werden. """ try: - Pub().pub(f"openWB/set/counter/{self.num}/set/reserved_surplus", 0) - Pub().pub(f"openWB/set/counter/{self.num}/set/released_surplus", 0) self.data.set.reserved_surplus = 0 self.data.set.released_surplus = 0 except Exception: diff --git a/packages/control/counter_all.py b/packages/control/counter_all.py index 3c737f563d..fa053f9ced 100644 --- a/packages/control/counter_all.py +++ b/packages/control/counter_all.py @@ -4,7 +4,7 @@ from dataclasses import dataclass, field import logging import re -from typing import Callable, Dict, List, Tuple, Union +from typing import Callable, Dict, List, Optional, Tuple, Union from control import data from control.counter import Counter @@ -20,7 +20,10 @@ @dataclass class Config: - reserve_for_not_charging: bool = False + home_consumption_source_id: Optional[str] = field( + default=None, metadata={"topic": "config/home_consumption_source_id"}) + reserve_for_not_charging: bool = field( + default=False, metadata={"topic": "config/reserve_for_not_charging"}) def config_factory() -> Config: @@ -29,18 +32,26 @@ def config_factory() -> Config: @dataclass class Set: - loadmanagement_active: bool = False - home_consumption: float = 0 - smarthome_power_excluded_from_home_consumption: float = 0 - invalid_home_consumption: int = 0 - daily_yield_home_consumption: float = 0 - imported_home_consumption: float = 0 - disengageable_smarthome_power: float = 0 + loadmanagement_active: bool = field( + default=False, metadata={"topic": "set/loadmanagement_active"}) + home_consumption: float = field(default=0, metadata={"topic": "set/home_consumption"}) + smarthome_power_excluded_from_home_consumption: float = field( + default=0, + metadata={"topic": "set/smarthome_power_excluded_from_home_consumption"}) + invalid_home_consumption: int = field( + default=0, metadata={"topic": "set/invalid_home_consumption"}) + daily_yield_home_consumption: float = field( + default=0, metadata={"topic": "set/daily_yield_home_consumption"}) + imported_home_consumption: float = field( + default=0, metadata={"topic": "set/imported_home_consumption"}) + disengageable_smarthome_power: float = field( + default=0, metadata={"topic": "set/disengageable_smarthome_power"}) @dataclass class Get: - hierarchy: List = field(default_factory=empty_list_factory) + hierarchy: List = field(default_factory=empty_list_factory, metadata={ + "topic": "get/hierarchy"}) def get_factory() -> Get: @@ -59,8 +70,7 @@ class CounterAllData: class CounterAll: - """ - """ + MISSING_EVU_COUNTER = "Bitte erst einen EVU-Zähler konfigurieren." def __init__(self): self.data = CounterAllData() @@ -89,51 +99,63 @@ def get_id_evu_counter(self) -> int: "möglich.") raise - def put_stats(self) -> None: - try: - Pub().pub("openWB/set/counter/set/loadmanagement_active", self.data.set.loadmanagement_active) - except Exception: - log.exception("Fehler in der allgemeinen Zähler-Klasse") - def set_home_consumption(self) -> None: try: + self._validate_home_consumption_counter() home_consumption, elements = self._calc_home_consumption() if home_consumption < 0: log.error( f"Ungültiger Hausverbrauch: {home_consumption}W, Berücksichtigte Komponenten neben EVU {elements}") - evu_counter_data = data.data.counter_data[self.get_evu_counter_str()].data - if evu_counter_data.get.fault_state == FaultStateLevel.NO_ERROR: - evu_counter_data.get.fault_state = FaultStateLevel.WARNING.value - evu_counter_data.get.fault_str = ("Der Wert für den Hausverbrauch ist nicht plausibel (negativ). " - "Bitte die Leistungen der Komponenten und die Anordnung in der " - "Hierarchie prüfen.") - evu_counter = self.get_id_evu_counter() - Pub().pub(f"openWB/set/counter/{evu_counter}/get/fault_state", - evu_counter_data.get.fault_state) - Pub().pub(f"openWB/set/counter/{evu_counter}/get/fault_str", - evu_counter_data.get.fault_str) + if self.data.config.home_consumption_source_id is None: + hc_counter_source = self.get_evu_counter_str() + else: + hc_counter_source = f"counter{self.data.config.home_consumption_source_id}" + hc_counter_data = data.data.counter_data[hc_counter_source].data + if hc_counter_data.get.fault_state == FaultStateLevel.NO_ERROR: + hc_counter_data.get.fault_state = FaultStateLevel.WARNING.value + hc_counter_data.get.fault_str = ("Hinweis: Es gibt mehr Stromerzeuger im Haus als in der openWB " + "eingetragen sind. Der Hausverbrauch kann nicht korrekt berechnet " + "werden. Dies hat auf die PV-Überschussladung keine negativen " + "Auswirkungen.") if self.data.set.invalid_home_consumption < 3: self.data.set.invalid_home_consumption += 1 - Pub().pub("openWB/set/counter/set/invalid_home_consumption", - self.data.set.invalid_home_consumption) return else: home_consumption = 0 else: self.data.set.invalid_home_consumption = 0 - Pub().pub("openWB/set/counter/set/invalid_home_consumption", - self.data.set.invalid_home_consumption) self.data.set.home_consumption = home_consumption - Pub().pub("openWB/set/counter/set/home_consumption", self.data.set.home_consumption) imported, _ = self.sim_counter.sim_count(self.data.set.home_consumption) - Pub().pub("openWB/set/counter/set/imported_home_consumption", imported) self.data.set.imported_home_consumption = imported except Exception: log.exception("Fehler in der allgemeinen Zähler-Klasse") + EVU_IS_HC_COUNTER_ERROR = ("Der EVU-Zähler kann nicht als Quelle für den Hausverbrauch verwendet werden. Meist ist " + "der Zähler am EVU-Punkt installiert, dann muss im Lastmanagement unter Hausverbrauch" + " 'von openWB berechnen' ausgewählt werden. Wenn der Zähler im Hausverbrauchszweig " + "installiert ist, einen virtuellen Zähler anlegen und im Lastmanagement ganz links " + "anordnen.") + + def _validate_home_consumption_counter(self): + if self.data.config.home_consumption_source_id is not None: + if self.data.config.home_consumption_source_id == self.get_id_evu_counter(): + hc_counter_data = data.data.counter_data[self.get_evu_counter_str()].data + hc_counter_data.get.fault_state = FaultStateLevel.ERROR.value + hc_counter_data.get.fault_str = self.EVU_IS_HC_COUNTER_ERROR + evu_counter = self.get_id_evu_counter() + Pub().pub(f"openWB/set/counter/{evu_counter}/get/fault_state", + hc_counter_data.get.fault_state) + Pub().pub(f"openWB/set/counter/{evu_counter}/get/fault_str", + hc_counter_data.get.fault_str) + raise Exception(self.EVU_IS_HC_COUNTER_ERROR) + def _calc_home_consumption(self) -> Tuple[float, List]: power = 0 - elements_to_sum_up = self.get_elements_for_downstream_calculation(self.get_id_evu_counter()) + if self.data.config.home_consumption_source_id is None: + id_source = self.get_id_evu_counter() + else: + id_source = self.data.config.home_consumption_source_id + elements_to_sum_up = self.get_elements_for_downstream_calculation(id_source) for element in elements_to_sum_up: if element["type"] == ComponentType.CHARGEPOINT.value: power += data.data.cp_data[f"cp{element['id']}"].data.get.power @@ -143,7 +165,7 @@ def _calc_home_consumption(self) -> Tuple[float, List]: power += data.data.counter_data[f"counter{element['id']}"].data.get.power elif element["type"] == ComponentType.INVERTER.value: power += data.data.pv_data[f"pv{element['id']}"].data.get.power - evu = data.data.counter_data[self.get_evu_counter_str()].data.get.power + evu = data.data.counter_data[f"counter{id_source}"].data.get.power return evu - power - self.data.set.smarthome_power_excluded_from_home_consumption, elements_to_sum_up def _add_hybrid_bat(self, id: int) -> List: @@ -290,7 +312,6 @@ def hierarchy_add_item_aside(self, new_id: int, new_type: ComponentType, id_to_f """ if self.__is_id_in_top_level(id_to_find): self.data.get.hierarchy.append({"id": new_id, "type": new_type.value, "children": []}) - Pub().pub("openWB/set/counter/get/hierarchy", self.data.get.hierarchy) else: if (self.__edit_element_in_hierarchy( self.data.get.hierarchy[0], @@ -301,7 +322,6 @@ def _add_item_aside( self, child: Dict, current_entry: Dict, id_to_find: int, new_id: int, new_type: ComponentType) -> bool: if id_to_find == child["id"]: current_entry["children"].append({"id": new_id, "type": new_type.value, "children": []}) - Pub().pub("openWB/set/counter/get/hierarchy", self.data.get.hierarchy) return True else: return False @@ -315,7 +335,6 @@ def hierarchy_remove_item(self, id_to_find: int, keep_children: bool = True) -> if keep_children: self.data.get.hierarchy.extend(item["children"]) self.data.get.hierarchy.remove(item) - Pub().pub("openWB/set/counter/get/hierarchy", self.data.get.hierarchy) else: if (self.__edit_element_in_hierarchy( self.data.get.hierarchy[0], @@ -327,18 +346,32 @@ def _remove_item(self, child: Dict, current_entry: Dict, id: str, keep_children: if keep_children: current_entry["children"].extend(child["children"]) current_entry["children"].remove(child) - Pub().pub("openWB/set/counter/get/hierarchy", self.data.get.hierarchy) return True else: return False + def hierarchy_add_item_below_evu(self, new_id: int, new_type: ComponentType) -> None: + try: + self.hierarchy_add_item_below(new_id, new_type, self.get_id_evu_counter()) + except (TypeError, IndexError): + if new_type == ComponentType.COUNTER: + # es gibt noch keinen EVU-Zähler + hierarchy = [{ + "id": new_id, + "type": ComponentType.COUNTER.value, + "children": self.data.get.hierarchy + }] + Pub().pub("openWB/set/counter/get/hierarchy", hierarchy) + self.data.get.hierarchy = hierarchy + else: + raise ValueError(self.MISSING_EVU_COUNTER) + def hierarchy_add_item_below(self, new_id: int, new_type: ComponentType, id_to_find: int) -> None: """ruft die rekursive Funktion zum Hinzufügen eines Elements als Kind des angegebenen Elements. """ item = self.__is_id_in_top_level(id_to_find) if item: item["children"].append({"id": new_id, "type": new_type.value, "children": []}) - Pub().pub("openWB/set/counter/get/hierarchy", self.data.get.hierarchy) else: if (self.__edit_element_in_hierarchy( self.data.get.hierarchy[0], @@ -349,7 +382,6 @@ def _add_item_below( self, child: Dict, current_entry: Dict, id_to_find: int, new_id: int, new_type: ComponentType) -> bool: if id_to_find == child["id"]: child["children"].append({"id": new_id, "type": new_type.value, "children": []}) - Pub().pub("openWB/set/counter/get/hierarchy", self.data.get.hierarchy) return True else: return False @@ -419,24 +451,21 @@ def check_and_add(type_name: ComponentType, data_structure): break else: try: - self.hierarchy_add_item_below(entry_num, type_name, self.get_evu_counter().num) - except (TypeError, IndexError): - # es gibt noch keinen EVU-Zähler - hierarchy = [{ - "id": entry_num, - "type": ComponentType.COUNTER.value, - "children": data.data.counter_all_data.data.get.hierarchy - }] - Pub().pub("openWB/set/counter/get/hierarchy", hierarchy) - data.data.counter_all_data.data.get.hierarchy = hierarchy + self.hierarchy_add_item_below_evu(entry_num, type_name) + except ValueError: + pub_system_message({}, "Die Struktur des Lastmanagements ist nicht plausibel. Bitte prüfe die " + "Konfiguration und Anordnung der Komponenten in der Hierarchie.", + MessageType.WARNING) pub_system_message({}, f"{component_type_to_readable_text(type_name)} mit ID {element['id']} wurde" - " in der Hierarchie hinzugefügt, da kein Eintrag in der Hierarchie gefunden " - "wurde. Bitte prüfe die Anordnung der Komponenten in der Hierarchie.", + " in der Struktur des Lastmanagements hinzugefügt, da kein Eintrag in der " + "Struktur gefunden wurde. Bitte prüfe die Anordnung der Komponenten in der " + "Struktur.", MessageType.WARNING) - check_and_add(ComponentType.BAT, data.data.bat_data) + # Falls EVU-Zähler fehlt, zuerst hinzufügen. check_and_add(ComponentType.COUNTER, data.data.counter_data) + check_and_add(ComponentType.BAT, data.data.bat_data) check_and_add(ComponentType.CHARGEPOINT, data.data.cp_data) check_and_add(ComponentType.INVERTER, data.data.pv_data) diff --git a/packages/control/counter_home_consumption_test.py b/packages/control/counter_home_consumption_test.py index b71a09e650..6303dd63e9 100644 --- a/packages/control/counter_home_consumption_test.py +++ b/packages/control/counter_home_consumption_test.py @@ -3,7 +3,7 @@ import pytest from control import data -from packages.conftest import hierarchy_standard, hierarchy_hybrid, hierarchy_nested +from packages.conftest import hierarchy_hc_counter, hierarchy_standard, hierarchy_hybrid, hierarchy_nested from control.counter_all import CounterAll from modules.common.fault_state import FaultStateLevel @@ -13,16 +13,18 @@ pytest.param(hierarchy_hybrid, id="hybrid"), pytest.param(hierarchy_nested, id="nested")]) def test_calc_home_consumption(counter_all: Callable[[], CounterAll], data_): - # c = counter_all() - - # execution home_consumption = c._calc_home_consumption()[0] - - # evaluation assert home_consumption == 500 +def test_calc_home_consumption_hc_counter(data_hc_counter_): + c = hierarchy_hc_counter() + c.data.config.home_consumption_source_id = 6 + home_consumption = c._calc_home_consumption()[0] + assert home_consumption == 1100 + + @pytest.mark.parametrize(["home_consumption", "invalid_home_consumption", "expected_home_consumption", @@ -50,3 +52,15 @@ def test_set_home_consumption(home_consumption: int, # evaluation assert c.data.set.invalid_home_consumption == expected_invalid_home_consumption assert c.data.set.home_consumption == expected_home_consumption + + +def test_validate_home_consumption_counter(monkeypatch): + c = CounterAll() + c.data.config.home_consumption_source_id = 0 + monkeypatch.setattr(c, "get_id_evu_counter", lambda: 0) + monkeypatch.setattr(c, "get_evu_counter_str", lambda: "counter0") + + with pytest.raises(Exception) as e: + c._validate_home_consumption_counter() + + assert str(e.value) == CounterAll.EVU_IS_HC_COUNTER_ERROR diff --git a/packages/control/ev.py b/packages/control/ev.py index 6c8e173294..71e1a74c4b 100644 --- a/packages/control/ev.py +++ b/packages/control/ev.py @@ -18,6 +18,7 @@ from dataclass_utils.factories import empty_dict_factory, empty_list_factory from helpermodules.abstract_plans import Limit, limit_factory, ScheduledChargingPlan, TimeChargingPlan from helpermodules import timecheck +from helpermodules.constants import NO_ERROR from modules.common.abstract_vehicle import VehicleUpdateData from modules.common.configurable_vehicle import ConfigurableVehicle @@ -28,30 +29,39 @@ def get_vehicle_default() -> dict: return { "charge_template": 0, "ev_template": 0, - "name": "Standard-Fahrzeug", + "name": "neues Fahrzeug", "tag_id": [], "get/soc": 0 } -def get_charge_template_default() -> dict: +def get_new_charge_template() -> dict: ct_default = asdict(ChargeTemplateData()) ct_default["chargemode"]["scheduled_charging"].pop("plans") ct_default["time_charging"].pop("plans") return ct_default + +def get_charge_template_default() -> dict: + ct_default = asdict(ChargeTemplateData(name="Lade-Profil")) + ct_default["chargemode"]["scheduled_charging"].pop("plans") + ct_default["time_charging"].pop("plans") + return ct_default + # Avoid anti-pattern: mutable default arguments @dataclass class ScheduledCharging: - plans: Dict[int, ScheduledChargingPlan] = field(default_factory=empty_dict_factory) + plans: Dict[int, ScheduledChargingPlan] = field(default_factory=empty_dict_factory, metadata={ + "topic": ""}) @dataclass class TimeCharging: active: bool = False - plans: Dict[int, TimeChargingPlan] = field(default_factory=empty_dict_factory) + plans: Dict[int, TimeChargingPlan] = field(default_factory=empty_dict_factory, metadata={ + "topic": ""}) @dataclass @@ -109,8 +119,7 @@ def et_factory() -> Et: @dataclass class ChargeTemplateData: - name: str = "Standard-Lade-Profil" - disable_after_unplug: bool = False + name: str = "neues Lade-Profil" prio: bool = False load_default: bool = False et: Et = field(default_factory=et_factory) @@ -118,9 +127,13 @@ class ChargeTemplateData: chargemode: Chargemode = field(default_factory=chargemode_factory) +def charge_template_data_factory() -> ChargeTemplateData: + return ChargeTemplateData() + + @dataclass class EvTemplateData: - name: str = "Standard-Fahrzeug-Profil" + name: str = "neues Fahrzeug-Profil" max_current_multi_phases: int = 16 max_phases: int = 3 phase_switch_pause: int = 2 @@ -130,7 +143,7 @@ class EvTemplateData: control_pilot_interruption_duration: int = 4 average_consump: float = 17000 min_current: int = 6 - max_current_single_phase: int = 32 + max_current_single_phase: int = 16 battery_capacity: float = 82000 efficiency: float = 90 nominal_difference: float = 1 @@ -146,7 +159,8 @@ class EvTemplate: """ Klasse mit den EV-Daten """ - data: EvTemplateData = field(default_factory=ev_template_data_factory) + data: EvTemplateData = field(default_factory=ev_template_data_factory, metadata={ + "topic": "config"}) et_num: int = 0 @@ -156,7 +170,8 @@ def ev_template_factory() -> EvTemplate: @dataclass class Set: - soc_error_counter: int = 0 + soc_error_counter: int = field( + default=0, metadata={"topic": "set/soc_error_counter"}) def set_factory() -> Set: @@ -165,12 +180,14 @@ def set_factory() -> Set: @dataclass class Get: - soc: Optional[int] = None - soc_timestamp: Optional[float] = None - force_soc_update: bool = False - range: Optional[float] = None - fault_state: int = 0 - fault_str: str = "" + soc: Optional[int] = field(default=None, metadata={"topic": "get/soc"}) + soc_timestamp: Optional[float] = field( + default=None, metadata={"topic": "get/soc_timestamp"}) + force_soc_update: bool = field(default=False, metadata={ + "topic": "get/force_soc_update"}) + range: Optional[float] = field(default=None, metadata={"topic": "get/range"}) + fault_state: int = field(default=0, metadata={"topic": "get/fault_state"}) + fault_str: str = field(default=NO_ERROR, metadata={"topic": "get/fault_str"}) def get_factory() -> Get: @@ -180,10 +197,11 @@ def get_factory() -> Get: @dataclass class EvData: set: Set = field(default_factory=set_factory) - charge_template: int = 0 - ev_template: int = 0 - name: str = "Standard-Fahrzeug" - tag_id: List[str] = field(default_factory=empty_list_factory) + charge_template: int = field(default=0, metadata={"topic": "charge_template"}) + ev_template: int = field(default=0, metadata={"topic": "ev_template"}) + name: str = field(default="neues Fahrzeug", metadata={"topic": "name"}) + tag_id: List[str] = field(default_factory=empty_list_factory, metadata={ + "topic": "tag_id"}) get: Get = field(default_factory=get_factory) @@ -197,6 +215,7 @@ def __init__(self, index: int): self.charge_template: ChargeTemplate = ChargeTemplate(0) self.soc_module: ConfigurableVehicle = None self.chargemode_changed = False + self.submode_changed = False self.num = index self.data = EvData() except Exception: @@ -342,6 +361,9 @@ def set_chargemode_changed(self, control_parameter: ControlParameter, submode: s else: self.chargemode_changed = False + def set_submode_changed(self, control_parameter: ControlParameter, submode: str) -> None: + self.submode_changed = (submode != control_parameter.submode) + def check_min_max_current(self, control_parameter: ControlParameter, required_current: float, @@ -378,8 +400,8 @@ def check_min_max_current(self, CURRENT_OUT_OF_NOMINAL_DIFFERENCE = (", da das Fahrzeug nicht mit der vorgegebenen Stromstärke +/- der erlaubten " + "Stromabweichung aus dem Fahrzeug-Profil lädt.") - ENOUGH_POWER = ", da ausreichend Leistung für mehrphasiges Laden zur Verfügung steht." - NOT_ENOUGH_POWER = ", da nicht ausreichend Leistung für mehrphasiges Laden zur Verfügung steht." + ENOUGH_POWER = ", da ausreichend Überschuss für mehrphasiges Laden zur Verfügung steht." + NOT_ENOUGH_POWER = ", da nicht ausreichend Überschuss für mehrphasiges Laden zur Verfügung steht." def _check_phase_switch_conditions(self, control_parameter: ControlParameter, @@ -398,26 +420,21 @@ def _check_phase_switch_conditions(self, feed_in_yield = pv_config.feed_in_yield else: feed_in_yield = 0 - evu_counter = data.data.counter_all_data.get_evu_counter() - # verbleibender EVU-Überschuss unter Berücksichtigung der Einspeisegrenze und Speicherleistung - all_surplus = (-evu_counter.calc_surplus() - evu_counter.data.set.released_surplus + - evu_counter.data.set.reserved_surplus - feed_in_yield) + all_surplus = data.data.counter_all_data.get_evu_counter().get_usable_surplus(feed_in_yield) + required_surplus = self.ev_template.data.min_current * max_phases_ev * 230 - get_power condition_1_to_3 = (((max(get_currents) > max_current and - all_surplus > self.ev_template.data.min_current * max_phases_ev * 230 - - get_power) or limit == LimitingValue.UNBALANCED_LOAD.value) and + all_surplus > required_surplus) or limit == LimitingValue.UNBALANCED_LOAD.value) and phases_in_use == 1) condition_3_to_1 = max(get_currents) < min_current and all_surplus <= 0 and phases_in_use > 1 if condition_1_to_3 or condition_3_to_1: return True, None else: - if ((phases_in_use > 1 and max(get_currents) > min_current) or - (phases_in_use == 1 and max(get_currents) < max_current)): - return False, self.CURRENT_OUT_OF_NOMINAL_DIFFERENCE + if phases_in_use > 1 and all_surplus > 0: + return False, self.ENOUGH_POWER + elif phases_in_use == 1 and all_surplus < required_surplus: + return False, self.NOT_ENOUGH_POWER else: - if phases_in_use > 1: - return False, self.ENOUGH_POWER - else: - return False, self.NOT_ENOUGH_POWER + return False, self.CURRENT_OUT_OF_NOMINAL_DIFFERENCE PHASE_SWITCH_DELAY_TEXT = '{} Phasen in {}.' @@ -435,17 +452,15 @@ def auto_phase_switch(self, phases_to_use = control_parameter.phases phases_in_use = control_parameter.phases pv_config = data.data.general_data.data.chargemode_config.pv_charging + cm_config = data.data.general_data.data.chargemode_config if self.charge_template.data.chargemode.pv_charging.feed_in_limit: feed_in_yield = pv_config.feed_in_yield else: feed_in_yield = 0 - evu_counter = data.data.counter_all_data.get_evu_counter() - # verbleibender EVU-Überschuss unter Berücksichtigung der Einspeisegrenze und Speicherleistung - all_surplus = (-evu_counter.calc_surplus() - evu_counter.data.set.released_surplus + - evu_counter.data.set.reserved_surplus - feed_in_yield) + all_surplus = data.data.counter_all_data.get_evu_counter().get_usable_surplus(feed_in_yield) if phases_in_use == 1: direction_str = f"Umschaltung von 1 auf {max_phases}" - delay = pv_config.phase_switch_delay * 60 + delay = cm_config.phase_switch_delay * 60 required_reserved_power = (self.ev_template.data.min_current * max_phases * 230 - self.ev_template.data.max_current_single_phase * 230) @@ -453,7 +468,7 @@ def auto_phase_switch(self, new_current = self.ev_template.data.min_current else: direction_str = f"Umschaltung von {max_phases} auf 1" - delay = (16 - pv_config.phase_switch_delay) * 60 + delay = (16 - cm_config.phase_switch_delay) * 60 # Es kann einphasig mit entsprechend niedriger Leistung gestartet werden. required_reserved_power = 0 new_phase = 1 @@ -481,6 +496,8 @@ def auto_phase_switch(self, direction_str, timecheck.convert_timestamp_delta_to_time_string(timestamp_auto_phase_switch, delay)) control_parameter.state = ChargepointState.PHASE_SWITCH_DELAY + elif condition_msg: + log.debug(f"Keine Phasenumschaltung{condition_msg}") else: if condition: # Timer laufen lassen @@ -554,16 +571,17 @@ class SelectedPlan: num: int = 0 +@dataclass class ChargeTemplate: """ Klasse der Lade-Profile """ + ct_num: int + data: ChargeTemplateData = field(default_factory=charge_template_data_factory, metadata={ + "topic": ""}) + BUFFER = -1200 # nach mehr als 20 Min Überschreitung wird der Termin als verpasst angesehen CHARGING_PRICE_EXCEEDED = "Keine Ladung, da der aktuelle Strompreis über dem maximalen Strompreis liegt." - def __init__(self, index): - self.data: ChargeTemplateData = ChargeTemplateData() - self.ct_num = index - TIME_CHARGING_NO_PLAN_CONFIGURED = "Keine Ladung, da keine Zeitfenster für Zeitladen konfiguriert sind." TIME_CHARGING_NO_PLAN_ACTIVE = "Keine Ladung, da kein Zeitfenster für Zeitladen aktiv ist." TIME_CHARGING_SOC_REACHED = "Kein Zeitladen, da der Soc bereits erreicht wurde." @@ -679,7 +697,8 @@ def scheduled_charging_recent_plan(self, Ladestrom ein. Um etwas mehr Puffer zu haben, wird bis 20 Min nach dem Zieltermin noch geladen, wenn dieser nicht eingehalten werden konnte. """ - if phase_switch_supported and data.data.general_data.get_phases_chargemode("scheduled_charging") == 0: + if phase_switch_supported and data.data.general_data.get_phases_chargemode("scheduled_charging", + "instant_charging") == 0: max_current = ev_template.data.max_current_multi_phases plan_data = self.search_plan(max_current, soc, ev_template, max_phases, used_amount) if plan_data: @@ -755,7 +774,11 @@ def calculate_duration(self, duration = missing_amount/(plan.current * phases*230) * 3600 return duration, missing_amount - SCHEDULED_CHARGING_REACHED_LIMIT_SOC = "Kein Zielladen, da der Ziel-Soc und das SoC-Limit bereits erreicht wurden." + SCHEDULED_REACHED_LIMIT_SOC = ("Kein Zielladen, da noch Zeit bis zum Zieltermin ist. " + "Kein Zielladen mit Überschuss, da das SoC-Limit für Überschuss-Laden " + + "erreicht wurde.") + SCHEDULED_CHARGING_REACHED_LIMIT_SOC = ("Kein Zielladen, da das Limit für Fahrzeug Laden mit Überschuss (SoC-Limit)" + " sowie der Fahrzeug-SoC (Ziel-SoC) bereits erreicht wurde.") SCHEDULED_CHARGING_REACHED_AMOUNT = "Kein Zielladen, da die Energiemenge bereits erreicht wurde." SCHEDULED_CHARGING_REACHED_SCHEDULED_SOC = ("Falls vorhanden wird mit EVU-Überschuss geladen, da der Ziel-Soc " "für Zielladen bereits erreicht wurde.") @@ -767,7 +790,8 @@ def calculate_duration(self, "Es wird bis max. 20 Minuten nach dem angegebenen Zieltermin geladen.") SCHEDULED_CHARGING_LIMITED_BY_SOC = 'einen SoC von {}%' SCHEDULED_CHARGING_LIMITED_BY_AMOUNT = '{}kWh geladene Energie' - SCHEDULED_CHARGING_IN_TIME = 'Zielladen mit {}A, um {} um {} zu erreichen.' + SCHEDULED_CHARGING_IN_TIME = ('Zielladen mit mindestens {}A, um {} um {} zu erreichen. Falls vorhanden wird ' + 'zusätzlich EVU-Überschuss geladen.') SCHEDULED_CHARGING_CHEAP_HOUR = "Zielladen, da ein günstiger Zeitpunkt zum preisbasierten Laden ist." SCHEDULED_CHARGING_EXPENSIVE_HOUR = ("Zielladen ausstehend, da jetzt kein günstiger Zeitpunkt zum preisbasierten " "Laden ist. Falls vorhanden, wird mit Überschuss geladen.") @@ -780,22 +804,22 @@ def scheduled_charging_calc_current(self, min_current: int, soc_request_intervall_offset: int) -> Tuple[float, str, str, int]: current = 0 - mode = "stop" + submode = "stop" if plan_data is None: if len(self.data.chargemode.scheduled_charging.plans) == 0: - return current, mode, self.SCHEDULED_CHARGING_NO_PLANS_CONFIGURED, control_parameter_phases + return current, submode, self.SCHEDULED_CHARGING_NO_PLANS_CONFIGURED, control_parameter_phases else: - return current, mode, self.SCHEDULED_CHARGING_NO_DATE_PENDING, control_parameter_phases + return current, submode, self.SCHEDULED_CHARGING_NO_DATE_PENDING, control_parameter_phases current_plan = self.data.chargemode.scheduled_charging.plans[plan_data.num] limit = current_plan.limit phases = plan_data.phases log.debug("Verwendeter Plan: "+str(current_plan.name)) - if limit.selected == "soc" and soc >= limit.soc_limit: + if limit.selected == "soc" and soc >= limit.soc_limit and soc >= limit.soc_scheduled: message = self.SCHEDULED_CHARGING_REACHED_LIMIT_SOC elif limit.selected == "soc" and limit.soc_scheduled <= soc < limit.soc_limit: message = self.SCHEDULED_CHARGING_REACHED_SCHEDULED_SOC current = min_current - mode = "pv_charging" + submode = "pv_charging" # bei Überschuss-Laden mit der Phasenzahl aus den control_parameter laden, # um die Umschaltung zu berücksichtigen. phases = control_parameter_phases @@ -810,7 +834,7 @@ def scheduled_charging_calc_current(self, message = self.SCHEDULED_CHARGING_IN_TIME.format( plan_data.available_current, limit_string, current_plan.time) current = plan_data.available_current - mode = "instant_charging" + submode = "instant_charging" # weniger als die berechnete Zeit verfügbar # Ladestart wurde um maximal 20 Min verpasst. elif plan_data.remaining_time <= 0 - soc_request_intervall_offset: @@ -820,7 +844,7 @@ def scheduled_charging_calc_current(self, current = min(plan_data.missing_amount/((plan_data.duration + plan_data.remaining_time) / 3600)/(phases*230), plan_data.max_current) message = self.SCHEDULED_CHARGING_MAX_CURRENT.format(round(current, 2)) - mode = "instant_charging" + submode = "instant_charging" else: # Wenn Elektronische Tarife aktiv sind, prüfen, ob jetzt ein günstiger Zeitpunkt zum Laden # ist. @@ -830,18 +854,24 @@ def scheduled_charging_calc_current(self, if timecheck.is_list_valid(hourlist): message = self.SCHEDULED_CHARGING_CHEAP_HOUR current = plan_data.available_current - mode = "instant_charging" - else: + submode = "instant_charging" + elif soc <= limit.soc_limit: message = self.SCHEDULED_CHARGING_EXPENSIVE_HOUR current = min_current - mode = "pv_charging" + submode = "pv_charging" phases = control_parameter_phases + else: + message = self.SCHEDULED_REACHED_LIMIT_SOC else: - message = self.SCHEDULED_CHARGING_USE_PV - current = min_current - mode = "pv_charging" - phases = control_parameter_phases - return current, mode, message, phases + # Wenn SoC-Limit erreicht wurde, soll nicht mehr mit Überschuss geladen werden + if soc >= limit.soc_limit: + message = self.SCHEDULED_REACHED_LIMIT_SOC + else: + message = self.SCHEDULED_CHARGING_USE_PV + current = min_current + submode = "pv_charging" + phases = control_parameter_phases + return current, submode, message, phases def standby(self) -> Tuple[int, str, str]: return 0, "standby", "Keine Ladung, da der Lademodus Standby aktiv ist." diff --git a/packages/control/ev_charge_template_test.py b/packages/control/ev_charge_template_test.py index a990f892a2..22be9e628f 100644 --- a/packages/control/ev_charge_template_test.py +++ b/packages/control/ev_charge_template_test.py @@ -26,24 +26,24 @@ def data_module() -> None: pytest.param({"0": TimeChargingPlan()}, 0, 0, None, (0, "stop", ChargeTemplate.TIME_CHARGING_NO_PLAN_ACTIVE, None), id="no plan active"), pytest.param({"0": TimeChargingPlan()}, 0, 0, TimeChargingPlan(), - (16, "time_charging", None, "Zeitladen-Standard"), id="plan active"), + (16, "time_charging", None, "neuer Zeitladen-Plan"), id="plan active"), pytest.param({"0": TimeChargingPlan(limit=Limit(selected="soc"))}, 100, 0, TimeChargingPlan(limit=Limit(selected="soc")), - (0, "stop", ChargeTemplate.TIME_CHARGING_SOC_REACHED, "Zeitladen-Standard"), + (0, "stop", ChargeTemplate.TIME_CHARGING_SOC_REACHED, "neuer Zeitladen-Plan"), id="plan active, soc is reached"), pytest.param({"0": TimeChargingPlan(limit=Limit(selected="soc"))}, 40, 0, TimeChargingPlan(limit=Limit(selected="soc")), - (16, "time_charging", None, "Zeitladen-Standard"), id="plan active, soc is not reached"), + (16, "time_charging", None, "neuer Zeitladen-Plan"), id="plan active, soc is not reached"), pytest.param({"0": TimeChargingPlan(limit=Limit(selected="soc"))}, None, 0, TimeChargingPlan(limit=Limit(selected="soc")), - (16, "time_charging", None, "Zeitladen-Standard"), id="plan active, soc is not defined"), + (16, "time_charging", None, "neuer Zeitladen-Plan"), id="plan active, soc is not defined"), pytest.param({"0": TimeChargingPlan(limit=Limit(selected="amount"))}, 0, 1500, TimeChargingPlan(limit=Limit(selected="amount")), - (0, "stop", ChargeTemplate.TIME_CHARGING_AMOUNT_REACHED, "Zeitladen-Standard"), + (0, "stop", ChargeTemplate.TIME_CHARGING_AMOUNT_REACHED, "neuer Zeitladen-Plan"), id="plan active, used_amount_time_charging is reached"), pytest.param({"0": TimeChargingPlan(limit=Limit(selected="amount"))}, 0, 500, TimeChargingPlan(limit=Limit(selected="amount")), - (16, "time_charging", None, "Zeitladen-Standard"), + (16, "time_charging", None, "neuer Zeitladen-Plan"), id="plan active, used_amount_time_charging is not reached"), pytest.param({"0": TimeChargingPlan()}, 0, 0, None, (0, "stop", ChargeTemplate.TIME_CHARGING_NO_PLAN_ACTIVE, None), id="plan defined but not found"), diff --git a/packages/control/general.py b/packages/control/general.py index 157f5b8659..3b7d4f3418 100644 --- a/packages/control/general.py +++ b/packages/control/general.py @@ -4,12 +4,11 @@ from enum import Enum import logging import random -from typing import List, Optional +from typing import Dict, List, Optional from control import data from control.bat_all import BatConsiderationMode from helpermodules.constants import NO_ERROR -from helpermodules.pub import Pub from helpermodules import timecheck from modules.common.configurable_ripple_control_receiver import ConfigurableRcr from modules.ripple_control_receivers.gpio.config import GpioRcr @@ -20,7 +19,8 @@ @dataclass class InstantCharging: - phases_to_use: int = 1 + phases_to_use: int = field(default=1, metadata={ + "topic": "chargemode_config/instant_charging/phases_to_use"}) def instant_charging_factory() -> InstantCharging: @@ -33,20 +33,34 @@ def control_range_factory() -> List: @dataclass class PvCharging: - bat_power_reserve: int = 2000 - bat_power_reserve_active: bool = False - control_range: List = field(default_factory=control_range_factory) - feed_in_yield: int = 15000 - phase_switch_delay: int = 7 - phases_to_use: int = 1 - bat_power_discharge: int = 1500 - bat_power_discharge_active: bool = False - min_bat_soc: int = 50 - bat_mode: BatConsiderationMode = BatConsiderationMode.EV_MODE.value - switch_off_delay: int = 60 - switch_off_threshold: int = 5 - switch_on_delay: int = 30 - switch_on_threshold: int = 1500 + bat_power_reserve: int = field(default=2000, metadata={ + "topic": "chargemode_config/pv_charging/bat_power_reserve"}) + bat_power_reserve_active: bool = field(default=False, metadata={ + "topic": "chargemode_config/pv_charging/bat_power_reserve_active"}) + control_range: List = field(default_factory=control_range_factory, metadata={ + "topic": "chargemode_config/pv_charging/control_range"}) + feed_in_yield: int = field(default=15000, metadata={ + "topic": "chargemode_config/pv_charging/feed_in_yield"}) + phase_switch_delay: int = field(default=7, metadata={ + "topic": "chargemode_config/pv_charging/phase_switch_delay"}) + phases_to_use: int = field(default=1, metadata={ + "topic": "chargemode_config/pv_charging/phases_to_use"}) + bat_power_discharge: int = field(default=1500, metadata={ + "topic": "chargemode_config/pv_charging/bat_power_discharge"}) + bat_power_discharge_active: bool = field(default=False, metadata={ + "topic": "chargemode_config/pv_charging/bat_power_discharge_active"}) + min_bat_soc: int = field(default=50, metadata={ + "topic": "chargemode_config/pv_charging/min_bat_soc"}) + bat_mode: BatConsiderationMode = field(default=BatConsiderationMode.EV_MODE.value, metadata={ + "topic": "chargemode_config/pv_charging/bat_mode"}) + switch_off_delay: int = field(default=60, metadata={ + "topic": "chargemode_config/pv_charging/switch_off_delay"}) + switch_off_threshold: int = field(default=5, metadata={ + "topic": "chargemode_config/pv_charging/switch_off_threshold"}) + switch_on_delay: int = field(default=30, metadata={ + "topic": "chargemode_config/pv_charging/switch_on_delay"}) + switch_on_threshold: int = field(default=1500, metadata={ + "topic": "chargemode_config/pv_charging/switch_on_threshold"}) def pv_charging_factory() -> PvCharging: @@ -55,7 +69,10 @@ def pv_charging_factory() -> PvCharging: @dataclass class ScheduledCharging: - phases_to_use: int = 0 + phases_to_use: int = field(default=0, metadata={ + "topic": "chargemode_config/scheduled_charging/phases_to_use"}) + phases_to_use_pv: int = field(default=0, metadata={ + "topic": "chargemode_config/scheduled_charging/phases_to_use_pv"}) def scheduled_charging_factory() -> ScheduledCharging: @@ -64,7 +81,8 @@ def scheduled_charging_factory() -> ScheduledCharging: @dataclass class TimeCharging: - phases_to_use: int = 1 + phases_to_use: int = field(default=1, metadata={ + "topic": "chargemode_config/time_charging/phases_to_use"}) def time_charging_factory() -> TimeCharging: @@ -74,12 +92,18 @@ def time_charging_factory() -> TimeCharging: @dataclass class ChargemodeConfig: instant_charging: InstantCharging = field(default_factory=instant_charging_factory) + phase_switch_delay: int = field(default=7, metadata={ + "topic": "chargemode_config/phase_switch_delay"}) pv_charging: PvCharging = field(default_factory=pv_charging_factory) - retry_failed_phase_switches: bool = False + retry_failed_phase_switches: bool = field( + default=False, + metadata={"topic": "chargemode_config/retry_failed_phase_switches"}) scheduled_charging: ScheduledCharging = field(default_factory=scheduled_charging_factory) time_charging: TimeCharging = field(default_factory=time_charging_factory) - unbalanced_load_limit: int = 18 - unbalanced_load: bool = False + unbalanced_load_limit: int = field( + default=18, metadata={"topic": "chargemode_config/unbalanced_load_limit"}) + unbalanced_load: bool = field(default=False, metadata={ + "topic": "chargemode_config/unbalanced_load"}) def chargemode_config_factory() -> ChargemodeConfig: @@ -88,9 +112,12 @@ def chargemode_config_factory() -> ChargemodeConfig: @dataclass class RippleControlReceiverGet: - fault_state: int = 0 - fault_str: str = NO_ERROR - override_value: float = 100 + fault_state: int = field(default=0, metadata={ + "topic": "ripple_control_receiver/get/fault_state"}) + fault_str: str = field(default=NO_ERROR, metadata={ + "topic": "ripple_control_receiver/get/fault_str"}) + override_value: float = field(default=100, metadata={ + "topic": "ripple_control_receiver/get/override_value"}) def rcr_get_factory() -> RippleControlReceiverGet: @@ -109,8 +136,10 @@ class OverrideReference(Enum): @dataclass class RippleControlReceiver: get: RippleControlReceiverGet = field(default_factory=rcr_get_factory) - module: ConfigurableRcr = field(default_factory=gpio_rcr_factory) - overrice_reference: OverrideReference = OverrideReference.CHARGEPOINT + module: Optional[Dict] = field(default=None, metadata={ + "topic": "ripple_control_receiver/module"}) + override_reference: OverrideReference = field(default=OverrideReference.CHARGEPOINT, metadata={ + "topic": "ripple_control_receiver/override_reference"}) def ripple_control_receiver_factory() -> RippleControlReceiver: @@ -119,10 +148,10 @@ def ripple_control_receiver_factory() -> RippleControlReceiver: @dataclass class Prices: - bat: float = 0.0002 - cp: float = 0 - grid: float = 0.0003 - pv: float = 0.00015 + bat: float = field(default=0.0002, metadata={"topic": "prices/bat"}) + cp: float = field(default=0, metadata={"topic": "prices/cp"}) + grid: float = field(default=0.0003, metadata={"topic": "prices/grid"}) + pv: float = field(default=0.00015, metadata={"topic": "prices/pv"}) def prices_factory() -> Prices: @@ -132,14 +161,22 @@ def prices_factory() -> Prices: @dataclass class GeneralData: chargemode_config: ChargemodeConfig = field(default_factory=chargemode_config_factory) - control_interval: int = 10 - extern_display_mode: str = "primary" - extern: bool = False - external_buttons_hw: bool = False - grid_protection_active: bool = False - grid_protection_configured: bool = True - grid_protection_random_stop: int = 0 - grid_protection_timestamp: Optional[float] = "" + control_interval: int = field(default=10, metadata={"topic": "control_interval"}) + extern_display_mode: str = field(default="primary", metadata={ + "topic": "extern_display_mode"}) + extern: bool = field(default=False, metadata={"topic": "extern"}) + external_buttons_hw: bool = field( + default=False, metadata={"topic": "external_buttons_hw"}) + grid_protection_active: bool = field( + default=False, metadata={"topic": "grid_protection_active"}) + grid_protection_configured: bool = field( + default=True, metadata={"topic": "grid_protection_configured"}) + grid_protection_random_stop: int = field( + default=0, metadata={"topic": "grid_protection_random_stop"}) + grid_protection_timestamp: Optional[float] = field( + default=None, metadata={"topic": "grid_protection_timestamp"}) + http_api: bool = field( + default=False, metadata={"topic": "http_api"}) mqtt_bridge: bool = False prices: Prices = field(default_factory=prices_factory) range_unit: str = "km" @@ -152,8 +189,9 @@ class General: def __init__(self): self.data: GeneralData = GeneralData() + self.ripple_control_receiver: ConfigurableRcr = None - def get_phases_chargemode(self, chargemode: str) -> Optional[int]: + def get_phases_chargemode(self, chargemode: str, submode: str) -> Optional[int]: """ gibt die Anzahl Phasen zurück, mit denen im jeweiligen Lademodus geladen wird. Wenn der Lademodus Stop oder Standby ist, wird 0 zurückgegeben, da in diesem Fall die bisher genutzte Phasenzahl weiter genutzt wird, bis der Algorithmus eine Umschaltung vorgibt. @@ -162,6 +200,9 @@ def get_phases_chargemode(self, chargemode: str) -> Optional[int]: if chargemode == "stop" or chargemode == "standby": # bei diesen Lademodi kann die bisherige Phasenzahl beibehalten werden. return None + elif chargemode == "scheduled_charging" and submode == "pv_charging": + # Phasenumschaltung bei PV-Ueberschuss nutzen + return getattr(self.data.chargemode_config, chargemode).phases_to_use_pv else: return getattr(self.data.chargemode_config, chargemode).phases_to_use except Exception: @@ -185,36 +226,20 @@ def grid_protection(self): self.data.grid_protection_timestamp = timecheck.create_timestamp( ) self.data.grid_protection_active = True - Pub().pub("openWB/set/general/grid_protection_timestamp", - self.data.grid_protection_timestamp) - Pub().pub("openWB/set/general/grid_protection_random_stop", - self.data.grid_protection_random_stop) - Pub().pub("openWB/set/general/grid_protection_active", - self.data.grid_protection_active) log.info("Netzschutz aktiv! Frequenz: " + str(data.data.counter_data[evu_counter].data.get.frequency)+"Hz") if 5180 < frequency < 5300: self.data.grid_protection_random_stop = 0 self.data.grid_protection_timestamp = None self.data.grid_protection_active = True - Pub().pub("openWB/set/general/grid_protection_timestamp", - self.data.grid_protection_timestamp) - Pub().pub("openWB/set/general/grid_protection_random_stop", - self.data.grid_protection_random_stop) - Pub().pub("openWB/set/general/grid_protection_active", - self.data.grid_protection_active) log.info("Netzschutz aktiv! Frequenz: " + str(data.data.counter_data[evu_counter].data.get.frequency)+"Hz") else: if 4962 < frequency < 5100: self.data.grid_protection_active = False - Pub().pub("openWB/set/general/grid_protection_active", - self.data.grid_protection_active) log.info("Netzfrequenz wieder im normalen Bereich. Frequenz: " + str(data.data.counter_data[evu_counter].data.get.frequency)+"Hz") - Pub().pub( - "openWB/set/general/grid_protection_timestamp", None) - Pub().pub( - "openWB/set/general/grid_protection_random_stop", 0) + self.data.grid_protection_timestamp = None + self.data.grid_protection_random_stop = 0 except Exception: log.exception("Fehler im General-Modul") diff --git a/packages/control/hierarchy_test.py b/packages/control/hierarchy_test.py index 2e142e7ec5..297a9d7a3b 100644 --- a/packages/control/hierarchy_test.py +++ b/packages/control/hierarchy_test.py @@ -367,7 +367,23 @@ def test_delete_obsolete_entries(hierarchy, data_): {"id": 5, "type": "cp", "children": []}]}, {"id": 2, "type": "bat", "children": []}, {"id": 1, "type": "inverter", "children": []} - ]}], id="add inverter 1") + ]}], id="add inverter 1"), + pytest.param([{"id": 3, "type": "cp", "children": []}, + {"id": 6, "type": "counter", + "children": [ + {"id": 4, "type": "cp", "children": []}, + {"id": 5, "type": "cp", "children": []}]}, + {"id": 2, "type": "bat", "children": []}], + [{"id": 0, "type": "counter", + "children": [ + {"id": 3, "type": "cp", "children": []}, + {"id": 6, "type": "counter", + "children": [ + {"id": 4, "type": "cp", "children": []}, + {"id": 5, "type": "cp", "children": []}]}, + {"id": 2, "type": "bat", "children": []}, + {"id": 1, "type": "inverter", "children": []} + ]}], id="add evu counter 0") ] ) def test_add_missing_entries(hierarchy, expected_hierarchy, data_, monkeypatch): diff --git a/packages/control/prepare.py b/packages/control/prepare.py index 2bbc9ca383..ee32f486ee 100644 --- a/packages/control/prepare.py +++ b/packages/control/prepare.py @@ -18,6 +18,8 @@ def setup_algorithm(self) -> None: """ bereitet die Daten für den Algorithmus vor und startet diesen. """ try: + data.data.pv_all_data.calc_power_for_all_components() + data.data.bat_all_data.calc_power_for_all_components() for cp in data.data.cp_data.values(): cp.setup_values_at_start() data.data.bat_all_data.setup_bat() diff --git a/packages/control/process.py b/packages/control/process.py index bf34f8579f..e89076e16c 100644 --- a/packages/control/process.py +++ b/packages/control/process.py @@ -69,8 +69,6 @@ def process_algorithm_results(self) -> None: log.error( thread.name + " konnte nicht innerhalb des Timeouts die Werte senden.") - - data.data.counter_all_data.get_evu_counter().put_stats() except Exception: log.exception("Fehler im Process-Modul") diff --git a/packages/dataclass_utils/_dataclass_asdict.py b/packages/dataclass_utils/_dataclass_asdict.py index eee6178f67..5d43362065 100644 --- a/packages/dataclass_utils/_dataclass_asdict.py +++ b/packages/dataclass_utils/_dataclass_asdict.py @@ -20,5 +20,4 @@ def asdict(value): return [None if v is None else asdict(v) for v in value] if not isinstance(value, dict): value = vars(value) - log.debug(value) return {key: None if value is None else asdict(value) for key, value in value.items()} diff --git a/packages/helpermodules/abstract_plans.py b/packages/helpermodules/abstract_plans.py index 77fa3752d4..0e2967cfe8 100644 --- a/packages/helpermodules/abstract_plans.py +++ b/packages/helpermodules/abstract_plans.py @@ -62,18 +62,18 @@ class TimeframePlan(PlanBase): @dataclass class ScheduledChargingPlan(PlanBase): current: int = 14 - name: str = "Zielladen-Standard" + name: str = "neuer Zielladen-Plan" limit: ScheduledLimit = field(default_factory=scheduled_limit_factory) time: str = "07:00" # ToDo: aktuelle Zeit verwenden @dataclass class TimeChargingPlan(TimeframePlan): - name: str = "Zeitladen-Standard" + name: str = "neuer Zeitladen-Plan" current: int = 16 limit: Limit = field(default_factory=limit_factory) @dataclass class AutolockPlan(TimeframePlan): - name: str = "Standard Autolock-Plan" + name: str = "neuer Autolock-Plan" diff --git a/packages/helpermodules/changed_values_handler.py b/packages/helpermodules/changed_values_handler.py index 2a8b093255..5f202e1980 100644 --- a/packages/helpermodules/changed_values_handler.py +++ b/packages/helpermodules/changed_values_handler.py @@ -14,9 +14,8 @@ # In den Metadaten wird unter dem Key der Topic-Suffix ab "openWB/ev/2/" angegeben. Der Topic-Prefix ("openWB/ev/2/") # wird automatisch ermittelt. -# Der Key mutable_by_algorithm ist True, wenn die Werte im Algorithmus geändert werden können. Nur diese Werte werden -# automatisiert gepublished. Werte, die an anderer Stelle gepublished werden, wie zB Zählerstände, werden mit False -# gekennzeichnet. Um eine Dokumentation der Topics zu erhalten, sollten die Metadaten dennoch ausgefüllt werden. +# Der Kontextmanager muss immer verwendet werden, wenn in den Funktionen Werte geändert werden, die nicht gepublished +# werden. # Metadaten werden nur für Felder erzeugt, die gepublished werden sollen, dh bei ganzen Klassen für das Feld der # jeweiligen Klasse. Wenn Werte aus einer instanziierten Klasse gepublished werden sollen, erhält die übergeordnete # Klasse keine Metadaten (siehe Beispiel unten). @@ -34,8 +33,8 @@ # @dataclass # class SampleNested: -# parameter1: bool = field(default=False, metadata={"topic": "get/nested1", "mutable_by_algorithm": True}) -# parameter2: int = field(default=0, metadata={"topic": "get/nested2", "mutable_by_algorithm": True}) +# parameter1: bool = field(default=False, metadata={"topic": "get/nested1"}) +# parameter2: int = field(default=0, metadata={"topic": "get/nested2"}) # def sample_nested() -> SampleNested: @@ -48,12 +47,12 @@ # diese Klasse eingetragen. Die Felder der Konfigurationsklasse bekommen keine Metadaten, da diese nicht einzeln # gepublished werden. # sample_field_class: SampleClass = field( -# default_factory=sample_class, metadata={"topic": "get/field_class", "mutable_by_algorithm": True}) -# sample_field_int: int = field(default=0, metadata={"topic": "get/field_int", "mutable_by_algorithm": True}) +# default_factory=sample_class, metadata={"topic": "get/field_class"}) +# sample_field_int: int = field(default=0, metadata={"topic": "get/field_int"}) # sample_field_immutable: float = field( -# default=0, metadata={"topic": "get/field_immutable", "mutable_by_algorithm": False}) +# default=0, metadata={"topic": "get/field_immutable"}) # sample_field_list: List = field(default_factory=currents_list_factory, metadata={ -# "topic": "get/field_list", "mutable_by_algorithm": True}) +# "topic": "get/field_list"}) # # Bei verschachtelten Klassen, wo der zu publishende Wert auf einer tieferen Ebene liegt, werden nur für den zu # publishenden Wert Metadaten erzeugt. # sample_field_nested: SampleNested = field(default_factory=sample_nested) @@ -78,48 +77,69 @@ def store_initial_values(self): def pub_changed_values(self): try: # publishen der geänderten Werte - self._update_value("openWB/set/bat/", self.prev_data.bat_all_data.data.get, data.data.bat_all_data.data.get) + self._update_value("openWB/set/bat/", self.prev_data.bat_all_data.data, data.data.bat_all_data.data) + self._update_value("openWB/set/chargepoint/", self.prev_data.cp_all_data.data.get, + data.data.cp_all_data.data.get) + self._update_value("openWB/set/counter/", self.prev_data.counter_all_data.data, + data.data.counter_all_data.data) for key, value in data.data.cp_data.items(): + self._update_value(f"openWB/set/chargepoint/{value.num}/", self.prev_data.cp_data[key].data, value.data) + for key, value in data.data.bat_data.items(): + self._update_value(f"openWB/set/bat/{value.num}/", self.prev_data.bat_data[key].data, value.data) + for key, value in data.data.counter_data.items(): + self._update_value(f"openWB/set/counter/{value.num}/", + self.prev_data.counter_data[key].data, value.data) + # chargepoint, ev template, autolock, time and scheduled charging plans mutable_by_algorithm immer false + except Exception as e: + log.exception(e) + + def _update_value(self, topic_prefix, data_inst_previous, data_inst): + try: + for f in fields(data_inst): try: - self._update_value(f"openWB/set/chargepoint/{value.num}/", - self.prev_data.cp_data[key].data, value.data) + changed = False + value = getattr(data_inst, f.name) + if isinstance(value, Enum): + value = value.value + previous_value = getattr(data_inst_previous, f.name) + if isinstance(previous_value, Enum): + previous_value = previous_value.value + if hasattr(f, "metadata"): + if f.metadata.get("topic"): + if isinstance(value, (str, int, float, Dict, List, Tuple)): + if previous_value != value: + changed = True + elif isinstance(value, (bool, type(None))): + if previous_value is not value: + changed = True + else: + dict_prev = dict(asdict(previous_value)) + dict_current = dict(asdict(value)) + if dict_prev != dict_current: + changed = True + value = asdict(value) + previous_value = asdict(previous_value) + + if changed: + topic = f"{topic_prefix}{f.metadata['topic']}" + Pub().pub(topic, value) + log.debug(f"Topic {topic}, Payload {value}, vorherige Payload: {previous_value}") + continue + if is_dataclass(value): + self._update_value(topic_prefix, previous_value, value) except Exception as e: log.exception(e) except Exception as e: log.exception(e) - def _update_value(self, topic_prefix, data_inst_previous, data_inst): - for f in fields(data_inst): - try: - changed = False - value = getattr(data_inst, f.name) - if isinstance(value, Enum): - value = value.value - previous_value = getattr(data_inst_previous, f.name) - if isinstance(previous_value, Enum): - previous_value = previous_value.value - if hasattr(f, "metadata"): - if f.metadata.get("mutable_by_algorithm"): - if isinstance(value, (str, int, float, Dict, List, Tuple)): - if previous_value != value: - changed = True - elif isinstance(value, (bool, type(None))): - if previous_value is not value: - changed = True - else: - dict_prev = dict(asdict(previous_value)) - dict_current = dict(asdict(value)) - if dict_prev != dict_current: - changed = True - value = asdict(value) - previous_value = asdict(previous_value) - - if changed: - topic = f"{topic_prefix}{f.metadata['topic']}" - Pub().pub(topic, value) - log.debug(f"Topic {topic}, Payload {value}, vorherige Payload: {previous_value}") - continue - if is_dataclass(value): - self._update_value(topic_prefix, previous_value, value) - except Exception as e: - log.exception(e) + +class ChangedValuesContext: + def __init__(self, event_module_update_completed: threading.Event): + self.changed_values_handler = ChangedValuesHandler(event_module_update_completed) + + def __enter__(self): + self.changed_values_handler.store_initial_values() + + def __exit__(self, exception_type, exception, exception_traceback) -> bool: + self.changed_values_handler.pub_changed_values() + return False diff --git a/packages/helpermodules/changed_values_handler_test.py b/packages/helpermodules/changed_values_handler_test.py index d42524dbe4..2e9225f435 100644 --- a/packages/helpermodules/changed_values_handler_test.py +++ b/packages/helpermodules/changed_values_handler_test.py @@ -23,8 +23,8 @@ def sample_class() -> SampleClass: @dataclass class SampleNested: - parameter1: bool = field(default=False, metadata={"topic": "get/nested1", "mutable_by_algorithm": True}) - parameter2: int = field(default=0, metadata={"topic": "get/nested2", "mutable_by_algorithm": True}) + parameter1: bool = field(default=False, metadata={"topic": "get/nested1"}) + parameter2: int = field(default=0, metadata={"topic": "get/nested2"}) def sample_nested() -> SampleNested: @@ -41,25 +41,25 @@ def sample_tuple_factory() -> Tuple: @dataclass class SampleData: - sample_field_bool: bool = field(default=False, metadata={"topic": "get/field_bool", "mutable_by_algorithm": True}) + sample_field_bool: bool = field(default=False, metadata={"topic": "get/field_bool"}) sample_field_class: SampleClass = field( - default_factory=sample_class, metadata={"topic": "get/field_class", "mutable_by_algorithm": True}) + default_factory=sample_class, metadata={"topic": "get/field_class"}) sample_field_dict: Dict = field(default_factory=sample_dict_factory, metadata={ - "topic": "get/field_dict", "mutable_by_algorithm": True}) + "topic": "get/field_dict"}) sample_field_enum: ChargepointState = field(default=ChargepointState.CHARGING_ALLOWED, metadata={ - "topic": "get/field_enum", "mutable_by_algorithm": True}) - sample_field_float: float = field(default=0, metadata={"topic": "get/field_float", "mutable_by_algorithm": True}) - sample_field_int: int = field(default=0, metadata={"topic": "get/field_int", "mutable_by_algorithm": True}) + "topic": "get/field_enum"}) + sample_field_float: float = field(default=0, metadata={"topic": "get/field_float"}) + sample_field_int: int = field(default=0, metadata={"topic": "get/field_int"}) sample_field_immutable: float = field( - default=0, metadata={"topic": "get/field_immutable", "mutable_by_algorithm": False}) + default=0, metadata={"topic": "get/field_immutable"}) sample_field_list: List = field(default_factory=currents_list_factory, metadata={ - "topic": "get/field_list", "mutable_by_algorithm": True}) + "topic": "get/field_list"}) sample_field_nested: SampleNested = field(default_factory=sample_nested) sample_field_none: Optional[str] = field( - default="Hi", metadata={"topic": "get/field_none", "mutable_by_algorithm": True}) - sample_field_str: str = field(default="Hi", metadata={"topic": "get/field_str", "mutable_by_algorithm": True}) + default="Hi", metadata={"topic": "get/field_none"}) + sample_field_str: str = field(default="Hi", metadata={"topic": "get/field_str"}) sample_field_tuple: Tuple = field(default_factory=sample_tuple_factory, metadata={ - "topic": "get/field_tuple", "mutable_by_algorithm": True}) + "topic": "get/field_tuple"}) @dataclass @@ -83,7 +83,6 @@ class Params: expected_pub_call=("openWB/get/field_float", 2.5)), Params(name="change int", sample_data=SampleData(sample_field_int=2), expected_pub_call=("openWB/get/field_int", 2)), - Params(name="immutable", sample_data=SampleData(sample_field_immutable=2), expected_calls=0), Params(name="change list", sample_data=SampleData(sample_field_list=[ 10, 0, 0]), expected_pub_call=("openWB/get/field_list", [10, 0, 0])), Params(name="change nested", sample_data=SampleData(sample_field_nested=SampleNested( diff --git a/packages/helpermodules/command.py b/packages/helpermodules/command.py index c296478a15..b36264d845 100644 --- a/packages/helpermodules/command.py +++ b/packages/helpermodules/command.py @@ -16,13 +16,14 @@ from control.chargepoint.chargepoint_template import get_autolock_plan_default, get_chargepoint_template_default # ToDo: move to module commands if implemented +from helpermodules.utils.run_command import run_command from modules.backup_clouds.onedrive.api import generateMSALAuthCode, retrieveMSALTokens from helpermodules.broker import InternalBrokerClient from helpermodules.data_migration.data_migration import MigrateData from helpermodules.measurement_logging.process_log import get_daily_log, get_monthly_log, get_yearly_log from helpermodules.messaging import MessageType, pub_user_message -from helpermodules.parse_send_debug import parse_send_debug_data +from helpermodules.create_debug import create_debug_log from helpermodules.pub import Pub, pub_single from helpermodules.subdata import SubData from helpermodules.utils.topic_parser import decode_payload @@ -168,7 +169,6 @@ def addChargepoint(self, connection_id: str, payload: dict) -> None: def setup_added_chargepoint(): Pub().pub(f'openWB/chargepoint/{new_id}/config', chargepoint_config) Pub().pub(f'openWB/chargepoint/{new_id}/set/manual_lock', False) - Pub().pub(f'openWB/chargepoint/{new_id}/set/change_ev_permitted', False) {Pub().pub(f"openWB/chargepoint/{new_id}/get/"+k, v) for (k, v) in asdict(chargepoint.Get()).items()} self.max_id_hierarchy = self.max_id_hierarchy + 1 Pub().pub("openWB/set/command/max_id/hierarchy", self.max_id_hierarchy) @@ -221,7 +221,7 @@ def setup_added_chargepoint(): MAX_NUM_REACHED = ("Es kann maximal ein interner Ladepunkt für eine openWB Series 1/2 Buchse, Custom, " "Standard oder Standard+ konfiguriert werden. Wenn ein zweiter Ladepunkt für eine " "Duo hinzugefügt werden soll, muss auch für den ersten Ladepunkt Bauart 'Duo' " - "gewählt werden.") + "gewählt und gespeichert werden.") def _check_max_num_of_internal_chargepoints(self, config: Dict) -> Optional[str]: if config["type"] == 'internal_openwb': @@ -328,7 +328,7 @@ def addChargeTemplate(self, connection_id: str, payload: dict) -> None: """ sendet das Topic, zu dem ein neues Lade-Profil erstellt werden soll. """ new_id = self.max_id_charge_template + 1 - charge_template_default = ev.get_charge_template_default() + charge_template_default = ev.get_new_charge_template() Pub().pub("openWB/set/vehicle/template/charge_template/" + str(new_id), charge_template_default) self.max_id_charge_template = new_id @@ -344,7 +344,7 @@ def removeChargeTemplate(self, connection_id: str, payload: dict) -> None: pub_user_message(payload, connection_id, "Die ID ist größer als die maximal vergebene ID.", MessageType.ERROR) if payload["data"]["id"] > 0: - Pub().pub(f'openWB/vehicle/template/charge_template/{payload["data"]["id"]}', "") + ProcessBrokerBranch(f'vehicle/template/charge_template/{payload["data"]["id"]}/').remove_topics() pub_user_message( payload, connection_id, f'Lade-Profil mit ID \'{payload["data"]["id"]}\' gelöscht.', @@ -434,22 +434,10 @@ def set_default(topic: str, defaults: dict): component_default["id"] = new_id general_type = special_to_general_type_mapping(payload["data"]["type"]) try: - data.data.counter_all_data.hierarchy_add_item_below( - new_id, general_type, data.data.counter_all_data.get_id_evu_counter()) - except (TypeError, IndexError): - if general_type == ComponentType.COUNTER: - # es gibt noch keinen EVU-Zähler - hierarchy = [{ - "id": new_id, - "type": ComponentType.COUNTER.value, - "children": data.data.counter_all_data.data.get.hierarchy - }] - Pub().pub("openWB/set/counter/get/hierarchy", hierarchy) - data.data.counter_all_data.data.get.hierarchy = hierarchy - else: - pub_user_message(payload, connection_id, - "Bitte erst einen EVU-Zähler konfigurieren!", MessageType.ERROR) - return + data.data.counter_all_data.hierarchy_add_item_below_evu(new_id, general_type) + except ValueError: + pub_user_message(payload, connection_id, counter_all.CounterAll.MISSING_EVU_COUNTER, MessageType.ERROR) + return # Bei Zählern müssen noch Standardwerte veröffentlicht werden. if general_type == ComponentType.BAT: topic = f"openWB/set/bat/{new_id}" @@ -503,7 +491,7 @@ def removeEvTemplate(self, connection_id: str, payload: dict) -> None: pub_user_message(payload, connection_id, "Die ID ist größer als die maximal vergebene ID.", MessageType.ERROR) if payload["data"]["id"] > 0: - Pub().pub(f'openWB/vehicle/template/ev_template/{payload["data"]["id"]}', "") + ProcessBrokerBranch(f'vehicle/template/ev_template/{payload["data"]["id"]}/').remove_topics() pub_user_message( payload, connection_id, f'Fahrzeug-Profil mit ID \'{payload["data"]["id"]}\' gelöscht.', MessageType.SUCCESS) @@ -548,10 +536,8 @@ def removeVehicle(self, connection_id: str, payload: dict) -> None: def sendDebug(self, connection_id: str, payload: dict) -> None: pub_user_message(payload, connection_id, "Systembericht wird erstellt...", MessageType.INFO) - parent_file = Path(__file__).resolve().parents[2] previous_log_level = SubData.system_data["system"].data["debug_level"] - subprocess.run([str(parent_file / "runs" / "send_debug.sh"), - json.dumps(payload["data"]), parse_send_debug_data()]) + create_debug_log(payload["data"]) Pub().pub("openWB/set/system/debug_level", previous_log_level) pub_user_message(payload, connection_id, "Systembericht wurde versandt.", MessageType.SUCCESS) @@ -572,21 +558,17 @@ def getYearlyLog(self, connection_id: str, payload: dict) -> None: def initCloud(self, connection_id: str, payload: dict) -> None: parent_file = Path(__file__).resolve().parents[2] - try: - result = subprocess.check_output( - ["php", "-f", str(parent_file / "runs" / "cloudRegister.php"), json.dumps(payload["data"])] - ) - # exit status = 0 is success, std_out contains json: {"username", "password"} - result_dict = json.loads(result) - connect_payload = { - "data": result_dict - } - connect_payload["data"]["partner"] = payload["data"]["partner"] - self.connectCloud(connection_id, connect_payload) - pub_user_message(payload, connection_id, "Verbindung zur Cloud wurde eingerichtet.", MessageType.SUCCESS) - except subprocess.CalledProcessError as error: - # exit status = 1 is failure, std_out contains error message - pub_user_message(payload, connection_id, error.output.decode("utf-8", MessageType.ERROR)) + result = run_command( + ["php", "-f", str(parent_file / "runs" / "cloudRegister.php"), json.dumps(payload["data"])] + ) + # exit status = 0 is success, std_out contains json: {"username", "password"} + result_dict = json.loads(result) + connect_payload = { + "data": result_dict + } + connect_payload["data"]["partner"] = payload["data"]["partner"] + self.connectCloud(connection_id, connect_payload) + pub_user_message(payload, connection_id, "Verbindung zur Cloud wurde eingerichtet.", MessageType.SUCCESS) def connectCloud(self, connection_id: str, payload: dict) -> None: cloud_config = bridge.get_cloud_config() @@ -623,27 +605,27 @@ def removeMqttBridge(self, connection_id: str, payload: dict) -> None: def systemReboot(self, connection_id: str, payload: dict) -> None: pub_user_message(payload, connection_id, "Neustart wird ausgeführt.", MessageType.INFO) parent_file = Path(__file__).resolve().parents[2] - subprocess.run([str(parent_file / "runs" / "reboot.sh")]) + run_command([str(parent_file / "runs" / "reboot.sh")]) def systemShutdown(self, connection_id: str, payload: dict) -> None: pub_user_message(payload, connection_id, "openWB wird heruntergefahren.", MessageType.INFO) parent_file = Path(__file__).resolve().parents[2] - subprocess.run([str(parent_file / "runs" / "shutdown.sh")]) + run_command([str(parent_file / "runs" / "shutdown.sh")]) def systemUpdate(self, connection_id: str, payload: dict) -> None: log.info("Update requested") # notify system about running update, notify about end update in script Pub().pub("openWB/system/update_in_progress", True) - if SubData.system_data["system"].data["backup_before_update"]: + if SubData.system_data["system"].data["backup_cloud"]["backup_before_update"]: try: self.createCloudBackup(connection_id, {}) except Exception: pub_user_message(payload, connection_id, - ("Fehler beim Erstellen der Cloud-Sicherung." - f" {traceback.format_exc()}
Update abgebrochen!" - "Bitte Fehlerstatus überprüfen!. " + - "Option Sicherung vor System Update kann unter Datenverwaltung deaktiviert werden."), - MessageType.WARNING) + ("Fehler beim Erstellen der Cloud-Sicherung. Update abgebrochen! " + "Bitte die Cloud-Konfiguration überprüfen! Die Option " + + "Sicherung vor System Update kann unter Datenverwaltung deaktiviert werden."), + MessageType.ERROR) + log.exception("Fehler beim Erstellen der Cloud-Sicherung: ") Pub().pub("openWB/system/update_in_progress", False) return parent_file = Path(__file__).resolve().parents[2] @@ -652,13 +634,13 @@ def systemUpdate(self, connection_id: str, payload: dict) -> None: payload, connection_id, f'Wechsel auf Zweig \'{payload["data"]["branch"]}\' Tag \'{payload["data"]["tag"]}\' gestartet.', MessageType.SUCCESS) - subprocess.run([ + run_command([ str(parent_file / "runs" / "update_self.sh"), str(payload["data"]["branch"]), str(payload["data"]["tag"])]) else: pub_user_message(payload, connection_id, "Update gestartet.", MessageType.INFO) - subprocess.run([ + run_command([ str(parent_file / "runs" / "update_self.sh"), SubData.system_data["system"].data["current_branch"]]) @@ -666,31 +648,20 @@ def systemFetchVersions(self, connection_id: str, payload: dict) -> None: log.info("Fetch versions requested") pub_user_message(payload, connection_id, "Versionsliste wird aktualisiert...", MessageType.INFO) parent_file = Path(__file__).resolve().parents[2] - result = subprocess.run([str(parent_file / "runs" / "update_available_versions.sh")]) - if result.returncode == 0: - pub_user_message(payload, connection_id, "Versionsliste erfolgreich aktualisiert.", MessageType.SUCCESS) - else: - pub_user_message( - payload, connection_id, - f'Version-Status: {result.returncode}
Meldung: {result.stdout.decode("utf-8", MessageType.ERROR)}') + run_command([str(parent_file / "runs" / "update_available_versions.sh")]) + pub_user_message(payload, connection_id, "Versionsliste erfolgreich aktualisiert.", MessageType.SUCCESS) def createBackup(self, connection_id: str, payload: dict) -> None: pub_user_message(payload, connection_id, "Backup wird erstellt...", MessageType.INFO) parent_file = Path(__file__).resolve().parents[2] - result = subprocess.run( + result = run_command( [str(parent_file / "runs" / "backup.sh"), - "1" if "use_extended_filename" in payload["data"] and payload["data"]["use_extended_filename"] else "0"], - stdout=subprocess.PIPE) - if result.returncode == 0: - file_name = result.stdout.decode("utf-8").rstrip('\n') - file_link = "/openWB/data/backup/" + file_name - pub_user_message(payload, connection_id, - "Backup erfolgreich erstellt.
" - f'Jetzt
herunterladen.', MessageType.SUCCESS) - else: - pub_user_message(payload, connection_id, - f'Backup-Status: {result.returncode}
Meldung: {result.stdout.decode("utf-8")}', - MessageType.ERROR) + "1" if "use_extended_filename" in payload["data"] and payload["data"]["use_extended_filename"] else "0"]) + file_name = result.rstrip('\n') + file_link = "/openWB/data/backup/" + file_name + pub_user_message(payload, connection_id, + "Backup erfolgreich erstellt.
" + f'Jetzt herunterladen.', MessageType.SUCCESS) def createCloudBackup(self, connection_id: str, payload: dict) -> None: if SubData.system_data["system"].backup_cloud is not None: @@ -705,18 +676,11 @@ def createCloudBackup(self, connection_id: str, payload: dict) -> None: def restoreBackup(self, connection_id: str, payload: dict) -> None: parent_file = Path(__file__).resolve().parents[2] - result = subprocess.run( - [str(parent_file / "runs" / "prepare_restore.sh")], - stdout=subprocess.PIPE) - if result.returncode == 0: - pub_user_message(payload, connection_id, - "Wiederherstellung wurde vorbereitet. openWB wird jetzt zum Abschluss neu gestartet.", - MessageType.INFO) - self.systemReboot(connection_id, payload) - else: - pub_user_message(payload, connection_id, - f'Restore-Status: {result.returncode}
Meldung: {result.stdout.decode("utf-8")}', - MessageType.ERROR) + run_command([str(parent_file / "runs" / "prepare_restore.sh")]) + pub_user_message(payload, connection_id, + "Wiederherstellung wurde vorbereitet. openWB wird jetzt zum Abschluss neu gestartet.", + MessageType.INFO) + self.systemReboot(connection_id, payload) # ToDo: move to module commands if implemented def requestMSALAuthCode(self, connection_id: str, payload: dict) -> None: @@ -771,7 +735,14 @@ def __enter__(self): def __exit__(self, exception_type, exception, exception_traceback) -> bool: if isinstance(exception, Exception): pub_user_message(self.payload, self.connection_id, - f'Es ist ein interner Fehler aufgetreten: {traceback.format_exc()}', MessageType.ERROR) + f'Es ist ein interner Fehler aufgetreten: {exception}', MessageType.ERROR) + log.error({traceback.format_exc()}) + return True + elif isinstance(exception, subprocess.CalledProcessError): + log.debug(exception.stdout) + pub_user_message(self.payload, self.connection_id, + f'Fehler-Status: {exception.returncode}
Meldung: {exception.stderr}', + MessageType.ERROR) return True else: return False @@ -835,28 +806,52 @@ def on_connect(self, client, userdata, flags, rc): client.subscribe(f'openWB/set/{self.topic_str}#', 2) def __on_message_rm(self, client, userdata, msg): - if decode_payload(msg.payload) != '': - log.debug(f'Gelöschtes Topic: {msg.topic}') - Pub().pub(msg.topic, "") - if "openWB/system/device/" in msg.topic and "component" in msg.topic and "config" in msg.topic: - payload = decode_payload(msg.payload) - topic = type_to_topic_mapping(payload["type"]) - data.data.counter_all_data.hierarchy_remove_item(payload["id"]) - client.subscribe(f'openWB/{topic}/{payload["id"]}/#', 2) - elif re.search("openWB/chargepoint/[0-9]+/config$", msg.topic) is not None: - payload = decode_payload(msg.payload) - if payload["type"] == "external_openwb": - pub_single( - f'openWB/set/internal_chargepoint/{payload["configuration"]["duo_num"]}/data/parent_cp', - None, - hostname=payload["configuration"]["ip_address"]) + try: + if decode_payload(msg.payload) != '': + log.debug(f'Gelöschtes Topic: {msg.topic}') + Pub().pub(msg.topic, "") + if "openWB/system/device/" in msg.topic and "component" in msg.topic and "config" in msg.topic: + payload = decode_payload(msg.payload) + topic = type_to_topic_mapping(payload["type"]) + data.data.counter_all_data.hierarchy_remove_item(payload["id"]) + client.subscribe(f'openWB/{topic}/{payload["id"]}/#', 2) + elif re.search("openWB/chargepoint/[0-9]+/config$", msg.topic) is not None: + payload = decode_payload(msg.payload) + if payload["type"] == "external_openwb": + pub_single( + f'openWB/set/internal_chargepoint/{payload["configuration"]["duo_num"]}/data/parent_cp', + None, + hostname=payload["configuration"]["ip_address"]) + elif re.search("openWB/chargepoint/template/[0-9]+$", msg.topic) is not None: + for cp in SubData.cp_data.values(): + if cp.chargepoint.data.config.template == int(msg.topic.split("/")[-1]): + pub_single(f'openWB/set/chargepoint/{cp.chargepoint.num}/config/template', 0) + elif re.search("openWB/vehicle/template/charge_template/[0-9]+$", msg.topic) is not None: + for vehicle in SubData.ev_data.values(): + if vehicle.data.charge_template == int(msg.topic.split("/")[-1]): + pub_single(f'openWB/set/vehicle/{vehicle.num}/charge_template', 0) + elif re.search("openWB/vehicle/template/ev_template/[0-9]+$", msg.topic) is not None: + for vehicle in SubData.ev_data.values(): + if vehicle.data.ev_template == int(msg.topic.split("/")[-1]): + pub_single(f'openWB/set/vehicle/{vehicle.num}/ev_template', 0) + except Exception: + log.exception("Fehler in ProcessBrokerBranch") def __on_message_max_id(self, client, userdata, msg): - self.received_topics.append(msg.topic) + try: + self.received_topics.append(msg.topic) + except Exception: + log.exception("Fehler in ProcessBrokerBranch") def __get_payload(self, client, userdata, msg): - self.payload = msg.payload + try: + self.payload = msg.payload + except Exception: + log.exception("Fehler in ProcessBrokerBranch") def __on_message_mqtt_bridge_exists(self, client, userdata, msg): - if decode_payload(msg.payload)["name"] == self.name: - self.mqtt_bridge_exists = True + try: + if decode_payload(msg.payload)["name"] == self.name: + self.mqtt_bridge_exists = True + except Exception: + log.exception("Fehler in ProcessBrokerBranch") diff --git a/packages/helpermodules/create_debug.py b/packages/helpermodules/create_debug.py new file mode 100644 index 0000000000..9b2455a51c --- /dev/null +++ b/packages/helpermodules/create_debug.py @@ -0,0 +1,279 @@ +import os +import time +import logging +from pathlib import Path +import pprint +from typing import Any, Optional +import requests + +from control import data +from control.chargepoint.chargepoint import Chargepoint +import dataclass_utils +from helpermodules import subdata +from helpermodules.broker import InternalBrokerClient +from helpermodules.pub import Pub +from helpermodules.utils.run_command import run_command +from helpermodules.utils.topic_parser import decode_payload +from modules.common import req +from modules.common.abstract_device import AbstractDevice + +log = logging.getLogger(__name__) + + +def config_and_state(): + parsed_data = "" + try: + secondary = subdata.SubData.general_data.data.extern + except Exception: + secondary = False + + with ErrorHandlingContext(): + parent_file = Path(__file__).resolve().parents[2] + with open(f"{parent_file}/web/version", "r") as f: + version = f.read().strip() + with open(f"{parent_file}/web/lastcommit", "r") as f: + lastcommit = f.read().strip() + parsed_data += f"# Version\n{version}\n{lastcommit}\n\n" + with ErrorHandlingContext(): + parsed_data += f"# Cloud/Brücken\n{BrokerContent().get_bridges()}" + with ErrorHandlingContext(): + chargemode_config = data.data.general_data.data.chargemode_config + parsed_data += "\n# Allgemein\n" + if secondary is False: + parsed_data += (f"Modus: Primary\nHausverbrauch: {data.data.counter_all_data.data.set.home_consumption}W\n" + f"Phasenvorgabe: Sofortladen {chargemode_config.instant_charging.phases_to_use}, Zielladen " + f"{chargemode_config.scheduled_charging.phases_to_use}, Zeitladen: " + f"{chargemode_config.time_charging.phases_to_use}, PV-Laden: " + f"{chargemode_config.pv_charging.phases_to_use}, Einschaltschwelle: " + f"{chargemode_config.pv_charging.switch_on_threshold}W, Ausschaltschwelle: " + f"{chargemode_config.pv_charging.switch_off_threshold}W\n" + f"Regelintervall: {data.data.general_data.data.control_interval}s, ") + else: + parsed_data += "Modus: Secondary\n" + parsed_data += f"Display aktiviert: {data.data.optional_data.data.int_display.active}\n" + + if secondary is False: + with ErrorHandlingContext(): + pretty_hierarchy = pprint.pformat(data.data.counter_all_data.data.get.hierarchy, + indent=4, compact=True, sort_dicts=False, width=100) + parsed_data += f"\n# Hierarchie\n{pretty_hierarchy}\n" + + with ErrorHandlingContext(): + if secondary: + with ErrorHandlingContext(): + parsed_data += "\n# Ladepunkte\n" + for cp in subdata.SubData.cp_data.values(): + parsed_data += get_parsed_cp_data(cp.chargepoint) + else: + parsed_data += "\n# Geräte und Komponenten\n" + for key, value in data.data.system_data.items(): + with ErrorHandlingContext(): + if isinstance(value, AbstractDevice): + parsed_data += f"{key}: {dataclass_utils.asdict(value.device_config)}\n" + for comp_key, comp_value in value.components.items(): + parsed_data += f"{comp_key}: {dataclass_utils.asdict(comp_value.component_config)}\n" + if "bat" in comp_value.component_config.type: + component_data = data.data.bat_data[f"bat{comp_value.component_config.id}"] + elif "counter" in comp_value.component_config.type: + component_data = data.data.counter_data[f"counter{comp_value.component_config.id}"] + elif "inverter" in comp_value.component_config.type: + component_data = data.data.pv_data[f"pv{comp_value.component_config.id}"] + if "bat" in comp_value.component_config.type: + parsed_data += (f"Leistung: {component_data.data.get.power/1000}kW, " + f"SoC: {component_data.data.get.soc}%, " + f"Fehlerstatus: {component_data.data.get.fault_str}\n") + elif "inverter" in comp_value.component_config.type: + parsed_data += (f"Leistung: {component_data.data.get.power/1000}kW, " + f"Fehlerstatus: {component_data.data.get.fault_str}\n") + else: + counter_all_data = data.data.counter_all_data + if counter_all_data.get_evu_counter_str() == f"counter{component_data.num}": + parsed_data += (f"{comp_key}: EVU-Zähler -> max. Leistung " + f"{component_data.data.config.max_total_power}, " + f"max. Ströme {component_data.data.config.max_currents}; ") + elif counter_all_data.data.config.home_consumption_source_id == component_data.num: + parsed_data += (f"{comp_key}: Hausverbrauchszähler -> max. Leistung " + f"{component_data.data.config.max_total_power}, " + f"max. Ströme {component_data.data.config.max_currents}; ") + else: + parsed_data += f"{key}: max. Ströme {component_data.data.config.max_currents}" + parsed_data += (f"Leistung: {component_data.data.get.power/1000}kW, Ströme: " + f"{component_data.data.get.currents}A, Fehlerstatus: " + f"{component_data.data.get.fault_str}\n") + with ErrorHandlingContext(): + parsed_data += "\n# Ladepunkte\n" + parsed_data += f"Ladeleistung aller Ladepunkte {data.data.cp_all_data.data.get.power / 1000}kW\n" + for cp in data.data.cp_data.values(): + parsed_data += get_parsed_cp_data(cp) + return parsed_data + + +def get_parsed_cp_data(cp: Chargepoint) -> str: + parsed_data = "" + with ErrorHandlingContext(): + if hasattr(cp.chargepoint_module.config.configuration, "ip_address"): + ip = cp.chargepoint_module.config.configuration.ip_address + else: + ip = None + parsed_data += (f"LP{cp.num}: Typ: {cp.chargepoint_module.config.type}; IP: " + f"{ip}; Stecker-Status: {cp.data.get.plug_state}, Leistung: " + f"{cp.data.get.power/1000}kW, {cp.data.get.currents}A, {cp.data.get.voltages}V, Lademodus: " + f"{cp.data.control_parameter.chargemode}, Submode: " + f"{cp.data.control_parameter.submode}, Sollstrom: " + f"{cp.data.set.current}A, Status: {cp.data.get.state_str}, " + f"Fehlerstatus: {cp.data.get.fault_str}\n") + if cp.chargepoint_module.config.type == "openwb_pro": + try: + parsed_data += f"{req.get_http_session().get(f'http://{ip}/connect.php', timeout=5).text}\n" + except requests.Timeout: + parsed_data += "Timeout beim Abrufen der Daten\n" + return parsed_data + + +openwb_base_dir = Path(__file__).resolve().parents[2] +ramdisk_dir = openwb_base_dir / 'ramdisk' +debug_file = ramdisk_dir / 'debug.log' + + +def merge_log_files(log_name, num_lines): + log_files = [f"{ramdisk_dir}/{log_name}.log.{i}" for i in range(5, 1)] + log_files.append(f"{ramdisk_dir}/{log_name}.log") + + lines = [] + try: + for log_file in log_files: + if os.path.isfile(log_file): + with open(log_file, 'r') as file: + lines += file.readlines() + except Exception as e: + log.exception(f"Fehler beim Lesen der Logdateien: {e}") + return ''.join(lines[-num_lines:]) + + +def get_uuids(): + try: + with open(openwb_base_dir / 'data/log/uuid', 'r') as uuid_file: + return (uuid_file.read()) + except Exception as e: + log.exception(f"Error reading UUID file: {e}") + + +def create_debug_log(input_data): + def write_to_file(file_handler, func, default: Optional[Any] = None): + try: + file_handler.write(func()+"\n") + except Exception as e: + log.exception(f"Error getting value for chargelog: {func}. Setting to default {default}.") + file_handler.write(f"Error getting value for chargelog: {func}. Setting to default {default}.\n" + f"Error: {e}\n") + + try: + broker = BrokerContent() + debug_email = input_data.get('email', '') + header = (f"{input_data['message']}\n{debug_email}\n{input_data['serialNumber']}\n" + f"{input_data['installedComponents']}\n{input_data['vehicles']}\n") + with open(debug_file, 'w+') as df: + write_to_file(df, lambda: header) + write_to_file(df, lambda: f"## section: configuration and state ##\n{config_and_state()}\n") + write_to_file(df, lambda: f'## section: system ##\n{run_command(["uptime"])}{run_command(["free"])}\n') + write_to_file(df, lambda: f"## section: uuids ##\n{get_uuids()}\n") + write_to_file(df, lambda: f'## section: network ##\n{run_command(["ifconfig"])}\n') + write_to_file(df, lambda: f'## section: storage ##\n{run_command(["df", "-h"])}\n') + write_to_file(df, lambda: f"## section: broker essentials ##\n{broker.get_broker_essentials()}\n") + write_to_file( + df, lambda: f"## section: retained log ##\n{merge_log_files('main', 500)}") + write_to_file(df, lambda: "## section: info log ##\n") + Pub().pub('openWB/set/system/debug_level', 20) + time.sleep(60) + write_to_file(df, lambda: merge_log_files("main", 1000)) + write_to_file(df, lambda: "## section: debug log ##\n") + Pub().pub('openWB/set/system/debug_level', 10) + time.sleep(60) + write_to_file(df, lambda: merge_log_files("main", 2500)) + write_to_file( + df, + lambda: f'## section: internal chargepoint log ##\n{merge_log_files("internal_chargepoint", 1000)}\n') + write_to_file(df, lambda: f'## section: mqtt log ##\n{merge_log_files("mqtt", 1000)}\n') + write_to_file(df, lambda: f'## section: soc log ##\n{merge_log_files("soc", 1000)}\n') + write_to_file(df, lambda: f'## section: charge log ##\n{merge_log_files("chargelog", 1000)}\n') + write_to_file(df, lambda: f"## section: broker ##\n{broker.get_broker()}") + + log.info("***** uploading debug log...") + with open(debug_file, 'rb') as f: + data = f.read() + req.get_http_session().put("https://openwb.de/tools/debug2.php", + data=data, + params={'debugemail': debug_email}) + + log.info("***** cleanup...") + os.remove(debug_file) + log.info("***** debug log end") + except Exception as e: + log.exception(f"Error creating debug log: {e}") + + +class BrokerContent: + def __init__(self) -> None: + self.content = "" + + def get_broker(self): + InternalBrokerClient("processBrokerBranch", self.__on_connect_broker, self.__get_content).start_finite_loop() + return self.content + + def __on_connect_broker(self, client, userdata, flags, rc): + client.subscribe("openWB/#", 2) + + def __get_content(self, client, userdata, msg): + self.content += f"{msg.topic} {decode_payload(msg.payload)}\n" + + def get_broker_essentials(self): + InternalBrokerClient("processBrokerBranch", self.__on_connect_broker_essentials, + self.__get_content).start_finite_loop() + return self.content + + def __on_connect_broker_essentials(self, client, userdata, flags, rc): + client.subscribe("openWB/system/ip_address", 2) + client.subscribe("openWB/system/current_commit", 2) + client.subscribe("openWB/system/boot_done", 2) + client.subscribe("openWB/system/update_in_progress", 2) + client.subscribe("openWB/system/device/#", 2) + client.subscribe("openWB/system/time", 2) + client.subscribe("openWB/chargepoint/#", 2) + client.subscribe("openWB/internal_chargepoint/#", 2) + client.subscribe("openWB/vehicle/#", 2) + client.subscribe("openWB/counter/#", 2) + client.subscribe("openWB/pv/#", 2) + client.subscribe("openWB/bat/#", 2) + client.subscribe("openWB/optional/et/provider", 2) + + def get_bridges(self): + InternalBrokerClient("processBrokerBranch", self.__on_connect_bridges, self.__get_bridges).start_finite_loop() + return self.content + + def __on_connect_bridges(self, client, userdata, flags, rc): + client.subscribe("openWB/system/mqtt/#", 2) + + def __get_bridges(self, client, userdata, msg): + if "openWB/system/mqtt/bridge" in msg.topic: + payload = decode_payload(msg.payload) + self.content += (f"Name: {payload['name']}, aktiv: {payload['active']}, " + f"openWB-Cloud: {payload['remote']['is_openwb_cloud']}") + if payload['remote'].get("is_openwb_cloud"): + self.content += (f", BN: {payload['remote']['username']}, PW: {payload['remote']['password']}, " + f"Partnerzugang: {payload['access']['partner']}") + self.content += "\n" + elif "openWB/system/mqtt/valid_partner_ids": + self.content += f"Partner-IDs: {decode_payload(msg.payload)}\n" + + +class ErrorHandlingContext: + def __init__(self): + pass + + def __enter__(self): + return None + + def __exit__(self, exception_type, exception, exception_traceback) -> bool: + if isinstance(exception, Exception): + log.exception("Fehler beim Parsen der Daten für das Support-Ticket") + return True diff --git a/packages/helpermodules/data_migration/data_migration.py b/packages/helpermodules/data_migration/data_migration.py index e8075f50a2..e42418d486 100644 --- a/packages/helpermodules/data_migration/data_migration.py +++ b/packages/helpermodules/data_migration/data_migration.py @@ -28,6 +28,7 @@ from helpermodules.timecheck import convert_timedelta_to_time_string, get_difference from helpermodules.utils import thread_handler from helpermodules.pub import Pub +from helpermodules.utils.json_file_handler import write_and_check from modules.ripple_control_receivers.gpio.config import GpioRcr import re @@ -154,8 +155,7 @@ def convert(old_file_name: str) -> None: except FileNotFoundError: pass new_entries.extend(content) - with open(filepath, "w") as jsonFile: - json.dump(new_entries, jsonFile) + write_and_check(filepath, new_entries) except Exception: log.exception(f"Fehler beim Konvertieren des Lade-Logs vom {old_file_name}") @@ -278,8 +278,7 @@ def convert(old_file_name: str) -> None: with open(filepath, "r") as jsonFile: content = json.load(jsonFile) except FileNotFoundError: - with open(filepath, "w+") as jsonFile: - json.dump({"entries": [], "totals": {}}, jsonFile) + write_and_check(filepath, {"entries": [], "totals": {}}) with open(filepath, "r") as jsonFile: content = json.load(jsonFile) entries = content["entries"] @@ -288,8 +287,7 @@ def convert(old_file_name: str) -> None: content["totals"] = get_totals(merged_entries) content["entries"] = merged_entries content["names"] = get_names(content["totals"], LegacySmartHomeLogData().sh_names) - with open(filepath, "w") as jsonFile: - json.dump(content, jsonFile) + write_and_check(filepath, content) except Exception: log.exception(f"Fehler beim Konvertieren des Logs vom {old_file_name}") @@ -559,8 +557,7 @@ def _migrate_settings_from_openwb_conf(self): def _move_serial_number(self) -> None: serial_number = self._get_openwb_conf_value("snnumber") if serial_number is not None: - with open("/home/openwb/snnumber", "w") as file: - file.write(f"snnumber={serial_number}") + write_and_check("/home/openwb/snnumber", f"snnumber={serial_number}") def _move_cloud_data(self) -> None: cloud_user = self._get_openwb_conf_value("clouduser") @@ -583,8 +580,7 @@ def _move_max_c_socket(self): def _move_pddate(self) -> None: pddate = self._get_openwb_conf_value("pddate") if pddate is not None: - with open("/home/openwb/pddate", "w") as file: - file.write(f"pddate={pddate}") + write_and_check("/home/openwb/pddate", f"pddate={pddate}") NOT_CONFIGURED = " wurde in openWB software2 nicht konfiguriert." diff --git a/packages/helpermodules/exceptions/os.py b/packages/helpermodules/exceptions/os.py index 3e9971ce5e..e39607da10 100644 --- a/packages/helpermodules/exceptions/os.py +++ b/packages/helpermodules/exceptions/os.py @@ -3,7 +3,7 @@ def handle_os_error(e: OSError): code = e.errno - if code == 113: + if code == 113 or e.args[0] == "timed out": return "Die Verbindung zum Host ist fehlgeschlagen. Überprüfe Adresse und Netzwerk." return "OSError {}: Unbekannter Fehler {}".format(code, e.strerror) diff --git a/packages/helpermodules/graph.py b/packages/helpermodules/graph.py index 3a13f7a01f..f18a9567ec 100644 --- a/packages/helpermodules/graph.py +++ b/packages/helpermodules/graph.py @@ -1,13 +1,13 @@ from dataclasses import dataclass, field import json from pathlib import Path -import subprocess import time import datetime import logging from control import data from helpermodules.pub import Pub +from helpermodules.utils.run_command import run_command from modules.common.fault_state import FaultStateLevel log = logging.getLogger(__name__) @@ -64,7 +64,7 @@ def _convert_to_kW(value): return round(value/1000, 3) Pub().pub("openWB/set/system/lastlivevaluesJson", data_line) with open(str(Path(__file__).resolve().parents[2] / "ramdisk"/"graph_live.json"), "a") as f: f.write(f"{json.dumps(data_line, separators=(',', ':'))}\n") - subprocess.run([str(Path(__file__).resolve().parents[2] / "runs"/"graphing.sh"), - str(self.data.config.duration*6)]) + run_command([str(Path(__file__).resolve().parents[2] / "runs"/"graphing.sh"), + str(self.data.config.duration*6)]) except Exception: log.exception("Fehler im Graph-Modul") diff --git a/packages/helpermodules/hardware_configuration.py b/packages/helpermodules/hardware_configuration.py index 6ad16782c8..cb81289693 100644 --- a/packages/helpermodules/hardware_configuration.py +++ b/packages/helpermodules/hardware_configuration.py @@ -2,24 +2,22 @@ import sys from typing import Dict +from helpermodules.utils.json_file_handler import write_and_check + HARDWARE_CONFIGURATION_FILE = "/home/openwb/configuration.json" def update_hardware_configuration(new_setting: Dict) -> None: with open(HARDWARE_CONFIGURATION_FILE, "r") as f: data = json.loads(f.read()) - with open(HARDWARE_CONFIGURATION_FILE, "w") as f: - data.update(new_setting) - f.write(json.dumps(data)) + write_and_check(HARDWARE_CONFIGURATION_FILE, data.update(new_setting)) def remove_setting_hardware_configuration(obsolet_setting: str) -> None: with open(HARDWARE_CONFIGURATION_FILE, "r") as f: data = json.loads(f.read()) if obsolet_setting in data: - with open(HARDWARE_CONFIGURATION_FILE, "w") as f: - data.pop(obsolet_setting) - f.write(json.dumps(data)) + write_and_check(HARDWARE_CONFIGURATION_FILE, data.pop(obsolet_setting)) def get_hardware_configuration_setting(name: str, default=None): diff --git a/packages/helpermodules/logger.py b/packages/helpermodules/logger.py index a1eb806a95..435088fa73 100644 --- a/packages/helpermodules/logger.py +++ b/packages/helpermodules/logger.py @@ -2,6 +2,8 @@ import logging from logging.handlers import RotatingFileHandler from pathlib import Path +import sys +import threading import typing_extensions FORMAT_STR_DETAILED = '%(asctime)s - {%(name)s:%(lineno)s} - {%(levelname)s:%(threadName)s} - %(message)s' @@ -25,7 +27,8 @@ def filter_pos(name: str, record) -> bool: def setup_logging() -> None: def mb_to_bytes(megabytes: int) -> int: return megabytes * 1000000 - main_file_handler = RotatingFileHandler(RAMDISK_PATH + 'main.log', maxBytes=mb_to_bytes(4), backupCount=1) + # Mehrere kleine Dateien verwenden, damit nicht zu viel verworfen wird, wenn die Datei voll ist. + main_file_handler = RotatingFileHandler(RAMDISK_PATH + 'main.log', maxBytes=mb_to_bytes(5.5), backupCount=4) main_file_handler.setFormatter(logging.Formatter(FORMAT_STR_DETAILED)) logging.basicConfig(level=logging.DEBUG, handlers=[main_file_handler]) logging.getLogger().handlers[0].addFilter(functools.partial(filter_neg, "soc")) @@ -35,14 +38,14 @@ def mb_to_bytes(megabytes: int) -> int: chargelog_log = logging.getLogger("chargelog") chargelog_log.propagate = False chargelog_file_handler = RotatingFileHandler( - RAMDISK_PATH + 'chargelog.log', maxBytes=mb_to_bytes(3), backupCount=1) + RAMDISK_PATH + 'chargelog.log', maxBytes=mb_to_bytes(2), backupCount=1) chargelog_file_handler.setFormatter(logging.Formatter(FORMAT_STR_SHORT)) chargelog_log.addHandler(chargelog_file_handler) data_migration_log = logging.getLogger("data_migration") data_migration_log.propagate = False data_migration_file_handler = RotatingFileHandler( - PERSISTENT_LOG_PATH + 'data_migration.log', maxBytes=mb_to_bytes(3), backupCount=1) + PERSISTENT_LOG_PATH + 'data_migration.log', maxBytes=mb_to_bytes(1), backupCount=1) data_migration_file_handler.setFormatter(logging.Formatter(FORMAT_STR_SHORT)) data_migration_log.addHandler(data_migration_file_handler) @@ -52,7 +55,7 @@ def mb_to_bytes(megabytes: int) -> int: mqtt_file_handler.setFormatter(logging.Formatter(FORMAT_STR_SHORT)) mqtt_log.addHandler(mqtt_file_handler) - smarthome_log_handler = RotatingFileHandler(RAMDISK_PATH + 'smarthome.log', maxBytes=mb_to_bytes(2), backupCount=1) + smarthome_log_handler = RotatingFileHandler(RAMDISK_PATH + 'smarthome.log', maxBytes=mb_to_bytes(1), backupCount=1) smarthome_log_handler.setFormatter(logging.Formatter(FORMAT_STR_SHORT)) smarthome_log_handler.addFilter(functools.partial(filter_pos, "smarthome")) logging.getLogger().addHandler(smarthome_log_handler) @@ -63,7 +66,7 @@ def mb_to_bytes(megabytes: int) -> int: logging.getLogger().addHandler(soc_log_handler) internal_chargepoint_log_handler = RotatingFileHandler(RAMDISK_PATH + 'internal_chargepoint.log', - maxBytes=mb_to_bytes(4), + maxBytes=mb_to_bytes(1), backupCount=1) internal_chargepoint_log_handler.setFormatter(logging.Formatter(FORMAT_STR_DETAILED)) internal_chargepoint_log_handler.addFilter(functools.partial(filter_pos, "Internal Chargepoint")) @@ -71,7 +74,7 @@ def mb_to_bytes(megabytes: int) -> int: urllib3_log = logging.getLogger("urllib3.connectionpool") urllib3_log.propagate = True - urllib3_file_handler = RotatingFileHandler(RAMDISK_PATH + 'soc.log', maxBytes=mb_to_bytes(5), backupCount=1) + urllib3_file_handler = RotatingFileHandler(RAMDISK_PATH + 'soc.log', maxBytes=mb_to_bytes(2), backupCount=1) urllib3_file_handler.setFormatter(logging.Formatter(FORMAT_STR_DETAILED)) urllib3_file_handler.addFilter(functools.partial(filter_pos, "soc")) urllib3_log.addHandler(urllib3_file_handler) @@ -79,6 +82,15 @@ def mb_to_bytes(megabytes: int) -> int: logging.getLogger("pymodbus").setLevel(logging.WARNING) logging.getLogger("uModbus").setLevel(logging.WARNING) + def threading_excepthook(args): + logging.getLogger(__name__).error("Uncaught exception in threading.excepthook:", exc_info=( + args.exc_type, args.exc_value, args.exc_traceback)) + threading.excepthook = threading_excepthook + + def handle_unhandled_exception(exc_type, exc_value, exc_traceback): + logging.getLogger(__name__).error("Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback)) + sys.excepthook = handle_unhandled_exception + log = logging.getLogger(__name__) diff --git a/packages/helpermodules/measurement_logging/process_log.py b/packages/helpermodules/measurement_logging/process_log.py index 5aec2e33f2..594c5e58b8 100644 --- a/packages/helpermodules/measurement_logging/process_log.py +++ b/packages/helpermodules/measurement_logging/process_log.py @@ -1,9 +1,10 @@ +import datetime from decimal import Decimal from enum import Enum import json import logging from pathlib import Path -from typing import Dict, List, Tuple +from typing import Dict, List, Tuple, Union from helpermodules import timecheck from helpermodules.measurement_logging.write_log import (LegacySmartHomeLogData, LogType, create_entry, @@ -181,14 +182,14 @@ def get_monthly_log(date: str): def _collect_monthly_log_data(date: str): try: - with open(str(Path(__file__).resolve().parents[3] / "data"/"monthly_log"/(date+".json")), "r") as jsonFile: + with open(f"{_get_data_folder_path()}/monthly_log/{date}.json", "r") as jsonFile: log_data = json.load(jsonFile) this_month = timecheck.create_timestamp_YYYYMM() if date == this_month: # add last entry of current day, if current month is requested try: today = timecheck.create_timestamp_YYYYMMDD() - with open(str(Path(__file__).resolve().parents[3] / "data" / "daily_log"/(today+".json")), + with open(f"{_get_data_folder_path()}/daily_log/{today}.json", "r") as todayJsonFile: today_log_data = json.load(todayJsonFile) if len(today_log_data["entries"]) > 0: @@ -199,7 +200,7 @@ def _collect_monthly_log_data(date: str): # add first entry of next month try: next_date = timecheck.get_relative_date_string(date, month_offset=1) - with open(str(Path(__file__).resolve().parents[3] / "data"/"monthly_log"/(next_date+".json")), + with open(f"{_get_data_folder_path()}/monthly_log/{next_date}.json", "r") as nextJsonFile: next_log_data = json.load(nextJsonFile) log_data["entries"].append(next_log_data["entries"][0]) @@ -218,6 +219,66 @@ def get_yearly_log(year: str): return data +def get_log_from_date_until_now(timestamp: int): + data = {} + try: + entries = _collect_log_data_from_date_until_now(timestamp) + data["entries"] = _process_entries(entries, CalculationType.ENERGY) + data["totals"] = get_totals(data["entries"], False) + data = _analyse_energy_source(data) + except Exception: + log.exception(f"Fehler beim Zusammenstellen der Logdaten von {timestamp}") + finally: + return data + + +def _collect_log_data_from_date_until_now(timestamp: int): + def add_to_list(log_data: List, data: Union[Dict, List]): + if isinstance(data, list): + log_data.extend(data) + else: + log_data.append(data) + return log_data + log_data = [] + try: + date = datetime.datetime.fromtimestamp(timestamp).strftime("%Y%m%d") + try: + with open(f"{_get_data_folder_path()}/daily_log/{date}.json", "r") as jsonFile: + entries = json.load(jsonFile)["entries"] + except FILE_ERRORS: + pass + for index, entry in enumerate(entries): + if entry["timestamp"] > timestamp: + log_data = add_to_list(log_data, entries[index:]) + break + else: + try: + # Wenn der Ladevorgang nicht über volle 5 Minuten ging, wurde während dem Laden kein Eintrag ins + # daily-log geschrieben. + log_data = add_to_list(log_data, entries[-1]) + except KeyError: + log.exception(f"Fehler beim Zusammenstellen der Logdaten. Bitte Logdatei daily_log/{date}.json prüfen.") + # Das Teillog vom ersten Tag wurde bereits ermittelt. + start_date = datetime.datetime.fromtimestamp(timestamp) + datetime.timedelta(days=1) + end_date = datetime.datetime.now() + current_date = start_date + date_list = [] + while current_date <= end_date: + date_list.append(current_date.strftime('%Y%m%d')) + current_date += datetime.timedelta(days=1) + for date_str in date_list: + try: + with open(f"{_get_data_folder_path()}/daily_log/{date_str}.json", "r") as jsonFile: + log_data = add_to_list(log_data, json.load(jsonFile)["entries"]) + except FILE_ERRORS: + pass + log_data = add_to_list(log_data, create_entry(LogType.DAILY, LegacySmartHomeLogData(), log_data[-1])) + except Exception: + log.exception(f"Fehler beim Zusammenstellen der Logdaten von {timestamp}") + finally: + return log_data + + def _collect_yearly_log_data(year: str): def add_monthly_log(month: str, check_next_month: bool = False) -> None: monthly_log_path = Path(__file__).resolve().parents[3]/"data"/"monthly_log" @@ -238,8 +299,7 @@ def add_monthly_log(month: str, check_next_month: bool = False) -> None: def add_daily_log(day: str) -> None: try: - with open(str(Path(__file__).resolve().parents[3] / "data" / "daily_log"/(day+".json")), - "r") as dayJsonFile: + with open(f"{_get_data_folder_path()}/daily_log/{day}.json", "r") as dayJsonFile: day_log_data = json.load(dayJsonFile) if len(day_log_data["entries"]) > 0: entries.append(day_log_data["entries"][-1]) @@ -473,3 +533,7 @@ def _calculate_average_power(time_diff: float, current_imported: float = 0, next power = power.quantize(Decimal('0.001')) # limit precision power = f'{power: f}' return string_to_float(power) if "." in power else string_to_int(power) + + +def _get_data_folder_path() -> str: + return str(Path(__file__).resolve().parents[3] / "data") diff --git a/packages/helpermodules/measurement_logging/update_yields.py b/packages/helpermodules/measurement_logging/update_yields.py index c5fefb9bf4..73961bae15 100644 --- a/packages/helpermodules/measurement_logging/update_yields.py +++ b/packages/helpermodules/measurement_logging/update_yields.py @@ -4,13 +4,11 @@ from typing import Dict, List from control import data -from control.bat_all import BatAll +from control.chargepoint.chargepoint import Chargepoint +from control.pv_all import PvAll from helpermodules import timecheck from helpermodules.measurement_logging.process_log import get_totals from helpermodules.pub import Pub -from control.bat import Bat -from control.chargepoint.chargepoint import Chargepoint -from control.counter import Counter from control.ev import Ev from control.pv import Pv @@ -24,7 +22,6 @@ def update_daily_yields(entries): totals = get_totals(entries) [update_module_yields(type, totals) for type in ("bat", "counter", "cp", "pv")] data.data.counter_all_data.data.set.daily_yield_home_consumption = totals["hc"]["all"]["energy_imported"] - Pub().pub("openWB/set/counter/set/daily_yield_home_consumption", totals["hc"]["all"]["energy_imported"]) except Exception: log.exception("Fehler beim Veröffentlichen der Tageserträge.") @@ -38,10 +35,10 @@ def update_imported_exported(daily_imported: float, daily_exported: float) -> No topic = "chargepoint" else: topic = module - if isinstance(module_data, (Ev, Chargepoint, Pv, Bat, Counter)): + if isinstance(module_data, (Ev, Pv, Chargepoint)): Pub().pub(f"openWB/set/{topic}/{module_data.num}/get/daily_imported", daily_imported) Pub().pub(f"openWB/set/{topic}/{module_data.num}/get/daily_exported", daily_exported) - elif not isinstance(module_data, BatAll): + elif isinstance(module_data, PvAll): # wird im changed_values_handler an den Broker gesendet Pub().pub(f"openWB/set/{topic}/get/daily_imported", daily_imported) Pub().pub(f"openWB/set/{topic}/get/daily_exported", daily_exported) @@ -78,21 +75,26 @@ def update_pv_monthly_yearly_yields(): def _update_pv_monthly_yields(): """ veröffentlicht die monatlichen Erträge für PV + für pv_all nicht die Differenz aus den Logs nehmen, sondern die Summe der Module. Wenn im laufenden Monat ein Modul + gelöscht wurde und keins oder eines mit niedrigerem Zählerstand hinzugefügt wird, wird sonst ein negativer Wert + ermittelt. """ try: + pv_all_monthly_yield = 0 with open(f"data/monthly_log/{timecheck.create_timestamp_YYYYMM()}.json", "r") as f: monthly_log = json.load(f) - monthly_yield = data.data.pv_all_data.data.get.exported - monthly_log["entries"][0]["pv"]["all"]["exported"] - Pub().pub("openWB/set/pv/get/monthly_exported", monthly_yield) for pv_module in data.data.pv_data.values(): - for i in range(0, len(monthly_log["entries"])): - # erster Eintrag im Monat, in dem das PV-Modul existiert (falls ein Modul im laufenden Monat hinzugefügt - # wurde) - if monthly_log["entries"][i]["pv"].get(f"pv{pv_module.num}"): + for entry in monthly_log["entries"]: + if entry["pv"].get(f"pv{pv_module.num}"): monthly_yield = data.data.pv_data[f"pv{pv_module.num}"].data.get.exported - \ - monthly_log["entries"][i]["pv"][f"pv{pv_module.num}"]["exported"] + entry["pv"][f"pv{pv_module.num}"]["exported"] + pv_all_monthly_yield += monthly_yield Pub().pub(f"openWB/set/pv/{pv_module.num}/get/monthly_exported", monthly_yield) break + Pub().pub("openWB/set/pv/get/monthly_exported", pv_all_monthly_yield) + except FileNotFoundError: + # am Tag der Ersteinrichtung gibt es noch kein Monatslog-File, das wird erst um Mitternacht erstellt. + log.debug("No monthly logfile found for calculation of monthly yield") except Exception: log.exception("Fehler beim Veröffentlichen der monatlichen Erträge für PV") @@ -101,29 +103,45 @@ def pub_yearly_module_yield(sorted_path_list: List[str], pv_module: Pv): for path in sorted_path_list: with open(path, "r") as f: monthly_log = json.load(f) - for i in range(0, len(monthly_log["entries"])): - # erster Eintrag im Jahr, in dem das PV-Modul existiert (falls ein Modul im laufenden Jahr hinzugefügt - # wurde) - if monthly_log["entries"][i]["pv"].get(f"pv{pv_module.num}"): + for entry in monthly_log["entries"]: + # erster Eintrag mit PV im Jahr,falls WR erst im laufenden Jahr hinzugefügt wurden + if entry["pv"].get(f"pv{pv_module.num}"): yearly_yield = data.data.pv_data[f"pv{pv_module.num}"].data.get.exported - \ - monthly_log["entries"][i]["pv"][f"pv{pv_module.num}"]["exported"] + entry["pv"][f"pv{pv_module.num}"]["exported"] Pub().pub(f"openWB/set/pv/{pv_module.num}/get/yearly_exported", yearly_yield) return def _update_pv_yearly_yields(): """ veröffentlicht die jährlichen Erträge für PV + für pv_all nicht die Differenz aus den Logs nehmen, sondern die Summe der Module. Wenn unterjährig ein Modul + gelöscht wurde und keins oder eines mit niedrigerem Zählerstand hinzugefügt wird, wird sonst ein negativer Wert + ermittelt. """ try: + pv_all_yearly_yield = 0 path_list = list(Path(_get_parent_path()/"data"/"monthly_log").glob(f"{timecheck.create_timestamp_YYYY()}*")) sorted_path_list = sorted([str(p) for p in path_list]) - with open(sorted_path_list[0], "r") as f: - monthly_log = json.load(f) - yearly_yield = data.data.pv_all_data.data.get.exported - monthly_log["entries"][0]["pv"]["all"]["exported"] - Pub().pub("openWB/set/pv/get/yearly_exported", yearly_yield) - log.debug(f"sorted_path_list{sorted_path_list}") for pv_module in data.data.pv_data.values(): - pub_yearly_module_yield(sorted_path_list, pv_module) + found_pv = False + for path in sorted_path_list: + with open(path, "r") as f: + monthly_log = json.load(f) + for entry in monthly_log["entries"]: + # erster Eintrag mit PV im Jahr, falls WR erst im laufenden Jahr hinzugefügt wurden + if entry["pv"].get(f"pv{pv_module.num}"): + yearly_yield = data.data.pv_data[f"pv{pv_module.num}"].data.get.exported - \ + entry["pv"][f"pv{pv_module.num}"]["exported"] + pv_all_yearly_yield += yearly_yield + Pub().pub(f"openWB/set/pv/{pv_module.num}/get/yearly_exported", yearly_yield) + found_pv = True + break + if found_pv: + break + else: + # am Tag der Ersteinrichtung gibt es noch kein Monatslog-File, das wird erst um Mitternacht erstellt. + log.debug("No monthly logfile found for calculation of yearly yield") + Pub().pub("openWB/set/pv/get/yearly_exported", pv_all_yearly_yield) except Exception: log.exception("Fehler beim Veröffentlichen der jährlichen Erträge für PV") diff --git a/packages/helpermodules/measurement_logging/update_yields_test.py b/packages/helpermodules/measurement_logging/update_yields_test.py index 4411cd23af..35a3fd7b81 100644 --- a/packages/helpermodules/measurement_logging/update_yields_test.py +++ b/packages/helpermodules/measurement_logging/update_yields_test.py @@ -1,5 +1,4 @@ -import pytest - +from control import data from helpermodules.measurement_logging.update_yields import update_module_yields @@ -8,28 +7,17 @@ def test_update_module_yields(daily_log_totals, mock_pub): [update_module_yields(type, daily_log_totals) for type in ("bat", "counter", "cp", "pv")] # evaluation - expected = { - "openWB/set/bat/2/get/daily_imported": 0.0, - "openWB/set/bat/2/get/daily_exported": 550.0, - "openWB/set/counter/0/get/daily_imported": 1492.0, - "openWB/set/counter/0/get/daily_exported": 0.0, - "openWB/set/chargepoint/get/daily_imported": 1920.0, - "openWB/set/chargepoint/get/daily_exported": 0.0, - "openWB/set/chargepoint/4/get/daily_imported": 384.0, - "openWB/set/chargepoint/4/get/daily_exported": 0.0, - "openWB/set/chargepoint/5/get/daily_imported": 192.0, - "openWB/set/chargepoint/5/get/daily_exported": 0.0, - "openWB/set/chargepoint/6/get/daily_imported": 0.0, - "openWB/set/chargepoint/6/get/daily_exported": 0.0, - "openWB/set/pv/get/daily_exported": 251.0, - "openWB/set/pv/1/get/daily_exported": 251.0} - for topic, value in expected.items(): - for call in mock_pub.mock_calls: - try: - if call.args[0] == topic: - assert value == call.args[1] - break - except IndexError: - pass - else: - pytest.fail(f"Topic {topic} is missing") + data.data.bat_data["bat2"].data.get.daily_imported = 0.0 + data.data.bat_data["bat2"].data.get.daily_exported = 550.0 + data.data.counter_data["counter0"].data.get.daily_imported = 1492.0 + data.data.counter_data["counter0"].data.get.daily_exported = 0.0 + data.data.cp_all_data.data.get.daily_imported = 1920.0 + data.data.cp_all_data.data.get.daily_exported = 0.0 + data.data.cp_data["cp4"].data.get.daily_imported = 384.0 + data.data.cp_data["cp4"].data.get.daily_exported = 0.0 + data.data.cp_data["cp5"].data.get.daily_imported = 192.0 + data.data.cp_data["cp5"].data.get.daily_exported = 0.0 + data.data.cp_data["cp6"].data.get.daily_imported = 0.0 + data.data.cp_data["cp6"].data.get.daily_exported = 0.0 + data.data.pv_all_data.data.get.daily_exported = 251.0 + data.data.pv_data["pv1"].data.get.daily_exported = 251.0 diff --git a/packages/helpermodules/measurement_logging/write_log.py b/packages/helpermodules/measurement_logging/write_log.py index 32e41128f3..40aeaa5f69 100644 --- a/packages/helpermodules/measurement_logging/write_log.py +++ b/packages/helpermodules/measurement_logging/write_log.py @@ -10,6 +10,7 @@ from control import data from helpermodules.broker import InternalBrokerClient from helpermodules import timecheck +from helpermodules.utils.json_file_handler import write_and_check from helpermodules.utils.topic_parser import decode_payload, get_index from modules.common.utils.component_parser import get_component_name_by_id @@ -153,8 +154,7 @@ def save_log(log_type: LogType): entries = content["entries"] entries.append(new_entry) content["names"] = get_names(content["entries"][-1], sh_log_data.sh_names) - with open(filepath, "w") as jsonFile: - json.dump(content, jsonFile) + write_and_check(filepath, content) return content["entries"] except Exception: log.exception("Fehler beim Speichern des Log-Eintrags") @@ -209,14 +209,15 @@ def create_entry(log_type: LogType, sh_log_data: LegacySmartHomeLogData, previou log.exception("Fehler im Werte-Logging-Modul für EV "+str(ev)) counter_dict = {} - for counter in data.data.counter_data: + for counter in data.data.counter_data.values(): try: - if "counter" in counter: + home_consumption_source_id = data.data.counter_all_data.data.config.home_consumption_source_id + if (home_consumption_source_id is None or counter.num != home_consumption_source_id): counter_dict.update( - {counter: { - "imported": data.data.counter_data[counter].data.get.imported, - "exported": data.data.counter_data[counter].data.get.exported, - "grid": True if data.data.counter_all_data.get_evu_counter_str() == counter else False}}) + {f"counter{counter.num}": { + "imported": counter.data.get.imported, + "exported": counter.data.get.exported, + "grid": True if data.data.counter_all_data.get_id_evu_counter() == counter.num else False}}) except Exception: log.exception("Fehler im Werte-Logging-Modul für Zähler "+str(counter)) diff --git a/packages/helpermodules/measurement_logging/write_log_test.py b/packages/helpermodules/measurement_logging/write_log_test.py index 64d05f7a76..a491716c85 100644 --- a/packages/helpermodules/measurement_logging/write_log_test.py +++ b/packages/helpermodules/measurement_logging/write_log_test.py @@ -15,9 +15,9 @@ def test_get_names(daily_log_totals, monkeypatch): assert names == {'bat2': "Speicher", 'counter0': "Zähler", 'cp3': "cp3", - 'cp4': "Standard-Ladepunkt", - 'cp5': "Standard-Ladepunkt", - 'cp6': "Standard-Ladepunkt", + 'cp4': "neuer Ladepunkt", + 'cp5': "neuer Ladepunkt", + 'cp6': "neuer Ladepunkt", 'pv1': "Wechselrichter", "sh1": "Smarthome1"} diff --git a/packages/helpermodules/parse_send_debug.py b/packages/helpermodules/parse_send_debug.py deleted file mode 100644 index e3d55734d2..0000000000 --- a/packages/helpermodules/parse_send_debug.py +++ /dev/null @@ -1,74 +0,0 @@ -import logging -import pprint -from control import data -import dataclass_utils -from modules.common.abstract_device import AbstractDevice - -log = logging.getLogger(__name__) - - -def parse_send_debug_data(): - parsed_data = "# Hierarchie\n" - pretty_hierarchy = pprint.pformat(data.data.counter_all_data.data.get.hierarchy, - indent=4, compact=True, sort_dicts=False, width=100) - parsed_data += f"{pretty_hierarchy}\n" - parsed_data += "\n# Geräte und Komponenten\n" - for key, value in data.data.system_data.items(): - try: - if isinstance(value, AbstractDevice): - parsed_data += f"{key}: {dataclass_utils.asdict(value.device_config)}\n" - for comp_key, comp_value in value.components.items(): - parsed_data += f"{comp_key}: {dataclass_utils.asdict(comp_value.component_config)}\n" - if "bat" in comp_value.component_config.type: - component_data = data.data.bat_data[f"bat{comp_value.component_config.id}"] - elif "counter" in comp_value.component_config.type: - component_data = data.data.counter_data[f"counter{comp_value.component_config.id}"] - elif "inverter" in comp_value.component_config.type: - component_data = data.data.pv_data[f"pv{comp_value.component_config.id}"] - if "bat" in comp_value.component_config.type: - parsed_data += (f"Leistung: {component_data.data.get.power/1000}kW, " - f"SoC: {component_data.data.get.soc}%, " - f"Fehlerstatus: {component_data.data.get.fault_str}\n") - elif "inverter" in comp_value.component_config.type: - parsed_data += (f"Leistung: {component_data.data.get.power/1000}kW, " - f"Fehlerstatus: {component_data.data.get.fault_str}\n") - else: - if data.data.counter_all_data.get_evu_counter_str() == f"counter{component_data.num}": - parsed_data += (f"{key}: EVU-Zähler -> max. Leistung " - f"{component_data.data.config.max_total_power}, " - f"max. Ströme {component_data.data.config.max_currents}; ") - else: - parsed_data += f"{key}: max. Ströme {component_data.data.config.max_currents}" - parsed_data += (f"Leistung: {component_data.data.get.power/1000}kW, Ströme: " - f"{component_data.data.get.currents}A, Fehlerstatus: " - f"{component_data.data.get.fault_str}\n") - except Exception: - log.exception("Fehler beim Parsen der Daten für das Support-Ticket") - parsed_data += f"Hausverbrauch: {data.data.counter_all_data.data.set.home_consumption}W\n" - chargemode_config = data.data.general_data.data.chargemode_config - parsed_data += (f"Phasenvorgabe: Sofortladen {chargemode_config.instant_charging.phases_to_use}, Zielladen " - f"{chargemode_config.scheduled_charging.phases_to_use}, Zeitladen: " - f"{chargemode_config.time_charging.phases_to_use}, PV-Laden: " - f"{chargemode_config.pv_charging.phases_to_use}, Einschaltschwelle: " - f"{chargemode_config.pv_charging.switch_on_threshold}W, Ausschaltschwelle: " - f"{chargemode_config.pv_charging.switch_off_threshold}W\n") - - parsed_data += "\n# Ladepunkte\n" - parsed_data += f"Ladeleistung aller Ladepunkte {data.data.cp_all_data.data.get.power / 1000}kW\n" - for cp in data.data.cp_data.values(): - try: - if hasattr(cp.chargepoint_module.config.configuration, "ip_address"): - ip = cp.chargepoint_module.config.configuration.ip_address - else: - ip = None - parsed_data += (f"LP{cp.num}: Typ: {cp.chargepoint_module.config.type}; IP: " - f"{ip}; Stecker-Status: {cp.data.get.plug_state}, Leistung: " - f"{cp.data.get.power/1000}kW, {cp.data.get.currents}A, {cp.data.get.voltages}V, Lademodus: " - f"{cp.data.control_parameter.chargemode}, Submode: " - f"{cp.data.control_parameter.submode}, Sollstrom: " - f"{cp.data.set.current}A, Status: {cp.data.get.state_str}, " - f"Fehlerstatus: {cp.data.get.fault_str}\n") - except Exception: - log.exception("Fehler beim Parsen der Daten für das Support-Ticket") - - return parsed_data diff --git a/packages/helpermodules/setdata.py b/packages/helpermodules/setdata.py index c21c4b3ee1..ddc0329ea3 100644 --- a/packages/helpermodules/setdata.py +++ b/packages/helpermodules/setdata.py @@ -446,7 +446,6 @@ def _subprocess_vehicle_chargemode_topic(self, msg: mqtt.MQTTMessage): if "/name" in msg.topic: self._validate_value(msg, str, pub_json=True) elif ("/load_default" in msg.topic or - "/disable_after_unplug" in msg.topic or "/prio" in msg.topic): self._validate_value(msg, bool, pub_json=True) elif "/chargemode/selected" in msg.topic: @@ -511,6 +510,8 @@ def process_chargepoint_topic(self, msg: mqtt.MQTTMessage): "openWB/set/chargepoint/get/daily_imported" in msg.topic or "openWB/set/chargepoint/get/daily_exported" in msg.topic): self._validate_value(msg, float, [(0, float("inf"))]) + elif re.search("chargepoint/[0-9]+/config/template$", msg.topic) is not None: + self._validate_value(msg, int, pub_json=True) elif "template" in msg.topic: self._validate_value(msg, "json") elif re.search("chargepoint/[0-9]+/config$", msg.topic) is not None: @@ -538,8 +539,6 @@ def process_chargepoint_topic(self, msg: mqtt.MQTTMessage): self._validate_value(msg, float) elif "/set/log" in msg.topic: self._validate_value(msg, "json") - elif "/set/change_ev_permitted" in msg.topic: - self._validate_value(msg, "json") elif "/config/ev" in msg.topic: self._validate_value( msg, int, [(0, float("inf"))], pub_json=True) @@ -567,6 +566,8 @@ def process_chargepoint_topic(self, msg: mqtt.MQTTMessage): self._validate_value(msg, float, [(0, float("inf"))]) elif "/control_parameter/state" in msg.topic: self._validate_value(msg, int, [(0, 7)]) + elif "/disable_after_unplug" in msg.topic: + self._validate_value(msg, bool, pub_json=True) else: self.__unknown_topic(msg) else: @@ -729,7 +730,8 @@ def process_general_topic(self, msg: mqtt.MQTTMessage): try: if "openWB/set/general/extern_display_mode" in msg.topic: self._validate_value(msg, str) - elif ("openWB/set/general/modbus_control" in msg.topic or + elif ("openWB/set/general/http_api" in msg.topic or + "openWB/set/general/modbus_control" in msg.topic or "openWB/set/general/extern" in msg.topic): self._validate_value(msg, bool) elif "openWB/set/general/control_interval" in msg.topic: @@ -750,12 +752,13 @@ def process_general_topic(self, msg: mqtt.MQTTMessage): self._validate_value(msg, int, [(0, float("inf"))]) elif "openWB/set/general/chargemode_config/pv_charging/switch_off_threshold" in msg.topic: self._validate_value(msg, float) - elif "openWB/set/general/chargemode_config/pv_charging/phase_switch_delay" in msg.topic: + elif "openWB/set/general/chargemode_config/phase_switch_delay" in msg.topic: self._validate_value(msg, int, [(1, 15)]) elif "openWB/set/general/chargemode_config/pv_charging/control_range" in msg.topic: self._validate_value(msg, int, collection=list) elif (("openWB/set/general/chargemode_config/pv_charging/phases_to_use" in msg.topic or - "openWB/set/general/chargemode_config/scheduled_charging/phases_to_use" in msg.topic)): + "openWB/set/general/chargemode_config/scheduled_charging/phases_to_use" in msg.topic or + "openWB/set/general/chargemode_config/scheduled_charging/phases_to_use_pv" in msg.topic)): self._validate_value(msg, int, [(0, 0), (1, 1), (3, 3)]) elif "openWB/set/general/chargemode_config/pv_charging/min_bat_soc" in msg.topic: self._validate_value(msg, int, [(0, 100)]) @@ -873,12 +876,12 @@ def process_counter_topic(self, msg: mqtt.MQTTMessage): self._validate_value(msg, float, [(0, float("inf"))]) elif "openWB/set/counter/get/hierarchy" in msg.topic: self._validate_value(msg, None) + elif "openWB/set/counter/config/home_consumption_source_id" in msg.topic: + self._validate_value(msg, int) elif "openWB/set/counter/set/simulation" in msg.topic: self._validate_value(msg, "json") elif "/set/consumption_left" in msg.topic: self._validate_value(msg, float) - elif "/config/selected" in msg.topic: - self._validate_value(msg, str) elif "/module" in msg.topic: self._validate_value(msg, "json") elif "/config/max_currents" in msg.topic: @@ -897,7 +900,6 @@ def process_counter_topic(self, msg: mqtt.MQTTMessage): self._validate_value( msg, float, [(-1, 1)], collection=list) elif ("/get/power_average" in msg.topic - or "/get/unbalanced_load" in msg.topic or "/get/frequency" in msg.topic or "/get/daily_exported" in msg.topic or "/get/daily_imported" in msg.topic @@ -909,8 +911,7 @@ def process_counter_topic(self, msg: mqtt.MQTTMessage): self._validate_value(msg, int, [(0, 2)]) elif "/set/error_counter" in msg.topic: self._validate_value(msg, int, [(0, float("inf"))]) - elif ("/get/fault_str" in msg.topic or - "/set/state_str" in msg.topic): + elif "/get/fault_str" in msg.topic: self._validate_value(msg, str) elif "/get/power" in msg.topic: self._validate_value( @@ -984,6 +985,7 @@ def process_system_topic(self, msg: mqtt.MQTTMessage): "openWB/set/system/wizard_done" in msg.topic or "openWB/set/system/update_in_progress" in msg.topic or "openWB/set/system/backup_cloud/backup_before_update" in msg.topic or + "openWB/set/system/installAssistantDone" in msg.topic or "openWB/set/system/dataprotection_acknowledged" in msg.topic or "openWB/set/system/usage_terms_acknowledged" in msg.topic or "openWB/set/system/update_config_completed" in msg.topic): diff --git a/packages/helpermodules/subdata.py b/packages/helpermodules/subdata.py index b7d3f23487..d8bc88df2b 100644 --- a/packages/helpermodules/subdata.py +++ b/packages/helpermodules/subdata.py @@ -23,6 +23,7 @@ from helpermodules.abstract_plans import AutolockPlan from helpermodules.broker import InternalBrokerClient from helpermodules.messaging import MessageType, pub_system_message +from helpermodules.utils.run_command import run_command from helpermodules.utils.topic_parser import decode_payload, get_index, get_second_index from control import optional from helpermodules.pub import Pub @@ -30,6 +31,9 @@ from control import pv from dataclass_utils import dataclass_from_dict from modules.common.abstract_vehicle import CalculatedSocState, GeneralVehicleConfig +from modules.common.configurable_backup_cloud import ConfigurableBackupCloud +from modules.common.configurable_ripple_control_receiver import ConfigurableRcr +from modules.common.configurable_tariff import ConfigurableElectricityTariff from modules.common.simcount.simcounter_state import SimCounterState from modules.internal_chargepoint_handler.internal_chargepoint_handler_config import ( GlobalHandlerData, InternalChargepoint, RfidData) @@ -571,11 +575,14 @@ def process_general_topic(self, var: general.General, msg: mqtt.MQTTMessage): config_dict = decode_payload(msg.payload) if config_dict["type"] is None: var.data.ripple_control_receiver.module = None + var.ripple_control_receiver = None else: mod = importlib.import_module(".ripple_control_receivers." + config_dict["type"]+".ripple_control_receiver", "modules") config = dataclass_from_dict(mod.device_descriptor.configuration_factory, config_dict) - var.data.ripple_control_receiver.module = mod.create_ripple_control_receiver(config) + var.data.ripple_control_receiver.module = config_dict + var.ripple_control_receiver = ConfigurableRcr( + config=config, component_initialiser=mod.create_ripple_control_receiver) elif re.search("/general/ripple_control_receiver/get/", msg.topic) is not None: self.set_json_payload_class(var.data.ripple_control_receiver.get, msg) elif re.search("/general/ripple_control_receiver/", msg.topic) is not None: @@ -602,12 +609,24 @@ def process_general_topic(self, var: general.General, msg: mqtt.MQTTMessage): # 5 Min Handler bis auf Heartbeat, Cleanup, ... beenden self.event_jobs_running.clear() self.set_json_payload_class(var.data, msg) - subprocess.run([ + run_command([ str(Path(__file__).resolve().parents[2] / "runs" / "setup_network.sh") - ]) + ], process_exception=True) elif "openWB/general/modbus_control" == msg.topic: if decode_payload(msg.payload) and self.general_data.data.extern: self.event_modbus_server.set() + elif "openWB/general/http_api" == msg.topic: + if ( + self.event_subdata_initialized.is_set() and + self.general_data.data.http_api != decode_payload(msg.payload) + ): + pub_system_message( + msg.payload, + "Bitte die openWB " + "neu starten, damit die Änderungen an der HTTP-API wirksam werden.", + MessageType.SUCCESS + ) + self.set_json_payload_class(var.data, msg) else: self.set_json_payload_class(var.data, msg) except Exception: @@ -633,9 +652,9 @@ def process_optional_topic(self, var: optional.Optional, msg: mqtt.MQTTMessage): self.set_json_payload_class(var.data.int_display, msg) if re.search("/(standby|active|rotation)$", msg.topic) is not None: # some topics require an update of the display manager or boot settings - subprocess.run([ + run_command([ str(Path(__file__).resolve().parents[2] / "runs" / "update_local_display.sh") - ]) + ], process_exception=True) elif re.search("/optional/et/", msg.topic) is not None: if re.search("/optional/et/get/prices", msg.topic) is not None: var.data.et.get.prices = decode_payload(msg.payload) @@ -649,7 +668,7 @@ def process_optional_topic(self, var: optional.Optional, msg: mqtt.MQTTMessage): mod = importlib.import_module( f".electricity_tariffs.{config_dict['type']}.tariff", "modules") config = dataclass_from_dict(mod.device_descriptor.configuration_factory, config_dict) - var.et_module = mod.create_electricity_tariff(config) + var.et_module = ConfigurableElectricityTariff(config, mod.create_electricity_tariff) var.et_get_prices() else: self.set_json_payload_class(var.data.et, msg) @@ -759,12 +778,14 @@ def process_system_topic(self, client: mqtt.Client, var: dict, msg: mqtt.MQTTMes if self.event_subdata_initialized.is_set(): index = get_index(msg.topic) parent_file = Path(__file__).resolve().parents[2] - result = subprocess.run( - ["php", "-f", str(parent_file / "runs" / "save_mqtt.php"), index, msg.payload], - stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) - if len(result.stdout) > 0: - pub_system_message(msg.payload, result.stdout, - MessageType.SUCCESS if result.returncode == 0 else MessageType.ERROR) + try: + result = run_command( + ["php", "-f", str(parent_file / "runs" / "save_mqtt.php"), index, msg.payload]) + pub_system_message(msg.payload, result, MessageType.SUCCESS) + except subprocess.CalledProcessError as e: + log.debug(e.stdout) + pub_system_message(msg.payload, f'Fehler-Status: {e.returncode}
Meldung: {e.stderr}', + MessageType.ERROR) else: log.debug("skipping mqtt bridge message on startup") elif "mqtt" and "valid_partner_ids" in msg.topic: @@ -779,8 +800,8 @@ def process_system_topic(self, client: mqtt.Client, var: dict, msg: mqtt.MQTTMes token = splitted[0] port = splitted[1] if len(splitted) > 1 else "2223" user = splitted[2] if len(splitted) > 2 else "getsupport" - subprocess.run([str(Path(__file__).resolve().parents[2] / "runs" / "start_remote_support.sh"), - token, port, user]) + run_command([str(Path(__file__).resolve().parents[2] / "runs" / "start_remote_support.sh"), + token, port, user], process_exception=True) elif "openWB/system/backup_cloud/config" in msg.topic: config_dict = decode_payload(msg.payload) if config_dict["type"] is None: @@ -788,9 +809,9 @@ def process_system_topic(self, client: mqtt.Client, var: dict, msg: mqtt.MQTTMes else: mod = importlib.import_module(".backup_clouds."+config_dict["type"]+".backup_cloud", "modules") config = dataclass_from_dict(mod.device_descriptor.configuration_factory, config_dict) - var["system"].backup_cloud = mod.create_backup_cloud(config) + var["system"].backup_cloud = ConfigurableBackupCloud(config, mod.create_backup_cloud) elif "openWB/system/backup_cloud/backup_before_update" in msg.topic: - self.set_json_payload(var["system"].data, msg) + self.set_json_payload(var["system"].data["backup_cloud"], msg) else: if "module_update_completed" in msg.topic: self.event_module_update_completed.set() diff --git a/packages/helpermodules/system.py b/packages/helpermodules/system.py index 340e10aecd..c49889e550 100644 --- a/packages/helpermodules/system.py +++ b/packages/helpermodules/system.py @@ -11,6 +11,7 @@ from helpermodules import pub from control import data +from helpermodules.utils.run_command import run_command from modules.common.configurable_backup_cloud import ConfigurableBackupCloud log = logging.getLogger(__name__) @@ -21,7 +22,8 @@ def __init__(self): """ """ self.data = {"update_in_progress": False, - "perform_update": False} + "perform_update": False, + "backup_cloud": {}} self.backup_cloud: Optional[ConfigurableBackupCloud] = None def perform_update(self): @@ -39,8 +41,8 @@ def perform_update(self): self._trigger_ext_update(train) time.sleep(15) # aktuell soll kein Update für den Master durchgeführt werden. - # subprocess.run([str(Path(__file__).resolve().parents[2]/"runs"/"update_self.sh"), train]) - subprocess.run(str(Path(__file__).resolve().parents[2]/"runs"/"atreboot.sh")) + # run_command([str(Path(__file__).resolve().parents[2]/"runs"/"update_self.sh"), train]) + run_command(str(Path(__file__).resolve().parents[2]/"runs"/"atreboot.sh"), process_exception=True) except Exception: log.exception("Fehler im System-Modul") @@ -98,12 +100,13 @@ def create_backup_and_send_to_cloud(self): log.debug('Nächtliche Sicherung erstellt und hochgeladen.') def create_backup(self) -> str: - result = subprocess.run([str(self._get_parent_file() / "runs" / "backup.sh"), "1"], stdout=subprocess.PIPE) - if result.returncode == 0: - file_name = result.stdout.decode("utf-8").rstrip('\n') + try: + result = run_command([str(self._get_parent_file() / "runs" / "backup.sh"), "1"]) + file_name = result.rstrip('\n') return file_name - else: - raise Exception(f'Backup-Status: {result.returncode}, Meldung: {result.stdout.decode("utf-8")}') + except subprocess.CalledProcessError as e: + log.debug(e.stdout) + raise Exception(f'Backup-Status: {e.returncode}, Meldung: {e.stderr}') def _get_parent_file(self) -> Path: return Path(__file__).resolve().parents[2] diff --git a/packages/helpermodules/timecheck.py b/packages/helpermodules/timecheck.py index 97f51fc6b2..27d6847483 100644 --- a/packages/helpermodules/timecheck.py +++ b/packages/helpermodules/timecheck.py @@ -3,7 +3,6 @@ import copy import logging import datetime -import math from dateutil.relativedelta import relativedelta from typing import Dict, List, Optional, Tuple, TypeVar, Union @@ -344,6 +343,12 @@ def convert_timedelta_to_time_string(timedelta_obj: datetime.timedelta) -> str: def convert_timestamp_delta_to_time_string(timestamp: int, delta: int) -> str: - diff = delta - (create_timestamp() - timestamp) - minute_diff = int(diff/60) - return f"{f'{minute_diff} Min. ' if minute_diff > 0 else ''}{math.ceil(diff%60)} Sek." + diff = int(delta - (create_timestamp() - timestamp)) + seconds_diff = diff % 60 + minute_diff = int((diff - seconds_diff) / 60) + if minute_diff > 0 and seconds_diff > 0: + return f"{minute_diff} Min. {seconds_diff} Sek." + elif minute_diff > 0: + return f"{minute_diff} Min." + elif seconds_diff > 0: + return f"{seconds_diff} Sek." diff --git a/packages/helpermodules/timecheck_test.py b/packages/helpermodules/timecheck_test.py index da47c316db..e8a0ce920e 100644 --- a/packages/helpermodules/timecheck_test.py +++ b/packages/helpermodules/timecheck_test.py @@ -151,12 +151,21 @@ def test_check_timeframe(plan: Union[AutolockPlan, TimeChargingPlan], now: str, assert state == expected_state -def test_convert_timestamp_delta_to_time_string(): +@pytest.mark.parametrize("timestamp, expected", + [ + pytest.param(1652683202, "40 Sek."), + pytest.param(1652683222, "1 Min."), + pytest.param(1652683221.8, "59 Sek."), + pytest.param(1652683222.2, "1 Min."), + pytest.param(1652683232, "1 Min. 10 Sek.") + ] + ) +def test_convert_timestamp_delta_to_time_string(timestamp, expected): # setup delta = 90 # execution - time_string = timecheck.convert_timestamp_delta_to_time_string(1652683202, delta) + time_string = timecheck.convert_timestamp_delta_to_time_string(timestamp, delta) # evaluation - assert time_string == "40 Sek." + assert time_string == expected diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index 8290151aa0..8e7cd57256 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -5,7 +5,6 @@ import logging from pathlib import Path import re -import subprocess import time from typing import List, Optional from paho.mqtt.client import Client as MqttClient, MQTTMessage @@ -23,6 +22,8 @@ from helpermodules.measurement_logging.write_log import get_names from helpermodules.messaging import MessageType, pub_system_message from helpermodules.pub import Pub +from helpermodules.utils.json_file_handler import write_and_check +from helpermodules.utils.run_command import run_command from helpermodules.utils.topic_parser import decode_payload, get_index, get_second_index from control import counter_all from control import ev @@ -33,6 +34,7 @@ from modules.display_themes.cards.config import CardsDisplayTheme from modules.ripple_control_receivers.gpio.config import GpioRcr from modules.web_themes.standard_legacy.config import StandardLegacyWebTheme +from modules.devices.good_we.version import GoodWeVersion log = logging.getLogger(__name__) @@ -40,7 +42,7 @@ class UpdateConfig: - DATASTORE_VERSION = 44 + DATASTORE_VERSION = 54 valid_topic = [ "^openWB/bat/config/configured$", "^openWB/bat/set/charging_power_left$", @@ -74,6 +76,7 @@ class UpdateConfig: "^openWB/chargepoint/[0-9]+/control_parameter/chargemode$", "^openWB/chargepoint/[0-9]+/control_parameter/current_plan$", "^openWB/chargepoint/[0-9]+/control_parameter/imported_at_plan_start$", + "^openWB/chargepoint/[0-9]+/control_parameter/imported_instant_charging$", "^openWB/chargepoint/[0-9]+/control_parameter/limit$", "^openWB/chargepoint/[0-9]+/control_parameter/prio$", "^openWB/chargepoint/[0-9]+/control_parameter/required_current$", @@ -85,6 +88,7 @@ class UpdateConfig: "^openWB/chargepoint/[0-9]+/control_parameter/state$", "^openWB/chargepoint/[0-9]+/get/charge_state$", "^openWB/chargepoint/[0-9]+/get/currents$", + "^openWB/chargepoint/[0-9]+/get/evse_current$", "^openWB/chargepoint/[0-9]+/get/fault_state$", "^openWB/chargepoint/[0-9]+/get/fault_str$", "^openWB/chargepoint/[0-9]+/get/frequency$", @@ -97,7 +101,11 @@ class UpdateConfig: "^openWB/chargepoint/[0-9]+/get/power$", "^openWB/chargepoint/[0-9]+/get/powers$", "^openWB/chargepoint/[0-9]+/get/power_factors$", + "^openWB/chargepoint/[0-9]+/get/vehicle_id$", "^openWB/chargepoint/[0-9]+/get/voltages$", + "^openWB/chargepoint/[0-9]+/get/serial_number$", + "^openWB/chargepoint/[0-9]+/get/soc$", + "^openWB/chargepoint/[0-9]+/get/soc_timestamp$", "^openWB/chargepoint/[0-9]+/get/state_str$", "^openWB/chargepoint/[0-9]+/get/connected_vehicle/soc$", "^openWB/chargepoint/[0-9]+/get/connected_vehicle/info$", @@ -111,7 +119,6 @@ class UpdateConfig: "^openWB/chargepoint/[0-9]+/set/plug_state_prev$", "^openWB/chargepoint/[0-9]+/set/plug_time$", "^openWB/chargepoint/[0-9]+/set/rfid$", - "^openWB/chargepoint/[0-9]+/set/change_ev_permitted$", "^openWB/chargepoint/[0-9]+/set/log$", "^openWB/chargepoint/[0-9]+/set/phases_to_use$", "^openWB/chargepoint/[0-9]+/set/charging_ev_prev$", @@ -130,6 +137,7 @@ class UpdateConfig: "^openWB/command/todo$", "^openWB/counter/config/reserve_for_not_charging$", + "^openWB/counter/config/home_consumption_source_id$", "^openWB/counter/get/hierarchy$", "^openWB/counter/set/disengageable_smarthome_power$", "^openWB/counter/set/imported_home_consumption$", @@ -153,7 +161,6 @@ class UpdateConfig: "^openWB/counter/[0-9]+/set/error_counter$", "^openWB/counter/[0-9]+/set/released_surplus$", "^openWB/counter/[0-9]+/set/reserved_surplus$", - "^openWB/counter/[0-9]+/set/state_str$", "^openWB/counter/[0-9]+/config/max_currents$", "^openWB/counter/[0-9]+/config/max_total_power$", @@ -163,6 +170,7 @@ class UpdateConfig: "^openWB/general/external_buttons_hw$", "^openWB/general/grid_protection_configured$", "^openWB/general/grid_protection_active$", + "^openWB/general/http_api$", "^openWB/general/modbus_control$", "^openWB/general/mqtt_bridge$", "^openWB/general/grid_protection_timestamp$", @@ -188,7 +196,7 @@ class UpdateConfig: "^openWB/general/chargemode_config/pv_charging/switch_on_delay$", "^openWB/general/chargemode_config/pv_charging/switch_off_threshold$", "^openWB/general/chargemode_config/pv_charging/switch_off_delay$", - "^openWB/general/chargemode_config/pv_charging/phase_switch_delay$", + "^openWB/general/chargemode_config/phase_switch_delay$", "^openWB/general/chargemode_config/pv_charging/control_range$", "^openWB/general/chargemode_config/pv_charging/phases_to_use$", "^openWB/general/chargemode_config/pv_charging/min_bat_soc$", @@ -198,6 +206,7 @@ class UpdateConfig: "^openWB/general/chargemode_config/pv_charging/bat_power_reserve_active$", "^openWB/general/chargemode_config/retry_failed_phase_switches$", "^openWB/general/chargemode_config/scheduled_charging/phases_to_use$", + "^openWB/general/chargemode_config/scheduled_charging/phases_to_use_pv$", "^openWB/general/chargemode_config/instant_charging/phases_to_use$", "^openWB/general/chargemode_config/time_charging/phases_to_use$", # obsolet, Daten hieraus müssen nach prices/ überführt werden @@ -238,6 +247,8 @@ class UpdateConfig: "^openWB/pv/config/configured$", "^openWB/pv/get/exported$", + "^openWB/pv/get/fault_state$", + "^openWB/pv/get/fault_str$", "^openWB/pv/get/power$", "^openWB/pv/get/daily_exported$", "^openWB/pv/get/monthly_exported$", @@ -352,6 +363,8 @@ class UpdateConfig: "^openWB/LegacySmartHome/config/get/Devices/[0-9]+/device_updatesec$", "^openWB/LegacySmartHome/config/get/Devices/[0-9]+/device_username$", "^openWB/LegacySmartHome/config/set/Devices/[0-9]+/mode$", + "^openWB/LegacySmartHome/Devices/[0-9]+/device_manual_control$", + "^openWB/LegacySmartHome/Devices/[0-9]+/mode$", "^openWB/LegacySmartHome/Devices/[0-9]+/WHImported_temp$", "^openWB/LegacySmartHome/Devices/[0-9]+/RunningTimeToday$", "^openWB/LegacySmartHome/Devices/[0-9]+/oncountnor$", @@ -381,6 +394,7 @@ class UpdateConfig: "^openWB/system/current_commit", "^openWB/system/current_missing_commits", "^openWB/system/dataprotection_acknowledged$", + "^openWB/system/installAssistantDone$", "^openWB/system/datastore_version", "^openWB/system/debug_level$", "^openWB/system/device/[0-9]+/component/[0-9]+/config$", @@ -393,7 +407,6 @@ class UpdateConfig: "^openWB/system/device/module_update_completed$", "^openWB/system/ip_address$", "^openWB/system/lastlivevaluesJson$", - "^openWB/system/messages/[0-9]+$", "^openWB/system/mqtt/bridge/[0-9]+$", "^openWB/system/mqtt/valid_partner_ids$", "^openWB/system/release_train$", @@ -403,20 +416,22 @@ class UpdateConfig: "^openWB/system/version$", ] default_topic = ( + ("openWB/bat/config/configured", False), ("openWB/bat/get/fault_state", 0), ("openWB/bat/get/fault_str", NO_ERROR), ("openWB/chargepoint/get/power", 0), ("openWB/chargepoint/template/0", get_chargepoint_template_default()), ("openWB/counter/get/hierarchy", []), ("openWB/counter/config/reserve_for_not_charging", counter_all.Config().reserve_for_not_charging), - ("openWB/vehicle/0/name", ev.EvData().name), + ("openWB/counter/config/home_consumption_source_id", counter_all.Config().home_consumption_source_id), + ("openWB/vehicle/0/name", "Standard-Fahrzeug"), ("openWB/vehicle/0/charge_template", ev.Ev(0).charge_template.ct_num), ("openWB/vehicle/0/soc_module/config", NO_MODULE), ("openWB/vehicle/0/soc_module/general_config", dataclass_utils.asdict(GeneralVehicleConfig())), ("openWB/vehicle/0/ev_template", ev.Ev(0).ev_template.et_num), ("openWB/vehicle/0/tag_id", ev.Ev(0).data.tag_id), ("openWB/vehicle/0/get/soc", ev.Ev(0).data.get.soc), - ("openWB/vehicle/template/ev_template/0", asdict(ev.EvTemplateData(min_current=10))), + ("openWB/vehicle/template/ev_template/0", asdict(ev.EvTemplateData(name="Fahrzeug-Profil", min_current=10))), ("openWB/vehicle/template/charge_template/0", ev.get_charge_template_default()), ("openWB/general/chargemode_config/instant_charging/phases_to_use", 3), ("openWB/general/chargemode_config/pv_charging/bat_mode", BatConsiderationMode.EV_MODE.value), @@ -431,11 +446,12 @@ class UpdateConfig: ("openWB/general/chargemode_config/pv_charging/switch_on_delay", 30), ("openWB/general/chargemode_config/pv_charging/switch_on_threshold", 1500), ("openWB/general/chargemode_config/pv_charging/feed_in_yield", 0), - ("openWB/general/chargemode_config/pv_charging/phase_switch_delay", 7), + ("openWB/general/chargemode_config/phase_switch_delay", 7), ("openWB/general/chargemode_config/pv_charging/phases_to_use", 0), ("openWB/general/chargemode_config/retry_failed_phase_switches", ChargemodeConfig().retry_failed_phase_switches), ("openWB/general/chargemode_config/scheduled_charging/phases_to_use", 0), + ("openWB/general/chargemode_config/scheduled_charging/phases_to_use_pv", 0), ("openWB/general/chargemode_config/time_charging/phases_to_use", 1), ("openWB/general/chargemode_config/unbalanced_load", False), ("openWB/general/chargemode_config/unbalanced_load_limit", 18), @@ -444,6 +460,7 @@ class UpdateConfig: ("openWB/general/extern_display_mode", "primary"), ("openWB/general/external_buttons_hw", False), ("openWB/general/grid_protection_configured", True), + ("openWB/general/http_api", False), ("openWB/general/modbus_control", False), ("openWB/general/notifications/selected", "none"), ("openWB/general/notifications/plug", False), @@ -473,6 +490,7 @@ class UpdateConfig: ("openWB/optional/rfid/active", False), ("openWB/system/backup_cloud/config", NO_MODULE), ("openWB/system/backup_cloud/backup_before_update", True), + ("openWB/system/installAssistantDone", False), ("openWB/system/dataprotection_acknowledged", False), ("openWB/system/datastore_version", DATASTORE_VERSION), ("openWB/system/usage_terms_acknowledged", False), @@ -767,12 +785,12 @@ def upgrade(topic: str, payload) -> Optional[dict]: def upgrade_datastore_4(self) -> None: moved_file = False for path in Path("/etc/mosquitto/conf.d").glob('99-bridge-openwb-*.conf'): - subprocess.run(["sudo", "mv", str(path), str(path).replace("conf.d", "conf_local.d")]) + run_command(["sudo", "mv", str(path), str(path).replace("conf.d", "conf_local.d")], process_exception=True) moved_file = True self.__update_topic("openWB/system/datastore_version", 5) if moved_file: time.sleep(1) - subprocess.run([str(self.base_path / "runs" / "reboot.sh")]) + run_command([str(self.base_path / "runs" / "reboot.sh")], process_exception=True) def upgrade_datastore_5(self) -> None: def upgrade(topic: str, payload) -> Optional[dict]: @@ -1058,16 +1076,11 @@ def upgrade(topic: str, payload) -> None: bridge_configuration = decode_payload(payload) if bridge_configuration["remote"]["is_openwb_cloud"]: index = get_index(topic) - result = subprocess.run( - ["php", "-f", str(self.base_path / "runs" / "save_mqtt.php"), index, payload], - stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) - if result.returncode == 0: - log.info("successfully updated configuration of bridge " - f"'{bridge_configuration['name']}' ({index})") - pub_system_message(payload, result.stdout, MessageType.SUCCESS) - else: - log.error("update of configuration for bridge " - f"'{bridge_configuration['name']}' ({index}) failed! {result.stdout}") + result = run_command(["php", "-f", str(self.base_path / "runs" / "save_mqtt.php"), index, payload], + process_exception=True) + log.info("successfully updated configuration of bridge " + f"'{bridge_configuration['name']}' ({index})") + pub_system_message(payload, result, MessageType.SUCCESS) self._loop_all_received_topics(upgrade) self.__update_topic("openWB/system/datastore_version", 24) @@ -1491,3 +1504,150 @@ def upgrade(topic: str, payload) -> None: self._loop_all_received_topics(upgrade) Pub().pub("openWB/system/datastore_version", 44) + + def upgrade_datastore_44(self) -> None: + try: + corrupt_days = ["20240620", "20240619", "20240618"] + for topic, payload in self.all_received_topics.items(): + if topic == "openWB/counter/get/hierarchy": + top_entry = decode_payload(payload)[0] + if top_entry["type"] != "counter": + raise Exception("First item in hierarchy must be a counter") + evu_counter_str = f"counter{top_entry['id']}" + for corrupt_day in corrupt_days: + try: + filepath = f"{self.base_path}/data/daily_log/{corrupt_day}.json" + with open(filepath, "r") as jsonFile: + content = json.load(jsonFile) + for entry in content["entries"]: + for counter_entry in entry["counter"]: + if evu_counter_str == counter_entry and entry["counter"][counter_entry]["grid"] is False: + entry["counter"][counter_entry]["grid"] = True + break + else: + log.debug("all grid: False-bug does not exist in this installation") + return + write_and_check(filepath, content) + except Exception: + log.exception(f"Logdatei '{filepath}' konnte nicht konvertiert werden.") + try: + filepath = f"{self.base_path}/data/monthly_log/202406.json" + with open(filepath, "r") as jsonFile: + content = json.load(jsonFile) + for entry in content["entries"]: + if entry["date"] in corrupt_days: + for counter_entry in entry["counter"]: + if evu_counter_str == counter_entry: + entry["counter"][counter_entry]["grid"] = True + break + write_and_check(filepath, content) + except Exception: + log.exception(f"Logdatei '{filepath}' konnte nicht konvertiert werden.") + except Exception: + log.exception("Fehler beim Konvertieren der Logdateien") + self.__update_topic("openWB/system/datastore_version", 45) + + def upgrade_datastore_45(self) -> None: + def upgrade(topic: str, payload) -> Optional[dict]: + if re.search("^openWB/general/chargemode_config/pv_charging/phase_switch_delay$", topic) is not None: + delay = decode_payload(payload) + return { + "openWB/general/chargemode_config/phase_switch_delay": delay, + } + self._loop_all_received_topics(upgrade) + self.__update_topic("openWB/system/datastore_version", 46) + + def upgrade_datastore_46(self) -> None: + def upgrade(topic: str, payload) -> Optional[dict]: + if re.search("openWB/vehicle/template/charge_template/[0-9]+$", topic) is not None: + payload = decode_payload(payload) + if "disable_after_unplug" in payload: + updated_payload = payload + payload.pop("disable_after_unplug") + return {topic: updated_payload} + if re.search("openWB/chargepoint/template/[0-9]+$", topic) is not None: + payload = decode_payload(payload) + if "rfid_enabling" in payload: + updated_payload = payload + updated_payload["rfid_enabling"] = {} + payload.pop("rfid_enabling") + return {topic: updated_payload} + self._loop_all_received_topics(upgrade) + self.__update_topic("openWB/system/datastore_version", 47) + + def upgrade_datastore_47(self) -> None: + def upgrade(topic: str, payload) -> Optional[dict]: + if re.search("openWB/chargepoint/template/[0-9]+$", topic) is not None: + payload = decode_payload(payload) + if "disable_after_unplug" not in payload: + updated_payload = payload + updated_payload.update({"disable_after_unplug": False}) + return {topic: updated_payload} + self._loop_all_received_topics(upgrade) + self.__update_topic("openWB/system/datastore_version", 48) + + def upgrade_datastore_48(self) -> None: + def upgrade(topic: str, payload) -> None: + if re.search("openWB/system/device/[0-9]+", topic) is not None: + payload = decode_payload(payload) + # update version and firmware of GoodWe + if payload.get("type") == "good_we" and "version" not in payload["configuration"]: + payload["configuration"].update({"firmware": 8}) + payload["configuration"].update({"version": GoodWeVersion.V_1_7}) + Pub().pub(topic, payload) + self._loop_all_received_topics(upgrade) + self.__update_topic("openWB/system/datastore_version", 49) + + def upgrade_datastore_49(self) -> None: + Pub().pub("openWB/system/installAssistantDone", True) + Pub().pub("openWB/system/datastore_version", 50) + + def upgrade_datastore_50(self) -> None: + # es gibt noch Topics von gelöschten Komponenten unter openWB/(counter|pv|bat)/[0-9], aber keine Konfiguration + # zu den Komponenten. + def upgrade(topic: str, payload) -> Optional[dict]: + if re.search("openWB/(counter|pv|bat)/[0-9]+", topic) is not None: + for component_topic in self.all_received_topics.keys(): + if re.search("openWB/system/device/[0-9]+/component/[0-9]+/config$", component_topic) is not None: + if get_second_index(component_topic) == get_index(topic): + return + else: + return {topic: ""} + self._loop_all_received_topics(upgrade) + self.__update_topic("openWB/system/datastore_version", 51) + + def upgrade_datastore_51(self) -> None: + def upgrade(topic: str, payload) -> None: + if re.search("openWB/system/device/[0-9]+", topic) is not None: + payload = decode_payload(payload) + # update version and firmware of GoodWe + if payload.get("type") == "deye" and "device_type" in payload["configuration"]: + payload["configuration"].pop("device_type") + Pub().pub(topic, payload) + self._loop_all_received_topics(upgrade) + self.__update_topic("openWB/system/datastore_version", 52) + + def upgrade_datastore_52(self) -> None: + # PR reverted + self.__update_topic("openWB/system/datastore_version", 53) + + def upgrade_datastore_53(self) -> None: + def upgrade(topic: str, payload) -> Optional[dict]: + if "openWB/optional/int_display/theme" == topic: + configuration_payload = decode_payload(payload) + if configuration_payload.get("type") == "cards": + if configuration_payload["configuration"].get("enable_energy_flow_view") is None: + configuration_payload["configuration"].update({ + "enable_energy_flow_view": True, + }) + if configuration_payload["configuration"].get("enable_dashboard_card_vehicles") is None: + configuration_payload["configuration"].update({ + "enable_dashboard_card_vehicles": True, + }) + if configuration_payload["configuration"].get("simple_charge_point_view") is None: + configuration_payload["configuration"].update({ + "simple_charge_point_view": True, + }) + return {topic: configuration_payload} + self._loop_all_received_topics(upgrade) + self.__update_topic("openWB/system/datastore_version", 54) diff --git a/packages/helpermodules/update_config_test.py b/packages/helpermodules/update_config_test.py index 749283171f..79405ebd4f 100644 --- a/packages/helpermodules/update_config_test.py +++ b/packages/helpermodules/update_config_test.py @@ -5,13 +5,13 @@ 'openWB/chargepoint/5/get/voltages': b'[230.2,230.2,230.2]', 'openWB/chargepoint/3/get/state_str': b'"Keine Ladung, da kein Auto angesteckt ist."', 'openWB/chargepoint/3/config': (b'{"name": "Standard-Ladepunkt", "type": "mqtt", "ev": 0, "template": 0,' - b'"connected_phases": 3, "phase_1": 0, "auto_phase_switch_hw": false, ' + b'"connected_phases": 3, "phase_1": 1, "auto_phase_switch_hw": false, ' b'"control_pilot_interruption_hw": false, "id": 3, "connection_module": ' b'{"type": "mqtt", "name": "MQTT-Ladepunkt", "configuration": {}}, ' b'"power_module": {}}'), 'openWB/chargepoint/get/power': b'0', 'openWB/chargepoint/template/0': (b'{"autolock": {"active": false, "plans": {}, "wait_for_charging_end": false}, ' - b'"name": "Standard Ladepunkt-Profil", "rfid_enabling": false, ' + b'"name": "Standard Ladepunkt-Profil" ' b'"valid_tags": [], "id": 0}'), 'openWB/optional/int_display/theme': b'"cards"'} diff --git a/packages/helpermodules/utils/json_file_handler.py b/packages/helpermodules/utils/json_file_handler.py new file mode 100644 index 0000000000..e953b0473d --- /dev/null +++ b/packages/helpermodules/utils/json_file_handler.py @@ -0,0 +1,49 @@ +import json +import logging +import os +import shutil + +log = logging.getLogger(__name__) + + +def write_and_check(file_path, content): + """ + Schreibt den Inhalt in die Datei und überprüft, ob der Inhalt erfolgreich geschrieben wurde. + Falls nicht, wird die Sicherung wiederhergestellt und der Schreibvorgang einmalig erneut durchgeführt. + Schlägt der Schreibvorgang wieder fehl, wird die Sicherung wiederhergestellt. + """ + def _write_and_check(): + with open(file_path, 'w', encoding="utf-8") as file: + json.dump(content, file) + with open(file_path, 'r', encoding="utf-8") as file: + written_content = json.load(file) + if content != written_content: + raise ValueError("Der geschriebene Inhalt stimmt nicht mit dem erwarteten Inhalt überein.") + + def restore_backup(): + shutil.copyfile(backup_path, file_path) + log.debug("Sicherung erfolgreich wiederhergestellt.") + + def handle_broken_file(): + restore_backup() + try: + _write_and_check() + except Exception: + log.exception("Fehler beim Wiederherstellen und erneuten Schreiben der Datei. " + "Wiederherstellen der Sicherung.") + restore_backup() + + try: + backup_path = file_path + '.bak' + if os.path.exists(file_path): + shutil.copyfile(file_path, backup_path) + _write_and_check() + else: + with open(file_path, 'w', encoding="utf-8") as file: + json.dump(content, file) + except Exception: + log.exception("Fehler beim Schreiben der Datei. Wiederherstellen der Sicherung.") + handle_broken_file() + finally: + if os.path.exists(backup_path): + os.remove(backup_path) diff --git a/packages/helpermodules/utils/json_file_handler_test.py b/packages/helpermodules/utils/json_file_handler_test.py new file mode 100644 index 0000000000..30ae0409eb --- /dev/null +++ b/packages/helpermodules/utils/json_file_handler_test.py @@ -0,0 +1,34 @@ +import json +import os +import tempfile +from unittest.mock import Mock + +from helpermodules.utils import json_file_handler +from helpermodules.utils.json_file_handler import write_and_check + +import pytest + + +@pytest.mark.parametrize("load_return, expected_content", [ + ([{"new_key": "new_value"}], {"new_key": "new_value"}), + ([ValueError("Ungültige Daten"), {"new_key": "new_value"}], {"new_key": "new_value"}), + ([ValueError("Ungültige Daten"), ValueError("Ungültige Daten")], {"key": "value"}) +]) +def test_backup_restore_and_corrupt_data_handling(load_return, expected_content, monkeypatch): + mock_json_load = Mock(side_effect=load_return) + monkeypatch.setattr(json_file_handler.json, "load", mock_json_load) + with tempfile.NamedTemporaryFile(delete=False) as temp_file: + file_path = temp_file.name + try: + with open(file_path, 'w') as file: + json.dump({"key": "value"}, file) + write_and_check(file_path, {"new_key": "new_value"}) + with open(file_path, 'r') as file: + # mocked auch hier json.load, obwohl monkeypatch für json_file_handler.json.load + restored_content = json.loads(file.read()) + assert restored_content == expected_content + finally: + # Löschen Sie die temporären Dateien + os.remove(file_path) + if os.path.exists(file_path + ".bak"): + os.remove(file_path + ".bak") diff --git a/packages/helpermodules/utils/run_command.py b/packages/helpermodules/utils/run_command.py new file mode 100644 index 0000000000..4bb4acdf7a --- /dev/null +++ b/packages/helpermodules/utils/run_command.py @@ -0,0 +1,23 @@ +import logging +import subprocess + +log = logging.getLogger(__name__) + + +def run_command(command, process_exception: bool = False): + # if return is non-zero a CalledProcessError is raised + try: + result = subprocess.run( + command, + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + return result.stdout + except subprocess.CalledProcessError as e: + if process_exception: + log.debug(e.stdout) + log.exception(e.stderr) + else: + raise e diff --git a/packages/main.py b/packages/main.py index ed9cf2925b..0ecff398cc 100755 --- a/packages/main.py +++ b/packages/main.py @@ -1,7 +1,13 @@ #!/usr/bin/env python3 """Starten der benötigten Prozesse """ +# flake8: noqa: F402 import logging +from helpermodules import logger +# als erstes logging initalisieren, damit auch ImportError geloggt werden +logger.setup_logging() +log = logging.getLogger() + from pathlib import Path from random import randrange import schedule @@ -11,7 +17,7 @@ from threading import Thread from control.chargelog.chargelog import calculate_charge_cost -from helpermodules.changed_values_handler import ChangedValuesHandler +from helpermodules.changed_values_handler import ChangedValuesContext from helpermodules.measurement_logging.update_yields import update_daily_yields, update_pv_monthly_yearly_yields from helpermodules.measurement_logging.write_log import LogType, save_log from modules import loadvars @@ -19,7 +25,6 @@ from helpermodules import timecheck, update_config from helpermodules import subdata from helpermodules import setdata -from helpermodules import logger from helpermodules import command from helpermodules.modbusserver import start_modbus_server from helpermodules.pub import Pub @@ -34,9 +39,6 @@ from modules.utils import wait_for_module_update_completed from smarthome.smarthome import readmq, smarthome_handler -logger.setup_logging() -log = logging.getLogger() - class HandlerAlgorithm: def __init__(self): @@ -52,23 +54,21 @@ def handler_with_control_interval(): if (data.data.general_data.data.control_interval / 10) == self.interval_counter: data.data.copy_data() loadvars_.get_values() - changed_values_handler.pub_changed_values() wait_for_module_update_completed(loadvars_.event_module_update_completed, "openWB/set/system/device/module_update_completed") data.data.copy_data() - changed_values_handler.store_initial_values() - self.heartbeat = True - if data.data.system_data["system"].data["perform_update"]: - data.data.system_data["system"].perform_update() - return - elif data.data.system_data["system"].data["update_in_progress"]: - log.info("Regelung pausiert, da ein Update durchgeführt wird.") - event_global_data_initialized.set() - prep.setup_algorithm() - control.calc_current() - proc.process_algorithm_results() - data.data.graph_data.pub_graph_data() - changed_values_handler.pub_changed_values() + with ChangedValuesContext(loadvars_.event_module_update_completed): + self.heartbeat = True + if data.data.system_data["system"].data["perform_update"]: + data.data.system_data["system"].perform_update() + return + elif data.data.system_data["system"].data["update_in_progress"]: + log.info("Regelung pausiert, da ein Update durchgeführt wird.") + event_global_data_initialized.set() + prep.setup_algorithm() + control.calc_current() + proc.process_algorithm_results() + data.data.graph_data.pub_graph_data() self.interval_counter = 1 else: self.interval_counter = self.interval_counter + 1 @@ -86,14 +86,13 @@ def handler5MinAlgorithm(self): ausführt, die nur alle 5 Minuten ausgeführt werden müssen. """ try: - changed_values_handler.store_initial_values() - totals = save_log(LogType.DAILY) - update_daily_yields(totals) - update_pv_monthly_yearly_yields() - data.data.general_data.grid_protection() - data.data.optional_data.et_get_prices() - data.data.counter_all_data.validate_hierarchy() - changed_values_handler.pub_changed_values() + with ChangedValuesContext(loadvars_.event_module_update_completed): + totals = save_log(LogType.DAILY) + update_daily_yields(totals) + update_pv_monthly_yearly_yields() + data.data.general_data.grid_protection() + data.data.optional_data.et_get_prices() + data.data.counter_all_data.validate_hierarchy() except KeyboardInterrupt: log.critical("Ausführung durch exit_after gestoppt: "+traceback.format_exc()) except Exception: @@ -126,8 +125,8 @@ def handler5Min(self): general_internal_chargepoint_handler.event_start.set() else: general_internal_chargepoint_handler.internal_chargepoint_handler.heartbeat = False - - sub.system_data["system"].update_ip_address() + with ChangedValuesContext(loadvars_.event_module_update_completed): + sub.system_data["system"].update_ip_address() except KeyboardInterrupt: log.critical("Ausführung durch exit_after gestoppt: "+traceback.format_exc()) except Exception: @@ -154,8 +153,9 @@ def handler_random_nightly(self): @exit_after(10) def handler_hour(self): try: - for cp in data.data.cp_data.values(): - calculate_charge_cost(cp) + with ChangedValuesContext(loadvars_.event_module_update_completed): + for cp in data.data.cp_data.values(): + calculate_charge_cost(cp) except KeyboardInterrupt: log.critical("Ausführung durch exit_after gestoppt: "+traceback.format_exc()) except Exception: @@ -193,7 +193,6 @@ def schedule_jobs(): prep = prepare.Prepare() general_internal_chargepoint_handler = GeneralInternalChargepointHandler() rfid = RfidReader() - changed_values_handler = ChangedValuesHandler(loadvars_.event_module_update_completed) event_ev_template = threading.Event() event_ev_template.set() event_charge_template = threading.Event() @@ -254,7 +253,6 @@ def schedule_jobs(): event_update_config_completed.wait(300) Pub().pub("openWB/set/system/boot_done", True) Path(Path(__file__).resolve().parents[1]/"ramdisk"/"bootdone").touch() - changed_values_handler.store_initial_values() schedule_jobs() except Exception: log.exception("Fehler im Main-Modul") diff --git a/packages/modules/backup_clouds/nextcloud/backup_cloud.py b/packages/modules/backup_clouds/nextcloud/backup_cloud.py index 2d1cfd4960..cee8d87290 100644 --- a/packages/modules/backup_clouds/nextcloud/backup_cloud.py +++ b/packages/modules/backup_clouds/nextcloud/backup_cloud.py @@ -5,14 +5,13 @@ from modules.backup_clouds.nextcloud.config import NextcloudBackupCloud, NextcloudBackupCloudConfiguration from modules.common import req from modules.common.abstract_device import DeviceDescriptor -from modules.common.configurable_backup_cloud import ConfigurableBackupCloud log = logging.getLogger(__name__) def upload_backup(config: NextcloudBackupCloudConfiguration, backup_filename: str, backup_file: bytes) -> None: if config.user is None: - url_match = re.fullmatch(r'(http[s]?):\/\/([^/]+)\/(?:index.php\/)?s\/(.+)', config.ip_address) + url_match = re.fullmatch(r'(http[s]?):\/\/([\S^/]+)\/(?:index.php\/)?s\/(.+)', config.ip_address) if not url_match: raise ValueError(f"URL '{config.ip_address}' hat nicht die erwartete Form " "'https://server/index.php/s/user_token' oder 'https://server/s/user_token'") @@ -34,7 +33,7 @@ def upload_backup(config: NextcloudBackupCloudConfiguration, backup_filename: st def create_backup_cloud(config: NextcloudBackupCloud): def updater(backup_filename: str, backup_file: bytes): upload_backup(config.configuration, backup_filename, backup_file) - return ConfigurableBackupCloud(config=config, component_updater=updater) + return updater device_descriptor = DeviceDescriptor(configuration_factory=NextcloudBackupCloud) diff --git a/packages/modules/backup_clouds/nfs/backup_cloud.py b/packages/modules/backup_clouds/nfs/backup_cloud.py index 6099461e6d..6e84f3c088 100644 --- a/packages/modules/backup_clouds/nfs/backup_cloud.py +++ b/packages/modules/backup_clouds/nfs/backup_cloud.py @@ -5,7 +5,6 @@ from modules.backup_clouds.nfs.config import NfsBackupCloud, NfsBackupCloudConfiguration from modules.common.abstract_device import DeviceDescriptor -from modules.common.configurable_backup_cloud import ConfigurableBackupCloud log = logging.getLogger(__name__) nfs_mount = '/mnt/nfs_mount' @@ -67,7 +66,7 @@ def upload_backup(config: NfsBackupCloudConfiguration, backup_filename: str, bac def create_backup_cloud(config: NfsBackupCloud): def updater(backup_filename: str, backup_file: bytes): upload_backup(config.configuration, backup_filename, backup_file) - return ConfigurableBackupCloud(config=config, component_updater=updater) + return updater device_descriptor = DeviceDescriptor(configuration_factory=NfsBackupCloud) diff --git a/packages/modules/backup_clouds/onedrive/backup_cloud.py b/packages/modules/backup_clouds/onedrive/backup_cloud.py index 8f2a4d5f96..d45bb70778 100644 --- a/packages/modules/backup_clouds/onedrive/backup_cloud.py +++ b/packages/modules/backup_clouds/onedrive/backup_cloud.py @@ -7,7 +7,6 @@ from modules.backup_clouds.onedrive.api import get_tokens from modules.backup_clouds.onedrive.config import OneDriveBackupCloud, OneDriveBackupCloudConfiguration from modules.common.abstract_device import DeviceDescriptor -from modules.common.configurable_backup_cloud import ConfigurableBackupCloud log = logging.getLogger(__name__) @@ -36,7 +35,7 @@ def upload_backup(config: OneDriveBackupCloudConfiguration, backup_filename: str def create_backup_cloud(config: OneDriveBackupCloud): def updater(backup_filename: str, backup_file: bytes): upload_backup(config.configuration, backup_filename, backup_file) - return ConfigurableBackupCloud(config=config, component_updater=updater) + return updater device_descriptor = DeviceDescriptor(configuration_factory=OneDriveBackupCloud) diff --git a/packages/modules/backup_clouds/samba/backup_cloud.py b/packages/modules/backup_clouds/samba/backup_cloud.py index fb9e4854b5..32a0d6b8f9 100644 --- a/packages/modules/backup_clouds/samba/backup_cloud.py +++ b/packages/modules/backup_clouds/samba/backup_cloud.py @@ -8,7 +8,6 @@ from modules.backup_clouds.samba.config import SambaBackupCloud, SambaBackupCloudConfiguration from modules.common.abstract_device import DeviceDescriptor -from modules.common.configurable_backup_cloud import ConfigurableBackupCloud log = logging.getLogger(__name__) @@ -57,7 +56,7 @@ def upload_backup(config: SambaBackupCloudConfiguration, backup_filename: str, b def create_backup_cloud(config: SambaBackupCloud): def updater(backup_filename: str, backup_file: bytes): upload_backup(config.configuration, backup_filename, backup_file) - return ConfigurableBackupCloud(config=config, component_updater=updater) + return updater device_descriptor = DeviceDescriptor(configuration_factory=SambaBackupCloud) diff --git a/packages/modules/backup_clouds/samba/config.py b/packages/modules/backup_clouds/samba/config.py index 5f207e9ad5..41b7c1f98b 100644 --- a/packages/modules/backup_clouds/samba/config.py +++ b/packages/modules/backup_clouds/samba/config.py @@ -2,7 +2,8 @@ class SambaBackupCloudConfiguration: - def __init__(self, smb_path: Optional[str] = None, + def __init__(self, + smb_path: Optional[str] = "/", smb_server: Optional[str] = None, smb_share: Optional[str] = None, smb_user: Optional[str] = None, diff --git a/packages/modules/chargepoints/openwb_series2_satellit/chargepoint_module.py b/packages/modules/chargepoints/openwb_series2_satellit/chargepoint_module.py index ccfc1712ff..768bc0a93f 100644 --- a/packages/modules/chargepoints/openwb_series2_satellit/chargepoint_module.py +++ b/packages/modules/chargepoints/openwb_series2_satellit/chargepoint_module.py @@ -10,7 +10,6 @@ from modules.common.abstract_device import DeviceDescriptor from modules.common.component_context import SingleComponentUpdateContext from modules.common.fault_state import ComponentInfo, FaultState -from modules.common.hardware_check_context import SeriesHardwareCheckContext from modules.common.store import get_chargepoint_value_store from modules.common.component_state import ChargepointState from modules.common.version_by_telnet import get_version_by_telnet @@ -75,29 +74,30 @@ def get_values(self) -> None: if self.version is not None: with self.__client_error_context: try: - with SeriesHardwareCheckContext(self._client): + self.delay_second_cp(self.CP0_DELAY) + with self._client.client: + self._client.check_hardware(self.fault_state) if self.version is False: raise ValueError( "Firmware des openWB Satellit ist nicht mit openWB 2 kompatibel. " "Bitte den Support kontaktieren.") - self.delay_second_cp(self.CP0_DELAY) - with self._client.client: - currents = self._client.meter_client.get_currents() - phases_in_use = sum(1 for current in currents if current > 3) - plug_state, charge_state, _ = self._client.evse_client.get_plug_charge_state() + currents = self._client.meter_client.get_currents() + phases_in_use = sum(1 for current in currents if current > 3) + plug_state, charge_state, _ = self._client.evse_client.get_plug_charge_state() - chargepoint_state = ChargepointState( - power=self._client.meter_client.get_power()[1], - currents=currents, - imported=self._client.meter_client.get_imported(), - exported=0, - voltages=self._client.meter_client.get_voltages(), - plug_state=plug_state, - charge_state=charge_state, - phases_in_use=phases_in_use, - serial_number=self._client.meter_client.get_serial_number() - ) - self.store.set(chargepoint_state) + chargepoint_state = ChargepointState( + power=self._client.meter_client.get_power()[1], + currents=currents, + imported=self._client.meter_client.get_imported(), + exported=0, + voltages=self._client.meter_client.get_voltages(), + plug_state=plug_state, + charge_state=charge_state, + phases_in_use=phases_in_use, + serial_number=self._client.meter_client.get_serial_number() + ) + self.store.set(chargepoint_state) + self.__client_error_context.reset_error_counter() except AttributeError: self._create_client() self._validate_version() @@ -107,16 +107,18 @@ def get_values(self) -> None: def set_current(self, current: float) -> None: if self.version is not None: + if self.__client_error_context.error_counter_exceeded(): + current = 0 with SingleComponentUpdateContext(self.fault_state, update_always=False): with self.__client_error_context: try: - with SeriesHardwareCheckContext(self._client): - self.delay_second_cp(self.CP0_DELAY) - with self._client.client: - if self.version: - self._client.evse_client.set_current(int(current)) - else: - self._client.evse_client.set_current(0) + self.delay_second_cp(self.CP0_DELAY) + with self._client.client: + self._client.check_hardware(self.fault_state) + if self.version: + self._client.evse_client.set_current(int(current)) + else: + self._client.evse_client.set_current(0) except AttributeError: self._create_client() self._validate_version() @@ -126,20 +128,20 @@ def switch_phases(self, phases_to_use: int, duration: int) -> None: with SingleComponentUpdateContext(self.fault_state, update_always=False): with self.__client_error_context: try: - with SeriesHardwareCheckContext(self._client): - with self._client.client: - if phases_to_use == 1: - self._client.client.delegate.write_register( - 0x0001, 256, unit=self.ID_PHASE_SWITCH_UNIT) - time.sleep(1) - self._client.client.delegate.write_register( - 0x0001, 512, unit=self.ID_PHASE_SWITCH_UNIT) - else: - self._client.client.delegate.write_register( - 0x0002, 512, unit=self.ID_PHASE_SWITCH_UNIT) - time.sleep(1) - self._client.client.delegate.write_register( - 0x0002, 256, unit=self.ID_PHASE_SWITCH_UNIT) + with self._client.client: + self._client.check_hardware(self.fault_state) + if phases_to_use == 1: + self._client.client.delegate.write_register( + 0x0001, 256, unit=self.ID_PHASE_SWITCH_UNIT) + time.sleep(1) + self._client.client.delegate.write_register( + 0x0001, 512, unit=self.ID_PHASE_SWITCH_UNIT) + else: + self._client.client.delegate.write_register( + 0x0002, 512, unit=self.ID_PHASE_SWITCH_UNIT) + time.sleep(1) + self._client.client.delegate.write_register( + 0x0002, 256, unit=self.ID_PHASE_SWITCH_UNIT) except AttributeError: self._create_client() self._validate_version() diff --git a/packages/modules/common/component_state.py b/packages/modules/common/component_state.py index 44a1b3a84b..afcd76d0bf 100644 --- a/packages/modules/common/component_state.py +++ b/packages/modules/common/component_state.py @@ -192,6 +192,7 @@ def __init__(self, self.prices = prices +@auto_str class RcrState: def __init__(self, override_value: float) -> None: self.override_value = override_value diff --git a/packages/modules/common/component_type.py b/packages/modules/common/component_type.py index 20ce20d93b..896f1f5737 100644 --- a/packages/modules/common/component_type.py +++ b/packages/modules/common/component_type.py @@ -3,6 +3,7 @@ class ComponentType(Enum): + BACKUP_CLOUD = "backup_cloud" BAT = "bat" CHARGEPOINT = "cp" COUNTER = "counter" diff --git a/packages/modules/common/configurable_backup_cloud.py b/packages/modules/common/configurable_backup_cloud.py index 1514d85f2b..c29ba2c764 100644 --- a/packages/modules/common/configurable_backup_cloud.py +++ b/packages/modules/common/configurable_backup_cloud.py @@ -1,5 +1,9 @@ from typing import TypeVar, Generic, Callable +from modules.common.component_context import SingleComponentUpdateContext +from modules.common.component_type import ComponentType +from modules.common.fault_state import ComponentInfo, FaultState + T_BACKUP_CLOUD_CONFIG = TypeVar("T_BACKUP_CLOUD_CONFIG") @@ -7,9 +11,14 @@ class ConfigurableBackupCloud(Generic[T_BACKUP_CLOUD_CONFIG]): def __init__(self, config: T_BACKUP_CLOUD_CONFIG, - component_updater: Callable[[str, bytes], None]) -> None: - self.__component_updater = component_updater + component_initialiser: Callable[[], float]) -> None: self.config = config + self.fault_state = FaultState(ComponentInfo(None, self.config.name, + ComponentType.BACKUP_CLOUD.value)) + with SingleComponentUpdateContext(self.fault_state): + self._component_updater = component_initialiser(config) def update(self, backup_filename: str, backup_file: bytes): - self.__component_updater(backup_filename, backup_file) + if hasattr(self, "_component_updater"): + # Wenn beim Initialisieren etwas schief gelaufen ist, ursprüngliche Fehlermeldung beibehalten + self._component_updater(backup_filename, backup_file) diff --git a/packages/modules/common/configurable_ripple_control_receiver.py b/packages/modules/common/configurable_ripple_control_receiver.py index ff94bd51ed..d238fccec5 100644 --- a/packages/modules/common/configurable_ripple_control_receiver.py +++ b/packages/modules/common/configurable_ripple_control_receiver.py @@ -12,13 +12,16 @@ class ConfigurableRcr(Generic[T_RCR_CONFIG]): def __init__(self, config: T_RCR_CONFIG, - component_updater: Callable[[], float]) -> None: - self.__component_updater = component_updater + component_initialiser: Callable[[], float]) -> None: self.config = config self.fault_state = FaultState(ComponentInfo(None, self.config.name, ComponentType.RIPPLE_CONTROL_RECEIVER.value)) + with SingleComponentUpdateContext(self.fault_state): + self._component_updater = component_initialiser(config) self.store = store.get_ripple_control_receiver_value_store() def update(self): - with SingleComponentUpdateContext(self.fault_state): - self.store.set(self.__component_updater()) + if hasattr(self, "_component_updater"): + # Wenn beim Initialisieren etwas schief gelaufen ist, ursprüngliche Fehlermeldung beibehalten + with SingleComponentUpdateContext(self.fault_state): + self.store.set(self._component_updater()) diff --git a/packages/modules/common/configurable_tariff.py b/packages/modules/common/configurable_tariff.py index 65d16dd4d8..6c83747861 100644 --- a/packages/modules/common/configurable_tariff.py +++ b/packages/modules/common/configurable_tariff.py @@ -13,22 +13,25 @@ class ConfigurableElectricityTariff(Generic[T_TARIFF_CONFIG]): def __init__(self, config: T_TARIFF_CONFIG, - component_updater: Callable[[], None]) -> None: - self.__component_updater = component_updater + component_initialiser: Callable[[], float]) -> None: self.config = config self.store = store.get_electricity_tariff_value_store() self.fault_state = FaultState(ComponentInfo(None, self.config.name, ComponentType.ELECTRICITY_TARIFF.value)) + with SingleComponentUpdateContext(self.fault_state): + self._component_updater = component_initialiser(config) def update(self): - with SingleComponentUpdateContext(self.fault_state): - tariff_state = self.__component_updater() - current_hour = create_unix_timestamp_current_full_hour() - self.store.set(tariff_state) - self.store.update() - for timestamp in tariff_state.prices.keys(): - if timestamp < current_hour: - self.fault_state.warning('Die Preisliste startet nicht mit der aktuellen Stunde.') - if len(tariff_state.prices) < 24: - self.fault_state.no_error(f'Die Preisliste hat nicht 24, sondern {len(tariff_state.prices)} Einträge. ' - 'Die Strompreise werden vom Anbieter erst um 14:00 für den Folgetag ' - 'aktualisiert.') + if hasattr(self, "_component_updater"): + # Wenn beim Initialisieren etwas schief gelaufen ist, ursprüngliche Fehlermeldung beibehalten + with SingleComponentUpdateContext(self.fault_state): + tariff_state = self._component_updater() + current_hour = create_unix_timestamp_current_full_hour() + self.store.set(tariff_state) + self.store.update() + for timestamp in tariff_state.prices.keys(): + if timestamp < current_hour: + self.fault_state.warning('Die Preisliste startet nicht mit der aktuellen Stunde.') + if len(tariff_state.prices) < 24: + self.fault_state.no_error( + f'Die Preisliste hat nicht 24, sondern {len(tariff_state.prices)} Einträge. ' + 'Die Strompreise werden vom Anbieter erst um 14:00 für den Folgetag aktualisiert.') diff --git a/packages/modules/common/hardware_check.py b/packages/modules/common/hardware_check.py index 8d620c75f4..a4700baba7 100644 --- a/packages/modules/common/hardware_check.py +++ b/packages/modules/common/hardware_check.py @@ -10,19 +10,16 @@ OPEN_TICKET = (" Bitte nehme bei anhaltenden Problemen über die Support-Funktion in den Einstellungen Kontakt mit " + "uns auf.") RS485_ADPATER_BROKEN = ("Auslesen von Zähler UND Evse nicht möglich. Vermutlich ist {} defekt oder zwei " - f"Busteilnehmer haben die gleiche Modbus-ID. Bitte die Zähler-ID prüfen. {OPEN_TICKET}") + "Busteilnehmer haben die gleiche Modbus-ID. Bitte die Zähler-ID prüfen.") USB_ADAPTER_BROKEN = RS485_ADPATER_BROKEN.format('der USB-Adapter') LAN_ADAPTER_BROKEN = (f"{RS485_ADPATER_BROKEN.format('der LAN-Konverter abgestürzt,')} " "Bitte den openWB series2 satellit stromlos machen.") -METER_PROBLEM = ("Der Zähler konnte nicht ausgelesen werden. " - f"Vermutlich ist der Zähler falsch konfiguriert oder defekt. {OPEN_TICKET}") -METER_BROKEN = ("Die Spannungen des Zählers konnten nicht korrekt ausgelesen werden. " - f"Der Zähler ist defekt. {OPEN_TICKET}") +METER_PROBLEM = "Der Zähler konnte nicht ausgelesen werden. Vermutlich ist der Zähler falsch konfiguriert oder defekt." +METER_BROKEN = "Die Spannungen des Zählers konnten nicht korrekt ausgelesen werden: {}V Der Zähler ist defekt." METER_NO_SERIAL_NUMBER = ("Die Seriennummer des Zählers für das Ladelog kann nicht ausgelesen werden. Wenn Sie die " "Seriennummer für Abrechnungszwecke benötigen, wenden Sie sich bitte an unseren Support. Die " "Funktionalität wird dadurch nicht beeinträchtigt!") -EVSE_BROKEN = ("Auslesen der EVSE nicht möglich. " - f"Vermutlich ist die EVSE defekt oder hat eine unbekannte Modbus-ID. {OPEN_TICKET}") +EVSE_BROKEN = "Auslesen der EVSE nicht möglich. Vermutlich ist die EVSE defekt oder hat eine unbekannte Modbus-ID." def check_meter_values(voltages: List[float]) -> Optional[str]: @@ -33,7 +30,7 @@ def valid_voltage(voltage) -> bool: (valid_voltage(voltages[0]) and valid_voltage(voltages[1]) and valid_voltage((voltages[2])))): return None else: - return METER_BROKEN + return METER_BROKEN.format(voltages) class ClientHandlerProtocol(Protocol): @@ -63,7 +60,7 @@ def handle_exception(self: ClientHandlerProtocol, exception: Exception): else: return False - def check_hardware(self: ClientHandlerProtocol): + def check_hardware(self: ClientHandlerProtocol, fault_state: FaultState): try: if self.evse_client.get_firmware_version() > EVSE_MIN_FIRMWARE: @@ -73,17 +70,23 @@ def check_hardware(self: ClientHandlerProtocol): except Exception as e: evse_check_passed = self.handle_exception(e) meter_check_passed, meter_error_msg = self.check_meter() - if meter_check_passed is False and evse_check_passed is False: - if isinstance(self.client, ModbusTcpClient_): - raise Exception(LAN_ADAPTER_BROKEN) - else: - raise Exception(USB_ADAPTER_BROKEN) if meter_check_passed is False: - raise Exception(meter_error_msg) - elif meter_check_passed and meter_error_msg is not None: - self.fault_state.warning(meter_error_msg) + if evse_check_passed is False: + if isinstance(self.client, ModbusTcpClient_): + raise Exception(LAN_ADAPTER_BROKEN + OPEN_TICKET) + else: + raise Exception(USB_ADAPTER_BROKEN + OPEN_TICKET) + else: + raise Exception(meter_error_msg + OPEN_TICKET) + elif evse_check_passed and meter_check_passed and meter_error_msg is not None: + if meter_error_msg != METER_NO_SERIAL_NUMBER: + meter_error_msg += OPEN_TICKET + fault_state.warning(meter_error_msg) if evse_check_passed is False: - raise Exception(EVSE_BROKEN) + if meter_error_msg is not None: + raise Exception(EVSE_BROKEN + " " + meter_error_msg + OPEN_TICKET) + else: + raise Exception(EVSE_BROKEN + OPEN_TICKET) def check_meter(self: ClientHandlerProtocol) -> Tuple[bool, Optional[str]]: try: diff --git a/packages/modules/common/hardware_check_context.py b/packages/modules/common/hardware_check_context.py deleted file mode 100644 index 112f5ee939..0000000000 --- a/packages/modules/common/hardware_check_context.py +++ /dev/null @@ -1,13 +0,0 @@ -from modules.internal_chargepoint_handler.clients import ClientHandler - - -class SeriesHardwareCheckContext: - def __init__(self, client: ClientHandler): - self.client = client - - def __enter__(self): - self.client.check_hardware() - return None - - def __exit__(self, exception_type, exception, exception_traceback) -> bool: - return True diff --git a/packages/modules/common/hardware_check_test.py b/packages/modules/common/hardware_check_test.py index 6e96898556..27bc555174 100644 --- a/packages/modules/common/hardware_check_test.py +++ b/packages/modules/common/hardware_check_test.py @@ -1,13 +1,14 @@ +import re from typing import List, Optional, Tuple, Union -from unittest.mock import Mock, patch +from unittest.mock import MagicMock, Mock, patch import pytest from modules.common import sdm from modules.common.evse import Evse from modules.common.hardware_check import ( - EVSE_BROKEN, LAN_ADAPTER_BROKEN, METER_BROKEN, METER_NO_SERIAL_NUMBER, METER_PROBLEM, USB_ADAPTER_BROKEN, - SeriesHardwareCheckMixin, check_meter_values) -from modules.common.modbus import NO_CONNECTION, ModbusSerialClient_, ModbusTcpClient_ + EVSE_BROKEN, LAN_ADAPTER_BROKEN, METER_BROKEN, METER_NO_SERIAL_NUMBER, METER_PROBLEM, OPEN_TICKET, + USB_ADAPTER_BROKEN, SeriesHardwareCheckMixin, check_meter_values) +from modules.common.modbus import NO_CONNECTION, ModbusClient, ModbusSerialClient_, ModbusTcpClient_ from modules.conftest import SAMPLE_IP, SAMPLE_PORT from modules.internal_chargepoint_handler.clients import ClientHandler @@ -17,6 +18,9 @@ "handle_exception_return_value, client_spec, expected_error_msg"), [pytest.param(Exception("Modbus"), None, None, [230]*3, None, False, ModbusSerialClient_, EVSE_BROKEN, id="EVSE defekt"), + pytest.param(Exception("Modbus"), None, None, [230, 0, 230], None, False, ModbusSerialClient_, + EVSE_BROKEN + " " + METER_BROKEN.format([230, 0, 230]) + OPEN_TICKET, + id="EVSE defekt und Zähler eine Phase defekt"), pytest.param(None, 18, Exception("Modbus"), None, None, None, ModbusSerialClient_, METER_PROBLEM, id="Zähler verkonfiguriert"), pytest.param(Exception("Modbus"), None, Exception("Modbus"), None, None, False, ModbusSerialClient_, @@ -51,9 +55,12 @@ def test_hardware_check_fails(evse_side_effect, handle_exception_mock = Mock(side_effect=handle_exception_side_effect, return_value=handle_exception_return_value) monkeypatch.setattr(SeriesHardwareCheckMixin, "handle_exception", handle_exception_mock) + mock_modbus_client = MagicMock(spec=client_spec, address=SAMPLE_IP, port=SAMPLE_PORT) + mock_modbus_client.__enter__.return_value = mock_modbus_client + # execution and evaluation - with pytest.raises(Exception, match=expected_error_msg): - ClientHandler(0, Mock(spec=client_spec, address=SAMPLE_IP, port=SAMPLE_PORT), [1], Mock()) + with pytest.raises(Exception, match=re.escape(expected_error_msg)): + ClientHandler(0, mock_modbus_client, [1], Mock()) def test_hardware_check_succeeds(monkeypatch): @@ -66,9 +73,12 @@ def test_hardware_check_succeeds(monkeypatch): mock_find_meter_client = Mock(spec=sdm.Sdm630, return_value=mock_meter_client) monkeypatch.setattr(ClientHandler, "find_meter_client", mock_find_meter_client) + mock_modbus_client = MagicMock(spec=ModbusClient) + mock_modbus_client.__enter__.return_value = mock_modbus_client + # execution and evaluation # keine Exception - ClientHandler(0, Mock(), [1], Mock()) + ClientHandler(0, mock_modbus_client, [1], Mock()) @pytest.mark.parametrize( @@ -87,7 +97,7 @@ def test_check_meter_values(voltages, expected_msg, monkeypatch): msg = check_meter_values(voltages) # assert - assert msg == expected_msg + assert msg == expected_msg if expected_msg is None else expected_msg.format(voltages) @patch('modules.common.hardware_check.ClientHandlerProtocol') diff --git a/packages/modules/common/simcount/_simcount.py b/packages/modules/common/simcount/_simcount.py index 66f14d3452..5c07af4a44 100644 --- a/packages/modules/common/simcount/_simcount.py +++ b/packages/modules/common/simcount/_simcount.py @@ -2,12 +2,13 @@ Berechnet die importierte und exportierte Leistung, wenn der Zähler / PV-Modul / Speicher diese nicht liefert. """ import logging +import math import time from control import data as data_module from modules.common.simcount._calculate import calculate_import_export from modules.common.simcount.simcounter_state import SimCounterState -from modules.common.simcount._simcounter_store import get_sim_counter_store +from modules.common.simcount._simcounter_store import get_sim_counter_store, restore_last_energy log = logging.getLogger(__name__) @@ -30,12 +31,21 @@ def sim_count(power_present: float, topic: str = "", data: SimCounterState = Non timestamp_present = time.time() previous_state = store.load(prefix, topic) if data is None else data + if math.isnan(power_present): + raise ValueError("power_present is NaN.") + if isinstance(power_present, (int, float)): if previous_state is None: log.debug("No previous state found. Starting new simulation.") return store.initialize(prefix, topic, power_present, timestamp_present) else: log.debug("Previous state: %s", previous_state) + if math.isnan(previous_state.imported): + log.error("imported is NaN. Reset simcount state.") + previous_state.imported = restore_last_energy(topic, "imported") + if math.isnan(previous_state.exported): + log.error("exported is NaN. Reset simcount state.") + previous_state.exported = restore_last_energy(topic, "exported") control_interval = data_module.data.general_data.data.control_interval if 2 * control_interval < timestamp_present - previous_state.timestamp: log.warning("Time difference between previous state and current state is too large. " @@ -44,6 +54,8 @@ def sim_count(power_present: float, topic: str = "", data: SimCounterState = Non else: hours_since_previous = (timestamp_present - previous_state.timestamp) / 3600 imported, exported = calculate_import_export(hours_since_previous, previous_state.power, power_present) + if math.isnan(imported) or math.isnan(exported): + raise ValueError("imported or exported is NaN. Retain previous state.") current_state = SimCounterState( timestamp_present, power_present, diff --git a/packages/modules/common/simcount/_simcounter_store.py b/packages/modules/common/simcount/_simcounter_store.py index aaaf7b84fe..587a8d1245 100644 --- a/packages/modules/common/simcount/_simcounter_store.py +++ b/packages/modules/common/simcount/_simcounter_store.py @@ -6,7 +6,10 @@ from paho.mqtt.client import Client as MqttClient, MQTTMessage +from control import data from helpermodules import pub, compatibility +from helpermodules.utils.topic_parser import get_index, get_second_index +from modules.common.component_type import type_to_topic_mapping from modules.common.simcount.simcounter_state import SimCounterState from modules.common.store import ramdisk_write, ramdisk_read_float from modules.common.store.ramdisk.io import RamdiskReadError @@ -182,5 +185,14 @@ def save(self, prefix: str, topic: str, state: SimCounterState): pub.Pub().pub(topic + "simulation", vars(state)) +def restore_last_energy(topic: str, value: str): + device_id = get_index(topic) + component_id = get_second_index(topic) + module_type = type_to_topic_mapping( + data.data.system_data[f"device{device_id}"].components[f"component{component_id}"].component_config.type) + module = getattr(data.data, f"{module_type}_data")[f"{module_type}{get_second_index(topic)}"].data.get + return getattr(module, value) + + def get_sim_counter_store() -> SimCounterStore: return SimCounterStoreRamdisk() if compatibility.is_ramdisk_in_use() else SimCounterStoreBroker() diff --git a/packages/modules/common/store/_api.py b/packages/modules/common/store/_api.py index 1c02aef108..8afca62477 100644 --- a/packages/modules/common/store/_api.py +++ b/packages/modules/common/store/_api.py @@ -27,8 +27,14 @@ def set(self, state: T) -> None: self.delegate.set(state) def update(self) -> None: - log.info("Saving %s", self.delegate.state) - self.delegate.update() + try: + log.info("Saving %s", self.delegate.state) + self.delegate.update() + except AttributeError: + # Wenn keine Daten ausgelesen werden, fehlt das state-Attribut. + pass + except Exception: + log.exception("Error while publishing module data") def update_values(component): diff --git a/packages/modules/common/store/_counter.py b/packages/modules/common/store/_counter.py index efd0e9bd86..a66430b247 100644 --- a/packages/modules/common/store/_counter.py +++ b/packages/modules/common/store/_counter.py @@ -93,17 +93,11 @@ def add_exported(element): for element in elements: if element["type"] == ComponentType.CHARGEPOINT.value: chargepoint = data.data.cp_data[f"cp{element['id']}"] - try: - self.currents = list(map(add, - self.currents, - convert_cp_currents_to_evu_currents( - chargepoint.data.config.phase_1, - chargepoint.data.get.currents))) - except KeyError: - raise KeyError("Für den virtuellen Zähler muss der Anschluss der Phasen von Ladepunkt" - f" {chargepoint.data.config.name} an die Phasen der EVU angegeben " - "werden.") - + self.currents = list(map(add, + self.currents, + convert_cp_currents_to_evu_currents( + chargepoint.data.config.phase_1, + chargepoint.data.get.currents))) self.power += chargepoint.data.get.power self.imported += chargepoint.data.get.imported elif element["type"] == ComponentType.BAT.value: diff --git a/packages/modules/devices/alpha_ess/counter.py b/packages/modules/devices/alpha_ess/counter.py index 42c7fb7a69..2a9466905b 100644 --- a/packages/modules/devices/alpha_ess/counter.py +++ b/packages/modules/devices/alpha_ess/counter.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 import time -from typing import Dict, Union +from typing import Callable, Dict, Union from dataclass_utils import dataclass_from_dict from modules.devices.alpha_ess.config import AlphaEssConfiguration, AlphaEssCounterSetup @@ -28,22 +28,23 @@ def __init__(self, def update(self): time.sleep(0.1) - counter_state = self.__get_values_factory() + factory_method = self.__get_values_factory() + counter_state = factory_method(self.__modbus_id) self.store.set(counter_state) - def __get_values_factory(self) -> CounterState: + def __get_values_factory(self) -> Callable[[int], CounterState]: if self.__device_config.source == 0 and self.__device_config.version == 0: return self.__get_values_before_v123 else: return self.__get_values_since_v123 - def __get_values_before_v123(self) -> CounterState: + def __get_values_before_v123(self, unit: int) -> CounterState: power, exported, imported = self.__tcp_client.read_holding_registers( - 0x6, [modbus.ModbusDataType.INT_32] * 3, unit=self.__modbus_id) + 0x6, [modbus.ModbusDataType.INT_32] * 3, unit=unit) exported *= 10 imported *= 10 currents = [val / 230 for val in self.__tcp_client.read_holding_registers( - 0x0000, [ModbusDataType.INT_32]*3, unit=self.__modbus_id)] + 0x0000, [ModbusDataType.INT_32]*3, unit=unit)] counter_state = CounterState( currents=currents, @@ -53,14 +54,14 @@ def __get_values_before_v123(self) -> CounterState: ) return counter_state - def __get_values_since_v123(self) -> CounterState: - power = self.__tcp_client.read_holding_registers(0x0021, ModbusDataType.INT_32, unit=self.__modbus_id) + def __get_values_since_v123(self, unit: int) -> CounterState: + power = self.__tcp_client.read_holding_registers(0x0021, ModbusDataType.INT_32, unit=unit) exported, imported = [ val * 10 for val in self.__tcp_client.read_holding_registers( - 0x0010, [ModbusDataType.INT_32] * 2, unit=self.__modbus_id + 0x0010, [ModbusDataType.INT_32] * 2, unit=unit )] currents = [val / 1000 for val in self.__tcp_client.read_holding_registers( - 0x0017, [ModbusDataType.INT_16]*3, unit=self.__modbus_id)] + 0x0017, [ModbusDataType.INT_16]*3, unit=unit)] counter_state = CounterState( currents=currents, diff --git a/packages/modules/devices/byd/bat.py b/packages/modules/devices/byd/bat.py index 330bc83316..c5c9cbfd03 100644 --- a/packages/modules/devices/byd/bat.py +++ b/packages/modules/devices/byd/bat.py @@ -3,7 +3,6 @@ from html.parser import HTMLParser from typing import Dict, List, Union, Tuple -from dataclass_utils import dataclass_from_dict from modules.devices.byd.config import BYDBatSetup from modules.common import req from modules.common.component_state import BatState @@ -20,7 +19,7 @@ def __init__(self, component_config: Union[Dict, BYDBatSetup], device_config) -> None: self.__device_config = device_config - self.component_config = dataclass_from_dict(BYDBatSetup, component_config) + self.component_config = component_config self.sim_counter = SimCounter(self.__device_config.id, self.component_config.id, prefix="speicher") self.store = get_bat_value_store(self.component_config.id) self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) diff --git a/packages/modules/devices/byd/device.py b/packages/modules/devices/byd/device.py index 503bc6d26d..116c7244e2 100644 --- a/packages/modules/devices/byd/device.py +++ b/packages/modules/devices/byd/device.py @@ -1,90 +1,25 @@ #!/usr/bin/env python3 import logging -from typing import Dict, Optional, List, Union -from dataclass_utils import dataclass_from_dict -from helpermodules.cli import run_using_positional_cli_args -from modules.devices.byd.config import BYD, BYDBatSetup, BYDConfiguration +from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, IndependentComponentUpdater +from modules.devices.byd.config import BYD, BYDBatSetup from modules.devices.byd import bat -from modules.common.abstract_device import AbstractDevice, DeviceDescriptor -from modules.common.component_context import SingleComponentUpdateContext +from modules.common.abstract_device import DeviceDescriptor log = logging.getLogger(__name__) -class Device(AbstractDevice): - COMPONENT_TYPE_TO_CLASS = { - "bat": bat.BYDBat - } +def create_device(device_config: BYD): + def create_bat_component(component_config: BYDBatSetup): + return bat.BYDBat(component_config, device_config) - def __init__(self, device_config: Union[Dict, BYD]) -> None: - self.components = {} # type: Dict[str, bat.BYDBat] - try: - self.device_config = dataclass_from_dict(BYD, device_config) - except Exception: - log.exception("Fehler im Modul "+self.device_config.name) - - def add_component(self, component_config: Union[Dict, BYDBatSetup]) -> None: - if isinstance(component_config, Dict): - component_type = component_config["type"] - else: - component_type = component_config.type - component_config = dataclass_from_dict(COMPONENT_TYPE_TO_MODULE[ - component_type].component_descriptor.configuration_factory, component_config) - if component_type in self.COMPONENT_TYPE_TO_CLASS: - self.components["component"+str(component_config.id)] = (self.COMPONENT_TYPE_TO_CLASS[component_type]( - component_config, - self.device_config)) - else: - raise Exception( - "illegal component type " + component_type + ". Allowed values: " + - ','.join(self.COMPONENT_TYPE_TO_CLASS.keys()) - ) - - def update(self) -> None: - log.debug("Start device reading " + str(self.components)) - if self.components: - for component in self.components: - # Auch wenn bei einer Komponente ein Fehler auftritt, sollen alle anderen noch ausgelesen werden. - with SingleComponentUpdateContext(self.components[component].fault_state): - self.components[component].update() - else: - log.warning( - self.device_config.name + - ": Es konnten keine Werte gelesen werden, da noch keine Komponenten konfiguriert wurden." - ) - - -COMPONENT_TYPE_TO_MODULE = { - "bat": bat -} - - -def read_legacy(component_type: str, - ip_address: str, - username: str, - password: str, - num: Optional[int] = None) -> None: - dev = Device(BYD(configuration=BYDConfiguration(user=username, password=password, ip_address=ip_address))) - if component_type in COMPONENT_TYPE_TO_MODULE: - component_config = COMPONENT_TYPE_TO_MODULE[component_type].component_descriptor.configuration_factory() - else: - raise Exception( - "illegal component type " + component_type + ". Allowed values: " + - ','.join(COMPONENT_TYPE_TO_MODULE.keys()) - ) - component_config.id = num - dev.add_component(component_config) - - log.debug('byd IP-Adresse: ' + ip_address) - log.debug('byd Benutzer: ' + username) - log.debug('byd Passwort: ' + password) - - dev.update() - - -def main(argv: List[str]): - run_using_positional_cli_args(read_legacy, argv) + return ConfigurableDevice( + device_config=device_config, + component_factory=ComponentFactoryByType( + bat=create_bat_component + ), + component_updater=IndependentComponentUpdater(lambda component: component.update()) + ) device_descriptor = DeviceDescriptor(configuration_factory=BYD) diff --git a/packages/modules/devices/carlo_gavazzi/counter.py b/packages/modules/devices/carlo_gavazzi/counter.py index dc25dabe37..efa07162e4 100644 --- a/packages/modules/devices/carlo_gavazzi/counter.py +++ b/packages/modules/devices/carlo_gavazzi/counter.py @@ -3,7 +3,6 @@ from pymodbus.constants import Endian -from dataclass_utils import dataclass_from_dict from modules.devices.carlo_gavazzi.config import CarloGavazziCounterSetup from modules.common import modbus from modules.common.component_state import CounterState @@ -21,7 +20,7 @@ def __init__(self, tcp_client: modbus.ModbusTcpClient_, modbus_id: int) -> None: self.__device_id = device_id - self.component_config = dataclass_from_dict(CarloGavazziCounterSetup, component_config) + self.component_config = component_config self.__tcp_client = tcp_client self.__modbus_id = modbus_id self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="bezug") diff --git a/packages/modules/devices/carlo_gavazzi/device.py b/packages/modules/devices/carlo_gavazzi/device.py index 18623efe52..e29dfe17bc 100644 --- a/packages/modules/devices/carlo_gavazzi/device.py +++ b/packages/modules/devices/carlo_gavazzi/device.py @@ -1,89 +1,39 @@ #!/usr/bin/env python3 import logging -from typing import Dict, Optional, List, Union +from typing import Iterable -from dataclass_utils import dataclass_from_dict -from helpermodules.cli import run_using_positional_cli_args +from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, MultiComponentUpdater from modules.devices.carlo_gavazzi import counter from modules.devices.carlo_gavazzi.config import CarloGavazzi, CarloGavazziCounterSetup from modules.common import modbus -from modules.common.abstract_device import AbstractDevice, DeviceDescriptor +from modules.common.abstract_device import DeviceDescriptor from modules.common.component_context import SingleComponentUpdateContext log = logging.getLogger(__name__) -class Device(AbstractDevice): - COMPONENT_TYPE_TO_CLASS = { - "counter": counter.CarloGavazziCounter, - } - - def __init__(self, device_config: Union[Dict, CarloGavazzi]) -> None: - self.components = {} # type: Dict[str, counter.CarloGavazziCounter] - try: - self.device_config = dataclass_from_dict(CarloGavazzi, device_config) - self.client = modbus.ModbusTcpClient_( - self.device_config.configuration.ip_address, self.device_config.configuration.port) - except Exception: - log.exception("Fehler im Modul "+self.device_config.name) - - def add_component(self, component_config: Union[Dict, CarloGavazziCounterSetup]) -> None: - if isinstance(component_config, Dict): - component_type = component_config["type"] - else: - component_type = component_config.type - component_config = dataclass_from_dict(COMPONENT_TYPE_TO_MODULE[ - component_type].component_descriptor.configuration_factory, component_config) - if component_type in self.COMPONENT_TYPE_TO_CLASS: - self.components["component"+str(component_config.id)] = self.COMPONENT_TYPE_TO_CLASS[component_type]( - self.device_config.id, component_config, self.client, - self.device_config.configuration.modbus_id) - else: - raise Exception( - "illegal component type " + component_type + ". Allowed values: " + - ','.join(self.COMPONENT_TYPE_TO_CLASS.keys()) - ) - - def update(self) -> None: - log.debug("Start device reading " + str(self.components)) - if self.components: - for component in self.components: - # Auch wenn bei einer Komponente ein Fehler auftritt, sollen alle anderen noch ausgelesen werden. - with SingleComponentUpdateContext(self.components[component].fault_state): - self.components[component].update() - else: - log.warning( - self.device_config.name + - ": Es konnten keine Werte gelesen werden, da noch keine Komponenten konfiguriert wurden." - ) - - -COMPONENT_TYPE_TO_MODULE = { - "counter": counter -} - - -def read_legacy(component_type: str, ip_address: str, num: Optional[int] = None) -> None: - device_config = CarloGavazzi() - device_config.configuration.ip_address = ip_address - dev = Device(device_config) - if component_type in COMPONENT_TYPE_TO_MODULE: - component_config = COMPONENT_TYPE_TO_MODULE[component_type].component_descriptor.configuration_factory() - else: - raise Exception( - "illegal component type " + component_type + ". Allowed values: " + - ','.join(COMPONENT_TYPE_TO_MODULE.keys()) - ) - component_config.id = num - dev.add_component(component_config) - - log.debug('carlo gavazzi IP-Adresse: ' + str(ip_address)) - - dev.update() - - -def main(argv: List[str]): - run_using_positional_cli_args(read_legacy, argv) +def create_device(device_config: CarloGavazzi): + def create_counter_component(component_config: CarloGavazziCounterSetup): + return counter.CarloGavazziCounter(device_config.id, component_config, client, + device_config.configuration.modbus_id) + + def update_components(components: Iterable[counter.CarloGavazziCounter]): + with client: + for component in components: + with SingleComponentUpdateContext(component.fault_state): + component.update() + + try: + client = modbus.ModbusTcpClient_(device_config.configuration.ip_address, device_config.configuration.port) + except Exception: + log.exception("Fehler in create_device") + return ConfigurableDevice( + device_config=device_config, + component_factory=ComponentFactoryByType( + counter=create_counter_component + ), + component_updater=MultiComponentUpdater(update_components) + ) device_descriptor = DeviceDescriptor(configuration_factory=CarloGavazzi) diff --git a/packages/modules/devices/deye/bat.py b/packages/modules/devices/deye/bat.py index 632b848842..aa52ea47c6 100644 --- a/packages/modules/devices/deye/bat.py +++ b/packages/modules/devices/deye/bat.py @@ -14,35 +14,41 @@ class DeyeBat: - def __init__(self, device_id: int, component_config: DeyeBatSetup) -> None: + def __init__(self, device_id: int, component_config: DeyeBatSetup, device_type: DeviceType) -> None: self.component_config = dataclass_from_dict(DeyeBatSetup, component_config) self.store = get_bat_value_store(self.component_config.id) self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) self.__device_id = device_id self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="speicher") + self.device_type = device_type - def update(self, client: ModbusTcpClient_, device_type: DeviceType) -> None: + def update(self, client: ModbusTcpClient_) -> None: unit = self.component_config.configuration.modbus_id - if device_type == DeviceType.THREE_PHASE: - power = client.read_holding_registers(590, ModbusDataType.INT_16, unit=unit) * -1 - soc = client.read_holding_registers(588, ModbusDataType.INT_16, unit=unit) - # 516: Geladen in kWh * 0,1 - imported = client.read_holding_registers(516, ModbusDataType.UINT_16, unit=unit) * 100 - # 518: Entladen in kWh * 0,1 - exported = client.read_holding_registers(518, ModbusDataType.UINT_16, unit=unit) * 100 - elif device_type == DeviceType.SINGLE_PHASE_STRING or device_type == DeviceType.SINGLE_PHASE_HYBRID: + if self.device_type == DeviceType.SINGLE_PHASE_STRING or self.device_type == DeviceType.SINGLE_PHASE_HYBRID: power = client.read_holding_registers(190, ModbusDataType.INT_16, unit=unit) * -1 soc = client.read_holding_registers(184, ModbusDataType.INT_16, unit=unit) - if device_type == DeviceType.SINGLE_PHASE_HYBRID: + if self.device_type == DeviceType.SINGLE_PHASE_HYBRID: # 516: Geladen in kWh * 0,1 imported = client.read_holding_registers(72, ModbusDataType.UINT_16, unit=unit) * 100 # 518: Entladen in kWh * 0,1 exported = client.read_holding_registers(74, ModbusDataType.UINT_16, unit=unit) * 100 - elif device_type == DeviceType.SINGLE_PHASE_STRING: + + elif self.device_type == DeviceType.SINGLE_PHASE_STRING: imported, exported = self.sim_counter.sim_count(power) + else: # THREE_PHASE_LV (0x0500, 0x0005), THREE_PHASE_HV (0x0006) + power = client.read_holding_registers(590, ModbusDataType.INT_16, unit=unit) * -1 + + if self.device_type == DeviceType.THREE_PHASE_HV: + power = power * 10 + soc = client.read_holding_registers(588, ModbusDataType.INT_16, unit=unit) + # 516: Geladen in kWh * 0,1 + imported = client.read_holding_registers(516, ModbusDataType.UINT_16, unit=unit) * 100 + # 518: Entladen in kWh * 0,1 + exported = client.read_holding_registers(518, ModbusDataType.UINT_16, unit=unit) * 100 + bat_state = BatState( power=power, soc=soc, diff --git a/packages/modules/devices/deye/config.py b/packages/modules/devices/deye/config.py index 95211f3155..38793440da 100644 --- a/packages/modules/devices/deye/config.py +++ b/packages/modules/devices/deye/config.py @@ -7,11 +7,9 @@ class DeyeConfiguration: def __init__(self, ip_address: Optional[str] = None, - port: int = 8899, - device_type: str = "three_phase"): + port: int = 8899): self.ip_address = ip_address self.port = port - self.device_type = device_type class Deye: diff --git a/packages/modules/devices/deye/counter.py b/packages/modules/devices/deye/counter.py index 925c5b29c6..1f6fc66bfe 100644 --- a/packages/modules/devices/deye/counter.py +++ b/packages/modules/devices/deye/counter.py @@ -11,44 +11,46 @@ class DeyeCounter: - def __init__(self, device_id: int, component_config: DeyeCounterSetup) -> None: + def __init__(self, device_id: int, component_config: DeyeCounterSetup, device_type: DeviceType) -> None: self.component_config = dataclass_from_dict(DeyeCounterSetup, component_config) self.store = get_counter_value_store(self.component_config.id) self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) self.__device_id = device_id self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="bezug") + self.device_type = device_type - def update(self, client: ModbusTcpClient_, device_type: DeviceType): + def update(self, client: ModbusTcpClient_): unit = self.component_config.configuration.modbus_id - if device_type == DeviceType.THREE_PHASE: - currents = [c / 100 for c in client.read_holding_registers(613, [ModbusDataType.INT_16]*3, unit=unit)] - voltages = [v / 10 for v in client.read_holding_registers(644, [ModbusDataType.INT_16]*3, unit=unit)] - powers = client.read_holding_registers(616, [ModbusDataType.INT_16]*3, unit=unit) - power = sum(powers) - frequency = client.read_holding_registers(187, ModbusDataType.INT_32, unit=unit) * 100 - - # Wenn der Import/export Netz in wh gerechnet wird => *100 !! kommt in kw/h *0.1 - imported = client.read_holding_registers(522, ModbusDataType.INT_16, unit=unit) * 100 - exported = client.read_holding_registers(524, ModbusDataType.INT_16, unit=unit) * 100 - - elif device_type == DeviceType.SINGLE_PHASE_STRING or device_type == DeviceType.SINGLE_PHASE_HYBRID: + if self.device_type == DeviceType.SINGLE_PHASE_STRING or self.device_type == DeviceType.SINGLE_PHASE_HYBRID: frequency = client.read_holding_registers(79, ModbusDataType.INT_16, unit=unit) * 100 - if device_type == DeviceType.SINGLE_PHASE_HYBRID: + if self.device_type == DeviceType.SINGLE_PHASE_HYBRID: powers = [0]*3 currents = [0]*3 voltages = [0]*3 power = [0] # High und low word vom import sind nicht in aufeinanderfolgenden Registern imported, exported = self.sim_counter.sim_count(power) - elif device_type == DeviceType.SINGLE_PHASE_STRING: + + elif self.device_type == DeviceType.SINGLE_PHASE_STRING: currents = [c / 100 for c in client.read_holding_registers(76, [ModbusDataType.INT_16]*3, unit=unit)] voltages = [v / 10 for v in client.read_holding_registers(70, [ModbusDataType.INT_16]*3, unit=unit)] powers = [currents[i] * voltages[i] for i in range(0, 3)] power = sum(powers) imported, exported = self.sim_counter.sim_count(power) + else: # THREE_PHASE_LV (0x0500, 0x0005), THREE_PHASE_HV (0x0006) + currents = [c / 100 for c in client.read_holding_registers(613, [ModbusDataType.INT_16]*3, unit=unit)] + voltages = [v / 10 for v in client.read_holding_registers(644, [ModbusDataType.INT_16]*3, unit=unit)] + powers = client.read_holding_registers(616, [ModbusDataType.INT_16]*3, unit=unit) + power = client.read_holding_registers(625, ModbusDataType.INT_16, unit=unit) + frequency = client.read_holding_registers(609, ModbusDataType.INT_16, unit=unit) / 100 + + # Wenn der Import/export Netz in wh gerechnet wird => *100 !! kommt in kw/h *0.1 + imported = client.read_holding_registers(522, ModbusDataType.INT_16, unit=unit) * 100 + exported = client.read_holding_registers(524, ModbusDataType.INT_16, unit=unit) * 100 + counter_state = CounterState( currents=currents, imported=imported, diff --git a/packages/modules/devices/deye/device.py b/packages/modules/devices/deye/device.py index 422c200919..18cef2eb42 100644 --- a/packages/modules/devices/deye/device.py +++ b/packages/modules/devices/deye/device.py @@ -6,10 +6,9 @@ from modules.common.abstract_device import DeviceDescriptor from modules.common.component_context import SingleComponentUpdateContext from modules.common.configurable_device import ConfigurableDevice, ComponentFactoryByType, MultiComponentUpdater -from modules.common.modbus import ModbusTcpClient_ +from modules.common.modbus import ModbusDataType, ModbusTcpClient_ from modules.devices.deye.bat import DeyeBat from modules.devices.deye.counter import DeyeCounter -from modules.devices.deye.device_type import DeviceType from modules.devices.deye.inverter import DeyeInverter from modules.devices.deye import bat, counter, inverter from modules.devices.deye.config import Deye, DeyeBatSetup, DeyeConfiguration, DeyeCounterSetup, DeyeInverterSetup @@ -19,19 +18,25 @@ def create_device(device_config: Deye): def create_bat_component(component_config: DeyeBatSetup): - return DeyeBat(device_config.id, component_config) + device_type = client.read_holding_registers( + 0, ModbusDataType.INT_16, unit=component_config.configuration.modbus_id) + return DeyeBat(device_config.id, component_config, device_type) def create_counter_component(component_config: DeyeCounterSetup): - return DeyeCounter(device_config.id, component_config) + device_type = client.read_holding_registers( + 0, ModbusDataType.INT_16, unit=component_config.configuration.modbus_id) + return DeyeCounter(device_config.id, component_config, device_type) def create_inverter_component(component_config: DeyeInverterSetup): - return DeyeInverter(device_config.id, component_config) + device_type = client.read_holding_registers( + 0, ModbusDataType.INT_16, unit=component_config.configuration.modbus_id) + return DeyeInverter(device_config.id, component_config, device_type) def update_components(components: Iterable[Union[DeyeBat, DeyeCounter, DeyeInverter]]): with client as c: for component in components: with SingleComponentUpdateContext(component.fault_state): - component.update(c, DeviceType(device_config.configuration.device_type)) + component.update(c) try: client = ModbusTcpClient_(device_config.configuration.ip_address, device_config.configuration.port) diff --git a/packages/modules/devices/deye/device_type.py b/packages/modules/devices/deye/device_type.py index 3baef0a7ba..6e38c65bab 100644 --- a/packages/modules/devices/deye/device_type.py +++ b/packages/modules/devices/deye/device_type.py @@ -2,6 +2,8 @@ class DeviceType(Enum): - SINGLE_PHASE_HYBRID = "single_phase_hybrid" - SINGLE_PHASE_STRING = "single_phase_string" - THREE_PHASE = "three_phase" + SINGLE_PHASE_STRING = 0x0200 + SINGLE_PHASE_HYBRID = 0x0300 + THREE_PHASE_LV_0 = 0x0500 + THREE_PHASE_LV_1 = 0x0005 + THREE_PHASE_HV = 0x0006 diff --git a/packages/modules/devices/deye/inverter.py b/packages/modules/devices/deye/inverter.py index b45979c46d..9d60ebc30d 100644 --- a/packages/modules/devices/deye/inverter.py +++ b/packages/modules/devices/deye/inverter.py @@ -13,24 +13,30 @@ class DeyeInverter: - def __init__(self, device_id: int, component_config: Union[Dict, DeyeInverterSetup]) -> None: + def __init__(self, device_id: int, + component_config: Union[Dict, DeyeInverterSetup], + device_type: DeviceType) -> None: self.component_config = dataclass_from_dict(DeyeInverterSetup, component_config) self.store = get_inverter_value_store(self.component_config.id) self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) self.__device_id = device_id self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="pv") + self.device_type = device_type - def update(self, client: ModbusTcpClient_, device_type: DeviceType) -> None: + def update(self, client: ModbusTcpClient_) -> None: unit = self.component_config.configuration.modbus_id - if device_type == DeviceType.THREE_PHASE: - # Wechselrichter hat 2 mppt Tracker + if self.device_type == DeviceType.SINGLE_PHASE_STRING or self.device_type == DeviceType.SINGLE_PHASE_HYBRID: + power = sum(client.read_holding_registers(186, [ModbusDataType.INT_16]*4, unit=unit)) * -1 + exported = self.sim_counter.sim_count(power)[1] + + else: # THREE_PHASE_LV (0x0500, 0x0005), THREE_PHASE_HV (0x0006) power = sum(client.read_holding_registers(672, [ModbusDataType.INT_16]*2, unit=unit)) * -1 + + if self.device_type == DeviceType.THREE_PHASE_HV: + power = power * 10 # 534: Gesamt Produktion Wechselrichter unsigned integer in kWh * 0,1 exported = client.read_holding_registers(534, ModbusDataType.UINT_16, unit=unit) * 100 - elif device_type == DeviceType.SINGLE_PHASE_STRING or device_type == DeviceType.SINGLE_PHASE_HYBRID: - power = sum(client.read_holding_registers(186, [ModbusDataType.INT_16]*4, unit=unit)) * -1 - exported = self.sim_counter.sim_count(power)[1] inverter_state = InverterState( power=power, diff --git a/packages/modules/devices/fox_ess/bat.py b/packages/modules/devices/fox_ess/bat.py new file mode 100644 index 0000000000..fa49a1c7b6 --- /dev/null +++ b/packages/modules/devices/fox_ess/bat.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +import logging +from dataclass_utils import dataclass_from_dict +from modules.common.component_state import BatState +from modules.common.component_type import ComponentDescriptor +from modules.common.fault_state import ComponentInfo, FaultState +from modules.common.modbus import ModbusDataType, ModbusTcpClient_ +from modules.common.store import get_bat_value_store +from modules.devices.fox_ess.config import FoxEssBatSetup + +log = logging.getLogger(__name__) + + +class FoxEssBat: + def __init__(self, component_config: FoxEssBatSetup) -> None: + self.component_config = dataclass_from_dict(FoxEssBatSetup, component_config) + self.store = get_bat_value_store(self.component_config.id) + self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) + + def update(self, client: ModbusTcpClient_) -> None: + unit = self.component_config.configuration.modbus_id + + power = client.read_holding_registers(31036, ModbusDataType.INT_16, unit=unit) * -1 + soc = client.read_holding_registers(31038, ModbusDataType.UINT_16, unit=unit) + # Geladen in kWh * 0,1 + imported = client.read_holding_registers(32003, ModbusDataType.UINT_32, unit=unit) * 100 + # Entladen in kWh * 0,1 + exported = client.read_holding_registers(32006, ModbusDataType.UINT_32, unit=unit) * 100 + + bat_state = BatState( + power=power, + soc=soc, + imported=imported, + exported=exported + ) + self.store.set(bat_state) + + +component_descriptor = ComponentDescriptor(configuration_factory=FoxEssBatSetup) diff --git a/packages/modules/devices/fox_ess/config.py b/packages/modules/devices/fox_ess/config.py new file mode 100644 index 0000000000..fd0b7a8019 --- /dev/null +++ b/packages/modules/devices/fox_ess/config.py @@ -0,0 +1,72 @@ +from typing import Optional +from helpermodules.auto_str import auto_str + +from modules.common.component_setup import ComponentSetup + + +class FoxEssConfiguration: + def __init__(self, + ip_address: Optional[str] = None, + port: int = 502): + self.ip_address = ip_address + self.port = port + + +class FoxEss: + def __init__(self, + name: str = "FoxESS", + type: str = "fox_ess", + id: int = 0, + configuration: FoxEssConfiguration = None) -> None: + self.name = name + self.type = type + self.id = id + self.configuration = configuration or FoxEssConfiguration() + + +@auto_str +class FoxEssBatConfiguration: + def __init__(self, modbus_id: int = 1): + self.modbus_id = modbus_id + + +@auto_str +class FoxEssBatSetup(ComponentSetup[FoxEssBatConfiguration]): + def __init__(self, + name: str = "FoxESS Speicher", + type: str = "bat", + id: int = 0, + configuration: FoxEssBatConfiguration = None) -> None: + super().__init__(name, type, id, configuration or FoxEssBatConfiguration()) + + +@auto_str +class FoxEssCounterConfiguration: + def __init__(self, modbus_id: int = 1): + self.modbus_id = modbus_id + + +@auto_str +class FoxEssCounterSetup(ComponentSetup[FoxEssCounterConfiguration]): + def __init__(self, + name: str = "FoxESS Zähler", + type: str = "counter", + id: int = 0, + configuration: FoxEssCounterConfiguration = None) -> None: + super().__init__(name, type, id, configuration or FoxEssCounterConfiguration()) + + +@auto_str +class FoxEssInverterConfiguration: + def __init__(self, modbus_id: int = 1): + self.modbus_id = modbus_id + + +@auto_str +class FoxEssInverterSetup(ComponentSetup[FoxEssInverterConfiguration]): + def __init__(self, + name: str = "FoxESS Wechselrichter", + type: str = "inverter", + id: int = 0, + configuration: FoxEssInverterConfiguration = None) -> None: + super().__init__(name, type, id, configuration or FoxEssInverterConfiguration()) diff --git a/packages/modules/devices/fox_ess/counter.py b/packages/modules/devices/fox_ess/counter.py new file mode 100644 index 0000000000..849548a0b7 --- /dev/null +++ b/packages/modules/devices/fox_ess/counter.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +from dataclass_utils import dataclass_from_dict +from modules.common.component_state import CounterState +from modules.common.component_type import ComponentDescriptor +from modules.common.fault_state import ComponentInfo, FaultState +from modules.common.modbus import ModbusDataType, ModbusTcpClient_ +from modules.common.store import get_counter_value_store +from modules.devices.fox_ess.config import FoxEssCounterSetup + + +class FoxEssCounter: + def __init__(self, component_config: FoxEssCounterSetup) -> None: + self.component_config = dataclass_from_dict(FoxEssCounterSetup, component_config) + self.store = get_counter_value_store(self.component_config.id) + self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) + + def update(self, client: ModbusTcpClient_): + unit = self.component_config.configuration.modbus_id + + powers = client.read_holding_registers(31026, [ModbusDataType.INT_16]*3, unit=unit) + power = sum(powers) + frequency = client.read_holding_registers(31015, ModbusDataType.UINT_16, unit=unit) / 100 + imported = client.read_holding_registers(32018, ModbusDataType.UINT_32, unit=unit) * 100 + exported = client.read_holding_registers(32015, ModbusDataType.UINT_32, unit=unit) * 100 + + counter_state = CounterState( + imported=imported, + exported=exported, + power=power, + powers=powers, + frequency=frequency + ) + self.store.set(counter_state) + + +component_descriptor = ComponentDescriptor(configuration_factory=FoxEssCounterSetup) diff --git a/packages/modules/devices/fox_ess/device.py b/packages/modules/devices/fox_ess/device.py new file mode 100644 index 0000000000..70592f4312 --- /dev/null +++ b/packages/modules/devices/fox_ess/device.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +import logging +from typing import Iterable, Union + +from modules.common.abstract_device import DeviceDescriptor +from modules.common.component_context import SingleComponentUpdateContext +from modules.common.configurable_device import ConfigurableDevice, ComponentFactoryByType, MultiComponentUpdater +from modules.common.modbus import ModbusTcpClient_ +from modules.devices.fox_ess.bat import FoxEssBat +from modules.devices.fox_ess.counter import FoxEssCounter +from modules.devices.fox_ess.inverter import FoxEssInverter +from modules.devices.fox_ess.config import FoxEss, FoxEssBatSetup, FoxEssCounterSetup, FoxEssInverterSetup + +log = logging.getLogger(__name__) + + +def create_device(device_config: FoxEss): + def create_bat_component(component_config: FoxEssBatSetup): + return FoxEssBat(component_config) + + def create_counter_component(component_config: FoxEssCounterSetup): + return FoxEssCounter(component_config) + + def create_inverter_component(component_config: FoxEssInverterSetup): + return FoxEssInverter(component_config) + + def update_components(components: Iterable[Union[FoxEssBat, FoxEssCounter, FoxEssInverter]]): + with client as c: + for component in components: + with SingleComponentUpdateContext(component.fault_state): + component.update(c) + + try: + client = ModbusTcpClient_(device_config.configuration.ip_address, device_config.configuration.port) + except Exception: + log.exception("Fehler in create_device") + return ConfigurableDevice( + device_config=device_config, + component_factory=ComponentFactoryByType( + bat=create_bat_component, + counter=create_counter_component, + inverter=create_inverter_component, + ), + component_updater=MultiComponentUpdater(update_components) + ) + + +device_descriptor = DeviceDescriptor(configuration_factory=FoxEss) diff --git a/packages/modules/devices/fox_ess/inverter.py b/packages/modules/devices/fox_ess/inverter.py new file mode 100644 index 0000000000..3a03811e5d --- /dev/null +++ b/packages/modules/devices/fox_ess/inverter.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +from typing import Dict, Union + +from dataclass_utils import dataclass_from_dict +from modules.common.component_state import InverterState +from modules.common.component_type import ComponentDescriptor +from modules.common.fault_state import ComponentInfo, FaultState +from modules.common.modbus import ModbusDataType, ModbusTcpClient_ +from modules.common.store import get_inverter_value_store +from modules.devices.fox_ess.config import FoxEssInverterSetup + + +class FoxEssInverter: + def __init__(self, component_config: Union[Dict, FoxEssInverterSetup]) -> None: + self.component_config = dataclass_from_dict(FoxEssInverterSetup, component_config) + self.store = get_inverter_value_store(self.component_config.id) + self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) + + def update(self, client: ModbusTcpClient_) -> None: + unit = self.component_config.configuration.modbus_id + # PV1 + PV2 Power + power = sum([self.__tcp_client.read_holding_registers( + reg, ModbusDataType.INT_16, unit=self.__modbus_id) + for reg in [31002, 31005]]) * -1 + # Gesamt Produktion Wechselrichter unsigned integer in kWh * 0,1 + exported = client.read_holding_registers(32000, ModbusDataType.UINT_32, unit=unit) * 100 + + inverter_state = InverterState( + power=power, + exported=exported, + ) + self.store.set(inverter_state) + + +component_descriptor = ComponentDescriptor(configuration_factory=FoxEssInverterSetup) diff --git a/packages/modules/devices/good_we/bat.py b/packages/modules/devices/good_we/bat.py index fb3592b39d..cdcc5109bd 100644 --- a/packages/modules/devices/good_we/bat.py +++ b/packages/modules/devices/good_we/bat.py @@ -9,14 +9,19 @@ from modules.common.fault_state import ComponentInfo, FaultState from modules.common.store import get_bat_value_store from modules.devices.good_we.config import GoodWeBatSetup +from modules.devices.good_we.version import GoodWeVersion class GoodWeBat: def __init__(self, modbus_id: int, + version: GoodWeVersion, + firmware: int, component_config: Union[Dict, GoodWeBatSetup], tcp_client: modbus.ModbusTcpClient_) -> None: self.__modbus_id = modbus_id + self.version = version + self.firmware = firmware self.component_config = dataclass_from_dict(GoodWeBatSetup, component_config) self.__tcp_client = tcp_client self.store = get_bat_value_store(self.component_config.id) @@ -24,7 +29,10 @@ def __init__(self, def update(self) -> None: with self.__tcp_client: - power = self.__tcp_client.read_holding_registers(35183, ModbusDataType.INT_16, unit=self.__modbus_id)*-1 + if self.version == GoodWeVersion.V_1_7: + power = self.__tcp_client.read_holding_registers(35183, ModbusDataType.INT_16, unit=self.__modbus_id)*-1 + else: + power = self.__tcp_client.read_holding_registers(35182, ModbusDataType.INT_32, unit=self.__modbus_id)*-1 soc = self.__tcp_client.read_holding_registers(37007, ModbusDataType.UINT_16, unit=self.__modbus_id) imported = self.__tcp_client.read_holding_registers( 35206, ModbusDataType.UINT_32, unit=self.__modbus_id) * 100 diff --git a/packages/modules/devices/good_we/config.py b/packages/modules/devices/good_we/config.py index db232d7833..32548da5b1 100644 --- a/packages/modules/devices/good_we/config.py +++ b/packages/modules/devices/good_we/config.py @@ -1,18 +1,25 @@ from typing import Optional from modules.common.component_setup import ComponentSetup +from modules.devices.good_we.version import GoodWeVersion class GoodWeConfiguration: - def __init__(self, ip_address: Optional[str] = None, modbus_id: int = 247, port: int = 502): + def __init__(self, ip_address: Optional[str] = None, + modbus_id: int = 247, + port: int = 502, + version: GoodWeVersion = GoodWeVersion.V_1_7, + firmware: int = 8): self.ip_address = ip_address self.modbus_id = modbus_id self.port = port + self.version = version + self.firmware = firmware class GoodWe: def __init__(self, - name: str = "GoodWe ET-Serie (5-10kW)", + name: str = "GoodWe ET-Serie", type: str = "good_we", id: int = 0, configuration: GoodWeConfiguration = None) -> None: diff --git a/packages/modules/devices/good_we/counter.py b/packages/modules/devices/good_we/counter.py index 8ff6feb683..f1c3c899a7 100644 --- a/packages/modules/devices/good_we/counter.py +++ b/packages/modules/devices/good_we/counter.py @@ -7,40 +7,61 @@ from modules.common.component_type import ComponentDescriptor from modules.common.fault_state import ComponentInfo, FaultState from modules.common.modbus import ModbusDataType +from modules.common.simcount import SimCounter from modules.common.store import get_counter_value_store from modules.devices.good_we.config import GoodWeCounterSetup +from modules.devices.good_we.version import GoodWeVersion class GoodWeCounter: def __init__(self, + device_id: int, modbus_id: int, + version: GoodWeVersion, + firmware: int, component_config: Union[Dict, GoodWeCounterSetup], tcp_client: modbus.ModbusTcpClient_) -> None: + self.__device_id = device_id self.__modbus_id = modbus_id + self.version = version + self.firmware = firmware self.component_config = dataclass_from_dict(GoodWeCounterSetup, component_config) + self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="bezug") self.__tcp_client = tcp_client self.store = get_counter_value_store(self.component_config.id) self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) def update(self): with self.__tcp_client: - power_factors = [ - val / 1000 for val in self.__tcp_client.read_holding_registers(36010, - [ModbusDataType.UINT_16]*3, - unit=self.__modbus_id)] - exported = self.__tcp_client.read_holding_registers( - 36015, ModbusDataType.FLOAT_32, unit=self.__modbus_id) - imported = self.__tcp_client.read_holding_registers( - 36017, ModbusDataType.FLOAT_32, unit=self.__modbus_id) - powers = [ - val * -1 for val in self.__tcp_client.read_holding_registers(36005, - [ModbusDataType.INT_16]*3, - unit=self.__modbus_id)] - power = self.__tcp_client.read_holding_registers(36008, ModbusDataType.INT_16, unit=self.__modbus_id) * -1 + if self.firmware < 9: + power = self.__tcp_client.read_holding_registers( + 36008, ModbusDataType.INT_16, unit=self.__modbus_id) * -1 + powers = [ + val * -1 for val in self.__tcp_client.read_holding_registers( + 36005, [ModbusDataType.INT_16]*3, unit=self.__modbus_id)] + else: + power = self.__tcp_client.read_holding_registers( + 36025, ModbusDataType.INT_32, unit=self.__modbus_id) * -1 + powers = [ + val * -1 for val in self.__tcp_client.read_holding_registers( + 36019, [ModbusDataType.INT_32]*3, unit=self.__modbus_id)] + power_factors = [ + val / 1000 for val in self.__tcp_client.read_holding_registers( + 36010, [ModbusDataType.INT_16]*3, unit=self.__modbus_id)] frequency = self.__tcp_client.read_holding_registers( 36014, ModbusDataType.UINT_16, unit=self.__modbus_id) / 100 + if self.version == GoodWeVersion.V_1_7: + exported = self.__tcp_client.read_holding_registers( + 36015, ModbusDataType.FLOAT_32, unit=self.__modbus_id) + imported = self.__tcp_client.read_holding_registers( + 36017, ModbusDataType.FLOAT_32, unit=self.__modbus_id) + else: + # v1.0 und v1.1 liefern keine Werte zurueck obwohl Register laut Doku gleich + # Alternative Register für die BTC Serie liefern statische Werte + imported, exported = self.sim_counter.sim_count(power) + counter_state = CounterState( powers=powers, imported=imported, diff --git a/packages/modules/devices/good_we/device.py b/packages/modules/devices/good_we/device.py index cb90cb2f50..661331c8b6 100644 --- a/packages/modules/devices/good_we/device.py +++ b/packages/modules/devices/good_we/device.py @@ -1,100 +1,56 @@ #!/usr/bin/env python3 import logging -from typing import Dict, List, Union, Optional +from typing import Iterable, Union -from dataclass_utils import dataclass_from_dict -from helpermodules.cli import run_using_positional_cli_args from modules.common import modbus -from modules.common.abstract_device import AbstractDevice, DeviceDescriptor +from modules.common.abstract_device import DeviceDescriptor from modules.common.component_context import SingleComponentUpdateContext +from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, MultiComponentUpdater from modules.devices.good_we import bat from modules.devices.good_we import counter from modules.devices.good_we import inverter -from modules.devices.good_we.config import (GoodWe, GoodWeBatSetup, GoodWeConfiguration, GoodWeCounterSetup, - GoodWeInverterSetup) +from modules.devices.good_we.config import GoodWe, GoodWeBatSetup, GoodWeCounterSetup, GoodWeInverterSetup log = logging.getLogger(__name__) good_we_component_classes = Union[bat.GoodWeBat, counter.GoodWeCounter, inverter.GoodWeInverter] -class Device(AbstractDevice): - COMPONENT_TYPE_TO_CLASS = { - "bat": bat.GoodWeBat, - "counter": counter.GoodWeCounter, - "inverter": inverter.GoodWeInverter - } - - def __init__(self, device_config: Union[Dict, GoodWe]) -> None: - self.components = {} # type: Dict[str, good_we_component_classes] - try: - self.device_config = dataclass_from_dict(GoodWe, device_config) - self.client = modbus.ModbusTcpClient_( - self.device_config.configuration.ip_address, self.device_config.configuration.port) - except Exception: - log.exception("Fehler im Modul " + self.device_config.name) - - def add_component(self, - component_config: Union[Dict, GoodWeBatSetup, GoodWeCounterSetup, GoodWeInverterSetup]) -> None: - if isinstance(component_config, Dict): - component_type = component_config["type"] - else: - component_type = component_config.type - component_config = dataclass_from_dict(COMPONENT_TYPE_TO_MODULE[ - component_type].component_descriptor.configuration_factory, - component_config) - if component_type in self.COMPONENT_TYPE_TO_CLASS: - self.components["component" + str(component_config.id)] = (self.COMPONENT_TYPE_TO_CLASS[component_type]( - self.device_config.configuration.modbus_id, component_config, self.client)) - else: - raise Exception( - "illegal component type " + component_type + ". Allowed values: " + - ','.join(self.COMPONENT_TYPE_TO_CLASS.keys()) - ) - - def update(self) -> None: - log.debug("Start device reading " + str(self.components)) - if self.components: - for component in self.components: - # Auch wenn bei einer Komponente ein Fehler auftritt, sollen alle anderen noch ausgelesen werden. - with SingleComponentUpdateContext(self.components[component].fault_state): - self.components[component].update() - else: - log.warning( - self.device_config.name + - ": Es konnten keine Werte gelesen werden, da noch keine Komponenten konfiguriert wurden." - ) - - -COMPONENT_TYPE_TO_MODULE = { - "bat": bat, - "counter": counter, - "inverter": inverter -} - - -def read_legacy(component_type: str, ip_address: str, id: int, num: Optional[int]) -> None: - device_config = GoodWe(configuration=GoodWeConfiguration(ip_address=ip_address, modbus_id=id)) - dev = Device(device_config) - if component_type in COMPONENT_TYPE_TO_MODULE: - component_config = COMPONENT_TYPE_TO_MODULE[component_type].component_descriptor.configuration_factory() - else: - raise Exception( - "illegal component type " + component_type + ". Allowed values: " + - ','.join(COMPONENT_TYPE_TO_MODULE.keys()) - ) - component_config.id = num - dev.add_component(component_config) - - log.debug('GoodWe IP-Adresse: ' + ip_address) - log.debug('GoodWe ID: ' + str(id)) - - dev.update() - dev.client.close() - - -def main(argv: List[str]): - run_using_positional_cli_args(read_legacy, argv) +def create_device(device_config: GoodWe): + def create_bat_component(component_config: GoodWeBatSetup): + return bat.GoodWeBat(device_config.configuration.modbus_id, + device_config.configuration.version, device_config.configuration.firmware, + component_config, client) + + def create_counter_component(component_config: GoodWeCounterSetup): + return counter.GoodWeCounter(device_config.id, device_config.configuration.modbus_id, + device_config.configuration.version, device_config.configuration.firmware, + component_config, client) + + def create_inverter_component(component_config: GoodWeInverterSetup): + return inverter.GoodWeInverter(device_config.configuration.modbus_id, + device_config.configuration.version, device_config.configuration.firmware, + component_config, client) + + def update_components(components: Iterable[good_we_component_classes]): + with client: + for component in components: + with SingleComponentUpdateContext(component.fault_state): + component.update() + + try: + client = modbus.ModbusTcpClient_(device_config.configuration.ip_address, device_config.configuration.port) + except Exception: + log.exception("Fehler in create_device") + return ConfigurableDevice( + device_config=device_config, + component_factory=ComponentFactoryByType( + bat=create_bat_component, + counter=create_counter_component, + inverter=create_inverter_component, + ), + component_updater=MultiComponentUpdater(update_components) + ) device_descriptor = DeviceDescriptor(configuration_factory=GoodWe) diff --git a/packages/modules/devices/good_we/inverter.py b/packages/modules/devices/good_we/inverter.py index 9b01b97629..c2de34fc1a 100644 --- a/packages/modules/devices/good_we/inverter.py +++ b/packages/modules/devices/good_we/inverter.py @@ -9,14 +9,19 @@ from modules.common.modbus import ModbusDataType from modules.common.store import get_inverter_value_store from modules.devices.good_we.config import GoodWeInverterSetup +from modules.devices.good_we.version import GoodWeVersion class GoodWeInverter: def __init__(self, modbus_id: int, + version: GoodWeVersion, + firmware: int, component_config: Union[Dict, GoodWeInverterSetup], tcp_client: modbus.ModbusTcpClient_) -> None: self.__modbus_id = modbus_id + self.version = version + self.firmware = firmware self.component_config = dataclass_from_dict(GoodWeInverterSetup, component_config) self.__tcp_client = tcp_client self.store = get_inverter_value_store(self.component_config.id) @@ -24,8 +29,9 @@ def __init__(self, def update(self) -> None: with self.__tcp_client: - power = sum([self.__tcp_client.read_holding_registers(reg, ModbusDataType.UINT_32, - unit=self.__modbus_id) for reg in [35105, 35109, 35113, 35117]]) * -1 + power = sum([self.__tcp_client.read_holding_registers( + reg, ModbusDataType.INT_32, unit=self.__modbus_id) + for reg in [35105, 35109, 35113, 35117]]) * -1 exported = self.__tcp_client.read_holding_registers( 35191, ModbusDataType.UINT_32, unit=self.__modbus_id) * 100 diff --git a/packages/modules/devices/good_we/version.py b/packages/modules/devices/good_we/version.py new file mode 100644 index 0000000000..35ce8df2dd --- /dev/null +++ b/packages/modules/devices/good_we/version.py @@ -0,0 +1,6 @@ +from enum import Enum + + +class GoodWeVersion(Enum): + V_1_7 = "v_1_7" + V_1_1 = "v_1_1" diff --git a/packages/modules/devices/huawei/config.py b/packages/modules/devices/huawei/config.py index 4c197e24a7..335aefe933 100644 --- a/packages/modules/devices/huawei/config.py +++ b/packages/modules/devices/huawei/config.py @@ -12,7 +12,7 @@ def __init__(self, modbus_id: int = 1, ip_address: Optional[str] = None, port: i class Huawei: def __init__(self, - name: str = "Huawei", + name: str = "Huawei Hybrid Wechselrichter", type: str = "huawei", id: int = 0, configuration: HuaweiConfiguration = None) -> None: diff --git a/packages/modules/devices/huawei_smartlogger/config.py b/packages/modules/devices/huawei_smartlogger/config.py index a0d5c08baa..e295ae5a9f 100644 --- a/packages/modules/devices/huawei_smartlogger/config.py +++ b/packages/modules/devices/huawei_smartlogger/config.py @@ -13,7 +13,7 @@ def __init__(self, ip_address: Optional[str] = None, port: int = 502): @auto_str class Huawei_Smartlogger: def __init__(self, - name: str = "Huawei_Smartlogger", + name: str = "Huawei Smartlogger", type: str = "huawei_smartlogger", id: int = 0, configuration: Huawei_SmartloggerConfiguration = None) -> None: diff --git a/packages/modules/devices/huawei_smartlogger/device.py b/packages/modules/devices/huawei_smartlogger/device.py index 9eb3b157d6..34f8aa058e 100644 --- a/packages/modules/devices/huawei_smartlogger/device.py +++ b/packages/modules/devices/huawei_smartlogger/device.py @@ -1,11 +1,11 @@ #!/usr/bin/env python3 import logging -from typing import Optional, Union, List, Dict -from dataclass_utils import dataclass_from_dict -from helpermodules.cli import run_using_positional_cli_args -from modules.common.abstract_device import AbstractDevice, DeviceDescriptor +from typing import Iterable, Union + +from modules.common.abstract_device import DeviceDescriptor from modules.common.component_context import SingleComponentUpdateContext from modules.common import modbus +from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, MultiComponentUpdater from modules.devices.huawei_smartlogger import counter from modules.devices.huawei_smartlogger import inverter from modules.devices.huawei_smartlogger import bat @@ -21,91 +21,35 @@ inverter.Huawei_SmartloggerInverter] -class Device(AbstractDevice): - COMPONENT_TYPE_TO_CLASS = { - "bat": bat.Huawei_SmartloggerBat, - "counter": counter.Huawei_SmartloggerCounter, - "inverter": inverter.Huawei_SmartloggerInverter - } - - def __init__(self, device_config: Union[Dict, Huawei_Smartlogger]) -> None: - self.components = {} # type: Dict[str, huawei_smartlogger_component_classes] - try: - self.device_config = dataclass_from_dict(Huawei_Smartlogger, device_config) - ip_address = self.device_config.configuration.ip_address - self.port = self.device_config.configuration.port - self.client = modbus.ModbusTcpClient_(ip_address, self.device_config.configuration.port) - self.client.connect() - except Exception: - log.exception("Fehler im Modul "+self.device_config.name) - - def add_component(self, component_config: Union[Dict, - Huawei_SmartloggerBatSetup, - Huawei_SmartloggerCounterSetup, - Huawei_SmartloggerInverterSetup]) -> None: - if isinstance(component_config, Dict): - component_type = component_config["type"] - else: - component_type = component_config.type - component_config = dataclass_from_dict(COMPONENT_TYPE_TO_MODULE[ - component_type].component_descriptor.configuration_factory, component_config) - if component_type in self.COMPONENT_TYPE_TO_CLASS: - self.components["component"+str(component_config.id)] = ( - self.COMPONENT_TYPE_TO_CLASS[component_type](self.device_config.id, component_config, self.client)) - else: - raise Exception( - "illegal component type " + component_type + ". Allowed values: " + - ','.join(self.COMPONENT_TYPE_TO_CLASS.keys()) - ) - - def update(self) -> None: - log.debug("Start device reading " + str(self.components)) - if self.components: - for component in self.components: - # Auch wenn bei einer Komponente ein Fehler auftritt, sollen alle anderen noch ausgelesen werden. - with SingleComponentUpdateContext(self.components[component].fault_state): - self.components[component].update() - else: - log.warning( - self.device_config.name + - ": Es konnten keine Werte gelesen werden, da noch keine Komponenten konfiguriert wurden." - ) - - -COMPONENT_TYPE_TO_MODULE = { - "bat": bat, - "counter": counter, - "inverter": inverter -} - - -def read_legacy(component_type: str, - ip_address: str, - modbus_id: Optional[int] = 1, - num: Optional[int] = None) -> None: - - device_config = Huawei_Smartlogger() - device_config.configuration.ip_address = ip_address - dev = Device(device_config) - - if component_type in COMPONENT_TYPE_TO_MODULE: - component_config = COMPONENT_TYPE_TO_MODULE[component_type].component_descriptor.configuration_factory() - else: - raise Exception( - "illegal component type " + component_type + ". Allowed values: " + - ','.join(COMPONENT_TYPE_TO_MODULE.keys()) - ) - component_config.id = num - component_config.configuration.modbus_id = modbus_id - dev.add_component(component_config) - - log.debug('Huawei Smartlogger IP-Adresse: ' + ip_address) - log.debug('Huawei Device Modbus-ID: ' + str(modbus_id)) - dev.update() - - -def main(argv: List[str]): - run_using_positional_cli_args(read_legacy, argv) +def create_device(device_config: Huawei_Smartlogger): + def create_bat_component(component_config: Huawei_SmartloggerBatSetup): + return bat.Huawei_SmartloggerBat(device_config.id, component_config, client) + + def create_counter_component(component_config: Huawei_SmartloggerCounterSetup): + return counter.Huawei_SmartloggerCounter(device_config.id, component_config, client) + + def create_inverter_component(component_config: Huawei_SmartloggerInverterSetup): + return inverter.Huawei_SmartloggerInverter(device_config.id, component_config, client) + + def update_components(components: Iterable[huawei_smartlogger_component_classes]): + with client: + for component in components: + with SingleComponentUpdateContext(component.fault_state): + component.update() + + try: + client = modbus.ModbusTcpClient_(device_config.configuration.ip_address, device_config.configuration.port) + except Exception: + log.exception("Fehler in create_device") + return ConfigurableDevice( + device_config=device_config, + component_factory=ComponentFactoryByType( + bat=create_bat_component, + counter=create_counter_component, + inverter=create_inverter_component, + ), + component_updater=MultiComponentUpdater(update_components) + ) device_descriptor = DeviceDescriptor(configuration_factory=Huawei_Smartlogger) diff --git a/packages/modules/devices/janitza/device.py b/packages/modules/devices/janitza/device.py index 59e0e69fc2..e3aef00be8 100644 --- a/packages/modules/devices/janitza/device.py +++ b/packages/modules/devices/janitza/device.py @@ -1,11 +1,10 @@ #!/usr/bin/env python3 import logging -from typing import Dict, Optional, List, Union +from typing import Iterable -from dataclass_utils import dataclass_from_dict -from helpermodules.cli import run_using_positional_cli_args +from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, MultiComponentUpdater from modules.common import modbus -from modules.common.abstract_device import AbstractDevice, DeviceDescriptor +from modules.common.abstract_device import DeviceDescriptor from modules.common.component_context import SingleComponentUpdateContext from modules.devices.janitza import counter from modules.devices.janitza.config import Janitza, JanitzaCounterSetup @@ -13,77 +12,28 @@ log = logging.getLogger(__name__) -class Device(AbstractDevice): - COMPONENT_TYPE_TO_CLASS = { - "counter": counter.JanitzaCounter - } - - def __init__(self, device_config: Union[Dict, Janitza]) -> None: - self.components = {} # type: Dict[str, counter.JanitzaCounter] - try: - self.device_config = dataclass_from_dict(Janitza, device_config) - self.client = modbus.ModbusTcpClient_( - self.device_config.configuration.ip_address, self.device_config.configuration.port) - except Exception: - log.exception("Fehler im Modul "+self.device_config.name) - - def add_component(self, component_config: Union[Dict, JanitzaCounterSetup]) -> None: - if isinstance(component_config, Dict): - component_type = component_config["type"] - else: - component_type = component_config.type - component_config = dataclass_from_dict(COMPONENT_TYPE_TO_MODULE[ - component_type].component_descriptor.configuration_factory, component_config) - if component_type in self.COMPONENT_TYPE_TO_CLASS: - self.components["component"+str(component_config.id)] = (self.COMPONENT_TYPE_TO_CLASS[component_type]( - self.device_config.id, component_config, self.client, - self.device_config.configuration.modbus_id)) - else: - raise Exception( - "illegal component type " + component_type + ". Allowed values: " + - ','.join(self.COMPONENT_TYPE_TO_CLASS.keys()) - ) - - def update(self) -> None: - log.debug("Start device reading " + str(self.components)) - if self.components: - for component in self.components: - # Auch wenn bei einer Komponente ein Fehler auftritt, sollen alle anderen noch ausgelesen werden. - with SingleComponentUpdateContext(self.components[component].fault_state): - self.components[component].update() - else: - log.warning( - self.device_config.name + - ": Es konnten keine Werte gelesen werden, da noch keine Komponenten konfiguriert wurden." - ) - - -COMPONENT_TYPE_TO_MODULE = { - "counter": counter -} - - -def read_legacy(component_type: str, ip_address: str, num: Optional[int] = None) -> None: - device_config = Janitza() - device_config.configuration.ip_address = ip_address - dev = Device(device_config) - if component_type in COMPONENT_TYPE_TO_MODULE: - component_config = COMPONENT_TYPE_TO_MODULE[component_type].component_descriptor.configuration_factory() - else: - raise Exception( - "illegal component type " + component_type + ". Allowed values: " + - ','.join(COMPONENT_TYPE_TO_MODULE.keys()) - ) - component_config.id = num - dev.add_component(component_config) - - log.debug('Janitza IP-Adresse: ' + ip_address) - - dev.update() - - -def main(argv: List[str]): - run_using_positional_cli_args(read_legacy, argv) +def create_device(device_config: Janitza): + def create_counter_component(component_config: JanitzaCounterSetup): + return counter.JanitzaCounter(device_config.id, component_config, client, + device_config.configuration.modbus_id) + + def update_components(components: Iterable[counter.JanitzaCounter]): + with client: + for component in components: + with SingleComponentUpdateContext(component.fault_state): + component.update() + + try: + client = modbus.ModbusTcpClient_(device_config.configuration.ip_address, device_config.configuration.port) + except Exception: + log.exception("Fehler in create_device") + return ConfigurableDevice( + device_config=device_config, + component_factory=ComponentFactoryByType( + counter=create_counter_component, + ), + component_updater=MultiComponentUpdater(update_components) + ) device_descriptor = DeviceDescriptor(configuration_factory=Janitza) diff --git a/packages/modules/devices/kostal_piko/device.py b/packages/modules/devices/kostal_piko/device.py index 20f18f1d10..567d6f26cd 100644 --- a/packages/modules/devices/kostal_piko/device.py +++ b/packages/modules/devices/kostal_piko/device.py @@ -1,136 +1,30 @@ #!/usr/bin/env python3 import logging -from typing import Dict, Union, Optional, List -from dataclass_utils import dataclass_from_dict -from helpermodules.cli import run_using_positional_cli_args -from modules.devices.byd.config import BYD, BYDBatSetup, BYDConfiguration -from modules.devices.byd.device import Device as BYDDevice -from modules.common.abstract_device import AbstractDevice, DeviceDescriptor -from modules.common.component_context import SingleComponentUpdateContext -from modules.common.component_state import CounterState -from modules.common.simcount import sim_count -from modules.common.store import get_counter_value_store +from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, IndependentComponentUpdater +from modules.common.abstract_device import DeviceDescriptor from modules.devices.kostal_piko import counter from modules.devices.kostal_piko import inverter -from modules.devices.kostal_piko.config import (KostalPiko, - KostalPikoConfiguration, - KostalPikoCounterSetup, KostalPikoInverterConfiguration, - KostalPikoInverterSetup) +from modules.devices.kostal_piko.config import KostalPiko, KostalPikoCounterSetup, KostalPikoInverterSetup log = logging.getLogger(__name__) -kostal_piko_component_classes = Union[counter.KostalPikoCounter, inverter.KostalPikoInverter] +def create_device(device_config: KostalPiko): + def create_counter_component(component_config: KostalPikoCounterSetup): + return counter.KostalPikoCounter(device_config.id, component_config, device_config.configuration.ip_address) + def create_inverter_component(component_config: KostalPikoInverterSetup): + return inverter.KostalPikoInverter(device_config.id, component_config, device_config.configuration.ip_address) -class Device(AbstractDevice): - COMPONENT_TYPE_TO_CLASS = { - "counter": counter.KostalPikoCounter, - "inverter": inverter.KostalPikoInverter - } - - def __init__(self, device_config: Union[Dict, KostalPiko]) -> None: - self.components = {} # type: Dict[str, kostal_piko_component_classes] - try: - self.device_config = dataclass_from_dict(KostalPiko, device_config) - except Exception: - log.exception("Fehler im Modul "+self.device_config.name) - - def add_component(self, component_config: Union[Dict, KostalPikoCounterSetup, KostalPikoInverterSetup]) -> None: - if isinstance(component_config, Dict): - component_type = component_config["type"] - else: - component_type = component_config.type - component_config = dataclass_from_dict(COMPONENT_TYPE_TO_MODULE[ - component_type].component_descriptor.configuration_factory, component_config) - if component_type in self.COMPONENT_TYPE_TO_CLASS: - self.components["component"+str(component_config.id)] = (self.COMPONENT_TYPE_TO_CLASS[component_type]( - self.device_config.id, component_config, self.device_config.configuration.ip_address)) - else: - raise Exception( - "illegal component type " + component_type + ". Allowed values: " + - ','.join(self.COMPONENT_TYPE_TO_CLASS.keys()) - ) - - def update(self) -> None: - log.debug("Start device reading " + str(self.components)) - if self.components: - for component in self.components: - # Auch wenn bei einer Komponente ein Fehler auftritt, sollen alle anderen noch ausgelesen werden. - with SingleComponentUpdateContext(self.components[component].fault_state): - self.components[component].update() - else: - log.warning( - self.device_config.name + - ": Es konnten keine Werte gelesen werden, da noch keine Komponenten konfiguriert wurden." - ) - - -COMPONENT_TYPE_TO_MODULE = { - "counter": counter, - "inverter": inverter -} - - -def read_legacy(component_type: str, - address: str, - bat_module: str, - bat_ip: str, - bat_username: str, - bat_password: str, - num: Optional[int] = None) -> None: - dev = Device(KostalPiko(configuration=KostalPikoConfiguration(ip_address=address))) - if component_type in COMPONENT_TYPE_TO_MODULE: - component_config = COMPONENT_TYPE_TO_MODULE[component_type].component_descriptor.configuration_factory() - else: - raise Exception( - "illegal component type " + component_type + ". Allowed values: " + - ','.join(COMPONENT_TYPE_TO_MODULE.keys()) - ) - component_config.id = num - if isinstance(component_config, KostalPikoInverterSetup) and bat_module != "none": - component_config.configuration.bat_configured = True - dev.add_component(component_config) - - log.debug('KostalPiko IP-Adresse: ' + address) - log.debug('KostalPiko Speicher: ' + bat_module) - - if isinstance(component_config, KostalPikoInverterSetup): - dev.update() - elif isinstance(component_config, KostalPikoCounterSetup): - with SingleComponentUpdateContext(dev.components["componentNone"].component_info): - home_consumption, powers = dev.components["componentNone"].get_values() - if bat_module == "speicher_bydhv": - bat_power = _get_byd_bat_power(bat_ip, bat_username, bat_password, 1) - home_consumption += bat_power - - dev.add_component(KostalPikoInverterSetup( - id=1, configuration=KostalPikoInverterConfiguration(bat_configured=True))) - inverter_power, _ = dev.components["component"+str(1)].update() - - power = home_consumption + inverter_power - imported, exported = sim_count(power, prefix="bezug") - counter_state = CounterState( - imported=imported, - exported=exported, - power=power, - powers=powers - ) - get_counter_value_store(None).set(counter_state) - - -def _get_byd_bat_power(bat_ip: str, bat_username: str, bat_password: str, num: int) -> float: - bat_dev = BYDDevice(BYD(configuration=BYDConfiguration(user=bat_username, - password=bat_password, - ip_address=bat_ip))) - bat_dev.add_component(BYDBatSetup(id=num)) - bat_power, _ = bat_dev.components["component"+str(num)].get_values() - return bat_power - - -def main(argv: List[str]): - run_using_positional_cli_args(read_legacy, argv) + return ConfigurableDevice( + device_config=device_config, + component_factory=ComponentFactoryByType( + counter=create_counter_component, + inverter=create_inverter_component, + ), + component_updater=IndependentComponentUpdater(lambda component: component.update()) + ) device_descriptor = DeviceDescriptor(configuration_factory=KostalPiko) diff --git a/packages/modules/devices/lg/device.py b/packages/modules/devices/lg/device.py index 0a55eac6ab..93a01be397 100644 --- a/packages/modules/devices/lg/device.py +++ b/packages/modules/devices/lg/device.py @@ -1,146 +1,75 @@ #!/usr/bin/env python3 import json import logging -import os -from typing import Dict, Union, Optional, List +from typing import Dict, Iterable, Union from requests import HTTPError, Session -from dataclass_utils import dataclass_from_dict -from helpermodules.cli import run_using_positional_cli_args from modules.common import req -from modules.common.abstract_device import AbstractDevice, DeviceDescriptor -from modules.common.component_context import MultiComponentUpdateContext -from modules.devices.lg.config import LG, LgBatSetup, LgConfiguration, LgCounterSetup, LgInverterSetup -from modules.devices.lg import bat, counter, inverter +from modules.common.abstract_device import DeviceDescriptor +from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, MultiComponentUpdater +from modules.devices.lg.bat import LgBat +from modules.devices.lg.config import LG, LgBatSetup, LgCounterSetup, LgInverterSetup +from modules.devices.lg.counter import LgCounter +from modules.devices.lg.inverter import LgInverter log = logging.getLogger(__name__) -lg_component_classes = Union[bat.LgBat, counter.LgCounter, inverter.LgInverter] - - -class Device(AbstractDevice): - """Beispiel JSON-Objekte liegen im Ordner lgessv1/JSON-Beispiele.txt - lg_ess_url: IP/URL des LG ESS V1.0 - lg_ess_pass: Passwort, um sich in den LG ESS V1.0 einzuloggen. - Das Passwort ist standardmäßig die Registrierungsnummer, - die sich auf dem PCS (dem Hybridwechselrichter und - Batteriemanagementsystem) befindet (Aufkleber!). Alter- - nativ findet man die Registrierungsnummer in der App unter - dem Menüpunkt "Systeminformationen". - Mit der Registrierungsnummer kann man sich dann in der - Rolle "installer" einloggen.""" - COMPONENT_TYPE_TO_CLASS = { - "bat": bat.LgBat, - "counter": counter.LgCounter, - "inverter": inverter.LgInverter - } - - def __init__(self, device_config: Union[Dict, LG]) -> None: - self.components = {} # type: Dict[str, lg_component_classes] - self.session_key = " " - try: - self.device_config = dataclass_from_dict(LG, device_config) - except Exception: - log.exception("Fehler im Modul "+self.device_config.name) - - def add_component(self, component_config: Union[Dict, LgBatSetup, LgCounterSetup, LgInverterSetup]) -> None: - if isinstance(component_config, Dict): - component_type = component_config["type"] - else: - component_type = component_config.type - component_config = dataclass_from_dict(COMPONENT_TYPE_TO_MODULE[ - component_type].component_descriptor.configuration_factory, component_config) - if component_type in self.COMPONENT_TYPE_TO_CLASS: - self.components["component"+str(component_config.id)] = (self.COMPONENT_TYPE_TO_CLASS[component_type]( - self.device_config.id, - component_config)) - else: - raise Exception( - "illegal component type " + component_type + ". Allowed values: " + - ','.join(self.COMPONENT_TYPE_TO_CLASS.keys()) - ) - - def update(self) -> None: - log.debug("Start device reading " + str(self.components)) - if self.components: - with MultiComponentUpdateContext(self.components): - session = req.get_http_session() - try: - response = self._request_data(session) - except HTTPError: - self._update_session_key(session) - response = self._request_data(session) - - for component in self.components: - self.components[component].update(response) - else: - log.warning( - self.device_config.name + - ": Es konnten keine Werte gelesen werden, da noch keine Komponenten konfiguriert wurden." - ) - - def _update_session_key(self, session: Session): - try: - headers = {'Content-Type': 'application/json', } - data = json.dumps({"password": self.device_config.configuration.password}) - response = session.put("https://"+self.device_config.configuration.ip_address+'/v1/login', headers=headers, - data=data, verify=False, timeout=5).json() - self.session_key = response["auth_key"] - except (HTTPError, KeyError) as e: - e.args += ("login failed! check password!", ) - raise e - - def _request_data(self, session: Session) -> Dict: +def _update_session_key(session: Session, ip_address: str, password: str) -> str: + try: headers = {'Content-Type': 'application/json', } - data = json.dumps({"auth_key": self.session_key}) - return session.post("https://"+self.device_config.configuration.ip_address + "/v1/user/essinfo/home", - headers=headers, - data=data, - verify=False, - timeout=5).json() - - -COMPONENT_TYPE_TO_MODULE = { - "bat": bat, - "counter": counter, - "inverter": inverter -} - - -def read_legacy(component_type: str, ip: str, password: str, num: Optional[int] = None) -> None: - dev = Device(LG(configuration=LgConfiguration(ip_address=ip, password=password))) - - if os.path.isfile("/var/www/html/openWB/ramdisk/ess_session_key"): - with open("/var/www/html/openWB/ramdisk/ess_session_key", "r") as f: - # erste Zeile ohne Zeilenumbruch lesen - old_session_key = f.readline().strip() - dev.session_key = old_session_key - else: - old_session_key = dev.session_key - - if component_type in COMPONENT_TYPE_TO_MODULE: - component_config = COMPONENT_TYPE_TO_MODULE[component_type].component_descriptor.configuration_factory() - else: - raise Exception( - "illegal component type " + component_type + ". Allowed values: " + - ','.join(COMPONENT_TYPE_TO_MODULE.keys()) - ) - if component_type == "bat" or component_type == "counter": - num = None - component_config.id = num - dev.add_component(component_config) - log.debug('LG ESS V1.0 IP: ' + ip) - log.debug('LG ESS V1.0 password: ' + password) - dev.update() - - if dev.session_key != old_session_key: - with open("/var/www/html/openWB/ramdisk/ess_session_key", "w") as f: - f.write(str(dev.session_key)) - - -def main(argv: List[str]): - run_using_positional_cli_args(read_legacy, argv) + data = json.dumps({"password": password}) + response = session.put(f"https://{ip_address}/v1/login", headers=headers, + data=data, verify=False, timeout=5).json() + return response["auth_key"] + except (HTTPError, KeyError) as e: + e.args += ("login failed! check password!", ) + raise e + + +def _request_data(session: Session, session_key: str, ip_address: str) -> Dict: + headers = {'Content-Type': 'application/json', } + data = json.dumps({"auth_key": session_key}) + return session.post(f"https://{ip_address}/v1/user/essinfo/home", + headers=headers, + data=data, + verify=False, + timeout=5).json() + + +def create_device(device_config: LG): + def create_bat_component(component_config: LgBatSetup): + return LgBat(device_config.id, component_config) + + def create_counter_component(component_config: LgCounterSetup): + return LgCounter(device_config.id, component_config) + + def create_inverter_component(component_config: LgInverterSetup): + return LgInverter(device_config.id, component_config) + + def update_components(components: Iterable[Union[LgBat, LgCounter, LgInverter]]): + nonlocal session_key + session = req.get_http_session() + try: + response = _request_data(session, session_key, device_config.configuration.ip_address) + except HTTPError: + session_key = _update_session_key( + session, device_config.configuration.ip_address, device_config.configuration.password) + response = _request_data(session, session_key, device_config.configuration.ip_address) + + for component in components: + component.update(response) + + session_key = " " + return ConfigurableDevice( + device_config=device_config, + component_factory=ComponentFactoryByType( + bat=create_bat_component, + counter=create_counter_component, + inverter=create_inverter_component, + ), + component_updater=MultiComponentUpdater(update_components) + ) device_descriptor = DeviceDescriptor(configuration_factory=LG) diff --git a/packages/modules/devices/lg/lg_test.py b/packages/modules/devices/lg/lg_test.py index 5bea372f85..58808a0f8a 100644 --- a/packages/modules/devices/lg/lg_test.py +++ b/packages/modules/devices/lg/lg_test.py @@ -3,7 +3,11 @@ from unittest.mock import Mock from modules.common.component_state import BatState, CounterState, InverterState -from modules.devices.lg import bat, counter, device, inverter +from modules.common.configurable_device import ConfigurableDevice +from modules.devices.lg import bat, counter, inverter +from modules.devices.lg import device +from modules.devices.lg.device import create_device + from modules.devices.lg.config import LG, LgConfiguration from test_utils.mock_ramdisk import MockRamdisk @@ -14,9 +18,10 @@ def mock_ramdisk(monkeypatch): @pytest.fixture -def dev() -> device.Device: - dev = device.Device(LG(configuration=LgConfiguration(ip_address=API_URL, password="some password"))) - dev.session_key = "67567d76-0c83-11ea-8a59-d84fb802005a" +def dev(monkeypatch) -> ConfigurableDevice: + dev = create_device(LG(configuration=LgConfiguration(ip_address=API_URL, password="some password"))) + mock_session_key = Mock(return_value="67567d76-0c83-11ea-8a59-d84fb802005a") + monkeypatch.setattr(device, "_update_session_key", mock_session_key) return dev @@ -38,7 +43,7 @@ def assert_inverter_state_correct(state: InverterState): assert state.exported == 200 -def test_valid_login(monkeypatch, dev: device.Device): +def test_valid_login(monkeypatch, dev: ConfigurableDevice): # setup mock_bat_value_store = Mock() monkeypatch.setattr(bat, "get_bat_value_store", Mock(return_value=mock_bat_value_store)) @@ -46,7 +51,7 @@ def test_valid_login(monkeypatch, dev: device.Device): monkeypatch.setattr(counter, "get_counter_value_store", Mock(return_value=mock_counter_value_store)) mock_inverter_value_store = Mock() monkeypatch.setattr(inverter, "get_inverter_value_store", Mock(return_value=mock_inverter_value_store)) - monkeypatch.setattr(device.Device, "_request_data", Mock(return_value=sample_auth_key_valid)) + monkeypatch.setattr(device, "_request_data", Mock(return_value=sample_auth_key_valid)) component_config = bat.component_descriptor.configuration_factory() component_config.id = None dev.add_component(component_config) @@ -66,7 +71,7 @@ def test_valid_login(monkeypatch, dev: device.Device): assert_inverter_state_correct(mock_inverter_value_store.set.call_args[0][0]) -def test_update_session_key(monkeypatch, dev: device.Device): +def test_update_session_key(monkeypatch, dev: ConfigurableDevice): # setup mock_bat_value_store = Mock() monkeypatch.setattr(bat, "get_bat_value_store", Mock(return_value=mock_bat_value_store)) @@ -74,8 +79,7 @@ def test_update_session_key(monkeypatch, dev: device.Device): monkeypatch.setattr(counter, "get_counter_value_store", Mock(return_value=mock_counter_value_store)) mock_inverter_value_store = Mock() monkeypatch.setattr(inverter, "get_inverter_value_store", Mock(return_value=mock_inverter_value_store)) - monkeypatch.setattr(device.Device, "_update_session_key", Mock()) - monkeypatch.setattr(device.Device, "_request_data", Mock( + monkeypatch.setattr(device, "_request_data", Mock( side_effect=[HTTPError, sample_auth_key_valid])) component_config = bat.component_descriptor.configuration_factory() component_config.id = None diff --git a/packages/modules/devices/mqtt/device.py b/packages/modules/devices/mqtt/device.py index dfbe0dc6fd..5bf377ed2b 100644 --- a/packages/modules/devices/mqtt/device.py +++ b/packages/modules/devices/mqtt/device.py @@ -1,55 +1,37 @@ #!/usr/bin/env python3 -from typing import Dict, Union +from typing import Iterable, Union import logging -from dataclass_utils import dataclass_from_dict -from modules.common.abstract_device import AbstractDevice, DeviceDescriptor -from modules.common.component_context import MultiComponentUpdateContext +from modules.common.abstract_device import DeviceDescriptor +from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, MultiComponentUpdater from modules.devices.mqtt import bat, counter, inverter from modules.devices.mqtt.config import Mqtt, MqttBatSetup, MqttCounterSetup, MqttInverterSetup log = logging.getLogger(__name__) -class Device(AbstractDevice): - COMPONENT_TYPE_TO_CLASS = { - "bat": bat.MqttBat, - "counter": counter.MqttCounter, - "inverter": inverter.MqttInverter - } - COMPONENT_TYPE_TO_MODULE = { - "bat": bat, - "counter": counter, - "inverter": inverter - } - - def __init__(self, device_config: Union[Dict, Mqtt]) -> None: - self.components = {} - try: - self.device_config = dataclass_from_dict(Mqtt, device_config) - except Exception: - log.exception("Fehler im Modul " + self.device_config.name) - - def add_component(self, component_config: Union[Dict, MqttBatSetup, MqttCounterSetup, MqttInverterSetup]) -> None: - if isinstance(component_config, Dict): - component_type = component_config["type"] - else: - component_type = component_config.type - component_config = dataclass_from_dict(self.COMPONENT_TYPE_TO_MODULE[ - component_type].component_descriptor.configuration_factory, component_config) - if component_type in self.COMPONENT_TYPE_TO_CLASS: - self.components["component"+str(component_config.id) - ] = (self.COMPONENT_TYPE_TO_CLASS[component_type](component_config)) - - def update(self) -> None: - if self.components: - with MultiComponentUpdateContext(self.components): - log.debug("MQTT-Module müssen nicht ausgelesen werden.") - else: - log.warning( - self.device_config.name + - ": Es konnten keine Werte gelesen werden, da noch keine Komponenten konfiguriert wurden." - ) +def create_device(device_config: Mqtt): + def create_bat_component(component_config: MqttBatSetup): + return bat.MqttBat(component_config) + + def create_counter_component(component_config: MqttCounterSetup): + return counter.MqttCounter(component_config) + + def create_inverter_component(component_config: MqttInverterSetup): + return inverter.MqttInverter(component_config) + + def update_components(components: Iterable[Union[bat.MqttBat, counter.MqttCounter, inverter.MqttInverter]]): + log.debug("MQTT-Module müssen nicht ausgelesen werden.") + + return ConfigurableDevice( + device_config=device_config, + component_factory=ComponentFactoryByType( + bat=create_bat_component, + counter=create_counter_component, + inverter=create_inverter_component, + ), + component_updater=MultiComponentUpdater(update_components) + ) device_descriptor = DeviceDescriptor(configuration_factory=Mqtt) diff --git a/packages/modules/devices/mtec/bat.py b/packages/modules/devices/mtec/bat.py new file mode 100644 index 0000000000..35caf4ccc7 --- /dev/null +++ b/packages/modules/devices/mtec/bat.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +import logging +from dataclass_utils import dataclass_from_dict +from modules.common.component_state import BatState +from modules.common.component_type import ComponentDescriptor +from modules.common.fault_state import ComponentInfo, FaultState +from modules.common.modbus import ModbusDataType, ModbusTcpClient_ +from modules.common.simcount import SimCounter +from modules.common.store import get_bat_value_store +from modules.devices.mtec.config import MTecBatSetup + +log = logging.getLogger(__name__) + + +class MTecBat: + def __init__(self, device_id: int, component_config: MTecBatSetup) -> None: + self.component_config = dataclass_from_dict(MTecBatSetup, component_config) + self.store = get_bat_value_store(self.component_config.id) + self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) + self.__device_id = device_id + self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="speicher") + + def update(self, client: ModbusTcpClient_, generation: int) -> None: + unit = self.component_config.configuration.modbus_id + generation = self.component_config.configuration.generation + + if generation == 2: + power = client.read_holding_registers(40258, ModbusDataType.INT_32, unit=unit) * -1 + # soc unit 0.01% + soc = client.read_holding_registers(43000, ModbusDataType.UINT_16, unit=unit) / 100 + else: + power = client.read_holding_registers(30258, ModbusDataType.INT_32, unit=unit) * -1 + soc = client.read_holding_registers(33000, ModbusDataType.UINT_16, unit=unit) / 100 + imported, exported = self.sim_counter.sim_count(power) + + bat_state = BatState( + power=power, + soc=soc, + imported=imported, + exported=exported + ) + self.store.set(bat_state) + + +component_descriptor = ComponentDescriptor(configuration_factory=MTecBatSetup) diff --git a/packages/modules/devices/mtec/config.py b/packages/modules/devices/mtec/config.py new file mode 100644 index 0000000000..d8c693d0a1 --- /dev/null +++ b/packages/modules/devices/mtec/config.py @@ -0,0 +1,73 @@ +from typing import Optional +from helpermodules.auto_str import auto_str + +from modules.common.component_setup import ComponentSetup + + +class MTecConfiguration: + def __init__(self, + ip_address: Optional[str] = None, + port: int = 502): + self.ip_address = ip_address + self.port = port + + +class MTec: + def __init__(self, + name: str = "M-Tec", + type: str = "mtec", + id: int = 0, + configuration: MTecConfiguration = None) -> None: + self.name = name + self.type = type + self.id = id + self.configuration = configuration or MTecConfiguration() + + +@auto_str +class MTecBatConfiguration: + def __init__(self, modbus_id: int = 247, generation: int = 2): + self.modbus_id = modbus_id + self.generation = generation + + +@auto_str +class MTecBatSetup(ComponentSetup[MTecBatConfiguration]): + def __init__(self, + name: str = "M-Tec Speicher", + type: str = "bat", + id: int = 0, + configuration: MTecBatConfiguration = None) -> None: + super().__init__(name, type, id, configuration or MTecBatConfiguration()) + + +@auto_str +class MTecCounterConfiguration: + def __init__(self, modbus_id: int = 247): + self.modbus_id = modbus_id + + +@auto_str +class MTecCounterSetup(ComponentSetup[MTecCounterConfiguration]): + def __init__(self, + name: str = "M-Tec Zähler", + type: str = "counter", + id: int = 0, + configuration: MTecCounterConfiguration = None) -> None: + super().__init__(name, type, id, configuration or MTecCounterConfiguration()) + + +@auto_str +class MTecInverterConfiguration: + def __init__(self, modbus_id: int = 247): + self.modbus_id = modbus_id + + +@auto_str +class MTecInverterSetup(ComponentSetup[MTecInverterConfiguration]): + def __init__(self, + name: str = "M-Tec Wechselrichter", + type: str = "inverter", + id: int = 0, + configuration: MTecInverterConfiguration = None) -> None: + super().__init__(name, type, id, configuration or MTecInverterConfiguration()) diff --git a/packages/modules/devices/mtec/counter.py b/packages/modules/devices/mtec/counter.py new file mode 100644 index 0000000000..c711d8ef82 --- /dev/null +++ b/packages/modules/devices/mtec/counter.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +from dataclass_utils import dataclass_from_dict +from modules.common.component_state import CounterState +from modules.common.component_type import ComponentDescriptor +from modules.common.fault_state import ComponentInfo, FaultState +from modules.common.modbus import ModbusDataType, ModbusTcpClient_ +from modules.common.simcount import SimCounter +from modules.common.store import get_counter_value_store +from modules.devices.mtec.config import MTecCounterSetup + + +class MTecCounter: + def __init__(self, device_id: int, component_config: MTecCounterSetup) -> None: + self.component_config = dataclass_from_dict(MTecCounterSetup, component_config) + self.store = get_counter_value_store(self.component_config.id) + self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) + self.__device_id = device_id + self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="bezug") + + def update(self, client: ModbusTcpClient_): + unit = self.component_config.configuration.modbus_id + + power = client.read_holding_registers(11000, ModbusDataType.INT_32, unit=unit) + imported, exported = self.sim_counter.sim_count(power) + + counter_state = CounterState( + imported=imported, + exported=exported, + power=power + ) + self.store.set(counter_state) + + +component_descriptor = ComponentDescriptor(configuration_factory=MTecCounterSetup) diff --git a/packages/modules/devices/mtec/device.py b/packages/modules/devices/mtec/device.py new file mode 100644 index 0000000000..3a68c9460a --- /dev/null +++ b/packages/modules/devices/mtec/device.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +import logging +from typing import Iterable, Union + +from modules.common.abstract_device import DeviceDescriptor +from modules.common.component_context import SingleComponentUpdateContext +from modules.common.configurable_device import ConfigurableDevice, ComponentFactoryByType, MultiComponentUpdater +from modules.common.modbus import ModbusTcpClient_ +from modules.devices.mtec.bat import MTecBat +from modules.devices.mtec.counter import MTecCounter +from modules.devices.mtec.inverter import MTecInverter +from modules.devices.mtec.config import MTec, MTecBatSetup, MTecCounterSetup, MTecInverterSetup + +log = logging.getLogger(__name__) + + +def create_device(device_config: MTec): + def create_bat_component(component_config: MTecBatSetup): + return MTecBat(device_config.id, component_config) + + def create_counter_component(component_config: MTecCounterSetup): + return MTecCounter(device_config.id, component_config) + + def create_inverter_component(component_config: MTecInverterSetup): + return MTecInverter(device_config.id, component_config) + + def update_components(components: Iterable[Union[MTecBat, MTecCounter, MTecInverter]]): + with client as c: + for component in components: + with SingleComponentUpdateContext(component.fault_state): + component.update(c) + + try: + client = ModbusTcpClient_(device_config.configuration.ip_address, device_config.configuration.port) + except Exception: + log.exception("Fehler in create_device") + return ConfigurableDevice( + device_config=device_config, + component_factory=ComponentFactoryByType( + bat=create_bat_component, + counter=create_counter_component, + inverter=create_inverter_component, + ), + component_updater=MultiComponentUpdater(update_components) + ) + + +device_descriptor = DeviceDescriptor(configuration_factory=MTec) diff --git a/packages/modules/devices/mtec/inverter.py b/packages/modules/devices/mtec/inverter.py new file mode 100644 index 0000000000..57fb56f136 --- /dev/null +++ b/packages/modules/devices/mtec/inverter.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +from typing import Dict, Union + +from dataclass_utils import dataclass_from_dict +from modules.common.component_state import InverterState +from modules.common.component_type import ComponentDescriptor +from modules.common.fault_state import ComponentInfo, FaultState +from modules.common.modbus import ModbusDataType, ModbusTcpClient_ +from modules.common.simcount import SimCounter +from modules.common.store import get_inverter_value_store +from modules.devices.mtec.config import MTecInverterSetup + + +class MTecInverter: + def __init__(self, device_id: int, component_config: Union[Dict, MTecInverterSetup]) -> None: + self.component_config = dataclass_from_dict(MTecInverterSetup, component_config) + self.store = get_inverter_value_store(self.component_config.id) + self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) + self.__device_id = device_id + self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="pv") + + def update(self, client: ModbusTcpClient_) -> None: + unit = self.component_config.configuration.modbus_id + + power = client.read_holding_registers(11028, ModbusDataType.UINT_32, unit=unit) * -1 + _, exported = self.sim_counter.sim_count(power) + + inverter_state = InverterState( + power=power, + exported=exported, + ) + self.store.set(inverter_state) + + +component_descriptor = ComponentDescriptor(configuration_factory=MTecInverterSetup) diff --git a/packages/modules/devices/opendtu/config.py b/packages/modules/devices/opendtu/config.py index 73602c42ae..84a9f82fad 100644 --- a/packages/modules/devices/opendtu/config.py +++ b/packages/modules/devices/opendtu/config.py @@ -13,7 +13,7 @@ def __init__(self, url: Optional[str] = None): @auto_str class OpenDTU(Json): def __init__(self, - name: str = "OpenDTU", + name: str = "Hoymiles über openDTU", type: str = "opendtu", id: int = 0, configuration: OpenDTUConfiguration = None) -> None: @@ -29,7 +29,7 @@ def __init__(self): @auto_str class OpenDTUInverterSetup(JsonInverterSetup): def __init__(self, - name: str = "Hoymiles Wechselrichter", + name: str = "Hoymiles Wechselrichter über openDTU", type: str = "inverter", id: int = 0, configuration: JsonInverterConfiguration = None) -> None: diff --git a/packages/modules/devices/openwb_evu_kit/counter.py b/packages/modules/devices/openwb_evu_kit/counter.py index 394989dc92..37414ad649 100644 --- a/packages/modules/devices/openwb_evu_kit/counter.py +++ b/packages/modules/devices/openwb_evu_kit/counter.py @@ -22,6 +22,8 @@ def __init__(self, id = 2 elif version == 2: id = 115 + elif version == 3: + id = 105 else: raise ValueError("Version " + str(version) + " unbekannt.") diff --git a/packages/modules/devices/openwb_flex/config.py b/packages/modules/devices/openwb_flex/config.py index 0294b1854e..b377b10c8b 100644 --- a/packages/modules/devices/openwb_flex/config.py +++ b/packages/modules/devices/openwb_flex/config.py @@ -62,7 +62,7 @@ def __init__(self, id: int = 115, type: str = "sdm630"): class ConsumptionCounterFlexSetup(ComponentSetup[ConsumptionCounterFlexConfiguration]): def __init__(self, - name: str = "openWB Verbrauchszähler flex", + name: str = "openWB Bezugszähler ohne Einspeisung flex", type: str = "consumption_counter", id: int = 0, configuration: ConsumptionCounterFlexConfiguration = None) -> None: diff --git a/packages/modules/devices/openwb_flex/counter.py b/packages/modules/devices/openwb_flex/counter.py index 021b822def..9ede1cd642 100644 --- a/packages/modules/devices/openwb_flex/counter.py +++ b/packages/modules/devices/openwb_flex/counter.py @@ -8,6 +8,7 @@ from modules.common.fault_state import ComponentInfo, FaultState from modules.common.lovato import Lovato from modules.common.mpm3pm import Mpm3pm +from modules.common.b23 import B23 from modules.common.simcount import SimCounter from modules.common.store import get_counter_value_store from modules.devices.openwb_flex.config import EvuKitFlexSetup @@ -39,13 +40,13 @@ def update(self): frequency = self.__client.get_frequency() power_factors = self.__client.get_power_factors() - if isinstance(self.__client, Mpm3pm): + if isinstance(self.__client, Mpm3pm or B23): imported = self.__client.get_imported() exported = self.__client.get_exported() else: currents = self.__client.get_currents() - if isinstance(self.__client, Mpm3pm): + if isinstance(self.__client, Mpm3pm or B23): currents = [powers[i] / voltages[i] for i in range(3)] else: if isinstance(self.__client, Lovato): diff --git a/packages/modules/devices/openwb_flex/versions.py b/packages/modules/devices/openwb_flex/versions.py index f5a62ce8b0..3e532b7657 100644 --- a/packages/modules/devices/openwb_flex/versions.py +++ b/packages/modules/devices/openwb_flex/versions.py @@ -6,13 +6,15 @@ def kit_counter_version_factory( - version: int) -> Type[Union[mpm3pm.Mpm3pm, lovato.Lovato, sdm.Sdm630]]: + version: int) -> Type[Union[mpm3pm.Mpm3pm, lovato.Lovato, sdm.Sdm630, b23.B23]]: if version == 0: return mpm3pm.Mpm3pm elif version == 1: return lovato.Lovato elif version == 2: return sdm.Sdm630 + elif version == 3: + return b23.B23 else: raise ValueError("Version "+str(version) + " unbekannt.") diff --git a/packages/modules/devices/powerdog/device.py b/packages/modules/devices/powerdog/device.py index a0a1935814..ee027fd814 100644 --- a/packages/modules/devices/powerdog/device.py +++ b/packages/modules/devices/powerdog/device.py @@ -1,112 +1,64 @@ #!/usr/bin/env python3 import logging -from typing import Dict, Union, Optional, List +from typing import Iterable, Union -from dataclass_utils import dataclass_from_dict -from helpermodules.cli import run_using_positional_cli_args from modules.common import modbus -from modules.common.abstract_device import AbstractDevice, DeviceDescriptor -from modules.common.component_context import MultiComponentUpdateContext, SingleComponentUpdateContext -from modules.devices.powerdog import counter -from modules.devices.powerdog import inverter -from modules.devices.powerdog.config import Powerdog, PowerdogConfiguration, PowerdogCounterSetup, PowerdogInverterSetup +from modules.common.abstract_device import DeviceDescriptor +from modules.common.component_context import SingleComponentUpdateContext +from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, MultiComponentUpdater +from modules.devices.powerdog.config import Powerdog, PowerdogCounterSetup, PowerdogInverterSetup +from modules.devices.powerdog.counter import PowerdogCounter +from modules.devices.powerdog.inverter import PowerdogInverter log = logging.getLogger(__name__) -class Device(AbstractDevice): - COMPONENT_TYPE_TO_CLASS = { - "counter": counter.PowerdogCounter, - "inverter": inverter.PowerdogInverter - } +def create_device(device_config: Powerdog): + def create_counter_component(component_config: PowerdogCounterSetup): + return PowerdogCounter(device_config.id, component_config, client, device_config.configuration.modbus_id) - def __init__(self, device_config: Union[Dict, Powerdog]) -> None: - self.components = {} # type: Dict[str, Union[counter.PowerdogCounter, inverter.PowerdogInverter]] - try: - self.device_config = dataclass_from_dict(Powerdog, device_config) - self.client = modbus.ModbusTcpClient_( - self.device_config.configuration.ip_address, self.device_config.configuration.port) - except Exception: - log.exception("Fehler im Modul "+self.device_config.name) + def create_inverter_component(component_config: PowerdogInverterSetup): + return PowerdogInverter(device_config.id, component_config, client, device_config.configuration.modbus_id) - def add_component(self, component_config: Union[Dict, PowerdogCounterSetup, PowerdogInverterSetup]) -> None: - if isinstance(component_config, Dict): - component_type = component_config["type"] - else: - component_type = component_config.type - component_config = dataclass_from_dict(COMPONENT_TYPE_TO_MODULE[ - component_type].component_descriptor.configuration_factory, component_config) - if component_type in self.COMPONENT_TYPE_TO_CLASS: - self.components["component"+str(component_config.id)] = ( - self.COMPONENT_TYPE_TO_CLASS[component_type]( - self.device_config.id, component_config, self.client, - self.device_config.configuration.modbus_id)) - else: - raise Exception( - "illegal component type " + component_type + ". Allowed values: " + - ','.join(self.COMPONENT_TYPE_TO_CLASS.keys()) - ) - - def update(self) -> None: - log.debug("Start device reading " + str(self.components)) - with MultiComponentUpdateContext(self.components): - if len(self.components) == 1: - for component in self.components: - if isinstance(self.components[component], inverter.PowerdogInverter): - with SingleComponentUpdateContext(self.components[component].fault_state): - self.components[component].update() + def update_components(components: Iterable[Union[PowerdogCounter, PowerdogInverter]]): + with client: + if len(components) == 1: + for component in components: + if isinstance(components[component], PowerdogInverter): + with SingleComponentUpdateContext(components[component].fault_state): + components[component].update() else: raise Exception( "Wenn ein EVU-Zähler konfiguriert wurde, muss immer auch ein WR konfiguriert sein.") - elif len(self.components) == 2: - for component in self.components: - if isinstance(self.components[component], inverter.PowerdogInverter): - inverter_power = self.components[component].update() + elif len(components) == 2: + for component in components: + if isinstance(components[component], PowerdogInverter): + inverter_power = components[component].update() break else: inverter_power = 0 - for component in self.components: - if isinstance(self.components[component], counter.PowerdogCounter): - self.components[component].update(inverter_power) + for component in components: + if isinstance(components[component], PowerdogCounter): + components[component].update(inverter_power) else: log.warning( - self.device_config.name + + device_config.name + ": Es konnten keine Werte gelesen werden, da noch keine oder zu viele Komponenten konfiguriert " + "wurden." ) - -COMPONENT_TYPE_TO_MODULE = { - "counter": counter, - "inverter": inverter -} - - -def read_legacy(component_type: str, ip_address: str, num: Optional[int] = None) -> None: - dev = Device(Powerdog(configuration=PowerdogConfiguration(ip_address=ip_address))) - if component_type in COMPONENT_TYPE_TO_MODULE: - component_config = COMPONENT_TYPE_TO_MODULE[component_type].component_descriptor.configuration_factory() - else: - raise Exception( - "illegal component type " + component_type + ". Allowed values: " + - ','.join(COMPONENT_TYPE_TO_MODULE.keys()) - ) - component_config.id = num - dev.add_component(component_config) - - # Wenn der EVU-Zähler ausgelesen werden soll, wird auch noch der Inverter benötigt. - if component_type in COMPONENT_TYPE_TO_MODULE and component_type == "counter": - inverter_config = PowerdogInverterSetup() - inverter_config.id = 1 - dev.add_component(inverter_config) - - log.debug('Powerdog IP-Adresse: ' + ip_address) - - dev.update() - - -def main(argv: List[str]): - run_using_positional_cli_args(read_legacy, argv) + try: + client = modbus.ModbusTcpClient_(device_config.configuration.ip_address, device_config.configuration.port) + except Exception: + log.exception("Fehler in create_device") + return ConfigurableDevice( + device_config=device_config, + component_factory=ComponentFactoryByType( + counter=create_counter_component, + inverter=create_inverter_component, + ), + component_updater=MultiComponentUpdater(update_components) + ) device_descriptor = DeviceDescriptor(configuration_factory=Powerdog) diff --git a/packages/modules/devices/qcells/device.py b/packages/modules/devices/qcells/device.py index 45efcf8f51..b2abe736c5 100644 --- a/packages/modules/devices/qcells/device.py +++ b/packages/modules/devices/qcells/device.py @@ -28,10 +28,15 @@ def create_inverter_component(component_config: QCellsInverterSetup): device_config.configuration.modbus_id) def update_components(components: Iterable[Union[QCellsBat, QCellsCounter, QCellsInverter]]): - with client as c: + if client.is_socket_open() is False: + client.connect() + try: for component in components: with SingleComponentUpdateContext(component.fault_state): - component.update(c) + component.update(client) + except Exception as e: + client.close() + raise e try: client = ModbusTcpClient_(device_config.configuration.ip_address, device_config.configuration.port) diff --git a/packages/modules/devices/saxpower/device.py b/packages/modules/devices/saxpower/device.py index 12e59c1734..727be7ed67 100644 --- a/packages/modules/devices/saxpower/device.py +++ b/packages/modules/devices/saxpower/device.py @@ -1,88 +1,38 @@ #!/usr/bin/env python3 import logging -from typing import Dict, List, Union +from typing import Iterable -from dataclass_utils import dataclass_from_dict -from helpermodules.cli import run_using_positional_cli_args from modules.common import modbus -from modules.common.abstract_device import AbstractDevice, DeviceDescriptor +from modules.common.abstract_device import DeviceDescriptor from modules.common.component_context import SingleComponentUpdateContext -from modules.devices.saxpower import bat +from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, MultiComponentUpdater +from modules.devices.saxpower.bat import SaxpowerBat from modules.devices.saxpower.config import Saxpower, SaxpowerBatSetup log = logging.getLogger(__name__) -class Device(AbstractDevice): - COMPONENT_TYPE_TO_CLASS = { - "bat": bat.SaxpowerBat - } - - def __init__(self, device_config: Union[Dict, Saxpower]) -> None: - self.components = {} # type: Dict[str, bat.SaxpowerBat] - try: - self.device_config = dataclass_from_dict(Saxpower, device_config) - self.client = modbus.ModbusTcpClient_( - self.device_config.configuration.ip_address, self.device_config.configuration.port) - except Exception: - log.exception("Fehler im Modul "+self.device_config.name) - - def add_component(self, component_config: Union[Dict, SaxpowerBatSetup]) -> None: - if isinstance(component_config, Dict): - component_type = component_config["type"] - else: - component_type = component_config.type - component_config = dataclass_from_dict(COMPONENT_TYPE_TO_MODULE[ - component_type].component_descriptor.configuration_factory, component_config) - if component_type in self.COMPONENT_TYPE_TO_CLASS: - self.components["component"+str(component_config.id)] = (self.COMPONENT_TYPE_TO_CLASS[component_type]( - self.device_config.id, component_config, self.client, self.device_config.configuration.modbus_id)) - else: - raise Exception( - "illegal component type " + component_type + ". Allowed values: " + - ','.join(self.COMPONENT_TYPE_TO_CLASS.keys()) - ) - - def update(self) -> None: - log.debug("Start device reading " + str(self.components)) - if self.components: - for component in self.components: - # Auch wenn bei einer Komponente ein Fehler auftritt, sollen alle anderen noch ausgelesen werden. - with SingleComponentUpdateContext(self.components[component].fault_state): - self.components[component].update() - else: - log.warning( - self.device_config.name + - ": Es konnten keine Werte gelesen werden, da noch keine Komponenten konfiguriert wurden." - ) - - -COMPONENT_TYPE_TO_MODULE = { - "bat": bat -} - - -def read_legacy(component_type: str, ip_address: str) -> None: - device_config = Saxpower() - device_config.configuration.ip_address = ip_address - dev = Device(device_config) - if component_type in COMPONENT_TYPE_TO_MODULE: - component_config = COMPONENT_TYPE_TO_MODULE[component_type].component_descriptor.configuration_factory() - else: - raise Exception( - "illegal component type " + component_type + ". Allowed values: " + - ','.join(COMPONENT_TYPE_TO_MODULE.keys()) - ) - component_config.id = None - dev.add_component(component_config) - - log.debug('Saxpower IP-Adresse: ' + ip_address) - - dev.update() - - -def main(argv: List[str]): - run_using_positional_cli_args(read_legacy, argv) +def create_device(device_config: Saxpower): + def create_bat_component(component_config: SaxpowerBatSetup): + return SaxpowerBat(device_config.id, component_config, client, device_config.configuration.modbus_id) + + def update_components(components: Iterable[SaxpowerBat]): + with client: + for component in components: + with SingleComponentUpdateContext(component.fault_state): + component.update() + + try: + client = modbus.ModbusTcpClient_(device_config.configuration.ip_address, device_config.configuration.port) + except Exception: + log.exception("Fehler in create_device") + return ConfigurableDevice( + device_config=device_config, + component_factory=ComponentFactoryByType( + bat=create_bat_component, + ), + component_updater=MultiComponentUpdater(update_components) + ) device_descriptor = DeviceDescriptor(configuration_factory=Saxpower) diff --git a/packages/modules/devices/shelly/bat.py b/packages/modules/devices/shelly/bat.py new file mode 100644 index 0000000000..5372b30cf5 --- /dev/null +++ b/packages/modules/devices/shelly/bat.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +import logging +from typing import Optional +from modules.common import req +from modules.common.component_state import BatState +from modules.common.component_type import ComponentDescriptor +from modules.common.fault_state import ComponentInfo, FaultState +from modules.common.store import get_bat_value_store +from modules.common.simcount._simcounter import SimCounter +from modules.devices.shelly.config import ShellyBatSetup + +log = logging.getLogger(__name__) + + +class ShellyBat: + + def __init__(self, + device_id: int, + component_config: ShellyBatSetup, + address: str, + generation: Optional[int]) -> None: + self.component_config = component_config + self.sim_counter = SimCounter(device_id, self.component_config.id, prefix="speicher") + self.store = get_bat_value_store(self.component_config.id) + self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) + self.address = address + self.generation = generation + + def total_power_from_shelly(self) -> int: + total = 0 + if self.generation == 1: + status_url = "http://" + self.address + "/status" + else: + status_url = "http://" + self.address + "/rpc/Shelly.GetStatus" + status = req.get_http_session().get(status_url, timeout=3).json() + try: + if self.generation == 1: + meters = status['emeters'] # shelly3EM + for meter in meters: + total = total + meter['power'] + else: + total = status['em:0']['total_act_power'] # shelly Pro3EM + except KeyError: + log.exception("unsupported shelly device?") + finally: + return int(total) + + def update(self) -> None: + bat = self.total_power_from_shelly() * -1 + imported, exported = self.sim_counter.sim_count(bat) + bat_state = BatState( + power=bat, + imported=imported, + exported=exported + ) + self.store.set(bat_state) + + +component_descriptor = ComponentDescriptor(configuration_factory=ShellyBatSetup) diff --git a/packages/modules/devices/shelly/config.py b/packages/modules/devices/shelly/config.py index a831ee9403..db5005aba3 100644 --- a/packages/modules/devices/shelly/config.py +++ b/packages/modules/devices/shelly/config.py @@ -23,6 +23,22 @@ def __init__(self, self.configuration = configuration or ShellyConfiguration() +@auto_str +class ShellyCounterConfiguration: + def __init__(self) -> None: + pass + + +@auto_str +class ShellyCounterSetup(ComponentSetup[ShellyCounterConfiguration]): + def __init__(self, + name: str = "Shelly Zähler", + type: str = "counter", + id: int = 0, + configuration: ShellyCounterConfiguration = None) -> None: + super().__init__(name, type, id, configuration or ShellyCounterConfiguration()) + + @auto_str class ShellyInverterConfiguration: def __init__(self) -> None: @@ -37,3 +53,19 @@ def __init__(self, id: int = 0, configuration: ShellyInverterConfiguration = None) -> None: super().__init__(name, type, id, configuration or ShellyInverterConfiguration()) + + +@auto_str +class ShellyBatConfiguration: + def __init__(self) -> None: + pass + + +@auto_str +class ShellyBatSetup(ComponentSetup[ShellyBatConfiguration]): + def __init__(self, + name: str = "Shelly Speicher", + type: str = "bat", + id: int = 0, + configuration: ShellyBatConfiguration = None) -> None: + super().__init__(name, type, id, configuration or ShellyBatConfiguration()) diff --git a/packages/modules/devices/shelly/counter.py b/packages/modules/devices/shelly/counter.py new file mode 100644 index 0000000000..f348fb0917 --- /dev/null +++ b/packages/modules/devices/shelly/counter.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +import logging +from typing import Optional +from modules.common import req +from modules.common.component_state import CounterState +from modules.common.component_type import ComponentDescriptor +from modules.common.fault_state import ComponentInfo, FaultState +from modules.common.store import get_counter_value_store +from modules.common.simcount._simcounter import SimCounter +from modules.devices.shelly.config import ShellyCounterSetup + +log = logging.getLogger(__name__) + + +class ShellyCounter: + + def __init__(self, + device_id: int, + component_config: ShellyCounterSetup, + address: str, + generation: Optional[int]) -> None: + self.component_config = component_config + self.sim_counter = SimCounter(device_id, self.component_config.id, prefix="bezug") + self.store = get_counter_value_store(self.component_config.id) + self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) + self.address = address + self.generation = generation + + def update(self) -> None: + power = 0 + if self.generation == 1: + status_url = "http://" + self.address + "/status" + else: + status_url = "http://" + self.address + "/rpc/Shelly.GetStatus" + status = req.get_http_session().get(status_url, timeout=3).json() + try: + if self.generation == 1: # shelly3EM + meters = status['emeters'] + # shelly3EM has three meters: + for meter in meters: + power = power + meter['power'] + power = power * -1 + + voltages = [status['emeters'][i]['voltage'] for i in range(0, 3)] + currents = [status['emeters'][i]['current'] for i in range(0, 3)] + powers = [status['emeters'][i]['power'] for i in range(0, 3)] + power_factors = [status['emeters'][i]['pf'] for i in range(0, 3)] + imported, exported = self.sim_counter.sim_count(power) + else: + # shelly Pro3EM + voltages = [status['em:0'][f'{i}_voltage'] for i in 'abc'] + currents = [status['em:0'][f'{i}_current'] for i in 'abc'] + powers = [status['em:0'][f'{i}_act_power'] for i in 'abc'] + power_factors = [status['em:0'][f'{i}_pf'] for i in 'abc'] + power = status['em:0']['total_act_power'] * -1 + imported, exported = self.sim_counter.sim_count(power) + + counter_state = CounterState( + voltages=voltages, + currents=currents, + powers=powers, + power_factors=power_factors, + imported=imported, + exported=exported, + power=power + ) + self.store.set(counter_state) + except KeyError: + log.exception("unsupported shelly device?") + + +component_descriptor = ComponentDescriptor(configuration_factory=ShellyCounterSetup) diff --git a/packages/modules/devices/shelly/device.py b/packages/modules/devices/shelly/device.py index 04be22c494..b2639df22f 100644 --- a/packages/modules/devices/shelly/device.py +++ b/packages/modules/devices/shelly/device.py @@ -8,8 +8,12 @@ from modules.common.abstract_device import DeviceDescriptor from modules.common.configurable_device import (ConfigurableDevice, ComponentFactoryByType, IndependentComponentUpdater) from modules.devices.shelly.inverter import ShellyInverter +from modules.devices.shelly.bat import ShellyBat +from modules.devices.shelly.counter import ShellyCounter from modules.devices.shelly.config import Shelly, ShellyConfiguration from modules.devices.shelly.config import ShellyInverterSetup, ShellyInverterConfiguration +from modules.devices.shelly.config import ShellyBatSetup +from modules.devices.shelly.config import ShellyCounterSetup log = logging.getLogger(__name__) @@ -25,17 +29,27 @@ def get_device_generation(address: str) -> int: def create_device(device_config: Shelly) -> ConfigurableDevice: + def create_counter_component(component_config: ShellyCounterSetup) -> ShellyCounter: + return ShellyCounter(device_config.id, component_config, device_config.configuration.ip_address, + device_config.configuration.generation) + def create_inverter_component(component_config: ShellyInverterSetup) -> ShellyInverter: return ShellyInverter(device_config.id, component_config, device_config.configuration.ip_address, device_config.configuration.generation) + def create_bat_component(component_config: ShellyBatSetup) -> ShellyBat: + return ShellyBat(device_config.id, component_config, device_config.configuration.ip_address, + device_config.configuration.generation) + if device_config.configuration.generation is None and device_config.configuration.ip_address is not None: device_config.configuration.generation = get_device_generation(device_config.configuration.ip_address) return ConfigurableDevice( device_config=device_config, component_factory=ComponentFactoryByType( - inverter=create_inverter_component + counter=create_counter_component, + inverter=create_inverter_component, + bat=create_bat_component ), component_updater=IndependentComponentUpdater(lambda component: component.update()) ) diff --git a/packages/modules/devices/siemens/device.py b/packages/modules/devices/siemens/device.py index 03f4ca2b4a..720c87dbe6 100644 --- a/packages/modules/devices/siemens/device.py +++ b/packages/modules/devices/siemens/device.py @@ -1,99 +1,51 @@ #!/usr/bin/env python3 import logging -from typing import Dict, Union, Optional, List +from typing import Iterable, Union -from dataclass_utils import dataclass_from_dict -from helpermodules.cli import run_using_positional_cli_args from modules.common import modbus -from modules.common.abstract_device import AbstractDevice, DeviceDescriptor +from modules.common.abstract_device import DeviceDescriptor from modules.common.component_context import SingleComponentUpdateContext -from modules.devices.siemens import bat, counter, inverter +from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, MultiComponentUpdater +from modules.devices.siemens.bat import SiemensBat from modules.devices.siemens.config import Siemens, SiemensBatSetup, SiemensCounterSetup, SiemensInverterSetup +from modules.devices.siemens.counter import SiemensCounter +from modules.devices.siemens.inverter import SiemensInverter log = logging.getLogger(__name__) -siemens_component_classes = Union[bat.SiemensBat, counter.SiemensCounter, inverter.SiemensInverter] +siemens_component_classes = Union[SiemensBat, SiemensCounter, SiemensInverter] -class Device(AbstractDevice): - COMPONENT_TYPE_TO_CLASS = { - "bat": bat.SiemensBat, - "counter": counter.SiemensCounter, - "inverter": inverter.SiemensInverter - } +def create_device(device_config: Siemens): + def create_bat_component(component_config: SiemensBatSetup): + return SiemensBat(device_config.id, component_config, client, device_config.configuration.modbus_id) - def __init__(self, device_config: Union[Dict, Siemens]) -> None: - self.components = {} # type: Dict[str, siemens_component_classes] - try: - self.device_config = dataclass_from_dict(Siemens, device_config) - self.client = modbus.ModbusTcpClient_( - self.device_config.configuration.ip_address, self.device_config.configuration.port) - except Exception: - log.exception("Fehler im Modul "+self.device_config.name) + def create_counter_component(component_config: SiemensCounterSetup): + return SiemensCounter(device_config.id, component_config, client, device_config.configuration.modbus_id) - def add_component(self, component_config: Union[Dict, - SiemensBatSetup, - SiemensCounterSetup, - SiemensInverterSetup]) -> None: - if isinstance(component_config, Dict): - component_type = component_config["type"] - else: - component_type = component_config.type - component_config = dataclass_from_dict(COMPONENT_TYPE_TO_MODULE[ - component_type].component_descriptor.configuration_factory, component_config) - if component_type in self.COMPONENT_TYPE_TO_CLASS: - self.components["component"+str(component_config.id)] = (self.COMPONENT_TYPE_TO_CLASS[component_type]( - self.device_config.id, component_config, self.client, - self.device_config.configuration.modbus_id)) - else: - raise Exception( - "illegal component type " + component_type + ". Allowed values: " + - ','.join(self.COMPONENT_TYPE_TO_CLASS.keys()) - ) + def create_inverter_component(component_config: SiemensInverterSetup): + return SiemensInverter(device_config.id, component_config, client, device_config.configuration.modbus_id) - def update(self) -> None: - log.debug("Start device reading" + str(self.components)) - if self.components: - for component in self.components: - # Auch wenn bei einer Komponente ein Fehler auftritt, sollen alle anderen noch ausgelesen werden. - with SingleComponentUpdateContext(self.components[component].fault_state): - self.components[component].update() - else: - log.warning( - self.device_config.name + - ": Es konnten keine Werte gelesen werden, da noch keine Komponenten konfiguriert wurden." - ) + def update_components(components: Iterable[siemens_component_classes]): + with client: + for component in components: + with SingleComponentUpdateContext(component.fault_state): + component.update() - -COMPONENT_TYPE_TO_MODULE = { - "bat": bat, - "counter": counter, - "inverter": inverter -} - - -def read_legacy(component_type: str, ip_address: str, num: Optional[int] = None) -> None: - device_config = Siemens() - device_config.configuration.ip_address = ip_address - dev = Device(device_config) - if component_type in COMPONENT_TYPE_TO_MODULE: - component_config = COMPONENT_TYPE_TO_MODULE[component_type].component_descriptor.configuration_factory() - else: - raise Exception( - "illegal component type " + component_type + ". Allowed values: " + - ','.join(COMPONENT_TYPE_TO_MODULE.keys()) - ) - component_config.id = num - dev.add_component(component_config) - - log.debug('Siemens IP-Adresse: ' + ip_address) - - dev.update() - - -def main(argv: List[str]): - run_using_positional_cli_args(read_legacy, argv) + try: + client = modbus.ModbusTcpClient_(device_config.configuration.ip_address, device_config.configuration.port) + except Exception: + log.exception("Fehler in create_device") + return ConfigurableDevice( + device_config=device_config, + component_factory=ComponentFactoryByType( + bat=create_bat_component, + counter=create_counter_component, + inverter=create_inverter_component, + ), + component_updater=MultiComponentUpdater(update_components) + ) device_descriptor = DeviceDescriptor(configuration_factory=Siemens) diff --git a/packages/modules/devices/sma_shm/config.py b/packages/modules/devices/sma_shm/config.py index 2b1a69f270..0e2bd2021f 100644 --- a/packages/modules/devices/sma_shm/config.py +++ b/packages/modules/devices/sma_shm/config.py @@ -8,7 +8,7 @@ def __init__(self): class Speedwire: def __init__(self, - name: str = "SMA Sunny Home Manager 2.0", + name: str = "SMA Sunny Home Manager 2.0, Energy Meter", type: str = "sma_shm", id: int = 0, configuration: SpeedwireComponentConfiguration = None) -> None: @@ -25,7 +25,7 @@ def __init__(self, serials: int = None): class SmaHomeManagerCounterSetup(ComponentSetup[SmaHomeManagerCounterConfiguration]): def __init__(self, - name: str = "SMA Sunny Home Manager 2.0 Zähler", + name: str = "SMA Sunny Home Manager 2.0, Energy Meter Zähler", type: str = "counter", id: int = 0, configuration: SmaHomeManagerCounterConfiguration = None) -> None: @@ -39,7 +39,7 @@ def __init__(self, serials: int = None): class SmaHomeManagerInverterSetup(ComponentSetup[SmaHomeManagerInverterConfiguration]): def __init__(self, - name: str = "SMA Sunny Home Manager 2.0 Wechselrichter", + name: str = "SMA Sunny Home Manager 2.0, Energy Meter Wechselrichter", type: str = "inverter", id: int = 0, configuration: SmaHomeManagerInverterConfiguration = None) -> None: diff --git a/packages/modules/devices/sma_sunny_boy/counter.py b/packages/modules/devices/sma_sunny_boy/counter.py index 57bdfec0db..f258193944 100644 --- a/packages/modules/devices/sma_sunny_boy/counter.py +++ b/packages/modules/devices/sma_sunny_boy/counter.py @@ -34,7 +34,8 @@ def update(self): else: power = exp * -1 - imported, exported = self.sim_counter.sim_count(power) + imported = self.__tcp_client.read_holding_registers(30581, ModbusDataType.UINT_32, unit=unit) + exported = self.__tcp_client.read_holding_registers(30583, ModbusDataType.UINT_32, unit=unit) counter_state = CounterState( imported=imported, diff --git a/packages/modules/devices/sma_sunny_boy/device.py b/packages/modules/devices/sma_sunny_boy/device.py index ba544bb8b8..1fdede73da 100644 --- a/packages/modules/devices/sma_sunny_boy/device.py +++ b/packages/modules/devices/sma_sunny_boy/device.py @@ -7,7 +7,7 @@ from helpermodules.cli import run_using_positional_cli_args from modules.common import modbus from modules.common.abstract_device import AbstractDevice, DeviceDescriptor -from modules.common.component_context import SingleComponentUpdateContext +from modules.common.component_context import MultiComponentUpdateContext, SingleComponentUpdateContext from modules.common.component_state import InverterState from modules.common.store import get_inverter_value_store from modules.devices.sma_sunny_boy import bat, bat_smart_energy, counter, inverter @@ -71,11 +71,13 @@ def add_component(self, component_config: Union[Dict, def update(self) -> None: log.debug("Start device reading " + str(self.components)) if self.components: - with self.client: - for component in self.components.values(): - # Auch wenn bei einer Komponente ein Fehler auftritt, sollen alle anderen noch ausgelesen werden. - with SingleComponentUpdateContext(component.fault_state): - component.update() + with MultiComponentUpdateContext(self.components): + with self.client: + for component in self.components.values(): + # Auch wenn bei einer Komponente ein Fehler auftritt, sollen alle anderen noch ausgelesen + # werden. + with SingleComponentUpdateContext(component.fault_state): + component.update() else: log.warning( self.device_config.name + diff --git a/packages/modules/devices/sma_sunny_boy/inverter.py b/packages/modules/devices/sma_sunny_boy/inverter.py index 6b89593639..22a6f6a9c0 100644 --- a/packages/modules/devices/sma_sunny_boy/inverter.py +++ b/packages/modules/devices/sma_sunny_boy/inverter.py @@ -18,6 +18,7 @@ class SmaSunnyBoyInverter: SMA_INT32_NAN = -0x80000000 # SMA uses this value to represent NaN + SMA_UINT_64_NAN = 0x100000000 SMA_NAN = -0xC000 def __init__(self, @@ -66,6 +67,8 @@ def read(self) -> InverterState: raise ValueError("Unbekannte Version "+str(self.component_config.configuration.version)) if power_total == self.SMA_INT32_NAN or power_total == self.SMA_NAN: power_total = 0 + if dc_power == self.SMA_UINT_64_NAN: + dc_power = 0 inverter_state = InverterState( power=power_total * -1, diff --git a/packages/modules/display_themes/cards/config.py b/packages/modules/display_themes/cards/config.py index da17e3c042..0d4e528838 100644 --- a/packages/modules/display_themes/cards/config.py +++ b/packages/modules/display_themes/cards/config.py @@ -15,7 +15,10 @@ def __init__(self, enable_dashboard_card_battery_sum: bool = True, enable_dashboard_card_inverter_sum: bool = True, enable_dashboard_card_charge_point_sum: bool = True, + enable_dashboard_card_vehicles: bool = True, + enable_energy_flow_view: bool = True, enable_charge_points_view: bool = True, + simple_charge_point_view: bool = False, enable_status_view: bool = True) -> None: # display lock settings self.lock_changes = lock_changes @@ -27,8 +30,12 @@ def __init__(self, self.enable_dashboard_card_battery_sum = enable_dashboard_card_battery_sum self.enable_dashboard_card_inverter_sum = enable_dashboard_card_inverter_sum self.enable_dashboard_card_charge_point_sum = enable_dashboard_card_charge_point_sum + self.enable_dashboard_card_vehicles = enable_dashboard_card_vehicles + # energy flow settings + self.enable_energy_flow_view = enable_energy_flow_view # charge point settings self.enable_charge_points_view = enable_charge_points_view + self.simple_charge_point_view = simple_charge_point_view # state settings self.enable_status_view = enable_status_view diff --git a/packages/modules/display_themes/cards/source/.eslintrc.cjs b/packages/modules/display_themes/cards/source/.eslintrc.cjs deleted file mode 100644 index dba6d34e8e..0000000000 --- a/packages/modules/display_themes/cards/source/.eslintrc.cjs +++ /dev/null @@ -1,14 +0,0 @@ -/* eslint-env node */ -require("@rushstack/eslint-patch/modern-module-resolution"); - -module.exports = { - root: true, - extends: [ - "plugin:vue/vue3-essential", - "eslint:recommended", - "@vue/eslint-config-prettier", - ], - parserOptions: { - ecmaVersion: "latest", - }, -}; diff --git a/packages/modules/display_themes/cards/source/eslint.config.js b/packages/modules/display_themes/cards/source/eslint.config.js new file mode 100644 index 0000000000..75e73d5160 --- /dev/null +++ b/packages/modules/display_themes/cards/source/eslint.config.js @@ -0,0 +1,13 @@ +import js from "@eslint/js" +import pluginVue from "eslint-plugin-vue" + +export default [ + js.configs.recommended, + ...pluginVue.configs['flat/recommended'], + { + files: ["**/*.{vue,js,jsx,cjs,mjs}"], + languageOptions: { + ecmaVersion: "latest", + }, + } +] diff --git a/packages/modules/display_themes/cards/source/package-lock.json b/packages/modules/display_themes/cards/source/package-lock.json index eb8a1ea378..a98ccbb98a 100644 --- a/packages/modules/display_themes/cards/source/package-lock.json +++ b/packages/modules/display_themes/cards/source/package-lock.json @@ -8,50 +8,54 @@ "name": "openwb-display-cards", "version": "0.0.0", "dependencies": { - "@fortawesome/fontawesome-svg-core": "^6.2.1", - "@fortawesome/free-regular-svg-icons": "^6.2.1", - "@fortawesome/free-solid-svg-icons": "^6.2.1", - "@fortawesome/vue-fontawesome": "^3.0.2", - "@inkline/inkline": "^3.2.0", + "@fortawesome/fontawesome-svg-core": "^6.5.2", + "@fortawesome/free-regular-svg-icons": "^6.5.2", + "@fortawesome/free-solid-svg-icons": "^6.5.2", + "@fortawesome/vue-fontawesome": "^3.0.8", + "@inkline/inkline": "^3.2.2", "buffer": "^6.0.3", "events": "^3.3.0", - "mqtt": "^5.3.3", + "mqtt": "^5.8.0", "node-stdlib-browser": "^1.2.0", - "pinia": "^2.0.28", - "vue": "^3.4.19", - "vue-router": "^4.1.6" + "pinia": "^2.1.7", + "vue": "^3.4.31", + "vue-router": "^4.4.0" }, "devDependencies": { - "@rushstack/eslint-patch": "^1.1.4", - "@vitejs/plugin-vue": "^5.0.4", + "@rushstack/eslint-patch": "^1.10.3", + "@vitejs/plugin-vue": "^5.0.5", "@vue/eslint-config-prettier": "^9.0.0", - "@vue/test-utils": "^2.2.4", - "eslint": "^8.30.0", - "eslint-plugin-vue": "^9.3.0", - "jsdom": "^24.0.0", - "postcss": "^8.4.35", - "postcss-preset-env": "^9.0.0", - "prettier": "^3.1.1", + "@vue/test-utils": "^2.4.6", + "eslint": "^9.6.0", + "eslint-plugin-vue": "^9.27.0", + "jsdom": "^24.1.0", + "postcss": "^8.4.39", + "postcss-preset-env": "^9.6.0", + "prettier": "^3.3.2", "rollup-plugin-polyfill-node": "^0.13.0", - "sass": "^1.57.1", - "vite": "^5.0.12", - "vite-plugin-node-polyfills": "^0.21.0", - "vitest": "^1.0.4" + "sass": "^1.77.7", + "vite": "^5.3.3", + "vite-plugin-node-polyfills": "^0.22.0", + "vitest": "^2.0.2" } }, - "node_modules/@aashutoshrathi/word-wrap": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", - "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, "engines": { - "node": ">=0.10.0" + "node": ">=6.0.0" } }, "node_modules/@babel/parser": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", - "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", + "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==", "bin": { "parser": "bin/babel-parser.js" }, @@ -60,9 +64,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz", - "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.7.tgz", + "integrity": "sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -71,9 +75,9 @@ } }, "node_modules/@csstools/cascade-layer-name-parser": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-1.0.7.tgz", - "integrity": "sha512-9J4aMRJ7A2WRjaRLvsMeWrL69FmEuijtiW1XlK/sG+V0UJiHVYUyvj9mY4WAXfU/hGIiGOgL8e0jJcRyaZTjDQ==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-1.0.13.tgz", + "integrity": "sha512-MX0yLTwtZzr82sQ0zOjqimpZbzjMaK/h2pmlrLK7DCzlmiZLYFpoO94WmN1akRVo6ll/TdpHb53vihHLUMyvng==", "dev": true, "funding": [ { @@ -89,14 +93,14 @@ "node": "^14 || ^16 || >=18" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^2.5.0", - "@csstools/css-tokenizer": "^2.2.3" + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1" } }, "node_modules/@csstools/color-helpers": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-4.0.0.tgz", - "integrity": "sha512-wjyXB22/h2OvxAr3jldPB7R7kjTUEzopvjitS8jWtyd8fN6xJ8vy1HnHu0ZNfEkqpBJgQ76Q+sBDshWcMvTa/w==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-4.2.1.tgz", + "integrity": "sha512-CEypeeykO9AN7JWkr1OEOQb0HRzZlPWGwV0Ya6DuVgFdDi6g3ma/cPZ5ZPZM4AWQikDpq/0llnGGlIL+j8afzw==", "dev": true, "funding": [ { @@ -113,9 +117,9 @@ } }, "node_modules/@csstools/css-calc": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-1.1.6.tgz", - "integrity": "sha512-YHPAuFg5iA4qZGzMzvrQwzkvJpesXXyIUyaONflQrjtHB+BcFFbgltJkIkb31dMGO4SE9iZFA4HYpdk7+hnYew==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-1.2.4.tgz", + "integrity": "sha512-tfOuvUQeo7Hz+FcuOd3LfXVp+342pnWUJ7D2y8NUpu1Ww6xnTbHLpz018/y6rtbHifJ3iIEf9ttxXd8KG7nL0Q==", "dev": true, "funding": [ { @@ -131,14 +135,14 @@ "node": "^14 || ^16 || >=18" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^2.5.0", - "@csstools/css-tokenizer": "^2.2.3" + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1" } }, "node_modules/@csstools/css-color-parser": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-1.5.1.tgz", - "integrity": "sha512-x+SajGB2paGrTjPOUorGi8iCztF008YMKXTn+XzGVDBEIVJ/W1121pPerpneJYGOe1m6zWLPLnzOPaznmQxKFw==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-2.0.4.tgz", + "integrity": "sha512-yUb0mk/k2yVNcQvRmd9uikpu6D0aamFJGgU++5d0lng6ucaJkhKyhDCQCj9rVuQYntvFQKqyU6UfTPQWU2UkXQ==", "dev": true, "funding": [ { @@ -151,21 +155,21 @@ } ], "dependencies": { - "@csstools/color-helpers": "^4.0.0", - "@csstools/css-calc": "^1.1.6" + "@csstools/color-helpers": "^4.2.1", + "@csstools/css-calc": "^1.2.4" }, "engines": { "node": "^14 || ^16 || >=18" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^2.5.0", - "@csstools/css-tokenizer": "^2.2.3" + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1" } }, "node_modules/@csstools/css-parser-algorithms": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.5.0.tgz", - "integrity": "sha512-abypo6m9re3clXA00eu5syw+oaPHbJTPapu9C4pzNsJ4hdZDzushT50Zhu+iIYXgEe1CxnRMn7ngsbV+MLrlpQ==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.7.1.tgz", + "integrity": "sha512-2SJS42gxmACHgikc1WGesXLIT8d/q2l0UFM7TaEeIzdFCE/FPMtTiizcPGGJtlPo2xuQzY09OhrLTzRxqJqwGw==", "dev": true, "funding": [ { @@ -181,13 +185,13 @@ "node": "^14 || ^16 || >=18" }, "peerDependencies": { - "@csstools/css-tokenizer": "^2.2.3" + "@csstools/css-tokenizer": "^2.4.1" } }, "node_modules/@csstools/css-tokenizer": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-2.2.3.tgz", - "integrity": "sha512-pp//EvZ9dUmGuGtG1p+n17gTHEOqu9jO+FiCUjNN3BDmyhdA2Jq9QsVeR7K8/2QCK17HSsioPlTW9ZkzoWb3Lg==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-2.4.1.tgz", + "integrity": "sha512-eQ9DIktFJBhGjioABJRtUucoWR2mwllurfnM8LuNGAqX3ViZXaUchqk+1s7jjtkFiT9ySdACsFEA3etErkALUg==", "dev": true, "funding": [ { @@ -204,9 +208,9 @@ } }, "node_modules/@csstools/media-query-list-parser": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.7.tgz", - "integrity": "sha512-lHPKJDkPUECsyAvD60joYfDmp8UERYxHGkFfyLJFTVK/ERJe0sVlIFLXU5XFxdjNDTerp5L4KeaKG+Z5S94qxQ==", + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.13.tgz", + "integrity": "sha512-XaHr+16KRU9Gf8XLi3q8kDlI18d5vzKSKCY510Vrtc9iNR0NJzbY9hhTmwhzYZj/ZwGL4VmB3TA9hJW0Um2qFA==", "dev": true, "funding": [ { @@ -222,14 +226,14 @@ "node": "^14 || ^16 || >=18" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^2.5.0", - "@csstools/css-tokenizer": "^2.2.3" + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1" } }, "node_modules/@csstools/postcss-cascade-layers": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-4.0.2.tgz", - "integrity": "sha512-PqM+jvg5T2tB4FHX+akrMGNWAygLupD4FNUjcv4PSvtVuWZ6ISxuo37m4jFGU7Jg3rCfloGzKd0+xfr5Ec3vZQ==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-4.0.6.tgz", + "integrity": "sha512-Xt00qGAQyqAODFiFEJNkTpSUz5VfYqnDLECdlA/Vv17nl/OIV5QfTRHGAXrBGG5YcJyHpJ+GF9gF/RZvOQz4oA==", "dev": true, "funding": [ { @@ -242,7 +246,7 @@ } ], "dependencies": { - "@csstools/selector-specificity": "^3.0.1", + "@csstools/selector-specificity": "^3.1.1", "postcss-selector-parser": "^6.0.13" }, "engines": { @@ -253,9 +257,9 @@ } }, "node_modules/@csstools/postcss-color-function": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-3.0.9.tgz", - "integrity": "sha512-6Hbkw/4k73UH121l4LG+LNLKSvrfHqk3GHHH0A6/iFlD0xGmsWAr80Jd0VqXjfYbUTOGmJTOMMoxv3jvNxt1uw==", + "version": "3.0.19", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-3.0.19.tgz", + "integrity": "sha512-d1OHEXyYGe21G3q88LezWWx31ImEDdmINNDy0LyLNN9ChgN2bPxoubUPiHf9KmwypBMaHmNcMuA/WZOKdZk/Lg==", "dev": true, "funding": [ { @@ -268,10 +272,11 @@ } ], "dependencies": { - "@csstools/css-color-parser": "^1.5.1", - "@csstools/css-parser-algorithms": "^2.5.0", - "@csstools/css-tokenizer": "^2.2.3", - "@csstools/postcss-progressive-custom-properties": "^3.0.3" + "@csstools/css-color-parser": "^2.0.4", + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1", + "@csstools/postcss-progressive-custom-properties": "^3.3.0", + "@csstools/utilities": "^1.0.0" }, "engines": { "node": "^14 || ^16 || >=18" @@ -281,9 +286,38 @@ } }, "node_modules/@csstools/postcss-color-mix-function": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-2.0.9.tgz", - "integrity": "sha512-fs1SOWJ/44DQSsDeJP+rxAkP2MYkCg6K4ZB8qJwFku2EjurgCAPiPZJvC6w94T1hBBinJwuMfT9qvvvniXyVgw==", + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-2.0.19.tgz", + "integrity": "sha512-mLvQlMX+keRYr16AuvuV8WYKUwF+D0DiCqlBdvhQ0KYEtcQl9/is9Ssg7RcIys8x0jIn2h1zstS4izckdZj9wg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/css-color-parser": "^2.0.4", + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1", + "@csstools/postcss-progressive-custom-properties": "^3.3.0", + "@csstools/utilities": "^1.0.0" + }, + "engines": { + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-content-alt-text": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-content-alt-text/-/postcss-content-alt-text-1.0.0.tgz", + "integrity": "sha512-SkHdj7EMM/57GVvSxSELpUg7zb5eAndBeuvGwFzYtU06/QXJ/h9fuK7wO5suteJzGhm3GDF/EWPCdWV2h1IGHQ==", "dev": true, "funding": [ { @@ -296,10 +330,10 @@ } ], "dependencies": { - "@csstools/css-color-parser": "^1.5.1", - "@csstools/css-parser-algorithms": "^2.5.0", - "@csstools/css-tokenizer": "^2.2.3", - "@csstools/postcss-progressive-custom-properties": "^3.0.3" + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1", + "@csstools/postcss-progressive-custom-properties": "^3.3.0", + "@csstools/utilities": "^1.0.0" }, "engines": { "node": "^14 || ^16 || >=18" @@ -309,9 +343,9 @@ } }, "node_modules/@csstools/postcss-exponential-functions": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@csstools/postcss-exponential-functions/-/postcss-exponential-functions-1.0.3.tgz", - "integrity": "sha512-IfGtEg3eC4b8Nd/kPgO3SxgKb33YwhHVsL0eJ3UYihx6fzzAiZwNbWmVW9MZTQjZ5GacgKxa4iAHikGvpwuIjw==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@csstools/postcss-exponential-functions/-/postcss-exponential-functions-1.0.9.tgz", + "integrity": "sha512-x1Avr15mMeuX7Z5RJUl7DmjhUtg+Amn5DZRD0fQ2TlTFTcJS8U1oxXQ9e5mA62S2RJgUU6db20CRoJyDvae2EQ==", "dev": true, "funding": [ { @@ -324,9 +358,9 @@ } ], "dependencies": { - "@csstools/css-calc": "^1.1.6", - "@csstools/css-parser-algorithms": "^2.5.0", - "@csstools/css-tokenizer": "^2.2.3" + "@csstools/css-calc": "^1.2.4", + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1" }, "engines": { "node": "^14 || ^16 || >=18" @@ -336,9 +370,9 @@ } }, "node_modules/@csstools/postcss-font-format-keywords": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-3.0.1.tgz", - "integrity": "sha512-D1lcG2sfotTq6yBEOMV3myFxJLT10F3DLYZJMbiny5YToqzHWodZen8WId3UTimm0mEHitXqAUNL5jdd6RzVdA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-3.0.2.tgz", + "integrity": "sha512-E0xz2sjm4AMCkXLCFvI/lyl4XO6aN1NCSMMVEOngFDJ+k2rDwfr6NDjWljk1li42jiLNChVX+YFnmfGCigZKXw==", "dev": true, "funding": [ { @@ -351,6 +385,7 @@ } ], "dependencies": { + "@csstools/utilities": "^1.0.0", "postcss-value-parser": "^4.2.0" }, "engines": { @@ -361,9 +396,9 @@ } }, "node_modules/@csstools/postcss-gamut-mapping": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-gamut-mapping/-/postcss-gamut-mapping-1.0.2.tgz", - "integrity": "sha512-zf9KHGM2PTuJEm4ZYg4DTmzCir38EbZBzlMPMbA4jbhLDqXHkqwnQ+Z5+UNrU8y6seVu5B4vzZmZarTFQwe+Ig==", + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@csstools/postcss-gamut-mapping/-/postcss-gamut-mapping-1.0.11.tgz", + "integrity": "sha512-KrHGsUPXRYxboXmJ9wiU/RzDM7y/5uIefLWKFSc36Pok7fxiPyvkSHO51kh+RLZS1W5hbqw9qaa6+tKpTSxa5g==", "dev": true, "funding": [ { @@ -376,9 +411,9 @@ } ], "dependencies": { - "@csstools/css-color-parser": "^1.5.1", - "@csstools/css-parser-algorithms": "^2.5.0", - "@csstools/css-tokenizer": "^2.2.3" + "@csstools/css-color-parser": "^2.0.4", + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1" }, "engines": { "node": "^14 || ^16 || >=18" @@ -388,9 +423,9 @@ } }, "node_modules/@csstools/postcss-gradients-interpolation-method": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-4.0.9.tgz", - "integrity": "sha512-PSqR6QH7h3ggOl8TsoH73kbwYTKVQjAJauGg6nDKwaGfi5IL5StV//ehrv1C7HuPsHixMTc9YoAuuv1ocT20EQ==", + "version": "4.0.20", + "resolved": "https://registry.npmjs.org/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-4.0.20.tgz", + "integrity": "sha512-ZFl2JBHano6R20KB5ZrB8KdPM2pVK0u+/3cGQ2T8VubJq982I2LSOvQ4/VtxkAXjkPkk1rXt4AD1ni7UjTZ1Og==", "dev": true, "funding": [ { @@ -403,10 +438,11 @@ } ], "dependencies": { - "@csstools/css-color-parser": "^1.5.1", - "@csstools/css-parser-algorithms": "^2.5.0", - "@csstools/css-tokenizer": "^2.2.3", - "@csstools/postcss-progressive-custom-properties": "^3.0.3" + "@csstools/css-color-parser": "^2.0.4", + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1", + "@csstools/postcss-progressive-custom-properties": "^3.3.0", + "@csstools/utilities": "^1.0.0" }, "engines": { "node": "^14 || ^16 || >=18" @@ -416,9 +452,9 @@ } }, "node_modules/@csstools/postcss-hwb-function": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-3.0.8.tgz", - "integrity": "sha512-CRQEG372Hivmt17rm/Ho22hBQI9K/a6grzGQ21Zwc7dyspmyG0ibmPIW8hn15vJmXqWGeNq7S+L2b8/OrU7O5A==", + "version": "3.0.18", + "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-3.0.18.tgz", + "integrity": "sha512-3ifnLltR5C7zrJ+g18caxkvSRnu9jBBXCYgnBznRjxm6gQJGnnCO9H6toHfywNdNr/qkiVf2dymERPQLDnjLRQ==", "dev": true, "funding": [ { @@ -431,9 +467,11 @@ } ], "dependencies": { - "@csstools/css-color-parser": "^1.5.1", - "@csstools/css-parser-algorithms": "^2.5.0", - "@csstools/css-tokenizer": "^2.2.3" + "@csstools/css-color-parser": "^2.0.4", + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1", + "@csstools/postcss-progressive-custom-properties": "^3.3.0", + "@csstools/utilities": "^1.0.0" }, "engines": { "node": "^14 || ^16 || >=18" @@ -443,9 +481,9 @@ } }, "node_modules/@csstools/postcss-ic-unit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-3.0.3.tgz", - "integrity": "sha512-MpcmIL0/uMm/cFWh5V/9nbKKJ7jRr2qTYW5Q6zoE6HZ6uzOBJr2KRERv5/x8xzEBQ1MthDT7iP1EBp9luSQy7g==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-3.0.7.tgz", + "integrity": "sha512-YoaNHH2wNZD+c+rHV02l4xQuDpfR8MaL7hD45iJyr+USwvr0LOheeytJ6rq8FN6hXBmEeoJBeXXgGmM8fkhH4g==", "dev": true, "funding": [ { @@ -458,7 +496,8 @@ } ], "dependencies": { - "@csstools/postcss-progressive-custom-properties": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^3.3.0", + "@csstools/utilities": "^1.0.0", "postcss-value-parser": "^4.2.0" }, "engines": { @@ -491,9 +530,9 @@ } }, "node_modules/@csstools/postcss-is-pseudo-class": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-4.0.4.tgz", - "integrity": "sha512-vTVO/uZixpTVAOQt3qZRUFJ/K1L03OfNkeJ8sFNDVNdVy/zW0h1L5WT7HIPMDUkvSrxQkFaCCybTZkUP7UESlQ==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-4.0.8.tgz", + "integrity": "sha512-0aj591yGlq5Qac+plaWCbn5cpjs5Sh0daovYUKJUOMjIp70prGH/XPLp7QjxtbFXz3CTvb0H9a35dpEuIuUi3Q==", "dev": true, "funding": [ { @@ -506,7 +545,7 @@ } ], "dependencies": { - "@csstools/selector-specificity": "^3.0.1", + "@csstools/selector-specificity": "^3.1.1", "postcss-selector-parser": "^6.0.13" }, "engines": { @@ -516,6 +555,34 @@ "postcss": "^8.4" } }, + "node_modules/@csstools/postcss-light-dark-function": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@csstools/postcss-light-dark-function/-/postcss-light-dark-function-1.0.8.tgz", + "integrity": "sha512-x0UtpCyVnERsplUeoaY6nEtp1HxTf4lJjoK/ULEm40DraqFfUdUSt76yoOyX5rGY6eeOUOkurHyYlFHVKv/pew==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1", + "@csstools/postcss-progressive-custom-properties": "^3.3.0", + "@csstools/utilities": "^1.0.0" + }, + "engines": { + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, "node_modules/@csstools/postcss-logical-float-and-clear": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-float-and-clear/-/postcss-logical-float-and-clear-2.0.1.tgz", @@ -608,9 +675,9 @@ } }, "node_modules/@csstools/postcss-logical-viewport-units": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-viewport-units/-/postcss-logical-viewport-units-2.0.5.tgz", - "integrity": "sha512-2fjSamKN635DSW6fEoyNd2Bkpv3FVblUpgk5cpghIgPW1aDHZE2SYfZK5xQALvjMYZVjfqsD5EbXA7uDVBQVQA==", + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-viewport-units/-/postcss-logical-viewport-units-2.0.11.tgz", + "integrity": "sha512-ElITMOGcjQtvouxjd90WmJRIw1J7KMP+M+O87HaVtlgOOlDt1uEPeTeii8qKGe2AiedEp0XOGIo9lidbiU2Ogg==", "dev": true, "funding": [ { @@ -623,7 +690,8 @@ } ], "dependencies": { - "@csstools/css-tokenizer": "^2.2.3" + "@csstools/css-tokenizer": "^2.4.1", + "@csstools/utilities": "^1.0.0" }, "engines": { "node": "^14 || ^16 || >=18" @@ -633,9 +701,9 @@ } }, "node_modules/@csstools/postcss-media-minmax": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-media-minmax/-/postcss-media-minmax-1.1.2.tgz", - "integrity": "sha512-7qTRTJxW96u2yiEaTep1+8nto1O/rEDacewKqH+Riq5E6EsHTOmGHxkB4Se5Ic5xgDC4I05lLZxzzxnlnSypxA==", + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@csstools/postcss-media-minmax/-/postcss-media-minmax-1.1.8.tgz", + "integrity": "sha512-KYQCal2i7XPNtHAUxCECdrC7tuxIWQCW+s8eMYs5r5PaAiVTeKwlrkRS096PFgojdNCmHeG0Cb7njtuNswNf+w==", "dev": true, "funding": [ { @@ -648,10 +716,10 @@ } ], "dependencies": { - "@csstools/css-calc": "^1.1.6", - "@csstools/css-parser-algorithms": "^2.5.0", - "@csstools/css-tokenizer": "^2.2.3", - "@csstools/media-query-list-parser": "^2.1.7" + "@csstools/css-calc": "^1.2.4", + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1", + "@csstools/media-query-list-parser": "^2.1.13" }, "engines": { "node": "^14 || ^16 || >=18" @@ -661,9 +729,9 @@ } }, "node_modules/@csstools/postcss-media-queries-aspect-ratio-number-values": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-2.0.5.tgz", - "integrity": "sha512-XHMPasWYPWa9XaUHXU6Iq0RLfoAI+nvGTPj51hOizNsHaAyFiq2SL4JvF1DU8lM6B70+HVzKM09Isbyrr755Bw==", + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-2.0.11.tgz", + "integrity": "sha512-YD6jrib20GRGQcnOu49VJjoAnQ/4249liuz7vTpy/JfgqQ1Dlc5eD4HPUMNLOw9CWey9E6Etxwf/xc/ZF8fECA==", "dev": true, "funding": [ { @@ -676,9 +744,9 @@ } ], "dependencies": { - "@csstools/css-parser-algorithms": "^2.5.0", - "@csstools/css-tokenizer": "^2.2.3", - "@csstools/media-query-list-parser": "^2.1.7" + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1", + "@csstools/media-query-list-parser": "^2.1.13" }, "engines": { "node": "^14 || ^16 || >=18" @@ -688,9 +756,9 @@ } }, "node_modules/@csstools/postcss-nested-calc": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-3.0.1.tgz", - "integrity": "sha512-bwwababZpWRm0ByHaWBxTsDGTMhZKmtUNl3Wt0Eom8AY7ORgXx5qF9SSk1vEFrCi+HOfJT6M6W5KPgzXuQNRwQ==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-3.0.2.tgz", + "integrity": "sha512-ySUmPyawiHSmBW/VI44+IObcKH0v88LqFe0d09Sb3w4B1qjkaROc6d5IA3ll9kjD46IIX/dbO5bwFN/swyoyZA==", "dev": true, "funding": [ { @@ -703,6 +771,7 @@ } ], "dependencies": { + "@csstools/utilities": "^1.0.0", "postcss-value-parser": "^4.2.0" }, "engines": { @@ -738,9 +807,9 @@ } }, "node_modules/@csstools/postcss-oklab-function": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-3.0.9.tgz", - "integrity": "sha512-l639gpcBfL3ogJe+og1M5FixQn8iGX8+29V7VtTSCUB37VzpzOC05URfde7INIdiJT65DkHzgdJ64/QeYggU8A==", + "version": "3.0.19", + "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-3.0.19.tgz", + "integrity": "sha512-e3JxXmxjU3jpU7TzZrsNqSX4OHByRC3XjItV3Ieo/JEQmLg5rdOL4lkv/1vp27gXemzfNt44F42k/pn0FpE21Q==", "dev": true, "funding": [ { @@ -753,10 +822,11 @@ } ], "dependencies": { - "@csstools/css-color-parser": "^1.5.1", - "@csstools/css-parser-algorithms": "^2.5.0", - "@csstools/css-tokenizer": "^2.2.3", - "@csstools/postcss-progressive-custom-properties": "^3.0.3" + "@csstools/css-color-parser": "^2.0.4", + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1", + "@csstools/postcss-progressive-custom-properties": "^3.3.0", + "@csstools/utilities": "^1.0.0" }, "engines": { "node": "^14 || ^16 || >=18" @@ -766,9 +836,9 @@ } }, "node_modules/@csstools/postcss-progressive-custom-properties": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-3.0.3.tgz", - "integrity": "sha512-WipTVh6JTMQfeIrzDV4wEPsV9NTzMK2jwXxyH6CGBktuWdivHnkioP/smp1x/0QDPQyx7NTS14RB+GV3zZZYEw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-3.3.0.tgz", + "integrity": "sha512-W2oV01phnILaRGYPmGFlL2MT/OgYjQDrL9sFlbdikMFi6oQkFki9B86XqEWR7HCsTZFVq7dbzr/o71B75TKkGg==", "dev": true, "funding": [ { @@ -791,9 +861,9 @@ } }, "node_modules/@csstools/postcss-relative-color-syntax": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-2.0.9.tgz", - "integrity": "sha512-2UoaRd2iIuzUGtYgteN5fJ0s+OfCiV7PvCnw8MCh3om8+SeVinfG8D5sqBOvImxFVfrp6k60XF5RFlH6oc//fg==", + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-2.0.19.tgz", + "integrity": "sha512-MxUMSNvio1WwuS6WRLlQuv6nNPXwIWUFzBBAvL/tBdWfiKjiJnAa6eSSN5gtaacSqUkQ/Ce5Z1OzLRfeaWhADA==", "dev": true, "funding": [ { @@ -806,10 +876,11 @@ } ], "dependencies": { - "@csstools/css-color-parser": "^1.5.1", - "@csstools/css-parser-algorithms": "^2.5.0", - "@csstools/css-tokenizer": "^2.2.3", - "@csstools/postcss-progressive-custom-properties": "^3.0.3" + "@csstools/css-color-parser": "^2.0.4", + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1", + "@csstools/postcss-progressive-custom-properties": "^3.3.0", + "@csstools/utilities": "^1.0.0" }, "engines": { "node": "^14 || ^16 || >=18" @@ -844,9 +915,9 @@ } }, "node_modules/@csstools/postcss-stepped-value-functions": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-3.0.4.tgz", - "integrity": "sha512-gyNQ2YaOVXPqLR737XtReRPVu7DGKBr9JBDLoiH1T+N1ggV3r4HotRCOC1l6rxVC0zOuU1KiOzUn9Z5W838/rg==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-3.0.10.tgz", + "integrity": "sha512-MZwo0D0TYrQhT5FQzMqfy/nGZ28D1iFtpN7Su1ck5BPHS95+/Y5O9S4kEvo76f2YOsqwYcT8ZGehSI1TnzuX2g==", "dev": true, "funding": [ { @@ -859,9 +930,9 @@ } ], "dependencies": { - "@csstools/css-calc": "^1.1.6", - "@csstools/css-parser-algorithms": "^2.5.0", - "@csstools/css-tokenizer": "^2.2.3" + "@csstools/css-calc": "^1.2.4", + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1" }, "engines": { "node": "^14 || ^16 || >=18" @@ -871,9 +942,9 @@ } }, "node_modules/@csstools/postcss-text-decoration-shorthand": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-3.0.4.tgz", - "integrity": "sha512-yUZmbnUemgQmja7SpOZeU45+P49wNEgQguRdyTktFkZsHf7Gof+ZIYfvF6Cm+LsU1PwSupy4yUeEKKjX5+k6cQ==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-3.0.7.tgz", + "integrity": "sha512-+cptcsM5r45jntU6VjotnkC9GteFR7BQBfZ5oW7inLCxj7AfLGAzMbZ60hKTP13AULVZBdxky0P8um0IBfLHVA==", "dev": true, "funding": [ { @@ -886,7 +957,7 @@ } ], "dependencies": { - "@csstools/color-helpers": "^4.0.0", + "@csstools/color-helpers": "^4.2.1", "postcss-value-parser": "^4.2.0" }, "engines": { @@ -897,9 +968,9 @@ } }, "node_modules/@csstools/postcss-trigonometric-functions": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-3.0.4.tgz", - "integrity": "sha512-qj4Cxth6c38iNYzfJJWAxt8jsLrZaMVmbfGDDLOlI2YJeZoC3A5Su6/Kr7oXaPFRuspUu+4EQHngOktqVHWfVg==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-3.0.10.tgz", + "integrity": "sha512-G9G8moTc2wiad61nY5HfvxLiM/myX0aYK4s1x8MQlPH29WDPxHQM7ghGgvv2qf2xH+rrXhztOmjGHJj4jsEqXw==", "dev": true, "funding": [ { @@ -912,9 +983,9 @@ } ], "dependencies": { - "@csstools/css-calc": "^1.1.6", - "@csstools/css-parser-algorithms": "^2.5.0", - "@csstools/css-tokenizer": "^2.2.3" + "@csstools/css-calc": "^1.2.4", + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1" }, "engines": { "node": "^14 || ^16 || >=18" @@ -945,10 +1016,32 @@ "postcss": "^8.4" } }, + "node_modules/@csstools/selector-resolve-nested": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-1.1.0.tgz", + "integrity": "sha512-uWvSaeRcHyeNenKg8tp17EVDRkpflmdyvbE0DHo6D/GdBb6PDnCYYU6gRpXhtICMGMcahQmj2zGxwFM/WC8hCg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^6.0.13" + } + }, "node_modules/@csstools/selector-specificity": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-3.0.1.tgz", - "integrity": "sha512-NPljRHkq4a14YzZ3YD406uaxh7s0g6eAq3L9aLOWywoqe8PkYamAvtsh7KNX6c++ihDrJ0RiU+/z7rGnhlZ5ww==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-3.1.1.tgz", + "integrity": "sha512-a7cxGcJ2wIlMFLlh8z2ONm+715QkPHiyJcxwQlKOz/03GPw1COpfhcmC9wm4xlZfp//jWHNNMwzjtqHXVWU9KA==", "dev": true, "funding": [ { @@ -967,10 +1060,32 @@ "postcss-selector-parser": "^6.0.13" } }, + "node_modules/@csstools/utilities": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@csstools/utilities/-/utilities-1.0.0.tgz", + "integrity": "sha512-tAgvZQe/t2mlvpNosA4+CkMiZ2azISW5WPAcdSalZlEjQvUfghHxfQcrCiK/7/CrfAWVxyM88kGFYO82heIGDg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", - "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", "cpu": [ "ppc64" ], @@ -984,9 +1099,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", - "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", "cpu": [ "arm" ], @@ -1000,9 +1115,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", - "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", "cpu": [ "arm64" ], @@ -1016,9 +1131,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", - "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", "cpu": [ "x64" ], @@ -1032,9 +1147,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", - "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", "cpu": [ "arm64" ], @@ -1048,9 +1163,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", - "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", "cpu": [ "x64" ], @@ -1064,9 +1179,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", - "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", "cpu": [ "arm64" ], @@ -1080,9 +1195,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", - "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", "cpu": [ "x64" ], @@ -1096,9 +1211,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", - "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", "cpu": [ "arm" ], @@ -1112,9 +1227,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", - "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", "cpu": [ "arm64" ], @@ -1128,9 +1243,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", - "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", "cpu": [ "ia32" ], @@ -1144,9 +1259,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", - "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", "cpu": [ "loong64" ], @@ -1160,9 +1275,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", - "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", "cpu": [ "mips64el" ], @@ -1176,9 +1291,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", - "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", "cpu": [ "ppc64" ], @@ -1192,9 +1307,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", - "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", "cpu": [ "riscv64" ], @@ -1208,9 +1323,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", - "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", "cpu": [ "s390x" ], @@ -1224,9 +1339,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", - "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", "cpu": [ "x64" ], @@ -1240,9 +1355,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", - "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", "cpu": [ "x64" ], @@ -1256,9 +1371,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", - "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", "cpu": [ "x64" ], @@ -1272,9 +1387,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", - "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", "cpu": [ "x64" ], @@ -1288,9 +1403,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", - "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", "cpu": [ "arm64" ], @@ -1304,9 +1419,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", - "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", "cpu": [ "ia32" ], @@ -1320,9 +1435,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", - "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", "cpu": [ "x64" ], @@ -1350,25 +1465,51 @@ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@eslint-community/regexpp": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", - "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", + "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, + "node_modules/@eslint/config-array": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.17.0.tgz", + "integrity": "sha512-A68TBu6/1mHHuc5YJL0U0VVeGNiklLAL6rRmhTCP2B5XjWLMnrX+HkO+IAXyHvks5cyyY1jjK5ITPQ1HGS2EVA==", + "dev": true, + "dependencies": { + "@eslint/object-schema": "^2.1.4", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", + "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", + "espree": "^10.0.1", + "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", @@ -1376,89 +1517,84 @@ "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/@eslint/js": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", - "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.6.0.tgz", + "integrity": "sha512-D9B0/3vNg44ZeWbYMpBoXqNP4j6eQD5vNwIlGAuFRRzK/WtT/jvDQW3Bi9kkf3PMDMlM7Yi+73VLUsn5bJcl8A==", "dev": true, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", + "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@fortawesome/fontawesome-common-types": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.5.1.tgz", - "integrity": "sha512-GkWzv+L6d2bI5f/Vk6ikJ9xtl7dfXtoRu3YGE6nq0p/FFqA1ebMOAWg3XgRyb0I6LYyYkiAo+3/KrwuBp8xG7A==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.5.2.tgz", + "integrity": "sha512-gBxPg3aVO6J0kpfHNILc+NMhXnqHumFxOmjYCFfOiLZfwhnnfhtsdA2hfJlDnj+8PjAs6kKQPenOTKj3Rf7zHw==", "hasInstallScript": true, "engines": { "node": ">=6" } }, "node_modules/@fortawesome/fontawesome-svg-core": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.5.1.tgz", - "integrity": "sha512-MfRCYlQPXoLlpem+egxjfkEuP9UQswTrlCOsknus/NcMoblTH2g0jPrapbcIb04KGA7E2GZxbAccGZfWoYgsrQ==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.5.2.tgz", + "integrity": "sha512-5CdaCBGl8Rh9ohNdxeeTMxIj8oc3KNBgIeLMvJosBMdslK/UnEB8rzyDRrbKdL1kDweqBPo4GT9wvnakHWucZw==", "hasInstallScript": true, "dependencies": { - "@fortawesome/fontawesome-common-types": "6.5.1" + "@fortawesome/fontawesome-common-types": "6.5.2" }, "engines": { "node": ">=6" } }, "node_modules/@fortawesome/free-regular-svg-icons": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.5.1.tgz", - "integrity": "sha512-m6ShXn+wvqEU69wSP84coxLbNl7sGVZb+Ca+XZq6k30SzuP3X4TfPqtycgUh9ASwlNh5OfQCd8pDIWxl+O+LlQ==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.5.2.tgz", + "integrity": "sha512-iabw/f5f8Uy2nTRtJ13XZTS1O5+t+anvlamJ3zJGLEVE2pKsAWhPv2lq01uQlfgCX7VaveT3EVs515cCN9jRbw==", "hasInstallScript": true, "dependencies": { - "@fortawesome/fontawesome-common-types": "6.5.1" + "@fortawesome/fontawesome-common-types": "6.5.2" }, "engines": { "node": ">=6" } }, "node_modules/@fortawesome/free-solid-svg-icons": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.5.1.tgz", - "integrity": "sha512-S1PPfU3mIJa59biTtXJz1oI0+KAXW6bkAb31XKhxdxtuXDiUIFsih4JR1v5BbxY7hVHsD1RKq+jRkVRaf773NQ==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.5.2.tgz", + "integrity": "sha512-QWFZYXFE7O1Gr1dTIp+D6UcFUF0qElOnZptpi7PBUMylJh+vFmIedVe1Ir6RM1t2tEQLLSV1k7bR4o92M+uqlw==", "hasInstallScript": true, "dependencies": { - "@fortawesome/fontawesome-common-types": "6.5.1" + "@fortawesome/fontawesome-common-types": "6.5.2" }, "engines": { "node": ">=6" } }, "node_modules/@fortawesome/vue-fontawesome": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@fortawesome/vue-fontawesome/-/vue-fontawesome-3.0.6.tgz", - "integrity": "sha512-akrL7lTroyNpPkoHtvK2UpsMzJr6jXdHaQ0YdcwqDsB8jdwlpNHZYijpOUd9KJsARr+VB3WXY4EyObepqJ4ytQ==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@fortawesome/vue-fontawesome/-/vue-fontawesome-3.0.8.tgz", + "integrity": "sha512-yyHHAj4G8pQIDfaIsMvQpwKMboIZtcHTUvPqXjOHyldh1O1vZfH4W03VDPv5RvI9P6DLTzJQlmVgj9wCf7c2Fw==", "peerDependencies": { "@fortawesome/fontawesome-svg-core": "~1 || ~6", "vue": ">= 3.0.0 < 4" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", - "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", - "debug": "^4.3.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -1472,11 +1608,18 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", - "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", - "dev": true + "node_modules/@humanwhocodes/retry": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz", + "integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==", + "dev": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } }, "node_modules/@inkline/inkline": { "version": "3.2.2", @@ -1537,22 +1680,52 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dev": true, "dependencies": { - "@sinclair/typebox": "^0.27.8" + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "engines": { + "node": ">=6.0.0" } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", @@ -1671,9 +1844,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.6.tgz", - "integrity": "sha512-MVNXSSYN6QXOulbHpLMKYi60ppyO13W9my1qogeiAqtjb2yR4LSmfU2+POvDkLzhjYLXz9Rf9+9a3zFHW1Lecg==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.1.tgz", + "integrity": "sha512-lncuC4aHicncmbORnx+dUaAgzee9cm/PbIqgWz1PpXuwc+sa1Ct83tnqUDy/GFKleLiN7ZIeytM6KJ4cAn1SxA==", "cpu": [ "arm" ], @@ -1684,9 +1857,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.6.tgz", - "integrity": "sha512-T14aNLpqJ5wzKNf5jEDpv5zgyIqcpn1MlwCrUXLrwoADr2RkWA0vOWP4XxbO9aiO3dvMCQICZdKeDrFl7UMClw==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.1.tgz", + "integrity": "sha512-F/tkdw0WSs4ojqz5Ovrw5r9odqzFjb5LIgHdHZG65dFI1lWTWRVy32KDJLKRISHgJvqUeUhdIvy43fX41znyDg==", "cpu": [ "arm64" ], @@ -1697,9 +1870,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.6.tgz", - "integrity": "sha512-CqNNAyhRkTbo8VVZ5R85X73H3R5NX9ONnKbXuHisGWC0qRbTTxnF1U4V9NafzJbgGM0sHZpdO83pLPzq8uOZFw==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.1.tgz", + "integrity": "sha512-vk+ma8iC1ebje/ahpxpnrfVQJibTMyHdWpOGZ3JpQ7Mgn/3QNHmPq7YwjZbIE7km73dH5M1e6MRRsnEBW7v5CQ==", "cpu": [ "arm64" ], @@ -1710,9 +1883,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.6.tgz", - "integrity": "sha512-zRDtdJuRvA1dc9Mp6BWYqAsU5oeLixdfUvkTHuiYOHwqYuQ4YgSmi6+/lPvSsqc/I0Omw3DdICx4Tfacdzmhog==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.1.tgz", + "integrity": "sha512-IgpzXKauRe1Tafcej9STjSSuG0Ghu/xGYH+qG6JwsAUxXrnkvNHcq/NL6nz1+jzvWAnQkuAJ4uIwGB48K9OCGA==", "cpu": [ "x64" ], @@ -1723,9 +1896,22 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.6.tgz", - "integrity": "sha512-oNk8YXDDnNyG4qlNb6is1ojTOGL/tRhbbKeE/YuccItzerEZT68Z9gHrY3ROh7axDc974+zYAPxK5SH0j/G+QQ==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.1.tgz", + "integrity": "sha512-P9bSiAUnSSM7EmyRK+e5wgpqai86QOSv8BwvkGjLwYuOpaeomiZWifEos517CwbG+aZl1T4clSE1YqqH2JRs+g==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.1.tgz", + "integrity": "sha512-5RnjpACoxtS+aWOI1dURKno11d7krfpGDEn19jI8BuWmSBbUC4ytIADfROM1FZrFhQPSoP+KEa3NlEScznBTyQ==", "cpu": [ "arm" ], @@ -1736,9 +1922,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.6.tgz", - "integrity": "sha512-Z3O60yxPtuCYobrtzjo0wlmvDdx2qZfeAWTyfOjEDqd08kthDKexLpV97KfAeUXPosENKd8uyJMRDfFMxcYkDQ==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.1.tgz", + "integrity": "sha512-8mwmGD668m8WaGbthrEYZ9CBmPug2QPGWxhJxh/vCgBjro5o96gL04WLlg5BA233OCWLqERy4YUzX3bJGXaJgQ==", "cpu": [ "arm64" ], @@ -1749,9 +1935,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.6.tgz", - "integrity": "sha512-gpiG0qQJNdYEVad+1iAsGAbgAnZ8j07FapmnIAQgODKcOTjLEWM9sRb+MbQyVsYCnA0Im6M6QIq6ax7liws6eQ==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.1.tgz", + "integrity": "sha512-dJX9u4r4bqInMGOAQoGYdwDP8lQiisWb9et+T84l2WXk41yEej8v2iGKodmdKimT8cTAYt0jFb+UEBxnPkbXEQ==", "cpu": [ "arm64" ], @@ -1761,10 +1947,23 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.1.tgz", + "integrity": "sha512-V72cXdTl4EI0x6FNmho4D502sy7ed+LuVW6Ym8aI6DRQ9hQZdp5sj0a2usYOlqvFBNKQnLQGwmYnujo2HvjCxQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.6.tgz", - "integrity": "sha512-+uCOcvVmFUYvVDr27aiyun9WgZk0tXe7ThuzoUTAukZJOwS5MrGbmSlNOhx1j80GdpqbOty05XqSl5w4dQvcOA==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.1.tgz", + "integrity": "sha512-f+pJih7sxoKmbjghrM2RkWo2WHUW8UbfxIQiWo5yeCaCM0TveMEuAzKJte4QskBp1TIinpnRcxkquY+4WuY/tg==", "cpu": [ "riscv64" ], @@ -1774,10 +1973,23 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.1.tgz", + "integrity": "sha512-qb1hMMT3Fr/Qz1OKovCuUM11MUNLUuHeBC2DPPAWUYYUAOFWaxInaTwTQmc7Fl5La7DShTEpmYwgdt2hG+4TEg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.6.tgz", - "integrity": "sha512-HUNqM32dGzfBKuaDUBqFB7tP6VMN74eLZ33Q9Y1TBqRDn+qDonkAUyKWwF9BR9unV7QUzffLnz9GrnKvMqC/fw==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.1.tgz", + "integrity": "sha512-7O5u/p6oKUFYjRbZkL2FLbwsyoJAjyeXHCU3O4ndvzg2OFO2GinFPSJFGbiwFDaCFc+k7gs9CF243PwdPQFh5g==", "cpu": [ "x64" ], @@ -1788,9 +2000,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.6.tgz", - "integrity": "sha512-ch7M+9Tr5R4FK40FHQk8VnML0Szi2KRujUgHXd/HjuH9ifH72GUmw6lStZBo3c3GB82vHa0ZoUfjfcM7JiiMrQ==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.1.tgz", + "integrity": "sha512-pDLkYITdYrH/9Cv/Vlj8HppDuLMDUBmgsM0+N+xLtFd18aXgM9Nyqupb/Uw+HeidhfYg2lD6CXvz6CjoVOaKjQ==", "cpu": [ "x64" ], @@ -1801,9 +2013,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.6.tgz", - "integrity": "sha512-VD6qnR99dhmTQ1mJhIzXsRcTBvTjbfbGGwKAHcu+52cVl15AC/kplkhxzW/uT0Xl62Y/meBKDZvoJSJN+vTeGA==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.1.tgz", + "integrity": "sha512-W2ZNI323O/8pJdBGil1oCauuCzmVd9lDmWBBqxYZcOqWD6aWqJtVBQ1dFrF4dYpZPks6F+xCZHfzG5hYlSHZ6g==", "cpu": [ "arm64" ], @@ -1814,9 +2026,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.6.tgz", - "integrity": "sha512-J9AFDq/xiRI58eR2NIDfyVmTYGyIZmRcvcAoJ48oDld/NTR8wyiPUu2X/v1navJ+N/FGg68LEbX3Ejd6l8B7MQ==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.1.tgz", + "integrity": "sha512-ELfEX1/+eGZYMaCIbK4jqLxO1gyTSOIlZr6pbC4SRYFaSIDVKOnZNMdoZ+ON0mrFDp4+H5MhwNC1H/AhE3zQLg==", "cpu": [ "ia32" ], @@ -1827,9 +2039,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.6.tgz", - "integrity": "sha512-jqzNLhNDvIZOrt69Ce4UjGRpXJBzhUBzawMwnaDAwyHriki3XollsewxWzOzz+4yOFDkuJHtTsZFwMxhYJWmLQ==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.1.tgz", + "integrity": "sha512-yjk2MAkQmoaPYCSu35RLJ62+dz358nE83VfTePJRp8CG7aMg25mEJYpXFiD+NcevhX8LxD5OP5tktPXnXN7GDw==", "cpu": [ "x64" ], @@ -1840,15 +2052,9 @@ ] }, "node_modules/@rushstack/eslint-patch": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.7.2.tgz", - "integrity": "sha512-RbhOOTCNoCrbfkRyoXODZp75MlpiHMgbE5MEBZAnnnLyQNgrigEj4p0lzsMDyc1zVsJDLrivB58tgg3emX0eEA==", - "dev": true - }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.3.tgz", + "integrity": "sha512-qC/xYId4NMebE6w/V33Fh9gWxLgURiNYgVNObbJl2LZv0GUUItCcCqC5axQSwRaAgaxl2mELq1rMzlswaQ0Zxg==", "dev": true }, "node_modules/@types/estree": { @@ -1858,17 +2064,17 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.11.16", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.16.tgz", - "integrity": "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ==", + "version": "20.14.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.10.tgz", + "integrity": "sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ==", "dependencies": { "undici-types": "~5.26.4" } }, "node_modules/@types/readable-stream": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-4.0.10.tgz", - "integrity": "sha512-AbUKBjcC8SHmImNi4yK2bbjogQlkFSg7shZCcicxPQapniOlajG8GCc39lvXzCWX4lLRRs7DM3VAeSlqmEVZUA==", + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-4.0.15.tgz", + "integrity": "sha512-oAZ3kw+kJFkEqyh7xORZOku1YAKvsFTogRY8kVl4vHpEKiDkfnSA/My8haRE7fvmix5Zyy+1pwzOi7yycGLBJw==", "dependencies": { "@types/node": "*", "safe-buffer": "~5.1.1" @@ -1882,16 +2088,10 @@ "@types/node": "*" } }, - "node_modules/@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true - }, "node_modules/@vitejs/plugin-vue": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.4.tgz", - "integrity": "sha512-WS3hevEszI6CEVEx28F8RjTX97k3KsrcY6kvTg7+Whm5y3oYvcqzVeGCU3hxSAn4uY2CLCkeokkGKpoctccilQ==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.5.tgz", + "integrity": "sha512-LOjm7XeIimLBZyzinBQ6OSm3UBCNVCpLkxGC0oWmm2YPzVZoxMsdvNVimLTBzpAnR9hl/yn1SHGuRfe6/Td9rQ==", "dev": true, "engines": { "node": "^18.0.0 || >=20.0.0" @@ -1902,96 +2102,81 @@ } }, "node_modules/@vitest/expect": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.2.2.tgz", - "integrity": "sha512-3jpcdPAD7LwHUUiT2pZTj2U82I2Tcgg2oVPvKxhn6mDI2On6tfvPQTjAI4628GUGDZrCm4Zna9iQHm5cEexOAg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.0.2.tgz", + "integrity": "sha512-nKAvxBYqcDugYZ4nJvnm5OR8eDJdgWjk4XM9owQKUjzW70q0icGV2HVnQOyYsp906xJaBDUXw0+9EHw2T8e0mQ==", "dev": true, "dependencies": { - "@vitest/spy": "1.2.2", - "@vitest/utils": "1.2.2", - "chai": "^4.3.10" + "@vitest/spy": "2.0.2", + "@vitest/utils": "2.0.2", + "chai": "^5.1.1", + "tinyrainbow": "^1.2.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/runner": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.2.2.tgz", - "integrity": "sha512-JctG7QZ4LSDXr5CsUweFgcpEvrcxOV1Gft7uHrvkQ+fsAVylmWQvnaAr/HDp3LAH1fztGMQZugIheTWjaGzYIg==", + "node_modules/@vitest/pretty-format": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.0.2.tgz", + "integrity": "sha512-SBCyOXfGVvddRd9r2PwoVR0fonQjh9BMIcBMlSzbcNwFfGr6ZhOhvBzurjvi2F4ryut2HcqiFhNeDVGwru8tLg==", "dev": true, "dependencies": { - "@vitest/utils": "1.2.2", - "p-limit": "^5.0.0", - "pathe": "^1.1.1" + "tinyrainbow": "^1.2.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/runner/node_modules/p-limit": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", - "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", + "node_modules/@vitest/runner": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.0.2.tgz", + "integrity": "sha512-OCh437Vi8Wdbif1e0OvQcbfM3sW4s2lpmOjAE7qfLrpzJX2M7J1IQlNvEcb/fu6kaIB9n9n35wS0G2Q3en5kHg==", "dev": true, "dependencies": { - "yocto-queue": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@vitest/runner/node_modules/yocto-queue": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", - "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", - "dev": true, - "engines": { - "node": ">=12.20" + "@vitest/utils": "2.0.2", + "pathe": "^1.1.2" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/snapshot": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.2.2.tgz", - "integrity": "sha512-SmGY4saEw1+bwE1th6S/cZmPxz/Q4JWsl7LvbQIky2tKE35US4gd0Mjzqfr84/4OD0tikGWaWdMja/nWL5NIPA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.0.2.tgz", + "integrity": "sha512-Yc2ewhhZhx+0f9cSUdfzPRcsM6PhIb+S43wxE7OG0kTxqgqzo8tHkXFuFlndXeDMp09G3sY/X5OAo/RfYydf1g==", "dev": true, "dependencies": { - "magic-string": "^0.30.5", - "pathe": "^1.1.1", - "pretty-format": "^29.7.0" + "@vitest/pretty-format": "2.0.2", + "magic-string": "^0.30.10", + "pathe": "^1.1.2" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/spy": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.2.2.tgz", - "integrity": "sha512-k9Gcahssw8d7X3pSLq3e3XEu/0L78mUkCjivUqCQeXJm9clfXR/Td8+AP+VC1O6fKPIDLcHDTAmBOINVuv6+7g==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.0.2.tgz", + "integrity": "sha512-MgwJ4AZtCgqyp2d7WcQVE8aNG5vQ9zu9qMPYQHjsld/QVsrvg78beNrXdO4HYkP0lDahCO3P4F27aagIag+SGQ==", "dev": true, "dependencies": { - "tinyspy": "^2.2.0" + "tinyspy": "^3.0.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/utils": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.2.2.tgz", - "integrity": "sha512-WKITBHLsBHlpjnDQahr+XK6RE7MiAsgrIkr0pGhQ9ygoxBfUeG0lUG5iLlzqjmKSlBv3+j5EGsriBzh+C3Tq9g==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.0.2.tgz", + "integrity": "sha512-pxCY1v7kmOCWYWjzc0zfjGTA3Wmn8PKnlPvSrsA643P1NHl1fOyXj2Q9SaNlrlFE+ivCsxM80Ov3AR82RmHCWQ==", "dev": true, "dependencies": { - "diff-sequences": "^29.6.3", + "@vitest/pretty-format": "2.0.2", "estree-walker": "^3.0.3", - "loupe": "^2.3.7", - "pretty-format": "^29.7.0" + "loupe": "^3.1.1", + "tinyrainbow": "^1.2.0" }, "funding": { "url": "https://opencollective.com/vitest" @@ -2007,55 +2192,55 @@ } }, "node_modules/@vue/compiler-core": { - "version": "3.4.19", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.19.tgz", - "integrity": "sha512-gj81785z0JNzRcU0Mq98E56e4ltO1yf8k5PQ+tV/7YHnbZkrM0fyFyuttnN8ngJZjbpofWE/m4qjKBiLl8Ju4w==", + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.31.tgz", + "integrity": "sha512-skOiodXWTV3DxfDhB4rOf3OGalpITLlgCeOwb+Y9GJpfQ8ErigdBUHomBzvG78JoVE8MJoQsb+qhZiHfKeNeEg==", "dependencies": { - "@babel/parser": "^7.23.9", - "@vue/shared": "3.4.19", + "@babel/parser": "^7.24.7", + "@vue/shared": "3.4.31", "entities": "^4.5.0", "estree-walker": "^2.0.2", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-dom": { - "version": "3.4.19", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.19.tgz", - "integrity": "sha512-vm6+cogWrshjqEHTzIDCp72DKtea8Ry/QVpQRYoyTIg9k7QZDX6D8+HGURjtmatfgM8xgCFtJJaOlCaRYRK3QA==", + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.31.tgz", + "integrity": "sha512-wK424WMXsG1IGMyDGyLqB+TbmEBFM78hIsOJ9QwUVLGrcSk0ak6zYty7Pj8ftm7nEtdU/DGQxAXp0/lM/2cEpQ==", "dependencies": { - "@vue/compiler-core": "3.4.19", - "@vue/shared": "3.4.19" + "@vue/compiler-core": "3.4.31", + "@vue/shared": "3.4.31" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.4.19", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.19.tgz", - "integrity": "sha512-LQ3U4SN0DlvV0xhr1lUsgLCYlwQfUfetyPxkKYu7dkfvx7g3ojrGAkw0AERLOKYXuAGnqFsEuytkdcComei3Yg==", - "dependencies": { - "@babel/parser": "^7.23.9", - "@vue/compiler-core": "3.4.19", - "@vue/compiler-dom": "3.4.19", - "@vue/compiler-ssr": "3.4.19", - "@vue/shared": "3.4.19", + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.31.tgz", + "integrity": "sha512-einJxqEw8IIJxzmnxmJBuK2usI+lJonl53foq+9etB2HAzlPjAS/wa7r0uUpXw5ByX3/0uswVSrjNb17vJm1kQ==", + "dependencies": { + "@babel/parser": "^7.24.7", + "@vue/compiler-core": "3.4.31", + "@vue/compiler-dom": "3.4.31", + "@vue/compiler-ssr": "3.4.31", + "@vue/shared": "3.4.31", "estree-walker": "^2.0.2", - "magic-string": "^0.30.6", - "postcss": "^8.4.33", - "source-map-js": "^1.0.2" + "magic-string": "^0.30.10", + "postcss": "^8.4.38", + "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-ssr": { - "version": "3.4.19", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.19.tgz", - "integrity": "sha512-P0PLKC4+u4OMJ8sinba/5Z/iDT84uMRRlrWzadgLA69opCpI1gG4N55qDSC+dedwq2fJtzmGald05LWR5TFfLw==", + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.31.tgz", + "integrity": "sha512-RtefmITAje3fJ8FSg1gwgDhdKhZVntIVbwupdyZDSifZTRMiWxWehAOTCc8/KZDnBOcYQ4/9VWxsTbd3wT0hAA==", "dependencies": { - "@vue/compiler-dom": "3.4.19", - "@vue/shared": "3.4.19" + "@vue/compiler-dom": "3.4.31", + "@vue/shared": "3.4.31" } }, "node_modules/@vue/devtools-api": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.5.1.tgz", - "integrity": "sha512-+KpckaAQyfbvshdDW5xQylLni1asvNSGme1JFs8I1+/H5pHEhqUKMEQD/qn3Nx5+/nycBq11qAEi8lk+LXI2dA==" + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.3.tgz", + "integrity": "sha512-0MiMsFma/HqA6g3KLKn+AGpL1kgKhFWszC9U29NfpWK5LE7bjeXxySWJrOJ77hBz+TBrBQ7o4QJqbPbqbs8rJw==" }, "node_modules/@vue/eslint-config-prettier": { "version": "9.0.0", @@ -2072,66 +2257,58 @@ } }, "node_modules/@vue/reactivity": { - "version": "3.4.19", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.19.tgz", - "integrity": "sha512-+VcwrQvLZgEclGZRHx4O2XhyEEcKaBi50WbxdVItEezUf4fqRh838Ix6amWTdX0CNb/b6t3Gkz3eOebfcSt+UA==", + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.31.tgz", + "integrity": "sha512-VGkTani8SOoVkZNds1PfJ/T1SlAIOf8E58PGAhIOUDYPC4GAmFA2u/E14TDAFcf3vVDKunc4QqCe/SHr8xC65Q==", "dependencies": { - "@vue/shared": "3.4.19" + "@vue/shared": "3.4.31" } }, "node_modules/@vue/runtime-core": { - "version": "3.4.19", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.19.tgz", - "integrity": "sha512-/Z3tFwOrerJB/oyutmJGoYbuoadphDcJAd5jOuJE86THNZji9pYjZroQ2NFsZkTxOq0GJbb+s2kxTYToDiyZzw==", + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.31.tgz", + "integrity": "sha512-LDkztxeUPazxG/p8c5JDDKPfkCDBkkiNLVNf7XZIUnJ+66GVGkP+TIh34+8LtPisZ+HMWl2zqhIw0xN5MwU1cw==", "dependencies": { - "@vue/reactivity": "3.4.19", - "@vue/shared": "3.4.19" + "@vue/reactivity": "3.4.31", + "@vue/shared": "3.4.31" } }, "node_modules/@vue/runtime-dom": { - "version": "3.4.19", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.19.tgz", - "integrity": "sha512-IyZzIDqfNCF0OyZOauL+F4yzjMPN2rPd8nhqPP2N1lBn3kYqJpPHHru+83Rkvo2lHz5mW+rEeIMEF9qY3PB94g==", + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.31.tgz", + "integrity": "sha512-2Auws3mB7+lHhTFCg8E9ZWopA6Q6L455EcU7bzcQ4x6Dn4cCPuqj6S2oBZgN2a8vJRS/LSYYxwFFq2Hlx3Fsaw==", "dependencies": { - "@vue/runtime-core": "3.4.19", - "@vue/shared": "3.4.19", + "@vue/reactivity": "3.4.31", + "@vue/runtime-core": "3.4.31", + "@vue/shared": "3.4.31", "csstype": "^3.1.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.4.19", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.19.tgz", - "integrity": "sha512-eAj2p0c429RZyyhtMRnttjcSToch+kTWxFPHlzGMkR28ZbF1PDlTcmGmlDxccBuqNd9iOQ7xPRPAGgPVj+YpQw==", + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.31.tgz", + "integrity": "sha512-D5BLbdvrlR9PE3by9GaUp1gQXlCNadIZytMIb8H2h3FMWJd4oUfkUTEH2wAr3qxoRz25uxbTcbqd3WKlm9EHQA==", "dependencies": { - "@vue/compiler-ssr": "3.4.19", - "@vue/shared": "3.4.19" + "@vue/compiler-ssr": "3.4.31", + "@vue/shared": "3.4.31" }, "peerDependencies": { - "vue": "3.4.19" + "vue": "3.4.31" } }, "node_modules/@vue/shared": { - "version": "3.4.19", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.19.tgz", - "integrity": "sha512-/KliRRHMF6LoiThEy+4c1Z4KB/gbPrGjWwJR+crg2otgrf/egKzRaCPvJ51S5oetgsgXLfc4Rm5ZgrKHZrtMSw==" + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.31.tgz", + "integrity": "sha512-Yp3wtJk//8cO4NItOPpi3QkLExAr/aLBGZMmTtW9WpdwBCJpRM6zj9WgWktXAl8IDIozwNMByT45JP3tO3ACWA==" }, "node_modules/@vue/test-utils": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.4.4.tgz", - "integrity": "sha512-8jkRxz8pNhClAf4Co4ZrpAoFISdvT3nuSkUlY6Ys6rmTpw3DMWG/X3mw3gQ7QJzgCZO9f+zuE2kW57fi09MW7Q==", + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.4.6.tgz", + "integrity": "sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==", "dev": true, "dependencies": { "js-beautify": "^1.14.9", - "vue-component-type-helpers": "^1.8.21" - }, - "peerDependencies": { - "@vue/server-renderer": "^3.0.1", - "vue": "^3.0.1" - }, - "peerDependenciesMeta": { - "@vue/server-renderer": { - "optional": true - } + "vue-component-type-helpers": "^2.0.0" } }, "node_modules/abbrev": { @@ -2155,9 +2332,9 @@ } }, "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -2175,19 +2352,10 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/acorn-walk": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", - "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/agent-base": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", - "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dev": true, "dependencies": { "debug": "^4.3.4" @@ -2256,14 +2424,13 @@ "dev": true }, "node_modules/asn1.js": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", - "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", "dependencies": { "bn.js": "^4.0.0", "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "safer-buffer": "^2.1.0" + "minimalistic-assert": "^1.0.0" } }, "node_modules/asn1.js/node_modules/bn.js": { @@ -2284,12 +2451,12 @@ } }, "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, "engines": { - "node": "*" + "node": ">=12" } }, "node_modules/asynckit": { @@ -2299,9 +2466,9 @@ "dev": true }, "node_modules/autoprefixer": { - "version": "10.4.17", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.17.tgz", - "integrity": "sha512-/cpVNRLSfhOtcGflT13P2794gVSgmPgTR+erw5ifnMLZb0UnSlkK4tquLmkd3BhA+nLo5tX8Cu0upUsGKvKbmg==", + "version": "10.4.19", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", + "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", "dev": true, "funding": [ { @@ -2318,8 +2485,8 @@ } ], "dependencies": { - "browserslist": "^4.22.2", - "caniuse-lite": "^1.0.30001578", + "browserslist": "^4.23.0", + "caniuse-lite": "^1.0.30001599", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.0.0", @@ -2336,9 +2503,12 @@ } }, "node_modules/available-typed-arrays": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.6.tgz", - "integrity": "sha512-j1QzY8iPNPG4o4xmO3ptzpRxTciqD3MgEHtifP/YnJpIo58Xu+ne4BejlbkuaLfXn/nz6HFiw29bLpj2PNMdGg==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, "engines": { "node": ">= 0.4" }, @@ -2372,19 +2542,23 @@ ] }, "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "dev": true, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/bl": { - "version": "6.0.10", - "resolved": "https://registry.npmjs.org/bl/-/bl-6.0.10.tgz", - "integrity": "sha512-F14DFhDZfxtVm2FY0k9kG2lWAwzZkO9+jX3Ytuoy/V0E1/5LBuBzzQHXAjqpxXEDIpmTPZZf5GVIGPQcLxFpaA==", + "version": "6.0.14", + "resolved": "https://registry.npmjs.org/bl/-/bl-6.0.14.tgz", + "integrity": "sha512-TJfbvGdL7KFGxTsEbsED7avqpFdY56q9IW0/aiytyheJzxST/+Io6cx/4Qx0K2/u0BPRDs65mjaQzYvMZeNocQ==", "dependencies": { + "@types/readable-stream": "^4.0.0", "buffer": "^6.0.3", "inherits": "^2.0.4", "readable-stream": "^4.2.0" @@ -2412,12 +2586,12 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -2480,37 +2654,44 @@ } }, "node_modules/browserify-sign": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.2.tgz", - "integrity": "sha512-1rudGyeYY42Dk6texmv7c4VcQ0EsvVbLwZkA+AQB7SxvXxmcD93jcHie8bzecJ+ChDlmAm2Qyu0+Ccg5uhZXCg==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.3.tgz", + "integrity": "sha512-JWCZW6SKhfhjJxO8Tyiiy+XYB7cqd2S5/+WeYHsKdNKFlCBhKbblba1A/HN/90YwtxKc8tCErjffZl++UNmGiw==", "dependencies": { "bn.js": "^5.2.1", "browserify-rsa": "^4.1.0", "create-hash": "^1.2.0", "create-hmac": "^1.1.7", - "elliptic": "^6.5.4", + "elliptic": "^6.5.5", + "hash-base": "~3.0", "inherits": "^2.0.4", - "parse-asn1": "^5.1.6", - "readable-stream": "^3.6.2", + "parse-asn1": "^5.1.7", + "readable-stream": "^2.3.8", "safe-buffer": "^5.2.1" }, "engines": { - "node": ">= 4" + "node": ">= 0.12" } }, "node_modules/browserify-sign/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, + "node_modules/browserify-sign/node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "node_modules/browserify-sign/node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -2530,6 +2711,19 @@ } ] }, + "node_modules/browserify-sign/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/browserify-sign/node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "node_modules/browserify-zlib": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", @@ -2539,9 +2733,9 @@ } }, "node_modules/browserslist": { - "version": "4.22.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.3.tgz", - "integrity": "sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A==", + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.2.tgz", + "integrity": "sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA==", "dev": true, "funding": [ { @@ -2558,10 +2752,10 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001580", - "electron-to-chromium": "^1.4.648", + "caniuse-lite": "^1.0.30001640", + "electron-to-chromium": "^1.4.820", "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" + "update-browserslist-db": "^1.1.0" }, "bin": { "browserslist": "cli.js" @@ -2618,13 +2812,18 @@ } }, "node_modules/call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2640,9 +2839,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001583", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001583.tgz", - "integrity": "sha512-acWTYaha8xfhA/Du/z4sNZjHUWjkiuoAi2LM+T/aL+kemKQgPT1xBb/YKjlQ0Qo8gvbHsGNplrEJ+9G3gL7i4Q==", + "version": "1.0.30001641", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001641.tgz", + "integrity": "sha512-Phv5thgl67bHYo1TtMY/MurjkHhV4EDaCosezRXgZ8jzA/Ub+wjxAvbGvjoFENStinwi5kCyOYV3mi5tOGykwA==", "dev": true, "funding": [ { @@ -2660,21 +2859,19 @@ ] }, "node_modules/chai": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", - "integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", + "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==", "dev": true, "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.3", - "deep-eql": "^4.1.3", - "get-func-name": "^2.0.2", - "loupe": "^2.3.6", - "pathval": "^1.1.1", - "type-detect": "^4.0.8" + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" }, "engines": { - "node": ">=4" + "node": ">=12" } }, "node_modules/chalk": { @@ -2694,28 +2891,19 @@ } }, "node_modules/check-error": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", - "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", "dev": true, - "dependencies": { - "get-func-name": "^2.0.2" - }, "engines": { - "node": "*" + "node": ">= 16" } }, "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -2728,6 +2916,9 @@ "engines": { "node": ">= 8.10.0" }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, "optionalDependencies": { "fsevents": "~2.3.2" } @@ -2850,6 +3041,11 @@ "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", "integrity": "sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==" }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, "node_modules/create-ecdh": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", @@ -2930,9 +3126,9 @@ } }, "node_modules/css-blank-pseudo": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-6.0.1.tgz", - "integrity": "sha512-goSnEITByxTzU4Oh5oJZrEWudxTqk7L6IXj1UW69pO6Hv0UdX+Vsrt02FFu5DweRh2bLu6WpX/+zsQCu5O1gKw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-6.0.2.tgz", + "integrity": "sha512-J/6m+lsqpKPqWHOifAFtKFeGLOzw3jR92rxQcwRUfA/eTuZzKfKlxOmYDx2+tqOPQAueNvBiY8WhAeHu5qNmTg==", "dev": true, "funding": [ { @@ -2955,9 +3151,9 @@ } }, "node_modules/css-has-pseudo": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-6.0.1.tgz", - "integrity": "sha512-WwoVKqNxApfEI7dWFyaHoeFCcUPD+lPyjL6lNpRUNX7IyIUuVpawOTwwA5D0ZR6V2xQZonNPVj8kEcxzEaAQfQ==", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-6.0.5.tgz", + "integrity": "sha512-ZTv6RlvJJZKp32jPYnAJVhowDCrRrHUTAxsYSuUPBEDJjzws6neMnzkRblxtgmv1RgcV5dhH2gn7E3wA9Wt6lw==", "dev": true, "funding": [ { @@ -2970,7 +3166,7 @@ } ], "dependencies": { - "@csstools/selector-specificity": "^3.0.1", + "@csstools/selector-specificity": "^3.1.1", "postcss-selector-parser": "^6.0.13", "postcss-value-parser": "^4.2.0" }, @@ -3004,9 +3200,9 @@ } }, "node_modules/cssdb": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-7.10.0.tgz", - "integrity": "sha512-yGZ5tmA57gWh/uvdQBHs45wwFY0IBh3ypABk5sEubPBPSzXzkNgsWReqx7gdx6uhC+QoFBe+V8JwBB9/hQ6cIA==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-8.1.0.tgz", + "integrity": "sha512-BQN57lfS4dYt2iL0LgyrlDbefZKEtUyrO8rbzrbGrqBk6OoyNTQLF+porY9DrpDBjLo4NEvj2IJttC7vf3x+Ew==", "dev": true, "funding": [ { @@ -3043,6 +3239,12 @@ "node": ">=18" } }, + "node_modules/cssstyle/node_modules/rrweb-cssom": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz", + "integrity": "sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==", + "dev": true + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -3062,9 +3264,9 @@ } }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "dependencies": { "ms": "2.1.2" }, @@ -3084,13 +3286,10 @@ "dev": true }, "node_modules/deep-eql": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", - "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", "dev": true, - "dependencies": { - "type-detect": "^4.0.0" - }, "engines": { "node": ">=6" } @@ -3102,16 +3301,19 @@ "dev": true }, "node_modules/define-data-property": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", - "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dependencies": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/define-properties": { @@ -3148,15 +3350,6 @@ "minimalistic-assert": "^1.0.0" } }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/diffie-hellman": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", @@ -3172,18 +3365,6 @@ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/domain-browser": { "version": "4.23.0", "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-4.23.0.tgz", @@ -3244,15 +3425,15 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.656", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.656.tgz", - "integrity": "sha512-9AQB5eFTHyR3Gvt2t/NwR0le2jBSUNwCnMbUCejFWHD+so4tH40/dRLgoE+jxlPeWS43XJewyvCv+I8LPMl49Q==", + "version": "1.4.825", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.825.tgz", + "integrity": "sha512-OCcF+LwdgFGcsYPYC5keEEFC2XT0gBhrYbeGzHCx7i9qRFbzO/AqTmc/C/1xNhJj+JA7rzlN7mpBuStshh96Cg==", "dev": true }, "node_modules/elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.5.tgz", + "integrity": "sha512-7EjbcmUm17NQFu4Pmgmq2olYMj8nwMnpcddByChSUjArp8F5DQWcIcpriwO4ZToLNAJig0yiyjswfyGNje/ixw==", "dependencies": { "bn.js": "^4.11.9", "brorand": "^1.1.0", @@ -3285,18 +3466,29 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-errors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.1.0.tgz", - "integrity": "sha512-ka/z/Hxav2YGgkzSwOp1ugbUk6fgIX5gI69PfRHCvODD+LuVOnV1jHPBWXBNPZqX0O900p2I+IdM9sEbac0BNA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "engines": { "node": ">= 0.4" } }, "node_modules/esbuild": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", - "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", "dev": true, "hasInstallScript": true, "bin": { @@ -3306,35 +3498,35 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.19.12", - "@esbuild/android-arm": "0.19.12", - "@esbuild/android-arm64": "0.19.12", - "@esbuild/android-x64": "0.19.12", - "@esbuild/darwin-arm64": "0.19.12", - "@esbuild/darwin-x64": "0.19.12", - "@esbuild/freebsd-arm64": "0.19.12", - "@esbuild/freebsd-x64": "0.19.12", - "@esbuild/linux-arm": "0.19.12", - "@esbuild/linux-arm64": "0.19.12", - "@esbuild/linux-ia32": "0.19.12", - "@esbuild/linux-loong64": "0.19.12", - "@esbuild/linux-mips64el": "0.19.12", - "@esbuild/linux-ppc64": "0.19.12", - "@esbuild/linux-riscv64": "0.19.12", - "@esbuild/linux-s390x": "0.19.12", - "@esbuild/linux-x64": "0.19.12", - "@esbuild/netbsd-x64": "0.19.12", - "@esbuild/openbsd-x64": "0.19.12", - "@esbuild/sunos-x64": "0.19.12", - "@esbuild/win32-arm64": "0.19.12", - "@esbuild/win32-ia32": "0.19.12", - "@esbuild/win32-x64": "0.19.12" + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" } }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", "dev": true, "engines": { "node": ">=6" @@ -3353,41 +3545,37 @@ } }, "node_modules/eslint": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", - "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.6.0.tgz", + "integrity": "sha512-ElQkdLMEEqQNM9Njff+2Y4q2afHk7JpkPvrd7Xh7xefwgQynqPxwf55J7di9+MEibWUGdNjFF9ITG9Pck5M84w==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.56.0", - "@humanwhocodes/config-array": "^0.11.13", + "@eslint/config-array": "^0.17.0", + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "9.6.0", "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.3.0", "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", - "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", + "eslint-scope": "^8.0.1", + "eslint-visitor-keys": "^4.0.0", + "espree": "^10.1.0", + "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", + "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", @@ -3401,10 +3589,10 @@ "eslint": "bin/eslint.js" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://eslint.org/donate" } }, "node_modules/eslint-config-prettier": { @@ -3450,75 +3638,91 @@ } }, "node_modules/eslint-plugin-vue": { - "version": "9.21.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.21.1.tgz", - "integrity": "sha512-XVtI7z39yOVBFJyi8Ljbn7kY9yHzznKXL02qQYn+ta63Iy4A9JFBw6o4OSB9hyD2++tVT+su9kQqetUyCCwhjw==", + "version": "9.27.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.27.0.tgz", + "integrity": "sha512-5Dw3yxEyuBSXTzT5/Ge1X5kIkRTQ3nvBn/VwPwInNiZBSJOO/timWMUaflONnFBzU6NhB68lxnCda7ULV5N7LA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", + "globals": "^13.24.0", "natural-compare": "^1.4.0", "nth-check": "^2.1.1", - "postcss-selector-parser": "^6.0.13", - "semver": "^7.5.4", - "vue-eslint-parser": "^9.4.2", + "postcss-selector-parser": "^6.0.15", + "semver": "^7.6.0", + "vue-eslint-parser": "^9.4.3", "xml-name-validator": "^4.0.0" }, "engines": { "node": "^14.17.0 || >=16.0.0" }, "peerDependencies": { - "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0" + "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-vue/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.1.tgz", + "integrity": "sha512-pL8XjgP4ZOmmwfFE8mEhSxA7ZY4C+LWyqjQ3o4yWkkmD0qcMT9kkW3zWHOczhWcjTSgqycYAgwSlXvZltv65og==", "dev": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", + "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", "dev": true, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz", + "integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==", "dev": true, "dependencies": { - "acorn": "^8.9.0", + "acorn": "^8.12.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" + "eslint-visitor-keys": "^4.0.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, "dependencies": { "estraverse": "^5.1.0" @@ -3647,30 +3851,30 @@ } }, "node_modules/fastq": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.0.tgz", - "integrity": "sha512-zGygtijUMT7jnk3h26kUms3BkSDp4IfIKjmnqI2tvx6nuBfiF1UqOxbnLfzdv+apBy+53oaImsKtMw/xYbW+1w==", + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", "dev": true, "dependencies": { "reusify": "^1.0.4" } }, "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, "dependencies": { - "flat-cache": "^3.0.4" + "flat-cache": "^4.0.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16.0.0" } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -3695,23 +3899,22 @@ } }, "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, "dependencies": { "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" + "keyv": "^4.5.4" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16" } }, "node_modules/flatted": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", - "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, "node_modules/for-each": { @@ -3723,9 +3926,9 @@ } }, "node_modules/foreground-child": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", - "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", + "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==", "dev": true, "dependencies": { "cross-spawn": "^7.0.0", @@ -3765,12 +3968,6 @@ "url": "https://github.com/sponsors/rawify" } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -3803,11 +4000,11 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.3.tgz", - "integrity": "sha512-JIcZczvcMVE7AUOP+X72bh8HqHBRxFdz5PDHYtNG/lE3yk9b3KZBJlwFcTyPYjg3L4RLLmZJzvjxhaZVapxFrQ==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dependencies": { - "es-errors": "^1.0.0", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", @@ -3833,23 +4030,21 @@ } }, "node_modules/glob": { - "version": "10.3.10", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", - "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, "funding": { "url": "https://github.com/sponsors/isaacs" } @@ -3876,9 +4071,9 @@ } }, "node_modules/glob/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -3891,15 +4086,12 @@ } }, "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -3916,12 +4108,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -3932,20 +4118,20 @@ } }, "node_modules/has-property-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", - "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dependencies": { - "get-intrinsic": "^1.2.2" + "es-define-property": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", "engines": { "node": ">= 0.4" }, @@ -3979,50 +4165,17 @@ } }, "node_modules/hash-base": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", - "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", + "integrity": "sha512-EeeoJKjTyt868liAlVmcv2ZsUfGHlE3Q+BICOXcZiwN3osr5Q/zFGYmTJpoIzuaSTAwndFy+GqhEwlU4L3j4Ow==", "dependencies": { - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" }, "engines": { "node": ">=4" } }, - "node_modules/hash-base/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/hash-base/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/hash.js": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", @@ -4033,9 +4186,9 @@ } }, "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dependencies": { "function-bind": "^1.1.2" }, @@ -4071,9 +4224,9 @@ } }, "node_modules/http-proxy-agent": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", - "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, "dependencies": { "agent-base": "^7.1.0", @@ -4089,9 +4242,9 @@ "integrity": "sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==" }, "node_modules/https-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", - "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", "dev": true, "dependencies": { "agent-base": "^7.0.2", @@ -4151,9 +4304,9 @@ } }, "node_modules/immutable": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.5.tgz", - "integrity": "sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.6.tgz", + "integrity": "sha512-Ju0+lEMyzMVZarkTn/gqRpdqd5dOPaz1mCZ0SH3JV6iFw81PldE/PEB1hWVEA288HPt4WXW8O7AWxB10M+03QQ==", "dev": true }, "node_modules/import-fresh": { @@ -4181,16 +4334,6 @@ "node": ">=0.8.19" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -4241,11 +4384,14 @@ } }, "node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.14.0.tgz", + "integrity": "sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==", "dependencies": { - "hasown": "^2.0.0" + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4360,6 +4506,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -4375,16 +4526,13 @@ } }, "node_modules/jackspeak": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", - "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, "dependencies": { "@isaacs/cliui": "^8.0.2" }, - "engines": { - "node": ">=14" - }, "funding": { "url": "https://github.com/sponsors/isaacs" }, @@ -4393,14 +4541,15 @@ } }, "node_modules/js-beautify": { - "version": "1.14.11", - "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.14.11.tgz", - "integrity": "sha512-rPogWqAfoYh1Ryqqh2agUpVfbxAhbjuN1SmU86dskQUKouRiggUTCO4+2ym9UPXllc2WAp0J+T5qxn7Um3lCdw==", + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.1.tgz", + "integrity": "sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA==", "dev": true, "dependencies": { "config-chain": "^1.1.13", - "editorconfig": "^1.0.3", + "editorconfig": "^1.0.4", "glob": "^10.3.3", + "js-cookie": "^3.0.5", "nopt": "^7.2.0" }, "bin": { @@ -4412,6 +4561,15 @@ "node": ">=14" } }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "dev": true, + "engines": { + "node": ">=14" + } + }, "node_modules/js-sdsl": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", @@ -4434,9 +4592,9 @@ } }, "node_modules/jsdom": { - "version": "24.0.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-24.0.0.tgz", - "integrity": "sha512-UDS2NayCvmXSXVP6mpTj+73JnNQadZlr9N68189xib2tx5Mls7swlTNao26IoHv46BZJFvXygyRtyXd1feAk1A==", + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-24.1.0.tgz", + "integrity": "sha512-6gpM7pRXCwIOKxX47cgOyvyQDN/Eh0f1MeKySBV2xGdKtqJBLj8P25eY3EVCWo2mglDDzozR2r2MW4T+JiNUZA==", "dev": true, "dependencies": { "cssstyle": "^4.0.1", @@ -4444,21 +4602,21 @@ "decimal.js": "^10.4.3", "form-data": "^4.0.0", "html-encoding-sniffer": "^4.0.0", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.2", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.4", "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.7", + "nwsapi": "^2.2.10", "parse5": "^7.1.2", - "rrweb-cssom": "^0.6.0", + "rrweb-cssom": "^0.7.0", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", - "tough-cookie": "^4.1.3", + "tough-cookie": "^4.1.4", "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^7.0.0", "whatwg-encoding": "^3.1.1", "whatwg-mimetype": "^4.0.0", "whatwg-url": "^14.0.0", - "ws": "^8.16.0", + "ws": "^8.17.0", "xml-name-validator": "^5.0.0" }, "engines": { @@ -4500,12 +4658,6 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, - "node_modules/jsonc-parser": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", - "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", - "dev": true - }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -4528,22 +4680,6 @@ "node": ">= 0.8.0" } }, - "node_modules/local-pkg": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz", - "integrity": "sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==", - "dev": true, - "dependencies": { - "mlly": "^1.4.2", - "pkg-types": "^1.0.3" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -4571,31 +4707,25 @@ "dev": true }, "node_modules/loupe": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", - "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.1.tgz", + "integrity": "sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==", "dev": true, "dependencies": { "get-func-name": "^2.0.1" } }, "node_modules/lru-cache": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", - "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", - "engines": { - "node": "14 || >=16.14" - } + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" }, "node_modules/magic-string": { - "version": "0.30.6", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.6.tgz", - "integrity": "sha512-n62qCLbPjNjyo+owKtveQxZFZTBm+Ms6YoGD23Wew6Vw337PElFNifQpknPruVRQV57kVShPnLGo9vWxVhpPvA==", + "version": "0.30.10", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz", + "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==", "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" } }, "node_modules/md5.js": { @@ -4695,30 +4825,18 @@ } }, "node_modules/minipass": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", - "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, "engines": { "node": ">=16 || 14 >=14.17" } }, - "node_modules/mlly": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.5.0.tgz", - "integrity": "sha512-NPVQvAY1xr1QoVeG0cy8yUYC7FQcOx6evl/RjT1wL5FvzPnzOysoqB/jmx/DhssT2dYa8nxECLAaFI/+gVLhDQ==", - "dev": true, - "dependencies": { - "acorn": "^8.11.3", - "pathe": "^1.1.2", - "pkg-types": "^1.0.3", - "ufo": "^1.3.2" - } - }, "node_modules/mqtt": { - "version": "5.3.5", - "resolved": "https://registry.npmjs.org/mqtt/-/mqtt-5.3.5.tgz", - "integrity": "sha512-xd7qt/LEM721U6yHQcqjlaAKXL1Fsqf/MXq6C2WPi/6OXK2jdSzL1eZ7ZUgjea7IY2yFLRWD5LNdT1mL0arPoA==", + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/mqtt/-/mqtt-5.8.0.tgz", + "integrity": "sha512-/+H04mv6goy6K5gHMNH3uS0icBzXapS+4uUf4yZyQWXi72APPZNb81bQhvkm99poEQettXVT8XETB0mPxl5Wjg==", "dependencies": { "@types/readable-stream": "^4.0.5", "@types/ws": "^8.5.9", @@ -4735,8 +4853,8 @@ "reinterval": "^1.1.0", "rfdc": "^1.3.0", "split2": "^4.2.0", - "worker-timers": "^7.0.78", - "ws": "^8.14.2" + "worker-timers": "^7.1.4", + "ws": "^8.17.1" }, "bin": { "mqtt": "build/bin/mqtt.js", @@ -4865,9 +4983,9 @@ } }, "node_modules/nopt": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.0.tgz", - "integrity": "sha512-CVDtwCdhYIvnAzFoJ6NJ6dX3oga9/HyciQDnG1vQDjSLMeKLJ4A93ZqYKDrgYSr1FBY5/hMYC+2VCi24pgpkGA==", + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", "dev": true, "dependencies": { "abbrev": "^2.0.0" @@ -4898,9 +5016,9 @@ } }, "node_modules/npm-run-path": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.2.0.tgz", - "integrity": "sha512-W4/tgAXFqFA0iL7fk0+uQ3g7wkL8xJmx3XdK0VGb4cHW//eZTtKGvFBBoRKVTpY7n6ze4NL9ly7rgXcHufqXKg==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", "dev": true, "dependencies": { "path-key": "^4.0.0" @@ -4946,26 +5064,29 @@ } }, "node_modules/nwsapi": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz", - "integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==", + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.12.tgz", + "integrity": "sha512-qXDmcVlZV4XRtKFzddidpfVP4oMSGhga+xdMc25mv8kaLUHtgzCDhUxkrN8exkGdTlLNaXj7CV3GtON7zuGZ+w==", "dev": true }, "node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/object-is": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" }, "engines": { "node": ">= 0.4" @@ -4999,15 +5120,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, "node_modules/onetime": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", @@ -5024,17 +5136,17 @@ } }, "node_modules/optionator": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", - "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, "dependencies": { - "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" }, "engines": { "node": ">= 0.8.0" @@ -5073,6 +5185,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", + "dev": true + }, "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -5091,17 +5209,40 @@ } }, "node_modules/parse-asn1": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", - "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.7.tgz", + "integrity": "sha512-CTM5kuWR3sx9IFamcl5ErfPl6ea/N8IYwiJ+vpeB2g+1iknv7zBl5uPwbMbRVznRVbrNY6lGuDoE5b30grmbqg==", "dependencies": { - "asn1.js": "^5.2.0", - "browserify-aes": "^1.0.0", - "evp_bytestokey": "^1.0.0", - "pbkdf2": "^3.0.3", - "safe-buffer": "^5.1.1" + "asn1.js": "^4.10.1", + "browserify-aes": "^1.2.0", + "evp_bytestokey": "^1.0.3", + "hash-base": "~3.0", + "pbkdf2": "^3.1.2", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" } }, + "node_modules/parse-asn1/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/parse5": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", @@ -5127,15 +5268,6 @@ "node": ">=8" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -5151,16 +5283,16 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/path-scurry": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", - "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, "dependencies": { - "lru-cache": "^9.1.1 || ^10.0.0", + "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=16 || 14 >=14.18" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -5173,12 +5305,12 @@ "dev": true }, "node_modules/pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", "dev": true, "engines": { - "node": "*" + "node": ">= 14.16" } }, "node_modules/pbkdf2": { @@ -5197,9 +5329,9 @@ } }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -5239,9 +5371,9 @@ } }, "node_modules/pinia/node_modules/vue-demi": { - "version": "0.14.7", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz", - "integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==", + "version": "0.14.8", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.8.tgz", + "integrity": "sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==", "hasInstallScript": true, "bin": { "vue-demi-fix": "bin/vue-demi-fix.js", @@ -5274,21 +5406,18 @@ "node": ">=10" } }, - "node_modules/pkg-types": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz", - "integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==", - "dev": true, - "dependencies": { - "jsonc-parser": "^3.2.0", - "mlly": "^1.2.0", - "pathe": "^1.1.0" + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "engines": { + "node": ">= 0.4" } }, "node_modules/postcss": { - "version": "8.4.35", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", - "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", + "version": "8.4.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.39.tgz", + "integrity": "sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==", "funding": [ { "type": "opencollective", @@ -5305,28 +5434,34 @@ ], "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "picocolors": "^1.0.1", + "source-map-js": "^1.2.0" }, "engines": { "node": "^10 || ^12 || >=14" } }, "node_modules/postcss-attribute-case-insensitive": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-6.0.2.tgz", - "integrity": "sha512-IRuCwwAAQbgaLhxQdQcIIK0dCVXg3XDUnzgKD8iwdiYdwU4rMWRWyl/W9/0nA4ihVpq5pyALiHB2veBJ0292pw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-6.0.3.tgz", + "integrity": "sha512-KHkmCILThWBRtg+Jn1owTnHPnFit4OkqS+eKiGEOPIGke54DCeYGJ6r0Fx/HjfE9M9kznApCLcU0DvnPchazMQ==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "dependencies": { - "postcss-selector-parser": "^6.0.10" + "postcss-selector-parser": "^6.0.13" }, "engines": { "node": "^14 || ^16 || >=18" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, "peerDependencies": { "postcss": "^8.4" } @@ -5347,9 +5482,9 @@ } }, "node_modules/postcss-color-functional-notation": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-6.0.4.tgz", - "integrity": "sha512-YBzfVvVUNR4U3N0imzU1NPKCuwxzfHJkEP6imJxzsJ8LozRKeej9mWmg9Ef1ovJdb0xrGTRVzUxgTrMun5iw/Q==", + "version": "6.0.14", + "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-6.0.14.tgz", + "integrity": "sha512-dNUX+UH4dAozZ8uMHZ3CtCNYw8fyFAmqqdcyxMr7PEdM9jLXV19YscoYO0F25KqZYhmtWKQ+4tKrIZQrwzwg7A==", "dev": true, "funding": [ { @@ -5362,10 +5497,11 @@ } ], "dependencies": { - "@csstools/css-color-parser": "^1.5.1", - "@csstools/css-parser-algorithms": "^2.5.0", - "@csstools/css-tokenizer": "^2.2.3", - "@csstools/postcss-progressive-custom-properties": "^3.0.3" + "@csstools/css-color-parser": "^2.0.4", + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1", + "@csstools/postcss-progressive-custom-properties": "^3.3.0", + "@csstools/utilities": "^1.0.0" }, "engines": { "node": "^14 || ^16 || >=18" @@ -5375,9 +5511,9 @@ } }, "node_modules/postcss-color-hex-alpha": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-9.0.3.tgz", - "integrity": "sha512-7sEHU4tAS6htlxun8AB9LDrCXoljxaC34tFVRlYKcvO+18r5fvGiXgv5bQzN40+4gXLCyWSMRK5FK31244WcCA==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-9.0.4.tgz", + "integrity": "sha512-XQZm4q4fNFqVCYMGPiBjcqDhuG7Ey2xrl99AnDJMyr5eDASsAGalndVgHZF8i97VFNy1GQeZc4q2ydagGmhelQ==", "dev": true, "funding": [ { @@ -5390,6 +5526,7 @@ } ], "dependencies": { + "@csstools/utilities": "^1.0.0", "postcss-value-parser": "^4.2.0" }, "engines": { @@ -5400,9 +5537,9 @@ } }, "node_modules/postcss-color-rebeccapurple": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-9.0.2.tgz", - "integrity": "sha512-f+RDEAPW2m8UbJWkSpRfV+QxhSaQhDMihI75DVGJJh4oRIoegjheeRtINFJum9D8BqGJcvD4GLjggTvCwZ4zuA==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-9.0.3.tgz", + "integrity": "sha512-ruBqzEFDYHrcVq3FnW3XHgwRqVMrtEPLBtD7K2YmsLKVc2jbkxzzNEctJKsPCpDZ+LeMHLKRDoSShVefGc+CkQ==", "dev": true, "funding": [ { @@ -5415,6 +5552,7 @@ } ], "dependencies": { + "@csstools/utilities": "^1.0.0", "postcss-value-parser": "^4.2.0" }, "engines": { @@ -5425,9 +5563,9 @@ } }, "node_modules/postcss-custom-media": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-10.0.2.tgz", - "integrity": "sha512-zcEFNRmDm2fZvTPdI1pIW3W//UruMcLosmMiCdpQnrCsTRzWlKQPYMa1ud9auL0BmrryKK1+JjIGn19K0UjO/w==", + "version": "10.0.8", + "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-10.0.8.tgz", + "integrity": "sha512-V1KgPcmvlGdxTel4/CyQtBJEFhMVpEmRGFrnVtgfGIHj5PJX9vO36eFBxKBeJn+aCDTed70cc+98Mz3J/uVdGQ==", "dev": true, "funding": [ { @@ -5440,10 +5578,10 @@ } ], "dependencies": { - "@csstools/cascade-layer-name-parser": "^1.0.5", - "@csstools/css-parser-algorithms": "^2.3.2", - "@csstools/css-tokenizer": "^2.2.1", - "@csstools/media-query-list-parser": "^2.1.5" + "@csstools/cascade-layer-name-parser": "^1.0.13", + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1", + "@csstools/media-query-list-parser": "^2.1.13" }, "engines": { "node": "^14 || ^16 || >=18" @@ -5453,9 +5591,9 @@ } }, "node_modules/postcss-custom-properties": { - "version": "13.3.4", - "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-13.3.4.tgz", - "integrity": "sha512-9YN0gg9sG3OH+Z9xBrp2PWRb+O4msw+5Sbp3ZgqrblrwKspXVQe5zr5sVqi43gJGwW/Rv1A483PRQUzQOEewvA==", + "version": "13.3.12", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-13.3.12.tgz", + "integrity": "sha512-oPn/OVqONB2ZLNqN185LDyaVByELAA/u3l2CS2TS16x2j2XsmV4kd8U49+TMxmUsEU9d8fB/I10E6U7kB0L1BA==", "dev": true, "funding": [ { @@ -5468,9 +5606,10 @@ } ], "dependencies": { - "@csstools/cascade-layer-name-parser": "^1.0.7", - "@csstools/css-parser-algorithms": "^2.5.0", - "@csstools/css-tokenizer": "^2.2.3", + "@csstools/cascade-layer-name-parser": "^1.0.13", + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1", + "@csstools/utilities": "^1.0.0", "postcss-value-parser": "^4.2.0" }, "engines": { @@ -5481,9 +5620,9 @@ } }, "node_modules/postcss-custom-selectors": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-7.1.6.tgz", - "integrity": "sha512-svsjWRaxqL3vAzv71dV0/65P24/FB8TbPX+lWyyf9SZ7aZm4S4NhCn7N3Bg+Z5sZunG3FS8xQ80LrCU9hb37cw==", + "version": "7.1.12", + "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-7.1.12.tgz", + "integrity": "sha512-ctIoprBMJwByYMGjXG0F7IT2iMF2hnamQ+aWZETyBM0aAlyaYdVZTeUkk8RB+9h9wP+NdN3f01lfvKl2ZSqC0g==", "dev": true, "funding": [ { @@ -5496,10 +5635,10 @@ } ], "dependencies": { - "@csstools/cascade-layer-name-parser": "^1.0.5", - "@csstools/css-parser-algorithms": "^2.3.2", - "@csstools/css-tokenizer": "^2.2.1", - "postcss-selector-parser": "^6.0.13" + "@csstools/cascade-layer-name-parser": "^1.0.13", + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1", + "postcss-selector-parser": "^6.1.0" }, "engines": { "node": "^14 || ^16 || >=18" @@ -5534,9 +5673,9 @@ } }, "node_modules/postcss-double-position-gradients": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-5.0.3.tgz", - "integrity": "sha512-QKYpwmaSm6HcdS0ndAuWSNNMv78R1oSySoh3mYBmctHWr2KWcwPJVakdOyU4lvFVW0GRu9wfIQwGeM4p3xU9ow==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-5.0.7.tgz", + "integrity": "sha512-1xEhjV9u1s4l3iP5lRt1zvMjI/ya8492o9l/ivcxHhkO3nOz16moC4JpMxDUGrOs4R3hX+KWT7gKoV842cwRgg==", "dev": true, "funding": [ { @@ -5549,7 +5688,8 @@ } ], "dependencies": { - "@csstools/postcss-progressive-custom-properties": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^3.3.0", + "@csstools/utilities": "^1.0.0", "postcss-value-parser": "^4.2.0" }, "engines": { @@ -5641,9 +5781,9 @@ } }, "node_modules/postcss-image-set-function": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-6.0.2.tgz", - "integrity": "sha512-/O1xwqpJiz/apxGQi7UUfv1xUcorvkHZfvCYHPpRxxZj2WvjD0rg0+/+c+u5/Do5CpUg3XvfYxMrhcnjW1ArDQ==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-6.0.3.tgz", + "integrity": "sha512-i2bXrBYzfbRzFnm+pVuxVePSTCRiNmlfssGI4H0tJQvDue+yywXwUxe68VyzXs7cGtMaH6MCLY6IbCShrSroCw==", "dev": true, "funding": [ { @@ -5656,6 +5796,7 @@ } ], "dependencies": { + "@csstools/utilities": "^1.0.0", "postcss-value-parser": "^4.2.0" }, "engines": { @@ -5666,9 +5807,9 @@ } }, "node_modules/postcss-lab-function": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-6.0.9.tgz", - "integrity": "sha512-PKFAVTBEWJYsoSTD7Kp/OzeiMsXaLX39Pv75XgUyF5VrbMfeTw+JqCGsvDP3dPhclh6BemdCFHcjXBG9gO4UCg==", + "version": "6.0.19", + "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-6.0.19.tgz", + "integrity": "sha512-vwln/mgvFrotJuGV8GFhpAOu9iGf3pvTBr6dLPDmUcqVD5OsQpEFyQMAFTxSxWXGEzBj6ld4pZ/9GDfEpXvo0g==", "dev": true, "funding": [ { @@ -5681,10 +5822,11 @@ } ], "dependencies": { - "@csstools/css-color-parser": "^1.5.1", - "@csstools/css-parser-algorithms": "^2.5.0", - "@csstools/css-tokenizer": "^2.2.3", - "@csstools/postcss-progressive-custom-properties": "^3.0.3" + "@csstools/css-color-parser": "^2.0.4", + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1", + "@csstools/postcss-progressive-custom-properties": "^3.3.0", + "@csstools/utilities": "^1.0.0" }, "engines": { "node": "^14 || ^16 || >=18" @@ -5719,9 +5861,9 @@ } }, "node_modules/postcss-nesting": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-12.0.2.tgz", - "integrity": "sha512-63PpJHSeNs93S3ZUIyi+7kKx4JqOIEJ6QYtG3x+0qA4J03+4n0iwsyA1GAHyWxsHYljQS4/4ZK1o2sMi70b5wQ==", + "version": "12.1.5", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-12.1.5.tgz", + "integrity": "sha512-N1NgI1PDCiAGWPTYrwqm8wpjv0bgDmkYHH72pNsqTCv9CObxjxftdYu6AKtGN+pnJa7FQjMm3v4sp8QJbFsYdQ==", "dev": true, "funding": [ { @@ -5734,8 +5876,9 @@ } ], "dependencies": { - "@csstools/selector-specificity": "^3.0.1", - "postcss-selector-parser": "^6.0.13" + "@csstools/selector-resolve-nested": "^1.1.0", + "@csstools/selector-specificity": "^3.1.1", + "postcss-selector-parser": "^6.1.0" }, "engines": { "node": "^14 || ^16 || >=18" @@ -5826,9 +5969,9 @@ } }, "node_modules/postcss-preset-env": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-9.3.0.tgz", - "integrity": "sha512-ycw6doPrqV6QxDCtgiyGDef61bEfiSc59HGM4gOw/wxQxmKnhuEery61oOC/5ViENz/ycpRsuhTexs1kUBTvVw==", + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-9.6.0.tgz", + "integrity": "sha512-Lxfk4RYjUdwPCYkc321QMdgtdCP34AeI94z+/8kVmqnTIlD4bMRQeGcMZgwz8BxHrzQiFXYIR5d7k/9JMs2MEA==", "dev": true, "funding": [ { @@ -5841,66 +5984,67 @@ } ], "dependencies": { - "@csstools/postcss-cascade-layers": "^4.0.1", - "@csstools/postcss-color-function": "^3.0.7", - "@csstools/postcss-color-mix-function": "^2.0.7", - "@csstools/postcss-exponential-functions": "^1.0.1", - "@csstools/postcss-font-format-keywords": "^3.0.0", - "@csstools/postcss-gamut-mapping": "^1.0.0", - "@csstools/postcss-gradients-interpolation-method": "^4.0.7", - "@csstools/postcss-hwb-function": "^3.0.6", - "@csstools/postcss-ic-unit": "^3.0.2", - "@csstools/postcss-initial": "^1.0.0", - "@csstools/postcss-is-pseudo-class": "^4.0.3", - "@csstools/postcss-logical-float-and-clear": "^2.0.0", - "@csstools/postcss-logical-overflow": "^1.0.0", - "@csstools/postcss-logical-overscroll-behavior": "^1.0.0", - "@csstools/postcss-logical-resize": "^2.0.0", - "@csstools/postcss-logical-viewport-units": "^2.0.3", - "@csstools/postcss-media-minmax": "^1.1.0", - "@csstools/postcss-media-queries-aspect-ratio-number-values": "^2.0.3", - "@csstools/postcss-nested-calc": "^3.0.0", - "@csstools/postcss-normalize-display-values": "^3.0.1", - "@csstools/postcss-oklab-function": "^3.0.7", - "@csstools/postcss-progressive-custom-properties": "^3.0.2", - "@csstools/postcss-relative-color-syntax": "^2.0.7", - "@csstools/postcss-scope-pseudo-class": "^3.0.0", - "@csstools/postcss-stepped-value-functions": "^3.0.2", - "@csstools/postcss-text-decoration-shorthand": "^3.0.3", - "@csstools/postcss-trigonometric-functions": "^3.0.2", - "@csstools/postcss-unset-value": "^3.0.0", - "autoprefixer": "^10.4.16", - "browserslist": "^4.22.1", - "css-blank-pseudo": "^6.0.0", - "css-has-pseudo": "^6.0.0", - "css-prefers-color-scheme": "^9.0.0", - "cssdb": "^7.9.0", - "postcss-attribute-case-insensitive": "^6.0.2", + "@csstools/postcss-cascade-layers": "^4.0.6", + "@csstools/postcss-color-function": "^3.0.19", + "@csstools/postcss-color-mix-function": "^2.0.19", + "@csstools/postcss-content-alt-text": "^1.0.0", + "@csstools/postcss-exponential-functions": "^1.0.9", + "@csstools/postcss-font-format-keywords": "^3.0.2", + "@csstools/postcss-gamut-mapping": "^1.0.11", + "@csstools/postcss-gradients-interpolation-method": "^4.0.20", + "@csstools/postcss-hwb-function": "^3.0.18", + "@csstools/postcss-ic-unit": "^3.0.7", + "@csstools/postcss-initial": "^1.0.1", + "@csstools/postcss-is-pseudo-class": "^4.0.8", + "@csstools/postcss-light-dark-function": "^1.0.8", + "@csstools/postcss-logical-float-and-clear": "^2.0.1", + "@csstools/postcss-logical-overflow": "^1.0.1", + "@csstools/postcss-logical-overscroll-behavior": "^1.0.1", + "@csstools/postcss-logical-resize": "^2.0.1", + "@csstools/postcss-logical-viewport-units": "^2.0.11", + "@csstools/postcss-media-minmax": "^1.1.8", + "@csstools/postcss-media-queries-aspect-ratio-number-values": "^2.0.11", + "@csstools/postcss-nested-calc": "^3.0.2", + "@csstools/postcss-normalize-display-values": "^3.0.2", + "@csstools/postcss-oklab-function": "^3.0.19", + "@csstools/postcss-progressive-custom-properties": "^3.3.0", + "@csstools/postcss-relative-color-syntax": "^2.0.19", + "@csstools/postcss-scope-pseudo-class": "^3.0.1", + "@csstools/postcss-stepped-value-functions": "^3.0.10", + "@csstools/postcss-text-decoration-shorthand": "^3.0.7", + "@csstools/postcss-trigonometric-functions": "^3.0.10", + "@csstools/postcss-unset-value": "^3.0.1", + "autoprefixer": "^10.4.19", + "browserslist": "^4.23.1", + "css-blank-pseudo": "^6.0.2", + "css-has-pseudo": "^6.0.5", + "css-prefers-color-scheme": "^9.0.1", + "cssdb": "^8.1.0", + "postcss-attribute-case-insensitive": "^6.0.3", "postcss-clamp": "^4.1.0", - "postcss-color-functional-notation": "^6.0.2", - "postcss-color-hex-alpha": "^9.0.2", - "postcss-color-rebeccapurple": "^9.0.1", - "postcss-custom-media": "^10.0.2", - "postcss-custom-properties": "^13.3.2", - "postcss-custom-selectors": "^7.1.6", - "postcss-dir-pseudo-class": "^8.0.0", - "postcss-double-position-gradients": "^5.0.2", - "postcss-focus-visible": "^9.0.0", - "postcss-focus-within": "^8.0.0", + "postcss-color-functional-notation": "^6.0.14", + "postcss-color-hex-alpha": "^9.0.4", + "postcss-color-rebeccapurple": "^9.0.3", + "postcss-custom-media": "^10.0.8", + "postcss-custom-properties": "^13.3.12", + "postcss-custom-selectors": "^7.1.12", + "postcss-dir-pseudo-class": "^8.0.1", + "postcss-double-position-gradients": "^5.0.7", + "postcss-focus-visible": "^9.0.1", + "postcss-focus-within": "^8.0.1", "postcss-font-variant": "^5.0.0", - "postcss-gap-properties": "^5.0.0", - "postcss-image-set-function": "^6.0.1", - "postcss-lab-function": "^6.0.7", - "postcss-logical": "^7.0.0", - "postcss-nesting": "^12.0.1", + "postcss-gap-properties": "^5.0.1", + "postcss-image-set-function": "^6.0.3", + "postcss-lab-function": "^6.0.19", + "postcss-logical": "^7.0.1", + "postcss-nesting": "^12.1.5", "postcss-opacity-percentage": "^2.0.0", - "postcss-overflow-shorthand": "^5.0.0", + "postcss-overflow-shorthand": "^5.0.1", "postcss-page-break": "^3.0.4", - "postcss-place": "^9.0.0", - "postcss-pseudo-class-any-link": "^9.0.0", + "postcss-place": "^9.0.1", + "postcss-pseudo-class-any-link": "^9.0.2", "postcss-replace-overflow-wrap": "^4.0.0", - "postcss-selector-not": "^7.0.1", - "postcss-value-parser": "^4.2.0" + "postcss-selector-not": "^7.0.2" }, "engines": { "node": "^14 || ^16 || >=18" @@ -5910,9 +6054,9 @@ } }, "node_modules/postcss-pseudo-class-any-link": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-9.0.1.tgz", - "integrity": "sha512-cKYGGZ9yzUZi+dZd7XT2M8iSDfo+T2Ctbpiizf89uBTBfIpZpjvTavzIJXpCReMVXSKROqzpxClNu6fz4DHM0Q==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-9.0.2.tgz", + "integrity": "sha512-HFSsxIqQ9nA27ahyfH37cRWGk3SYyQLpk0LiWw/UGMV4VKT5YG2ONee4Pz/oFesnK0dn2AjcyequDbIjKJgB0g==", "dev": true, "funding": [ { @@ -5944,28 +6088,34 @@ } }, "node_modules/postcss-selector-not": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-7.0.1.tgz", - "integrity": "sha512-1zT5C27b/zeJhchN7fP0kBr16Cc61mu7Si9uWWLoA3Px/D9tIJPKchJCkUH3tPO5D0pCFmGeApAv8XpXBQJ8SQ==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-7.0.2.tgz", + "integrity": "sha512-/SSxf/90Obye49VZIfc0ls4H0P6i6V1iHv0pzZH8SdgvZOPFkF37ef1r5cyWcMflJSFJ5bfuoluTnFnBBFiuSA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "dependencies": { - "postcss-selector-parser": "^6.0.10" + "postcss-selector-parser": "^6.0.13" }, "engines": { "node": "^14 || ^16 || >=18" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, "peerDependencies": { "postcss": "^8.4" } }, "node_modules/postcss-selector-parser": { - "version": "6.0.15", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz", - "integrity": "sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.0.tgz", + "integrity": "sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ==", "dev": true, "dependencies": { "cssesc": "^3.0.0", @@ -5991,9 +6141,9 @@ } }, "node_modules/prettier": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", - "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz", + "integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -6017,32 +6167,6 @@ "node": ">=6.0.0" } }, - "node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -6092,11 +6216,11 @@ "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==" }, "node_modules/qs": { - "version": "6.11.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", - "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", + "version": "6.12.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.3.tgz", + "integrity": "sha512-AWJm14H1vVaO/iNZ4/hO+HyaTehuy9nRqVdkTqlJt0HWvBiBIEXFmb4C0DGeYo3Xes9rrEW+TxHsaigCbN5ICQ==", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -6156,12 +6280,6 @@ "safe-buffer": "^5.1.0" } }, - "node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, "node_modules/readable-stream": { "version": "4.5.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", @@ -6241,44 +6359,9 @@ } }, "node_modules/rfdc": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz", - "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==" - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==" }, "node_modules/ripemd160": { "version": "2.0.2", @@ -6290,9 +6373,9 @@ } }, "node_modules/rollup": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.6.tgz", - "integrity": "sha512-05lzkCS2uASX0CiLFybYfVkwNbKZG5NFQ6Go0VWyogFTXXbR039UVsegViTntkk4OglHBdF54ccApXRRuXRbsg==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.1.tgz", + "integrity": "sha512-Elx2UT8lzxxOXMpy5HWQGZqkrQOtrVDDa/bm9l10+U4rQnVzbL/LgZ4NOM1MPIDyHk69W4InuYDF5dzRh4Kw1A==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -6305,19 +6388,22 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.9.6", - "@rollup/rollup-android-arm64": "4.9.6", - "@rollup/rollup-darwin-arm64": "4.9.6", - "@rollup/rollup-darwin-x64": "4.9.6", - "@rollup/rollup-linux-arm-gnueabihf": "4.9.6", - "@rollup/rollup-linux-arm64-gnu": "4.9.6", - "@rollup/rollup-linux-arm64-musl": "4.9.6", - "@rollup/rollup-linux-riscv64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-musl": "4.9.6", - "@rollup/rollup-win32-arm64-msvc": "4.9.6", - "@rollup/rollup-win32-ia32-msvc": "4.9.6", - "@rollup/rollup-win32-x64-msvc": "4.9.6", + "@rollup/rollup-android-arm-eabi": "4.18.1", + "@rollup/rollup-android-arm64": "4.18.1", + "@rollup/rollup-darwin-arm64": "4.18.1", + "@rollup/rollup-darwin-x64": "4.18.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.18.1", + "@rollup/rollup-linux-arm-musleabihf": "4.18.1", + "@rollup/rollup-linux-arm64-gnu": "4.18.1", + "@rollup/rollup-linux-arm64-musl": "4.18.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.18.1", + "@rollup/rollup-linux-riscv64-gnu": "4.18.1", + "@rollup/rollup-linux-s390x-gnu": "4.18.1", + "@rollup/rollup-linux-x64-gnu": "4.18.1", + "@rollup/rollup-linux-x64-musl": "4.18.1", + "@rollup/rollup-win32-arm64-msvc": "4.18.1", + "@rollup/rollup-win32-ia32-msvc": "4.18.1", + "@rollup/rollup-win32-x64-msvc": "4.18.1", "fsevents": "~2.3.2" } }, @@ -6334,9 +6420,9 @@ } }, "node_modules/rrweb-cssom": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz", - "integrity": "sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", + "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==", "dev": true }, "node_modules/run-parallel": { @@ -6370,12 +6456,13 @@ "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true }, "node_modules/sass": { - "version": "1.70.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.70.0.tgz", - "integrity": "sha512-uUxNQ3zAHeAx5nRFskBnrWzDUJrrvpCPD5FNAoRvTi0WwremlheES3tg+56PaVtCs5QDRX5CBLxxKMDJMEa1WQ==", + "version": "1.77.7", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.7.tgz", + "integrity": "sha512-9ywH75cO+rLjbrZ6en3Gp8qAMwPGBapFtlsMJoDTkcMU/bSe5a6cjKVUn5Jr4Gzg5GbP3HE8cm+02pLCgcoMow==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -6402,13 +6489,10 @@ } }, "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, "bin": { "semver": "bin/semver.js" }, @@ -6416,28 +6500,17 @@ "node": ">=10" } }, - "node_modules/semver/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/set-function-length": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.0.tgz", - "integrity": "sha512-4DBHDoyHlM1IRPGYcoxexgh67y4ueR53FKV1yyxwFMY7aCqcN/38M1+SwZ/qJQ8iLv7+ck385ot4CcisOAPT9w==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "dependencies": { - "define-data-property": "^1.1.1", + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.2", + "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.1" + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -6482,13 +6555,17 @@ } }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6513,9 +6590,9 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "engines": { "node": ">=0.10.0" } @@ -6727,18 +6804,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strip-literal": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.3.0.tgz", - "integrity": "sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==", - "dev": true, - "dependencies": { - "acorn": "^8.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -6802,24 +6867,33 @@ } }, "node_modules/tinybench": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.6.0.tgz", - "integrity": "sha512-N8hW3PG/3aOoZAN5V/NSAEDz0ZixDSSt5b/a05iqtpgfLWMSVuCo7w0k2vVvEjdrIoeGqZzweX2WlyioNIHchA==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.8.0.tgz", + "integrity": "sha512-1/eK7zUnIklz4JUUlL+658n58XO2hHLQfSk1Zf2LKieUjxidN16eKFEoDEfjHc3ohofSSqK3X5yO6VGb6iW8Lw==", "dev": true }, "node_modules/tinypool": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.2.tgz", - "integrity": "sha512-SUszKYe5wgsxnNOVlBYO6IC+8VGWdVGZWAqUxp3UErNBtptZvWbwyUOyzNL59zigz2rCA92QiL3wvG+JDSdJdQ==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.0.tgz", + "integrity": "sha512-KIKExllK7jp3uvrNtvRBYBWBOAXSX8ZvoaD8T+7KB/QHIuoJW3Pmr60zucywjAlMb5TeXUkcs/MWeWLu0qvuAQ==", + "dev": true, + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", "dev": true, "engines": { "node": ">=14.0.0" } }, "node_modules/tinyspy": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.0.tgz", - "integrity": "sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.0.tgz", + "integrity": "sha512-q5nmENpTHgiPVd1cJDDc9cVoYN5x4vCvwT3FMilvKPKneCBZAxn2YWQjDF0UMcE9k0Cay1gBiDfTMU0g+mPMQA==", "dev": true, "engines": { "node": ">=14.0.0" @@ -6838,9 +6912,9 @@ } }, "node_modules/tough-cookie": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", - "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", "dev": true, "dependencies": { "psl": "^1.1.33", @@ -6883,9 +6957,9 @@ } }, "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" }, "node_modules/tty-browserify": { "version": "0.0.1", @@ -6904,15 +6978,6 @@ "node": ">= 0.8.0" } }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -6930,12 +6995,6 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" }, - "node_modules/ufo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.3.2.tgz", - "integrity": "sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==", - "dev": true - }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", @@ -6951,9 +7010,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", + "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", "dev": true, "funding": [ { @@ -6970,8 +7029,8 @@ } ], "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.1.2", + "picocolors": "^1.0.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -7035,14 +7094,14 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/vite": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz", - "integrity": "sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.3.tgz", + "integrity": "sha512-NPQdeCU0Dv2z5fu+ULotpuq5yfCS1BzKUIPhNbP3YBfAMGJXbt2nS+sbTFu+qchaqWTD+H3JK++nRwr6XIcp6A==", "dev": true, "dependencies": { - "esbuild": "^0.19.3", - "postcss": "^8.4.32", - "rollup": "^4.2.0" + "esbuild": "^0.21.3", + "postcss": "^8.4.39", + "rollup": "^4.13.0" }, "bin": { "vite": "bin/vite.js" @@ -7090,15 +7149,15 @@ } }, "node_modules/vite-node": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.2.2.tgz", - "integrity": "sha512-1as4rDTgVWJO3n1uHmUYqq7nsFgINQ9u+mRcXpjeOMJUmviqNKjcZB7UfRZrlM7MjYXMKpuWp5oGkjaFLnjawg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.0.2.tgz", + "integrity": "sha512-w4vkSz1Wo+NIQg8pjlEn0jQbcM/0D+xVaYjhw3cvarTanLLBh54oNiRbsT8PNK5GfuST0IlVXjsNRoNlqvY/fw==", "dev": true, "dependencies": { "cac": "^6.7.14", - "debug": "^4.3.4", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", + "debug": "^4.3.5", + "pathe": "^1.1.2", + "tinyrainbow": "^1.2.0", "vite": "^5.0.0" }, "bin": { @@ -7112,9 +7171,9 @@ } }, "node_modules/vite-plugin-node-polyfills": { - "version": "0.21.0", - "resolved": "https://registry.npmjs.org/vite-plugin-node-polyfills/-/vite-plugin-node-polyfills-0.21.0.tgz", - "integrity": "sha512-Sk4DiKnmxN8E0vhgEhzLudfJQfaT8k4/gJ25xvUPG54KjLJ6HAmDKbr4rzDD/QWEY+Lwg80KE85fGYBQihEPQA==", + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/vite-plugin-node-polyfills/-/vite-plugin-node-polyfills-0.22.0.tgz", + "integrity": "sha512-F+G3LjiGbG8QpbH9bZ//GSBr9i1InSTkaulfUHFa9jkLqVGORFBoqc2A/Yu5Mmh1kNAbiAeKeK+6aaQUf3x0JA==", "dev": true, "dependencies": { "@rollup/plugin-inject": "^5.0.5", @@ -7128,31 +7187,29 @@ } }, "node_modules/vitest": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.2.2.tgz", - "integrity": "sha512-d5Ouvrnms3GD9USIK36KG8OZ5bEvKEkITFtnGv56HFaSlbItJuYr7hv2Lkn903+AvRAgSixiamozUVfORUekjw==", - "dev": true, - "dependencies": { - "@vitest/expect": "1.2.2", - "@vitest/runner": "1.2.2", - "@vitest/snapshot": "1.2.2", - "@vitest/spy": "1.2.2", - "@vitest/utils": "1.2.2", - "acorn-walk": "^8.3.2", - "cac": "^6.7.14", - "chai": "^4.3.10", - "debug": "^4.3.4", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.0.2.tgz", + "integrity": "sha512-WlpZ9neRIjNBIOQwBYfBSr0+of5ZCbxT2TVGKW4Lv0c8+srCFIiRdsP7U009t8mMn821HQ4XKgkx5dVWpyoyLw==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "@vitest/expect": "2.0.2", + "@vitest/pretty-format": "^2.0.2", + "@vitest/runner": "2.0.2", + "@vitest/snapshot": "2.0.2", + "@vitest/spy": "2.0.2", + "@vitest/utils": "2.0.2", + "chai": "^5.1.1", + "debug": "^4.3.5", "execa": "^8.0.1", - "local-pkg": "^0.5.0", - "magic-string": "^0.30.5", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", - "std-env": "^3.5.0", - "strip-literal": "^1.3.0", - "tinybench": "^2.5.1", - "tinypool": "^0.8.2", + "magic-string": "^0.30.10", + "pathe": "^1.1.2", + "std-env": "^3.7.0", + "tinybench": "^2.8.0", + "tinypool": "^1.0.0", + "tinyrainbow": "^1.2.0", "vite": "^5.0.0", - "vite-node": "1.2.2", + "vite-node": "2.0.2", "why-is-node-running": "^2.2.2" }, "bin": { @@ -7167,8 +7224,8 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "^1.0.0", - "@vitest/ui": "^1.0.0", + "@vitest/browser": "2.0.2", + "@vitest/ui": "2.0.2", "happy-dom": "*", "jsdom": "*" }, @@ -7199,15 +7256,15 @@ "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==" }, "node_modules/vue": { - "version": "3.4.19", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.19.tgz", - "integrity": "sha512-W/7Fc9KUkajFU8dBeDluM4sRGc/aa4YJnOYck8dkjgZoXtVsn3OeTGni66FV1l3+nvPA7VBFYtPioaGKUmEADw==", + "version": "3.4.31", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.31.tgz", + "integrity": "sha512-njqRrOy7W3YLAlVqSKpBebtZpDVg21FPoaq1I7f/+qqBThK9ChAIjkRWgeP6Eat+8C+iia4P3OYqpATP21BCoQ==", "dependencies": { - "@vue/compiler-dom": "3.4.19", - "@vue/compiler-sfc": "3.4.19", - "@vue/runtime-dom": "3.4.19", - "@vue/server-renderer": "3.4.19", - "@vue/shared": "3.4.19" + "@vue/compiler-dom": "3.4.31", + "@vue/compiler-sfc": "3.4.31", + "@vue/runtime-dom": "3.4.31", + "@vue/server-renderer": "3.4.31", + "@vue/shared": "3.4.31" }, "peerDependencies": { "typescript": "*" @@ -7219,15 +7276,15 @@ } }, "node_modules/vue-component-type-helpers": { - "version": "1.8.27", - "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-1.8.27.tgz", - "integrity": "sha512-0vOfAtI67UjeO1G6UiX5Kd76CqaQ67wrRZiOe7UAb9Jm6GzlUr/fC7CV90XfwapJRjpCMaZFhv1V0ajWRmE9Dg==", + "version": "2.0.26", + "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-2.0.26.tgz", + "integrity": "sha512-sO9qQ8oC520SW6kqlls0iqDak53gsTVSrYylajgjmkt1c0vcgjsGSy1KzlDrbEx8pm02IEYhlUkU5hCYf8rwtg==", "dev": true }, "node_modules/vue-eslint-parser": { - "version": "9.4.2", - "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.2.tgz", - "integrity": "sha512-Ry9oiGmCAK91HrKMtCrKFWmSFWvYkpGglCeFAIqDdr9zdXmMMpJOmUJS7WWsW7fX81h6mwHmUZCQQ1E0PkSwYQ==", + "version": "9.4.3", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz", + "integrity": "sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==", "dev": true, "dependencies": { "debug": "^4.3.4", @@ -7248,12 +7305,57 @@ "eslint": ">=6.0.0" } }, + "node_modules/vue-eslint-parser/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/vue-eslint-parser/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/vue-eslint-parser/node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/vue-router": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.2.5.tgz", - "integrity": "sha512-DIUpKcyg4+PTQKfFPX88UWhlagBEBEfJ5A8XDXRJLUnZOvcpMF8o/dnL90vpVkGaPbjvXazV/rC1qBKrZlFugw==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.4.0.tgz", + "integrity": "sha512-HB+t2p611aIZraV2aPSRNXf0Z/oLZFrlygJm+sZbdJaW6lcFqEDQwnzUBXn+DApw+/QzDU/I9TeWx9izEjTmsA==", "dependencies": { - "@vue/devtools-api": "^6.5.0" + "@vue/devtools-api": "^6.5.1" }, "funding": { "url": "https://github.com/sponsors/posva" @@ -7342,15 +7444,15 @@ } }, "node_modules/which-typed-array": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.14.tgz", - "integrity": "sha512-VnXFiIW8yNn9kIHN88xvZ4yOWchftKDsRJ8fEPacX/wl1lOvBrhsJ/OeJCXq7B0AaijRuqgzSKalJoPk+D8MPg==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", "dependencies": { - "available-typed-arrays": "^1.0.6", - "call-bind": "^1.0.5", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-tostringtag": "^1.0.1" + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -7360,9 +7462,9 @@ } }, "node_modules/why-is-node-running": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", - "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", "dev": true, "dependencies": { "siginfo": "^2.0.0", @@ -7375,34 +7477,43 @@ "node": ">=8" } }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/worker-timers": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/worker-timers/-/worker-timers-7.1.1.tgz", - "integrity": "sha512-axtq83GwPqYwkQmQmei2abQ9cT7oSwmLw4lQCZ9VmMH9g4t4kuEF1Gw+tdnIJGHCiZ2QPDnr/+307bYx6tynLA==", + "version": "7.1.8", + "resolved": "https://registry.npmjs.org/worker-timers/-/worker-timers-7.1.8.tgz", + "integrity": "sha512-R54psRKYVLuzff7c1OTFcq/4Hue5Vlz4bFtNEIarpSiCYhpifHU3aIQI29S84o1j87ePCYqbmEJPqwBTf+3sfw==", "dependencies": { - "@babel/runtime": "^7.23.8", + "@babel/runtime": "^7.24.5", "tslib": "^2.6.2", - "worker-timers-broker": "^6.1.1", - "worker-timers-worker": "^7.0.65" + "worker-timers-broker": "^6.1.8", + "worker-timers-worker": "^7.0.71" } }, "node_modules/worker-timers-broker": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/worker-timers-broker/-/worker-timers-broker-6.1.1.tgz", - "integrity": "sha512-CTlDnkXAewtYvw5gOwVIc6UuIPcNHJrqWxBMhZbCWOmadvl20nPs9beAsXlaTEwW3G2KBpuKiSgkhBkhl3mxDA==", + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/worker-timers-broker/-/worker-timers-broker-6.1.8.tgz", + "integrity": "sha512-FUCJu9jlK3A8WqLTKXM9E6kAmI/dR1vAJ8dHYLMisLNB/n3GuaFIjJ7pn16ZcD1zCOf7P6H62lWIEBi+yz/zQQ==", "dependencies": { - "@babel/runtime": "^7.23.8", + "@babel/runtime": "^7.24.5", "fast-unique-numbers": "^8.0.13", "tslib": "^2.6.2", - "worker-timers-worker": "^7.0.65" + "worker-timers-worker": "^7.0.71" } }, "node_modules/worker-timers-worker": { - "version": "7.0.65", - "resolved": "https://registry.npmjs.org/worker-timers-worker/-/worker-timers-worker-7.0.65.tgz", - "integrity": "sha512-Dl4nGONr8A8Fr+vQnH7Ee+o2iB480S1fBcyJYqnMyMwGRVyQZLZU+o91vbMvU1vHqiryRQmjXzzMYlh86wx+YQ==", + "version": "7.0.71", + "resolved": "https://registry.npmjs.org/worker-timers-worker/-/worker-timers-worker-7.0.71.tgz", + "integrity": "sha512-ks/5YKwZsto1c2vmljroppOKCivB/ma97g9y77MAAz2TBBjPPgpoOiS1qYQKIgvGTr2QYPT3XhJWIB6Rj2MVPQ==", "dependencies": { - "@babel/runtime": "^7.23.8", + "@babel/runtime": "^7.24.5", "tslib": "^2.6.2" } }, @@ -7500,16 +7611,10 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, "node_modules/ws": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", - "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "engines": { "node": ">=10.0.0" }, @@ -7549,12 +7654,6 @@ "node": ">=0.4" } }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/packages/modules/display_themes/cards/source/package.json b/packages/modules/display_themes/cards/source/package.json index 6bd4b7fe7a..17352cc6bd 100644 --- a/packages/modules/display_themes/cards/source/package.json +++ b/packages/modules/display_themes/cards/source/package.json @@ -8,38 +8,38 @@ "build": "vite build --out-dir=../web/ --emptyOutDir", "preview": "vite preview --host", "test:unit": "vitest run --environment jsdom --root src/", - "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore", + "lint": "eslint . --fix", "build-dev": "vite build --mode=development --out-dir=../web/ --emptyOutDir" }, "dependencies": { - "@fortawesome/fontawesome-svg-core": "^6.2.1", - "@fortawesome/free-regular-svg-icons": "^6.2.1", - "@fortawesome/free-solid-svg-icons": "^6.2.1", - "@fortawesome/vue-fontawesome": "^3.0.2", - "@inkline/inkline": "^3.2.0", + "@fortawesome/fontawesome-svg-core": "^6.5.2", + "@fortawesome/free-regular-svg-icons": "^6.5.2", + "@fortawesome/free-solid-svg-icons": "^6.5.2", + "@fortawesome/vue-fontawesome": "^3.0.8", + "@inkline/inkline": "^3.2.2", "buffer": "^6.0.3", "events": "^3.3.0", - "mqtt": "^5.3.3", + "mqtt": "^5.8.0", "node-stdlib-browser": "^1.2.0", - "pinia": "^2.0.28", - "vue": "^3.4.19", - "vue-router": "^4.1.6" + "pinia": "^2.1.7", + "vue": "^3.4.31", + "vue-router": "^4.4.0" }, "devDependencies": { - "@rushstack/eslint-patch": "^1.1.4", - "@vitejs/plugin-vue": "^5.0.4", + "@rushstack/eslint-patch": "^1.10.3", + "@vitejs/plugin-vue": "^5.0.5", "@vue/eslint-config-prettier": "^9.0.0", - "@vue/test-utils": "^2.2.4", - "eslint": "^8.30.0", - "eslint-plugin-vue": "^9.3.0", - "jsdom": "^24.0.0", - "postcss": "^8.4.35", - "postcss-preset-env": "^9.0.0", - "prettier": "^3.1.1", + "@vue/test-utils": "^2.4.6", + "eslint": "^9.6.0", + "eslint-plugin-vue": "^9.27.0", + "jsdom": "^24.1.0", + "postcss": "^8.4.39", + "postcss-preset-env": "^9.6.0", + "prettier": "^3.3.2", "rollup-plugin-polyfill-node": "^0.13.0", - "sass": "^1.57.1", - "vite": "^5.0.12", - "vite-plugin-node-polyfills": "^0.21.0", - "vitest": "^1.0.4" + "sass": "^1.77.7", + "vite": "^5.3.3", + "vite-plugin-node-polyfills": "^0.22.0", + "vitest": "^2.0.2" } } diff --git a/packages/modules/display_themes/cards/source/public/icons/icon_Data.md b/packages/modules/display_themes/cards/source/public/icons/icon_Data.md new file mode 100644 index 0000000000..e0977a0983 --- /dev/null +++ b/packages/modules/display_themes/cards/source/public/icons/icon_Data.md @@ -0,0 +1,3 @@ + +Herkunft der Icons: +https://www.veryicon.com/ \ No newline at end of file diff --git a/packages/modules/display_themes/cards/source/public/icons/owbBattery.svg b/packages/modules/display_themes/cards/source/public/icons/owbBattery.svg new file mode 100644 index 0000000000..dd1fc748d3 --- /dev/null +++ b/packages/modules/display_themes/cards/source/public/icons/owbBattery.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/modules/display_themes/cards/source/public/icons/owbChargePoint.svg b/packages/modules/display_themes/cards/source/public/icons/owbChargePoint.svg new file mode 100644 index 0000000000..29cca3f93c --- /dev/null +++ b/packages/modules/display_themes/cards/source/public/icons/owbChargePoint.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/modules/display_themes/cards/source/public/icons/owbGrid.svg b/packages/modules/display_themes/cards/source/public/icons/owbGrid.svg new file mode 100644 index 0000000000..e098048efa --- /dev/null +++ b/packages/modules/display_themes/cards/source/public/icons/owbGrid.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/modules/display_themes/cards/source/public/icons/owbHouse.svg b/packages/modules/display_themes/cards/source/public/icons/owbHouse.svg new file mode 100644 index 0000000000..412dc8613b --- /dev/null +++ b/packages/modules/display_themes/cards/source/public/icons/owbHouse.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/modules/display_themes/cards/source/public/icons/owbPV.svg b/packages/modules/display_themes/cards/source/public/icons/owbPV.svg new file mode 100644 index 0000000000..becf88bc97 --- /dev/null +++ b/packages/modules/display_themes/cards/source/public/icons/owbPV.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/modules/display_themes/cards/source/public/icons/owbVehicle.svg b/packages/modules/display_themes/cards/source/public/icons/owbVehicle.svg new file mode 100644 index 0000000000..e721de12f4 --- /dev/null +++ b/packages/modules/display_themes/cards/source/public/icons/owbVehicle.svg @@ -0,0 +1,39 @@ + + + + + + diff --git a/packages/modules/display_themes/cards/source/src/App.vue b/packages/modules/display_themes/cards/source/src/App.vue index 6b8c375c93..92f135b11f 100644 --- a/packages/modules/display_themes/cards/source/src/App.vue +++ b/packages/modules/display_themes/cards/source/src/App.vue @@ -9,7 +9,7 @@ import LockNavItem from "@/components/LockNavItem.vue"; import { useMqttStore } from "@/stores/mqtt.js"; export default { - name: "openwbDisplayCardsApp", + name: "OpenwbDisplayCardsApp", components: { RouterView, DateTime, @@ -41,7 +41,6 @@ export default { "openWB/chargepoint/+/get/plug_state", "openWB/chargepoint/+/get/power", "openWB/chargepoint/+/get/rfid", - "openWB/chargepoint/+/set/change_ev_permitted", "openWB/chargepoint/+/set/current", "openWB/chargepoint/+/set/manual_lock", "openWB/chargepoint/+/set/log", @@ -76,6 +75,30 @@ export default { ); }, }, + created() { + this.createConnection(); + }, + mounted() { + // parse and add url parameters to store + let uri = window.location.search; + if (uri != "") { + console.debug("search", uri); + let params = new URLSearchParams(uri); + params.forEach((value, key) => { + this.mqttStore.updateSetting(key, parseInt(value)); + }); + } + // subscribe our topics + this.doSubscribe(this.mqttTopicsToSubscribe); + // timer for chart data + this.chartInterval = setInterval(this.mqttStore.updateChartData, 5000); + }, + beforeUnmount() { + // unsubscribe our topics + this.doUnsubscribe(this.mqttTopicsToSubscribe); + // clear timer for chart data + clearInterval(this.chartInterval); + }, methods: { /** * Establishes a connection to the configured broker @@ -105,7 +128,7 @@ export default { try { myPayload = JSON.parse(message.toString()); } catch (error) { - console.debug("Json parsing failed, fallback to string: ", topic); + console.debug("Json parsing failed, fallback to string: ", topic, error); myPayload = message.toString(); } this.mqttStore.addTopic(topic, myPayload); @@ -197,37 +220,16 @@ export default { }); }, }, - created() { - this.createConnection(); - }, - mounted() { - // parse and add url parameters to store - let uri = window.location.search; - if (uri != "") { - console.debug("search", uri); - let params = new URLSearchParams(uri); - params.forEach((value, key) => { - this.mqttStore.updateSetting(key, parseInt(value)); - }); - } - // subscribe our topics - this.doSubscribe(this.mqttTopicsToSubscribe); - // timer for chart data - this.chartInterval = setInterval(this.mqttStore.updateChartData, 5000); - }, - beforeUnmount() { - // unsubscribe our topics - this.doUnsubscribe(this.mqttTopicsToSubscribe); - // clear timer for chart data - clearInterval(this.chartInterval); - }, }; diff --git a/packages/modules/display_themes/cards/source/src/components/ChargePointCodeButton.vue b/packages/modules/display_themes/cards/source/src/components/ChargePointCodeButton.vue index a5c8743b94..77fb540fae 100644 --- a/packages/modules/display_themes/cards/source/src/components/ChargePointCodeButton.vue +++ b/packages/modules/display_themes/cards/source/src/components/ChargePointCodeButton.vue @@ -11,6 +11,10 @@ import CodeInputModal from "./CodeInputModal.vue"; export default { name: "ChargePointCodeButton", + components: { + FontAwesomeIcon, + CodeInputModal, + }, props: { chargePointId: { type: Number, required: true }, }, @@ -22,10 +26,6 @@ export default { code: "", }; }, - components: { - FontAwesomeIcon, - CodeInputModal, - }, computed: { tagState() { return this.mqttStore.getChargepointTagState(this.chargePointId); @@ -68,11 +68,11 @@ export default { diff --git a/packages/modules/display_themes/cards/source/src/components/ChargePointLockButton.vue b/packages/modules/display_themes/cards/source/src/components/ChargePointLockButton.vue index d9bedbf0c2..2d9fc3f86a 100644 --- a/packages/modules/display_themes/cards/source/src/components/ChargePointLockButton.vue +++ b/packages/modules/display_themes/cards/source/src/components/ChargePointLockButton.vue @@ -13,6 +13,7 @@ library.add(fasLock, fasLockOpen); export default { name: "ChargePointLockButton", + components: { FontAwesomeIcon }, props: { chargePointId: { required: true, type: Number }, changesLocked: { required: false, type: Boolean, default: false }, @@ -22,7 +23,6 @@ export default { mqttStore: useMqttStore(), }; }, - components: { FontAwesomeIcon }, computed: { locked() { return this.mqttStore.getChargePointManualLock(this.chargePointId); @@ -56,7 +56,11 @@ export default {